fix: remove LDAP groups claim and store them on server (#9637)

Groups information shall be now stored as part of the
credential data structure, this is a more idiomatic
way to support large LDAP groups.

Avoids the complication of setups where LDAP groups
can be in the range of 150+ which may lead to excess
HTTP header size > 8KiB, to reduce such an occurrence
we shall save the group information on the server as
part of the credential data structure.

Bonus change support multiple mapped policies, across
all types of users.
master
Harshavardhana 5 years ago committed by GitHub
parent 6656fa3066
commit 189c861835
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      cmd/iam-etcd-store.go
  2. 2
      cmd/iam-object-store.go
  3. 236
      cmd/iam.go
  4. 12
      cmd/sts-handlers.go
  5. 1
      pkg/auth/credentials.go

@ -197,7 +197,7 @@ func (ies *IAMEtcdStore) migrateUsersConfigToV1(ctx context.Context, isSTS bool)
// then the parsed auth.Credentials will have // then the parsed auth.Credentials will have
// the zero value for the struct. // the zero value for the struct.
var zeroCred auth.Credentials var zeroCred auth.Credentials
if cred == zeroCred { if cred.Equal(zeroCred) {
// nothing to do // nothing to do
continue continue
} }

@ -140,7 +140,7 @@ func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context, isSTS b
// then the parsed auth.Credentials will have // then the parsed auth.Credentials will have
// the zero value for the struct. // the zero value for the struct.
var zeroCred auth.Credentials var zeroCred auth.Credentials
if cred == zeroCred { if cred.Equal(zeroCred) {
// nothing to do // nothing to do
continue continue
} }

@ -162,16 +162,37 @@ func newGroupInfo(members []string) GroupInfo {
// MappedPolicy represents a policy name mapped to a user or group // MappedPolicy represents a policy name mapped to a user or group
type MappedPolicy struct { type MappedPolicy struct {
Version int `json:"version"` Version int `json:"version"`
Policy string `json:"policy"` Policies string `json:"policy"`
}
// converts a mapped policy into a slice of distinct policies
func (mp MappedPolicy) toSlice() []string {
var policies []string
for _, policy := range strings.Split(mp.Policies, ",") {
policy = strings.TrimSpace(policy)
if policy == "" {
continue
}
policies = append(policies, policy)
}
return policies
} }
func (mp MappedPolicy) policySet() set.StringSet { func (mp MappedPolicy) policySet() set.StringSet {
return set.CreateStringSet(strings.Split(mp.Policy, ",")...) var policies []string
for _, policy := range strings.Split(mp.Policies, ",") {
policy = strings.TrimSpace(policy)
if policy == "" {
continue
}
policies = append(policies, policy)
}
return set.CreateStringSet(policies...)
} }
func newMappedPolicy(policy string) MappedPolicy { func newMappedPolicy(policy string) MappedPolicy {
return MappedPolicy{Version: 1, Policy: policy} return MappedPolicy{Version: 1, Policies: policy}
} }
// IAMSys - config system. // IAMSys - config system.
@ -435,38 +456,32 @@ func (sys *IAMSys) DeletePolicy(policyName string) error {
delete(sys.iamPolicyDocsMap, policyName) delete(sys.iamPolicyDocsMap, policyName)
// Delete user-policy mappings that will no longer apply // Delete user-policy mappings that will no longer apply
var usersToDel []string
var usersType []IAMUserType
for u, mp := range sys.iamUserPolicyMap { for u, mp := range sys.iamUserPolicyMap {
if mp.Policy == policyName { pset := mp.policySet()
if pset.Contains(policyName) {
cr, ok := sys.iamUsersMap[u] cr, ok := sys.iamUsersMap[u]
if !ok { if !ok {
// This case cannot happen // This case cannot happen
return errNoSuchUser return errNoSuchUser
} }
pset.Remove(policyName)
// User is from STS if the cred are temporary // User is from STS if the cred are temporary
if cr.IsTemp() { if cr.IsTemp() {
usersType = append(usersType, stsUser) sys.policyDBSet(u, strings.Join(pset.ToSlice(), ","), stsUser, false)
} else { } else {
usersType = append(usersType, regularUser) sys.policyDBSet(u, strings.Join(pset.ToSlice(), ","), regularUser, false)
} }
usersToDel = append(usersToDel, u)
} }
} }
for i, u := range usersToDel {
sys.policyDBSet(u, "", usersType[i], false)
}
// Delete group-policy mappings that will no longer apply // Delete group-policy mappings that will no longer apply
var groupsToDel []string
for g, mp := range sys.iamGroupPolicyMap { for g, mp := range sys.iamGroupPolicyMap {
if mp.Policy == policyName { pset := mp.policySet()
groupsToDel = append(groupsToDel, g) if pset.Contains(policyName) {
pset.Remove(policyName)
sys.policyDBSet(g, strings.Join(pset.ToSlice(), ","), regularUser, true)
} }
} }
for _, g := range groupsToDel {
sys.policyDBSet(g, "", regularUser, true)
}
return err return err
} }
@ -596,14 +611,11 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
// policies for this server. // policies for this server.
if globalPolicyOPA == nil && policyName != "" { if globalPolicyOPA == nil && policyName != "" {
var availablePolicies []iampolicy.Policy var availablePolicies []iampolicy.Policy
for _, pname := range strings.Split(policyName, ",") { mp := newMappedPolicy(policyName)
pname = strings.TrimSpace(pname) for _, policy := range mp.toSlice() {
if pname == "" { p, found := sys.iamPolicyDocsMap[policy]
continue
}
p, found := sys.iamPolicyDocsMap[pname]
if !found { if !found {
return fmt.Errorf("%w: (%s)", errNoSuchPolicy, pname) return fmt.Errorf("%w: (%s)", errNoSuchPolicy, policy)
} }
availablePolicies = append(availablePolicies, p) availablePolicies = append(availablePolicies, p)
} }
@ -619,7 +631,6 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
return nil return nil
} }
mp := newMappedPolicy(policyName)
if err := sys.store.saveMappedPolicy(accessKey, stsUser, false, mp); err != nil { if err := sys.store.saveMappedPolicy(accessKey, stsUser, false, mp); err != nil {
return err return err
} }
@ -655,7 +666,7 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
for k, v := range sys.iamUsersMap { for k, v := range sys.iamUsersMap {
if !v.IsTemp() && !v.IsServiceAccount() { if !v.IsTemp() && !v.IsServiceAccount() {
users[k] = madmin.UserInfo{ users[k] = madmin.UserInfo{
PolicyName: sys.iamUserPolicyMap[k].Policy, PolicyName: sys.iamUserPolicyMap[k].Policies,
Status: func() madmin.AccountStatus { Status: func() madmin.AccountStatus {
if v.IsValid() { if v.IsValid() {
return madmin.AccountEnabled return madmin.AccountEnabled
@ -728,7 +739,7 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
return u, errNoSuchUser return u, errNoSuchUser
} }
return madmin.UserInfo{ return madmin.UserInfo{
PolicyName: mappedPolicy.Policy, PolicyName: mappedPolicy.Policies,
MemberOf: memberships.ToSlice(), MemberOf: memberships.ToSlice(),
}, nil }, nil
} }
@ -743,7 +754,7 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
} }
u = madmin.UserInfo{ u = madmin.UserInfo{
PolicyName: sys.iamUserPolicyMap[name].Policy, PolicyName: sys.iamUserPolicyMap[name].Policies,
Status: func() madmin.AccountStatus { Status: func() madmin.AccountStatus {
if cred.IsValid() { if cred.IsValid() {
return madmin.AccountEnabled return madmin.AccountEnabled
@ -1320,19 +1331,15 @@ func (sys *IAMSys) policyDBSet(name, policyName string, userType IAMUserType, is
return nil return nil
} }
for _, pname := range strings.Split(policyName, ",") { mp := newMappedPolicy(policyName)
pname = strings.TrimSpace(pname) for _, policy := range mp.toSlice() {
if pname == "" { if _, found := sys.iamPolicyDocsMap[policy]; !found {
continue logger.LogIf(GlobalContext, fmt.Errorf("%w: (%s)", errNoSuchPolicy, policy))
}
if _, found := sys.iamPolicyDocsMap[pname]; !found {
logger.LogIf(GlobalContext, fmt.Errorf("%w: (%s)", errNoSuchPolicy, pname))
return errNoSuchPolicy return errNoSuchPolicy
} }
} }
// Handle policy mapping set/update // Handle policy mapping set/update
mp := newMappedPolicy(policyName)
if err := sys.store.saveMappedPolicy(name, userType, isGroup, mp); err != nil { if err := sys.store.saveMappedPolicy(name, userType, isGroup, mp); err != nil {
return err return err
} }
@ -1370,12 +1377,8 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
return nil, errNoSuchGroup return nil, errNoSuchGroup
} }
policy := sys.iamGroupPolicyMap[name] mp := sys.iamGroupPolicyMap[name]
// returned policy could be empty return mp.toSlice(), nil
if policy.Policy == "" {
return nil, nil
}
return []string{policy.Policy}, nil
} }
// When looking for a user's policies, we also check if the // When looking for a user's policies, we also check if the
@ -1388,12 +1391,12 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
return nil, nil return nil, nil
} }
result := []string{} var policies []string
policy := sys.iamUserPolicyMap[name]
mp := sys.iamUserPolicyMap[name]
// returned policy could be empty // returned policy could be empty
if policy.Policy != "" { policies = append(policies, mp.toSlice()...)
result = append(result, policy.Policy)
}
for _, group := range sys.iamUserGroupMemberships[name].ToSlice() { for _, group := range sys.iamUserGroupMemberships[name].ToSlice() {
// Skip missing or disabled groups // Skip missing or disabled groups
gi, ok := sys.iamGroupsMap[group] gi, ok := sys.iamGroupsMap[group]
@ -1401,12 +1404,10 @@ func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
continue continue
} }
p, ok := sys.iamGroupPolicyMap[group] p := sys.iamGroupPolicyMap[group]
if ok && p.Policy != "" { policies = append(policies, p.toSlice()...)
result = append(result, p.Policy)
}
} }
return result, nil return policies, nil
} }
// IsAllowedServiceAccount - checks if the given service account is allowed to perform // IsAllowedServiceAccount - checks if the given service account is allowed to perform
@ -1512,81 +1513,74 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parent string) b
return combinedPolicy.IsAllowed(parentArgs) && subPolicy.IsAllowed(parentArgs) return combinedPolicy.IsAllowed(parentArgs) && subPolicy.IsAllowed(parentArgs)
} }
// IsAllowedSTS is meant for STS based temporary credentials, // IsAllowedLDAPSTS - checks for LDAP specific claims and values
// which implements claims validation and verification other than func (sys *IAMSys) IsAllowedLDAPSTS(args iampolicy.Args) bool {
// applying policies. userIface, ok := args.Claims[ldapUser]
func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool { if !ok {
// If it is an LDAP request, check that user and group return false
// policies allow the request. }
if sys.usersSysType == LDAPUsersSysType { user, ok := userIface.(string)
if userIface, ok := args.Claims[ldapUser]; ok { if !ok {
var user string return false
if u, ok := userIface.(string); ok { }
user = u
} else {
return false
}
var groups []string sys.store.rlock()
groupsVal := args.Claims[ldapGroups] defer sys.store.runlock()
if g, ok := groupsVal.([]interface{}); ok {
for _, eachG := range g {
if eachGStr, ok := eachG.(string); ok {
groups = append(groups, eachGStr)
}
}
}
sys.store.rlock() var groups []string
defer sys.store.runlock() cred, ok := sys.iamUsersMap[args.AccountName]
if !ok {
// We look up the policy mapping directly to bypass return false
// users exists, group exists validations that do not }
// apply here. groups = cred.Groups
var policies []iampolicy.Policy
if mp, ok := sys.iamUserPolicyMap[user]; ok { // We look up the policy mapping directly to bypass
for _, pname := range strings.Split(mp.Policy, ",") { // users exists, group exists validations that do not
pname = strings.TrimSpace(pname) // apply here.
if pname == "" { var policies []iampolicy.Policy
continue if mp, ok := sys.iamUserPolicyMap[user]; ok {
} for _, pname := range mp.toSlice() {
p, found := sys.iamPolicyDocsMap[pname] p, found := sys.iamPolicyDocsMap[pname]
if !found { if !found {
return false
}
policies = append(policies, p)
}
}
for _, group := range groups {
mp, ok := sys.iamGroupPolicyMap[group]
if !ok {
continue
}
for _, pname := range strings.Split(mp.Policy, ",") {
pname = strings.TrimSpace(pname)
if pname == "" {
continue
}
p, found := sys.iamPolicyDocsMap[pname]
if !found {
return false
}
policies = append(policies, p)
}
}
if len(policies) == 0 {
return false return false
} }
combinedPolicy := policies[0] policies = append(policies, p)
for i := 1; i < len(policies); i++ { }
combinedPolicy.Statements = }
append(combinedPolicy.Statements, for _, group := range groups {
policies[i].Statements...) mp, ok := sys.iamGroupPolicyMap[group]
if !ok {
continue
}
for _, pname := range mp.toSlice() {
p, found := sys.iamPolicyDocsMap[pname]
if !found {
return false
} }
return combinedPolicy.IsAllowed(args) policies = append(policies, p)
} }
}
if len(policies) == 0 {
return false return false
} }
combinedPolicy := policies[0]
for i := 1; i < len(policies); i++ {
combinedPolicy.Statements =
append(combinedPolicy.Statements,
policies[i].Statements...)
}
return combinedPolicy.IsAllowed(args)
}
// IsAllowedSTS is meant for STS based temporary credentials,
// which implements claims validation and verification other than
// applying policies.
func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args) bool {
// If it is an LDAP request, check that user and group
// policies allow the request.
if sys.usersSysType == LDAPUsersSysType {
return sys.IsAllowedLDAPSTS(args)
}
policies, ok := args.GetPolicies(iamPolicyClaimNameOpenID()) policies, ok := args.GetPolicies(iamPolicyClaimNameOpenID())
if !ok { if !ok {

@ -61,8 +61,7 @@ const (
parentClaim = "parent" parentClaim = "parent"
// LDAP claim keys // LDAP claim keys
ldapUser = "ldapUser" ldapUser = "ldapUser"
ldapGroups = "ldapGroups"
) )
// stsAPIHandlers implements and provides http handlers for AWS STS API. // stsAPIHandlers implements and provides http handlers for AWS STS API.
@ -491,9 +490,8 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
expiryDur := globalLDAPConfig.GetExpiryDuration() expiryDur := globalLDAPConfig.GetExpiryDuration()
m := map[string]interface{}{ m := map[string]interface{}{
expClaim: UTCNow().Add(expiryDur).Unix(), expClaim: UTCNow().Add(expiryDur).Unix(),
ldapUser: ldapUsername, ldapUser: ldapUsername,
ldapGroups: groups,
} }
if len(sessionPolicyStr) > 0 { if len(sessionPolicyStr) > 0 {
@ -511,6 +509,10 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
// in obtaining service accounts by this cred. // in obtaining service accounts by this cred.
cred.ParentUser = ldapUsername cred.ParentUser = ldapUsername
// Set this value to LDAP groups, LDAP user can be part
// of large number of groups
cred.Groups = groups
// Set the newly generated credentials, policyName is empty on purpose // Set the newly generated credentials, policyName is empty on purpose
// LDAP policies are applied automatically using their ldapUser, ldapGroups // LDAP policies are applied automatically using their ldapUser, ldapGroups
// mapping. // mapping.

@ -91,6 +91,7 @@ type Credentials struct {
SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"` SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
Status string `xml:"-" json:"status,omitempty"` Status string `xml:"-" json:"status,omitempty"`
ParentUser string `xml:"-" json:"parentUser,omitempty"` ParentUser string `xml:"-" json:"parentUser,omitempty"`
Groups []string `xml:"-" json:"groups,omitempty"`
} }
func (cred Credentials) String() string { func (cred Credentials) String() string {

Loading…
Cancel
Save