diff --git a/cmd/addoperator.go b/cmd/addoperator.go index 74e58036..2df61314 100644 --- a/cmd/addoperator.go +++ b/cmd/addoperator.go @@ -44,7 +44,8 @@ func createAddOperatorCmd() *cobra.Command { } cmd.Flags().StringVarP(¶ms.name, "name", "n", "", "operator name") cmd.Flags().StringVarP(¶ms.jwtPath, "url", "u", "", "import from a jwt server url, file, or well known operator") - cmd.Flags().BoolVarP(¶ms.sysAcc, "sys", "s", false, "generate system account with the operator") + cmd.Flags().BoolVarP(¶ms.genSk, "generate-signing-key", "", false, "generate a signing key with the operator") + cmd.Flags().BoolVarP(¶ms.sysAcc, "sys", "s", false, "generate system account with the operator (if specified will be signed with signing key)") cmd.Flags().BoolVarP(¶ms.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(p.signerKP); 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 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/addoperator_test.go b/cmd/addoperator_test.go index ef679b96..d6efe8fe 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.go b/cmd/editoperator.go index aeac018f..4af03f5d 100644 --- a/cmd/editoperator.go +++ b/cmd/editoperator.go @@ -45,6 +45,7 @@ func createEditOperatorCmd() *cobra.Command { cmd.Flags().StringVarP(¶ms.sysAcc, "system-account", "", "", "set system account by account by public key or name") cmd.Flags().StringSliceVarP(¶ms.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(¶ms.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(¶ms.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/editoperator_test.go b/cmd/editoperator_test.go index 2665ee0e..f7177d49 100644 --- a/cmd/editoperator_test.go +++ b/cmd/editoperator_test.go @@ -39,6 +39,7 @@ 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{"strict signing key usage set to: true"}, false}, } tests.Run(t, "root", "edit") diff --git a/cmd/init.go b/cmd/init.go index d75fc9c8..ca6e96e3 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,8 +312,14 @@ 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 { @@ -328,7 +335,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/go.mod b/go.mod index 11b70e5a..0880477c 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-20210125223648-1c24d462becc 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 4c783ff9..7edf3433 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-20210125223648-1c24d462becc h1:pu+s4XC+bYnI0iD2vDtOl83zjCYUau/q6c83pEvsGZc= +github.com/nats-io/jwt/v2 v2.0.0-20210125223648-1c24d462becc/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 2037c111..67169483 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/operator_claims.go b/vendor/github.com/nats-io/jwt/v2/operator_claims.go index a41d9c04..61d474e5 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 } @@ -174,7 +176,7 @@ func (oc *OperatorClaims) DidSign(op Claims) bool { } issuer := op.Claims().Issuer if issuer == oc.Subject { - return true + return !oc.StrictSigningKeyUsage } return oc.SigningKeys.Contains(issuer) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 0e4aaa29..dcee5c48 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-20210125223648-1c24d462becc ## explicit github.com/nats-io/jwt/v2 # github.com/nats-io/nats-server/v2 v2.1.8-0.20210107160521-ceb1fda2024b