2022-01-22 19:36:11 -05:00
|
|
|
/// Copyright (c) 2015-2021 MinIO, Inc.
|
2021-04-18 15:41:13 -04:00
|
|
|
//
|
|
|
|
// This file is part of MinIO Object Storage stack
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2018-10-09 17:00:01 -04:00
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
2019-06-20 18:28:33 -04:00
|
|
|
"bytes"
|
2019-02-27 20:46:55 -05:00
|
|
|
"context"
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
"crypto/sha256"
|
2021-09-07 22:03:48 -04:00
|
|
|
"crypto/x509"
|
2019-06-20 18:28:33 -04:00
|
|
|
"encoding/base64"
|
2021-04-29 16:01:42 -04:00
|
|
|
"errors"
|
2019-02-05 18:47:11 -05:00
|
|
|
"fmt"
|
2018-10-09 17:00:01 -04:00
|
|
|
"net/http"
|
2020-04-28 15:49:56 -04:00
|
|
|
"strings"
|
2021-09-07 22:03:48 -04:00
|
|
|
"time"
|
2018-10-09 17:00:01 -04:00
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
2021-10-06 19:36:31 -04:00
|
|
|
"github.com/minio/madmin-go"
|
2021-06-01 17:59:40 -04:00
|
|
|
"github.com/minio/minio/internal/auth"
|
|
|
|
"github.com/minio/minio/internal/config/identity/openid"
|
|
|
|
xhttp "github.com/minio/minio/internal/http"
|
|
|
|
"github.com/minio/minio/internal/logger"
|
2021-05-30 00:16:42 -04:00
|
|
|
iampolicy "github.com/minio/pkg/iam/policy"
|
2021-05-28 18:17:01 -04:00
|
|
|
"github.com/minio/pkg/wildcard"
|
2018-10-09 17:00:01 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// STS API version.
|
2021-09-13 19:22:14 -04:00
|
|
|
stsAPIVersion = "2011-06-15"
|
|
|
|
stsVersion = "Version"
|
|
|
|
stsAction = "Action"
|
|
|
|
stsPolicy = "Policy"
|
|
|
|
stsToken = "Token"
|
2021-11-26 22:22:40 -05:00
|
|
|
stsRoleArn = "RoleArn"
|
2021-09-13 19:22:14 -04:00
|
|
|
stsWebIdentityToken = "WebIdentityToken"
|
|
|
|
stsWebIdentityAccessToken = "WebIdentityAccessToken" // only valid if UserInfo is enabled.
|
|
|
|
stsDurationSeconds = "DurationSeconds"
|
|
|
|
stsLDAPUsername = "LDAPUsername"
|
|
|
|
stsLDAPPassword = "LDAPPassword"
|
2019-02-05 18:47:11 -05:00
|
|
|
|
|
|
|
// STS API action constants
|
2021-09-07 22:03:48 -04:00
|
|
|
clientGrants = "AssumeRoleWithClientGrants"
|
|
|
|
webIdentity = "AssumeRoleWithWebIdentity"
|
|
|
|
ldapIdentity = "AssumeRoleWithLDAPIdentity"
|
|
|
|
clientCertificate = "AssumeRoleWithCertificate"
|
|
|
|
assumeRole = "AssumeRole"
|
2019-08-05 13:06:40 -04:00
|
|
|
|
|
|
|
stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB
|
2019-09-09 19:12:29 -04:00
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
// JWT claim keys
|
|
|
|
expClaim = "exp"
|
|
|
|
subClaim = "sub"
|
2021-07-26 22:40:15 -04:00
|
|
|
audClaim = "aud"
|
2021-04-29 16:01:42 -04:00
|
|
|
issClaim = "iss"
|
2019-11-29 08:27:54 -05:00
|
|
|
|
2020-03-17 13:36:13 -04:00
|
|
|
// JWT claim to check the parent user
|
|
|
|
parentClaim = "parent"
|
|
|
|
|
2019-09-09 19:12:29 -04:00
|
|
|
// LDAP claim keys
|
2021-07-11 21:38:52 -04:00
|
|
|
ldapUser = "ldapUser"
|
|
|
|
ldapUserN = "ldapUsername"
|
2021-11-26 22:22:40 -05:00
|
|
|
|
|
|
|
// Role Claim key
|
|
|
|
roleArnClaim = "roleArn"
|
2018-10-09 17:00:01 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// stsAPIHandlers implements and provides http handlers for AWS STS API.
|
|
|
|
type stsAPIHandlers struct{}
|
|
|
|
|
|
|
|
// registerSTSRouter - registers AWS STS compatible APIs.
|
|
|
|
func registerSTSRouter(router *mux.Router) {
|
|
|
|
// Initialize STS.
|
|
|
|
sts := &stsAPIHandlers{}
|
|
|
|
|
|
|
|
// STS Router
|
2019-08-06 15:08:58 -04:00
|
|
|
stsRouter := router.NewRoute().PathPrefix(SlashSeparator).Subrouter()
|
2018-10-09 17:00:01 -04:00
|
|
|
|
2019-02-27 20:46:55 -05:00
|
|
|
// Assume roles with no JWT, handles AssumeRole.
|
2019-07-03 01:34:32 -04:00
|
|
|
stsRouter.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
|
|
|
|
ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType))
|
|
|
|
authOk := wildcard.MatchSimple(signV4Algorithm+"*", r.Header.Get(xhttp.Authorization))
|
2021-08-08 01:43:01 -04:00
|
|
|
noQueries := len(r.URL.RawQuery) == 0
|
2019-04-23 18:55:41 -04:00
|
|
|
return ctypeOk && authOk && noQueries
|
|
|
|
}).HandlerFunc(httpTraceAll(sts.AssumeRole))
|
2019-02-27 20:46:55 -05:00
|
|
|
|
2019-02-05 18:47:11 -05:00
|
|
|
// Assume roles with JWT handler, handles both ClientGrants and WebIdentity.
|
2019-11-29 08:27:54 -05:00
|
|
|
stsRouter.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
|
2019-07-03 01:34:32 -04:00
|
|
|
ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType))
|
2021-08-08 01:43:01 -04:00
|
|
|
noQueries := len(r.URL.RawQuery) == 0
|
2019-04-23 18:55:41 -04:00
|
|
|
return ctypeOk && noQueries
|
2020-09-12 02:02:32 -04:00
|
|
|
}).HandlerFunc(httpTraceAll(sts.AssumeRoleWithSSO))
|
2019-02-05 18:47:11 -05:00
|
|
|
|
2018-10-09 17:00:01 -04:00
|
|
|
// AssumeRoleWithClientGrants
|
2019-11-29 08:27:54 -05:00
|
|
|
stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)).
|
|
|
|
Queries(stsAction, clientGrants).
|
|
|
|
Queries(stsVersion, stsAPIVersion).
|
|
|
|
Queries(stsToken, "{Token:.*}")
|
2018-10-09 17:00:01 -04:00
|
|
|
|
2019-01-04 16:48:12 -05:00
|
|
|
// AssumeRoleWithWebIdentity
|
2019-11-29 08:27:54 -05:00
|
|
|
stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)).
|
|
|
|
Queries(stsAction, webIdentity).
|
|
|
|
Queries(stsVersion, stsAPIVersion).
|
|
|
|
Queries(stsWebIdentityToken, "{Token:.*}")
|
2018-10-09 17:00:01 -04:00
|
|
|
|
2019-09-09 19:12:29 -04:00
|
|
|
// AssumeRoleWithLDAPIdentity
|
2019-11-29 08:27:54 -05:00
|
|
|
stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithLDAPIdentity)).
|
|
|
|
Queries(stsAction, ldapIdentity).
|
|
|
|
Queries(stsVersion, stsAPIVersion).
|
|
|
|
Queries(stsLDAPUsername, "{LDAPUsername:.*}").
|
|
|
|
Queries(stsLDAPPassword, "{LDAPPassword:.*}")
|
2021-09-07 22:03:48 -04:00
|
|
|
|
|
|
|
// AssumeRoleWithCertificate
|
|
|
|
stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithCertificate)).
|
|
|
|
Queries(stsAction, clientCertificate).
|
|
|
|
Queries(stsVersion, stsAPIVersion)
|
2018-10-09 17:00:01 -04:00
|
|
|
}
|
|
|
|
|
2020-05-21 12:09:18 -04:00
|
|
|
func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Credentials, isErrCodeSTS bool, stsErr STSErrorCode) {
|
2022-01-27 19:17:11 -05:00
|
|
|
if !isRequestSignatureV4(r) {
|
2020-05-21 12:09:18 -04:00
|
|
|
return user, true, ErrSTSAccessDenied
|
2022-01-27 19:17:11 -05:00
|
|
|
}
|
2021-05-04 14:58:19 -04:00
|
|
|
|
2022-01-27 19:17:11 -05:00
|
|
|
s3Err := isReqAuthenticated(ctx, r, globalSite.Region, serviceSTS)
|
|
|
|
if s3Err != ErrNone {
|
|
|
|
return user, false, STSErrorCode(s3Err)
|
|
|
|
}
|
2021-05-04 14:58:19 -04:00
|
|
|
|
2022-01-27 19:17:11 -05:00
|
|
|
user, _, s3Err = getReqAccessKeyV4(r, globalSite.Region, serviceSTS)
|
|
|
|
if s3Err != ErrNone {
|
|
|
|
return user, false, STSErrorCode(s3Err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Temporary credentials or Service accounts cannot generate further temporary credentials.
|
|
|
|
if user.IsTemp() || user.IsServiceAccount() {
|
|
|
|
return user, true, ErrSTSAccessDenied
|
2019-02-27 20:46:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Session tokens are not allowed in STS AssumeRole requests.
|
|
|
|
if getSessionToken(r) != "" {
|
2020-05-21 12:09:18 -04:00
|
|
|
return user, true, ErrSTSAccessDenied
|
2019-02-27 20:46:55 -05:00
|
|
|
}
|
|
|
|
|
2020-05-21 12:09:18 -04:00
|
|
|
return user, true, ErrSTSNone
|
2019-02-27 20:46:55 -05:00
|
|
|
}
|
|
|
|
|
2021-08-08 01:43:01 -04:00
|
|
|
func parseForm(r *http.Request) error {
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for k, v := range r.PostForm {
|
|
|
|
if _, ok := r.Form[k]; !ok {
|
|
|
|
r.Form[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-02-27 20:46:55 -05:00
|
|
|
// AssumeRole - implementation of AWS STS API AssumeRole to get temporary
|
|
|
|
// credentials for regular users on Minio.
|
|
|
|
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
|
|
|
|
func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AssumeRole")
|
|
|
|
|
2022-01-27 19:17:11 -05:00
|
|
|
// Check auth here (otherwise r.Form will have unexpected values from
|
|
|
|
// the call to `parseForm` below), but return failure only after we are
|
|
|
|
// able to validate that it is a valid STS request, so that we are able
|
|
|
|
// to send an appropriate audit log.
|
2020-05-21 12:09:18 -04:00
|
|
|
user, isErrCodeSTS, stsErr := checkAssumeRoleAuth(ctx, r)
|
2021-05-04 14:58:19 -04:00
|
|
|
|
2021-08-08 01:43:01 -04:00
|
|
|
if err := parseForm(r); err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-02-27 20:46:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
if r.Form.Get(stsVersion) != stsAPIVersion {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get(stsVersion), stsAPIVersion))
|
2019-02-27 20:46:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
action := r.Form.Get(stsAction)
|
2019-02-27 20:46:55 -05:00
|
|
|
switch action {
|
|
|
|
case assumeRole:
|
|
|
|
default:
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
|
2019-02-27 20:46:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = newContext(r, w, action)
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, nil)
|
2019-02-27 20:46:55 -05:00
|
|
|
|
2022-01-27 19:17:11 -05:00
|
|
|
// Validate the authentication result here so that failures will be
|
|
|
|
// audit-logged.
|
|
|
|
if stsErr != ErrSTSNone {
|
|
|
|
writeSTSErrorResponse(ctx, w, isErrCodeSTS, stsErr, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
sessionPolicyStr := r.Form.Get(stsPolicy)
|
2019-06-20 18:28:33 -04:00
|
|
|
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
|
|
|
|
// The plain text that you use for both inline and managed session
|
|
|
|
// policies shouldn't exceed 2048 characters.
|
|
|
|
if len(sessionPolicyStr) > 2048 {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy shouldn't exceed 2048 characters"))
|
2019-06-20 18:28:33 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(sessionPolicyStr) > 0 {
|
|
|
|
sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
|
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-06-20 18:28:33 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Version in policy must not be empty
|
|
|
|
if sessionPolicy.Version == "" {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Version cannot be empty expecting '2012-10-17'"))
|
2019-06-20 18:28:33 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-05 15:20:08 -04:00
|
|
|
duration, err := openid.GetDefaultExpiration(r.Form.Get(stsDurationSeconds))
|
2019-02-27 20:46:55 -05:00
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-02-27 20:46:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-05 15:20:08 -04:00
|
|
|
m := map[string]interface{}{
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
expClaim: UTCNow().Add(duration).Unix(),
|
|
|
|
parentClaim: user.AccessKey,
|
2021-11-05 15:20:08 -04:00
|
|
|
}
|
|
|
|
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
// Validate that user.AccessKey's policies can be retrieved - it may not
|
|
|
|
// be in case the user is disabled.
|
|
|
|
_, err = globalIAMSys.PolicyDBGet(user.AccessKey, false)
|
2019-02-27 20:46:55 -05:00
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-02-27 20:46:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-20 18:28:33 -04:00
|
|
|
if len(sessionPolicyStr) > 0 {
|
|
|
|
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
|
|
|
|
}
|
2019-02-27 20:46:55 -05:00
|
|
|
|
2019-10-23 01:59:13 -04:00
|
|
|
secret := globalActiveCred.SecretKey
|
2019-02-27 20:46:55 -05:00
|
|
|
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
|
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
2019-02-27 20:46:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
// Set the parent of the temporary access key, so that it's access
|
|
|
|
// policy is inherited from `user.AccessKey`.
|
2020-04-28 15:49:56 -04:00
|
|
|
cred.ParentUser = user.AccessKey
|
|
|
|
|
2019-02-27 20:46:55 -05:00
|
|
|
// Set the newly generated credentials.
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
if err = globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, ""); err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
2019-02-27 20:46:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 18:52:43 -05:00
|
|
|
// Call hook for site replication.
|
|
|
|
if cred.ParentUser != globalActiveCred.AccessKey {
|
|
|
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemSTSAcc,
|
|
|
|
STSCredential: &madmin.SRSTSCredential{
|
|
|
|
AccessKey: cred.AccessKey,
|
|
|
|
SecretKey: cred.SecretKey,
|
|
|
|
SessionToken: cred.SessionToken,
|
|
|
|
ParentUser: cred.ParentUser,
|
|
|
|
},
|
|
|
|
}); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-27 20:46:55 -05:00
|
|
|
assumeRoleResponse := &AssumeRoleResponse{
|
|
|
|
Result: AssumeRoleResult{
|
|
|
|
Credentials: cred,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-07-03 01:34:32 -04:00
|
|
|
assumeRoleResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
|
2019-02-27 20:46:55 -05:00
|
|
|
writeSuccessResponseXML(w, encodeResponse(assumeRoleResponse))
|
|
|
|
}
|
|
|
|
|
2020-09-12 02:02:32 -04:00
|
|
|
func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AssumeRoleSSOCommon")
|
2019-01-04 16:48:12 -05:00
|
|
|
|
2019-02-05 18:47:11 -05:00
|
|
|
// Parse the incoming form data.
|
2021-08-08 01:43:01 -04:00
|
|
|
if err := parseForm(r); err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-01-04 16:48:12 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
if r.Form.Get(stsVersion) != stsAPIVersion {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
|
2019-01-04 16:48:12 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
action := r.Form.Get(stsAction)
|
2019-02-05 18:47:11 -05:00
|
|
|
switch action {
|
2020-09-12 02:02:32 -04:00
|
|
|
case ldapIdentity:
|
|
|
|
sts.AssumeRoleWithLDAPIdentity(w, r)
|
|
|
|
return
|
2019-02-05 18:47:11 -05:00
|
|
|
case clientGrants, webIdentity:
|
|
|
|
default:
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
|
2019-01-04 16:48:12 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-02-05 18:47:11 -05:00
|
|
|
ctx = newContext(r, w, action)
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, nil)
|
2018-11-21 23:03:24 -05:00
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
token := r.Form.Get(stsToken)
|
2019-02-05 18:47:11 -05:00
|
|
|
if token == "" {
|
2019-11-29 08:27:54 -05:00
|
|
|
token = r.Form.Get(stsWebIdentityToken)
|
2019-02-05 18:47:11 -05:00
|
|
|
}
|
|
|
|
|
2021-09-13 19:22:14 -04:00
|
|
|
accessToken := r.Form.Get(stsWebIdentityAccessToken)
|
|
|
|
|
2022-04-28 21:27:09 -04:00
|
|
|
roleArn := openid.DummyRoleARN
|
|
|
|
if globalIAMSys.HasRolePolicy() {
|
|
|
|
var err error
|
|
|
|
roleArnStr := r.Form.Get(stsRoleArn)
|
|
|
|
roleArn, _, err = globalIAMSys.GetRolePolicy(roleArnStr)
|
|
|
|
if err != nil {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
|
|
|
fmt.Errorf("Error processing %s parameter: %v", stsRoleArn, err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate JWT; check clientID in claims matches the one associated with the roleArn
|
|
|
|
m, err := globalOpenIDConfig.Validate(roleArn, token, accessToken, r.Form.Get(stsDurationSeconds))
|
2018-10-09 17:00:01 -04:00
|
|
|
if err != nil {
|
|
|
|
switch err {
|
2019-10-01 18:07:20 -04:00
|
|
|
case openid.ErrTokenExpired:
|
2019-02-05 18:47:11 -05:00
|
|
|
switch action {
|
|
|
|
case clientGrants:
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSClientGrantsExpiredToken, err)
|
2019-02-05 18:47:11 -05:00
|
|
|
case webIdentity:
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSWebIdentityExpiredToken, err)
|
2019-02-05 18:47:11 -05:00
|
|
|
}
|
|
|
|
return
|
2019-11-29 08:27:54 -05:00
|
|
|
case auth.ErrInvalidDuration:
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-02-05 18:47:11 -05:00
|
|
|
return
|
2018-10-09 17:00:01 -04:00
|
|
|
}
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2018-10-09 17:00:01 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-19 18:34:01 -04:00
|
|
|
var policyName string
|
2021-12-02 18:43:39 -05:00
|
|
|
if globalIAMSys.HasRolePolicy() {
|
2021-11-26 22:22:40 -05:00
|
|
|
// If roleArn is used, we set it as a claim, and use the
|
|
|
|
// associated policy when credentials are used.
|
2022-04-28 21:27:09 -04:00
|
|
|
m[roleArnClaim] = roleArn.String()
|
2021-11-26 22:22:40 -05:00
|
|
|
} else {
|
2021-12-02 18:43:39 -05:00
|
|
|
// If no role policy is configured, then we use claims from the
|
|
|
|
// JWT. This is a MinIO STS API specific value, this value
|
|
|
|
// should be set and configured on your identity provider as
|
|
|
|
// part of JWT custom claims.
|
2021-11-26 22:22:40 -05:00
|
|
|
policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID())
|
|
|
|
policies := strings.Join(policySet.ToSlice(), ",")
|
|
|
|
if ok {
|
|
|
|
policyName = globalIAMSys.CurrentPolicies(policies)
|
|
|
|
}
|
|
|
|
|
2022-05-10 20:14:55 -04:00
|
|
|
if globalAuthZPlugin == nil {
|
2021-11-26 22:22:40 -05:00
|
|
|
if !ok {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
|
|
|
fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID()))
|
|
|
|
return
|
|
|
|
} else if policyName == "" {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
|
|
|
fmt.Errorf("None of the given policies (`%s`) are defined, credentials will not be generated", policies))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m[iamPolicyClaimNameOpenID()] = policyName
|
2020-07-19 18:34:01 -04:00
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
sessionPolicyStr := r.Form.Get(stsPolicy)
|
2019-06-20 18:28:33 -04:00
|
|
|
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
|
|
|
|
// The plain text that you use for both inline and managed session
|
|
|
|
// policies shouldn't exceed 2048 characters.
|
|
|
|
if len(sessionPolicyStr) > 2048 {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy should not exceed 2048 characters"))
|
2019-06-20 18:28:33 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(sessionPolicyStr) > 0 {
|
|
|
|
sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
|
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-06-20 18:28:33 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Version in policy must not be empty
|
|
|
|
if sessionPolicy.Version == "" {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid session policy version"))
|
2019-06-20 18:28:33 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-23 18:21:16 -04:00
|
|
|
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
|
|
|
|
}
|
|
|
|
|
2019-10-23 01:59:13 -04:00
|
|
|
secret := globalActiveCred.SecretKey
|
2018-10-09 17:00:01 -04:00
|
|
|
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
|
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
2018-10-09 17:00:01 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-29 16:01:42 -04:00
|
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#ClaimStability
|
|
|
|
// claim is only considered stable when subject and iss are used together
|
|
|
|
// this is to ensure that ParentUser doesn't change and we get to use
|
|
|
|
// parentUser as per the requirements for service accounts for OpenID
|
|
|
|
// based logins.
|
2021-11-26 22:22:40 -05:00
|
|
|
var subFromToken string
|
|
|
|
if v, ok := m[subClaim]; ok {
|
|
|
|
subFromToken, _ = v.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
if subFromToken == "" {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
|
|
|
errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var issFromToken string
|
|
|
|
if v, ok := m[issClaim]; ok {
|
|
|
|
issFromToken, _ = v.(string)
|
|
|
|
}
|
|
|
|
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
// Since issFromToken can have `/` characters (it is typically the
|
|
|
|
// provider URL), we hash and encode it to base64 here. This is needed
|
|
|
|
// because there will be a policy mapping stored on drives whose
|
|
|
|
// filename is this parentUser: therefore, it needs to have only valid
|
|
|
|
// filename characters and needs to have bounded length.
|
|
|
|
{
|
|
|
|
h := sha256.New()
|
|
|
|
h.Write([]byte("openid:" + subFromToken + ":" + issFromToken))
|
|
|
|
bs := h.Sum(nil)
|
|
|
|
cred.ParentUser = base64.RawURLEncoding.EncodeToString(bs)
|
|
|
|
}
|
2019-01-04 16:48:12 -05:00
|
|
|
|
2018-10-09 17:00:01 -04:00
|
|
|
// Set the newly generated credentials.
|
2021-11-15 17:14:22 -05:00
|
|
|
if err = globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, policyName); err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
2018-10-09 17:00:01 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 18:52:43 -05:00
|
|
|
// Call hook for site replication.
|
|
|
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemSTSAcc,
|
|
|
|
STSCredential: &madmin.SRSTSCredential{
|
|
|
|
AccessKey: cred.AccessKey,
|
|
|
|
SecretKey: cred.SecretKey,
|
|
|
|
SessionToken: cred.SessionToken,
|
|
|
|
ParentUser: cred.ParentUser,
|
|
|
|
ParentPolicyMapping: policyName,
|
|
|
|
},
|
|
|
|
}); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-02-05 18:47:11 -05:00
|
|
|
var encodedSuccessResponse []byte
|
|
|
|
switch action {
|
|
|
|
case clientGrants:
|
2019-02-27 20:46:55 -05:00
|
|
|
clientGrantsResponse := &AssumeRoleWithClientGrantsResponse{
|
2019-02-05 18:47:11 -05:00
|
|
|
Result: ClientGrantsResult{
|
|
|
|
Credentials: cred,
|
|
|
|
SubjectFromToken: subFromToken,
|
|
|
|
},
|
2019-02-27 20:46:55 -05:00
|
|
|
}
|
2019-07-03 01:34:32 -04:00
|
|
|
clientGrantsResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
|
2019-02-27 20:46:55 -05:00
|
|
|
encodedSuccessResponse = encodeResponse(clientGrantsResponse)
|
2019-02-05 18:47:11 -05:00
|
|
|
case webIdentity:
|
2019-02-27 20:46:55 -05:00
|
|
|
webIdentityResponse := &AssumeRoleWithWebIdentityResponse{
|
2019-02-05 18:47:11 -05:00
|
|
|
Result: WebIdentityResult{
|
|
|
|
Credentials: cred,
|
|
|
|
SubjectFromWebIdentityToken: subFromToken,
|
|
|
|
},
|
2019-02-27 20:46:55 -05:00
|
|
|
}
|
2019-07-03 01:34:32 -04:00
|
|
|
webIdentityResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
|
2019-02-27 20:46:55 -05:00
|
|
|
encodedSuccessResponse = encodeResponse(webIdentityResponse)
|
2019-02-05 18:47:11 -05:00
|
|
|
}
|
2018-10-09 17:00:01 -04:00
|
|
|
|
|
|
|
writeSuccessResponseXML(w, encodedSuccessResponse)
|
|
|
|
}
|
2019-02-05 18:47:11 -05:00
|
|
|
|
|
|
|
// AssumeRoleWithWebIdentity - implementation of AWS STS API supporting OAuth2.0
|
|
|
|
// users from web identity provider such as Facebook, Google, or any OpenID
|
|
|
|
// Connect-compatible identity provider.
|
|
|
|
//
|
|
|
|
// Eg:-
|
|
|
|
// $ curl https://minio:9000/?Action=AssumeRoleWithWebIdentity&WebIdentityToken=<jwt>
|
|
|
|
func (sts *stsAPIHandlers) AssumeRoleWithWebIdentity(w http.ResponseWriter, r *http.Request) {
|
2020-09-12 02:02:32 -04:00
|
|
|
sts.AssumeRoleWithSSO(w, r)
|
2019-02-05 18:47:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// AssumeRoleWithClientGrants - implementation of AWS STS extension API supporting
|
|
|
|
// OAuth2.0 client credential grants.
|
|
|
|
//
|
|
|
|
// Eg:-
|
|
|
|
// $ curl https://minio:9000/?Action=AssumeRoleWithClientGrants&Token=<jwt>
|
|
|
|
func (sts *stsAPIHandlers) AssumeRoleWithClientGrants(w http.ResponseWriter, r *http.Request) {
|
2020-09-12 02:02:32 -04:00
|
|
|
sts.AssumeRoleWithSSO(w, r)
|
2019-02-05 18:47:11 -05:00
|
|
|
}
|
2019-09-09 19:12:29 -04:00
|
|
|
|
|
|
|
// AssumeRoleWithLDAPIdentity - implements user auth against LDAP server
|
|
|
|
func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AssumeRoleWithLDAPIdentity")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, nil, stsLDAPPassword)
|
2020-06-05 01:07:55 -04:00
|
|
|
|
2019-09-09 19:12:29 -04:00
|
|
|
// Parse the incoming form data.
|
2021-08-08 01:43:01 -04:00
|
|
|
if err := parseForm(r); err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-09-09 19:12:29 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
if r.Form.Get(stsVersion) != stsAPIVersion {
|
2020-06-05 01:07:55 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter,
|
|
|
|
fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
|
2019-09-09 19:12:29 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
ldapUsername := r.Form.Get(stsLDAPUsername)
|
|
|
|
ldapPassword := r.Form.Get(stsLDAPPassword)
|
2019-09-09 19:12:29 -04:00
|
|
|
|
|
|
|
if ldapUsername == "" || ldapPassword == "" {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, fmt.Errorf("LDAPUsername and LDAPPassword cannot be empty"))
|
2019-09-09 19:12:29 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-06-05 01:07:55 -04:00
|
|
|
action := r.Form.Get(stsAction)
|
|
|
|
switch action {
|
|
|
|
case ldapIdentity:
|
|
|
|
default:
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-29 08:27:54 -05:00
|
|
|
sessionPolicyStr := r.Form.Get(stsPolicy)
|
2019-09-23 18:21:16 -04:00
|
|
|
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
|
|
|
|
// The plain text that you use for both inline and managed session
|
|
|
|
// policies shouldn't exceed 2048 characters.
|
|
|
|
if len(sessionPolicyStr) > 2048 {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Session policy should not exceed 2048 characters"))
|
2019-09-23 18:21:16 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(sessionPolicyStr) > 0 {
|
|
|
|
sessionPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicyStr)))
|
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-09-23 18:21:16 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Version in policy must not be empty
|
|
|
|
if sessionPolicy.Version == "" {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Version needs to be specified in session policy"))
|
2019-09-23 18:21:16 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-10 19:52:49 -05:00
|
|
|
ldapUserDN, groupDistNames, err := globalLDAPConfig.Bind(ldapUsername, ldapPassword)
|
2019-09-09 19:12:29 -04:00
|
|
|
if err != nil {
|
2021-01-18 00:54:32 -05:00
|
|
|
err = fmt.Errorf("LDAP server error: %w", err)
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
2019-09-09 19:12:29 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-02-10 19:52:49 -05:00
|
|
|
// Check if this user or their groups have a policy applied.
|
2021-03-25 03:38:15 -04:00
|
|
|
ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, false, groupDistNames...)
|
2022-05-10 20:14:55 -04:00
|
|
|
if len(ldapPolicies) == 0 && globalAuthZPlugin == nil {
|
2021-03-23 18:15:51 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
|
|
|
fmt.Errorf("expecting a policy to be set for user `%s` or one of their groups: `%s` - rejecting this request",
|
|
|
|
ldapUserDN, strings.Join(groupDistNames, "`,`")))
|
2021-02-10 19:52:49 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-22 15:13:21 -04:00
|
|
|
expiryDur, err := globalLDAPConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds))
|
|
|
|
if err != nil {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-09 19:12:29 -04:00
|
|
|
m := map[string]interface{}{
|
2021-07-11 21:38:52 -04:00
|
|
|
expClaim: UTCNow().Add(expiryDur).Unix(),
|
|
|
|
ldapUser: ldapUserDN,
|
|
|
|
ldapUserN: ldapUsername,
|
2019-09-09 19:12:29 -04:00
|
|
|
}
|
|
|
|
|
2019-09-23 18:21:16 -04:00
|
|
|
if len(sessionPolicyStr) > 0 {
|
|
|
|
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicyStr))
|
|
|
|
}
|
|
|
|
|
2019-10-23 01:59:13 -04:00
|
|
|
secret := globalActiveCred.SecretKey
|
2019-09-09 19:12:29 -04:00
|
|
|
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
|
|
|
|
if err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
2019-09-09 19:12:29 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-28 15:49:56 -04:00
|
|
|
// Set the parent of the temporary access key, this is useful
|
|
|
|
// in obtaining service accounts by this cred.
|
2021-01-18 00:54:32 -05:00
|
|
|
cred.ParentUser = ldapUserDN
|
2020-04-28 15:49:56 -04:00
|
|
|
|
2020-05-20 14:33:35 -04:00
|
|
|
// Set this value to LDAP groups, LDAP user can be part
|
|
|
|
// of large number of groups
|
2021-02-10 19:52:49 -05:00
|
|
|
cred.Groups = groupDistNames
|
2020-05-20 14:33:35 -04:00
|
|
|
|
2020-04-28 15:49:56 -04:00
|
|
|
// Set the newly generated credentials, policyName is empty on purpose
|
|
|
|
// LDAP policies are applied automatically using their ldapUser, ldapGroups
|
|
|
|
// mapping.
|
2021-11-15 17:14:22 -05:00
|
|
|
if err = globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, ""); err != nil {
|
2020-05-21 12:09:18 -04:00
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
2019-09-09 19:12:29 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 18:52:43 -05:00
|
|
|
// Call hook for site replication.
|
2021-10-06 19:36:31 -04:00
|
|
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemSTSAcc,
|
|
|
|
STSCredential: &madmin.SRSTSCredential{
|
|
|
|
AccessKey: cred.AccessKey,
|
|
|
|
SecretKey: cred.SecretKey,
|
|
|
|
SessionToken: cred.SessionToken,
|
2022-01-06 18:52:43 -05:00
|
|
|
ParentUser: cred.ParentUser,
|
2021-10-06 19:36:31 -04:00
|
|
|
},
|
|
|
|
}); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-09 19:12:29 -04:00
|
|
|
ldapIdentityResponse := &AssumeRoleWithLDAPResponse{
|
|
|
|
Result: LDAPIdentityResult{
|
|
|
|
Credentials: cred,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
ldapIdentityResponse.ResponseMetadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
|
|
|
|
encodedSuccessResponse := encodeResponse(ldapIdentityResponse)
|
|
|
|
|
|
|
|
writeSuccessResponseXML(w, encodedSuccessResponse)
|
|
|
|
}
|
2021-09-07 22:03:48 -04:00
|
|
|
|
|
|
|
// AssumeRoleWithCertificate implements user authentication with client certificates.
|
|
|
|
// It verifies the client-provided X.509 certificate, maps the certificate to an S3 policy
|
|
|
|
// and returns temp. S3 credentials to the client.
|
|
|
|
//
|
|
|
|
// API endpoint: https://minio:9000?Action=AssumeRoleWithCertificate&Version=2011-06-15
|
|
|
|
func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *http.Request) {
|
2022-01-02 12:15:06 -05:00
|
|
|
ctx := newContext(r, w, "AssumeRoleWithCertificate")
|
2021-09-07 22:03:48 -04:00
|
|
|
|
|
|
|
if !globalSTSTLSConfig.Enabled {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSNotInitialized, errors.New("STS API 'AssumeRoleWithCertificate' is disabled"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have to establish a TLS connection and the
|
|
|
|
// client must provide exactly one client certificate.
|
|
|
|
// Otherwise, we don't have a certificate to verify or
|
|
|
|
// the policy lookup would ambigious.
|
|
|
|
if r.TLS == nil {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInsecureConnection, errors.New("No TLS connection attempt"))
|
|
|
|
return
|
|
|
|
}
|
2021-09-17 12:37:01 -04:00
|
|
|
|
|
|
|
// A client may send a certificate chain such that we end up
|
|
|
|
// with multiple peer certificates. However, we can only accept
|
|
|
|
// a single client certificate. Otherwise, the certificate to
|
|
|
|
// policy mapping would be ambigious.
|
|
|
|
// However, we can filter all CA certificates and only check
|
|
|
|
// whether they client has sent exactly one (non-CA) leaf certificate.
|
2022-01-02 12:15:06 -05:00
|
|
|
peerCertificates := make([]*x509.Certificate, 0, len(r.TLS.PeerCertificates))
|
2021-09-17 12:37:01 -04:00
|
|
|
for _, cert := range r.TLS.PeerCertificates {
|
|
|
|
if cert.IsCA {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
peerCertificates = append(peerCertificates, cert)
|
|
|
|
}
|
|
|
|
r.TLS.PeerCertificates = peerCertificates
|
|
|
|
|
|
|
|
// Now, we have to check that the client has provided exactly one leaf
|
|
|
|
// certificate that we can map to a policy.
|
2021-09-07 22:03:48 -04:00
|
|
|
if len(r.TLS.PeerCertificates) == 0 {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, errors.New("No client certificate provided"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(r.TLS.PeerCertificates) > 1 {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, errors.New("More than one client certificate provided"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
certificate := r.TLS.PeerCertificates[0]
|
2021-09-17 12:37:01 -04:00
|
|
|
if !globalSTSTLSConfig.InsecureSkipVerify { // Verify whether the client certificate has been issued by a trusted CA.
|
2021-09-07 22:03:48 -04:00
|
|
|
_, err := certificate.Verify(x509.VerifyOptions{
|
|
|
|
KeyUsages: []x509.ExtKeyUsage{
|
|
|
|
x509.ExtKeyUsageClientAuth,
|
|
|
|
},
|
|
|
|
Roots: globalRootCAs,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidClientCertificate, err)
|
|
|
|
return
|
|
|
|
}
|
2021-11-05 05:16:26 -04:00
|
|
|
} else {
|
|
|
|
// Technically, there is no security argument for verifying the key usage
|
|
|
|
// when we don't verify that the certificate has been issued by a trusted CA.
|
|
|
|
// Any client can create a certificate with arbitrary key usage settings.
|
|
|
|
//
|
|
|
|
// However, this check ensures that a certificate with an invalid key usage
|
|
|
|
// gets rejected even when we skip certificate verification. This helps
|
|
|
|
// clients detect malformed certificates during testing instead of e.g.
|
|
|
|
// a self-signed certificate that works while a comparable certificate
|
|
|
|
// issued by a trusted CA fails due to the MinIO server being less strict
|
|
|
|
// w.r.t. key usage verification.
|
|
|
|
//
|
|
|
|
// Basically, MinIO is more consistent (from a client perspective) when
|
|
|
|
// we verify the key usage all the time.
|
|
|
|
var validKeyUsage bool
|
|
|
|
for _, usage := range certificate.ExtKeyUsage {
|
|
|
|
if usage == x509.ExtKeyUsageAny || usage == x509.ExtKeyUsageClientAuth {
|
|
|
|
validKeyUsage = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !validKeyUsage {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, errors.New("certificate is not valid for client authentication"))
|
|
|
|
return
|
|
|
|
}
|
2021-09-07 22:03:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// We map the X.509 subject common name to the policy. So, a client
|
|
|
|
// with the common name "foo" will be associated with the policy "foo".
|
|
|
|
// Other mapping functions - e.g. public-key hash based mapping - are
|
|
|
|
// possible but not implemented.
|
|
|
|
//
|
|
|
|
// Group mapping is not possible with standard X.509 certificates.
|
|
|
|
if certificate.Subject.CommonName == "" {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, errors.New("certificate subject CN cannot be empty"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
expiry, err := globalSTSTLSConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds))
|
|
|
|
if err != nil {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// We set the expiry of the temp. credentials to the minimum of the
|
|
|
|
// configured expiry and the duration until the certificate itself
|
|
|
|
// expires.
|
|
|
|
// We must not issue credentials that out-live the certificate.
|
|
|
|
if validUntil := time.Until(certificate.NotAfter); validUntil < expiry {
|
|
|
|
expiry = validUntil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Associate any service accounts to the certificate CN
|
|
|
|
parentUser := "tls:" + certificate.Subject.CommonName
|
|
|
|
|
|
|
|
tmpCredentials, err := auth.GetNewCredentialsWithMetadata(map[string]interface{}{
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
expClaim: UTCNow().Add(expiry).Unix(),
|
|
|
|
parentClaim: parentUser,
|
|
|
|
subClaim: certificate.Subject.CommonName,
|
|
|
|
audClaim: certificate.Subject.Organization,
|
|
|
|
issClaim: certificate.Issuer.CommonName,
|
2021-09-07 22:03:48 -04:00
|
|
|
}, globalActiveCred.SecretKey)
|
|
|
|
if err != nil {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpCredentials.ParentUser = parentUser
|
2022-01-06 18:52:43 -05:00
|
|
|
policyName := certificate.Subject.CommonName
|
|
|
|
err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName)
|
2021-09-07 22:03:48 -04:00
|
|
|
if err != nil {
|
|
|
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 18:52:43 -05:00
|
|
|
// Call hook for site replication.
|
|
|
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemSTSAcc,
|
|
|
|
STSCredential: &madmin.SRSTSCredential{
|
|
|
|
AccessKey: tmpCredentials.AccessKey,
|
|
|
|
SecretKey: tmpCredentials.SecretKey,
|
|
|
|
SessionToken: tmpCredentials.SessionToken,
|
|
|
|
ParentUser: tmpCredentials.ParentUser,
|
|
|
|
ParentPolicyMapping: policyName,
|
|
|
|
},
|
|
|
|
}); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
response := new(AssumeRoleWithCertificateResponse)
|
2021-09-07 22:03:48 -04:00
|
|
|
response.Result.Credentials = tmpCredentials
|
|
|
|
response.Metadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
|
|
|
|
writeSuccessResponseXML(w, encodeResponse(response))
|
|
|
|
}
|