Repository: nats-io/nats-server
Pull request #1862 (2021-01-29 02:35:00)

[added] field to varz output containing the operator jwt
Signed-off-by: Matthias Hanel <mh@synadia.com>



Patch:
diff --git a/server/jwt.go b/server/jwt.go
index 49efd4ec..38a10c22 100644
--- a/server/jwt.go
+++ b/server/jwt.go
@@ -32,34 +32,39 @@ const jwtPrefix = "eyJ"
 
 // ReadOperatorJWT will read a jwt file for an operator claim. This can be a decorated file.
 func ReadOperatorJWT(jwtfile string) (*jwt.OperatorClaims, error) {
+	_, claim, err := readOperatorJWT(jwtfile)
+	return claim, err
+}
+
+func readOperatorJWT(jwtfile string) (string, *jwt.OperatorClaims, error) {
 	contents, err := ioutil.ReadFile(jwtfile)
 	if err != nil {
 		// Check to see if the JWT has been inlined.
 		if !strings.HasPrefix(jwtfile, jwtPrefix) {
-			return nil, err
+			return "", nil, err
 		}
 		// We may have an inline jwt here.
 		contents = []byte(jwtfile)
 	}
 	defer wipeSlice(contents)
 
-	var claim string
+	var theJWT string
 	items := nscDecoratedRe.FindAllSubmatch(contents, -1)
 	if len(items) == 0 {
-		claim = string(contents)
+		theJWT = string(contents)
 	} else {
 		// First result should be the JWT.
 		// We copy here so that if the file contained a seed file too we wipe appropriately.
 		raw := items[0][1]
 		tmp := make([]byte, len(raw))
 		copy(tmp, raw)
-		claim = string(tmp)
+		theJWT = string(tmp)
 	}
-	opc, err := jwt.DecodeOperatorClaims(claim)
+	opc, err := jwt.DecodeOperatorClaims(theJWT)
 	if err != nil {
-		return nil, err
+		return "", nil, err
 	}
-	return opc, nil
+	return theJWT, opc, nil
 }
 
 // Just wipe slice with 'x', for clearing contents of nkey seed file.
