github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/pkg/servicetokens/servicetokens.go (about) 1 package servicetokens 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/x509" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/bluele/gcache" 11 jwt "github.com/dgrijalva/jwt-go" 12 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/secrets" 13 "go.aporeto.io/enforcerd/trireme-lib/policy" 14 "go.aporeto.io/enforcerd/trireme-lib/utils/cache" 15 "go.uber.org/zap" 16 ) 17 18 var ( 19 localCache = cache.NewCacheWithExpiration("tokens", time.Second*10) 20 ) 21 22 // JWTClaims is the structure of the claims we are sending on the wire. 23 type JWTClaims struct { 24 jwt.StandardClaims 25 Scopes []string 26 Profile []string 27 Data map[string]string 28 PingPayload *policy.PingPayload `json:",omitempty"` 29 } 30 31 // Verifier keeps all the structures for processing tokens. 32 type Verifier struct { 33 secrets secrets.Secrets 34 globalCert *x509.Certificate 35 tokenCache gcache.Cache 36 sync.RWMutex 37 } 38 39 // NewVerifier creates a new Aporeto JWT Verifier. The globalCertificate is optional 40 // and is needed for configurations that do not transmit the token over the wire. 41 func NewVerifier(s secrets.Secrets, globalCertificate *x509.Certificate) *Verifier { 42 return &Verifier{ 43 secrets: s, 44 globalCert: globalCertificate, 45 // tokenCache will cache the token results to accelerate performance 46 tokenCache: gcache.New(2048).LRU().Expiration(20 * time.Second).Build(), 47 } 48 } 49 50 // ParseToken parses and validates the JWT token, give the publicKey. It returns the scopes 51 // the identity and the subject of the provided token. These tokens are strictly 52 // signed with EC. 53 // TODO: We can be more flexible with the algorithm selection here. 54 func (p *Verifier) ParseToken(token string, publicKey string) (string, []string, []string, *policy.PingPayload, error) { 55 p.RLock() 56 defer p.RUnlock() 57 58 if data, _ := p.tokenCache.Get(token); data != nil { 59 claims := data.(*JWTClaims) 60 return claims.Subject, claims.Scopes, claims.Profile, claims.PingPayload, nil 61 } 62 63 // if a public key is transmitted in the wire, we need to verify its validity and use it. 64 // Otherwise we use the public key of the stored secrets. 65 var key *ecdsa.PublicKey 66 var ok bool 67 var enforcerclaims []string 68 69 if len(publicKey) > 0 { 70 // Public keys are cached and verified and they are the compact keys 71 // that we transmit in all other requests signed by the CA. These keys 72 // are not full certificates. 73 gKey, rootClaims, _, _, err := p.secrets.KeyAndClaims([]byte(publicKey)) 74 if err != nil { 75 return "", nil, nil, nil, fmt.Errorf("Cannot validate public key: %s", err) 76 } 77 enforcerclaims = rootClaims 78 key, ok = gKey.(*ecdsa.PublicKey) 79 if !ok { 80 return "", nil, nil, nil, fmt.Errorf("Provided public key not supported") 81 } 82 } else { 83 if p.globalCert == nil { 84 return "", nil, nil, nil, fmt.Errorf("Cannot validate global public key") 85 } 86 key, ok = p.globalCert.PublicKey.(*ecdsa.PublicKey) 87 if !ok { 88 return "", nil, nil, nil, fmt.Errorf("Global public key is not supported") 89 } 90 } 91 92 claims := &JWTClaims{} 93 if _, err := jwt.ParseWithClaims(token, claims, func(*jwt.Token) (interface{}, error) { // nolint 94 return key, nil 95 }); err != nil { 96 return "", nil, nil, nil, err 97 } 98 claims.Profile = append(claims.Profile, enforcerclaims...) 99 if err := p.tokenCache.Set(token, claims); err != nil { 100 zap.L().Error("Failed to cache token", zap.Error(err)) 101 } 102 103 for k, v := range claims.Data { 104 claims.Scopes = append(claims.Scopes, "data:"+k+"="+v) 105 } 106 return claims.Subject, claims.Scopes, claims.Profile, claims.PingPayload, nil 107 } 108 109 // UpdateSecrets updates the secrets of the token Verifier. 110 func (p *Verifier) UpdateSecrets(s secrets.Secrets, globalCert *x509.Certificate) { 111 p.Lock() 112 defer p.Unlock() 113 114 p.secrets = s 115 p.globalCert = globalCert 116 } 117 118 // CreateAndSign creates a new JWT token based on the Aporeto identities. 119 func CreateAndSign(server string, profile, scopes []string, id string, validity time.Duration, gkey interface{}, pingPayload *policy.PingPayload) (string, error) { 120 key, ok := gkey.(*ecdsa.PrivateKey) 121 if !ok { 122 return "", fmt.Errorf("Not a valid private key format") 123 } 124 if token, err := localCache.Get(id); err == nil { 125 return token.(string), nil 126 } 127 claims := &JWTClaims{ 128 StandardClaims: jwt.StandardClaims{ 129 Issuer: server, 130 ExpiresAt: time.Now().Add(validity).Unix(), 131 Subject: id, 132 }, 133 Profile: profile, 134 Scopes: scopes, 135 PingPayload: pingPayload, 136 } 137 138 token, err := jwt.NewWithClaims(jwt.SigningMethodES256, claims).SignedString(key) 139 if err != nil { 140 return "", err 141 } 142 143 // pingPayload should be nil for non-ping requests. If pingPayload is not nil, 144 // we disable the token caching. 145 if pingPayload == nil { 146 localCache.AddOrUpdate(id, token) 147 } 148 return token, nil 149 }