github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/sts-handlers.go (about)

     1  /// Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"crypto/x509"
    24  	"encoding/base64"
    25  	"errors"
    26  	"fmt"
    27  	"net/http"
    28  	"strconv"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/minio/madmin-go/v3"
    33  	"github.com/minio/minio/internal/auth"
    34  	"github.com/minio/minio/internal/config/identity/openid"
    35  	"github.com/minio/minio/internal/hash/sha256"
    36  	xhttp "github.com/minio/minio/internal/http"
    37  	"github.com/minio/minio/internal/logger"
    38  	"github.com/minio/mux"
    39  	"github.com/minio/pkg/v2/policy"
    40  	"github.com/minio/pkg/v2/wildcard"
    41  )
    42  
    43  const (
    44  	// STS API version.
    45  	stsAPIVersion             = "2011-06-15"
    46  	stsVersion                = "Version"
    47  	stsAction                 = "Action"
    48  	stsPolicy                 = "Policy"
    49  	stsToken                  = "Token"
    50  	stsRoleArn                = "RoleArn"
    51  	stsWebIdentityToken       = "WebIdentityToken"
    52  	stsWebIdentityAccessToken = "WebIdentityAccessToken" // only valid if UserInfo is enabled.
    53  	stsDurationSeconds        = "DurationSeconds"
    54  	stsLDAPUsername           = "LDAPUsername"
    55  	stsLDAPPassword           = "LDAPPassword"
    56  
    57  	// STS API action constants
    58  	clientGrants        = "AssumeRoleWithClientGrants"
    59  	webIdentity         = "AssumeRoleWithWebIdentity"
    60  	ldapIdentity        = "AssumeRoleWithLDAPIdentity"
    61  	clientCertificate   = "AssumeRoleWithCertificate"
    62  	customTokenIdentity = "AssumeRoleWithCustomToken"
    63  	assumeRole          = "AssumeRole"
    64  
    65  	stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB
    66  
    67  	// JWT claim keys
    68  	expClaim = "exp"
    69  	subClaim = "sub"
    70  	audClaim = "aud"
    71  	issClaim = "iss"
    72  
    73  	// JWT claim to check the parent user
    74  	parentClaim = "parent"
    75  
    76  	// LDAP claim keys
    77  	ldapUser  = "ldapUser"
    78  	ldapUserN = "ldapUsername"
    79  
    80  	// Role Claim key
    81  	roleArnClaim = "roleArn"
    82  )
    83  
    84  // stsAPIHandlers implements and provides http handlers for AWS STS API.
    85  type stsAPIHandlers struct{}
    86  
    87  // registerSTSRouter - registers AWS STS compatible APIs.
    88  func registerSTSRouter(router *mux.Router) {
    89  	// Initialize STS.
    90  	sts := &stsAPIHandlers{}
    91  
    92  	// STS Router
    93  	stsRouter := router.NewRoute().PathPrefix(SlashSeparator).Subrouter()
    94  
    95  	// Assume roles with no JWT, handles AssumeRole.
    96  	stsRouter.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
    97  		ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType))
    98  		authOk := wildcard.MatchSimple(signV4Algorithm+"*", r.Header.Get(xhttp.Authorization))
    99  		noQueries := len(r.URL.RawQuery) == 0
   100  		return ctypeOk && authOk && noQueries
   101  	}).HandlerFunc(httpTraceAll(sts.AssumeRole))
   102  
   103  	// Assume roles with JWT handler, handles both ClientGrants and WebIdentity.
   104  	stsRouter.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
   105  		ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType))
   106  		noQueries := len(r.URL.RawQuery) == 0
   107  		return ctypeOk && noQueries
   108  	}).HandlerFunc(httpTraceAll(sts.AssumeRoleWithSSO))
   109  
   110  	// AssumeRoleWithClientGrants
   111  	stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)).
   112  		Queries(stsAction, clientGrants).
   113  		Queries(stsVersion, stsAPIVersion).
   114  		Queries(stsToken, "{Token:.*}")
   115  
   116  	// AssumeRoleWithWebIdentity
   117  	stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)).
   118  		Queries(stsAction, webIdentity).
   119  		Queries(stsVersion, stsAPIVersion).
   120  		Queries(stsWebIdentityToken, "{Token:.*}")
   121  
   122  	// AssumeRoleWithLDAPIdentity
   123  	stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithLDAPIdentity)).
   124  		Queries(stsAction, ldapIdentity).
   125  		Queries(stsVersion, stsAPIVersion).
   126  		Queries(stsLDAPUsername, "{LDAPUsername:.*}").
   127  		Queries(stsLDAPPassword, "{LDAPPassword:.*}")
   128  
   129  	// AssumeRoleWithCertificate
   130  	stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithCertificate)).
   131  		Queries(stsAction, clientCertificate).
   132  		Queries(stsVersion, stsAPIVersion)
   133  
   134  	// AssumeRoleWithCustomToken
   135  	stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithCustomToken)).
   136  		Queries(stsAction, customTokenIdentity).
   137  		Queries(stsVersion, stsAPIVersion)
   138  }
   139  
   140  func apiToSTSError(authErr APIErrorCode) (stsErrCode STSErrorCode) {
   141  	switch authErr {
   142  	case ErrSignatureDoesNotMatch, ErrInvalidAccessKeyID, ErrAccessKeyDisabled:
   143  		return ErrSTSAccessDenied
   144  	case ErrServerNotInitialized:
   145  		return ErrSTSNotInitialized
   146  	case ErrInternalError:
   147  		return ErrSTSInternalError
   148  	default:
   149  		return ErrSTSAccessDenied
   150  	}
   151  }
   152  
   153  func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (auth.Credentials, APIErrorCode) {
   154  	if !isRequestSignatureV4(r) {
   155  		return auth.Credentials{}, ErrAccessDenied
   156  	}
   157  
   158  	s3Err := isReqAuthenticated(ctx, r, globalSite.Region, serviceSTS)
   159  	if s3Err != ErrNone {
   160  		return auth.Credentials{}, s3Err
   161  	}
   162  
   163  	user, _, s3Err := getReqAccessKeyV4(r, globalSite.Region, serviceSTS)
   164  	if s3Err != ErrNone {
   165  		return auth.Credentials{}, s3Err
   166  	}
   167  
   168  	// Temporary credentials or Service accounts cannot generate further temporary credentials.
   169  	if user.IsTemp() || user.IsServiceAccount() {
   170  		return auth.Credentials{}, ErrAccessDenied
   171  	}
   172  
   173  	// Session tokens are not allowed in STS AssumeRole requests.
   174  	if getSessionToken(r) != "" {
   175  		return auth.Credentials{}, ErrAccessDenied
   176  	}
   177  
   178  	return user, ErrNone
   179  }
   180  
   181  func parseForm(r *http.Request) error {
   182  	if err := r.ParseForm(); err != nil {
   183  		return err
   184  	}
   185  	for k, v := range r.PostForm {
   186  		if _, ok := r.Form[k]; !ok {
   187  			r.Form[k] = v
   188  		}
   189  	}
   190  	return nil
   191  }
   192  
   193  // getTokenSigningKey returns secret key used to sign JWT session tokens
   194  func getTokenSigningKey() (string, error) {
   195  	secret := globalActiveCred.SecretKey
   196  	if globalSiteReplicationSys.isEnabled() {
   197  		c, err := globalSiteReplicatorCred.Get(GlobalContext)
   198  		if err != nil {
   199  			return "", err
   200  		}
   201  		return c.SecretKey, nil
   202  	}
   203  	return secret, nil
   204  }
   205  
   206  // AssumeRole - implementation of AWS STS API AssumeRole to get temporary
   207  // credentials for regular users on Minio.
   208  // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
   209  func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
   210  	ctx := newContext(r, w, "AssumeRole")
   211  
   212  	claims := make(map[string]interface{})
   213  	defer logger.AuditLog(ctx, w, r, claims)
   214  
   215  	// Check auth here (otherwise r.Form will have unexpected values from
   216  	// the call to `parseForm` below), but return failure only after we are
   217  	// able to validate that it is a valid STS request, so that we are able
   218  	// to send an appropriate audit log.
   219  	user, apiErrCode := checkAssumeRoleAuth(ctx, r)
   220  
   221  	if err := parseForm(r); err != nil {
   222  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   223  		return
   224  	}
   225  
   226  	if r.Form.Get(stsVersion) != stsAPIVersion {
   227  		writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get(stsVersion), stsAPIVersion))
   228  		return
   229  	}
   230  
   231  	action := r.Form.Get(stsAction)
   232  	switch action {
   233  	case assumeRole:
   234  	default:
   235  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
   236  		return
   237  	}
   238  
   239  	ctx = newContext(r, w, action)
   240  
   241  	// Validate the authentication result here so that failures will be audit-logged.
   242  	if apiErrCode != ErrNone {
   243  		stsErr := apiToSTSError(apiErrCode)
   244  		// Borrow the description error from the API error code
   245  		writeSTSErrorResponse(ctx, w, stsErr, fmt.Errorf(errorCodes[apiErrCode].Description))
   246  		return
   247  	}
   248  
   249  	sessionPolicyStr := r.Form.Get(stsPolicy)
   250  	// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
   251  	// The plain text that you use for both inline and managed session
   252  	// policies shouldn't exceed 2048 characters.
   253  	if len(sessionPolicyStr) > 2048 {
   254  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, errSessionPolicyTooLarge)
   255  		return
   256  	}
   257  
   258  	if len(sessionPolicyStr) > 0 {
   259  		sessionPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
   260  		if err != nil {
   261  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   262  			return
   263  		}
   264  
   265  		// Version in policy must not be empty
   266  		if sessionPolicy.Version == "" {
   267  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Version cannot be empty expecting '2012-10-17'"))
   268  			return
   269  		}
   270  	}
   271  
   272  	duration, err := openid.GetDefaultExpiration(r.Form.Get(stsDurationSeconds))
   273  	if err != nil {
   274  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   275  		return
   276  	}
   277  
   278  	claims[expClaim] = UTCNow().Add(duration).Unix()
   279  	claims[parentClaim] = user.AccessKey
   280  
   281  	// Validate that user.AccessKey's policies can be retrieved - it may not
   282  	// be in case the user is disabled.
   283  	if _, err = globalIAMSys.PolicyDBGet(user.AccessKey, user.Groups...); err != nil {
   284  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   285  		return
   286  	}
   287  
   288  	if len(sessionPolicyStr) > 0 {
   289  		claims[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
   290  	}
   291  
   292  	secret, err := getTokenSigningKey()
   293  	if err != nil {
   294  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   295  		return
   296  	}
   297  
   298  	cred, err := auth.GetNewCredentialsWithMetadata(claims, secret)
   299  	if err != nil {
   300  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   301  		return
   302  	}
   303  
   304  	// Set the parent of the temporary access key, so that it's access
   305  	// policy is inherited from `user.AccessKey`.
   306  	cred.ParentUser = user.AccessKey
   307  
   308  	// Set the newly generated credentials.
   309  	updatedAt, err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, "")
   310  	if err != nil {
   311  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   312  		return
   313  	}
   314  
   315  	// Call hook for site replication.
   316  	if cred.ParentUser != globalActiveCred.AccessKey {
   317  		logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
   318  			Type: madmin.SRIAMItemSTSAcc,
   319  			STSCredential: &madmin.SRSTSCredential{
   320  				AccessKey:    cred.AccessKey,
   321  				SecretKey:    cred.SecretKey,
   322  				SessionToken: cred.SessionToken,
   323  				ParentUser:   cred.ParentUser,
   324  			},
   325  			UpdatedAt: updatedAt,
   326  		}))
   327  	}
   328  
   329  	assumeRoleResponse := &AssumeRoleResponse{
   330  		Result: AssumeRoleResult{
   331  			Credentials: cred,
   332  		},
   333  	}
   334  
   335  	assumeRoleResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
   336  	writeSuccessResponseXML(w, encodeResponse(assumeRoleResponse))
   337  }
   338  
   339  func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Request) {
   340  	ctx := newContext(r, w, "AssumeRoleSSOCommon")
   341  
   342  	claims := make(map[string]interface{})
   343  	defer logger.AuditLog(ctx, w, r, claims)
   344  
   345  	// Parse the incoming form data.
   346  	if err := parseForm(r); err != nil {
   347  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   348  		return
   349  	}
   350  
   351  	if r.Form.Get(stsVersion) != stsAPIVersion {
   352  		writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
   353  		return
   354  	}
   355  
   356  	action := r.Form.Get(stsAction)
   357  	switch action {
   358  	case ldapIdentity:
   359  		sts.AssumeRoleWithLDAPIdentity(w, r)
   360  		return
   361  	case clientGrants, webIdentity:
   362  	default:
   363  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
   364  		return
   365  	}
   366  
   367  	ctx = newContext(r, w, action)
   368  
   369  	token := r.Form.Get(stsToken)
   370  	if token == "" {
   371  		token = r.Form.Get(stsWebIdentityToken)
   372  	}
   373  
   374  	accessToken := r.Form.Get(stsWebIdentityAccessToken)
   375  
   376  	// RoleARN parameter processing: If a role ARN is given in the request, we
   377  	// use that and validate the authentication request. If not, we assume this
   378  	// is an STS request for a claim based IDP (if one is present) and set
   379  	// roleArn = openid.DummyRoleARN.
   380  	//
   381  	// Currently, we do not support multiple claim based IDPs, as there is no
   382  	// defined parameter to disambiguate the intended IDP in this STS request.
   383  	roleArn := openid.DummyRoleARN
   384  	roleArnStr := r.Form.Get(stsRoleArn)
   385  	if roleArnStr != "" {
   386  		var err error
   387  		roleArn, _, err = globalIAMSys.GetRolePolicy(roleArnStr)
   388  		if err != nil {
   389  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue,
   390  				fmt.Errorf("Error processing %s parameter: %v", stsRoleArn, err))
   391  			return
   392  		}
   393  	}
   394  
   395  	if !globalIAMSys.Initialized() {
   396  		writeSTSErrorResponse(ctx, w, ErrSTSIAMNotInitialized, errIAMNotInitialized)
   397  		return
   398  	}
   399  
   400  	// Validate JWT; check clientID in claims matches the one associated with the roleArn
   401  	if err := globalIAMSys.OpenIDConfig.Validate(r.Context(), roleArn, token, accessToken, r.Form.Get(stsDurationSeconds), claims); err != nil {
   402  		switch err {
   403  		case openid.ErrTokenExpired:
   404  			switch action {
   405  			case clientGrants:
   406  				writeSTSErrorResponse(ctx, w, ErrSTSClientGrantsExpiredToken, err)
   407  			case webIdentity:
   408  				writeSTSErrorResponse(ctx, w, ErrSTSWebIdentityExpiredToken, err)
   409  			}
   410  			return
   411  		case auth.ErrInvalidDuration:
   412  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   413  			return
   414  		}
   415  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   416  		return
   417  	}
   418  
   419  	var policyName string
   420  	if roleArnStr != "" && globalIAMSys.HasRolePolicy() {
   421  		// If roleArn is used, we set it as a claim, and use the
   422  		// associated policy when credentials are used.
   423  		claims[roleArnClaim] = roleArn.String()
   424  	} else {
   425  		// If no role policy is configured, then we use claims from the
   426  		// JWT. This is a MinIO STS API specific value, this value
   427  		// should be set and configured on your identity provider as
   428  		// part of JWT custom claims.
   429  		policySet, ok := policy.GetPoliciesFromClaims(claims, iamPolicyClaimNameOpenID())
   430  		policies := strings.Join(policySet.ToSlice(), ",")
   431  		if ok {
   432  			policyName = globalIAMSys.CurrentPolicies(policies)
   433  		}
   434  
   435  		if newGlobalAuthZPluginFn() == nil {
   436  			if !ok {
   437  				writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue,
   438  					fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID()))
   439  				return
   440  			} else if policyName == "" {
   441  				writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue,
   442  					fmt.Errorf("None of the given policies (`%s`) are defined, credentials will not be generated", policies))
   443  				return
   444  			}
   445  		}
   446  		claims[iamPolicyClaimNameOpenID()] = policyName
   447  	}
   448  
   449  	sessionPolicyStr := r.Form.Get(stsPolicy)
   450  	// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
   451  	// The plain text that you use for both inline and managed session
   452  	// policies shouldn't exceed 2048 characters.
   453  	if len(sessionPolicyStr) > 2048 {
   454  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy should not exceed 2048 characters"))
   455  		return
   456  	}
   457  
   458  	if len(sessionPolicyStr) > 0 {
   459  		sessionPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
   460  		if err != nil {
   461  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   462  			return
   463  		}
   464  
   465  		// Version in policy must not be empty
   466  		if sessionPolicy.Version == "" {
   467  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid session policy version"))
   468  			return
   469  		}
   470  
   471  		claims[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
   472  	}
   473  
   474  	secret, err := getTokenSigningKey()
   475  	if err != nil {
   476  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   477  		return
   478  	}
   479  	cred, err := auth.GetNewCredentialsWithMetadata(claims, secret)
   480  	if err != nil {
   481  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   482  		return
   483  	}
   484  
   485  	// https://openid.net/specs/openid-connect-core-1_0.html#ClaimStability
   486  	// claim is only considered stable when subject and iss are used together
   487  	// this is to ensure that ParentUser doesn't change and we get to use
   488  	// parentUser as per the requirements for service accounts for OpenID
   489  	// based logins.
   490  	var subFromToken string
   491  	if v, ok := claims[subClaim]; ok {
   492  		subFromToken, _ = v.(string)
   493  	}
   494  
   495  	if subFromToken == "" {
   496  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue,
   497  			errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
   498  		return
   499  	}
   500  
   501  	var issFromToken string
   502  	if v, ok := claims[issClaim]; ok {
   503  		issFromToken, _ = v.(string)
   504  	}
   505  
   506  	// Since issFromToken can have `/` characters (it is typically the
   507  	// provider URL), we hash and encode it to base64 here. This is needed
   508  	// because there will be a policy mapping stored on drives whose
   509  	// filename is this parentUser: therefore, it needs to have only valid
   510  	// filename characters and needs to have bounded length.
   511  	{
   512  		h := sha256.New()
   513  		h.Write([]byte("openid:" + subFromToken + ":" + issFromToken))
   514  		bs := h.Sum(nil)
   515  		cred.ParentUser = base64.RawURLEncoding.EncodeToString(bs)
   516  	}
   517  
   518  	// Deny this assume role request if the policy that the user intends to bind
   519  	// has a sts:DurationSeconds condition, which is not satisfied as well
   520  	{
   521  		p := policyName
   522  		if p == "" {
   523  			var err error
   524  			_, p, err = globalIAMSys.GetRolePolicy(roleArnStr)
   525  			if err != nil {
   526  				writeSTSErrorResponse(ctx, w, ErrSTSAccessDenied, err)
   527  				return
   528  			}
   529  		}
   530  
   531  		if !globalIAMSys.doesPolicyAllow(p, policy.Args{
   532  			DenyOnly:        true,
   533  			Action:          policy.AssumeRoleWithWebIdentityAction,
   534  			ConditionValues: getSTSConditionValues(r, "", cred),
   535  			Claims:          cred.Claims,
   536  		}) {
   537  			writeSTSErrorResponse(ctx, w, ErrSTSAccessDenied, errors.New("this user does not have enough permission"))
   538  			return
   539  		}
   540  	}
   541  
   542  	// Set the newly generated credentials.
   543  	updatedAt, err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, policyName)
   544  	if err != nil {
   545  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   546  		return
   547  	}
   548  
   549  	// Call hook for site replication.
   550  	logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
   551  		Type: madmin.SRIAMItemSTSAcc,
   552  		STSCredential: &madmin.SRSTSCredential{
   553  			AccessKey:           cred.AccessKey,
   554  			SecretKey:           cred.SecretKey,
   555  			SessionToken:        cred.SessionToken,
   556  			ParentUser:          cred.ParentUser,
   557  			ParentPolicyMapping: policyName,
   558  		},
   559  		UpdatedAt: updatedAt,
   560  	}))
   561  
   562  	var encodedSuccessResponse []byte
   563  	switch action {
   564  	case clientGrants:
   565  		clientGrantsResponse := &AssumeRoleWithClientGrantsResponse{
   566  			Result: ClientGrantsResult{
   567  				Credentials:      cred,
   568  				SubjectFromToken: subFromToken,
   569  			},
   570  		}
   571  		clientGrantsResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
   572  		encodedSuccessResponse = encodeResponse(clientGrantsResponse)
   573  	case webIdentity:
   574  		webIdentityResponse := &AssumeRoleWithWebIdentityResponse{
   575  			Result: WebIdentityResult{
   576  				Credentials:                 cred,
   577  				SubjectFromWebIdentityToken: subFromToken,
   578  			},
   579  		}
   580  		webIdentityResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
   581  		encodedSuccessResponse = encodeResponse(webIdentityResponse)
   582  	}
   583  
   584  	writeSuccessResponseXML(w, encodedSuccessResponse)
   585  }
   586  
   587  // AssumeRoleWithWebIdentity - implementation of AWS STS API supporting OAuth2.0
   588  // users from web identity provider such as Facebook, Google, or any OpenID
   589  // Connect-compatible identity provider.
   590  //
   591  // Eg:-
   592  //
   593  //	$ curl https://minio:9000/?Action=AssumeRoleWithWebIdentity&WebIdentityToken=<jwt>
   594  func (sts *stsAPIHandlers) AssumeRoleWithWebIdentity(w http.ResponseWriter, r *http.Request) {
   595  	sts.AssumeRoleWithSSO(w, r)
   596  }
   597  
   598  // AssumeRoleWithClientGrants - implementation of AWS STS extension API supporting
   599  // OAuth2.0 client credential grants.
   600  //
   601  // Eg:-
   602  //
   603  //	$ curl https://minio:9000/?Action=AssumeRoleWithClientGrants&Token=<jwt>
   604  func (sts *stsAPIHandlers) AssumeRoleWithClientGrants(w http.ResponseWriter, r *http.Request) {
   605  	sts.AssumeRoleWithSSO(w, r)
   606  }
   607  
   608  // AssumeRoleWithLDAPIdentity - implements user auth against LDAP server
   609  func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *http.Request) {
   610  	ctx := newContext(r, w, "AssumeRoleWithLDAPIdentity")
   611  
   612  	claims := make(map[string]interface{})
   613  	defer logger.AuditLog(ctx, w, r, claims, stsLDAPPassword)
   614  
   615  	// Parse the incoming form data.
   616  	if err := parseForm(r); err != nil {
   617  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   618  		return
   619  	}
   620  
   621  	if r.Form.Get(stsVersion) != stsAPIVersion {
   622  		writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter,
   623  			fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
   624  		return
   625  	}
   626  
   627  	ldapUsername := r.Form.Get(stsLDAPUsername)
   628  	ldapPassword := r.Form.Get(stsLDAPPassword)
   629  
   630  	if ldapUsername == "" || ldapPassword == "" {
   631  		writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("LDAPUsername and LDAPPassword cannot be empty"))
   632  		return
   633  	}
   634  
   635  	action := r.Form.Get(stsAction)
   636  	switch action {
   637  	case ldapIdentity:
   638  	default:
   639  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
   640  		return
   641  	}
   642  
   643  	sessionPolicyStr := r.Form.Get(stsPolicy)
   644  	// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
   645  	// The plain text that you use for both inline and managed session
   646  	// policies shouldn't exceed 2048 characters.
   647  	if len(sessionPolicyStr) > 2048 {
   648  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy should not exceed 2048 characters"))
   649  		return
   650  	}
   651  
   652  	if len(sessionPolicyStr) > 0 {
   653  		sessionPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
   654  		if err != nil {
   655  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   656  			return
   657  		}
   658  
   659  		// Version in policy must not be empty
   660  		if sessionPolicy.Version == "" {
   661  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Version needs to be specified in session policy"))
   662  			return
   663  		}
   664  	}
   665  
   666  	if !globalIAMSys.Initialized() {
   667  		writeSTSErrorResponse(ctx, w, ErrSTSIAMNotInitialized, errIAMNotInitialized)
   668  		return
   669  	}
   670  
   671  	ldapUserDN, groupDistNames, err := globalIAMSys.LDAPConfig.Bind(ldapUsername, ldapPassword)
   672  	if err != nil {
   673  		err = fmt.Errorf("LDAP server error: %w", err)
   674  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   675  		return
   676  	}
   677  
   678  	// Check if this user or their groups have a policy applied.
   679  	ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, groupDistNames...)
   680  	if len(ldapPolicies) == 0 && newGlobalAuthZPluginFn() == nil {
   681  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue,
   682  			fmt.Errorf("expecting a policy to be set for user `%s` or one of their groups: `%s` - rejecting this request",
   683  				ldapUserDN, strings.Join(groupDistNames, "`,`")))
   684  		return
   685  	}
   686  
   687  	expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds))
   688  	if err != nil {
   689  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   690  		return
   691  	}
   692  
   693  	claims[expClaim] = UTCNow().Add(expiryDur).Unix()
   694  	claims[ldapUser] = ldapUserDN
   695  	claims[ldapUserN] = ldapUsername
   696  
   697  	if len(sessionPolicyStr) > 0 {
   698  		claims[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
   699  	}
   700  
   701  	secret, err := getTokenSigningKey()
   702  	if err != nil {
   703  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   704  		return
   705  	}
   706  
   707  	cred, err := auth.GetNewCredentialsWithMetadata(claims, secret)
   708  	if err != nil {
   709  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   710  		return
   711  	}
   712  
   713  	// Set the parent of the temporary access key, this is useful
   714  	// in obtaining service accounts by this cred.
   715  	cred.ParentUser = ldapUserDN
   716  
   717  	// Set this value to LDAP groups, LDAP user can be part
   718  	// of large number of groups
   719  	cred.Groups = groupDistNames
   720  
   721  	// Set the newly generated credentials, policyName is empty on purpose
   722  	// LDAP policies are applied automatically using their ldapUser, ldapGroups
   723  	// mapping.
   724  	updatedAt, err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, "")
   725  	if err != nil {
   726  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   727  		return
   728  	}
   729  
   730  	// Call hook for site replication.
   731  	logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
   732  		Type: madmin.SRIAMItemSTSAcc,
   733  		STSCredential: &madmin.SRSTSCredential{
   734  			AccessKey:    cred.AccessKey,
   735  			SecretKey:    cred.SecretKey,
   736  			SessionToken: cred.SessionToken,
   737  			ParentUser:   cred.ParentUser,
   738  		},
   739  		UpdatedAt: updatedAt,
   740  	}))
   741  
   742  	ldapIdentityResponse := &AssumeRoleWithLDAPResponse{
   743  		Result: LDAPIdentityResult{
   744  			Credentials: cred,
   745  		},
   746  	}
   747  	ldapIdentityResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
   748  	encodedSuccessResponse := encodeResponse(ldapIdentityResponse)
   749  
   750  	writeSuccessResponseXML(w, encodedSuccessResponse)
   751  }
   752  
   753  // AssumeRoleWithCertificate implements user authentication with client certificates.
   754  // It verifies the client-provided X.509 certificate, maps the certificate to an S3 policy
   755  // and returns temp. S3 credentials to the client.
   756  //
   757  // API endpoint: https://minio:9000?Action=AssumeRoleWithCertificate&Version=2011-06-15
   758  func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *http.Request) {
   759  	ctx := newContext(r, w, "AssumeRoleWithCertificate")
   760  
   761  	claims := make(map[string]interface{})
   762  	defer logger.AuditLog(ctx, w, r, claims)
   763  
   764  	if !globalIAMSys.Initialized() {
   765  		writeSTSErrorResponse(ctx, w, ErrSTSIAMNotInitialized, errIAMNotInitialized)
   766  		return
   767  	}
   768  
   769  	if !globalIAMSys.STSTLSConfig.Enabled {
   770  		writeSTSErrorResponse(ctx, w, ErrSTSNotInitialized, errors.New("STS API 'AssumeRoleWithCertificate' is disabled"))
   771  		return
   772  	}
   773  
   774  	// We have to establish a TLS connection and the
   775  	// client must provide exactly one client certificate.
   776  	// Otherwise, we don't have a certificate to verify or
   777  	// the policy lookup would ambiguous.
   778  	if r.TLS == nil {
   779  		writeSTSErrorResponse(ctx, w, ErrSTSInsecureConnection, errors.New("No TLS connection attempt"))
   780  		return
   781  	}
   782  
   783  	// A client may send a certificate chain such that we end up
   784  	// with multiple peer certificates. However, we can only accept
   785  	// a single client certificate. Otherwise, the certificate to
   786  	// policy mapping would be ambiguous.
   787  	// However, we can filter all CA certificates and only check
   788  	// whether they client has sent exactly one (non-CA) leaf certificate.
   789  	peerCertificates := make([]*x509.Certificate, 0, len(r.TLS.PeerCertificates))
   790  	for _, cert := range r.TLS.PeerCertificates {
   791  		if cert.IsCA {
   792  			continue
   793  		}
   794  		peerCertificates = append(peerCertificates, cert)
   795  	}
   796  	r.TLS.PeerCertificates = peerCertificates
   797  
   798  	// Now, we have to check that the client has provided exactly one leaf
   799  	// certificate that we can map to a policy.
   800  	if len(r.TLS.PeerCertificates) == 0 {
   801  		writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, errors.New("No client certificate provided"))
   802  		return
   803  	}
   804  	if len(r.TLS.PeerCertificates) > 1 {
   805  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, errors.New("More than one client certificate provided"))
   806  		return
   807  	}
   808  
   809  	certificate := r.TLS.PeerCertificates[0]
   810  	if !globalIAMSys.STSTLSConfig.InsecureSkipVerify { // Verify whether the client certificate has been issued by a trusted CA.
   811  		_, err := certificate.Verify(x509.VerifyOptions{
   812  			KeyUsages: []x509.ExtKeyUsage{
   813  				x509.ExtKeyUsageClientAuth,
   814  			},
   815  			Roots: globalRootCAs,
   816  		})
   817  		if err != nil {
   818  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidClientCertificate, err)
   819  			return
   820  		}
   821  	} else {
   822  		// Technically, there is no security argument for verifying the key usage
   823  		// when we don't verify that the certificate has been issued by a trusted CA.
   824  		// Any client can create a certificate with arbitrary key usage settings.
   825  		//
   826  		// However, this check ensures that a certificate with an invalid key usage
   827  		// gets rejected even when we skip certificate verification. This helps
   828  		// clients detect malformed certificates during testing instead of e.g.
   829  		// a self-signed certificate that works while a comparable certificate
   830  		// issued by a trusted CA fails due to the MinIO server being less strict
   831  		// w.r.t. key usage verification.
   832  		//
   833  		// Basically, MinIO is more consistent (from a client perspective) when
   834  		// we verify the key usage all the time.
   835  		var validKeyUsage bool
   836  		for _, usage := range certificate.ExtKeyUsage {
   837  			if usage == x509.ExtKeyUsageAny || usage == x509.ExtKeyUsageClientAuth {
   838  				validKeyUsage = true
   839  				break
   840  			}
   841  		}
   842  		if !validKeyUsage {
   843  			writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, errors.New("certificate is not valid for client authentication"))
   844  			return
   845  		}
   846  	}
   847  
   848  	// We map the X.509 subject common name to the policy. So, a client
   849  	// with the common name "foo" will be associated with the policy "foo".
   850  	// Other mapping functions - e.g. public-key hash based mapping - are
   851  	// possible but not implemented.
   852  	//
   853  	// Group mapping is not possible with standard X.509 certificates.
   854  	if certificate.Subject.CommonName == "" {
   855  		writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, errors.New("certificate subject CN cannot be empty"))
   856  		return
   857  	}
   858  
   859  	expiry, err := globalIAMSys.STSTLSConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds))
   860  	if err != nil {
   861  		writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, err)
   862  		return
   863  	}
   864  
   865  	// We set the expiry of the temp. credentials to the minimum of the
   866  	// configured expiry and the duration until the certificate itself
   867  	// expires.
   868  	// We must not issue credentials that out-live the certificate.
   869  	if validUntil := time.Until(certificate.NotAfter); validUntil < expiry {
   870  		expiry = validUntil
   871  	}
   872  
   873  	// Associate any service accounts to the certificate CN
   874  	parentUser := "tls:" + certificate.Subject.CommonName
   875  
   876  	claims[expClaim] = UTCNow().Add(expiry).Unix()
   877  	claims[subClaim] = certificate.Subject.CommonName
   878  	claims[audClaim] = certificate.Subject.Organization
   879  	claims[issClaim] = certificate.Issuer.CommonName
   880  	claims[parentClaim] = parentUser
   881  	secretKey, err := getTokenSigningKey()
   882  	if err != nil {
   883  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   884  		return
   885  	}
   886  	tmpCredentials, err := auth.GetNewCredentialsWithMetadata(claims, secretKey)
   887  	if err != nil {
   888  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   889  		return
   890  	}
   891  
   892  	tmpCredentials.ParentUser = parentUser
   893  	policyName := certificate.Subject.CommonName
   894  	updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName)
   895  	if err != nil {
   896  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
   897  		return
   898  	}
   899  
   900  	// Call hook for site replication.
   901  	logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
   902  		Type: madmin.SRIAMItemSTSAcc,
   903  		STSCredential: &madmin.SRSTSCredential{
   904  			AccessKey:           tmpCredentials.AccessKey,
   905  			SecretKey:           tmpCredentials.SecretKey,
   906  			SessionToken:        tmpCredentials.SessionToken,
   907  			ParentUser:          tmpCredentials.ParentUser,
   908  			ParentPolicyMapping: policyName,
   909  		},
   910  		UpdatedAt: updatedAt,
   911  	}))
   912  
   913  	response := new(AssumeRoleWithCertificateResponse)
   914  	response.Result.Credentials = tmpCredentials
   915  	response.Metadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
   916  	writeSuccessResponseXML(w, encodeResponse(response))
   917  }
   918  
   919  // AssumeRoleWithCustomToken implements user authentication with custom tokens.
   920  // These tokens are opaque to MinIO and are verified by a configured (external)
   921  // Identity Management Plugin.
   922  //
   923  // API endpoint: https://minio:9000?Action=AssumeRoleWithCustomToken&Token=xxx
   924  func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *http.Request) {
   925  	ctx := newContext(r, w, "AssumeRoleWithCustomToken")
   926  
   927  	claims := make(map[string]interface{})
   928  	defer logger.AuditLog(ctx, w, r, claims)
   929  
   930  	if !globalIAMSys.Initialized() {
   931  		writeSTSErrorResponse(ctx, w, ErrSTSIAMNotInitialized, errIAMNotInitialized)
   932  		return
   933  	}
   934  
   935  	authn := newGlobalAuthNPluginFn()
   936  	if authn == nil {
   937  		writeSTSErrorResponse(ctx, w, ErrSTSNotInitialized, errors.New("STS API 'AssumeRoleWithCustomToken' is disabled"))
   938  		return
   939  	}
   940  
   941  	action := r.Form.Get(stsAction)
   942  	if action != customTokenIdentity {
   943  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
   944  		return
   945  	}
   946  
   947  	token := r.Form.Get(stsToken)
   948  	if token == "" {
   949  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid empty `Token` parameter provided"))
   950  		return
   951  	}
   952  
   953  	durationParam := r.Form.Get(stsDurationSeconds)
   954  	var requestedDuration int
   955  	if durationParam != "" {
   956  		var err error
   957  		requestedDuration, err = strconv.Atoi(durationParam)
   958  		if err != nil {
   959  			writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid requested duration: %s", durationParam))
   960  			return
   961  		}
   962  	}
   963  
   964  	roleArnStr := r.Form.Get(stsRoleArn)
   965  	roleArn, _, err := globalIAMSys.GetRolePolicy(roleArnStr)
   966  	if err != nil {
   967  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue,
   968  			fmt.Errorf("Error processing parameter %s: %v", stsRoleArn, err))
   969  		return
   970  	}
   971  
   972  	res, err := authn.Authenticate(roleArn, token)
   973  	if err != nil {
   974  		writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
   975  		return
   976  	}
   977  
   978  	// If authentication failed, return the error message to the user.
   979  	if res.Failure != nil {
   980  		writeSTSErrorResponse(ctx, w, ErrSTSUpstreamError, errors.New(res.Failure.Reason))
   981  		return
   982  	}
   983  
   984  	// It is required that parent user be set.
   985  	if res.Success.User == "" {
   986  		writeSTSErrorResponse(ctx, w, ErrSTSUpstreamError, errors.New("A valid user was not returned by the authenticator."))
   987  		return
   988  	}
   989  
   990  	// Expiry is set as minimum of requested value and value allowed by auth
   991  	// plugin.
   992  	expiry := res.Success.MaxValiditySeconds
   993  	if durationParam != "" && requestedDuration < expiry {
   994  		expiry = requestedDuration
   995  	}
   996  
   997  	parentUser := "custom:" + res.Success.User
   998  
   999  	// metadata map
  1000  	claims[expClaim] = UTCNow().Add(time.Duration(expiry) * time.Second).Unix()
  1001  	claims[subClaim] = parentUser
  1002  	claims[roleArnClaim] = roleArn.String()
  1003  	claims[parentClaim] = parentUser
  1004  
  1005  	// Add all other claims from the plugin **without** replacing any
  1006  	// existing claims.
  1007  	for k, v := range res.Success.Claims {
  1008  		if _, ok := claims[k]; !ok {
  1009  			claims[k] = v
  1010  		}
  1011  	}
  1012  	secretKey, err := getTokenSigningKey()
  1013  	if err != nil {
  1014  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
  1015  		return
  1016  	}
  1017  	tmpCredentials, err := auth.GetNewCredentialsWithMetadata(claims, secretKey)
  1018  	if err != nil {
  1019  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
  1020  		return
  1021  	}
  1022  
  1023  	tmpCredentials.ParentUser = parentUser
  1024  	updatedAt, err := globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, "")
  1025  	if err != nil {
  1026  		writeSTSErrorResponse(ctx, w, ErrSTSInternalError, err)
  1027  		return
  1028  	}
  1029  
  1030  	// Call hook for site replication.
  1031  	logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
  1032  		Type: madmin.SRIAMItemSTSAcc,
  1033  		STSCredential: &madmin.SRSTSCredential{
  1034  			AccessKey:    tmpCredentials.AccessKey,
  1035  			SecretKey:    tmpCredentials.SecretKey,
  1036  			SessionToken: tmpCredentials.SessionToken,
  1037  			ParentUser:   tmpCredentials.ParentUser,
  1038  		},
  1039  		UpdatedAt: updatedAt,
  1040  	}))
  1041  
  1042  	response := new(AssumeRoleWithCustomTokenResponse)
  1043  	response.Result.Credentials = tmpCredentials
  1044  	response.Result.AssumedUser = parentUser
  1045  	response.Metadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
  1046  	writeSuccessResponseXML(w, encodeResponse(response))
  1047  }