2021-11-03 22:47:49 -04:00
|
|
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
|
|
//
|
|
|
|
// This file is part of MinIO Object Storage stack
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-11-07 17:35:09 -05:00
|
|
|
"sort"
|
2021-11-03 22:47:49 -04:00
|
|
|
"strings"
|
2021-12-11 12:03:39 -05:00
|
|
|
"time"
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2021-12-11 12:03:39 -05:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2022-12-06 16:46:50 -05:00
|
|
|
"github.com/minio/madmin-go/v2"
|
2021-11-03 22:47:49 -04:00
|
|
|
"github.com/minio/minio-go/v7/pkg/set"
|
|
|
|
"github.com/minio/minio/internal/auth"
|
2022-04-28 21:27:09 -04:00
|
|
|
"github.com/minio/minio/internal/config/identity/openid"
|
2023-02-27 13:10:22 -05:00
|
|
|
"github.com/minio/minio/internal/jwt"
|
2021-11-03 22:47:49 -04:00
|
|
|
"github.com/minio/minio/internal/logger"
|
|
|
|
iampolicy "github.com/minio/pkg/iam/policy"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// IAM configuration directory.
|
|
|
|
iamConfigPrefix = minioConfigPrefix + "/iam"
|
|
|
|
|
|
|
|
// IAM users directory.
|
|
|
|
iamConfigUsersPrefix = iamConfigPrefix + "/users/"
|
|
|
|
|
|
|
|
// IAM service accounts directory.
|
|
|
|
iamConfigServiceAccountsPrefix = iamConfigPrefix + "/service-accounts/"
|
|
|
|
|
|
|
|
// IAM groups directory.
|
|
|
|
iamConfigGroupsPrefix = iamConfigPrefix + "/groups/"
|
|
|
|
|
|
|
|
// IAM policies directory.
|
|
|
|
iamConfigPoliciesPrefix = iamConfigPrefix + "/policies/"
|
|
|
|
|
|
|
|
// IAM sts directory.
|
|
|
|
iamConfigSTSPrefix = iamConfigPrefix + "/sts/"
|
|
|
|
|
|
|
|
// IAM Policy DB prefixes.
|
|
|
|
iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/"
|
|
|
|
iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/"
|
|
|
|
iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/"
|
|
|
|
iamConfigPolicyDBServiceAccountsPrefix = iamConfigPolicyDBPrefix + "service-accounts/"
|
|
|
|
iamConfigPolicyDBGroupsPrefix = iamConfigPolicyDBPrefix + "groups/"
|
|
|
|
|
|
|
|
// IAM identity file which captures identity credentials.
|
|
|
|
iamIdentityFile = "identity.json"
|
|
|
|
|
|
|
|
// IAM policy file which provides policies for each users.
|
|
|
|
iamPolicyFile = "policy.json"
|
|
|
|
|
|
|
|
// IAM group members file
|
|
|
|
iamGroupMembersFile = "members.json"
|
|
|
|
|
|
|
|
// IAM format file
|
|
|
|
iamFormatFile = "format.json"
|
|
|
|
|
|
|
|
iamFormatVersion1 = 1
|
2023-02-27 13:10:22 -05:00
|
|
|
|
|
|
|
minServiceAccountExpiry time.Duration = 15 * time.Minute
|
|
|
|
maxServiceAccountExpiry time.Duration = 365 * 24 * time.Hour
|
2021-11-03 22:47:49 -04:00
|
|
|
)
|
|
|
|
|
2023-02-27 13:10:22 -05:00
|
|
|
var errInvalidSvcAcctExpiration = errors.New("invalid service account expiration")
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
type iamFormat struct {
|
|
|
|
Version int `json:"version"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newIAMFormatVersion1() iamFormat {
|
|
|
|
return iamFormat{Version: iamFormatVersion1}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getIAMFormatFilePath() string {
|
|
|
|
return iamConfigPrefix + SlashSeparator + iamFormatFile
|
|
|
|
}
|
|
|
|
|
|
|
|
func getUserIdentityPath(user string, userType IAMUserType) string {
|
|
|
|
var basePath string
|
|
|
|
switch userType {
|
|
|
|
case svcUser:
|
|
|
|
basePath = iamConfigServiceAccountsPrefix
|
|
|
|
case stsUser:
|
|
|
|
basePath = iamConfigSTSPrefix
|
|
|
|
default:
|
|
|
|
basePath = iamConfigUsersPrefix
|
|
|
|
}
|
|
|
|
return pathJoin(basePath, user, iamIdentityFile)
|
|
|
|
}
|
|
|
|
|
2022-08-05 20:53:23 -04:00
|
|
|
func saveIAMFormat(ctx context.Context, store IAMStorageAPI) error {
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("Load IAM format file")
|
2022-08-05 20:53:23 -04:00
|
|
|
var iamFmt iamFormat
|
|
|
|
path := getIAMFormatFilePath()
|
|
|
|
if err := store.loadIAMConfig(ctx, &iamFmt, path); err != nil {
|
|
|
|
switch err {
|
|
|
|
case errConfigNotFound:
|
|
|
|
// Need to migrate to V1.
|
|
|
|
default:
|
|
|
|
// if IAM format
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if iamFmt.Version >= iamFormatVersion1 {
|
|
|
|
// Nothing to do.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("Write IAM format file")
|
2022-08-05 20:53:23 -04:00
|
|
|
// Save iam format to version 1.
|
|
|
|
if err := store.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
func getGroupInfoPath(group string) string {
|
|
|
|
return pathJoin(iamConfigGroupsPrefix, group, iamGroupMembersFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPolicyDocPath(name string) string {
|
|
|
|
return pathJoin(iamConfigPoliciesPrefix, name, iamPolicyFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMappedPolicyPath(name string, userType IAMUserType, isGroup bool) string {
|
|
|
|
if isGroup {
|
|
|
|
return pathJoin(iamConfigPolicyDBGroupsPrefix, name+".json")
|
|
|
|
}
|
|
|
|
switch userType {
|
|
|
|
case svcUser:
|
|
|
|
return pathJoin(iamConfigPolicyDBServiceAccountsPrefix, name+".json")
|
|
|
|
case stsUser:
|
|
|
|
return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json")
|
|
|
|
default:
|
|
|
|
return pathJoin(iamConfigPolicyDBUsersPrefix, name+".json")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// UserIdentity represents a user's secret key and their status
|
|
|
|
type UserIdentity struct {
|
|
|
|
Version int `json:"version"`
|
|
|
|
Credentials auth.Credentials `json:"credentials"`
|
2022-04-24 05:36:31 -04:00
|
|
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func newUserIdentity(cred auth.Credentials) UserIdentity {
|
2022-04-24 05:36:31 -04:00
|
|
|
return UserIdentity{Version: 1, Credentials: cred, UpdatedAt: UTCNow()}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GroupInfo contains info about a group
|
|
|
|
type GroupInfo struct {
|
2022-04-24 05:36:31 -04:00
|
|
|
Version int `json:"version"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
Members []string `json:"members"`
|
|
|
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func newGroupInfo(members []string) GroupInfo {
|
2022-04-24 05:36:31 -04:00
|
|
|
return GroupInfo{Version: 1, Status: statusEnabled, Members: members, UpdatedAt: UTCNow()}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// MappedPolicy represents a policy name mapped to a user or group
|
|
|
|
type MappedPolicy struct {
|
2022-04-24 05:36:31 -04:00
|
|
|
Version int `json:"version"`
|
|
|
|
Policies string `json:"policy"`
|
|
|
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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, ",") {
|
2022-05-02 12:27:35 -04:00
|
|
|
if strings.TrimSpace(policy) == "" {
|
2021-11-03 22:47:49 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
policies = append(policies, policy)
|
|
|
|
}
|
|
|
|
return policies
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mp MappedPolicy) policySet() set.StringSet {
|
|
|
|
return set.CreateStringSet(mp.toSlice()...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newMappedPolicy(policy string) MappedPolicy {
|
2022-04-24 05:36:31 -04:00
|
|
|
return MappedPolicy{Version: 1, Policies: policy, UpdatedAt: UTCNow()}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2021-12-11 12:03:39 -05:00
|
|
|
// PolicyDoc represents an IAM policy with some metadata.
|
|
|
|
type PolicyDoc struct {
|
|
|
|
Version int `json:",omitempty"`
|
|
|
|
Policy iampolicy.Policy
|
|
|
|
CreateDate time.Time `json:",omitempty"`
|
|
|
|
UpdateDate time.Time `json:",omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newPolicyDoc(p iampolicy.Policy) PolicyDoc {
|
|
|
|
now := UTCNow().Round(time.Millisecond)
|
|
|
|
return PolicyDoc{
|
|
|
|
Version: 1,
|
|
|
|
Policy: p,
|
|
|
|
CreateDate: now,
|
|
|
|
UpdateDate: now,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultPolicyDoc - used to wrap a default policy as PolicyDoc.
|
|
|
|
func defaultPolicyDoc(p iampolicy.Policy) PolicyDoc {
|
|
|
|
return PolicyDoc{
|
|
|
|
Version: 1,
|
|
|
|
Policy: p,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *PolicyDoc) update(p iampolicy.Policy) {
|
|
|
|
now := UTCNow().Round(time.Millisecond)
|
|
|
|
d.UpdateDate = now
|
|
|
|
if d.CreateDate.IsZero() {
|
|
|
|
d.CreateDate = now
|
|
|
|
}
|
|
|
|
d.Policy = p
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseJSON parses both the old and the new format for storing policy
|
|
|
|
// definitions.
|
|
|
|
//
|
|
|
|
// The on-disk format of policy definitions has changed (around early 12/2021)
|
|
|
|
// from iampolicy.Policy to PolicyDoc. To avoid a migration, loading supports
|
|
|
|
// both the old and the new formats.
|
|
|
|
func (d *PolicyDoc) parseJSON(data []byte) error {
|
2022-01-02 12:15:06 -05:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2021-12-11 12:03:39 -05:00
|
|
|
var doc PolicyDoc
|
|
|
|
err := json.Unmarshal(data, &doc)
|
|
|
|
if err != nil {
|
|
|
|
err2 := json.Unmarshal(data, &doc.Policy)
|
|
|
|
if err2 != nil {
|
|
|
|
// Just return the first error.
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Policy = doc.Policy
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
*d = doc
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// key options
|
|
|
|
type options struct {
|
2021-11-16 12:28:29 -05:00
|
|
|
ttl int64 // expiry in seconds
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type iamWatchEvent struct {
|
|
|
|
isCreated bool // !isCreated implies a delete event.
|
|
|
|
keyPath string
|
|
|
|
}
|
|
|
|
|
|
|
|
// iamCache contains in-memory cache of IAM data.
|
|
|
|
type iamCache struct {
|
2022-05-17 22:58:47 -04:00
|
|
|
updatedAt time.Time
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// map of policy names to policy definitions
|
2021-12-11 12:03:39 -05:00
|
|
|
iamPolicyDocsMap map[string]PolicyDoc
|
2021-11-03 22:47:49 -04:00
|
|
|
// map of usernames to credentials
|
2022-07-01 16:19:13 -04:00
|
|
|
iamUsersMap map[string]UserIdentity
|
2021-11-03 22:47:49 -04:00
|
|
|
// map of group names to group info
|
|
|
|
iamGroupsMap map[string]GroupInfo
|
|
|
|
// map of user names to groups they are a member of
|
|
|
|
iamUserGroupMemberships map[string]set.StringSet
|
|
|
|
// map of usernames/temporary access keys to policy names
|
|
|
|
iamUserPolicyMap map[string]MappedPolicy
|
|
|
|
// map of group names to policy names
|
|
|
|
iamGroupPolicyMap map[string]MappedPolicy
|
|
|
|
}
|
|
|
|
|
|
|
|
func newIamCache() *iamCache {
|
|
|
|
return &iamCache{
|
2021-12-11 12:03:39 -05:00
|
|
|
iamPolicyDocsMap: map[string]PolicyDoc{},
|
2022-07-01 16:19:13 -04:00
|
|
|
iamUsersMap: map[string]UserIdentity{},
|
2021-11-03 22:47:49 -04:00
|
|
|
iamGroupsMap: map[string]GroupInfo{},
|
|
|
|
iamUserGroupMemberships: map[string]set.StringSet{},
|
|
|
|
iamUserPolicyMap: map[string]MappedPolicy{},
|
|
|
|
iamGroupPolicyMap: map[string]MappedPolicy{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildUserGroupMemberships - builds the memberships map. IMPORTANT:
|
|
|
|
// Assumes that c.Lock is held by caller.
|
|
|
|
func (c *iamCache) buildUserGroupMemberships() {
|
|
|
|
for group, gi := range c.iamGroupsMap {
|
|
|
|
c.updateGroupMembershipsMap(group, &gi)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// updateGroupMembershipsMap - updates the memberships map for a
|
|
|
|
// group. IMPORTANT: Assumes c.Lock() is held by caller.
|
|
|
|
func (c *iamCache) updateGroupMembershipsMap(group string, gi *GroupInfo) {
|
|
|
|
if gi == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, member := range gi.Members {
|
|
|
|
v := c.iamUserGroupMemberships[member]
|
|
|
|
if v == nil {
|
|
|
|
v = set.CreateStringSet(group)
|
|
|
|
} else {
|
|
|
|
v.Add(group)
|
|
|
|
}
|
|
|
|
c.iamUserGroupMemberships[member] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// removeGroupFromMembershipsMap - removes the group from every member
|
|
|
|
// in the cache. IMPORTANT: Assumes c.Lock() is held by caller.
|
|
|
|
func (c *iamCache) removeGroupFromMembershipsMap(group string) {
|
|
|
|
for member, groups := range c.iamUserGroupMemberships {
|
|
|
|
if !groups.Contains(group) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
groups.Remove(group)
|
|
|
|
c.iamUserGroupMemberships[member] = groups
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// policyDBGet - lower-level helper; does not take locks.
|
|
|
|
//
|
|
|
|
// If a group is passed, it returns policies associated with the group.
|
|
|
|
//
|
|
|
|
// If a user is passed, it returns policies of the user along with any groups
|
|
|
|
// that the server knows the user is a member of.
|
|
|
|
//
|
|
|
|
// In LDAP users mode, the server does not store any group membership
|
|
|
|
// information in IAM (i.e sys.iam*Map) - this info is stored only in the STS
|
|
|
|
// generated credentials. Thus we skip looking up group memberships, user map,
|
|
|
|
// and group map and check the appropriate policy maps directly.
|
2022-04-24 05:36:31 -04:00
|
|
|
func (c *iamCache) policyDBGet(mode UsersSysType, name string, isGroup bool) ([]string, time.Time, error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
if isGroup {
|
|
|
|
if mode == MinIOUsersSysType {
|
|
|
|
g, ok := c.iamGroupsMap[name]
|
|
|
|
if !ok {
|
2022-04-24 05:36:31 -04:00
|
|
|
return nil, time.Time{}, errNoSuchGroup
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Group is disabled, so we return no policy - this
|
|
|
|
// ensures the request is denied.
|
|
|
|
if g.Status == statusDisabled {
|
2022-04-24 05:36:31 -04:00
|
|
|
return nil, time.Time{}, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-24 05:36:31 -04:00
|
|
|
return c.iamGroupPolicyMap[name].toSlice(), c.iamGroupPolicyMap[name].UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// When looking for a user's policies, we also check if the user
|
|
|
|
// and the groups they are member of are enabled.
|
|
|
|
u, ok := c.iamUsersMap[name]
|
|
|
|
if ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
if !u.Credentials.IsValid() {
|
2022-04-24 05:36:31 -04:00
|
|
|
return nil, time.Time{}, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-26 22:06:55 -04:00
|
|
|
mp := c.iamUserPolicyMap[name]
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
// returned policy could be empty
|
|
|
|
policies := mp.toSlice()
|
|
|
|
|
|
|
|
for _, group := range c.iamUserGroupMemberships[name].ToSlice() {
|
|
|
|
// Skip missing or disabled groups
|
|
|
|
gi, ok := c.iamGroupsMap[group]
|
|
|
|
if !ok || gi.Status == statusDisabled {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
policies = append(policies, c.iamGroupPolicyMap[group].toSlice()...)
|
|
|
|
}
|
|
|
|
|
2022-04-24 05:36:31 -04:00
|
|
|
return policies, mp.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2023-02-27 13:10:22 -05:00
|
|
|
func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error {
|
|
|
|
if u.Credentials.SessionToken != "" {
|
|
|
|
jwtClaims, err := extractJWTClaims(u)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
u.Credentials.Claims = jwtClaims.Map()
|
|
|
|
}
|
|
|
|
c.iamUsersMap[key] = u
|
|
|
|
c.updatedAt = time.Now()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// IAMStorageAPI defines an interface for the IAM persistence layer
|
|
|
|
type IAMStorageAPI interface {
|
|
|
|
// The role of the read-write lock is to prevent go routines from
|
|
|
|
// concurrently reading and writing the IAM storage. The (r)lock()
|
|
|
|
// functions return the iamCache. The cache can be safely written to
|
|
|
|
// only when returned by `lock()`.
|
|
|
|
lock() *iamCache
|
|
|
|
unlock()
|
|
|
|
rlock() *iamCache
|
|
|
|
runlock()
|
|
|
|
getUsersSysType() UsersSysType
|
2021-12-11 12:03:39 -05:00
|
|
|
loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error
|
|
|
|
loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error
|
2022-07-01 16:19:13 -04:00
|
|
|
loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error
|
|
|
|
loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error
|
2021-11-03 22:47:49 -04:00
|
|
|
loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error
|
|
|
|
loadGroups(ctx context.Context, m map[string]GroupInfo) error
|
|
|
|
loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
|
|
|
|
loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
|
|
|
|
saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error
|
|
|
|
loadIAMConfig(ctx context.Context, item interface{}, path string) error
|
|
|
|
deleteIAMConfig(ctx context.Context, path string) error
|
2021-12-11 12:03:39 -05:00
|
|
|
savePolicyDoc(ctx context.Context, policyName string, p PolicyDoc) error
|
2021-11-03 22:47:49 -04:00
|
|
|
saveMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, mp MappedPolicy, opts ...options) error
|
|
|
|
saveUserIdentity(ctx context.Context, name string, userType IAMUserType, u UserIdentity, opts ...options) error
|
|
|
|
saveGroupInfo(ctx context.Context, group string, gi GroupInfo) error
|
|
|
|
deletePolicyDoc(ctx context.Context, policyName string) error
|
|
|
|
deleteMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool) error
|
|
|
|
deleteUserIdentity(ctx context.Context, name string, userType IAMUserType) error
|
|
|
|
deleteGroupInfo(ctx context.Context, name string) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// iamStorageWatcher is implemented by `IAMStorageAPI` implementers that
|
|
|
|
// additionally support watching storage for changes.
|
|
|
|
type iamStorageWatcher interface {
|
|
|
|
watch(ctx context.Context, keyPath string) <-chan iamWatchEvent
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set default canned policies only if not already overridden by users.
|
2021-12-11 12:03:39 -05:00
|
|
|
func setDefaultCannedPolicies(policies map[string]PolicyDoc) {
|
2021-11-03 22:47:49 -04:00
|
|
|
for _, v := range iampolicy.DefaultPolicies {
|
|
|
|
if _, ok := policies[v.Name]; !ok {
|
2021-12-11 12:03:39 -05:00
|
|
|
policies[v.Name] = defaultPolicyDoc(v.Definition)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadIAMCache reads all IAM items and populates a new iamCache object and
|
|
|
|
// replaces the in-memory cache object.
|
|
|
|
func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading IAM data")
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
newCache := newIamCache()
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
loadedAt := time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-03-27 21:48:01 -04:00
|
|
|
if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok {
|
|
|
|
err := iamOS.loadAllFromObjStore(ctx, newCache)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading policy documents")
|
2022-03-27 21:48:01 -04:00
|
|
|
if err := store.loadPolicyDocs(ctx, newCache.iamPolicyDocsMap); err != nil {
|
2021-11-03 22:47:49 -04:00
|
|
|
return err
|
|
|
|
}
|
2022-03-27 21:48:01 -04:00
|
|
|
|
|
|
|
// Sets default canned policies, if none are set.
|
|
|
|
setDefaultCannedPolicies(newCache.iamPolicyDocsMap)
|
|
|
|
|
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading regular users")
|
2022-03-27 21:48:01 -04:00
|
|
|
if err := store.loadUsers(ctx, regUser, newCache.iamUsersMap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading regular groups")
|
2022-03-27 21:48:01 -04:00
|
|
|
if err := store.loadGroups(ctx, newCache.iamGroupsMap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading user policy mapping")
|
2022-03-27 21:48:01 -04:00
|
|
|
// load polices mapped to users
|
|
|
|
if err := store.loadMappedPolicies(ctx, regUser, false, newCache.iamUserPolicyMap); err != nil {
|
2021-11-03 22:47:49 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading group policy mapping")
|
2022-03-27 21:48:01 -04:00
|
|
|
// load policies mapped to groups
|
|
|
|
if err := store.loadMappedPolicies(ctx, regUser, true, newCache.iamGroupPolicyMap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading service accounts")
|
2022-03-27 21:48:01 -04:00
|
|
|
// load service accounts
|
|
|
|
if err := store.loadUsers(ctx, svcUser, newCache.iamUsersMap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading STS users")
|
2022-03-27 21:48:01 -04:00
|
|
|
// load STS temp users
|
|
|
|
if err := store.loadUsers(ctx, stsUser, newCache.iamUsersMap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-12-21 18:52:29 -05:00
|
|
|
bootstrapTrace("loading STS policy mapping")
|
2022-03-27 21:48:01 -04:00
|
|
|
// load STS policy mappings
|
|
|
|
if err := store.loadMappedPolicies(ctx, stsUser, false, newCache.iamUserPolicyMap); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-03-27 21:48:01 -04:00
|
|
|
newCache.buildUserGroupMemberships()
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
// We should only update the in-memory cache if there were no changes
|
|
|
|
// to the in-memory cache since the disk loading began. If there
|
|
|
|
// were changes to the in-memory cache we should wait for the next
|
|
|
|
// cycle until we can safely update the in-memory cache.
|
|
|
|
//
|
|
|
|
// An in-memory cache must be replaced only if we know for sure that
|
|
|
|
// the values loaded from disk are not stale. They might be stale
|
|
|
|
// if the cached.updatedAt is recent than the refresh cycle began.
|
|
|
|
if cache.updatedAt.Before(loadedAt) {
|
|
|
|
// No one has updated anything since the config was loaded,
|
|
|
|
// so we just replace whatever is on the disk into memory.
|
|
|
|
cache.iamGroupPolicyMap = newCache.iamGroupPolicyMap
|
|
|
|
cache.iamGroupsMap = newCache.iamGroupsMap
|
|
|
|
cache.iamPolicyDocsMap = newCache.iamPolicyDocsMap
|
|
|
|
cache.iamUserGroupMemberships = newCache.iamUserGroupMemberships
|
|
|
|
cache.iamUserPolicyMap = newCache.iamUserPolicyMap
|
|
|
|
cache.iamUsersMap = newCache.iamUsersMap
|
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IAMStoreSys contains IAMStorageAPI to add higher-level methods on the storage
|
|
|
|
// layer.
|
|
|
|
type IAMStoreSys struct {
|
|
|
|
IAMStorageAPI
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasWatcher - returns if the storage system has a watcher.
|
|
|
|
func (store *IAMStoreSys) HasWatcher() bool {
|
|
|
|
_, ok := store.IAMStorageAPI.(iamStorageWatcher)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUser - fetches credential from memory.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) GetUser(user string) (UserIdentity, bool) {
|
2021-11-03 22:47:49 -04:00
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
u, ok := cache.iamUsersMap[user]
|
|
|
|
return u, ok
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetMappedPolicy - fetches mapped policy from memory.
|
|
|
|
func (store *IAMStoreSys) GetMappedPolicy(name string, isGroup bool) (MappedPolicy, bool) {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
if isGroup {
|
|
|
|
v, ok := cache.iamGroupPolicyMap[name]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
v, ok := cache.iamUserPolicyMap[name]
|
|
|
|
return v, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// GroupNotificationHandler - updates in-memory cache on notification of
|
|
|
|
// change (e.g. peer notification for object storage and etcd watch
|
|
|
|
// notification).
|
|
|
|
func (store *IAMStoreSys) GroupNotificationHandler(ctx context.Context, group string) error {
|
2021-11-07 20:42:32 -05:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
err := store.loadGroup(ctx, group, cache.iamGroupsMap)
|
|
|
|
if err != nil && err != errNoSuchGroup {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == errNoSuchGroup {
|
|
|
|
// group does not exist - so remove from memory.
|
|
|
|
cache.removeGroupFromMembershipsMap(group)
|
|
|
|
delete(cache.iamGroupsMap, group)
|
|
|
|
delete(cache.iamGroupPolicyMap, group)
|
2022-05-17 22:58:47 -04:00
|
|
|
|
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
gi := cache.iamGroupsMap[group]
|
|
|
|
|
|
|
|
// Updating the group memberships cache happens in two steps:
|
|
|
|
//
|
|
|
|
// 1. Remove the group from each user's list of memberships.
|
|
|
|
// 2. Add the group to each member's list of memberships.
|
|
|
|
//
|
|
|
|
// This ensures that regardless of members being added or
|
|
|
|
// removed, the cache stays current.
|
|
|
|
cache.removeGroupFromMembershipsMap(group)
|
|
|
|
cache.updateGroupMembershipsMap(group, &gi)
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PolicyDBGet - fetches policies associated with the given user or group, and
|
|
|
|
// additional groups if provided.
|
|
|
|
func (store *IAMStoreSys) PolicyDBGet(name string, isGroup bool, groups ...string) ([]string, error) {
|
|
|
|
if name == "" {
|
|
|
|
return nil, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
2022-04-24 05:36:31 -04:00
|
|
|
policies, _, err := cache.policyDBGet(store.getUsersSysType(), name, isGroup)
|
2021-11-03 22:47:49 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isGroup {
|
|
|
|
for _, group := range groups {
|
2022-04-24 05:36:31 -04:00
|
|
|
ps, _, err := cache.policyDBGet(store.getUsersSysType(), group, true)
|
2021-11-03 22:47:49 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
policies = append(policies, ps...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return policies, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddUsersToGroup - adds users to group, creating the group if needed.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
if group == "" {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errInvalidArgument
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
// Validate that all members exist.
|
|
|
|
for _, member := range members {
|
2022-07-01 16:19:13 -04:00
|
|
|
u, ok := cache.iamUsersMap[member]
|
2021-11-03 22:47:49 -04:00
|
|
|
if !ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errNoSuchUser
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
cr := u.Credentials
|
2021-11-03 22:47:49 -04:00
|
|
|
if cr.IsTemp() || cr.IsServiceAccount() {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errIAMActionNotAllowed
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gi, ok := cache.iamGroupsMap[group]
|
|
|
|
if !ok {
|
|
|
|
// Set group as enabled by default when it doesn't
|
|
|
|
// exist.
|
|
|
|
gi = newGroupInfo(members)
|
|
|
|
} else {
|
2021-11-16 12:28:29 -05:00
|
|
|
gi.Members = set.CreateStringSet(append(gi.Members, members...)...).ToSlice()
|
2022-07-01 16:19:13 -04:00
|
|
|
gi.UpdatedAt = UTCNow()
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := store.saveGroupInfo(ctx, group, gi); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cache.iamGroupsMap[group] = gi
|
|
|
|
|
|
|
|
// update user-group membership map
|
|
|
|
for _, member := range members {
|
|
|
|
gset := cache.iamUserGroupMemberships[member]
|
|
|
|
if gset == nil {
|
|
|
|
gset = set.CreateStringSet(group)
|
|
|
|
} else {
|
|
|
|
gset.Add(group)
|
|
|
|
}
|
|
|
|
cache.iamUserGroupMemberships[member] = gset
|
|
|
|
}
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2022-07-01 16:19:13 -04:00
|
|
|
return gi.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// helper function - does not take any locks. Updates only cache if
|
|
|
|
// updateCacheOnly is set.
|
2022-07-01 16:19:13 -04:00
|
|
|
func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamCache, group string, members []string, updateCacheOnly bool) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
gi, ok := cache.iamGroupsMap[group]
|
|
|
|
if !ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errNoSuchGroup
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
s := set.CreateStringSet(gi.Members...)
|
|
|
|
d := set.CreateStringSet(members...)
|
|
|
|
gi.Members = s.Difference(d).ToSlice()
|
|
|
|
|
|
|
|
if !updateCacheOnly {
|
|
|
|
err := store.saveGroupInfo(ctx, group, gi)
|
|
|
|
if err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
gi.UpdatedAt = UTCNow()
|
2021-11-03 22:47:49 -04:00
|
|
|
cache.iamGroupsMap[group] = gi
|
|
|
|
|
|
|
|
// update user-group membership map
|
|
|
|
for _, member := range members {
|
|
|
|
gset := cache.iamUserGroupMemberships[member]
|
|
|
|
if gset == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
gset.Remove(group)
|
|
|
|
cache.iamUserGroupMemberships[member] = gset
|
|
|
|
}
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2022-07-01 16:19:13 -04:00
|
|
|
return gi.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveUsersFromGroup - removes users from group, deleting it if it is empty.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
if group == "" {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errInvalidArgument
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
// Validate that all members exist.
|
|
|
|
for _, member := range members {
|
2022-07-01 16:19:13 -04:00
|
|
|
u, ok := cache.iamUsersMap[member]
|
2021-11-03 22:47:49 -04:00
|
|
|
if !ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errNoSuchUser
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
cr := u.Credentials
|
2021-11-03 22:47:49 -04:00
|
|
|
if cr.IsTemp() || cr.IsServiceAccount() {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errIAMActionNotAllowed
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gi, ok := cache.iamGroupsMap[group]
|
|
|
|
if !ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errNoSuchGroup
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if attempting to delete a non-empty group.
|
|
|
|
if len(members) == 0 && len(gi.Members) != 0 {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errGroupNotEmpty
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(members) == 0 {
|
|
|
|
// len(gi.Members) == 0 here.
|
|
|
|
|
|
|
|
// Remove the group from storage. First delete the
|
|
|
|
// mapped policy. No-mapped-policy case is ignored.
|
2022-11-14 10:15:46 -05:00
|
|
|
if err := store.deleteMappedPolicy(ctx, group, regUser, true); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
if err := store.deleteGroupInfo(ctx, group); err != nil && err != errNoSuchGroup {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete from server memory
|
|
|
|
delete(cache.iamGroupsMap, group)
|
|
|
|
delete(cache.iamGroupPolicyMap, group)
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2022-07-01 16:19:13 -04:00
|
|
|
return cache.updatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return removeMembersFromGroup(ctx, store, cache, group, members, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetGroupStatus - updates group status
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enabled bool) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
if group == "" {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errInvalidArgument
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
gi, ok := cache.iamGroupsMap[group]
|
|
|
|
if !ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errNoSuchGroup
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if enabled {
|
|
|
|
gi.Status = statusEnabled
|
|
|
|
} else {
|
|
|
|
gi.Status = statusDisabled
|
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
gi.UpdatedAt = UTCNow()
|
2021-11-03 22:47:49 -04:00
|
|
|
if err := store.saveGroupInfo(ctx, group, gi); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return gi.UpdatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-05-17 22:58:47 -04:00
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
cache.iamGroupsMap[group] = gi
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
return gi.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetGroupDescription - builds up group description
|
|
|
|
func (store *IAMStoreSys) GetGroupDescription(group string) (gd madmin.GroupDesc, err error) {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
2022-04-24 05:36:31 -04:00
|
|
|
ps, updatedAt, err := cache.policyDBGet(store.getUsersSysType(), group, true)
|
2021-11-03 22:47:49 -04:00
|
|
|
if err != nil {
|
|
|
|
return gd, err
|
|
|
|
}
|
|
|
|
|
|
|
|
policy := strings.Join(ps, ",")
|
|
|
|
|
|
|
|
if store.getUsersSysType() != MinIOUsersSysType {
|
|
|
|
return madmin.GroupDesc{
|
2022-04-24 05:36:31 -04:00
|
|
|
Name: group,
|
|
|
|
Policy: policy,
|
|
|
|
UpdatedAt: updatedAt,
|
2021-11-03 22:47:49 -04:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
gi, ok := cache.iamGroupsMap[group]
|
|
|
|
if !ok {
|
|
|
|
return gd, errNoSuchGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
return madmin.GroupDesc{
|
2022-04-24 05:36:31 -04:00
|
|
|
Name: group,
|
|
|
|
Status: gi.Status,
|
|
|
|
Members: gi.Members,
|
|
|
|
Policy: policy,
|
|
|
|
UpdatedAt: gi.UpdatedAt,
|
2021-11-03 22:47:49 -04:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListGroups - lists groups. Since this is not going to be a frequent
|
|
|
|
// operation, we fetch this info from storage, and refresh the cache as well.
|
|
|
|
func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err error) {
|
2021-11-07 20:42:32 -05:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
|
|
|
m := map[string]GroupInfo{}
|
|
|
|
err = store.loadGroups(ctx, m)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cache.iamGroupsMap = m
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
for k := range cache.iamGroupsMap {
|
|
|
|
res = append(res, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if store.getUsersSysType() == LDAPUsersSysType {
|
|
|
|
m := map[string]MappedPolicy{}
|
|
|
|
err = store.loadMappedPolicies(ctx, stsUser, true, m)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cache.iamGroupPolicyMap = m
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
for k := range cache.iamGroupPolicyMap {
|
|
|
|
res = append(res, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-09 00:16:53 -05:00
|
|
|
// listGroups - lists groups - fetch groups from cache
|
|
|
|
func (store *IAMStoreSys) listGroups(ctx context.Context) (res []string, err error) {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
|
|
|
for k := range cache.iamGroupsMap {
|
|
|
|
res = append(res, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if store.getUsersSysType() == LDAPUsersSysType {
|
|
|
|
for k := range cache.iamGroupPolicyMap {
|
|
|
|
res = append(res, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-09 16:08:33 -05:00
|
|
|
// PolicyDBUpdate - adds or removes given policies to/from the user or group's
|
|
|
|
// policy associations.
|
|
|
|
func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGroup bool,
|
|
|
|
userType IAMUserType, policies []string, isAttach bool) (updatedAt time.Time, addedOrRemoved []string,
|
|
|
|
err error,
|
|
|
|
) {
|
|
|
|
if name == "" {
|
|
|
|
return updatedAt, nil, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
// Load existing policy mapping
|
|
|
|
var mp MappedPolicy
|
|
|
|
if !isGroup {
|
|
|
|
mp = cache.iamUserPolicyMap[name]
|
|
|
|
} else {
|
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
|
|
|
g, ok := cache.iamGroupsMap[name]
|
|
|
|
if !ok {
|
|
|
|
return updatedAt, nil, errNoSuchGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
if g.Status == statusDisabled {
|
2023-03-06 13:46:24 -05:00
|
|
|
return updatedAt, nil, errGroupDisabled
|
2022-12-09 16:08:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
mp = cache.iamGroupPolicyMap[name]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute net policy change effect and updated policy mapping
|
|
|
|
existingPolicySet := mp.policySet()
|
|
|
|
policiesToUpdate := set.CreateStringSet(policies...)
|
|
|
|
newPolicyMapping := mp
|
|
|
|
if isAttach {
|
|
|
|
// new policies to attach => inputPolicies - existing (set difference)
|
|
|
|
policiesToUpdate = policiesToUpdate.Difference(existingPolicySet)
|
|
|
|
// validate that new policies to add are defined.
|
|
|
|
for _, p := range policiesToUpdate.ToSlice() {
|
|
|
|
if _, found := cache.iamPolicyDocsMap[p]; !found {
|
|
|
|
return updatedAt, nil, errNoSuchPolicy
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newPolicyMapping.Policies = strings.Join(existingPolicySet.Union(policiesToUpdate).ToSlice(), ",")
|
|
|
|
} else {
|
|
|
|
// policies to detach => inputPolicies ∩ existing (intersection)
|
|
|
|
policiesToUpdate = policiesToUpdate.Intersection(existingPolicySet)
|
|
|
|
newPolicyMapping.Policies = strings.Join(existingPolicySet.Difference(policiesToUpdate).ToSlice(), ",")
|
|
|
|
}
|
|
|
|
newPolicyMapping.UpdatedAt = UTCNow()
|
|
|
|
|
|
|
|
// We return an error if the requested policy update will have no effect.
|
|
|
|
if policiesToUpdate.IsEmpty() {
|
|
|
|
return updatedAt, nil, errNoPolicyToAttachOrDetach
|
|
|
|
}
|
|
|
|
|
|
|
|
addedOrRemoved = policiesToUpdate.ToSlice()
|
|
|
|
|
|
|
|
if err := store.saveMappedPolicy(ctx, name, userType, isGroup, newPolicyMapping); err != nil {
|
|
|
|
return updatedAt, addedOrRemoved, err
|
|
|
|
}
|
|
|
|
if !isGroup {
|
|
|
|
cache.iamUserPolicyMap[name] = newPolicyMapping
|
|
|
|
} else {
|
|
|
|
cache.iamGroupPolicyMap[name] = newPolicyMapping
|
|
|
|
}
|
|
|
|
cache.updatedAt = UTCNow()
|
|
|
|
return cache.updatedAt, addedOrRemoved, nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// PolicyDBSet - update the policy mapping for the given user or group in
|
2022-08-23 14:11:45 -04:00
|
|
|
// storage and in cache. We do not check for the existence of the user here
|
|
|
|
// since users can be virtual, such as for:
|
|
|
|
// - LDAP users
|
|
|
|
// - CommonName for STS accounts generated by AssumeRoleWithCertificate
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, userType IAMUserType, isGroup bool) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
if name == "" {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errInvalidArgument
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
// Handle policy mapping removal.
|
|
|
|
if policy == "" {
|
|
|
|
if store.getUsersSysType() == LDAPUsersSysType {
|
|
|
|
// Add a fallback removal towards previous content that may come back
|
|
|
|
// as a ghost user due to lack of delete, this change occurred
|
|
|
|
// introduced in PR #11840
|
|
|
|
store.deleteMappedPolicy(ctx, name, regUser, false)
|
|
|
|
}
|
|
|
|
err := store.deleteMappedPolicy(ctx, name, userType, isGroup)
|
2022-11-14 10:15:46 -05:00
|
|
|
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
if !isGroup {
|
|
|
|
delete(cache.iamUserPolicyMap, name)
|
|
|
|
} else {
|
|
|
|
delete(cache.iamGroupPolicyMap, name)
|
|
|
|
}
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2022-07-01 16:19:13 -04:00
|
|
|
return cache.updatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle policy mapping set/update
|
|
|
|
mp := newMappedPolicy(policy)
|
|
|
|
for _, p := range mp.toSlice() {
|
2021-11-23 11:57:29 -05:00
|
|
|
if _, found := cache.iamPolicyDocsMap[p]; !found {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errNoSuchPolicy
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := store.saveMappedPolicy(ctx, name, userType, isGroup, mp); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
if !isGroup {
|
|
|
|
cache.iamUserPolicyMap[name] = mp
|
|
|
|
} else {
|
|
|
|
cache.iamGroupPolicyMap[name] = mp
|
|
|
|
}
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2022-07-01 16:19:13 -04:00
|
|
|
return mp.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// PolicyNotificationHandler - loads given policy from storage. If not present,
|
|
|
|
// deletes from cache. This notification only reads from storage, and updates
|
|
|
|
// cache. When the notification is for a policy deletion, it updates the
|
|
|
|
// user-policy and group-policy maps as well.
|
|
|
|
func (store *IAMStoreSys) PolicyNotificationHandler(ctx context.Context, policy string) error {
|
|
|
|
if policy == "" {
|
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
2021-11-07 20:42:32 -05:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
err := store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap)
|
2022-11-14 10:15:46 -05:00
|
|
|
if errors.Is(err, errNoSuchPolicy) {
|
2021-11-03 22:47:49 -04:00
|
|
|
// policy was deleted, update cache.
|
|
|
|
delete(cache.iamPolicyDocsMap, policy)
|
|
|
|
|
|
|
|
// update user policy map
|
|
|
|
for u, mp := range cache.iamUserPolicyMap {
|
|
|
|
pset := mp.policySet()
|
|
|
|
if !pset.Contains(policy) {
|
|
|
|
continue
|
|
|
|
}
|
2021-12-08 14:50:15 -05:00
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
|
|
|
_, ok := cache.iamUsersMap[u]
|
|
|
|
if !ok {
|
|
|
|
// happens when account is deleted or
|
|
|
|
// expired.
|
|
|
|
delete(cache.iamUserPolicyMap, u)
|
|
|
|
continue
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
pset.Remove(policy)
|
|
|
|
cache.iamUserPolicyMap[u] = newMappedPolicy(strings.Join(pset.ToSlice(), ","))
|
|
|
|
}
|
|
|
|
|
|
|
|
// update group policy map
|
|
|
|
for g, mp := range cache.iamGroupPolicyMap {
|
|
|
|
pset := mp.policySet()
|
|
|
|
if !pset.Contains(policy) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pset.Remove(policy)
|
|
|
|
cache.iamGroupPolicyMap[g] = newMappedPolicy(strings.Join(pset.ToSlice(), ","))
|
|
|
|
}
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeletePolicy - deletes policy from storage and cache.
|
|
|
|
func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string) error {
|
|
|
|
if policy == "" {
|
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
// Check if policy is mapped to any existing user or group.
|
|
|
|
users := []string{}
|
|
|
|
groups := []string{}
|
|
|
|
for u, mp := range cache.iamUserPolicyMap {
|
|
|
|
pset := mp.policySet()
|
2021-12-08 14:50:15 -05:00
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
|
|
|
if _, ok := cache.iamUsersMap[u]; !ok {
|
|
|
|
// This case can happen when a temporary account is
|
|
|
|
// deleted or expired - remove it from userPolicyMap.
|
|
|
|
delete(cache.iamUserPolicyMap, u)
|
|
|
|
continue
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
if pset.Contains(policy) {
|
|
|
|
users = append(users, u)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for g, mp := range cache.iamGroupPolicyMap {
|
|
|
|
pset := mp.policySet()
|
|
|
|
if pset.Contains(policy) {
|
|
|
|
groups = append(groups, g)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(users) != 0 || len(groups) != 0 {
|
|
|
|
return errPolicyInUse
|
|
|
|
}
|
|
|
|
|
|
|
|
err := store.deletePolicyDoc(ctx, policy)
|
2022-11-14 10:15:46 -05:00
|
|
|
if errors.Is(err, errNoSuchPolicy) {
|
2021-11-03 22:47:49 -04:00
|
|
|
// Ignore error if policy is already deleted.
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(cache.iamPolicyDocsMap, policy)
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPolicy - gets the policy definition. Allows specifying multiple comma
|
|
|
|
// separated policies - returns a combined policy.
|
|
|
|
func (store *IAMStoreSys) GetPolicy(name string) (iampolicy.Policy, error) {
|
|
|
|
if name == "" {
|
|
|
|
return iampolicy.Policy{}, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
policies := newMappedPolicy(name).toSlice()
|
|
|
|
var combinedPolicy iampolicy.Policy
|
|
|
|
for _, policy := range policies {
|
|
|
|
if policy == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
v, ok := cache.iamPolicyDocsMap[policy]
|
|
|
|
if !ok {
|
2021-12-11 12:03:39 -05:00
|
|
|
return v.Policy, errNoSuchPolicy
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2021-12-11 12:03:39 -05:00
|
|
|
combinedPolicy = combinedPolicy.Merge(v.Policy)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
return combinedPolicy, nil
|
|
|
|
}
|
|
|
|
|
2021-12-11 12:03:39 -05:00
|
|
|
// GetPolicyDoc - gets the policy doc which has the policy and some metadata.
|
|
|
|
// Exactly one policy must be specified here.
|
|
|
|
func (store *IAMStoreSys) GetPolicyDoc(name string) (r PolicyDoc, err error) {
|
|
|
|
name = strings.TrimSpace(name)
|
|
|
|
if name == "" {
|
|
|
|
return r, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
v, ok := cache.iamPolicyDocsMap[name]
|
|
|
|
if !ok {
|
|
|
|
return r, errNoSuchPolicy
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// SetPolicy - creates a policy with name.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy iampolicy.Policy) (time.Time, error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
if policy.IsEmpty() || name == "" {
|
2022-07-01 16:19:13 -04:00
|
|
|
return time.Time{}, errInvalidArgument
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2021-12-11 12:03:39 -05:00
|
|
|
var (
|
|
|
|
d PolicyDoc
|
|
|
|
ok bool
|
|
|
|
)
|
|
|
|
if d, ok = cache.iamPolicyDocsMap[name]; ok {
|
|
|
|
d.update(policy)
|
|
|
|
} else {
|
|
|
|
d = newPolicyDoc(policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := store.savePolicyDoc(ctx, name, d); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return d.UpdateDate, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2021-12-11 12:03:39 -05:00
|
|
|
cache.iamPolicyDocsMap[name] = d
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
return d.UpdateDate, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListPolicies - fetches all policies from storage and updates cache as well.
|
|
|
|
// If bucketName is non-empty, returns policies matching the bucket.
|
|
|
|
func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (map[string]iampolicy.Policy, error) {
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2021-12-11 12:03:39 -05:00
|
|
|
m := map[string]PolicyDoc{}
|
2021-11-03 22:47:49 -04:00
|
|
|
err := store.loadPolicyDocs(ctx, m)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets default canned policies
|
|
|
|
setDefaultCannedPolicies(m)
|
|
|
|
|
|
|
|
cache.iamPolicyDocsMap = m
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
ret := map[string]iampolicy.Policy{}
|
|
|
|
for k, v := range m {
|
2021-12-11 12:03:39 -05:00
|
|
|
if bucketName == "" || v.Policy.MatchResource(bucketName) {
|
|
|
|
ret[k] = v.Policy
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2022-04-24 05:36:31 -04:00
|
|
|
// ListPolicyDocs - fetches all policy docs from storage and updates cache as well.
|
|
|
|
// If bucketName is non-empty, returns policy docs matching the bucket.
|
|
|
|
func (store *IAMStoreSys) ListPolicyDocs(ctx context.Context, bucketName string) (map[string]PolicyDoc, error) {
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
m := map[string]PolicyDoc{}
|
|
|
|
err := store.loadPolicyDocs(ctx, m)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets default canned policies
|
|
|
|
setDefaultCannedPolicies(m)
|
|
|
|
|
|
|
|
cache.iamPolicyDocsMap = m
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2022-04-24 05:36:31 -04:00
|
|
|
|
|
|
|
ret := map[string]PolicyDoc{}
|
|
|
|
for k, v := range m {
|
|
|
|
if bucketName == "" || v.Policy.MatchResource(bucketName) {
|
|
|
|
ret[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2023-02-09 00:16:53 -05:00
|
|
|
// fetches all policy docs from cache.
|
|
|
|
// If bucketName is non-empty, returns policy docs matching the bucket.
|
|
|
|
func (store *IAMStoreSys) listPolicyDocs(ctx context.Context, bucketName string) (map[string]PolicyDoc, error) {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
ret := map[string]PolicyDoc{}
|
|
|
|
for k, v := range cache.iamPolicyDocsMap {
|
|
|
|
if bucketName == "" || v.Policy.MatchResource(bucketName) {
|
|
|
|
ret[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// helper function - does not take locks.
|
|
|
|
func filterPolicies(cache *iamCache, policyName string, bucketName string) (string, iampolicy.Policy) {
|
|
|
|
var policies []string
|
|
|
|
mp := newMappedPolicy(policyName)
|
|
|
|
combinedPolicy := iampolicy.Policy{}
|
|
|
|
for _, policy := range mp.toSlice() {
|
|
|
|
if policy == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
p, found := cache.iamPolicyDocsMap[policy]
|
2022-01-10 17:26:26 -05:00
|
|
|
if !found {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if bucketName == "" || p.Policy.MatchResource(bucketName) {
|
|
|
|
policies = append(policies, policy)
|
|
|
|
combinedPolicy = combinedPolicy.Merge(p.Policy)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return strings.Join(policies, ","), combinedPolicy
|
|
|
|
}
|
|
|
|
|
|
|
|
// FilterPolicies - accepts a comma separated list of policy names as a string
|
|
|
|
// and bucket and returns only policies that currently exist in MinIO. If
|
|
|
|
// bucketName is non-empty, additionally filters policies matching the bucket.
|
|
|
|
// The first returned value is the list of currently existing policies, and the
|
|
|
|
// second is their combined policy definition.
|
|
|
|
func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, iampolicy.Policy) {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
return filterPolicies(cache, policyName, bucketName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetBucketUsers - returns users (not STS or service accounts) that have access
|
|
|
|
// to the bucket. User is included even if a group policy that grants access to
|
|
|
|
// the bucket is disabled.
|
|
|
|
func (store *IAMStoreSys) GetBucketUsers(bucket string) (map[string]madmin.UserInfo, error) {
|
|
|
|
if bucket == "" {
|
|
|
|
return nil, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
result := map[string]madmin.UserInfo{}
|
|
|
|
for k, v := range cache.iamUsersMap {
|
2022-07-01 16:19:13 -04:00
|
|
|
c := v.Credentials
|
|
|
|
if c.IsTemp() || c.IsServiceAccount() {
|
2021-11-03 22:47:49 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
var policies []string
|
|
|
|
mp, ok := cache.iamUserPolicyMap[k]
|
|
|
|
if ok {
|
|
|
|
policies = append(policies, mp.Policies)
|
|
|
|
for _, group := range cache.iamUserGroupMemberships[k].ToSlice() {
|
|
|
|
if nmp, ok := cache.iamGroupPolicyMap[group]; ok {
|
|
|
|
policies = append(policies, nmp.Policies)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
matchedPolicies, _ := filterPolicies(cache, strings.Join(policies, ","), bucket)
|
|
|
|
if len(matchedPolicies) > 0 {
|
|
|
|
result[k] = madmin.UserInfo{
|
|
|
|
PolicyName: matchedPolicies,
|
|
|
|
Status: func() madmin.AccountStatus {
|
2022-07-01 16:19:13 -04:00
|
|
|
if c.IsValid() {
|
2021-11-03 22:47:49 -04:00
|
|
|
return madmin.AccountEnabled
|
|
|
|
}
|
|
|
|
return madmin.AccountDisabled
|
|
|
|
}(),
|
|
|
|
MemberOf: cache.iamUserGroupMemberships[k].ToSlice(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUsers - returns all users (not STS or service accounts).
|
|
|
|
func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
result := map[string]madmin.UserInfo{}
|
2022-07-01 16:19:13 -04:00
|
|
|
for k, u := range cache.iamUsersMap {
|
|
|
|
v := u.Credentials
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
if v.IsTemp() || v.IsServiceAccount() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
result[k] = madmin.UserInfo{
|
|
|
|
PolicyName: cache.iamUserPolicyMap[k].Policies,
|
|
|
|
Status: func() madmin.AccountStatus {
|
|
|
|
if v.IsValid() {
|
|
|
|
return madmin.AccountEnabled
|
|
|
|
}
|
|
|
|
return madmin.AccountDisabled
|
|
|
|
}(),
|
2022-04-24 05:36:31 -04:00
|
|
|
MemberOf: cache.iamUserGroupMemberships[k].ToSlice(),
|
|
|
|
UpdatedAt: cache.iamUserPolicyMap[k].UpdatedAt,
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-04-16 00:26:02 -04:00
|
|
|
// GetUsersWithMappedPolicies - safely returns the name of access keys with associated policies
|
|
|
|
func (store *IAMStoreSys) GetUsersWithMappedPolicies() map[string]string {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
result := make(map[string]string)
|
|
|
|
for k, v := range cache.iamUserPolicyMap {
|
|
|
|
result[k] = v.Policies
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// GetUserInfo - get info on a user.
|
|
|
|
func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
|
|
|
|
if name == "" {
|
|
|
|
return u, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
if store.getUsersSysType() != MinIOUsersSysType {
|
|
|
|
// If the user has a mapped policy or is a member of a group, we
|
|
|
|
// return that info. Otherwise we return error.
|
|
|
|
var groups []string
|
|
|
|
for _, v := range cache.iamUsersMap {
|
2022-07-01 16:19:13 -04:00
|
|
|
if v.Credentials.ParentUser == name {
|
|
|
|
groups = v.Credentials.Groups
|
2021-11-03 22:47:49 -04:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mappedPolicy, ok := cache.iamUserPolicyMap[name]
|
|
|
|
if !ok {
|
|
|
|
return u, errNoSuchUser
|
|
|
|
}
|
|
|
|
return madmin.UserInfo{
|
|
|
|
PolicyName: mappedPolicy.Policies,
|
|
|
|
MemberOf: groups,
|
2022-04-24 05:36:31 -04:00
|
|
|
UpdatedAt: mappedPolicy.UpdatedAt,
|
2021-11-03 22:47:49 -04:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
ui, found := cache.iamUsersMap[name]
|
2021-11-03 22:47:49 -04:00
|
|
|
if !found {
|
|
|
|
return u, errNoSuchUser
|
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
cred := ui.Credentials
|
2021-11-03 22:47:49 -04:00
|
|
|
if cred.IsTemp() || cred.IsServiceAccount() {
|
|
|
|
return u, errIAMActionNotAllowed
|
|
|
|
}
|
|
|
|
|
|
|
|
return madmin.UserInfo{
|
|
|
|
PolicyName: cache.iamUserPolicyMap[name].Policies,
|
|
|
|
Status: func() madmin.AccountStatus {
|
|
|
|
if cred.IsValid() {
|
|
|
|
return madmin.AccountEnabled
|
|
|
|
}
|
|
|
|
return madmin.AccountDisabled
|
|
|
|
}(),
|
2022-04-24 05:36:31 -04:00
|
|
|
MemberOf: cache.iamUserGroupMemberships[name].ToSlice(),
|
|
|
|
UpdatedAt: cache.iamUserPolicyMap[name].UpdatedAt,
|
2021-11-03 22:47:49 -04:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2022-12-13 15:13:23 -05:00
|
|
|
// GetUserPolicies - returns the policies attached to a user.
|
|
|
|
func (store *IAMStoreSys) GetUserPolicies(name string) ([]string, error) {
|
|
|
|
if name == "" {
|
|
|
|
return nil, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
if cache.iamUserPolicyMap[name].Policies == "" {
|
|
|
|
return []string{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
policies := cache.iamUserPolicyMap[name].toSlice()
|
|
|
|
return policies, nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// PolicyMappingNotificationHandler - handles updating a policy mapping from storage.
|
|
|
|
func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context, userOrGroup string, isGroup bool, userType IAMUserType) error {
|
|
|
|
if userOrGroup == "" {
|
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
2021-11-07 20:42:32 -05:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
m := cache.iamGroupPolicyMap
|
|
|
|
if !isGroup {
|
|
|
|
m = cache.iamUserPolicyMap
|
|
|
|
}
|
|
|
|
err := store.loadMappedPolicy(ctx, userOrGroup, userType, isGroup, m)
|
2022-11-14 10:15:46 -05:00
|
|
|
if errors.Is(err, errNoSuchPolicy) {
|
2021-11-03 22:47:49 -04:00
|
|
|
// This means that the policy mapping was deleted, so we update
|
|
|
|
// the cache.
|
|
|
|
delete(m, userOrGroup)
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// UserNotificationHandler - handles updating a user/STS account/service account
|
|
|
|
// from storage.
|
|
|
|
func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey string, userType IAMUserType) error {
|
|
|
|
if accessKey == "" {
|
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
2021-11-07 20:42:32 -05:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
err := store.loadUser(ctx, accessKey, userType, cache.iamUsersMap)
|
|
|
|
if err == errNoSuchUser {
|
|
|
|
// User was deleted - we update the cache.
|
|
|
|
delete(cache.iamUsersMap, accessKey)
|
|
|
|
|
|
|
|
// 1. Start with updating user-group memberships
|
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
|
|
|
memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice()
|
|
|
|
for _, group := range memberOf {
|
2022-07-01 16:19:13 -04:00
|
|
|
_, removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, true)
|
2021-11-03 22:47:49 -04:00
|
|
|
if removeErr == errNoSuchGroup {
|
|
|
|
removeErr = nil
|
|
|
|
}
|
|
|
|
if removeErr != nil {
|
|
|
|
return removeErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Remove any derived credentials from memory
|
|
|
|
if userType == regUser {
|
|
|
|
for _, u := range cache.iamUsersMap {
|
2022-07-01 16:19:13 -04:00
|
|
|
if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey {
|
|
|
|
delete(cache.iamUsersMap, u.Credentials.AccessKey)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
if u.Credentials.IsTemp() && u.Credentials.ParentUser == accessKey {
|
|
|
|
delete(cache.iamUsersMap, u.Credentials.AccessKey)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. Delete any mapped policy
|
|
|
|
delete(cache.iamUserPolicyMap, accessKey)
|
2022-05-17 22:58:47 -04:00
|
|
|
|
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
return nil
|
|
|
|
}
|
2022-05-17 22:58:47 -04:00
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-05-17 22:58:47 -04:00
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
if userType != svcUser {
|
|
|
|
err = store.loadMappedPolicy(ctx, accessKey, userType, false, cache.iamUserPolicyMap)
|
|
|
|
// Ignore policy not mapped error
|
2022-11-14 10:15:46 -05:00
|
|
|
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
2021-11-03 22:47:49 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are on purpose not persisting the policy map for parent
|
|
|
|
// user, although this is a hack, it is a good enough hack
|
|
|
|
// at this point in time - we need to overhaul our OIDC
|
|
|
|
// usage with service accounts with a more cleaner implementation
|
|
|
|
//
|
|
|
|
// This mapping is necessary to ensure that valid credentials
|
|
|
|
// have necessary ParentUser present - this is mainly for only
|
|
|
|
// webIdentity based STS tokens.
|
2022-07-01 16:19:13 -04:00
|
|
|
u, ok := cache.iamUsersMap[accessKey]
|
2021-11-03 22:47:49 -04:00
|
|
|
if ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
cred := u.Credentials
|
2021-11-03 22:47:49 -04:00
|
|
|
if cred.IsTemp() && cred.ParentUser != "" && cred.ParentUser != globalActiveCred.AccessKey {
|
|
|
|
if _, ok := cache.iamUserPolicyMap[cred.ParentUser]; !ok {
|
|
|
|
cache.iamUserPolicyMap[cred.ParentUser] = cache.iamUserPolicyMap[accessKey]
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteUser - deletes a user from storage and cache. This only used with
|
|
|
|
// long-term users and service accounts, not STS.
|
|
|
|
func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, userType IAMUserType) error {
|
|
|
|
if accessKey == "" {
|
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
// first we remove the user from their groups.
|
|
|
|
if store.getUsersSysType() == MinIOUsersSysType && userType == regUser {
|
|
|
|
memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice()
|
|
|
|
for _, group := range memberOf {
|
2022-07-01 16:19:13 -04:00
|
|
|
_, removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, false)
|
2021-11-03 22:47:49 -04:00
|
|
|
if removeErr != nil {
|
|
|
|
return removeErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we can remove the user from memory and IAM store
|
|
|
|
|
|
|
|
// Delete any STS and service account derived from this credential
|
|
|
|
// first.
|
|
|
|
if userType == regUser {
|
2022-07-01 16:19:13 -04:00
|
|
|
for _, ui := range cache.iamUsersMap {
|
|
|
|
u := ui.Credentials
|
2023-02-27 13:10:22 -05:00
|
|
|
if u.ParentUser == accessKey {
|
|
|
|
switch {
|
|
|
|
case u.IsServiceAccount():
|
|
|
|
_ = store.deleteUserIdentity(ctx, u.AccessKey, svcUser)
|
|
|
|
delete(cache.iamUsersMap, u.AccessKey)
|
|
|
|
case u.IsTemp():
|
|
|
|
_ = store.deleteUserIdentity(ctx, u.AccessKey, stsUser)
|
|
|
|
delete(cache.iamUsersMap, u.AccessKey)
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// It is ok to ignore deletion error on the mapped policy
|
|
|
|
store.deleteMappedPolicy(ctx, accessKey, userType, false)
|
|
|
|
delete(cache.iamUserPolicyMap, accessKey)
|
|
|
|
|
|
|
|
err := store.deleteUserIdentity(ctx, accessKey, userType)
|
|
|
|
if err == errNoSuchUser {
|
|
|
|
// ignore if user is already deleted.
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
delete(cache.iamUsersMap, accessKey)
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
// SetTempUser - saves temporary (STS) credential to storage and cache. If a
|
|
|
|
// policy name is given, it is associated with the parent user specified in the
|
|
|
|
// credential.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cred auth.Credentials, policyName string) (time.Time, error) {
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
if accessKey == "" || !cred.IsTemp() || cred.IsExpired() || cred.ParentUser == "" {
|
2022-07-01 16:19:13 -04:00
|
|
|
return time.Time{}, errInvalidArgument
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
ttl := int64(cred.Expiration.Sub(UTCNow()).Seconds())
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
if policyName != "" {
|
|
|
|
mp := newMappedPolicy(policyName)
|
|
|
|
_, combinedPolicyStmt := filterPolicies(cache, mp.Policies, "")
|
|
|
|
|
|
|
|
if combinedPolicyStmt.IsEmpty() {
|
2022-07-01 16:19:13 -04:00
|
|
|
return time.Time{}, fmt.Errorf("specified policy %s, not found %w", policyName, errNoSuchPolicy)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
err := store.saveMappedPolicy(ctx, cred.ParentUser, stsUser, false, mp, options{ttl: ttl})
|
2021-11-03 22:47:49 -04:00
|
|
|
if err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return time.Time{}, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
Map policy to parent for STS (#13884)
When STS credentials are created for a user, a unique (hopefully stable) parent
user value exists for the credential, which corresponds to the user for whom the
credentials are created. The access policy is mapped to this parent-user and is
persisted. This helps ensure that all STS credentials of a user have the same
policy assignment at all times.
Before this change, for an OIDC STS credential, when the policy claim changes in
the provider (when not using RoleARNs), the change would not take effect on
existing credentials, but only on new ones.
To support existing STS credentials without parent-user policy mappings, we
lookup the policy in the policy claim value. This behavior should be deprecated
when such support is no longer required, as it can still lead to stale
policy mappings.
Additionally this change also simplifies the implementation for all non-RoleARN
STS credentials. Specifically, for AssumeRole (internal IDP) STS credentials,
policies are picked up from the parent user's policies; for
AssumeRoleWithCertificate STS credentials, policies are picked up from the
parent user mapping created when the STS credential is generated.
AssumeRoleWithLDAP already picks up policies mapped to the virtual parent user.
2021-12-17 03:46:30 -05:00
|
|
|
cache.iamUserPolicyMap[cred.ParentUser] = mp
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
u := newUserIdentity(cred)
|
2021-11-15 17:14:22 -05:00
|
|
|
err := store.saveUserIdentity(ctx, accessKey, stsUser, u, options{ttl: ttl})
|
2021-11-03 22:47:49 -04:00
|
|
|
if err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return time.Time{}, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
cache.iamUsersMap[accessKey] = u
|
2022-05-17 22:58:47 -04:00
|
|
|
|
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
return u.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteUsers - given a set of users or access keys, deletes them along with
|
|
|
|
// any derived credentials (STS or service accounts) and any associated policy
|
|
|
|
// mappings.
|
|
|
|
func (store *IAMStoreSys) DeleteUsers(ctx context.Context, users []string) error {
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
var deleted bool
|
2021-11-03 22:47:49 -04:00
|
|
|
usersToDelete := set.CreateStringSet(users...)
|
2022-07-01 16:19:13 -04:00
|
|
|
for user, ui := range cache.iamUsersMap {
|
2021-11-03 22:47:49 -04:00
|
|
|
userType := regUser
|
2022-07-01 16:19:13 -04:00
|
|
|
cred := ui.Credentials
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
if cred.IsServiceAccount() {
|
|
|
|
userType = svcUser
|
|
|
|
} else if cred.IsTemp() {
|
|
|
|
userType = stsUser
|
|
|
|
}
|
|
|
|
|
|
|
|
if usersToDelete.Contains(user) || usersToDelete.Contains(cred.ParentUser) {
|
|
|
|
// Delete this user account and its policy mapping
|
|
|
|
store.deleteMappedPolicy(ctx, user, userType, false)
|
|
|
|
delete(cache.iamUserPolicyMap, user)
|
|
|
|
|
|
|
|
// we are only logging errors, not handling them.
|
|
|
|
err := store.deleteUserIdentity(ctx, user, userType)
|
|
|
|
logger.LogIf(GlobalContext, err)
|
|
|
|
delete(cache.iamUsersMap, user)
|
2022-05-17 22:58:47 -04:00
|
|
|
|
|
|
|
deleted = true
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
if deleted {
|
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-28 21:27:09 -04:00
|
|
|
// ParentUserInfo contains extra info about a the parent user.
|
|
|
|
type ParentUserInfo struct {
|
|
|
|
subClaimValue string
|
|
|
|
roleArns set.StringSet
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllParentUsers - returns all distinct "parent-users" associated with STS
|
|
|
|
// or service credentials, mapped to all distinct roleARNs associated with the
|
|
|
|
// parent user. The dummy role ARN is associated with parent users from
|
|
|
|
// policy-claim based OpenID providers.
|
|
|
|
func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
|
2021-11-03 22:47:49 -04:00
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
2022-04-28 21:27:09 -04:00
|
|
|
res := map[string]ParentUserInfo{}
|
2022-07-01 16:19:13 -04:00
|
|
|
for _, ui := range cache.iamUsersMap {
|
|
|
|
cred := ui.Credentials
|
2022-04-28 21:27:09 -04:00
|
|
|
// Only consider service account or STS credentials with
|
|
|
|
// non-empty session tokens.
|
|
|
|
if !(cred.IsServiceAccount() || cred.IsTemp()) ||
|
|
|
|
cred.SessionToken == "" {
|
|
|
|
continue
|
|
|
|
}
|
2022-03-14 12:09:22 -04:00
|
|
|
|
2022-04-28 21:27:09 -04:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
claims map[string]interface{} = cred.Claims
|
|
|
|
)
|
2022-03-14 12:09:22 -04:00
|
|
|
|
2022-04-28 21:27:09 -04:00
|
|
|
if cred.IsServiceAccount() {
|
|
|
|
claims, err = getClaimsFromTokenWithSecret(cred.SessionToken, cred.SecretKey)
|
|
|
|
} else if cred.IsTemp() {
|
|
|
|
claims, err = getClaimsFromTokenWithSecret(cred.SessionToken, globalActiveCred.SecretKey)
|
|
|
|
}
|
2022-03-14 12:09:22 -04:00
|
|
|
|
2022-04-28 21:27:09 -04:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if cred.ParentUser == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
subClaimValue := cred.ParentUser
|
|
|
|
if v, ok := claims[subClaim]; ok {
|
|
|
|
subFromToken, ok := v.(string)
|
|
|
|
if ok {
|
|
|
|
subClaimValue = subFromToken
|
2022-03-14 12:09:22 -04:00
|
|
|
}
|
2022-04-28 21:27:09 -04:00
|
|
|
}
|
2022-03-14 12:09:22 -04:00
|
|
|
|
2022-04-28 21:27:09 -04:00
|
|
|
roleArn := openid.DummyRoleARN.String()
|
|
|
|
s, ok := claims[roleArnClaim]
|
|
|
|
val, ok2 := s.(string)
|
|
|
|
if ok && ok2 {
|
|
|
|
roleArn = val
|
|
|
|
}
|
|
|
|
v, ok := res[cred.ParentUser]
|
|
|
|
if ok {
|
|
|
|
res[cred.ParentUser] = ParentUserInfo{
|
|
|
|
subClaimValue: subClaimValue,
|
|
|
|
roleArns: v.roleArns.Union(set.CreateStringSet(roleArn)),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
res[cred.ParentUser] = ParentUserInfo{
|
|
|
|
subClaimValue: subClaimValue,
|
|
|
|
roleArns: set.CreateStringSet(roleArn),
|
2022-02-24 14:43:15 -05:00
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 14:43:15 -05:00
|
|
|
return res
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2022-12-19 13:37:03 -05:00
|
|
|
// Assumes store is locked by caller. If users is empty, returns all user mappings.
|
2023-05-26 01:31:05 -04:00
|
|
|
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string,
|
|
|
|
userPredicate func(string) bool,
|
|
|
|
) []madmin.UserPolicyEntities {
|
2022-12-19 13:37:03 -05:00
|
|
|
var r []madmin.UserPolicyEntities
|
|
|
|
usersSet := set.CreateStringSet(users...)
|
|
|
|
for user, mappedPolicy := range cache.iamUserPolicyMap {
|
2023-05-26 01:31:05 -04:00
|
|
|
if userPredicate != nil && !userPredicate(user) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-12-19 13:37:03 -05:00
|
|
|
if !usersSet.IsEmpty() && !usersSet.Contains(user) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ps := mappedPolicy.toSlice()
|
|
|
|
sort.Strings(ps)
|
|
|
|
r = append(r, madmin.UserPolicyEntities{
|
|
|
|
User: user,
|
|
|
|
Policies: ps,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(r, func(i, j int) bool {
|
|
|
|
return r[i].User < r[j].User
|
|
|
|
})
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assumes store is locked by caller. If groups is empty, returns all group mappings.
|
2023-05-26 01:31:05 -04:00
|
|
|
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []string,
|
|
|
|
groupPredicate func(string) bool,
|
|
|
|
) []madmin.GroupPolicyEntities {
|
2022-12-19 13:37:03 -05:00
|
|
|
var r []madmin.GroupPolicyEntities
|
|
|
|
groupsSet := set.CreateStringSet(groups...)
|
|
|
|
for group, mappedPolicy := range cache.iamGroupPolicyMap {
|
2023-05-26 01:31:05 -04:00
|
|
|
if groupPredicate != nil && !groupPredicate(group) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-12-19 13:37:03 -05:00
|
|
|
if !groupsSet.IsEmpty() && !groupsSet.Contains(group) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ps := mappedPolicy.toSlice()
|
|
|
|
sort.Strings(ps)
|
|
|
|
r = append(r, madmin.GroupPolicyEntities{
|
|
|
|
Group: group,
|
|
|
|
Policies: ps,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(r, func(i, j int) bool {
|
|
|
|
return r[i].Group < r[j].Group
|
|
|
|
})
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assumes store is locked by caller. If policies is empty, returns all policy mappings.
|
2023-05-26 01:31:05 -04:00
|
|
|
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
|
|
|
userPredicate, groupPredicate func(string) bool,
|
|
|
|
) []madmin.PolicyEntities {
|
2022-12-19 13:37:03 -05:00
|
|
|
queryPolSet := set.CreateStringSet(policies...)
|
|
|
|
|
|
|
|
policyToUsersMap := make(map[string]set.StringSet)
|
|
|
|
for user, mappedPolicy := range cache.iamUserPolicyMap {
|
2023-05-26 01:31:05 -04:00
|
|
|
if userPredicate != nil && !userPredicate(user) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-12-19 13:37:03 -05:00
|
|
|
commonPolicySet := mappedPolicy.policySet()
|
|
|
|
if !queryPolSet.IsEmpty() {
|
|
|
|
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
|
|
|
}
|
|
|
|
for _, policy := range commonPolicySet.ToSlice() {
|
|
|
|
s, ok := policyToUsersMap[policy]
|
|
|
|
if !ok {
|
|
|
|
policyToUsersMap[policy] = set.CreateStringSet(user)
|
|
|
|
} else {
|
|
|
|
s.Add(user)
|
|
|
|
policyToUsersMap[policy] = s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
policyToGroupsMap := make(map[string]set.StringSet)
|
|
|
|
for group, mappedPolicy := range cache.iamGroupPolicyMap {
|
2023-05-26 01:31:05 -04:00
|
|
|
if groupPredicate != nil && !groupPredicate(group) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-12-19 13:37:03 -05:00
|
|
|
commonPolicySet := mappedPolicy.policySet()
|
|
|
|
if !queryPolSet.IsEmpty() {
|
|
|
|
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
|
|
|
}
|
|
|
|
for _, policy := range commonPolicySet.ToSlice() {
|
|
|
|
s, ok := policyToGroupsMap[policy]
|
|
|
|
if !ok {
|
|
|
|
policyToGroupsMap[policy] = set.CreateStringSet(group)
|
|
|
|
} else {
|
|
|
|
s.Add(group)
|
|
|
|
policyToGroupsMap[policy] = s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m := make(map[string]madmin.PolicyEntities, len(policyToGroupsMap))
|
|
|
|
for policy, groups := range policyToGroupsMap {
|
|
|
|
s := groups.ToSlice()
|
|
|
|
sort.Strings(s)
|
|
|
|
m[policy] = madmin.PolicyEntities{
|
|
|
|
Policy: policy,
|
|
|
|
Groups: s,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for policy, users := range policyToUsersMap {
|
|
|
|
s := users.ToSlice()
|
|
|
|
sort.Strings(s)
|
|
|
|
|
|
|
|
// Update existing value in map
|
|
|
|
pe := m[policy]
|
|
|
|
pe.Policy = policy
|
|
|
|
pe.Users = s
|
|
|
|
m[policy] = pe
|
|
|
|
}
|
|
|
|
|
|
|
|
policyEntities := make([]madmin.PolicyEntities, 0, len(m))
|
|
|
|
for _, v := range m {
|
|
|
|
policyEntities = append(policyEntities, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(policyEntities, func(i, j int) bool {
|
|
|
|
return policyEntities[i].Policy < policyEntities[j].Policy
|
|
|
|
})
|
|
|
|
|
|
|
|
return policyEntities
|
|
|
|
}
|
|
|
|
|
2023-05-26 01:31:05 -04:00
|
|
|
// ListPolicyMappings - return users/groups mapped to policies.
|
|
|
|
func (store *IAMStoreSys) ListPolicyMappings(q madmin.PolicyEntitiesQuery,
|
|
|
|
userPredicate, groupPredicate func(string) bool,
|
|
|
|
) madmin.PolicyEntitiesResult {
|
2022-12-19 13:37:03 -05:00
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
var result madmin.PolicyEntitiesResult
|
|
|
|
|
|
|
|
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policy) == 0
|
|
|
|
|
|
|
|
if len(q.Users) > 0 {
|
2023-05-26 01:31:05 -04:00
|
|
|
result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate)
|
2022-12-19 13:37:03 -05:00
|
|
|
}
|
|
|
|
if len(q.Groups) > 0 {
|
2023-05-26 01:31:05 -04:00
|
|
|
result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate)
|
2022-12-19 13:37:03 -05:00
|
|
|
}
|
|
|
|
if len(q.Policy) > 0 || isAllPoliciesQuery {
|
2023-05-26 01:31:05 -04:00
|
|
|
result.PolicyMappings = store.listPolicyMappings(cache, q.Policy, userPredicate, groupPredicate)
|
2022-12-19 13:37:03 -05:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// SetUserStatus - sets current user status.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
if accessKey != "" && status != madmin.AccountEnabled && status != madmin.AccountDisabled {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errInvalidArgument
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
ui, ok := cache.iamUsersMap[accessKey]
|
2021-11-03 22:47:49 -04:00
|
|
|
if !ok {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errNoSuchUser
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
cred := ui.Credentials
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
if cred.IsTemp() || cred.IsServiceAccount() {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errIAMActionNotAllowed
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
uinfo := newUserIdentity(auth.Credentials{
|
|
|
|
AccessKey: accessKey,
|
|
|
|
SecretKey: cred.SecretKey,
|
|
|
|
Status: func() string {
|
2022-01-19 23:02:24 -05:00
|
|
|
switch string(status) {
|
|
|
|
case string(madmin.AccountEnabled), string(auth.AccountOn):
|
2021-11-03 22:47:49 -04:00
|
|
|
return auth.AccountOn
|
|
|
|
}
|
|
|
|
return auth.AccountOff
|
|
|
|
}(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err := store.saveUserIdentity(ctx, accessKey, regUser, uinfo); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2023-02-27 13:10:22 -05:00
|
|
|
if err := cache.updateUserWithClaims(accessKey, uinfo); err != nil {
|
|
|
|
return updatedAt, err
|
|
|
|
}
|
2022-05-17 22:58:47 -04:00
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
return uinfo.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddServiceAccount - add a new service account
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) AddServiceAccount(ctx context.Context, cred auth.Credentials) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
|
|
|
accessKey := cred.AccessKey
|
|
|
|
parentUser := cred.ParentUser
|
|
|
|
|
|
|
|
// Found newly requested service account, to be an existing account -
|
|
|
|
// reject such operation (updates to the service account are handled in
|
|
|
|
// a different API).
|
2022-07-01 16:19:13 -04:00
|
|
|
if su, found := cache.iamUsersMap[accessKey]; found {
|
|
|
|
scred := su.Credentials
|
2022-01-10 17:26:26 -05:00
|
|
|
if scred.ParentUser != parentUser {
|
2023-01-25 11:20:12 -05:00
|
|
|
return updatedAt, fmt.Errorf("%w: the service account access key is taken by another user", errIAMServiceAccountNotAllowed)
|
2022-01-10 17:26:26 -05:00
|
|
|
}
|
2023-01-25 11:20:12 -05:00
|
|
|
return updatedAt, fmt.Errorf("%w: the service account access key already taken", errIAMServiceAccountNotAllowed)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parent user must not be a service account.
|
2022-07-01 16:19:13 -04:00
|
|
|
if u, found := cache.iamUsersMap[parentUser]; found && u.Credentials.IsServiceAccount() {
|
2023-01-25 11:20:12 -05:00
|
|
|
return updatedAt, fmt.Errorf("%w: unable to create a service account for another service account", errIAMServiceAccountNotAllowed)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
u := newUserIdentity(cred)
|
2022-07-01 16:19:13 -04:00
|
|
|
err = store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u)
|
2021-11-03 22:47:49 -04:00
|
|
|
if err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2023-02-27 13:10:22 -05:00
|
|
|
cache.updateUserWithClaims(u.Credentials.AccessKey, u)
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
return u.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateServiceAccount - updates a service account on storage.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey string, opts updateServiceAccountOpts) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
ui, ok := cache.iamUsersMap[accessKey]
|
|
|
|
if !ok || !ui.Credentials.IsServiceAccount() {
|
|
|
|
return updatedAt, errNoSuchServiceAccount
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
cr := ui.Credentials
|
2022-03-14 12:09:22 -04:00
|
|
|
currentSecretKey := cr.SecretKey
|
2021-11-03 22:47:49 -04:00
|
|
|
if opts.secretKey != "" {
|
|
|
|
if !auth.IsSecretKeyValid(opts.secretKey) {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, auth.ErrInvalidSecretKeyLength
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
cr.SecretKey = opts.secretKey
|
|
|
|
}
|
|
|
|
|
2023-05-17 20:05:36 -04:00
|
|
|
if opts.name != "" {
|
|
|
|
cr.Name = opts.name
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.description != "" {
|
|
|
|
cr.Description = opts.description
|
2023-01-10 12:57:52 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 13:10:22 -05:00
|
|
|
if opts.expiration != nil {
|
|
|
|
expirationInUTC := opts.expiration.UTC()
|
|
|
|
if err := validateSvcExpirationInUTC(expirationInUTC); err != nil {
|
|
|
|
return updatedAt, err
|
|
|
|
}
|
|
|
|
cr.Expiration = expirationInUTC
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
switch opts.status {
|
|
|
|
// The caller did not ask to update status account, do nothing
|
|
|
|
case "":
|
2022-01-19 23:02:24 -05:00
|
|
|
case string(madmin.AccountEnabled):
|
|
|
|
cr.Status = auth.AccountOn
|
|
|
|
case string(madmin.AccountDisabled):
|
|
|
|
cr.Status = auth.AccountOff
|
2021-11-03 22:47:49 -04:00
|
|
|
// Update account status
|
|
|
|
case auth.AccountOn, auth.AccountOff:
|
|
|
|
cr.Status = opts.status
|
|
|
|
default:
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, errors.New("unknown account status value")
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2022-03-14 12:09:22 -04:00
|
|
|
m, err := getClaimsFromTokenWithSecret(cr.SessionToken, currentSecretKey)
|
|
|
|
if err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, fmt.Errorf("unable to get svc acc claims: %v", err)
|
2022-03-14 12:09:22 -04:00
|
|
|
}
|
2021-11-20 05:07:16 -05:00
|
|
|
|
2022-05-02 20:56:19 -04:00
|
|
|
// Extracted session policy name string can be removed as its not useful
|
|
|
|
// at this point.
|
|
|
|
delete(m, sessionPolicyNameExtracted)
|
|
|
|
|
|
|
|
// sessionPolicy is nil and there is embedded policy attached we remove
|
2023-05-09 03:53:08 -04:00
|
|
|
// embedded policy at that point.
|
2022-05-02 20:56:19 -04:00
|
|
|
if _, ok := m[iampolicy.SessionPolicyName]; ok && opts.sessionPolicy == nil {
|
|
|
|
delete(m, iampolicy.SessionPolicyName)
|
|
|
|
m[iamPolicyClaimNameSA()] = inheritedPolicyType
|
|
|
|
}
|
|
|
|
|
2023-05-09 03:53:08 -04:00
|
|
|
if opts.sessionPolicy != nil { // session policies is being updated
|
2022-03-14 12:09:22 -04:00
|
|
|
if err := opts.sessionPolicy.Validate(); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-03-14 12:09:22 -04:00
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
policyBuf, err := json.Marshal(opts.sessionPolicy)
|
|
|
|
if err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-03-14 12:09:22 -04:00
|
|
|
|
2023-05-09 03:53:08 -04:00
|
|
|
if len(policyBuf) > 2048 {
|
|
|
|
return updatedAt, errSessionPolicyTooLarge
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2021-11-20 05:07:16 -05:00
|
|
|
// Overwrite session policy claims.
|
2021-11-03 22:47:49 -04:00
|
|
|
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
|
2022-05-02 20:56:19 -04:00
|
|
|
m[iamPolicyClaimNameSA()] = embeddedPolicyType
|
2022-03-14 12:09:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cr.SessionToken, err = auth.JWTSignWithAccessKey(accessKey, m, cr.SecretKey)
|
|
|
|
if err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
u := newUserIdentity(cr)
|
|
|
|
if err := store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2023-02-27 13:10:22 -05:00
|
|
|
if err := cache.updateUserWithClaims(u.Credentials.AccessKey, u); err != nil {
|
|
|
|
return updatedAt, err
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
return u.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
2022-04-28 05:39:00 -04:00
|
|
|
// ListTempAccounts - lists only temporary accounts from the cache.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) ListTempAccounts(ctx context.Context, accessKey string) ([]UserIdentity, error) {
|
2022-04-28 05:39:00 -04:00
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
2022-05-25 18:28:54 -04:00
|
|
|
userExists := false
|
2022-07-01 16:19:13 -04:00
|
|
|
var tempAccounts []UserIdentity
|
2022-04-28 05:39:00 -04:00
|
|
|
for _, v := range cache.iamUsersMap {
|
2022-05-25 18:28:54 -04:00
|
|
|
isDerived := false
|
2022-07-01 16:19:13 -04:00
|
|
|
if v.Credentials.IsServiceAccount() || v.Credentials.IsTemp() {
|
2022-05-25 18:28:54 -04:00
|
|
|
isDerived = true
|
|
|
|
}
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
if !isDerived && v.Credentials.AccessKey == accessKey {
|
2022-05-25 18:28:54 -04:00
|
|
|
userExists = true
|
2022-07-01 16:19:13 -04:00
|
|
|
} else if isDerived && v.Credentials.ParentUser == accessKey {
|
2022-05-25 18:28:54 -04:00
|
|
|
userExists = true
|
2022-07-01 16:19:13 -04:00
|
|
|
if v.Credentials.IsTemp() {
|
2022-05-25 18:28:54 -04:00
|
|
|
// Hide secret key & session key here
|
2022-07-01 16:19:13 -04:00
|
|
|
v.Credentials.SecretKey = ""
|
|
|
|
v.Credentials.SessionToken = ""
|
2022-05-25 18:28:54 -04:00
|
|
|
tempAccounts = append(tempAccounts, v)
|
|
|
|
}
|
2022-04-28 05:39:00 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-25 18:28:54 -04:00
|
|
|
if !userExists {
|
|
|
|
return nil, errNoSuchUser
|
|
|
|
}
|
|
|
|
|
2022-04-28 05:39:00 -04:00
|
|
|
return tempAccounts, nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
// ListServiceAccounts - lists only service accounts from the cache.
|
|
|
|
func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
2022-05-25 18:28:54 -04:00
|
|
|
userExists := false
|
2021-11-03 22:47:49 -04:00
|
|
|
var serviceAccounts []auth.Credentials
|
2022-07-01 16:19:13 -04:00
|
|
|
for _, u := range cache.iamUsersMap {
|
2022-05-25 18:28:54 -04:00
|
|
|
isDerived := false
|
2022-07-01 16:19:13 -04:00
|
|
|
v := u.Credentials
|
2022-05-25 18:28:54 -04:00
|
|
|
if v.IsServiceAccount() || v.IsTemp() {
|
|
|
|
isDerived = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isDerived && v.AccessKey == accessKey {
|
|
|
|
userExists = true
|
|
|
|
} else if isDerived && v.ParentUser == accessKey {
|
|
|
|
userExists = true
|
|
|
|
if v.IsServiceAccount() {
|
|
|
|
// Hide secret key & session key here
|
|
|
|
v.SecretKey = ""
|
|
|
|
v.SessionToken = ""
|
|
|
|
serviceAccounts = append(serviceAccounts, v)
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-25 18:28:54 -04:00
|
|
|
if !userExists {
|
|
|
|
return nil, errNoSuchUser
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
return serviceAccounts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddUser - adds/updates long term user account to storage.
|
2022-07-01 16:19:13 -04:00
|
|
|
func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) (updatedAt time.Time, err error) {
|
2021-11-03 22:47:49 -04:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
ui, ok := cache.iamUsersMap[accessKey]
|
2021-11-03 22:47:49 -04:00
|
|
|
|
|
|
|
// It is not possible to update an STS account.
|
2022-07-01 16:19:13 -04:00
|
|
|
if ok && ui.Credentials.IsTemp() {
|
|
|
|
return updatedAt, errIAMActionNotAllowed
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
u := newUserIdentity(auth.Credentials{
|
|
|
|
AccessKey: accessKey,
|
2021-12-23 12:21:21 -05:00
|
|
|
SecretKey: ureq.SecretKey,
|
2021-11-03 22:47:49 -04:00
|
|
|
Status: func() string {
|
2022-01-19 23:02:24 -05:00
|
|
|
switch string(ureq.Status) {
|
|
|
|
case string(madmin.AccountEnabled), string(auth.AccountOn):
|
2021-11-03 22:47:49 -04:00
|
|
|
return auth.AccountOn
|
|
|
|
}
|
|
|
|
return auth.AccountOff
|
|
|
|
}(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil {
|
2022-07-01 16:19:13 -04:00
|
|
|
return updatedAt, err
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2023-02-27 13:10:22 -05:00
|
|
|
if err := cache.updateUserWithClaims(accessKey, u); err != nil {
|
|
|
|
return updatedAt, err
|
|
|
|
}
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
return u.UpdatedAt, nil
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateUserSecretKey - sets user secret key to storage.
|
|
|
|
func (store *IAMStoreSys) UpdateUserSecretKey(ctx context.Context, accessKey, secretKey string) error {
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2022-07-01 16:19:13 -04:00
|
|
|
ui, ok := cache.iamUsersMap[accessKey]
|
2021-11-03 22:47:49 -04:00
|
|
|
if !ok {
|
|
|
|
return errNoSuchUser
|
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
cred := ui.Credentials
|
2021-11-03 22:47:49 -04:00
|
|
|
cred.SecretKey = secretKey
|
|
|
|
u := newUserIdentity(cred)
|
|
|
|
if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-02-27 13:10:22 -05:00
|
|
|
return cache.updateUserWithClaims(accessKey, u)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetSTSAndServiceAccounts - returns all STS and Service account credentials.
|
|
|
|
func (store *IAMStoreSys) GetSTSAndServiceAccounts() []auth.Credentials {
|
|
|
|
cache := store.rlock()
|
|
|
|
defer store.runlock()
|
|
|
|
|
|
|
|
var res []auth.Credentials
|
2022-07-01 16:19:13 -04:00
|
|
|
for _, u := range cache.iamUsersMap {
|
|
|
|
cred := u.Credentials
|
2021-11-03 22:47:49 -04:00
|
|
|
if cred.IsTemp() || cred.IsServiceAccount() {
|
|
|
|
res = append(res, cred)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateUserIdentity - updates a user credential.
|
|
|
|
func (store *IAMStoreSys) UpdateUserIdentity(ctx context.Context, cred auth.Credentials) error {
|
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
userType := regUser
|
|
|
|
if cred.IsServiceAccount() {
|
|
|
|
userType = svcUser
|
|
|
|
} else if cred.IsTemp() {
|
|
|
|
userType = stsUser
|
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
ui := newUserIdentity(cred)
|
2021-11-03 22:47:49 -04:00
|
|
|
// Overwrite the user identity here. As store should be
|
|
|
|
// atomic, it shouldn't cause any corruption.
|
2022-07-01 16:19:13 -04:00
|
|
|
if err := store.saveUserIdentity(ctx, cred.AccessKey, userType, ui); err != nil {
|
2021-11-03 22:47:49 -04:00
|
|
|
return err
|
|
|
|
}
|
2023-02-27 13:10:22 -05:00
|
|
|
|
|
|
|
return cache.updateUserWithClaims(cred.AccessKey, ui)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoadUser - attempts to load user info from storage and updates cache.
|
|
|
|
func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) {
|
2021-11-07 20:42:32 -05:00
|
|
|
cache := store.lock()
|
|
|
|
defer store.unlock()
|
2021-11-03 22:47:49 -04:00
|
|
|
|
2022-05-17 22:58:47 -04:00
|
|
|
cache.updatedAt = time.Now()
|
|
|
|
|
2021-11-03 22:47:49 -04:00
|
|
|
_, found := cache.iamUsersMap[accessKey]
|
|
|
|
if !found {
|
|
|
|
store.loadUser(ctx, accessKey, regUser, cache.iamUsersMap)
|
|
|
|
if _, found = cache.iamUsersMap[accessKey]; found {
|
|
|
|
// load mapped policies
|
|
|
|
store.loadMappedPolicy(ctx, accessKey, regUser, false, cache.iamUserPolicyMap)
|
|
|
|
} else {
|
|
|
|
// check for service account
|
|
|
|
store.loadUser(ctx, accessKey, svcUser, cache.iamUsersMap)
|
|
|
|
if svc, found := cache.iamUsersMap[accessKey]; found {
|
|
|
|
// Load parent user and mapped policies.
|
|
|
|
if store.getUsersSysType() == MinIOUsersSysType {
|
2022-07-01 16:19:13 -04:00
|
|
|
store.loadUser(ctx, svc.Credentials.ParentUser, regUser, cache.iamUsersMap)
|
2021-11-03 22:47:49 -04:00
|
|
|
}
|
2022-07-01 16:19:13 -04:00
|
|
|
store.loadMappedPolicy(ctx, svc.Credentials.ParentUser, regUser, false, cache.iamUserPolicyMap)
|
2021-11-03 22:47:49 -04:00
|
|
|
} else {
|
|
|
|
// check for STS account
|
|
|
|
store.loadUser(ctx, accessKey, stsUser, cache.iamUsersMap)
|
|
|
|
if _, found = cache.iamUsersMap[accessKey]; found {
|
|
|
|
// Load mapped policy
|
|
|
|
store.loadMappedPolicy(ctx, accessKey, stsUser, false, cache.iamUserPolicyMap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load any associated policy definitions
|
|
|
|
for _, policy := range cache.iamUserPolicyMap[accessKey].toSlice() {
|
|
|
|
if _, found = cache.iamPolicyDocsMap[policy]; !found {
|
|
|
|
store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-27 13:10:22 -05:00
|
|
|
|
|
|
|
func extractJWTClaims(u UserIdentity) (*jwt.MapClaims, error) {
|
|
|
|
jwtClaims, err := auth.ExtractClaims(u.Credentials.SessionToken, u.Credentials.SecretKey)
|
|
|
|
if err != nil {
|
|
|
|
// Session tokens for STS creds will be generated with root secret
|
|
|
|
jwtClaims, err = auth.ExtractClaims(u.Credentials.SessionToken, globalActiveCred.SecretKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return jwtClaims, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateSvcExpirationInUTC(expirationInUTC time.Time) error {
|
|
|
|
currentTime := time.Now().UTC()
|
|
|
|
minExpiration := currentTime.Add(minServiceAccountExpiry)
|
|
|
|
maxExpiration := currentTime.Add(maxServiceAccountExpiry)
|
|
|
|
if expirationInUTC.Before(minExpiration) || expirationInUTC.After(maxExpiration) {
|
|
|
|
return errInvalidSvcAcctExpiration
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|