[added] support for jwt operator option DisallowBearerToken

I modified an existing data structure that held a similar attribute already.
Instead this data structure references the claim.

change 3 out of 3. Fixes #3084
corresponds to:
https://github.com/nats-io/jwt/pull/177
https://github.com/nats-io/nsc/pull/495

Signed-off-by: Matthias Hanel <mh@synadia.com>
This commit is contained in:
Matthias Hanel
2022-04-29 14:05:23 -04:00
parent 0bb7abccba
commit bd2883122e
7 changed files with 74 additions and 21 deletions

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.17
require ( require (
github.com/klauspost/compress v1.14.4 github.com/klauspost/compress v1.14.4
github.com/minio/highwayhash v1.0.2 github.com/minio/highwayhash v1.0.2
github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a github.com/nats-io/jwt/v2 v2.2.1-0.20220429175140-9f4f1d5cc977
github.com/nats-io/nats.go v1.14.0 github.com/nats-io/nats.go v1.14.0
github.com/nats-io/nkeys v0.3.0 github.com/nats-io/nkeys v0.3.0
github.com/nats-io/nuid v1.0.1 github.com/nats-io/nuid v1.0.1

4
go.sum
View File

@@ -12,8 +12,8 @@ github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6
github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I= github.com/nats-io/jwt/v2 v2.2.1-0.20220429175140-9f4f1d5cc977 h1:cbP9xTzFVGWXCJtApKchvFZIIEDWKfLkThVYLonpOpQ=
github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= github.com/nats-io/jwt/v2 v2.2.1-0.20220429175140-9f4f1d5cc977/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
github.com/nats-io/nats.go v1.14.0 h1:/QLCss4vQ6wvDpbqXucsVRDi13tFIR6kTdau+nXzKJw= github.com/nats-io/nats.go v1.14.0 h1:/QLCss4vQ6wvDpbqXucsVRDi13tFIR6kTdau+nXzKJw=
github.com/nats-io/nats.go v1.14.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nats.go v1.14.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=

View File

@@ -2983,7 +2983,8 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
// update account signing keys // update account signing keys
a.signingKeys = nil a.signingKeys = nil
_, strict := s.strictSigningKeyUsage[a.Issuer] op, ok := s.keyToClaim[a.Issuer]
strict := ok && op.StrictSigningKeyUsage
if len(ac.SigningKeys) > 0 || !strict { if len(ac.SigningKeys) > 0 || !strict {
a.signingKeys = make(map[string]jwt.Scope) a.signingKeys = make(map[string]jwt.Scope)
} }

View File

@@ -607,9 +607,12 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo
c.Debugf("Account JWT lookup error: %v", err) c.Debugf("Account JWT lookup error: %v", err)
return false return false
} }
if !s.isTrustedIssuer(acc.Issuer) { if isTrusted, noBearer := s.isTrustedIssuer(acc.Issuer); !isTrusted {
c.Debugf("Account JWT not signed by trusted operator") c.Debugf("Account JWT not signed by trusted operator")
return false return false
} else if noBearer && juc.BearerToken {
c.Debugf("Bearer Token are disallowed by operator")
return false
} }
if scope, ok := acc.hasIssuer(juc.Issuer); !ok { if scope, ok := acc.hasIssuer(juc.Issuer); !ok {
c.Debugf("User JWT issuer is not known") c.Debugf("User JWT issuer is not known")

View File

@@ -45,11 +45,10 @@ func createAccount(s *Server) (*Account, nkeys.KeyPair) {
return acc, akp return acc, akp
} }
func createUserCreds(t *testing.T, s *Server, akp nkeys.KeyPair) nats.Option { func createUserCredsEx(t *testing.T, nuc *jwt.UserClaims, akp nkeys.KeyPair) nats.Option {
t.Helper() t.Helper()
kp, _ := nkeys.CreateUser() kp, _ := nkeys.CreateUser()
pub, _ := kp.PublicKey() nuc.Subject, _ = kp.PublicKey()
nuc := jwt.NewUserClaims(pub)
ujwt, err := nuc.Encode(akp) ujwt, err := nuc.Encode(akp)
if err != nil { if err != nil {
t.Fatalf("Error generating user JWT: %v", err) t.Fatalf("Error generating user JWT: %v", err)
@@ -64,6 +63,10 @@ func createUserCreds(t *testing.T, s *Server, akp nkeys.KeyPair) nats.Option {
return nats.UserJWT(userCB, sigCB) return nats.UserJWT(userCB, sigCB)
} }
func createUserCreds(t *testing.T, s *Server, akp nkeys.KeyPair) nats.Option {
return createUserCredsEx(t, jwt.NewUserClaims("test"), akp)
}
func runTrustedServer(t *testing.T) (*Server, *Options) { func runTrustedServer(t *testing.T) (*Server, *Options) {
t.Helper() t.Helper()
opts := DefaultOptions() opts := DefaultOptions()

View File

@@ -5760,6 +5760,51 @@ func TestJWTStrictSigningKeys(t *testing.T) {
}) })
} }
func TestJWTDisallowBearer(t *testing.T) {
opId, err := nkeys.CreateOperator()
require_NoError(t, err)
opIdPub, err := opId.PublicKey()
require_NoError(t, err)
oClaim := jwt.NewOperatorClaims(opIdPub)
oClaim.DisallowBearerToken = true
oJwt, err := oClaim.Encode(opId)
require_NoError(t, err)
accId, err := nkeys.CreateAccount()
require_NoError(t, err)
accIdPub, err := accId.PublicKey()
require_NoError(t, err)
aClaim := jwt.NewAccountClaims(accIdPub)
accJwt, err := aClaim.Encode(opId)
require_NoError(t, err)
uc := jwt.NewUserClaims("dummy")
uc.BearerToken = true
uOpt1 := createUserCredsEx(t, uc, accId)
uc.BearerToken = false
uOpt2 := createUserCredsEx(t, uc, accId)
cf := createConfFile(t, []byte(fmt.Sprintf(`
port: -1
operator = %s
resolver: MEMORY
resolver_preload = {
%s : "%s"
}
`, oJwt, accIdPub, accJwt)))
defer removeFile(t, cf)
s, _ := RunServerWithConfig(cf)
defer s.Shutdown()
nc1, err := nats.Connect(s.ClientURL(), uOpt1)
require_Error(t, err)
defer nc1.Close()
nc2, err := nats.Connect(s.ClientURL(), uOpt2)
require_NoError(t, err)
defer nc2.Close()
}
func TestJWTAccountProtectedImport(t *testing.T) { func TestJWTAccountProtectedImport(t *testing.T) {
srvFmt := ` srvFmt := `
port: -1 port: -1

View File

@@ -209,8 +209,8 @@ type Server struct {
// Trusted public operator keys. // Trusted public operator keys.
trustedKeys []string trustedKeys []string
// map of trusted keys to operator setting StrictSigningKeyUsage // map of trusted keys to operator claims
strictSigningKeyUsage map[string]struct{} keyToClaim map[string]*jwt.OperatorClaims
// We use this to minimize mem copies for requests to monitoring // We use this to minimize mem copies for requests to monitoring
// endpoint /varz (when it comes from http). // endpoint /varz (when it comes from http).
@@ -972,25 +972,28 @@ func (s *Server) ActivePeers() (peers []string) {
// isTrustedIssuer will check that the issuer is a trusted public key. // isTrustedIssuer will check that the issuer is a trusted public key.
// This is used to make sure an account was signed by a trusted operator. // This is used to make sure an account was signed by a trusted operator.
func (s *Server) isTrustedIssuer(issuer string) bool { func (s *Server) isTrustedIssuer(issuer string) (bool, bool) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
// If we are not running in trusted mode and there is no issuer, that is ok. // If we are not running in trusted mode and there is no issuer, that is ok.
if s.trustedKeys == nil && issuer == "" { if s.trustedKeys == nil && issuer == "" {
return true return true, false
} }
for _, tk := range s.trustedKeys { for _, tk := range s.trustedKeys {
if tk == issuer { if tk == issuer {
return true if op, ok := s.keyToClaim[issuer]; ok {
return true, op.DisallowBearerToken
}
return true, false
} }
} }
return false return false, false
} }
// processTrustedKeys will process binary stamped and // processTrustedKeys will process binary stamped and
// options-based trusted nkeys. Returns success. // options-based trusted nkeys. Returns success.
func (s *Server) processTrustedKeys() bool { func (s *Server) processTrustedKeys() bool {
s.strictSigningKeyUsage = map[string]struct{}{} s.keyToClaim = map[string]*jwt.OperatorClaims{}
if trustedKeys != "" && !s.initStampedTrustedKeys() { if trustedKeys != "" && !s.initStampedTrustedKeys() {
return false return false
} else if s.opts.TrustedKeys != nil { } else if s.opts.TrustedKeys != nil {
@@ -1001,11 +1004,9 @@ func (s *Server) processTrustedKeys() bool {
} }
s.trustedKeys = append([]string(nil), s.opts.TrustedKeys...) s.trustedKeys = append([]string(nil), s.opts.TrustedKeys...)
for _, claim := range s.opts.TrustedOperators { for _, claim := range s.opts.TrustedOperators {
if !claim.StrictSigningKeyUsage { s.keyToClaim[claim.Subject] = claim
continue
}
for _, key := range claim.SigningKeys { for _, key := range claim.SigningKeys {
s.strictSigningKeyUsage[key] = struct{}{} s.keyToClaim[key] = claim
} }
} }
} }
@@ -1222,7 +1223,7 @@ func (s *Server) setSystemAccount(acc *Account) error {
} }
// If we are running with trusted keys for an operator // If we are running with trusted keys for an operator
// make sure we check the account is legit. // make sure we check the account is legit.
if !s.isTrustedIssuer(acc.Issuer) { if isTrusted, _ := s.isTrustedIssuer(acc.Issuer); !isTrusted {
return ErrAccountValidation return ErrAccountValidation
} }
@@ -1542,7 +1543,7 @@ func (s *Server) verifyAccountClaims(claimJWT string) (*jwt.AccountClaims, strin
if err != nil { if err != nil {
return nil, _EMPTY_, err return nil, _EMPTY_, err
} }
if !s.isTrustedIssuer(accClaims.Issuer) { if isTrusted, _ := s.isTrustedIssuer(accClaims.Issuer); !isTrusted {
return nil, _EMPTY_, ErrAccountValidation return nil, _EMPTY_, ErrAccountValidation
} }
vr := jwt.CreateValidationResults() vr := jwt.CreateValidationResults()