diff --git a/server/monitor.go b/server/monitor.go
index 06c3ce09..4bbeb70a 100644
--- a/server/monitor.go
+++ b/server/monitor.go
@@ -992,59 +992,61 @@ func (s *Server) HandleStacksz(w http.ResponseWriter, r *http.Request) {
 
 // Varz will output server information on the monitoring port at /varz.
 type Varz struct {
-	ID                string            `json:"server_id"`
-	Name              string            `json:"server_name"`
-	Version           string            `json:"version"`
-	Proto             int               `json:"proto"`
-	GitCommit         string            `json:"git_commit,omitempty"`
-	GoVersion         string            `json:"go"`
-	Host              string            `json:"host"`
-	Port              int               `json:"port"`
-	AuthRequired      bool              `json:"auth_required,omitempty"`
-	TLSRequired       bool              `json:"tls_required,omitempty"`
-	TLSVerify         bool              `json:"tls_verify,omitempty"`
-	IP                string            `json:"ip,omitempty"`
-	ClientConnectURLs []string          `json:"connect_urls,omitempty"`
-	WSConnectURLs     []string          `json:"ws_connect_urls,omitempty"`
-	MaxConn           int               `json:"max_connections"`
-	MaxSubs           int               `json:"max_subscriptions,omitempty"`
-	PingInterval      time.Duration     `json:"ping_interval"`
-	MaxPingsOut       int               `json:"ping_max"`
-	HTTPHost          string            `json:"http_host"`
-	HTTPPort          int               `json:"http_port"`
-	HTTPBasePath      string            `json:"http_base_path"`
-	HTTPSPort         int               `json:"https_port"`
-	AuthTimeout       float64           `json:"auth_timeout"`
-	MaxControlLine    int32             `json:"max_control_line"`
-	MaxPayload        int               `json:"max_payload"`
-	MaxPending        int64             `json:"max_pending"`
-	Cluster           ClusterOptsVarz   `json:"cluster,omitempty"`
-	Gateway           GatewayOptsVarz   `json:"gateway,omitempty"`
-	LeafNode          LeafNodeOptsVarz  `json:"leaf,omitempty"`
-	JetStream         JetStreamVarz     `json:"jetstream,omitempty"`
-	TLSTimeout        float64           `json:"tls_timeout"`
-	WriteDeadline     time.Duration     `json:"write_deadline"`
-	Start             time.Time         `json:"start"`
-	Now               time.Time         `json:"now"`
-	Uptime            string            `json:"uptime"`
-	Mem               int64             `json:"mem"`
-	Cores             int               `json:"cores"`
-	MaxProcs          int               `json:"gomaxprocs"`
-	CPU               float64           `json:"cpu"`
-	Connections       int               `json:"connections"`
-	TotalConnections  uint64            `json:"total_connections"`
-	Routes            int               `json:"routes"`
-	Remotes           int               `json:"remotes"`
-	Leafs             int               `json:"leafnodes"`
-	InMsgs            int64             `json:"in_msgs"`
-	OutMsgs           int64             `json:"out_msgs"`
-	InBytes           int64             `json:"in_bytes"`
-	OutBytes          int64             `json:"out_bytes"`
-	SlowConsumers     int64             `json:"slow_consumers"`
-	Subscriptions     uint32            `json:"subscriptions"`
-	HTTPReqStats      map[string]uint64 `json:"http_req_stats"`
-	ConfigLoadTime    time.Time         `json:"config_load_time"`
-	Tags              jwt.TagList       `json:"tags,omitempty"`
+	ID                    string                `json:"server_id"`
+	Name                  string                `json:"server_name"`
+	Version               string                `json:"version"`
+	Proto                 int                   `json:"proto"`
+	GitCommit             string                `json:"git_commit,omitempty"`
+	GoVersion             string                `json:"go"`
+	Host                  string                `json:"host"`
+	Port                  int                   `json:"port"`
+	AuthRequired          bool                  `json:"auth_required,omitempty"`
+	TLSRequired           bool                  `json:"tls_required,omitempty"`
+	TLSVerify             bool                  `json:"tls_verify,omitempty"`
+	IP                    string                `json:"ip,omitempty"`
+	ClientConnectURLs     []string              `json:"connect_urls,omitempty"`
+	WSConnectURLs         []string              `json:"ws_connect_urls,omitempty"`
+	MaxConn               int                   `json:"max_connections"`
+	MaxSubs               int                   `json:"max_subscriptions,omitempty"`
+	PingInterval          time.Duration         `json:"ping_interval"`
+	MaxPingsOut           int                   `json:"ping_max"`
+	HTTPHost              string                `json:"http_host"`
+	HTTPPort              int                   `json:"http_port"`
+	HTTPBasePath          string                `json:"http_base_path"`
+	HTTPSPort             int                   `json:"https_port"`
+	AuthTimeout           float64               `json:"auth_timeout"`
+	MaxControlLine        int32                 `json:"max_control_line"`
+	MaxPayload            int                   `json:"max_payload"`
+	MaxPending            int64                 `json:"max_pending"`
+	Cluster               ClusterOptsVarz       `json:"cluster,omitempty"`
+	Gateway               GatewayOptsVarz       `json:"gateway,omitempty"`
+	LeafNode              LeafNodeOptsVarz      `json:"leaf,omitempty"`
+	JetStream             JetStreamVarz         `json:"jetstream,omitempty"`
+	TLSTimeout            float64               `json:"tls_timeout"`
+	WriteDeadline         time.Duration         `json:"write_deadline"`
+	Start                 time.Time             `json:"start"`
+	Now                   time.Time             `json:"now"`
+	Uptime                string                `json:"uptime"`
+	Mem                   int64                 `json:"mem"`
+	Cores                 int                   `json:"cores"`
+	MaxProcs              int                   `json:"gomaxprocs"`
+	CPU                   float64               `json:"cpu"`
+	Connections           int                   `json:"connections"`
+	TotalConnections      uint64                `json:"total_connections"`
+	Routes                int                   `json:"routes"`
+	Remotes               int                   `json:"remotes"`
+	Leafs                 int                   `json:"leafnodes"`
+	InMsgs                int64                 `json:"in_msgs"`
+	OutMsgs               int64                 `json:"out_msgs"`
+	InBytes               int64                 `json:"in_bytes"`
+	OutBytes              int64                 `json:"out_bytes"`
+	SlowConsumers         int64                 `json:"slow_consumers"`
+	Subscriptions         uint32                `json:"subscriptions"`
+	HTTPReqStats          map[string]uint64     `json:"http_req_stats"`
+	ConfigLoadTime        time.Time             `json:"config_load_time"`
+	Tags                  jwt.TagList           `json:"tags,omitempty"`
+	TrustedOperatorsJwt   []string              `json:"trusted_operators_jwt,omitempty"`
+	TrustedOperatorsClaim []*jwt.OperatorClaims `json:"trusted_operators_claim,omitempty"`
 }
 
 // JetStreamVarz contains basic runtime information about jetstream
@@ -1251,11 +1253,13 @@ func (s *Server) createVarz(pcpu float64, rss int64) *Varz {
 			TLSVerify:   leafTlsVerify,
 			Remotes:     []RemoteLeafOptsVarz{},
 		},
-		Start:    s.start,
-		MaxSubs:  opts.MaxSubs,
-		Cores:    numCores,
-		MaxProcs: maxProcs,
-		Tags:     opts.Tags,
+		Start:                 s.start,
+		MaxSubs:               opts.MaxSubs,
+		Cores:                 numCores,
+		MaxProcs:              maxProcs,
+		Tags:                  opts.Tags,
+		TrustedOperatorsJwt:   opts.operatorJWT,
+		TrustedOperatorsClaim: opts.TrustedOperators,
 	}
 	if len(opts.Routes) > 0 {
 		varz.Cluster.URLs = urlsToStrings(opts.Routes)
@@ -1281,7 +1285,6 @@ func (s *Server) createVarz(pcpu float64, rss int64) *Varz {
 		}
 		varz.LeafNode.Remotes = rlna
 	}
