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  }