Add support for Identity Management Plugin (#14913)

- Adds an STS API `AssumeRoleWithCustomToken` that can be used to 
  authenticate via the Id. Mgmt. Plugin.
- Adds a sample identity manager plugin implementation
- Add doc for plugin and STS API
- Add an example program using go SDK for AssumeRoleWithCustomToken
This commit is contained in:
Aditya Manthramurthy
2022-05-26 17:58:09 -07:00
committed by GitHub
parent 5c81d0d89a
commit 464b9d7c80
14 changed files with 888 additions and 28 deletions

View File

@@ -26,6 +26,7 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
@@ -54,11 +55,12 @@ const (
stsLDAPPassword = "LDAPPassword"
// STS API action constants
clientGrants = "AssumeRoleWithClientGrants"
webIdentity = "AssumeRoleWithWebIdentity"
ldapIdentity = "AssumeRoleWithLDAPIdentity"
clientCertificate = "AssumeRoleWithCertificate"
assumeRole = "AssumeRole"
clientGrants = "AssumeRoleWithClientGrants"
webIdentity = "AssumeRoleWithWebIdentity"
ldapIdentity = "AssumeRoleWithLDAPIdentity"
clientCertificate = "AssumeRoleWithCertificate"
customTokenIdentity = "AssumeRoleWithCustomToken"
assumeRole = "AssumeRole"
stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB
@@ -128,6 +130,11 @@ func registerSTSRouter(router *mux.Router) {
stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithCertificate)).
Queries(stsAction, clientCertificate).
Queries(stsVersion, stsAPIVersion)
// AssumeRoleWithCustomToken
stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithCustomToken)).
Queries(stsAction, customTokenIdentity).
Queries(stsVersion, stsAPIVersion)
}
func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Credentials, isErrCodeSTS bool, stsErr STSErrorCode) {
@@ -815,3 +822,125 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
response.Metadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
writeSuccessResponseXML(w, encodeResponse(response))
}
// AssumeRoleWithCustomToken implements user authentication with custom tokens.
// These tokens are opaque to MinIO and are verified by a configured (external)
// Identity Management Plugin.
//
// API endpoint: https://minio:9000?Action=AssumeRoleWithCustomToken&Token=xxx
func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AssumeRoleWithCustomToken")
if globalAuthNPlugin == nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSNotInitialized, errors.New("STS API 'AssumeRoleWithCustomToken' is disabled"))
return
}
action := r.Form.Get(stsAction)
if action != customTokenIdentity {
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action))
return
}
defer logger.AuditLog(ctx, w, r, nil)
token := r.Form.Get(stsToken)
if token == "" {
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid empty `Token` parameter provided"))
return
}
durationParam := r.Form.Get(stsDurationSeconds)
var requestedDuration int
if durationParam != "" {
var err error
requestedDuration, err = strconv.Atoi(durationParam)
if err != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid requested duration: %s", durationParam))
return
}
}
roleArnStr := r.Form.Get(stsRoleArn)
roleArn, _, err := globalIAMSys.GetRolePolicy(roleArnStr)
if err != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
fmt.Errorf("Error processing parameter %s: %v", stsRoleArn, err))
return
}
res, err := globalAuthNPlugin.Authenticate(roleArn, token)
if err != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
return
}
// If authentication failed, return the error message to the user.
if res.Failure != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSUpstreamError, errors.New(res.Failure.Reason))
return
}
// It is required that parent user be set.
if res.Success.User == "" {
writeSTSErrorResponse(ctx, w, true, ErrSTSUpstreamError, errors.New("A valid user was not returned by the authenticator."))
return
}
// Expiry is set as minimum of requested value and value allowed by auth
// plugin.
expiry := res.Success.MaxValiditySeconds
if durationParam != "" && requestedDuration < expiry {
expiry = requestedDuration
}
parentUser := "custom:" + res.Success.User
// metadata map
m := map[string]interface{}{
expClaim: UTCNow().Add(time.Duration(expiry) * time.Second).Unix(),
parentClaim: parentUser,
subClaim: parentUser,
roleArnClaim: roleArn.String(),
}
// Add all other claims from the plugin **without** replacing any
// existing claims.
for k, v := range res.Success.Claims {
if _, ok := m[k]; !ok {
m[k] = v
}
}
tmpCredentials, err := auth.GetNewCredentialsWithMetadata(m, globalActiveCred.SecretKey)
if err != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
return
}
tmpCredentials.ParentUser = parentUser
err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, "")
if err != nil {
writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err)
return
}
// 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,
},
}); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
response := new(AssumeRoleWithCustomTokenResponse)
response.Result.Credentials = tmpCredentials
response.Result.AssumedUser = parentUser
response.Metadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
writeSuccessResponseXML(w, encodeResponse(response))
}