storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/sts-handlers.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018-2020 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 "encoding/base64" 23 "fmt" 24 "net/http" 25 "strings" 26 27 "github.com/gorilla/mux" 28 29 "storj.io/minio/cmd/config/identity/openid" 30 xhttp "storj.io/minio/cmd/http" 31 "storj.io/minio/cmd/logger" 32 "storj.io/minio/pkg/auth" 33 iampolicy "storj.io/minio/pkg/iam/policy" 34 "storj.io/minio/pkg/wildcard" 35 ) 36 37 const ( 38 // STS API version. 39 stsAPIVersion = "2011-06-15" 40 stsVersion = "Version" 41 stsAction = "Action" 42 stsPolicy = "Policy" 43 stsToken = "Token" 44 stsWebIdentityToken = "WebIdentityToken" 45 stsDurationSeconds = "DurationSeconds" 46 stsLDAPUsername = "LDAPUsername" 47 stsLDAPPassword = "LDAPPassword" 48 49 // STS API action constants 50 clientGrants = "AssumeRoleWithClientGrants" 51 webIdentity = "AssumeRoleWithWebIdentity" 52 ldapIdentity = "AssumeRoleWithLDAPIdentity" 53 assumeRole = "AssumeRole" 54 55 stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB 56 57 // JWT claim keys 58 expClaim = "exp" 59 subClaim = "sub" 60 61 // JWT claim to check the parent user 62 parentClaim = "parent" 63 64 // LDAP claim keys 65 ldapUser = "ldapUser" 66 ) 67 68 // stsAPIHandlers implements and provides http handlers for AWS STS API. 69 type stsAPIHandlers struct{} 70 71 // registerSTSRouter - registers AWS STS compatible APIs. 72 func registerSTSRouter(router *mux.Router) { 73 // Initialize STS. 74 sts := &stsAPIHandlers{} 75 76 // STS Router 77 stsRouter := router.NewRoute().PathPrefix(SlashSeparator).Subrouter() 78 79 // Assume roles with no JWT, handles AssumeRole. 80 stsRouter.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool { 81 ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType)) 82 authOk := wildcard.MatchSimple(signV4Algorithm+"*", r.Header.Get(xhttp.Authorization)) 83 noQueries := len(r.URL.Query()) == 0 84 return ctypeOk && authOk && noQueries 85 }).HandlerFunc(HTTPTraceAll(sts.AssumeRole)) 86 87 // Assume roles with JWT handler, handles both ClientGrants and WebIdentity. 88 stsRouter.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool { 89 ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType)) 90 noQueries := len(r.URL.Query()) == 0 91 return ctypeOk && noQueries 92 }).HandlerFunc(HTTPTraceAll(sts.AssumeRoleWithSSO)) 93 94 // AssumeRoleWithClientGrants 95 stsRouter.Methods(http.MethodPost).HandlerFunc(HTTPTraceAll(sts.AssumeRoleWithClientGrants)). 96 Queries(stsAction, clientGrants). 97 Queries(stsVersion, stsAPIVersion). 98 Queries(stsToken, "{Token:.*}") 99 100 // AssumeRoleWithWebIdentity 101 stsRouter.Methods(http.MethodPost).HandlerFunc(HTTPTraceAll(sts.AssumeRoleWithWebIdentity)). 102 Queries(stsAction, webIdentity). 103 Queries(stsVersion, stsAPIVersion). 104 Queries(stsWebIdentityToken, "{Token:.*}") 105 106 // AssumeRoleWithLDAPIdentity 107 stsRouter.Methods(http.MethodPost).HandlerFunc(HTTPTraceAll(sts.AssumeRoleWithLDAPIdentity)). 108 Queries(stsAction, ldapIdentity). 109 Queries(stsVersion, stsAPIVersion). 110 Queries(stsLDAPUsername, "{LDAPUsername:.*}"). 111 Queries(stsLDAPPassword, "{LDAPPassword:.*}") 112 } 113 114 func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Credentials, isErrCodeSTS bool, stsErr STSErrorCode) { 115 switch getRequestAuthType(r) { 116 default: 117 return user, true, ErrSTSAccessDenied 118 case authTypeSigned: 119 s3Err := isReqAuthenticated(ctx, r, globalServerRegion, serviceSTS) 120 if APIErrorCode(s3Err) != ErrNone { 121 return user, false, STSErrorCode(s3Err) 122 } 123 var owner bool 124 user, owner, s3Err = getReqAccessKeyV4(r, globalServerRegion, serviceSTS) 125 if APIErrorCode(s3Err) != ErrNone { 126 return user, false, STSErrorCode(s3Err) 127 } 128 // Root credentials are not allowed to use STS API 129 if owner { 130 return user, true, ErrSTSAccessDenied 131 } 132 } 133 134 // Session tokens are not allowed in STS AssumeRole requests. 135 if getSessionToken(r) != "" { 136 return user, true, ErrSTSAccessDenied 137 } 138 139 // Temporary credentials or Service accounts cannot generate further temporary credentials. 140 if user.IsTemp() || user.IsServiceAccount() { 141 return user, true, ErrSTSAccessDenied 142 } 143 144 return user, true, ErrSTSNone 145 } 146 147 // AssumeRole - implementation of AWS STS API AssumeRole to get temporary 148 // credentials for regular users on Minio. 149 // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html 150 func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) { 151 ctx := NewContext(r, w, "AssumeRole") 152 153 user, isErrCodeSTS, stsErr := checkAssumeRoleAuth(ctx, r) 154 if stsErr != ErrSTSNone { 155 writeSTSErrorResponse(ctx, w, isErrCodeSTS, stsErr, nil) 156 return 157 } 158 if err := r.ParseForm(); err != nil { 159 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 160 return 161 } 162 163 if r.Form.Get(stsVersion) != stsAPIVersion { 164 writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get(stsVersion), stsAPIVersion)) 165 return 166 } 167 168 action := r.Form.Get(stsAction) 169 switch action { 170 case assumeRole: 171 default: 172 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action)) 173 return 174 } 175 176 ctx = NewContext(r, w, action) 177 defer logger.AuditLog(ctx, w, r, nil) 178 179 sessionPolicyStr := r.Form.Get(stsPolicy) 180 // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html 181 // The plain text that you use for both inline and managed session 182 // policies shouldn't exceed 2048 characters. 183 if len(sessionPolicyStr) > 2048 { 184 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy shouldn't exceed 2048 characters")) 185 return 186 } 187 188 if len(sessionPolicyStr) > 0 { 189 sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr))) 190 if err != nil { 191 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 192 return 193 } 194 195 // Version in policy must not be empty 196 if sessionPolicy.Version == "" { 197 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Version cannot be empty expecting '2012-10-17'")) 198 return 199 } 200 } 201 202 var err error 203 m := make(map[string]interface{}) 204 m[expClaim], err = openid.GetDefaultExpiration(r.Form.Get(stsDurationSeconds)) 205 if err != nil { 206 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 207 return 208 } 209 210 policies, err := GlobalIAMSys.PolicyDBGet(user.AccessKey, false) 211 if err != nil { 212 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 213 return 214 } 215 216 policyName := strings.Join(policies, ",") 217 218 // This policy is the policy associated with the user 219 // requesting for temporary credentials. The temporary 220 // credentials will inherit the same policy requirements. 221 m[iamPolicyClaimNameOpenID()] = policyName 222 223 if len(sessionPolicyStr) > 0 { 224 m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr)) 225 } 226 227 secret := globalActiveCred.SecretKey 228 cred, err := auth.GetNewCredentialsWithMetadata(m, secret) 229 if err != nil { 230 writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) 231 return 232 } 233 234 // Set the parent of the temporary access key, this is useful 235 // in obtaining service accounts by this cred. 236 cred.ParentUser = user.AccessKey 237 238 // Set the newly generated credentials. 239 if err = GlobalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil { 240 writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) 241 return 242 } 243 244 // Notify all other MinIO peers to reload temp users 245 for _, nerr := range GlobalNotificationSys.LoadUser(cred.AccessKey, true) { 246 if nerr.Err != nil { 247 logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) 248 logger.LogIf(ctx, nerr.Err) 249 } 250 } 251 252 assumeRoleResponse := &AssumeRoleResponse{ 253 Result: AssumeRoleResult{ 254 Credentials: cred, 255 }, 256 } 257 258 assumeRoleResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID) 259 WriteSuccessResponseXML(w, EncodeResponse(assumeRoleResponse)) 260 } 261 262 func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Request) { 263 ctx := NewContext(r, w, "AssumeRoleSSOCommon") 264 265 // Parse the incoming form data. 266 if err := r.ParseForm(); err != nil { 267 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 268 return 269 } 270 271 if r.Form.Get(stsVersion) != stsAPIVersion { 272 writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion)) 273 return 274 } 275 276 action := r.Form.Get(stsAction) 277 switch action { 278 case ldapIdentity: 279 sts.AssumeRoleWithLDAPIdentity(w, r) 280 return 281 case clientGrants, webIdentity: 282 default: 283 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action)) 284 return 285 } 286 287 ctx = NewContext(r, w, action) 288 defer logger.AuditLog(ctx, w, r, nil) 289 290 if globalOpenIDValidators == nil { 291 writeSTSErrorResponse(ctx, w, true, ErrSTSNotInitialized, errServerNotInitialized) 292 return 293 } 294 295 v, err := globalOpenIDValidators.Get("jwt") 296 if err != nil { 297 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 298 return 299 } 300 301 token := r.Form.Get(stsToken) 302 if token == "" { 303 token = r.Form.Get(stsWebIdentityToken) 304 } 305 306 m, err := v.Validate(token, r.Form.Get(stsDurationSeconds)) 307 if err != nil { 308 switch err { 309 case openid.ErrTokenExpired: 310 switch action { 311 case clientGrants: 312 writeSTSErrorResponse(ctx, w, true, ErrSTSClientGrantsExpiredToken, err) 313 case webIdentity: 314 writeSTSErrorResponse(ctx, w, true, ErrSTSWebIdentityExpiredToken, err) 315 } 316 return 317 case auth.ErrInvalidDuration: 318 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 319 return 320 } 321 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 322 return 323 } 324 325 // JWT has requested a custom claim with policy value set. 326 // This is a MinIO STS API specific value, this value should 327 // be set and configured on your identity provider as part of 328 // JWT custom claims. 329 var policyName string 330 policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID()) 331 if ok { 332 policyName = GlobalIAMSys.CurrentPolicies(strings.Join(policySet.ToSlice(), ",")) 333 } 334 335 if policyName == "" && GlobalPolicyOPA == nil { 336 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, 337 fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID())) 338 return 339 } 340 m[iamPolicyClaimNameOpenID()] = policyName 341 342 sessionPolicyStr := r.Form.Get(stsPolicy) 343 // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html 344 // The plain text that you use for both inline and managed session 345 // policies shouldn't exceed 2048 characters. 346 if len(sessionPolicyStr) > 2048 { 347 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy should not exceed 2048 characters")) 348 return 349 } 350 351 if len(sessionPolicyStr) > 0 { 352 sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr))) 353 if err != nil { 354 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 355 return 356 } 357 358 // Version in policy must not be empty 359 if sessionPolicy.Version == "" { 360 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid session policy version")) 361 return 362 } 363 364 m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr)) 365 } 366 367 secret := globalActiveCred.SecretKey 368 cred, err := auth.GetNewCredentialsWithMetadata(m, secret) 369 if err != nil { 370 writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) 371 return 372 } 373 374 var subFromToken string 375 if v, ok := m[subClaim]; ok { 376 subFromToken, _ = v.(string) 377 } 378 379 // Set the newly generated credentials. 380 if err = GlobalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil { 381 writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) 382 return 383 } 384 385 // Notify all other MinIO peers to reload temp users 386 for _, nerr := range GlobalNotificationSys.LoadUser(cred.AccessKey, true) { 387 if nerr.Err != nil { 388 logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) 389 logger.LogIf(ctx, nerr.Err) 390 } 391 } 392 393 var encodedSuccessResponse []byte 394 switch action { 395 case clientGrants: 396 clientGrantsResponse := &AssumeRoleWithClientGrantsResponse{ 397 Result: ClientGrantsResult{ 398 Credentials: cred, 399 SubjectFromToken: subFromToken, 400 }, 401 } 402 clientGrantsResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID) 403 encodedSuccessResponse = EncodeResponse(clientGrantsResponse) 404 case webIdentity: 405 webIdentityResponse := &AssumeRoleWithWebIdentityResponse{ 406 Result: WebIdentityResult{ 407 Credentials: cred, 408 SubjectFromWebIdentityToken: subFromToken, 409 }, 410 } 411 webIdentityResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID) 412 encodedSuccessResponse = EncodeResponse(webIdentityResponse) 413 } 414 415 WriteSuccessResponseXML(w, encodedSuccessResponse) 416 } 417 418 // AssumeRoleWithWebIdentity - implementation of AWS STS API supporting OAuth2.0 419 // users from web identity provider such as Facebook, Google, or any OpenID 420 // Connect-compatible identity provider. 421 // 422 // Eg:- 423 // $ curl https://minio:9000/?Action=AssumeRoleWithWebIdentity&WebIdentityToken=<jwt> 424 func (sts *stsAPIHandlers) AssumeRoleWithWebIdentity(w http.ResponseWriter, r *http.Request) { 425 sts.AssumeRoleWithSSO(w, r) 426 } 427 428 // AssumeRoleWithClientGrants - implementation of AWS STS extension API supporting 429 // OAuth2.0 client credential grants. 430 // 431 // Eg:- 432 // $ curl https://minio:9000/?Action=AssumeRoleWithClientGrants&Token=<jwt> 433 func (sts *stsAPIHandlers) AssumeRoleWithClientGrants(w http.ResponseWriter, r *http.Request) { 434 sts.AssumeRoleWithSSO(w, r) 435 } 436 437 // AssumeRoleWithLDAPIdentity - implements user auth against LDAP server 438 func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *http.Request) { 439 ctx := NewContext(r, w, "AssumeRoleWithLDAPIdentity") 440 441 defer logger.AuditLog(ctx, w, r, nil, stsLDAPPassword) 442 443 // Parse the incoming form data. 444 if err := r.ParseForm(); err != nil { 445 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 446 return 447 } 448 449 if r.Form.Get(stsVersion) != stsAPIVersion { 450 writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, 451 fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion)) 452 return 453 } 454 455 ldapUsername := r.Form.Get(stsLDAPUsername) 456 ldapPassword := r.Form.Get(stsLDAPPassword) 457 458 if ldapUsername == "" || ldapPassword == "" { 459 writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, fmt.Errorf("LDAPUsername and LDAPPassword cannot be empty")) 460 return 461 } 462 463 action := r.Form.Get(stsAction) 464 switch action { 465 case ldapIdentity: 466 default: 467 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action)) 468 return 469 } 470 471 sessionPolicyStr := r.Form.Get(stsPolicy) 472 // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html 473 // The plain text that you use for both inline and managed session 474 // policies shouldn't exceed 2048 characters. 475 if len(sessionPolicyStr) > 2048 { 476 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy should not exceed 2048 characters")) 477 return 478 } 479 480 if len(sessionPolicyStr) > 0 { 481 sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr))) 482 if err != nil { 483 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 484 return 485 } 486 487 // Version in policy must not be empty 488 if sessionPolicy.Version == "" { 489 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Version needs to be specified in session policy")) 490 return 491 } 492 } 493 494 ldapUserDN, groupDistNames, err := globalLDAPConfig.Bind(ldapUsername, ldapPassword) 495 if err != nil { 496 err = fmt.Errorf("LDAP server error: %w", err) 497 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err) 498 return 499 } 500 501 // Check if this user or their groups have a policy applied. 502 ldapPolicies, _ := GlobalIAMSys.PolicyDBGet(ldapUserDN, false, groupDistNames...) 503 if len(ldapPolicies) == 0 { 504 writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, 505 fmt.Errorf("expecting a policy to be set for user `%s` or one of their groups: `%s` - rejecting this request", 506 ldapUserDN, strings.Join(groupDistNames, "`,`"))) 507 return 508 } 509 510 expiryDur := globalLDAPConfig.GetExpiryDuration() 511 m := map[string]interface{}{ 512 expClaim: UTCNow().Add(expiryDur).Unix(), 513 ldapUser: ldapUserDN, 514 } 515 516 if len(sessionPolicyStr) > 0 { 517 m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr)) 518 } 519 520 secret := globalActiveCred.SecretKey 521 cred, err := auth.GetNewCredentialsWithMetadata(m, secret) 522 if err != nil { 523 writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) 524 return 525 } 526 527 // Set the parent of the temporary access key, this is useful 528 // in obtaining service accounts by this cred. 529 cred.ParentUser = ldapUserDN 530 531 // Set this value to LDAP groups, LDAP user can be part 532 // of large number of groups 533 cred.Groups = groupDistNames 534 535 // Set the newly generated credentials, policyName is empty on purpose 536 // LDAP policies are applied automatically using their ldapUser, ldapGroups 537 // mapping. 538 if err = GlobalIAMSys.SetTempUser(cred.AccessKey, cred, ""); err != nil { 539 writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) 540 return 541 } 542 543 // Notify all other MinIO peers to reload temp users 544 for _, nerr := range GlobalNotificationSys.LoadUser(cred.AccessKey, true) { 545 if nerr.Err != nil { 546 logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) 547 logger.LogIf(ctx, nerr.Err) 548 } 549 } 550 551 ldapIdentityResponse := &AssumeRoleWithLDAPResponse{ 552 Result: LDAPIdentityResult{ 553 Credentials: cred, 554 }, 555 } 556 ldapIdentityResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID) 557 encodedSuccessResponse := EncodeResponse(ldapIdentityResponse) 558 559 WriteSuccessResponseXML(w, encodedSuccessResponse) 560 }