mirror of
https://github.com/minio/minio.git
synced 2025-05-24 02:46:14 -04:00
Add APIs to create and list access keys for LDAP (#18402)
This commit is contained in:
parent
162eced7d2
commit
ad8a34858f
@ -19,10 +19,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/mux"
|
"github.com/minio/mux"
|
||||||
"github.com/minio/pkg/v2/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
@ -175,3 +179,267 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
writeSuccessResponseJSON(w, encryptedData)
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddServiceAccountLDAP adds a new service account for provided LDAP username or DN
|
||||||
|
//
|
||||||
|
// PUT /minio/admin/v3/idp/ldap/add-service-account
|
||||||
|
func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cred, opts, createReq, targetUser, APIError := commonAddServiceAccount(r)
|
||||||
|
if APIError.Code != "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, APIError, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail if ldap is not enabled
|
||||||
|
if !globalIAMSys.LDAPConfig.Enabled() {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errors.New("LDAP not enabled")), r.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the user for the request sender (as it may be sent via a service
|
||||||
|
// account or STS account):
|
||||||
|
requestorUser := cred.AccessKey
|
||||||
|
requestorParentUser := cred.AccessKey
|
||||||
|
requestorGroups := cred.Groups
|
||||||
|
requestorIsDerivedCredential := false
|
||||||
|
if cred.IsServiceAccount() || cred.IsTemp() {
|
||||||
|
requestorParentUser = cred.ParentUser
|
||||||
|
requestorIsDerivedCredential = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are creating svc account for request sender.
|
||||||
|
isSvcAccForRequestor := false
|
||||||
|
if targetUser == requestorUser || targetUser == requestorParentUser {
|
||||||
|
isSvcAccForRequestor = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
targetGroups []string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// If we are creating svc account for request sender, ensure
|
||||||
|
// that targetUser is a real user (i.e. not derived
|
||||||
|
// credentials).
|
||||||
|
if isSvcAccForRequestor {
|
||||||
|
if requestorIsDerivedCredential {
|
||||||
|
if requestorParentUser == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
||||||
|
errors.New("service accounts cannot be generated for temporary credentials without parent")), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetUser = requestorParentUser
|
||||||
|
}
|
||||||
|
targetGroups = requestorGroups
|
||||||
|
|
||||||
|
// Deny if the target user is not LDAP
|
||||||
|
isLDAP, err := globalIAMSys.LDAPConfig.DoesUsernameExist(targetUser)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isLDAP == "" {
|
||||||
|
err := errors.New("Specified user does not exist on LDAP server")
|
||||||
|
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
||||||
|
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of LDAP/OIDC we need to set `opts.claims` to ensure
|
||||||
|
// it is associated with the LDAP/OIDC user properly.
|
||||||
|
for k, v := range cred.Claims {
|
||||||
|
if k == expClaim {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.claims[k] = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isDN := globalIAMSys.LDAPConfig.IsLDAPUserDN(targetUser)
|
||||||
|
|
||||||
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
|
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
|
if err != nil {
|
||||||
|
// if not found, check if DN
|
||||||
|
if strings.Contains(err.Error(), "not found") && isDN {
|
||||||
|
// warn user that DNs are not allowed
|
||||||
|
err = fmt.Errorf("Must use short username to add service account. %w", err)
|
||||||
|
}
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.claims[ldapUser] = targetUser // DN
|
||||||
|
}
|
||||||
|
|
||||||
|
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createResp := madmin.AddServiceAccountResp{
|
||||||
|
Credentials: madmin.Credentials{
|
||||||
|
AccessKey: newCred.AccessKey,
|
||||||
|
SecretKey: newCred.SecretKey,
|
||||||
|
Expiration: newCred.Expiration,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(createResp)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Call hook for cluster-replication if the service account is not for a
|
||||||
|
// root user.
|
||||||
|
if newCred.ParentUser != globalActiveCred.AccessKey {
|
||||||
|
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||||
|
Type: madmin.SRIAMItemSvcAcc,
|
||||||
|
SvcAccChange: &madmin.SRSvcAccChange{
|
||||||
|
Create: &madmin.SRSvcAccCreate{
|
||||||
|
Parent: newCred.ParentUser,
|
||||||
|
AccessKey: newCred.AccessKey,
|
||||||
|
SecretKey: newCred.SecretKey,
|
||||||
|
Groups: newCred.Groups,
|
||||||
|
Name: newCred.Name,
|
||||||
|
Description: newCred.Description,
|
||||||
|
Claims: opts.claims,
|
||||||
|
SessionPolicy: createReq.Policy,
|
||||||
|
Status: auth.AccountOn,
|
||||||
|
Expiration: createReq.Expiration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccessKeysLDAP - GET /minio/admin/v3/idp/ldap/list-access-keys
|
||||||
|
func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userDN := r.Form.Get("userDN")
|
||||||
|
|
||||||
|
// If listing is requested for a specific user (who is not the request
|
||||||
|
// sender), check that the user has permissions.
|
||||||
|
if userDN != "" && userDN != cred.ParentUser {
|
||||||
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.ListServiceAccountsAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
DenyOnly: true,
|
||||||
|
}) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userDN = cred.AccessKey
|
||||||
|
if cred.ParentUser != "" {
|
||||||
|
userDN = cred.ParentUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetAccount, err := globalIAMSys.LDAPConfig.DoesUsernameExist(userDN)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
} else if userDN == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listType := r.Form.Get("listType")
|
||||||
|
if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
|
||||||
|
// default to both
|
||||||
|
listType = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceAccounts []auth.Credentials
|
||||||
|
var stsKeys []auth.Credentials
|
||||||
|
|
||||||
|
if listType == "" || listType == "sts-only" {
|
||||||
|
stsKeys, err = globalIAMSys.ListSTSAccounts(ctx, targetAccount)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if listType == "" || listType == "svcacc-only" {
|
||||||
|
serviceAccounts, err = globalIAMSys.ListServiceAccounts(ctx, targetAccount)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceAccountList []madmin.ServiceAccountInfo
|
||||||
|
var stsKeyList []madmin.ServiceAccountInfo
|
||||||
|
|
||||||
|
for _, svc := range serviceAccounts {
|
||||||
|
expiryTime := svc.Expiration
|
||||||
|
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
||||||
|
AccessKey: svc.AccessKey,
|
||||||
|
Expiration: &expiryTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, sts := range stsKeys {
|
||||||
|
expiryTime := sts.Expiration
|
||||||
|
stsKeyList = append(stsKeyList, madmin.ServiceAccountInfo{
|
||||||
|
AccessKey: sts.AccessKey,
|
||||||
|
Expiration: &expiryTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
listResp := madmin.ListAccessKeysLDAPResp{
|
||||||
|
ServiceAccounts: serviceAccountList,
|
||||||
|
STSKeys: stsKeyList,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(listResp)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
@ -614,72 +614,17 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||||
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx, cred, opts, createReq, targetUser, APIError := commonAddServiceAccount(r)
|
||||||
|
if APIError.Code != "" {
|
||||||
// Get current object layer instance.
|
writeErrorResponseJSON(ctx, w, APIError, r.URL)
|
||||||
objectAPI := newObjectLayerFn()
|
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
|
||||||
if s3Err != ErrNone {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
password := cred.SecretKey
|
|
||||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var createReq madmin.AddServiceAccountReq
|
|
||||||
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// service account access key cannot have space characters beginning and end of the string.
|
|
||||||
if hasSpaceBE(createReq.AccessKey) {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := createReq.Validate(); err != nil {
|
|
||||||
// Since this validation would happen client side as well, we only send
|
|
||||||
// a generic error message here.
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
targetUser string
|
|
||||||
targetGroups []string
|
targetGroups []string
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
// If the request did not set a TargetUser, the service account is
|
|
||||||
// created for the request sender.
|
|
||||||
targetUser = createReq.TargetUser
|
|
||||||
if targetUser == "" {
|
|
||||||
targetUser = cred.AccessKey
|
|
||||||
}
|
|
||||||
|
|
||||||
description := createReq.Description
|
|
||||||
if description == "" {
|
|
||||||
description = createReq.Comment
|
|
||||||
}
|
|
||||||
opts := newServiceAccountOpts{
|
|
||||||
accessKey: createReq.AccessKey,
|
|
||||||
secretKey: createReq.SecretKey,
|
|
||||||
name: createReq.Name,
|
|
||||||
description: description,
|
|
||||||
expiration: createReq.Expiration,
|
|
||||||
claims: make(map[string]interface{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the user for the request sender (as it may be sent via a service
|
// Find the user for the request sender (as it may be sent via a service
|
||||||
// account or STS account):
|
// account or STS account):
|
||||||
requestorUser := cred.AccessKey
|
requestorUser := cred.AccessKey
|
||||||
@ -713,23 +658,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// that targetUser is a real user (i.e. not derived
|
// that targetUser is a real user (i.e. not derived
|
||||||
// credentials).
|
// credentials).
|
||||||
if isSvcAccForRequestor {
|
if isSvcAccForRequestor {
|
||||||
// Check if adding service account is explicitly denied.
|
|
||||||
//
|
|
||||||
// This allows turning off service accounts for request sender,
|
|
||||||
// if there is no deny statement this call is implicitly enabled.
|
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
|
||||||
AccountName: requestorUser,
|
|
||||||
Groups: requestorGroups,
|
|
||||||
Action: policy.CreateServiceAccountAdminAction,
|
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
|
||||||
IsOwner: owner,
|
|
||||||
Claims: cred.Claims,
|
|
||||||
DenyOnly: true,
|
|
||||||
}) {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if requestorIsDerivedCredential {
|
if requestorIsDerivedCredential {
|
||||||
if requestorParentUser == "" {
|
if requestorParentUser == "" {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
||||||
@ -748,32 +676,16 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
opts.claims[k] = v
|
opts.claims[k] = v
|
||||||
}
|
}
|
||||||
} else {
|
} else if globalIAMSys.LDAPConfig.Enabled() {
|
||||||
// Need permission if we are creating a service account for a
|
|
||||||
// user <> to the request sender
|
|
||||||
if !globalIAMSys.IsAllowed(policy.Args{
|
|
||||||
AccountName: requestorUser,
|
|
||||||
Groups: requestorGroups,
|
|
||||||
Action: policy.CreateServiceAccountAdminAction,
|
|
||||||
ConditionValues: getConditionValues(r, "", cred),
|
|
||||||
IsOwner: owner,
|
|
||||||
Claims: cred.Claims,
|
|
||||||
}) {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||||
// query their groups:
|
// query their groups:
|
||||||
if globalIAMSys.LDAPConfig.Enabled() {
|
opts.claims[ldapUserN] = targetUser // simple username
|
||||||
opts.claims[ldapUserN] = targetUser // simple username
|
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
if err != nil {
|
||||||
if err != nil {
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
opts.claims[ldapUser] = targetUser // username DN
|
|
||||||
}
|
}
|
||||||
|
opts.claims[ldapUser] = targetUser // username DN
|
||||||
|
|
||||||
// NOTE: if not using LDAP, then internal IDP or open ID is
|
// NOTE: if not using LDAP, then internal IDP or open ID is
|
||||||
// being used - in the former, group info is enforced when
|
// being used - in the former, group info is enforced when
|
||||||
@ -781,19 +693,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
// latter, a group notion is not supported.
|
// latter, a group notion is not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
var sp *policy.Policy
|
|
||||||
if len(createReq.Policy) > 0 {
|
|
||||||
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if sp.Version == "" && len(sp.Statements) == 0 {
|
|
||||||
sp = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.sessionPolicy = sp
|
|
||||||
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@ -814,7 +713,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedData, err := madmin.EncryptData(password, data)
|
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@ -2514,3 +2413,85 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
// Get current object layer instance.
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrServerNotInitialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(s3Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
password := cred.SecretKey
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var createReq madmin.AddServiceAccountReq
|
||||||
|
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// service account access key cannot have space characters beginning and end of the string.
|
||||||
|
if hasSpaceBE(createReq.AccessKey) {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createReq.Validate(); err != nil {
|
||||||
|
// Since this validation would happen client side as well, we only send
|
||||||
|
// a generic error message here.
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||||
|
}
|
||||||
|
// If the request did not set a TargetUser, the service account is
|
||||||
|
// created for the request sender.
|
||||||
|
targetUser := createReq.TargetUser
|
||||||
|
if targetUser == "" {
|
||||||
|
targetUser = cred.AccessKey
|
||||||
|
}
|
||||||
|
|
||||||
|
description := createReq.Description
|
||||||
|
if description == "" {
|
||||||
|
description = createReq.Comment
|
||||||
|
}
|
||||||
|
opts := newServiceAccountOpts{
|
||||||
|
accessKey: createReq.AccessKey,
|
||||||
|
secretKey: createReq.SecretKey,
|
||||||
|
name: createReq.Name,
|
||||||
|
description: description,
|
||||||
|
expiration: createReq.Expiration,
|
||||||
|
claims: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if action is allowed if creating access key for another user
|
||||||
|
// Check if action is explicitly denied if for self
|
||||||
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.CreateServiceAccountAdminAction,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
DenyOnly: (targetUser == cred.AccessKey || targetUser == cred.ParentUser),
|
||||||
|
}) {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAccessDenied)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sp *policy.Policy
|
||||||
|
if len(createReq.Policy) > 0 {
|
||||||
|
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
||||||
|
if err != nil {
|
||||||
|
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.sessionPolicy = sp
|
||||||
|
|
||||||
|
return ctx, cred, opts, createReq, targetUser, APIError{}
|
||||||
|
}
|
||||||
|
@ -300,6 +300,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.GetIdentityProviderCfg))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.GetIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
||||||
|
|
||||||
|
// LDAP specific service accounts ops
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp/ldap/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccountLDAP))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys").
|
||||||
|
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAP)).
|
||||||
|
Queries("userDN", "{userDN:.*}", "listType", "{listType:.*}")
|
||||||
|
|
||||||
// LDAP IAM operations
|
// LDAP IAM operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
||||||
|
@ -2319,6 +2319,27 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
|
|||||||
return serviceAccounts, nil
|
return serviceAccounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListSTSAccounts - lists only STS accounts from the cache.
|
||||||
|
func (store *IAMStoreSys) ListSTSAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
||||||
|
cache := store.rlock()
|
||||||
|
defer store.runlock()
|
||||||
|
|
||||||
|
var stsAccounts []auth.Credentials
|
||||||
|
for _, u := range cache.iamSTSAccountsMap {
|
||||||
|
v := u.Credentials
|
||||||
|
if accessKey != "" && v.ParentUser == accessKey {
|
||||||
|
if v.IsTemp() {
|
||||||
|
// Hide secret key & session key here
|
||||||
|
v.SecretKey = ""
|
||||||
|
v.SessionToken = ""
|
||||||
|
stsAccounts = append(stsAccounts, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stsAccounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddUser - adds/updates long term user account to storage.
|
// AddUser - adds/updates long term user account to storage.
|
||||||
func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) (updatedAt time.Time, err error) {
|
func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) (updatedAt time.Time, err error) {
|
||||||
cache := store.lock()
|
cache := store.lock()
|
||||||
|
18
cmd/iam.go
18
cmd/iam.go
@ -1041,7 +1041,7 @@ func (sys *IAMSys) UpdateServiceAccount(ctx context.Context, accessKey string, o
|
|||||||
return updatedAt, nil
|
return updatedAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListServiceAccounts - lists all services accounts associated to a specific user
|
// ListServiceAccounts - lists all service accounts associated to a specific user
|
||||||
func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
||||||
if !sys.Initialized() {
|
if !sys.Initialized() {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
@ -1055,7 +1055,7 @@ func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTempAccounts - lists all services accounts associated to a specific user
|
// ListTempAccounts - lists all temporary service accounts associated to a specific user
|
||||||
func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]UserIdentity, error) {
|
func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]UserIdentity, error) {
|
||||||
if !sys.Initialized() {
|
if !sys.Initialized() {
|
||||||
return nil, errServerNotInitialized
|
return nil, errServerNotInitialized
|
||||||
@ -1069,6 +1069,20 @@ func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]Us
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListSTSAccounts - lists all STS accounts associated to a specific user
|
||||||
|
func (sys *IAMSys) ListSTSAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
||||||
|
if !sys.Initialized() {
|
||||||
|
return nil, errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sys.configLoaded:
|
||||||
|
return sys.store.ListSTSAccounts(ctx, accessKey)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetServiceAccount - wrapper method to get information about a service account
|
// GetServiceAccount - wrapper method to get information about a service account
|
||||||
func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *policy.Policy, error) {
|
func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *policy.Policy, error) {
|
||||||
sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey)
|
sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user