storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/auth-handler.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2015-2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/subtle"
    23  	"encoding/base64"
    24  	"encoding/hex"
    25  	"errors"
    26  	"io"
    27  	"io/ioutil"
    28  	"net/http"
    29  	"strconv"
    30  	"strings"
    31  	"sync/atomic"
    32  	"time"
    33  
    34  	xhttp "storj.io/minio/cmd/http"
    35  	xjwt "storj.io/minio/cmd/jwt"
    36  	"storj.io/minio/cmd/logger"
    37  	"storj.io/minio/pkg/auth"
    38  	objectlock "storj.io/minio/pkg/bucket/object/lock"
    39  	"storj.io/minio/pkg/bucket/policy"
    40  	"storj.io/minio/pkg/etag"
    41  	"storj.io/minio/pkg/hash"
    42  	iampolicy "storj.io/minio/pkg/iam/policy"
    43  )
    44  
    45  // Verify if request has JWT.
    46  func isRequestJWT(r *http.Request) bool {
    47  	return strings.HasPrefix(r.Header.Get(xhttp.Authorization), jwtAlgorithm)
    48  }
    49  
    50  // Verify if request has AWS Signature Version '4'.
    51  func isRequestSignatureV4(r *http.Request) bool {
    52  	return strings.HasPrefix(r.Header.Get(xhttp.Authorization), signV4Algorithm)
    53  }
    54  
    55  // Verify if request has AWS Signature Version '2'.
    56  func isRequestSignatureV2(r *http.Request) bool {
    57  	return (!strings.HasPrefix(r.Header.Get(xhttp.Authorization), signV4Algorithm) &&
    58  		strings.HasPrefix(r.Header.Get(xhttp.Authorization), signV2Algorithm))
    59  }
    60  
    61  // Verify if request has AWS PreSign Version '4'.
    62  func isRequestPresignedSignatureV4(r *http.Request) bool {
    63  	_, ok := r.URL.Query()[xhttp.AmzCredential]
    64  	return ok
    65  }
    66  
    67  // Verify request has AWS PreSign Version '2'.
    68  func isRequestPresignedSignatureV2(r *http.Request) bool {
    69  	_, ok := r.URL.Query()[xhttp.AmzAccessKeyID]
    70  	return ok
    71  }
    72  
    73  // Verify if request has AWS Post policy Signature Version '4'.
    74  func isRequestPostPolicySignatureV4(r *http.Request) bool {
    75  	return strings.Contains(r.Header.Get(xhttp.ContentType), "multipart/form-data") &&
    76  		r.Method == http.MethodPost
    77  }
    78  
    79  // Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
    80  func isRequestSignStreamingV4(r *http.Request) bool {
    81  	return r.Header.Get(xhttp.AmzContentSha256) == streamingContentSHA256 &&
    82  		r.Method == http.MethodPut
    83  }
    84  
    85  // Authorization type.
    86  type authType int
    87  
    88  // List of all supported auth types.
    89  const (
    90  	authTypeUnknown authType = iota
    91  	authTypeAnonymous
    92  	authTypePresigned
    93  	authTypePresignedV2
    94  	authTypePostPolicy
    95  	authTypeStreamingSigned
    96  	authTypeSigned
    97  	authTypeSignedV2
    98  	authTypeJWT
    99  	authTypeSTS
   100  )
   101  
   102  // Get request authentication type.
   103  func getRequestAuthType(r *http.Request) authType {
   104  	if isRequestSignatureV2(r) {
   105  		return authTypeSignedV2
   106  	} else if isRequestPresignedSignatureV2(r) {
   107  		return authTypePresignedV2
   108  	} else if isRequestSignStreamingV4(r) {
   109  		return authTypeStreamingSigned
   110  	} else if isRequestSignatureV4(r) {
   111  		return authTypeSigned
   112  	} else if isRequestPresignedSignatureV4(r) {
   113  		return authTypePresigned
   114  	} else if isRequestJWT(r) {
   115  		return authTypeJWT
   116  	} else if isRequestPostPolicySignatureV4(r) {
   117  		return authTypePostPolicy
   118  	} else if _, ok := r.URL.Query()[xhttp.Action]; ok {
   119  		return authTypeSTS
   120  	} else if _, ok := r.Header[xhttp.Authorization]; !ok {
   121  		return authTypeAnonymous
   122  	}
   123  	return authTypeUnknown
   124  }
   125  
   126  func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, map[string]interface{}, bool, APIErrorCode) {
   127  	var cred auth.Credentials
   128  	var owner bool
   129  	s3Err := ErrAccessDenied
   130  	if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
   131  		getRequestAuthType(r) == authTypeSigned && !skipContentSha256Cksum(r) {
   132  		// We only support admin credentials to access admin APIs.
   133  		cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
   134  		if s3Err != ErrNone {
   135  			return cred, nil, owner, s3Err
   136  		}
   137  
   138  		// we only support V4 (no presign) with auth body
   139  		s3Err = isReqAuthenticated(ctx, r, region, serviceS3)
   140  	}
   141  	if s3Err != ErrNone {
   142  		reqInfo := (&logger.ReqInfo{}).AppendTags("requestHeaders", dumpRequest(r))
   143  		ctx := logger.SetReqInfo(ctx, reqInfo)
   144  		logger.LogIf(ctx, errors.New(GetAPIError(s3Err).Description), logger.Application)
   145  		return cred, nil, owner, s3Err
   146  	}
   147  
   148  	claims, s3Err := checkClaimsFromToken(r, cred)
   149  	if s3Err != ErrNone {
   150  		return cred, nil, owner, s3Err
   151  	}
   152  
   153  	return cred, claims, owner, ErrNone
   154  }
   155  
   156  // checkAdminRequestAuth checks for authentication and authorization for the incoming
   157  // request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
   158  // are automatically rejected.
   159  func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
   160  	cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
   161  	if s3Err != ErrNone {
   162  		return cred, s3Err
   163  	}
   164  	if GlobalIAMSys.IsAllowed(iampolicy.Args{
   165  		AccountName:     cred.AccessKey,
   166  		Groups:          cred.Groups,
   167  		Action:          iampolicy.Action(action),
   168  		ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
   169  		IsOwner:         owner,
   170  		Claims:          claims,
   171  	}) {
   172  		// Request is allowed return the appropriate access key.
   173  		return cred, ErrNone
   174  	}
   175  
   176  	return cred, ErrAccessDenied
   177  }
   178  
   179  // Fetch the security token set by the client.
   180  func getSessionToken(r *http.Request) (token string) {
   181  	token = r.Header.Get(xhttp.AmzSecurityToken)
   182  	if token != "" {
   183  		return token
   184  	}
   185  	return r.URL.Query().Get(xhttp.AmzSecurityToken)
   186  }
   187  
   188  // Fetch claims in the security token returned by the client, doesn't return
   189  // errors - upon errors the returned claims map will be empty.
   190  func mustGetClaimsFromToken(r *http.Request) map[string]interface{} {
   191  	claims, _ := getClaimsFromToken(getSessionToken(r))
   192  	return claims
   193  }
   194  
   195  // Fetch claims in the security token returned by the client.
   196  func getClaimsFromToken(token string) (map[string]interface{}, error) {
   197  	if token == "" {
   198  		claims := xjwt.NewMapClaims()
   199  		return claims.Map(), nil
   200  	}
   201  
   202  	// JWT token for x-amz-security-token is signed with admin
   203  	// secret key, temporary credentials become invalid if
   204  	// server admin credentials change. This is done to ensure
   205  	// that clients cannot decode the token using the temp
   206  	// secret keys and generate an entirely new claim by essentially
   207  	// hijacking the policies. We need to make sure that this is
   208  	// based an admin credential such that token cannot be decoded
   209  	// on the client side and is treated like an opaque value.
   210  	claims, err := auth.ExtractClaims(token, globalActiveCred.SecretKey)
   211  	if err != nil {
   212  		return nil, errAuthentication
   213  	}
   214  
   215  	if GlobalPolicyOPA == nil {
   216  		// If OPA is not set and if ldap claim key is set, allow the claim.
   217  		if _, ok := claims.MapClaims[ldapUser]; ok {
   218  			return claims.Map(), nil
   219  		}
   220  
   221  		// If OPA is not set, session token should
   222  		// have a policy and its mandatory, reject
   223  		// requests without policy claim.
   224  		_, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()]
   225  		_, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]
   226  		if !pokOpenID && !pokSA {
   227  			return nil, errAuthentication
   228  		}
   229  
   230  		sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
   231  		if !spok {
   232  			return claims.Map(), nil
   233  		}
   234  		// Looks like subpolicy is set and is a string, if set then its
   235  		// base64 encoded, decode it. Decoding fails reject such requests.
   236  		spBytes, err := base64.StdEncoding.DecodeString(sp)
   237  		if err != nil {
   238  			// Base64 decoding fails, we should log to indicate
   239  			// something is malforming the request sent by client.
   240  			logger.LogIf(GlobalContext, err, logger.Application)
   241  			return nil, errAuthentication
   242  		}
   243  		claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
   244  	}
   245  
   246  	return claims.Map(), nil
   247  }
   248  
   249  // Fetch claims in the security token returned by the client and validate the token.
   250  func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]interface{}, APIErrorCode) {
   251  	token := getSessionToken(r)
   252  	if token != "" && cred.AccessKey == "" {
   253  		return nil, ErrNoAccessKey
   254  	}
   255  	if cred.IsServiceAccount() && token == "" {
   256  		token = cred.SessionToken
   257  	}
   258  	if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
   259  		return nil, ErrInvalidToken
   260  	}
   261  	claims, err := getClaimsFromToken(token)
   262  	if err != nil {
   263  		return nil, toAPIErrorCode(r.Context(), err)
   264  	}
   265  	return claims, ErrNone
   266  }
   267  
   268  // Check request auth type verifies the incoming http request
   269  // - validates the request signature
   270  // - validates the policy action if anonymous tests bucket policies if any,
   271  //   for authenticated requests validates IAM policies.
   272  // returns APIErrorCode if any to be replied to the client.
   273  func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) {
   274  	_, _, s3Err = CheckRequestAuthTypeCredential(ctx, r, action, bucketName, objectName)
   275  	return s3Err
   276  }
   277  
   278  // Check request auth type verifies the incoming http request
   279  // - validates the request signature
   280  // - validates the policy action if anonymous tests bucket policies if any,
   281  //   for authenticated requests validates IAM policies.
   282  // returns APIErrorCode if any to be replied to the client.
   283  // Additionally returns the accessKey used in the request, and if this request is by an admin.
   284  func CheckRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (cred auth.Credentials, owner bool, s3Err APIErrorCode) {
   285  	switch getRequestAuthType(r) {
   286  	case authTypeUnknown, authTypeStreamingSigned:
   287  		return cred, owner, ErrSignatureVersionNotSupported
   288  	case authTypePresignedV2, authTypeSignedV2:
   289  		if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
   290  			return cred, owner, s3Err
   291  		}
   292  		cred, owner, s3Err = getReqAccessKeyV2(r)
   293  	case authTypeSigned, authTypePresigned:
   294  		region := globalServerRegion
   295  		switch action {
   296  		case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
   297  			region = ""
   298  		}
   299  		if s3Err = isReqAuthenticated(ctx, r, region, serviceS3); s3Err != ErrNone {
   300  			return cred, owner, s3Err
   301  		}
   302  		cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
   303  	}
   304  	if s3Err != ErrNone {
   305  		return cred, owner, s3Err
   306  	}
   307  
   308  	var claims map[string]interface{}
   309  	claims, s3Err = checkClaimsFromToken(r, cred)
   310  	if s3Err != ErrNone {
   311  		return cred, owner, s3Err
   312  	}
   313  
   314  	// LocationConstraint is valid only for CreateBucketAction.
   315  	var locationConstraint string
   316  	if action == policy.CreateBucketAction {
   317  		// To extract region from XML in request body, get copy of request body.
   318  		payload, err := ioutil.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
   319  		if err != nil {
   320  			logger.LogIf(ctx, err, logger.Application)
   321  			return cred, owner, ErrMalformedXML
   322  		}
   323  
   324  		// Populate payload to extract location constraint.
   325  		r.Body = ioutil.NopCloser(bytes.NewReader(payload))
   326  
   327  		var s3Error APIErrorCode
   328  		locationConstraint, s3Error = parseLocationConstraint(r)
   329  		if s3Error != ErrNone {
   330  			return cred, owner, s3Error
   331  		}
   332  
   333  		// Populate payload again to handle it in HTTP handler.
   334  		r.Body = ioutil.NopCloser(bytes.NewReader(payload))
   335  	}
   336  	if cred.AccessKey != "" {
   337  		logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
   338  	}
   339  	if cred.AccessGrant != "" {
   340  		logger.GetReqInfo(ctx).AccessGrant = cred.AccessGrant
   341  	}
   342  
   343  	if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
   344  		// Anonymous checks are not meant for ListBuckets action
   345  		if globalPolicySys.IsAllowed(policy.Args{
   346  			AccountName:     cred.AccessKey,
   347  			Action:          action,
   348  			BucketName:      bucketName,
   349  			ConditionValues: getConditionValues(r, locationConstraint, "", nil),
   350  			IsOwner:         false,
   351  			ObjectName:      objectName,
   352  		}) {
   353  			// Request is allowed return the appropriate access key.
   354  			return cred, owner, ErrNone
   355  		}
   356  
   357  		if action == policy.ListBucketVersionsAction {
   358  			// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
   359  			// verify as a fallback.
   360  			if globalPolicySys.IsAllowed(policy.Args{
   361  				AccountName:     cred.AccessKey,
   362  				Action:          policy.ListBucketAction,
   363  				BucketName:      bucketName,
   364  				ConditionValues: getConditionValues(r, locationConstraint, "", nil),
   365  				IsOwner:         false,
   366  				ObjectName:      objectName,
   367  			}) {
   368  				// Request is allowed return the appropriate access key.
   369  				return cred, owner, ErrNone
   370  			}
   371  		}
   372  
   373  		return cred, owner, ErrAccessDenied
   374  	}
   375  
   376  	if GlobalIAMSys.IsAllowed(iampolicy.Args{
   377  		AccountName:     cred.AccessKey,
   378  		Groups:          cred.Groups,
   379  		Action:          iampolicy.Action(action),
   380  		BucketName:      bucketName,
   381  		ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
   382  		ObjectName:      objectName,
   383  		IsOwner:         owner,
   384  		Claims:          claims,
   385  	}) {
   386  		// Request is allowed return the appropriate access key.
   387  		return cred, owner, ErrNone
   388  	}
   389  
   390  	if action == policy.ListBucketVersionsAction {
   391  		// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
   392  		// verify as a fallback.
   393  		if GlobalIAMSys.IsAllowed(iampolicy.Args{
   394  			AccountName:     cred.AccessKey,
   395  			Groups:          cred.Groups,
   396  			Action:          iampolicy.ListBucketAction,
   397  			BucketName:      bucketName,
   398  			ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
   399  			ObjectName:      objectName,
   400  			IsOwner:         owner,
   401  			Claims:          claims,
   402  		}) {
   403  			// Request is allowed return the appropriate access key.
   404  			return cred, owner, ErrNone
   405  		}
   406  	}
   407  
   408  	return cred, owner, ErrAccessDenied
   409  }
   410  
   411  // Verify if request has valid AWS Signature Version '2'.
   412  func isReqAuthenticatedV2(r *http.Request) (s3Error APIErrorCode) {
   413  	if isRequestSignatureV2(r) {
   414  		return doesSignV2Match(r)
   415  	}
   416  	return doesPresignV2SignatureMatch(r)
   417  }
   418  
   419  func reqSignatureV4Verify(r *http.Request, region string, stype serviceType) (s3Error APIErrorCode) {
   420  	sha256sum := getContentSha256Cksum(r, stype)
   421  	switch {
   422  	case isRequestSignatureV4(r):
   423  		return doesSignatureMatch(sha256sum, r, region, stype)
   424  	case isRequestPresignedSignatureV4(r):
   425  		return doesPresignedSignatureMatch(sha256sum, r, region, stype)
   426  	default:
   427  		return ErrAccessDenied
   428  	}
   429  }
   430  
   431  // Verify if request has valid AWS Signature Version '4'.
   432  func isReqAuthenticated(ctx context.Context, r *http.Request, region string, stype serviceType) (s3Error APIErrorCode) {
   433  	if errCode := reqSignatureV4Verify(r, region, stype); errCode != ErrNone {
   434  		return errCode
   435  	}
   436  
   437  	clientETag, err := etag.FromContentMD5(r.Header)
   438  	if err != nil {
   439  		return ErrInvalidDigest
   440  	}
   441  
   442  	// Extract either 'X-Amz-Content-Sha256' header or 'X-Amz-Content-Sha256' query parameter (if V4 presigned)
   443  	// Do not verify 'X-Amz-Content-Sha256' if skipSHA256.
   444  	var contentSHA256 []byte
   445  	if skipSHA256 := skipContentSha256Cksum(r); !skipSHA256 && isRequestPresignedSignatureV4(r) {
   446  		if sha256Sum, ok := r.URL.Query()[xhttp.AmzContentSha256]; ok && len(sha256Sum) > 0 {
   447  			contentSHA256, err = hex.DecodeString(sha256Sum[0])
   448  			if err != nil {
   449  				return ErrContentSHA256Mismatch
   450  			}
   451  		}
   452  	} else if _, ok := r.Header[xhttp.AmzContentSha256]; !skipSHA256 && ok {
   453  		contentSHA256, err = hex.DecodeString(r.Header.Get(xhttp.AmzContentSha256))
   454  		if err != nil || len(contentSHA256) == 0 {
   455  			return ErrContentSHA256Mismatch
   456  		}
   457  	}
   458  
   459  	// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
   460  	// The verification happens implicit during reading.
   461  	reader, err := hash.NewReader(r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
   462  	if err != nil {
   463  		return toAPIErrorCode(ctx, err)
   464  	}
   465  	r.Body = reader
   466  	return ErrNone
   467  }
   468  
   469  // List of all support S3 auth types.
   470  var supportedS3AuthTypes = map[authType]struct{}{
   471  	authTypeAnonymous:       {},
   472  	authTypePresigned:       {},
   473  	authTypePresignedV2:     {},
   474  	authTypeSigned:          {},
   475  	authTypeSignedV2:        {},
   476  	authTypePostPolicy:      {},
   477  	authTypeStreamingSigned: {},
   478  }
   479  
   480  // Validate if the authType is valid and supported.
   481  func isSupportedS3AuthType(aType authType) bool {
   482  	_, ok := supportedS3AuthTypes[aType]
   483  	return ok
   484  }
   485  
   486  // setAuthHandler to validate authorization header for the incoming request.
   487  func setAuthHandler(h http.Handler) http.Handler {
   488  	// handler for validating incoming authorization headers.
   489  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   490  		aType := getRequestAuthType(r)
   491  		if isSupportedS3AuthType(aType) {
   492  			// Let top level caller validate for anonymous and known signed requests.
   493  			h.ServeHTTP(w, r)
   494  			return
   495  		} else if aType == authTypeJWT {
   496  			// Validate Authorization header if its valid for JWT request.
   497  			if _, _, authErr := webRequestAuthenticate(r); authErr != nil {
   498  				w.WriteHeader(http.StatusUnauthorized)
   499  				w.Write([]byte(authErr.Error()))
   500  				return
   501  			}
   502  			h.ServeHTTP(w, r)
   503  			return
   504  		} else if aType == authTypeSTS {
   505  			h.ServeHTTP(w, r)
   506  			return
   507  		}
   508  		// NOTE(artur): for requests having an empty Authorization header, the
   509  		// "real" S3 returns the AccessDenied error code. We do the same thing
   510  		// to be compatible with ceph/splunk-s3-tests. There are plenty of other
   511  		// incompatibilities in there, but they are not easily fixable with how
   512  		// MinIO is structured, so we do at least this.
   513  		WriteErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL, guessIsBrowserReq(r))
   514  		atomic.AddUint64(&globalHTTPStats.rejectedRequestsAuth, 1)
   515  	})
   516  }
   517  
   518  func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool, map[string]interface{}, APIErrorCode) {
   519  	var cred auth.Credentials
   520  	var owner bool
   521  	var s3Err APIErrorCode
   522  	switch atype {
   523  	case authTypeUnknown, authTypeStreamingSigned:
   524  		return cred, owner, nil, ErrSignatureVersionNotSupported
   525  	case authTypeSignedV2, authTypePresignedV2:
   526  		if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
   527  			return cred, owner, nil, s3Err
   528  		}
   529  		cred, owner, s3Err = getReqAccessKeyV2(r)
   530  	case authTypePresigned, authTypeSigned:
   531  		region := globalServerRegion
   532  		if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
   533  			return cred, owner, nil, s3Err
   534  		}
   535  		cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
   536  	}
   537  	if s3Err != ErrNone {
   538  		return cred, owner, nil, s3Err
   539  	}
   540  
   541  	claims, s3Err := checkClaimsFromToken(r, cred)
   542  	if s3Err != ErrNone {
   543  		return cred, owner, nil, s3Err
   544  	}
   545  
   546  	return cred, owner, claims, ErrNone
   547  }
   548  
   549  func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate time.Time, retMode objectlock.RetMode, byPassSet bool, r *http.Request, cred auth.Credentials, owner bool, claims map[string]interface{}) (s3Err APIErrorCode) {
   550  	var retSet bool
   551  	if cred.AccessKey == "" {
   552  		conditions := getConditionValues(r, "", "", nil)
   553  		conditions["object-lock-mode"] = []string{string(retMode)}
   554  		conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
   555  		if retDays > 0 {
   556  			conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
   557  		}
   558  		if retMode == objectlock.RetGovernance && byPassSet {
   559  			byPassSet = globalPolicySys.IsAllowed(policy.Args{
   560  				AccountName:     cred.AccessKey,
   561  				Groups:          cred.Groups,
   562  				Action:          policy.BypassGovernanceRetentionAction,
   563  				BucketName:      bucketName,
   564  				ConditionValues: conditions,
   565  				IsOwner:         false,
   566  				ObjectName:      objectName,
   567  			})
   568  		}
   569  		if globalPolicySys.IsAllowed(policy.Args{
   570  			AccountName:     cred.AccessKey,
   571  			Groups:          cred.Groups,
   572  			Action:          policy.PutObjectRetentionAction,
   573  			BucketName:      bucketName,
   574  			ConditionValues: conditions,
   575  			IsOwner:         false,
   576  			ObjectName:      objectName,
   577  		}) {
   578  			retSet = true
   579  		}
   580  		if byPassSet || retSet {
   581  			return ErrNone
   582  		}
   583  		return ErrAccessDenied
   584  	}
   585  
   586  	conditions := getConditionValues(r, "", cred.AccessKey, claims)
   587  	conditions["object-lock-mode"] = []string{string(retMode)}
   588  	conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
   589  	if retDays > 0 {
   590  		conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
   591  	}
   592  	if retMode == objectlock.RetGovernance && byPassSet {
   593  		byPassSet = GlobalIAMSys.IsAllowed(iampolicy.Args{
   594  			AccountName:     cred.AccessKey,
   595  			Groups:          cred.Groups,
   596  			Action:          iampolicy.BypassGovernanceRetentionAction,
   597  			BucketName:      bucketName,
   598  			ObjectName:      objectName,
   599  			ConditionValues: conditions,
   600  			IsOwner:         owner,
   601  			Claims:          claims,
   602  		})
   603  	}
   604  	if GlobalIAMSys.IsAllowed(iampolicy.Args{
   605  		AccountName:     cred.AccessKey,
   606  		Groups:          cred.Groups,
   607  		Action:          iampolicy.PutObjectRetentionAction,
   608  		BucketName:      bucketName,
   609  		ConditionValues: conditions,
   610  		ObjectName:      objectName,
   611  		IsOwner:         owner,
   612  		Claims:          claims,
   613  	}) {
   614  		retSet = true
   615  	}
   616  	if byPassSet || retSet {
   617  		return ErrNone
   618  	}
   619  	return ErrAccessDenied
   620  }
   621  
   622  // isPutActionAllowed - check if PUT operation is allowed on the resource, this
   623  // call verifies bucket policies and IAM policies, supports multi user
   624  // checks etc.
   625  func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
   626  	var cred auth.Credentials
   627  	var owner bool
   628  	switch atype {
   629  	case authTypeUnknown:
   630  		return ErrSignatureVersionNotSupported
   631  	case authTypeSignedV2, authTypePresignedV2:
   632  		cred, owner, s3Err = getReqAccessKeyV2(r)
   633  	case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
   634  		region := globalServerRegion
   635  		cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
   636  	}
   637  	if s3Err != ErrNone {
   638  		return s3Err
   639  	}
   640  
   641  	claims, s3Err := checkClaimsFromToken(r, cred)
   642  	if s3Err != ErrNone {
   643  		return s3Err
   644  	}
   645  
   646  	if cred.AccessKey != "" {
   647  		logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
   648  	}
   649  	if cred.AccessGrant != "" {
   650  		logger.GetReqInfo(ctx).AccessGrant = cred.AccessGrant
   651  	}
   652  
   653  	// Do not check for PutObjectRetentionAction permission,
   654  	// if mode and retain until date are not set.
   655  	// Can happen when bucket has default lock config set
   656  	if action == iampolicy.PutObjectRetentionAction &&
   657  		r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
   658  		r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
   659  		return ErrNone
   660  	}
   661  
   662  	if cred.AccessKey == "" {
   663  		if globalPolicySys.IsAllowed(policy.Args{
   664  			AccountName:     cred.AccessKey,
   665  			Groups:          cred.Groups,
   666  			Action:          policy.Action(action),
   667  			BucketName:      bucketName,
   668  			ConditionValues: getConditionValues(r, "", "", nil),
   669  			IsOwner:         false,
   670  			ObjectName:      objectName,
   671  		}) {
   672  			return ErrNone
   673  		}
   674  		return ErrAccessDenied
   675  	}
   676  
   677  	if GlobalIAMSys.IsAllowed(iampolicy.Args{
   678  		AccountName:     cred.AccessKey,
   679  		Groups:          cred.Groups,
   680  		Action:          action,
   681  		BucketName:      bucketName,
   682  		ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
   683  		ObjectName:      objectName,
   684  		IsOwner:         owner,
   685  		Claims:          claims,
   686  	}) {
   687  		return ErrNone
   688  	}
   689  	return ErrAccessDenied
   690  }