Skip to content

Commit

Permalink
Add jwt bearer authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
kthomas committed Sep 28, 2020
1 parent 2792fd2 commit 5c0b7cb
Show file tree
Hide file tree
Showing 26 changed files with 2,053 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module github.com/nats-io/nats-server/v2

require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/minio/highwayhash v1.0.0
github.com/nats-io/jwt/v2 v2.0.0-20200916203241-1f8ce17dff02
github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
Expand Down
7 changes: 7 additions & 0 deletions server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ func (s *Server) configureAuthorization() {
return
}

bearerAuth, err := BearerAuthFactory(s)
if err != nil {
s.Debugf("Bearer authorization not configured; %s", err.Error())
} else {
opts.CustomClientAuthentication = bearerAuth
}

// Check for multiple users first
// This just checks and sets up the user map if we have multiple users.
if opts.CustomClientAuthentication != nil {
Expand Down
117 changes: 117 additions & 0 deletions server/bearer_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package server

import (
"crypto/rsa"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/dgrijalva/jwt-go"
natsjwt "github.com/nats-io/jwt/v2"
)

type BearerAuth struct {
server *Server
publicKey *rsa.PublicKey
}

func BearerAuthFactory(s *Server) (*BearerAuth, error) {
auth := &BearerAuth{
server: s,
}
err := auth.readPublicKey()
if err != nil {
return nil, fmt.Errorf("failed to read JWT_SIGNER_PUBLIC_KEY from environment")
}
return auth, nil
}

func (bearer *BearerAuth) readPublicKey() error {
jwtPublicKeyPEM := strings.Replace(os.Getenv("JWT_SIGNER_PUBLIC_KEY"), `\n`, "\n", -1)
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(jwtPublicKeyPEM))
if err != nil {
return err
}
bearer.publicKey = publicKey
return nil
}

func (bearer *BearerAuth) Check(c ClientAuthentication) bool {
bearerToken := c.GetOpts().JWT
jwtToken, err := jwt.Parse(bearerToken, func(_jwtToken *jwt.Token) (interface{}, error) {
if _, ok := _jwtToken.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("failed to parse bearer authorization; unexpected signing alg: %s", _jwtToken.Method.Alg())
}
return bearer.publicKey, nil
})
if err != nil {
bearer.server.Debugf(fmt.Sprintf("failed to parse bearer authorization; %s", err.Error()))
return false
}

bearer.server.Debugf(fmt.Sprintf("parsed bearer authorization: %s\n; client authentication: %s", jwtToken.Claims, c))
claims, claimsOk := jwtToken.Claims.(jwt.MapClaims)
if !claimsOk {
bearer.server.Warnf(fmt.Sprintf("no claims present in verified JWT; %s", err.Error()))
return false
}

permissions := &Permissions{}
if permissionsClaim, permissionsClaimOk := claims["permissions"].(map[string]interface{}); permissionsClaimOk {
if _, pubOk := permissionsClaim["publish"]; !pubOk {
permissionsClaim["publish"] = map[string]interface{}{
"allow": []string{},
"deny": []string{},
}
}
if _, subOk := permissionsClaim["subscribe"]; !subOk {
permissionsClaim["subscribe"] = map[string]interface{}{
"allow": []string{},
"deny": []string{},
}
}
if _, respOk := permissionsClaim["responses"]; !respOk {
permissionsClaim["responses"] = map[string]interface{}{
"max": DEFAULT_ALLOW_RESPONSE_MAX_MSGS,
"ttl": DEFAULT_ALLOW_RESPONSE_EXPIRATION,
}
}
permissionsRaw, _ := json.Marshal(permissionsClaim)
json.Unmarshal(permissionsRaw, &permissions) // HACK
} else {
bearer.server.Warnf(fmt.Sprintf("no permissions claim present in verified JWT; %s", bearerToken))
return false
}

bearer.server.Debugf("registering user with permissions: %s", permissions)
c.RegisterUser(&User{
Permissions: permissions,
})

if cl, clOk := c.(*client); clOk {
var exp int64
switch expClaim := claims["exp"].(type) {
case float64:
exp = int64(expClaim)
case json.Number:
exp, _ = expClaim.Int64()
default:
bearer.server.Debugf("failed to parse bearer authorization expiration")
return false
}

now := time.Now().Unix()
if now >= exp {
return false
}

bearer.server.Debugf("enforcing authorized expiration: %v", exp)
// validFor := (exp - now) * time.Second
cl.setExpiration(&natsjwt.ClaimsData{
Expires: exp,
}, 0)
}
return true
}
Loading

0 comments on commit 5c0b7cb

Please sign in to comment.