mirror of
https://github.com/minio/minio.git
synced 2025-11-10 05:59:43 -05:00
Add internal IDP and OIDC users support for site-replication (#14041)
- This allows site-replication to be configured when using OpenID or the internal IDentity Provider. - Internal IDP IAM users and groups will now be replicated to all members of the set of replicated sites. - When using OpenID as the external identity provider, STS and service accounts are replicated. - Currently this change dis-allows root service accounts from being replicated (TODO: discuss security implications).
This commit is contained in:
committed by
GitHub
parent
f68bd37acf
commit
1981fe2072
@@ -178,6 +178,10 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
||||
err = globalSiteReplicationSys.PeerPolicyMappingHandler(ctx, item.PolicyMapping)
|
||||
case madmin.SRIAMItemSTSAcc:
|
||||
err = globalSiteReplicationSys.PeerSTSAccHandler(ctx, item.STSCredential)
|
||||
case madmin.SRIAMItemIAMUser:
|
||||
err = globalSiteReplicationSys.PeerIAMUserChangeHandler(ctx, item.IAMUser)
|
||||
case madmin.SRIAMItemGroupInfo:
|
||||
err = globalSiteReplicationSys.PeerGroupInfoChangeHandler(ctx, item.GroupInfo)
|
||||
}
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
|
||||
@@ -62,6 +62,17 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemIAMUser,
|
||||
IAMUser: &madmin.SRIAMUser{
|
||||
AccessKey: accessKey,
|
||||
IsDeleteReq: true,
|
||||
},
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ListUsers - GET /minio/admin/v3/list-users?bucket={bucket}
|
||||
@@ -220,11 +231,20 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
||||
} else {
|
||||
err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemGroupInfo,
|
||||
GroupInfo: &madmin.SRGroupInfo{
|
||||
UpdateReq: updReq,
|
||||
},
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetGroup - /minio/admin/v3/group?group=mygroup1
|
||||
@@ -427,6 +447,18 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemIAMUser,
|
||||
IAMUser: &madmin.SRIAMUser{
|
||||
AccessKey: accessKey,
|
||||
IsDeleteReq: false,
|
||||
UserReq: &ureq,
|
||||
},
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||
@@ -585,14 +617,9 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
// Call hook for cluster-replication.
|
||||
//
|
||||
// FIXME: This wont work in an OpenID situation as the parent credential
|
||||
// may not be present on peer clusters to provide inherited policies.
|
||||
// Also, we should not be replicating root user's service account - as
|
||||
// they are not authenticated by a common external IDP, so we skip when
|
||||
// opts.ldapUser == "".
|
||||
if _, isLDAPAccount := opts.claims[ldapUserN]; isLDAPAccount {
|
||||
// Call hook for cluster-replication if the service account is not for a
|
||||
// root user.
|
||||
if newCred.ParentUser != globalActiveCred.AccessKey {
|
||||
err = globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSvcAcc,
|
||||
SvcAccChange: &madmin.SRSvcAccChange{
|
||||
|
||||
@@ -636,6 +636,7 @@ func (sys *IAMSys) SetTempUser(ctx context.Context, accessKey string, cred auth.
|
||||
}
|
||||
|
||||
sys.notifyForUser(ctx, cred.AccessKey, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -551,13 +552,19 @@ func (c *SiteReplicationSys) PeerJoinReq(ctx context.Context, arg madmin.SRPeerJ
|
||||
// GetIDPSettings returns info about the configured identity provider. It is
|
||||
// used to validate that all peers have the same IDP.
|
||||
func (c *SiteReplicationSys) GetIDPSettings(ctx context.Context) madmin.IDPSettings {
|
||||
return madmin.IDPSettings{
|
||||
s := madmin.IDPSettings{}
|
||||
s.LDAP = madmin.LDAPSettings{
|
||||
IsLDAPEnabled: globalLDAPConfig.Enabled,
|
||||
LDAPUserDNSearchBase: globalLDAPConfig.UserDNSearchBaseDN,
|
||||
LDAPUserDNSearchFilter: globalLDAPConfig.UserDNSearchFilter,
|
||||
LDAPGroupSearchBase: globalLDAPConfig.GroupSearchBaseDistName,
|
||||
LDAPGroupSearchFilter: globalLDAPConfig.GroupSearchFilter,
|
||||
}
|
||||
s.OpenID = globalOpenIDConfig.GetSettings()
|
||||
if s.OpenID.Enabled {
|
||||
s.OpenID.Region = globalSite.Region
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *SiteReplicationSys) validateIDPSettings(ctx context.Context, peers []PeerSiteInfo) (bool, error) {
|
||||
@@ -580,13 +587,8 @@ func (c *SiteReplicationSys) validateIDPSettings(ctx context.Context, peers []Pe
|
||||
s = append(s, is)
|
||||
}
|
||||
|
||||
for _, v := range s {
|
||||
if !v.IsLDAPEnabled {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] != s[0] {
|
||||
if !reflect.DeepEqual(s[i], s[0]) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
@@ -984,12 +986,8 @@ func (c *SiteReplicationSys) PeerBucketDeleteHandler(ctx context.Context, bucket
|
||||
// OpenID, such mappings are provided from the IDP directly and so are not
|
||||
// applicable here.
|
||||
//
|
||||
// Only certain service accounts can be replicated:
|
||||
//
|
||||
// Service accounts created for STS credentials using an external IDP: such STS
|
||||
// credentials would be valid on the peer clusters as they are assumed to be
|
||||
// using the same external IDP. Service accounts when using internal IDP or for
|
||||
// root user will not be replicated.
|
||||
// Service accounts are replicated as long as they are not meant for the root
|
||||
// user.
|
||||
//
|
||||
// STS accounts are replicated, but only if the session token is verifiable
|
||||
// using the local cluster's root credential.
|
||||
@@ -1031,6 +1029,44 @@ func (c *SiteReplicationSys) PeerAddPolicyHandler(ctx context.Context, policyNam
|
||||
return nil
|
||||
}
|
||||
|
||||
// PeerIAMUserChangeHandler - copies IAM user to local.
|
||||
func (c *SiteReplicationSys) PeerIAMUserChangeHandler(ctx context.Context, change *madmin.SRIAMUser) error {
|
||||
if change == nil {
|
||||
return errSRInvalidRequest(errInvalidArgument)
|
||||
}
|
||||
var err error
|
||||
if change.IsDeleteReq {
|
||||
err = globalIAMSys.DeleteUser(ctx, change.AccessKey, true)
|
||||
} else {
|
||||
if change.UserReq == nil {
|
||||
return errSRInvalidRequest(errInvalidArgument)
|
||||
}
|
||||
err = globalIAMSys.CreateUser(ctx, change.AccessKey, *change.UserReq)
|
||||
}
|
||||
if err != nil {
|
||||
return wrapSRErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PeerGroupInfoChangeHandler - copies group changes to local.
|
||||
func (c *SiteReplicationSys) PeerGroupInfoChangeHandler(ctx context.Context, change *madmin.SRGroupInfo) error {
|
||||
if change == nil {
|
||||
return errSRInvalidRequest(errInvalidArgument)
|
||||
}
|
||||
updReq := change.UpdateReq
|
||||
var err error
|
||||
if updReq.IsRemove {
|
||||
err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members)
|
||||
} else {
|
||||
err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members)
|
||||
}
|
||||
if err != nil {
|
||||
return wrapSRErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PeerSvcAccChangeHandler - copies service-account change to local.
|
||||
func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change *madmin.SRSvcAccChange) error {
|
||||
if change == nil {
|
||||
@@ -1120,31 +1156,31 @@ func (c *SiteReplicationSys) PeerSTSAccHandler(ctx context.Context, stsCred *mad
|
||||
return fmt.Errorf("Expiry claim was not found: %v", mapClaims)
|
||||
}
|
||||
|
||||
// Extract the username and lookup DN and groups in LDAP.
|
||||
ldapUser, ok := claims.Lookup(ldapUserN)
|
||||
if !ok {
|
||||
return fmt.Errorf("Could not find LDAP username in claims: %v", mapClaims)
|
||||
}
|
||||
|
||||
// Need to lookup the groups from LDAP.
|
||||
ldapUserDN, ldapGroups, err := globalLDAPConfig.LookupUserDN(ldapUser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to query LDAP server for %s: %v", ldapUser, err)
|
||||
}
|
||||
|
||||
cred := auth.Credentials{
|
||||
AccessKey: stsCred.AccessKey,
|
||||
SecretKey: stsCred.SecretKey,
|
||||
Expiration: time.Unix(expiry, 0).UTC(),
|
||||
SessionToken: stsCred.SessionToken,
|
||||
ParentUser: stsCred.ParentUser,
|
||||
Status: auth.AccountOn,
|
||||
ParentUser: ldapUserDN,
|
||||
Groups: ldapGroups,
|
||||
}
|
||||
|
||||
// Extract the username and lookup DN and groups in LDAP.
|
||||
ldapUser, isLDAPSTS := claims.Lookup(ldapUserN)
|
||||
switch {
|
||||
case isLDAPSTS:
|
||||
// Need to lookup the groups from LDAP.
|
||||
_, ldapGroups, err := globalLDAPConfig.LookupUserDN(ldapUser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to query LDAP server for %s: %v", ldapUser, err)
|
||||
}
|
||||
|
||||
cred.Groups = ldapGroups
|
||||
}
|
||||
|
||||
// Set these credentials to IAM.
|
||||
if err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, ""); err != nil {
|
||||
return fmt.Errorf("unable to save STS credential: %v", err)
|
||||
if err := globalIAMSys.SetTempUser(ctx, cred.AccessKey, cred, stsCred.ParentPolicyMapping); err != nil {
|
||||
return fmt.Errorf("unable to save STS credential and/or parent policy mapping: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1497,6 +1533,11 @@ func (c *SiteReplicationSys) syncLocalToPeers(ctx context.Context) error {
|
||||
return errSRBackendIssue(err)
|
||||
}
|
||||
for user, acc := range serviceAccounts {
|
||||
if user == siteReplicatorSvcAcc {
|
||||
// skip the site replicate svc account as it is
|
||||
// already replicated.
|
||||
continue
|
||||
}
|
||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, acc.AccessKey)
|
||||
if err != nil {
|
||||
return errSRBackendIssue(err)
|
||||
|
||||
@@ -278,6 +278,22 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
assumeRoleResponse := &AssumeRoleResponse{
|
||||
Result: AssumeRoleResult{
|
||||
Credentials: cred,
|
||||
@@ -497,6 +513,21 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var encodedSuccessResponse []byte
|
||||
switch action {
|
||||
case clientGrants:
|
||||
@@ -653,13 +684,14 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
|
||||
return
|
||||
}
|
||||
|
||||
// Call hook for cluster-replication.
|
||||
// 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,
|
||||
},
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
@@ -805,12 +837,28 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
|
||||
}
|
||||
|
||||
tmpCredentials.ParentUser = parentUser
|
||||
err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, certificate.Subject.CommonName)
|
||||
policyName := certificate.Subject.CommonName
|
||||
err = globalIAMSys.SetTempUser(ctx, tmpCredentials.AccessKey, tmpCredentials, policyName)
|
||||
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,
|
||||
ParentPolicyMapping: policyName,
|
||||
},
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
response := new(AssumeRoleWithCertificateResponse)
|
||||
response.Result.Credentials = tmpCredentials
|
||||
response.Metadata.RequestID = w.Header().Get(xhttp.AmzRequestID)
|
||||
|
||||
Reference in New Issue
Block a user