Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[added] support for jwt account option DisallowBearer #3127

Merged
merged 2 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.17
require (
github.com/klauspost/compress v1.15.5
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.3.0
github.com/nats-io/nats.go v1.16.0
github.com/nats-io/nkeys v0.3.0
github.com/nats-io/nuid v1.0.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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/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.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
github.com/nats-io/nats.go v1.16.0 h1:zvLE7fGBQYW6MWaFaRdsgm9qT39PJDQoju+DS8KsO1g=
github.com/nats-io/nats.go v1.16.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
Expand Down
25 changes: 19 additions & 6 deletions server/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ type Account struct {

// Account based limits.
type limits struct {
mpay int32
msubs int32
mconns int32
mleafs int32
mpay int32
msubs int32
mconns int32
mleafs int32
disallowBearer bool
}

// Used to track remote clients and leafnodes per remote server.
Expand Down Expand Up @@ -230,7 +231,7 @@ type importMap struct {
func NewAccount(name string) *Account {
a := &Account{
Name: name,
limits: limits{-1, -1, -1, -1},
limits: limits{-1, -1, -1, -1, false},
eventIds: nuid.New(),
}
return a
Expand Down Expand Up @@ -2838,6 +2839,13 @@ func (a *Account) checkUserRevoked(nkey string, issuedAt int64) bool {
return isRevoked(a.usersRevoked, nkey, issuedAt)
}

// failBearer will return if bearer token are allowed (false) or disallowed (true)
func (a *Account) failBearer() bool {
a.mu.RLock()
defer a.mu.RUnlock()
return a.disallowBearer
}

// Check expiration and set the proper state as needed.
func (a *Account) checkExpiration(claims *jwt.ClaimsData) {
a.mu.Lock()
Expand Down Expand Up @@ -3242,6 +3250,7 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
a.mpay = int32(ac.Limits.Payload)
a.mconns = int32(ac.Limits.Conn)
a.mleafs = int32(ac.Limits.LeafNodeConn)
a.disallowBearer = ac.Limits.DisallowBearer
// Check for any revocations
if len(ac.Revocations) > 0 {
// We will always replace whatever we had with most current, so no
Expand Down Expand Up @@ -3344,11 +3353,15 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
theJWT := c.opts.JWT
c.mu.Unlock()
// Check for being revoked here. We use ac one to avoid the account lock.
if ac.Revocations != nil && theJWT != _EMPTY_ {
if (ac.Revocations != nil || ac.Limits.DisallowBearer) && theJWT != _EMPTY_ {
if juc, err := jwt.DecodeUserClaims(theJWT); err != nil {
c.Debugf("User JWT not valid: %v", err)
c.authViolation()
continue
} else if juc.BearerToken && ac.Limits.DisallowBearer {
c.Debugf("Bearer User JWT not allowed")
c.authViolation()
continue
} else if ok := ac.IsClaimRevoked(juc); ok {
c.sendErrAndDebug("User Authentication Revoked")
c.closeConnection(Revocation)
Expand Down
4 changes: 4 additions & 0 deletions server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,10 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo
c.Debugf("Account JWT has expired")
return false
}
if juc.BearerToken && acc.failBearer() {
c.Debugf("Account does not allow bearer token")
return false
}
// skip validation of nonce when presented with a bearer token
// FIXME: if BearerToken is only for WSS, need check for server with that port enabled
if !juc.BearerToken {
Expand Down
9 changes: 6 additions & 3 deletions server/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ func createAccount(s *Server) (*Account, nkeys.KeyPair) {
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()
kp, _ := nkeys.CreateUser()
pub, _ := kp.PublicKey()
nuc := jwt.NewUserClaims(pub)
nuc.Subject, _ = kp.PublicKey()
ujwt, err := nuc.Encode(akp)
if err != nil {
t.Fatalf("Error generating user JWT: %v", err)
Expand All @@ -64,6 +63,10 @@ func createUserCreds(t *testing.T, s *Server, akp nkeys.KeyPair) nats.Option {
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) {
t.Helper()
opts := DefaultOptions()
Expand Down
71 changes: 71 additions & 0 deletions server/jetstream_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,77 @@ func TestJetStreamJWTLimits(t *testing.T) {
c.Close()
}

func TestJetStreamJWTDisallowBearer(t *testing.T) {
sysKp, syspub := createKey(t)
sysJwt := encodeClaim(t, jwt.NewAccountClaims(syspub), syspub)
sysCreds := newUser(t, sysKp)
defer removeFile(t, sysCreds)

accKp, err := nkeys.CreateAccount()
require_NoError(t, err)
accIdPub, err := accKp.PublicKey()
require_NoError(t, err)
aClaim := jwt.NewAccountClaims(accIdPub)
accJwt1, err := aClaim.Encode(oKp)
require_NoError(t, err)
aClaim.Limits.DisallowBearer = true
accJwt2, err := aClaim.Encode(oKp)
require_NoError(t, err)

uc := jwt.NewUserClaims("dummy")
uc.BearerToken = true
uOpt1 := createUserCredsEx(t, uc, accKp)
uc.BearerToken = false
uOpt2 := createUserCredsEx(t, uc, accKp)

dir := createDir(t, "srv")
defer removeDir(t, dir)
cf := createConfFile(t, []byte(fmt.Sprintf(`
port: -1
operator = %s
system_account: %s
resolver: {
type: full
dir: '%s/jwt'
}
resolver_preload = {
%s : "%s"
}
`, ojwt, syspub, dir, syspub, sysJwt)))
defer removeFile(t, cf)
s, _ := RunServerWithConfig(cf)
defer s.Shutdown()

updateJwt(t, s.ClientURL(), sysCreds, accJwt1, 1)
disconnectErrCh := make(chan error, 10)
defer close(disconnectErrCh)
nc1, err := nats.Connect(s.ClientURL(), uOpt1,
nats.NoReconnect(),
nats.ErrorHandler(func(conn *nats.Conn, s *nats.Subscription, err error) {
disconnectErrCh <- err
}))
require_NoError(t, err)
defer nc1.Close()

// update jwt and observe bearer token get disconnected
updateJwt(t, s.ClientURL(), sysCreds, accJwt2, 1)
select {
case err := <-disconnectErrCh:
require_Contains(t, err.Error(), "authorization violation")
case <-time.After(time.Second):
t.Fatalf("expected error on disconnect")
}

// assure bearer token is not allowed to connect
_, err = nats.Connect(s.ClientURL(), uOpt1)
require_Error(t, err)

// assure non bearer token can connect
nc2, err := nats.Connect(s.ClientURL(), uOpt2)
require_NoError(t, err)
defer nc2.Close()
}

func TestJetStreamJWTMove(t *testing.T) {
sysKp, syspub := createKey(t)
sysJwt := encodeClaim(t, jwt.NewAccountClaims(syspub), syspub)
Expand Down