-
 	if s.js != nil {
 		s.js.mu.RLock()
 		varz.JetStream = JetStreamVarz{
diff --git a/server/opts.go b/server/opts.go
index 17f6a74f..ee3e9a77 100644
--- a/server/opts.go
+++ b/server/opts.go
@@ -229,7 +229,6 @@ type Options struct {
 	TrustedOperators         []*jwt.OperatorClaims `json:"-"`
 	AccountResolver          AccountResolver       `json:"-"`
 	AccountResolverTLSConfig *tls.Config           `json:"-"`
-	resolverPreloads         map[string]string
 
 	CustomClientAuthentication Authentication `json:"-"`
 	CustomRouterAuthentication Authentication `json:"-"`
@@ -256,6 +255,10 @@ type Options struct {
 	inConfig  map[string]bool
 	inCmdLine map[string]bool
 
+	// private fields for operator mode
+	operatorJWT      []string
+	resolverPreloads map[string]string
+
 	// private fields, used for testing
 	gatewaysSolicitDelay time.Duration
 	routeProto           int
@@ -853,12 +856,13 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
 		// Assume for now these are file names, but they can also be the JWT itself inline.
 		o.TrustedOperators = make([]*jwt.OperatorClaims, 0, len(opFiles))
 		for _, fname := range opFiles {
-			opc, err := ReadOperatorJWT(fname)
+			theJWT, opc, err := readOperatorJWT(fname)
 			if err != nil {
 				err := &configErr{tk, fmt.Sprintf("error parsing operator JWT: %v", err)}
 				*errors = append(*errors, err)
 				continue
 			}
+			o.operatorJWT = append(o.operatorJWT, theJWT)
 			o.TrustedOperators = append(o.TrustedOperators, opc)
 		}
 		if len(o.TrustedOperators) == 1 {


Test patch:
diff --git a/server/monitor_test.go b/server/monitor_test.go
index 69d8a5d1..a0512988 100644
--- a/server/monitor_test.go
+++ b/server/monitor_test.go
@@ -3495,6 +3495,43 @@ func pollLeafz(t *testing.T, s *Server, mode int, url string, opts *LeafzOptions
 	return l
 }
 
+func TestMonitorOpJWT(t *testing.T) {
+	content := `
+	listen: "127.0.0.1:-1"
+	http: "127.0.0.1:-1"
+	operator = "../test/configs/nkeys/op.jwt"
+	resolver = MEMORY
+	`
+	conf := createConfFile(t, []byte(content))
+	defer os.Remove(conf)
+	sa, _ := RunServerWithConfig(conf)
+	defer sa.Shutdown()
+
+	theJWT, err := ioutil.ReadFile("../test/configs/nkeys/op.jwt")
+	require_NoError(t, err)
+	theJWT = []byte(strings.Split(string(theJWT), "\n")[1])
+	claim, err := jwt.DecodeOperatorClaims(string(theJWT))
+	require_NoError(t, err)
+
+	pollURL := fmt.Sprintf("http://127.0.0.1:%d/varz", sa.MonitorAddr().Port)
+	for pollMode := 1; pollMode < 2; pollMode++ {
+		l := pollVarz(t, sa, pollMode, pollURL, nil)
+
+		if len(l.TrustedOperatorsJwt) != 1 {
+			t.Fatalf("Expected one operator jwt")
+		}
+		if len(l.TrustedOperatorsClaim) != 1 {
+			t.Fatalf("Expected one operator claim")
+		}
+		if l.TrustedOperatorsJwt[0] != string(theJWT) {
+			t.Fatalf("Expected operator to be identical to configuration")
+		}
+		if !reflect.DeepEqual(l.TrustedOperatorsClaim[0], claim) {
+			t.Fatal("claims need to be equal")
+		}
+	}
+}
+
 func TestMonitorLeafz(t *testing.T) {
 	content := `
 	listen: "127.0.0.1:-1"