diff --git a/go.mod b/go.mod index 20a9eee8..ba5dcfad 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.15 require ( github.com/klauspost/compress v1.11.7 github.com/minio/highwayhash v1.0.0 - 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.go v1.10.1-0.20210122204956-b8ea7fc17ea6 github.com/nats-io/nkeys v0.2.0 github.com/nats-io/nuid v1.0.1 diff --git a/go.sum b/go.sum index 496a8bcc..c9554443 100644 --- a/go.sum +++ b/go.sum @@ -3,49 +3,30 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU= -github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA= github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7 h1:RnGotxlghqR5D2KDAu4TyuLqyjuylOsJiAFhXvMvQIc= github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M= github.com/nats-io/jwt v1.1.0 h1:+vOlgtM0ZsF46GbmUoadq0/2rChNS45gtxHEa3H1gqM= github.com/nats-io/jwt v1.1.0/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M= -github.com/nats-io/jwt v1.2.3-0.20210107222814-18c5cc45d263 h1:x3J+0KMQhbQE8iHxChJdJHNk7rJnCwXFX+WI0hfvYtE= -github.com/nats-io/jwt v1.2.3-0.20210107222814-18c5cc45d263/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-20201015190852-e11ce317263c/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ= -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= github.com/nats-io/nats-server/v2 v2.1.8-0.20201129161730-ebe63db3e3ed/go.mod h1:XD0zHR/jTXdZvWaQfS5mQgsXj6x12kMjKLyAk/cOGgY= github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE= github.com/nats-io/nats.go v1.10.1-0.20200531124210-96f2130e4d55/go.mod h1:ARiFsjW9DVxk48WJbO3OSZ2DG8fjkMi7ecLmXoY/n9I= -github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a h1:gzSKZOBlu/DpbuPbt34paXCOvA6+E+lVfU2BmomQ9HA= github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a/go.mod h1:8eAIv96Mo9QW6Or40jUHejS7e4VwZ3VRYD6Sf0BTDp4= -github.com/nats-io/nats.go v1.10.1-0.20201021145452-94be476ad6e0 h1:s//xxvMgNRVJNTRXTRNvLlES0zvJoDLXfcmbK0YgN0E= github.com/nats-io/nats.go v1.10.1-0.20201021145452-94be476ad6e0/go.mod h1:VU2zERjp8xmF+Lw2NH4u2t5qWZxwc7jB3+7HVMWQXPI= -github.com/nats-io/nats.go v1.10.1-0.20201218032324-b4450fb04511 h1:VMkjinILzhvMPbG+MwRC9q5IElHdsOaB5aHIiCT8vd4= -github.com/nats-io/nats.go v1.10.1-0.20201218032324-b4450fb04511/go.mod h1:Sa3kLIonafChP5IF0b55i9uvGR10I3hPETFbi4+9kOI= -github.com/nats-io/nats.go v1.10.1-0.20210113000055-be60b8378d4f h1:HjROEVuamG+e0lCwjKs3yfTzDbbaogriW4HQQ2OQcr0= -github.com/nats-io/nats.go v1.10.1-0.20210113000055-be60b8378d4f/go.mod h1:Sa3kLIonafChP5IF0b55i9uvGR10I3hPETFbi4+9kOI= -github.com/nats-io/nats.go v1.10.1-0.20210113025029-acca6d69b4e3 h1:M7a1IwTIUnZ/ucEz+/oyOoQ11CJ/sDfybZYjdGMGjdE= -github.com/nats-io/nats.go v1.10.1-0.20210113025029-acca6d69b4e3/go.mod h1:Sa3kLIonafChP5IF0b55i9uvGR10I3hPETFbi4+9kOI= -github.com/nats-io/nats.go v1.10.1-0.20210114001154-0a6b5f686ab3 h1:2uYi4zZJ6zni7jwix8l1bA/srT3hjFwGN2iUvx0CWpA= -github.com/nats-io/nats.go v1.10.1-0.20210114001154-0a6b5f686ab3/go.mod h1:Sa3kLIonafChP5IF0b55i9uvGR10I3hPETFbi4+9kOI= -github.com/nats-io/nats.go v1.10.1-0.20210115180731-7fb8bacca613 h1:Zio4IMHHsFjtTeksjF4PySxFNcKwSH9urNIiIW7A/FQ= -github.com/nats-io/nats.go v1.10.1-0.20210115180731-7fb8bacca613/go.mod h1:Sa3kLIonafChP5IF0b55i9uvGR10I3hPETFbi4+9kOI= github.com/nats-io/nats.go v1.10.1-0.20210122204956-b8ea7fc17ea6 h1:cpS+9uyfHXvRG/Q+WcDd3KXRgPa9fo9tDbIeDHCxYAg= github.com/nats-io/nats.go v1.10.1-0.20210122204956-b8ea7fc17ea6/go.mod h1:Sa3kLIonafChP5IF0b55i9uvGR10I3hPETFbi4+9kOI= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -56,7 +37,6 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -64,19 +44,16 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= diff --git a/server/accounts.go b/server/accounts.go index 388580b9..73251227 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -2746,10 +2746,6 @@ func (a *Account) hasIssuer(issuer string) (jwt.Scope, bool) { // hasIssuerNoLock is the unlocked version of hasIssuer func (a *Account) hasIssuerNoLock(issuer string) (jwt.Scope, bool) { - // same issuer -- keep this for safety on the calling code - if a.Name == issuer { - return nil, true - } scope, ok := a.signingKeys[issuer] return scope, ok } @@ -2844,13 +2840,17 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim // update account signing keys a.signingKeys = nil - if len(ac.SigningKeys) > 0 { - a.signingKeys = make(map[string]jwt.Scope, len(ac.SigningKeys)) + _, strict := s.strictSigningKeyUsage[a.Issuer] + if len(ac.SigningKeys) > 0 || !strict { + a.signingKeys = make(map[string]jwt.Scope) } signersChanged := false for k, scope := range ac.SigningKeys { a.signingKeys[k] = scope } + if !strict { + a.signingKeys[a.Name] = nil + } if len(a.signingKeys) != len(old.signingKeys) { signersChanged = true } @@ -3150,6 +3150,10 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim if signersChanged { for _, c := range clients { c.mu.Lock() + if c.user == nil { + c.mu.Unlock() + continue + } sk := c.user.SigningKey c.mu.Unlock() if sk == _EMPTY_ { @@ -3506,19 +3510,21 @@ func handleDeleteRequest(store *DirJWTStore, s *Server, msg []byte, reply string } } -func getOperator(s *Server) (string, error) { +func getOperator(s *Server) (string, bool, error) { var op string + var strict bool if opts := s.getOpts(); opts != nil && len(opts.TrustedOperators) > 0 { op = opts.TrustedOperators[0].Subject + strict = opts.TrustedOperators[0].StrictSigningKeyUsage } if op == "" { - return "", fmt.Errorf("no operator found") + return "", false, fmt.Errorf("no operator found") } - return op, nil + return op, strict, nil } func (dr *DirAccResolver) Start(s *Server) error { - op, err := getOperator(s) + op, strict, err := getOperator(s) if err != nil { return err } @@ -3553,6 +3559,9 @@ func (dr *DirAccResolver) Start(s *Server) error { } else if claim.Subject != pubKey { err := errors.New("subject does not match jwt content") respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) + } else if claim.Issuer == op && strict { + err := errors.New("operator requires issuer to be a signing key") + respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) } else if err := dr.save(pubKey, string(msg)); err != nil { respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) } else { @@ -3565,6 +3574,9 @@ func (dr *DirAccResolver) Start(s *Server) error { if _, err := s.sysSubscribe(accClaimsReqSubj, func(_ *subscription, _ *client, subj, resp string, msg []byte) { if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil { respondToUpdate(s, resp, "n/a", "jwt update resulted in error", err) + } else if claim.Issuer == op && strict { + err := errors.New("operator requires issuer to be a signing key") + respondToUpdate(s, resp, claim.Subject, "jwt update resulted in error", err) } else if err := dr.save(claim.Subject, string(msg)); err != nil { respondToUpdate(s, resp, claim.Subject, "jwt update resulted in error", err) } else { @@ -3760,7 +3772,7 @@ func NewCacheDirAccResolver(path string, limit int64, ttl time.Duration, _ ...di } func (dr *CacheDirAccResolver) Start(s *Server) error { - op, err := getOperator(s) + op, strict, err := getOperator(s) if err != nil { return err } @@ -3794,6 +3806,9 @@ func (dr *CacheDirAccResolver) Start(s *Server) error { } else if claim.Subject != pubKey { err := errors.New("subject does not match jwt content") respondToUpdate(s, resp, pubKey, "jwt update cache resulted in error", err) + } else if claim.Issuer == op && strict { + err := errors.New("operator requires issuer to be a signing key") + respondToUpdate(s, resp, pubKey, "jwt update cache resulted in error", err) } else if _, ok := s.accounts.Load(pubKey); !ok { respondToUpdate(s, resp, pubKey, "jwt update cache skipped", nil) } else if err := dr.save(pubKey, string(msg)); err != nil { @@ -3808,6 +3823,9 @@ func (dr *CacheDirAccResolver) Start(s *Server) error { if _, err := s.sysSubscribe(accClaimsReqSubj, func(_ *subscription, _ *client, subj, resp string, msg []byte) { if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil { respondToUpdate(s, resp, "n/a", "jwt update cache resulted in error", err) + } else if claim.Issuer == op && strict { + err := errors.New("operator requires issuer to be a signing key") + respondToUpdate(s, resp, claim.Subject, "jwt update cache resulted in error", err) } else if _, ok := s.accounts.Load(claim.Subject); !ok { respondToUpdate(s, resp, claim.Subject, "jwt update cache skipped", nil) } else if err := dr.save(claim.Subject, string(msg)); err != nil { diff --git a/server/auth.go b/server/auth.go index 6265dfcd..8c520051 100644 --- a/server/auth.go +++ b/server/auth.go @@ -238,7 +238,7 @@ func (s *Server) configureAuthorization() { // This just checks and sets up the user map if we have multiple users. if opts.CustomClientAuthentication != nil { s.info.AuthRequired = true - } else if len(s.trustedKeys) > 0 { + } else if s.trustedKeys != nil { s.info.AuthRequired = true } else if opts.Nkeys != nil || opts.Users != nil { s.nkeys, s.users = s.buildNkeysAndUsersFromOptions(opts.Nkeys, opts.Users) @@ -560,21 +560,18 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo c.Debugf("Account JWT not signed by trusted operator") return false } - // this only executes IF there's an issuer on the Juc - otherwise the account is already vetted - if juc.IssuerAccount != _EMPTY_ { - if scope, ok := acc.hasIssuer(juc.Issuer); !ok { - c.Debugf("User JWT issuer is not known") + if scope, ok := acc.hasIssuer(juc.Issuer); !ok { + c.Debugf("User JWT issuer is not known") + return false + } else if scope != nil { + if err := scope.ValidateScopedSigner(juc); err != nil { + c.Debugf("User JWT is not valid: %v", err) return false - } else if scope != nil { - if err := scope.ValidateScopedSigner(juc); err != nil { - c.Debugf("User JWT is not valid: %v", err) - return false - } else if uSc, ok := scope.(*jwt.UserScope); !ok { - c.Debugf("User JWT is not valid") - return false - } else { - juc.UserPermissionLimits = uSc.Template - } + } else if uSc, ok := scope.(*jwt.UserScope); !ok { + c.Debugf("User JWT is not valid") + return false + } else { + juc.UserPermissionLimits = uSc.Template } } if acc.IsExpired() { diff --git a/server/client.go b/server/client.go index 2c09f55b..39527779 100644 --- a/server/client.go +++ b/server/client.go @@ -1787,7 +1787,7 @@ func (c *client) authViolation() { var hasTrustedNkeys, hasNkeys, hasUsers bool if s = c.srv; s != nil { s.mu.Lock() - hasTrustedNkeys = len(s.trustedKeys) > 0 + hasTrustedNkeys = s.trustedKeys != nil hasNkeys = s.nkeys != nil hasUsers = s.users != nil s.mu.Unlock() diff --git a/server/jwt.go b/server/jwt.go index f7e76ba3..49efd4ec 100644 --- a/server/jwt.go +++ b/server/jwt.go @@ -132,7 +132,9 @@ func validateTrustedOperators(o *Options) error { if o.TrustedKeys == nil { o.TrustedKeys = make([]string, 0, 4) } - o.TrustedKeys = append(o.TrustedKeys, opc.Issuer) + if !opc.StrictSigningKeyUsage { + o.TrustedKeys = append(o.TrustedKeys, opc.Subject) + } o.TrustedKeys = append(o.TrustedKeys, opc.SigningKeys...) } for _, key := range o.TrustedKeys { diff --git a/server/jwt_test.go b/server/jwt_test.go index 739fbcc0..8df61f0a 100644 --- a/server/jwt_test.go +++ b/server/jwt_test.go @@ -5144,3 +5144,121 @@ func TestJWScopedSigningKeys(t *testing.T) { }) require_Len(t, len(errChan), 0) } + +func TestJWTStrictSigningKeys(t *testing.T) { + newAccount := func(opKp nkeys.KeyPair) (nkeys.KeyPair, nkeys.KeyPair, string, *jwt.AccountClaims, string) { + accId, err := nkeys.CreateAccount() + require_NoError(t, err) + accIdPub, err := accId.PublicKey() + require_NoError(t, err) + + accSig, err := nkeys.CreateAccount() + require_NoError(t, err) + accSigPub, err := accSig.PublicKey() + require_NoError(t, err) + + aClaim := jwt.NewAccountClaims(accIdPub) + aClaim.SigningKeys.Add(accSigPub) + theJwt, err := aClaim.Encode(opKp) + require_NoError(t, err) + return accId, accSig, accIdPub, aClaim, theJwt + } + + opId, err := nkeys.CreateOperator() + require_NoError(t, err) + opIdPub, err := opId.PublicKey() + require_NoError(t, err) + + opSig, err := nkeys.CreateOperator() + require_NoError(t, err) + opSigPub, err := opSig.PublicKey() + require_NoError(t, err) + + aBadBadKp, aBadGoodKp, aBadPub, _, aBadJwt := newAccount(opId) + aGoodBadKp, aGoodGoodKp, aGoodPub, _, aGoodJwt := newAccount(opSig) + _, aSysKp, aSysPub, _, aSysJwt := newAccount(opSig) + + oClaim := jwt.NewOperatorClaims(opIdPub) + oClaim.StrictSigningKeyUsage = true + oClaim.SigningKeys.Add(opSigPub) + oClaim.SystemAccount = aSysPub + oJwt, err := oClaim.Encode(opId) + require_NoError(t, err) + + uBadBadCreds := newUserEx(t, aBadBadKp, false, aBadPub) + defer os.Remove(uBadBadCreds) + uBadGoodCreds := newUserEx(t, aBadGoodKp, false, aBadPub) + defer os.Remove(uBadGoodCreds) + uGoodBadCreds := newUserEx(t, aGoodBadKp, false, aGoodPub) + defer os.Remove(uGoodBadCreds) + uGoodGoodCreds := newUserEx(t, aGoodGoodKp, false, aGoodPub) + defer os.Remove(uGoodGoodCreds) + uSysCreds := newUserEx(t, aSysKp, false, aSysPub) + defer os.Remove(uSysCreds) + + connectTest := func(url string) { + for _, test := range []struct { + creds string + fail bool + }{ + {uBadBadCreds, true}, + {uBadGoodCreds, true}, + {uGoodBadCreds, true}, + {uGoodGoodCreds, false}, + } { + nc, err := nats.Connect(url, nats.UserCredentials(test.creds)) + nc.Close() + if test.fail { + require_Error(t, err) + } else { + require_NoError(t, err) + } + } + } + + t.Run("resolver", func(t *testing.T) { + dirSrv := createDir(t, "srv") + defer os.RemoveAll(dirSrv) + cf := createConfFile(t, []byte(fmt.Sprintf(` + port: -1 + operator = %s + resolver: { + type: full + dir: %s + } + resolver_preload = { + %s : "%s" + } + `, oJwt, dirSrv, aSysPub, aSysJwt))) + defer os.Remove(cf) + s, _ := RunServerWithConfig(cf) + defer s.Shutdown() + url := s.ClientURL() + if updateJwt(t, url, uSysCreds, aBadJwt, 1) != 0 { + t.Fatal("Expected negative response") + } + if updateJwt(t, url, uSysCreds, aGoodJwt, 1) != 1 { + t.Fatal("Expected positive response") + } + connectTest(url) + }) + + t.Run("mem-resolver", func(t *testing.T) { + dirSrv := createDir(t, "srv") + defer os.RemoveAll(dirSrv) + cf := createConfFile(t, []byte(fmt.Sprintf(` + port: -1 + operator = %s + resolver: MEMORY + resolver_preload = { + %s : "%s" + %s : "%s" + %s : "%s" + } + `, oJwt, aSysPub, aSysJwt, aBadPub, aBadJwt, aGoodPub, aGoodJwt))) + defer os.Remove(cf) + s, _ := RunServerWithConfig(cf) + defer s.Shutdown() + connectTest(s.ClientURL()) + }) +} diff --git a/server/nkey.go b/server/nkey.go index a792c6e0..87b20a4f 100644 --- a/server/nkey.go +++ b/server/nkey.go @@ -33,7 +33,7 @@ func (s *Server) NonceRequired() bool { // nonceRequired tells us if we should send a nonce. // Lock should be held on entry. func (s *Server) nonceRequired() bool { - return len(s.nkeys) > 0 || len(s.trustedKeys) > 0 + return len(s.nkeys) > 0 || s.trustedKeys != nil } // Generate a nonce for INFO challenge. diff --git a/server/server.go b/server/server.go index afe08ddb..60962674 100644 --- a/server/server.go +++ b/server/server.go @@ -194,6 +194,8 @@ type Server struct { // Trusted public operator keys. trustedKeys []string + // map of trusted keys to operator setting StrictSigningKeyUsage + strictSigningKeyUsage map[string]struct{} // We use this to minimize mem copies for requests to monitoring // endpoint /varz (when it comes from http). @@ -419,6 +421,8 @@ func NewServer(opts *Options) (*Server, error) { if a == nil { sac := NewAccount(s.opts.SystemAccount) sac.Issuer = opts.TrustedOperators[0].Issuer + sac.signingKeys = map[string]jwt.Scope{} + sac.signingKeys[s.opts.SystemAccount] = nil s.registerAccountNoLock(sac) } } @@ -748,7 +752,7 @@ func (s *Server) generateRouteInfoJSON() { func (s *Server) globalAccountOnly() bool { var hasOthers bool - if len(s.trustedKeys) > 0 { + if s.trustedKeys != nil { return false } @@ -798,7 +802,7 @@ func (s *Server) isTrustedIssuer(issuer string) bool { s.mu.Lock() defer s.mu.Unlock() // If we are not running in trusted mode and there is no issuer, that is ok. - if len(s.trustedKeys) == 0 && issuer == "" { + if s.trustedKeys == nil && issuer == "" { return true } for _, tk := range s.trustedKeys { @@ -812,6 +816,7 @@ func (s *Server) isTrustedIssuer(issuer string) bool { // processTrustedKeys will process binary stamped and // options-based trusted nkeys. Returns success. func (s *Server) processTrustedKeys() bool { + s.strictSigningKeyUsage = map[string]struct{}{} if trustedKeys != "" && !s.initStampedTrustedKeys() { return false } else if s.opts.TrustedKeys != nil { @@ -820,7 +825,15 @@ func (s *Server) processTrustedKeys() bool { return false } } - s.trustedKeys = s.opts.TrustedKeys + s.trustedKeys = append([]string(nil), s.opts.TrustedKeys...) + for _, claim := range s.opts.TrustedOperators { + if !claim.StrictSigningKeyUsage { + continue + } + for _, key := range claim.SigningKeys { + s.strictSigningKeyUsage[key] = struct{}{} + } + } } return true } 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 60277f2c..c4e2429a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,7 +4,7 @@ github.com/klauspost/compress/s2 # github.com/minio/highwayhash v1.0.0 ## explicit github.com/minio/highwayhash -# 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.go v1.10.1-0.20210122204956-b8ea7fc17ea6