[added] support for StrictSigningKeyUsage and updated jwt library (#1845)

This will cause the server to not trust accounts/user signed by an
identity key

The boot strapping system account will assume the account is issued by
the operator.
If this is not desirable, the system account can be provided right away
as resolver_preload.

[fixes] crash when the system account uses signing keys and an update changes that key set.

Signed-off-by: Matthias Hanel <mh@synadia.com>
This commit is contained in:
Matthias Hanel
2021-01-26 17:49:58 -05:00
committed by GitHub
parent 695539c922
commit dea9effa8d
12 changed files with 188 additions and 61 deletions

2
go.mod
View File

@@ -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

27
go.sum
View File

@@ -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=

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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())
})
}

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

2
vendor/modules.txt vendored
View File

@@ -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