Repository: nats-io/nsc
Pull request #352 (2021-01-26 01:11:09)

[added] operator edit  option to require signing keys in operator jwt
If on edit, accounts or users violating this rule exist the operation
fails.
Also generating system account with a signing key

Signed-off-by: Matthias Hanel <mh@synadia.com>


Patch:
diff --git a/cmd/addaccount.go b/cmd/addaccount.go
index 0aa7b63..bca28ce 100644
--- a/cmd/addaccount.go
+++ b/cmd/addaccount.go
@@ -151,7 +151,9 @@ func (p *AddAccountParams) validSigners(ctx ActionCtx) ([]string, error) {
 		return nil, err
 	}
 	var signers []string
-	signers = append(signers, oc.Subject)
+	if !oc.StrictSigningKeyUsage {
+		signers = append(signers, oc.Subject)
+	}
 	signers = append(signers, oc.SigningKeys...)
 	if ctx.StoreCtx().Store.IsManaged() && p.akp != nil {
 		pk, err := p.akp.PublicKey()
diff --git a/cmd/addoperator.go b/cmd/addoperator.go
index 74e5803..cc1c2e5 100644
--- a/cmd/addoperator.go
+++ b/cmd/addoperator.go
@@ -44,7 +44,8 @@ func createAddOperatorCmd() *cobra.Command {
 	}
 	cmd.Flags().StringVarP(&params.name, "name", "n", "", "operator name")
 	cmd.Flags().StringVarP(&params.jwtPath, "url", "u", "", "import from a jwt server url, file, or well known operator")
-	cmd.Flags().BoolVarP(&params.sysAcc, "sys", "s", false, "generate system account with the operator")
+	cmd.Flags().BoolVarP(&params.genSk, "generate-signing-key", "", false, "generate a signing key with the operator")
+	cmd.Flags().BoolVarP(&params.sysAcc, "sys", "s", false, "generate system account with the operator (if specified will be signed with signing key)")
 	cmd.Flags().BoolVarP(&params.force, "force", "", false, "on import, overwrite existing when already present")
 	params.TimeParams.BindFlags(cmd)
 
@@ -69,6 +70,7 @@ type AddOperatorParams struct {
 	generate bool
 	sysAcc   bool
 	force    bool
+	genSk    bool
 	keyPath  string
 }
 
@@ -122,6 +124,9 @@ func (p *AddOperatorParams) PreInteractive(ctx ActionCtx) error {
 		if p.sysAcc, err = cli.Confirm("Generate system account?", true); err != nil {
 			return err
 		}
+		if p.genSk, err = cli.Confirm("Generate signing key?", true); err != nil {
+			return err
+		}
 	}
 
 	return nil
@@ -252,6 +257,10 @@ func (p *AddOperatorParams) Validate(ctx ActionCtx) error {
 		if p.keyPath, err = ctx.StoreCtx().KeyStore.Store(p.signerKP); err != nil {
 			return err
 		}
+	} else {
+		if p.genSk {
+			return fmt.Errorf("signing key can not be added when importing the operator")
+		}
 	}
 
 	if p.keyPath != "" {
@@ -272,7 +281,7 @@ func (p *AddOperatorParams) Validate(ctx ActionCtx) error {
 	return nil
 }
 
-func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
+func (p *AddOperatorParams) Run(ctx ActionCtx) (store.Status, error) {
 	r := store.NewDetailedReport(false)
 	operator := &store.NamedKey{Name: p.name, KP: p.signerKP}
 	s, err := GetConfig().LoadStore(p.name)
@@ -288,6 +297,7 @@ func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
 
 	var sAcc *keys
 	var sUsr *keys
+	var skPub string
 
 	if p.token == "" {
 		ctx, err := s.GetContext()
@@ -300,7 +310,6 @@ func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
 				return nil, err
 			}
 		}
-
 		oc, err := ctx.Store.ReadOperatorClaim()
 		if err != nil {
 			return nil, err
@@ -317,9 +326,23 @@ func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
 				return nil, err
 			}
 		}
-
+		sysAccSigner := p.signerKP
+		if p.genSk {
+			sysAccSigner, err = nkeys.CreateOperator()
+			if err != nil {
+				return nil, err
+			}
+			if _, err := ctx.KeyStore.Store(sysAccSigner); err != nil {
+				return nil, err
+			}
+			skPub, err = sysAccSigner.PublicKey()
+			if err != nil {
+				return nil, err
+			}
+			oc.SigningKeys.Add(skPub)
+		}
 		if p.sysAcc {
-			if sAcc, sUsr, err = createSystemAccount(ctx, p.signerKP); err != nil {
+			if sAcc, sUsr, err = createSystemAccount(ctx, sysAccSigner); err != nil {
 				return nil, err
 			}
 			oc.SystemAccount = sAcc.PubKey
@@ -375,6 +398,9 @@ func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
 		}
 		r.AddOK("%s operator %q", verb, p.name)
 		if sAcc != nil && sUsr != nil {
+			if skPub != "" {
+				r.AddOK("created operator signing key: %s", skPub)
+			}
 			r.AddOK("created system_account: name:SYS id:%s", sAcc.PubKey)
 			r.AddOK("created system account user: name:sys id:%s", sUsr.PubKey)
 			r.AddOK("system account user creds file stored in %#q", AbbrevHomePaths(sUsr.CredsPath))
diff --git a/cmd/adduser.go b/cmd/adduser.go
index b3722d3..e238c6d 100644
--- a/cmd/adduser.go
+++ b/cmd/adduser.go
@@ -179,12 +179,16 @@ func (p *AddUserParams) Load(_ ActionCtx) error {
 }
 
 func validUserSigners(ctx ActionCtx, accName string) ([]string, error) {
+	opc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
+	if err != nil {
+		return nil, err
+	}
 	ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName)
 	if err != nil {
 		return nil, err
 	}
 	var signers []string
-	if ctx.StoreCtx().KeyStore.HasPrivateKey(ac.Subject) {
+	if !opc.StrictSigningKeyUsage && ctx.StoreCtx().KeyStore.HasPrivateKey(ac.Subject) {
 		signers = append(signers, ac.Subject)
 	}
 	for signingKey := range ac.SigningKeys {
diff --git a/cmd/common.go b/cmd/common.go
index 6de5593..1ea1761 100644
--- a/cmd/common.go
+++ b/cmd/common.go
@@ -493,7 +493,9 @@ func GetOperatorSigners(ctx ActionCtx) ([]string, error) {
 		return nil, err
 	}
 	var signers []string
-	signers = append(signers, oc.Subject)
+	if !oc.StrictSigningKeyUsage {
+		signers = append(signers, oc.Subject)
+	}
 	signers = append(signers, oc.SigningKeys...)
 	return signers, nil
 }
diff --git a/cmd/describer.go b/cmd/describer.go
index 708e9cc..62e8c23 100644
--- a/cmd/describer.go
+++ b/cmd/describer.go
@@ -454,6 +454,7 @@ func (o *OperatorDescriber) Describe() string {
 		}
 		table.AddRow("System Account", o.SystemAccount+decoration)
 	}
+	table.AddRow("Require Signing Keys", o.StrictSigningKeyUsage)
 
 	if len(o.SigningKeys) > 0 {
 		table.AddSeparator()
diff --git a/cmd/editoperator.go b/cmd/editoperator.go
index aeac018..75cfd07 100644
--- a/cmd/editoperator.go
+++ b/cmd/editoperator.go
@@ -45,6 +45,7 @@ func createEditOperatorCmd() *cobra.Command {
 	cmd.Flags().StringVarP(&params.sysAcc, "system-account", "", "", "set system account by account by public key or name")
 	cmd.Flags().StringSliceVarP(&params.serviceURLs, "service-url", "n", nil, "add an operator service url for nsc where clients can access the NATS service (only nats/tls urls supported)")
 	cmd.Flags().StringSliceVarP(&params.rmServiceURLs, "rm-service-url", "", nil, "remove an operator service url for nsc where clients can access the NATS service (only nats/tls urls supported)")
+	cmd.Flags().BoolVarP(&params.reqSk, "require-signing-keys", "", false, "require accounts/user to be signed with a signing key")
 	params.TimeParams.BindFlags(cmd)
 
 	return cmd
@@ -65,12 +66,13 @@ type EditOperatorParams struct {
 	rmServiceURLs []string
 	signingKeys   SigningKeysParams
 	rmSigningKeys []string
+	reqSk         bool
 }
 
 func (p *EditOperatorParams) SetDefaults(ctx ActionCtx) error {
 	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, false, ctx)
 
-	if !InteractiveFlag && ctx.NothingToDo("sk", "rm-sk", "start", "expiry", "tag", "rm-tag", "account-jwt-server-url", "service-url", "rm-service-url", "system-account") {
+	if !InteractiveFlag && ctx.NothingToDo("sk", "rm-sk", "start", "expiry", "tag", "rm-tag", "account-jwt-server-url", "service-url", "rm-service-url", "system-account", "require-signing-keys") {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("specify an edit option")
 	}
@@ -201,6 +203,31 @@ func (p *EditOperatorParams) Validate(ctx ActionCtx) error {
 			}
 		}
 	}
+	if p.reqSk {
+		accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts)
+		if err != nil {
+			return err
+		}
+		for _, accName := range accounts {
+			ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName)
+			if err != nil {
+				return err
+			}
+			if ac.Issuer == p.claim.Subject {
+				return fmt.Errorf("account %q needs to be issued with a signing key first", accName)
+			}
+			usrs, _ := ctx.StoreCtx().Store.ListEntries(store.Accounts, accName, store.Users)
+			for _, usrName := range usrs {
+				uc, err := ctx.StoreCtx().Store.ReadUserClaim(accName, usrName)
+				if err != nil {
+					return err
+				}
+				if uc.Issuer == ac.Subject {
+					return fmt.Errorf("user %q in account %q needs to be issued with a signing key first", usrName, accName)
+				}
+			}
+		}
+	}
 	if err = p.signingKeys.Valid(); err != nil {
 		return err
 	}
@@ -230,6 +257,8 @@ func (p *EditOperatorParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("removed signing key %q", k)
 	}
 
+	p.claim.StrictSigningKeyUsage = p.reqSk
+	r.AddOK("strict signing key usage set to: %t", p.reqSk)
 	flags := ctx.CurrentCmd().Flags()
 	p.claim.AccountServerURL = p.asu
 	if flags.Changed("account-jwt-server-url") {
diff --git a/cmd/init.go b/cmd/init.go
index d75fc9c..3338eea 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -303,6 +303,7 @@ func (p *InitCmdParams) createStore(cmd *cobra.Command) error {
 
 func createSystemAccount(s *store.Context, opKp nkeys.KeyPair) (*keys, *keys, error) {
 	var acc keys
+	var sig keys
 	var usr keys
 	var err error
 	// create system account, signed by this operator
@@ -311,14 +312,22 @@ func createSystemAccount(s *store.Context, opKp nkeys.KeyPair) (*keys, *keys, er
 	} else if acc.PubKey, err = acc.KP.PublicKey(); err != nil {
 		return nil, nil, err
 	}
+	if sig.KP, err = nkeys.CreateAccount(); err != nil {
+		return nil, nil, err
+	} else if sig.PubKey, err = sig.KP.PublicKey(); err != nil {
+		return nil, nil, err
+	}
 	sysAccClaim := jwt.NewAccountClaims(acc.PubKey)
 	sysAccClaim.Name = "SYS"
+	sysAccClaim.SigningKeys.Add(sig.PubKey)
 	if sysAccJwt, err := sysAccClaim.Encode(opKp); err != nil {
 		return nil, nil, err
 	} else if _, err := s.Store.StoreClaim([]byte(sysAccJwt)); err != nil {
 		return nil, nil, err
 	} else if acc.KeyPath, err = s.KeyStore.Store(acc.KP); err != nil {
 		return nil, nil, err
+	} else if _, err := s.KeyStore.Store(sig.KP); err != nil {
+		return nil, nil, err
 	}
 	// create system account user and creds
 	if usr.KP, err = nkeys.CreateUser(); err != nil {
@@ -328,7 +337,8 @@ func createSystemAccount(s *store.Context, opKp nkeys.KeyPair) (*keys, *keys, er
 	}
 	sysUsrClaim := jwt.NewUserClaims(usr.PubKey)
 	sysUsrClaim.Name = "sys"
-	if sysUsrJwt, err := sysUsrClaim.Encode(acc.KP); err != nil {
+	sysUsrClaim.IssuerAccount = acc.PubKey
+	if sysUsrJwt, err := sysUsrClaim.Encode(sig.KP); err != nil {
 		return nil, nil, err
 	} else if _, err := s.Store.StoreClaim([]byte(sysUsrJwt)); err != nil {
 		return nil, nil, err
diff --git a/cmd/reissueoperator.go b/cmd/reissueoperator.go
index bf6e4fc..5330109 100644
--- a/cmd/reissueoperator.go
+++ b/cmd/reissueoperator.go
@@ -88,8 +88,10 @@ func (p *reIssueOperator) Validate(ctx ActionCtx) error {
 	if store.IsManaged() {
 		return fmt.Errorf("resign is only supported in non managed stores")
 	}
-	if _, err := store.ReadOperatorClaim(); err != nil {
+	if op, err := store.ReadOperatorClaim(); err != nil {
 		return err
+	} else if op.StrictSigningKeyUsage && !p.turnIntoSigningKey && len(op.SigningKeys) == 0 {
+		return fmt.Errorf("resign of strict operator is only supported with signing keys present")
 	}
 	return nil
 }
@@ -114,6 +116,16 @@ func (p *reIssueOperator) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddError("failed to generate new operator identity: %v", err)
 		return r, err
 	}
+	accountSigningKey := opKp
+	if op.StrictSigningKeyUsage && !p.turnIntoSigningKey {
+		sp := SignerParams{kind: []nkeys.PrefixByte{nkeys.PrefixByteOperator}}
+		err = sp.Resolve(ctx)
+		if err != nil || sp.signerKP == nil {
+			r.AddError("failed to obtain signing key: %v", err)
+			return r, err
+		}
+		accountSigningKey = sp.signerKP
+	}
 	if _, err := ctx.StoreCtx().KeyStore.Store(opKp); err != nil {
 		r.AddError("failed to store new operator identity: %v", err)
 		return r, err
@@ -153,7 +165,7 @@ func (p *reIssueOperator) Run(ctx ActionCtx) (store.Status, error) {
 			r.AddOK("account %q already signed properly", acName)
 			continue
 		}
-		if accJWT, err := claim.Encode(opKp); err != nil {
+		if accJWT, err := claim.Encode(accountSigningKey); err != nil {
 			r.AddError("account %q error during encoding: %v", acName, err)
 		} else if err := s.StoreRaw([]byte(accJWT)); err != nil {
 			r.AddError("failed to store new account jwt for account %q: %v", acName, err)
diff --git a/cmd/signerparams.go b/cmd/signerparams.go
index dcab8dd..4780d34 100644
--- a/cmd/signerparams.go
+++ b/cmd/signerparams.go
@@ -134,6 +134,11 @@ func (p *SignerParams) Edit(ctx ActionCtx) error {
 func (p *SignerParams) getSigners(ctx ActionCtx) ([]string, error) {
 	sctx := ctx.StoreCtx()
 	ks := sctx.KeyStore
+	oc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
+	if err != nil {
+		return nil, err
+	}
+
 	var signers []string
 	for _, kind := range p.kind {
 		switch kind {
@@ -143,6 +148,13 @@ func (p *SignerParams) getSigners(ctx ActionCtx) ([]string, error) {
 			if err != nil {
 				return nil, err
 			}
+			if oc.StrictSigningKeyUsage {
+				if len(sgnrs) > 1 {
+					sgnrs = sgnrs[1:]
+				} else {
+					sgnrs = []string{}
+				}
+			}
 			signers = append(signers, sgnrs...)
 		case nkeys.PrefixByteAccount:
 			KeyPathFlag = ks.GetKeyPath(sctx.Account.PublicKey)
@@ -150,6 +162,17 @@ func (p *SignerParams) getSigners(ctx ActionCtx) ([]string, error) {
 			if err != nil {
 				return nil, err
 			}
+			if oc.StrictSigningKeyUsage {
+				skipId := sctx.Store.IsManaged()
+				if !skipId && len(p.kind) == 1 {
+					skipId = true
+				}
+				if len(sgnrs) > 1 {
+					sgnrs = sgnrs[1:]
+				} else {
+					sgnrs = []string{}
+				}
+			}
 			signers = append(signers, sgnrs...)
 		}
 	}
diff --git a/cmd/signingkeyseditor.go b/cmd/signingkeyseditor.go
index 99ccdf6..8aaea44 100644
--- a/cmd/signingkeyseditor.go
+++ b/cmd/signingkeyseditor.go
@@ -35,7 +35,7 @@ type SigningKeysParams struct {
 func (e *SigningKeysParams) BindFlags(flagName string, shorthand string, kind nkeys.PrefixByte, cmd *cobra.Command) {
 	e.flagName = flagName
 	e.kind = kind
-	cmd.Flags().StringSliceVarP(&e.paths, flagName, shorthand, nil, "signing key or keypath - comma separated list or option can be specified multiple times")
+	cmd.Flags().StringSliceVarP(&e.paths, flagName, shorthand, nil, `signing key or keypath or the value "generate"" to generate a key pair on the fly - comma separated list or option can be specified multiple times`)
 }
 
 func (e *SigningKeysParams) valid(s string) error {
@@ -47,6 +47,15 @@ func (e *SigningKeysParams) resolve(s string) (nkeys.KeyPair, error) {
 	if s == "" {
 		return nil, fmt.Errorf("signing key cannot be empty")
 	}
+	if s == "generate" {
+		if kp, err := nkeys.CreatePair(e.kind); err != nil {
+			return nil, err
+		} else if s, err = kp.PublicKey(); err != nil {
+			return nil, err
+		} else if _, err = store.StoreKey(kp); err != nil {
+			return nil, err
+		}
+	}
 	kp, err := store.ResolveKey(s)
 	if err != nil {
 		return nil, err
diff --git a/cmd/store/keys.go b/cmd/store/keys.go
index 87c3653..2b77b40 100644
--- a/cmd/store/keys.go
+++ b/cmd/store/keys.go
@@ -233,12 +233,12 @@ func (k *KeyStore) MaybeStoreUserCreds(account string, user string, data []byte)
 	return fp, ioutil.WriteFile(fp, data, 0600)
 }
 
-func (k *KeyStore) keypath(kp nkeys.KeyPair) (string, error) {
+func keypath(kp nkeys.KeyPair) (string, error) {
 	pk, err := kp.PublicKey()
 	if err != nil {
 		return "", err
 	}
-	return k.GetKeyPath(pk), nil
+	return GetKeyPath(pk), nil
 }
 
 func makeKeyStore(dir string) error {
@@ -255,6 +255,10 @@ func makeKeyStore(dir string) error {
 }
 
 func (k *KeyStore) GetKeyPath(pubkey string) string {
+	return GetKeyPath(pubkey)
+}
+
+func GetKeyPath(pubkey string) string {
 	if pubkey == "" {
 		return ""
 	}
@@ -264,7 +268,7 @@ func (k *KeyStore) GetKeyPath(pubkey string) string {
 }
 
 func (k *KeyStore) GetKeyPair(pubkey string) (nkeys.KeyPair, error) {
-	return k.Read(k.GetKeyPath(pubkey))
+	return k.Read(GetKeyPath(pubkey))
 }
 
 func (k *KeyStore) GetPublicKey(pubkey string) (string, error) {
@@ -300,7 +304,7 @@ func (k *KeyStore) getSeed(kp nkeys.KeyPair, err error) (string, error) {
 }
 
 func (k *KeyStore) Remove(pubkey string) error {
-	kp := k.GetKeyPath(pubkey)
+	kp := GetKeyPath(pubkey)
 	_, err := os.Stat(kp)
 	if err != nil {
 		if os.IsNotExist(err) {
@@ -347,10 +351,14 @@ func AddGitIgnore(dir string) error {
 }
 
 func (k *KeyStore) Store(kp nkeys.KeyPair) (string, error) {
+	return StoreKey(kp)
+}
+
+func StoreKey(kp nkeys.KeyPair) (string, error) {
 	if err := makeKeyStore(GetKeysDir()); err != nil {
 		return "", err
 	}
-	fp, err := k.keypath(kp)
+	fp, err := keypath(kp)
 	if err != nil {
 		return "", err
 	}
diff --git a/cmd/validate.go b/cmd/validate.go
index 4d5931a..5b0c2a4 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -249,6 +249,8 @@ func (p *ValidateCmdParams) validate(ctx ActionCtx) error {
 					p.accountValidations[v] = &jwt.ValidationResults{}
 				}
 				p.accountValidations[v].AddError("user %q is not issued by account or account signing keys", u)
+			} else if oc.StrictSigningKeyUsage && uc.Issuer == ac.Subject {
+				p.accountValidations[v].AddError("user %q is issued by account key but operator is in strict mode", u)
 			}
 		}
 	}
diff --git a/go.mod b/go.mod
index 11b70e5..801b774 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/nats-io/cliprompts/v2 v2.0.0-20191226174129-372d79b36768
 	github.com/nats-io/jwt v1.2.2
-	github.com/nats-io/jwt/v2 v2.0.0-20210107222814-18c5cc45d263
+	github.com/nats-io/jwt/v2 v2.0.0-20210129014916-f3ef7d605453
 	github.com/nats-io/nats-server/v2 v2.1.8-0.20210107160521-ceb1fda2024b
 	github.com/nats-io/nats.go v1.10.1-0.20201021145452-94be476ad6e0
 	github.com/nats-io/nkeys v0.2.0
diff --git a/go.sum b/go.sum
index 4c783ff..096c7da 100644
--- a/go.sum
+++ b/go.sum
@@ -83,8 +83,8 @@ github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU=
 github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
 github.com/nats-io/jwt/v2 v2.0.0-20200916203241-1f8ce17dff02/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ=
 github.com/nats-io/jwt/v2 v2.0.0-20210104170214-da06b7f0b569/go.mod h1:PuO5FToRL31ecdFqVjc794vK0Bj0CwzveQEDvkb7MoQ=
-github.com/nats-io/jwt/v2 v2.0.0-20210107222814-18c5cc45d263 h1:M2bkT/arzYFYPtRqQWN2m+LSLfcqFmPxlxvTxdB4aVE=
-github.com/nats-io/jwt/v2 v2.0.0-20210107222814-18c5cc45d263/go.mod h1:PuO5FToRL31ecdFqVjc794vK0Bj0CwzveQEDvkb7MoQ=
+github.com/nats-io/jwt/v2 v2.0.0-20210129014916-f3ef7d605453 h1:gt9KGX38jAx1U5+4zkSE44Wy8ofi6zBQ9cNcyFvnyRE=
+github.com/nats-io/jwt/v2 v2.0.0-20210129014916-f3ef7d605453/go.mod h1:PuO5FToRL31ecdFqVjc794vK0Bj0CwzveQEDvkb7MoQ=
 github.com/nats-io/nats-server/v2 v2.1.8-0.20200524125952-51ebd92a9093/go.mod h1:rQnBf2Rv4P9adtAs/Ti6LfFmVtFG6HLhl/H7cVshcJU=
 github.com/nats-io/nats-server/v2 v2.1.8-0.20200601203034-f8d6dd992b71/go.mod h1:Nan/1L5Sa1JRW+Thm4HNYcIDcVRFc5zK9OpSZeI2kk4=
 github.com/nats-io/nats-server/v2 v2.1.8-0.20200929001935-7f44d075f7ad/go.mod h1:TkHpUIDETmTI7mrHN40D1pzxfzHZuGmtMbtb83TGVQw=
diff --git a/vendor/github.com/nats-io/jwt/v2/account_claims.go b/vendor/github.com/nats-io/jwt/v2/account_claims.go
index 2037c11..6716948 100644
--- a/vendor/github.com/nats-io/jwt/v2/account_claims.go
+++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go
@@ -148,7 +148,7 @@ func NewAccountClaims(subject string) *AccountClaims {
 	c.Limits = OperatorLimits{
 		NatsLimits{NoLimit, NoLimit, NoLimit},
 		AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit},
-		JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit}}
+		JetStreamLimits{0, 0, 0, 0}}
 	c.Subject = subject
 	return c
 }
diff --git a/vendor/github.com/nats-io/jwt/v2/exports.go b/vendor/github.com/nats-io/jwt/v2/exports.go
index 23065fb..852ffae 100644
--- a/vendor/github.com/nats-io/jwt/v2/exports.go
+++ b/vendor/github.com/nats-io/jwt/v2/exports.go
@@ -118,6 +118,7 @@ type Export struct {
 	ResponseThreshold    time.Duration   `json:"response_threshold,omitempty"`
 	Latency              *ServiceLatency `json:"service_latency,omitempty"`
 	AccountTokenPosition uint            `json:"account_token_position,omitempty"`
+	Advertise            bool            `json:"advertise,omitempty"`
 	Info
 }
 
diff --git a/vendor/github.com/nats-io/jwt/v2/operator_claims.go b/vendor/github.com/nats-io/jwt/v2/operator_claims.go
index a41d9c0..f806002 100644
--- a/vendor/github.com/nats-io/jwt/v2/operator_claims.go
+++ b/vendor/github.com/nats-io/jwt/v2/operator_claims.go
@@ -43,6 +43,8 @@ type Operator struct {
 	SystemAccount string `json:"system_account,omitempty"`
 	// Min Server version
 	AssertServerVersion string `json:"assert_server_version,omitempty"`
+	// Signing of subordinate objects will require signing keys
+	StrictSigningKeyUsage bool `json:"strict_signing_key_usage,omitempty"`
 	GenericFields
 }
 
@@ -164,6 +166,7 @@ func NewOperatorClaims(subject string) *OperatorClaims {
 	}
 	c := &OperatorClaims{}
 	c.Subject = subject
+	c.Issuer = subject
 	return c
 }
 
@@ -174,7 +177,10 @@ func (oc *OperatorClaims) DidSign(op Claims) bool {
 	}
 	issuer := op.Claims().Issuer
 	if issuer == oc.Subject {
-		return true
+		if !oc.StrictSigningKeyUsage {
+			return true
+		}
+		return op.Claims().Subject == oc.Subject
 	}
 	return oc.SigningKeys.Contains(issuer)
 }
diff --git a/vendor/github.com/nats-io/jwt/v2/types.go b/vendor/github.com/nats-io/jwt/v2/types.go
index 3aa4f3a..b538b62 100644
--- a/vendor/github.com/nats-io/jwt/v2/types.go
+++ b/vendor/github.com/nats-io/jwt/v2/types.go
@@ -26,7 +26,7 @@ import (
 	"time"
 )
 
-const MaxInfoLength = 255
+const MaxInfoLength = 8 * 1024
 
 type Info struct {
 	Description string `json:"description,omitempty"`
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 0e4aaa2..f21f1d7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -72,7 +72,7 @@ github.com/nats-io/cliprompts/v2
 # github.com/nats-io/jwt v1.2.2
 ## explicit
 github.com/nats-io/jwt
-# github.com/nats-io/jwt/v2 v2.0.0-20210107222814-18c5cc45d263
+# github.com/nats-io/jwt/v2 v2.0.0-20210129014916-f3ef7d605453
 ## explicit
 github.com/nats-io/jwt/v2
 # github.com/nats-io/nats-server/v2 v2.1.8-0.20210107160521-ceb1fda2024b


Test patch:
diff --git a/cmd/addoperator_test.go b/cmd/addoperator_test.go
index ef679b9..d6efe8f 100644
--- a/cmd/addoperator_test.go
+++ b/cmd/addoperator_test.go
@@ -40,7 +40,7 @@ func Test_AddOperator(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--name", "O", "--sys")
+	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--name", "O", "--sys", "--generate-signing-key")
 	require.NoError(t, err)
 
 	require.FileExists(t, filepath.Join(ts.Dir, "store", "O", ".nsc"))
@@ -97,7 +97,7 @@ func TestAddOperatorInteractive(t *testing.T) {
 	ts := NewEmptyStore(t)
 	defer ts.Done(t)
 
-	_, _, err := ExecuteInteractiveCmd(createAddOperatorCmd(), []interface{}{false, "O", "2019-12-01", "2029-12-01", true, true})
+	_, _, err := ExecuteInteractiveCmd(createAddOperatorCmd(), []interface{}{false, "O", "2019-12-01", "2029-12-01", true, true, true})
 	require.NoError(t, err)
 	d, err := Read(filepath.Join(ts.Dir, "store", "O", "O.jwt"))
 	require.NoError(t, err)
@@ -108,6 +108,7 @@ func TestAddOperatorInteractive(t *testing.T) {
 	require.Equal(t, 2019, start.Year())
 	require.Equal(t, time.Month(12), start.Month())
 	require.Equal(t, 1, start.Day())
+	require.Len(t, oc.SigningKeys, 1)
 
 	expiry := time.Unix(oc.Expires, 0).UTC()
 	require.Equal(t, 2029, expiry.Year())
@@ -115,8 +116,22 @@ func TestAddOperatorInteractive(t *testing.T) {
 	require.Equal(t, 1, expiry.Day())
 	require.NotEmpty(t, oc.SystemAccount)
 
-	require.FileExists(t, filepath.Join(ts.Dir, "store", "O", "accounts", "SYS", "SYS.jwt"))
-	require.FileExists(t, filepath.Join(ts.Dir, "store", "O", "accounts", "SYS", "users", "sys.jwt"))
+	sys := filepath.Join(ts.Dir, "store", "O", "accounts", "SYS", "SYS.jwt")
+	require.FileExists(t, sys)
+	sysJWT, err := Read(sys)
+	require.NoError(t, err)
+	sysClaim, err := jwt.DecodeAccountClaims(string(sysJWT))
+	require.NoError(t, err)
+	require.Equal(t, sysClaim.Issuer, oc.SigningKeys[0])
+
+	usr := filepath.Join(ts.Dir, "store", "O", "accounts", "SYS", "users", "sys.jwt")
+	require.FileExists(t, usr)
+	usrJWT, err := Read(usr)
+	require.NoError(t, err)
+	usrClaim, err := jwt.DecodeUserClaims(string(usrJWT))
+	require.NoError(t, err)
+	_, ok := sysClaim.SigningKeys[usrClaim.Issuer]
+	require.True(t, ok)
 }
 
 func TestImportOperatorInteractive(t *testing.T) {
@@ -196,7 +211,7 @@ func Test_AddOperatorWithKeyInteractive(t *testing.T) {
 	cmd := createAddOperatorCmd()
 	HoistRootFlags(cmd)
 
-	args := []interface{}{false, "T", "0", "0", false, false, string(seed)}
+	args := []interface{}{false, "T", "0", "0", false, false, false, string(seed)}
 	_, _, err := ExecuteInteractiveCmd(cmd, args)
 	require.NoError(t, err)
 
diff --git a/cmd/editoperator_test.go b/cmd/editoperator_test.go
index 2665ee0..dccc6f6 100644
--- a/cmd/editoperator_test.go
+++ b/cmd/editoperator_test.go
@@ -18,8 +18,15 @@
 package cmd
 
 import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
 	"testing"
 
+	"github.com/nats-io/nkeys"
+
 	"github.com/nats-io/jwt/v2"
 	"github.com/nats-io/nsc/cmd/store"
 	"github.com/stretchr/testify/require"
@@ -39,11 +46,170 @@ func Test_EditOperator(t *testing.T) {
 		{createEditOperatorCmd(), []string{"edit", "operator", "--sk", "SAADOZRUTPZS6LIXS6CSSSW5GXY3DNMQMSDTVWHQNHQTIBPGNSADSMBPEU"}, nil, []string{"invalid operator signing key"}, true},
 		{createEditOperatorCmd(), []string{"edit", "operator", "--sk", "OBMWGGURAFWMH3AFDX65TVIH4ZYSL7UKZ3LOH2ZRWIAU7PGZ3IJNR6W5"}, nil, []string{"edited operator"}, false},
 		{createEditOperatorCmd(), []string{"edit", "operator", "--tag", "O", "--start", "2019-04-13", "--expiry", "2050-01-01"}, nil, []string{"edited operator"}, false},
+		{createEditOperatorCmd(), []string{"edit", "operator", "--require-signing-keys"}, nil, []string{"needs to be issued with a signing key first"}, true},
 	}
 
 	tests.Run(t, "root", "edit")
 }
 
+func readJWT(t *testing.T, elem ...string) string {
+	t.Helper()
+	fp := filepath.Join(elem...)
+	require.FileExists(t, fp)
+	theJWT, err := ioutil.ReadFile(fp)
+	require.NoError(t, err)
+	return string(theJWT)
+}
+
+func checkAcc(t *testing.T, ts *TestStore, acc string) {
+	t.Helper()
+	opJWT := readJWT(t, ts.Dir, "store", "O", "O.jwt")
+	op, err := jwt.DecodeOperatorClaims(opJWT)
+	require.NoError(t, err)
+	require.True(t, op.StrictSigningKeyUsage)
+	accJWT := readJWT(t, ts.Dir, "store", "O", "accounts", acc, fmt.Sprintf("%s.jwt", acc))
+	ac, err := jwt.DecodeAccountClaims(accJWT)
+	require.NoError(t, err)
+	require.NotEqual(t, ac.Issuer, op.Subject)
+	require.Equal(t, ac.Issuer, op.SigningKeys[0])
+	_, _, err = ExecuteCmd(createValidateCommand(), "--all-accounts")
+	require.NoError(t, err)
+}
+
+func checkUsr(t *testing.T, ts *TestStore, acc string) {
+	t.Helper()
+	opJWT := readJWT(t, ts.Dir, "store", "O", "O.jwt")
+	op, err := jwt.DecodeOperatorClaims(opJWT)
+	require.NoError(t, err)
+	require.True(t, op.StrictSigningKeyUsage)
+	accJWT := readJWT(t, ts.Dir, "store", "O", "accounts", acc, fmt.Sprintf("%s.jwt", acc))
+	ac, err := jwt.DecodeAccountClaims(accJWT)
+	require.NoError(t, err)
+	require.NotEqual(t, ac.Issuer, op.Subject)
+	require.Equal(t, ac.Issuer, op.SigningKeys[0])
+	usrJWT := readJWT(t, ts.Dir, "store", "O", "accounts", acc, "users", "U.jwt")
+	uc, err := jwt.DecodeUserClaims(usrJWT)
+	require.NoError(t, err)
+	require.NotEqual(t, uc.Issuer, ac.Subject)
+	require.Equal(t, uc.IssuerAccount, ac.Subject)
+	require.Equal(t, uc.Issuer, ac.SigningKeys.Keys()[0])
+}
+
+func Test_EditOperatorRequireSigningKeys(t *testing.T) {
+	ts := NewEmptyStore(t)
+
+	_, err := os.Lstat(filepath.Join(ts.Dir, "store"))
+	if err != nil && !os.IsNotExist(err) {
+		t.Fatal(err)
+	}
+	// Perform all operations that would end up signing account/user/activation jwt
+	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--name", "O")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--sk", "generate")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--require-signing-keys")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "EXPORTER")
+	require.NoError(t, err)
+	checkAcc(t, ts, "EXPORTER")
+	_, _, err = ExecuteCmd(createEditAccount(), "--name", "EXPORTER", "--sk", "generate")
+	require.NoError(t, err)
+	checkAcc(t, ts, "EXPORTER")
+	_, _, err = ExecuteCmd(createAddExportCmd(), "--subject", "sub.public")
+	require.NoError(t, err)
+	checkAcc(t, ts, "EXPORTER")
+	_, _, err = ExecuteCmd(createAddExportCmd(), "--subject", "sub.private", "--private")
+	require.NoError(t, err)
+	checkAcc(t, ts, "EXPORTER")
+	_, _, err = ExecuteCmd(createEditExportCmd(), "--account", "EXPORTER", "--subject", "sub.public", "--description", "foo")
+	require.NoError(t, err)
+	checkAcc(t, ts, "EXPORTER")
+	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "A")
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	_, _, err = ExecuteCmd(createEditAccount(), "--name", "A", "--sk", "generate")
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	aAc, err := jwt.DecodeAccountClaims(readJWT(t, ts.Dir, "store", "O", "accounts", "A", "A.jwt"))
+	require.NoError(t, err)
+	expAc, err := jwt.DecodeAccountClaims(readJWT(t, ts.Dir, "store", "O", "accounts", "EXPORTER", "EXPORTER.jwt"))
+	require.NoError(t, err)
+	outpath := filepath.Join(ts.Dir, "token.jwt")
+	_, _, err = ExecuteCmd(createGenerateActivationCmd(), "--account", "EXPORTER", "--subject", "sub.private",
+		"--target-account", aAc.Subject, "--output-file", outpath)
+	require.NoError(t, err)
+	act, err := jwt.DecodeActivationClaims(strings.Split(readJWT(t, outpath), "\n")[1]) // strip decoration
+	require.NoError(t, err)
+	require.NotEqual(t, act.Issuer, act.IssuerAccount)
+	require.Equal(t, act.IssuerAccount, expAc.Subject)
+	require.Equal(t, act.Issuer, expAc.SigningKeys.Keys()[0])
+	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "A", "--token", outpath)
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "A", "--src-account", expAc.Subject,
+		"--remote-subject", "sub.public")
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	_, _, err = ExecuteCmd(createDeleteImportCmd(), "--account", "A", "--subject", "sub.public")
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	_, _, err = ExecuteCmd(createDeleteExportCmd(), "--account", "EXPORTER", "--subject", "sub.public")
+	require.NoError(t, err)
+	checkAcc(t, ts, "EXPORTER")
+	_, _, err = ExecuteCmd(CreateAddUserCmd(), "--account", "A", "--name", "U")
+	require.NoError(t, err)
+	checkUsr(t, ts, "A")
+	_, _, err = ExecuteCmd(createEditUserCmd(), "--account", "A", "--name", "U", "--tag", "foo")
+	require.NoError(t, err)
+	checkUsr(t, ts, "A")
+	_, _, err = ExecuteCmd(createDeleteUserCmd(), "--account", "A", "--name", "U", "--revoke")
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	uk, err := nkeys.CreateUser()
+	require.NoError(t, err)
+	pubUk, err := uk.PublicKey()
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createRevokeUserCmd(), "--account", "A", "--user-public-key", pubUk)
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+}
+
+func Test_EditOperatorRequireSigningKeysManaged(t *testing.T) {
+	ts := NewEmptyStore(t)
+	defer ts.Done(t)
+	_, err := os.Lstat(filepath.Join(ts.Dir, "store"))
+	if err != nil && !os.IsNotExist(err) {
+		t.Fatal(err)
+	}
+	_, pub, kp := CreateOperatorKey(t)
+	oc := jwt.NewOperatorClaims(pub)
+	oc.Name = "O"
+	oc.StrictSigningKeyUsage = true
+	_, psk, sk := CreateOperatorKey(t)
+	ts.KeyStore.Store(sk)
+	oc.SigningKeys.Add(psk)
+	token, err := oc.Encode(kp)
+	require.NoError(t, err)
+	tf := filepath.Join(ts.Dir, "O.jwt")
+	err = Write(tf, []byte(token))
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf) // causes a managed store
+	require.NoError(t, err)
+	// perform operations in a managed store and assure identity is not used
+	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "A")
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	_, _, err = ExecuteCmd(createEditAccount(), "--name", "A", "--sk", "generate")
+	require.NoError(t, err)
+	checkAcc(t, ts, "A")
+	_, _, err = ExecuteCmd(CreateAddUserCmd(), "--account", "A", "--name", "U")
+	require.NoError(t, err)
+	checkUsr(t, ts, "A")
+	_, _, err = ExecuteCmd(createEditUserCmd(), "--account", "A", "--name", "U", "--tag", "foo")
+	require.NoError(t, err)
+	checkUsr(t, ts, "A")
+}
+
 func Test_EditOperatorSigningKeys(t *testing.T) {
 	ts := NewTestStore(t, "O")
 	defer ts.Done(t)
@@ -51,7 +217,7 @@ func Test_EditOperatorSigningKeys(t *testing.T) {
 	s1, pk1, _ := CreateOperatorKey(t)
 	_, pk2, _ := CreateOperatorKey(t)
 
-	_, _, err := ExecuteCmd(createEditOperatorCmd(), "--sk", pk1, "--sk", pk2)
+	_, _, err := ExecuteCmd(createEditOperatorCmd(), "--sk", pk1, "--sk", pk2, "--sk", "generate")
 	require.NoError(t, err)
 
 	d, err := ts.Store.Read(store.JwtName("O"))
@@ -62,6 +228,7 @@ func Test_EditOperatorSigningKeys(t *testing.T) {
 
 	require.Contains(t, oc.SigningKeys, pk1)
 	require.Contains(t, oc.SigningKeys, pk2)
+	require.Len(t, oc.SigningKeys, 3)
 
 	_, _, err = ExecuteCmd(HoistRootFlags(CreateAddAccountCmd()), "--name", "A", "-K", string(s1))
 	require.NoError(t, err)
diff --git a/cmd/reissueoperator_test.go b/cmd/reissueoperator_test.go
index 8568266..e495736 100644
--- a/cmd/reissueoperator_test.go
+++ b/cmd/reissueoperator_test.go
@@ -59,3 +59,29 @@ func Test_ReIssue(t *testing.T) {
 	require.NoError(t, err)
 	require.True(t, op4.DidSign(ac))
 }
+
+func Test_ReIssueStrict(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	op1, err := ts.Store.ReadOperatorClaim()
+	require.NoError(t, err)
+
+	// add testing account
+	ts.AddAccount(t, "A")
+
+	_, _, err = ExecuteCmd(createReIssueOperatorCmd(), "--convert-to-signing-key")
+	require.NoError(t, err)
+	op3, err := ts.Store.ReadOperatorClaim()
+	require.NoError(t, err)
+	require.NotEqual(t, op1.Subject, op3.Subject)
+	require.Len(t, op3.SigningKeys, 1)
+	require.True(t, op3.SigningKeys.Contains(op1.Subject))
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.True(t, op3.DidSign(ac))
+
+	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--require-signing-keys")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createReIssueOperatorCmd(), "--convert-to-signing-key")
+	require.NoError(t, err)
+}