Skip to content

Commit

Permalink
[added] operator edit option to require signing keys in operator jwt
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
matthiashanel committed Jan 26, 2021
1 parent 1fe1b68 commit b5b38a0
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 18 deletions.
36 changes: 31 additions & 5 deletions cmd/addoperator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -69,6 +70,7 @@ type AddOperatorParams struct {
generate bool
sysAcc bool
force bool
genSk bool
keyPath string
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand Down
25 changes: 20 additions & 5 deletions cmd/addoperator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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)
Expand All @@ -108,15 +108,30 @@ 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())
require.Equal(t, time.Month(12), expiry.Month())
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) {
Expand Down Expand Up @@ -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)

Expand Down
31 changes: 30 additions & 1 deletion cmd/editoperator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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") {
Expand Down
1 change: 1 addition & 0 deletions cmd/editoperator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 9 additions & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion vendor/github.com/nats-io/jwt/v2/account_claims.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion vendor/github.com/nats-io/jwt/v2/operator_claims.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit b5b38a0

Please sign in to comment.