diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index 5f569d820..5d1131dbd 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -528,6 +528,89 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) { } } +// TemporaryAccountInfo - GET /minio/admin/v3/temporary-account-info +func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "TemporaryAccountInfo") + + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + // Get current object layer instance. + objectAPI := newObjectLayerFn() + if objectAPI == nil || globalNotificationSys == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + + cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "") + if s3Err != ErrNone { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL) + return + } + + accessKey := mux.Vars(r)["accessKey"] + if accessKey == "" { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL) + return + } + + if !globalIAMSys.IsAllowed(iampolicy.Args{ + AccountName: cred.AccessKey, + Action: iampolicy.ListTemporaryAccountsAdminAction, + ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), + IsOwner: owner, + Claims: claims, + }) { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL) + return + } + + stsAccount, policy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + var stsAccountPolicy iampolicy.Policy + + if policy != nil { + stsAccountPolicy = *policy + } else { + policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + stsAccountPolicy = globalIAMSys.GetCombinedPolicy(policiesNames...) + } + + policyJSON, err := json.MarshalIndent(stsAccountPolicy, "", " ") + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + infoResp := madmin.TemporaryAccountInfoResp{ + ParentUser: stsAccount.ParentUser, + AccountStatus: stsAccount.Status, + ImpliedPolicy: policy == nil, + Policy: string(policyJSON), + } + + data, err := json.Marshal(infoResp) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + encryptedData, err := madmin.EncryptData(cred.SecretKey, data) + if err != nil { + writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) + return + } + + writeSuccessResponseJSON(w, encryptedData) +} + // AddServiceAccount - PUT /minio/admin/v3/add-service-account func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "AddServiceAccount") diff --git a/cmd/admin-router.go b/cmd/admin-router.go index eca9e7599..dd9289ae0 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -142,6 +142,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) { adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts))) adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}") + // STS accounts ops + adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.TemporaryAccountInfo))).Queries("accessKey", "{accessKey:.*}") + // Info policy IAM latest adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}") // List policies latest diff --git a/cmd/iam.go b/cmd/iam.go index 413b308de..1a43e24f0 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1034,7 +1034,7 @@ func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (aut } func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) { - sa, embeddedPolicy, err := sys.getAccountWithEmbeddedPolicy(ctx, accessKey) + sa, jwtClaims, err := sys.getAccountWithClaims(ctx, accessKey) if err != nil { if err == errNoSuchAccount { return UserIdentity{}, nil, errNoSuchServiceAccount @@ -1045,11 +1045,38 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (Use return UserIdentity{}, nil, errNoSuchServiceAccount } + var embeddedPolicy *iampolicy.Policy + + pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA()) + sp, spok := jwtClaims.Lookup(iampolicy.SessionPolicyName) + if ptok && spok && pt == embeddedPolicyType { + policyBytes, err := base64.StdEncoding.DecodeString(sp) + if err != nil { + return UserIdentity{}, nil, err + } + embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes)) + if err != nil { + return UserIdentity{}, nil, err + } + } + return sa, embeddedPolicy, nil } +// GetTemporaryAccount - wrapper method to get information about a temporary account +func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) { + tmpAcc, embeddedPolicy, err := sys.getTempAccount(ctx, accessKey) + if err != nil { + return auth.Credentials{}, nil, err + } + // Hide secret & session keys + tmpAcc.Credentials.SecretKey = "" + tmpAcc.Credentials.SessionToken = "" + return tmpAcc.Credentials, embeddedPolicy, nil +} + func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) { - tmpAcc, embeddedPolicy, err := sys.getAccountWithEmbeddedPolicy(ctx, accessKey) + tmpAcc, claims, err := sys.getAccountWithClaims(ctx, accessKey) if err != nil { if err == errNoSuchAccount { return UserIdentity{}, nil, errNoSuchTempAccount @@ -1060,43 +1087,43 @@ func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserId return UserIdentity{}, nil, errNoSuchTempAccount } - return tmpAcc, embeddedPolicy, nil -} - -// getAccountWithEmbeddedPolicy - gets information about an account with its embedded policy if found -func (sys *IAMSys) getAccountWithEmbeddedPolicy(ctx context.Context, accessKey string) (u UserIdentity, p *iampolicy.Policy, err error) { - if !sys.Initialized() { - return u, nil, errServerNotInitialized - } - - sa, ok := sys.store.GetUser(accessKey) - if !ok { - return u, nil, errNoSuchAccount - } - var embeddedPolicy *iampolicy.Policy - jwtClaims, err := auth.ExtractClaims(sa.Credentials.SessionToken, sa.Credentials.SecretKey) - if err != nil { - jwtClaims, err = auth.ExtractClaims(sa.Credentials.SessionToken, globalActiveCred.SecretKey) - if err != nil { - return u, nil, err - } - } - pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA()) - sp, spok := jwtClaims.Lookup(iampolicy.SessionPolicyName) - if ptok && spok && pt == embeddedPolicyType { + sp, spok := claims.Lookup(iampolicy.SessionPolicyName) + if spok { policyBytes, err := base64.StdEncoding.DecodeString(sp) if err != nil { - return u, nil, err + return UserIdentity{}, nil, err } embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes)) if err != nil { - return u, nil, err + return UserIdentity{}, nil, err } } - return sa, embeddedPolicy, nil + return tmpAcc, embeddedPolicy, nil +} + +// getAccountWithClaims - gets information about an account with claims +func (sys *IAMSys) getAccountWithClaims(ctx context.Context, accessKey string) (UserIdentity, *jwt.MapClaims, error) { + if !sys.Initialized() { + return UserIdentity{}, nil, errServerNotInitialized + } + + acc, ok := sys.store.GetUser(accessKey) + if !ok { + return UserIdentity{}, nil, errNoSuchAccount + } + + jwtClaims, err := auth.ExtractClaims(acc.Credentials.SessionToken, acc.Credentials.SecretKey) + if err != nil { + jwtClaims, err = auth.ExtractClaims(acc.Credentials.SessionToken, globalActiveCred.SecretKey) + if err != nil { + return UserIdentity{}, nil, err + } + } + + return acc, jwtClaims, nil } // GetClaimsForSvcAcc - gets the claims associated with the service account.