go.temporal.io/server@v1.23.0/common/authorization/default_jwt_claim_mapper.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package authorization
    26  
    27  import (
    28  	"context"
    29  	"fmt"
    30  	"strings"
    31  
    32  	"github.com/golang-jwt/jwt/v4"
    33  	"go.temporal.io/api/serviceerror"
    34  	"go.temporal.io/server/common/primitives"
    35  
    36  	"go.temporal.io/server/common/config"
    37  	"go.temporal.io/server/common/log"
    38  )
    39  
    40  const (
    41  	defaultPermissionsClaimName = "permissions"
    42  	authorizationBearer         = "bearer"
    43  	headerSubject               = "sub"
    44  	permissionScopeSystem       = primitives.SystemLocalNamespace
    45  	permissionRead              = "read"
    46  	permissionWrite             = "write"
    47  	permissionWorker            = "worker"
    48  	permissionAdmin             = "admin"
    49  )
    50  
    51  // Default claim mapper that gives system level admin permission to everybody
    52  type defaultJWTClaimMapper struct {
    53  	keyProvider          TokenKeyProvider
    54  	logger               log.Logger
    55  	permissionsClaimName string
    56  }
    57  
    58  func NewDefaultJWTClaimMapper(provider TokenKeyProvider, cfg *config.Authorization, logger log.Logger) ClaimMapper {
    59  	claimName := cfg.PermissionsClaimName
    60  	if claimName == "" {
    61  		claimName = defaultPermissionsClaimName
    62  	}
    63  	return &defaultJWTClaimMapper{keyProvider: provider, logger: logger, permissionsClaimName: claimName}
    64  }
    65  
    66  var _ ClaimMapper = (*defaultJWTClaimMapper)(nil)
    67  
    68  func (a *defaultJWTClaimMapper) GetClaims(authInfo *AuthInfo) (*Claims, error) {
    69  
    70  	claims := Claims{}
    71  
    72  	if authInfo.AuthToken == "" {
    73  		return &claims, nil
    74  	}
    75  
    76  	parts := strings.Split(authInfo.AuthToken, " ")
    77  	if len(parts) != 2 {
    78  		return nil, serviceerror.NewPermissionDenied("unexpected authorization token format", "")
    79  	}
    80  	if !strings.EqualFold(parts[0], authorizationBearer) {
    81  		return nil, serviceerror.NewPermissionDenied("unexpected name in authorization token", "")
    82  	}
    83  	jwtClaims, err := parseJWTWithAudience(parts[1], a.keyProvider, authInfo.Audience)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	subject, ok := jwtClaims[headerSubject].(string)
    88  	if !ok {
    89  		return nil, serviceerror.NewPermissionDenied("unexpected value type of \"sub\" claim", "")
    90  	}
    91  	claims.Subject = subject
    92  	permissions, ok := jwtClaims[a.permissionsClaimName].([]interface{})
    93  	if ok {
    94  		err := a.extractPermissions(permissions, &claims)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  	}
    99  	return &claims, nil
   100  }
   101  
   102  func (a *defaultJWTClaimMapper) extractPermissions(permissions []interface{}, claims *Claims) error {
   103  	for _, permission := range permissions {
   104  		p, ok := permission.(string)
   105  		if !ok {
   106  			a.logger.Warn(fmt.Sprintf("ignoring permission that is not a string: %v", permission))
   107  			continue
   108  		}
   109  		parts := strings.Split(p, ":")
   110  		if len(parts) != 2 {
   111  			a.logger.Warn(fmt.Sprintf("ignoring permission in unexpected format: %v", permission))
   112  			continue
   113  		}
   114  		namespace := parts[0]
   115  		if namespace == permissionScopeSystem {
   116  			claims.System |= permissionToRole(parts[1])
   117  		} else {
   118  			if claims.Namespaces == nil {
   119  				claims.Namespaces = make(map[string]Role)
   120  			}
   121  			role := claims.Namespaces[namespace]
   122  			role |= permissionToRole(parts[1])
   123  			claims.Namespaces[namespace] = role
   124  		}
   125  	}
   126  	return nil
   127  }
   128  
   129  func parseJWT(tokenString string, keyProvider TokenKeyProvider) (jwt.MapClaims, error) {
   130  	return parseJWTWithAudience(tokenString, keyProvider, "")
   131  }
   132  
   133  func parseJWTWithAudience(tokenString string, keyProvider TokenKeyProvider, audience string) (jwt.MapClaims, error) {
   134  
   135  	parser := jwt.NewParser(jwt.WithValidMethods(keyProvider.SupportedMethods()))
   136  
   137  	var keyFunc jwt.Keyfunc
   138  	if provider, _ := keyProvider.(RawTokenKeyProvider); provider != nil {
   139  		keyFunc = func(token *jwt.Token) (interface{}, error) {
   140  			// reserve context
   141  			// impl may introduce network request to get public key
   142  			return provider.GetKey(context.Background(), token)
   143  		}
   144  	} else {
   145  		keyFunc = func(token *jwt.Token) (interface{}, error) {
   146  			kid, ok := token.Header["kid"].(string)
   147  			if !ok {
   148  				return nil, fmt.Errorf("malformed token - no \"kid\" header")
   149  			}
   150  			alg := token.Header["alg"].(string)
   151  			switch token.Method.(type) {
   152  			case *jwt.SigningMethodHMAC:
   153  				return keyProvider.HmacKey(alg, kid)
   154  			case *jwt.SigningMethodRSA:
   155  				return keyProvider.RsaKey(alg, kid)
   156  			case *jwt.SigningMethodECDSA:
   157  				return keyProvider.EcdsaKey(alg, kid)
   158  			default:
   159  				return nil, serviceerror.NewPermissionDenied(
   160  					fmt.Sprintf("unexpected signing method: %v for algorithm: %v", token.Method, token.Header["alg"]), "")
   161  			}
   162  		}
   163  	}
   164  
   165  	token, err := parser.Parse(tokenString, keyFunc)
   166  
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	claims, ok := token.Claims.(jwt.MapClaims)
   171  	if !ok {
   172  		return nil, serviceerror.NewPermissionDenied("invalid token with no claims", "")
   173  	}
   174  	if err := claims.Valid(); err != nil {
   175  		return nil, err
   176  	}
   177  	if strings.TrimSpace(audience) != "" && !claims.VerifyAudience(audience, true) {
   178  		return nil, serviceerror.NewPermissionDenied("audience mismatch", "")
   179  	}
   180  	return claims, nil
   181  }
   182  
   183  func permissionToRole(permission string) Role {
   184  	switch strings.ToLower(permission) {
   185  	case permissionRead:
   186  		return RoleReader
   187  	case permissionWrite:
   188  		return RoleWriter
   189  	case permissionAdmin:
   190  		return RoleAdmin
   191  	case permissionWorker:
   192  		return RoleWorker
   193  	}
   194  	return RoleUndefined
   195  }