mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
feat: introduce listUsers, listPolicies for any bucket (#12372)
Bonus change LDAP settings such as user, group mappings are now listed as part of `mc admin user list` and `mc admin group list` Additionally this PR also deprecates the `/v2` API that is no longer in use.
This commit is contained in:
parent
b5ebfd35b4
commit
be541dba8a
@ -94,6 +94,42 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// ListUsers - GET /minio/admin/v3/list-users?bucket={bucket}
|
||||
func (a adminAPIHandlers) ListBucketUsers(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListBucketUsers")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
bucket := mux.Vars(r)["bucket"]
|
||||
|
||||
password := cred.SecretKey
|
||||
|
||||
allCredentials, err := globalIAMSys.ListBucketUsers(bucket)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(allCredentials)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
econfigData, err := madmin.EncryptData(password, data)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, econfigData)
|
||||
}
|
||||
|
||||
// ListUsers - GET /minio/admin/v3/list-users
|
||||
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListUsers")
|
||||
@ -1062,33 +1098,6 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
||||
writeSuccessResponseJSON(w, usageInfoJSON)
|
||||
}
|
||||
|
||||
// InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName}
|
||||
func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "InfoCannedPolicyV2")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := globalIAMSys.InfoPolicy(mux.Vars(r)["name"])
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(data)
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
// InfoCannedPolicy - GET /minio/admin/v3/info-canned-policy?name={policyName}
|
||||
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "InfoCannedPolicy")
|
||||
@ -1113,9 +1122,9 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
// ListCannedPoliciesV2 - GET /minio/admin/v2/list-canned-policies
|
||||
func (a adminAPIHandlers) ListCannedPoliciesV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListCannedPoliciesV2")
|
||||
// ListBucketPolicies - GET /minio/admin/v3/list-canned-policies?bucket={bucket}
|
||||
func (a adminAPIHandlers) ListBucketPolicies(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "ListBucketPolicies")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
@ -1124,27 +1133,29 @@ func (a adminAPIHandlers) ListCannedPoliciesV2(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
policies, err := globalIAMSys.ListPolicies()
|
||||
bucket := mux.Vars(r)["bucket"]
|
||||
policies, err := globalIAMSys.ListPolicies(bucket)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
policyMap := make(map[string][]byte, len(policies))
|
||||
for k, p := range policies {
|
||||
var err error
|
||||
policyMap[k], err = json.Marshal(p)
|
||||
var newPolicies = make(map[string]iampolicy.Policy)
|
||||
for name, p := range policies {
|
||||
_, err = json.Marshal(p)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
}
|
||||
newPolicies[name] = p
|
||||
}
|
||||
if err = json.NewEncoder(w).Encode(policyMap); err != nil {
|
||||
if err = json.NewEncoder(w).Encode(newPolicies); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
w.(http.Flusher).Flush()
|
||||
|
||||
}
|
||||
|
||||
// ListCannedPolicies - GET /minio/admin/v3/list-canned-policies
|
||||
@ -1158,7 +1169,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
policies, err := globalIAMSys.ListPolicies()
|
||||
policies, err := globalIAMSys.ListPolicies("")
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
|
@ -25,11 +25,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
adminPathPrefix = minioReservedBucketPath + "/admin"
|
||||
adminAPIVersionV2 = madmin.AdminAPIVersionV2
|
||||
adminAPIVersion = madmin.AdminAPIVersion
|
||||
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
||||
adminAPIVersionV2Prefix = SlashSeparator + adminAPIVersionV2
|
||||
adminPathPrefix = minioReservedBucketPath + "/admin"
|
||||
adminAPIVersion = madmin.AdminAPIVersion
|
||||
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
||||
)
|
||||
|
||||
// adminAPIHandlers provides HTTP handlers for MinIO admin API.
|
||||
@ -46,7 +44,6 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
||||
|
||||
adminVersions := []string{
|
||||
adminAPIVersionPrefix,
|
||||
adminAPIVersionV2Prefix,
|
||||
}
|
||||
|
||||
for _, adminVersion := range adminVersions {
|
||||
@ -127,19 +124,11 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(httpTraceHdrs(adminAPI.ListServiceAccounts))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(httpTraceHdrs(adminAPI.DeleteServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||
|
||||
if adminVersion == adminAPIVersionV2Prefix {
|
||||
// Info policy IAM v2
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicyV2)).Queries("name", "{name:.*}")
|
||||
|
||||
// List policies v2
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPoliciesV2))
|
||||
} else {
|
||||
// Info policy IAM latest
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
||||
|
||||
// List policies latest
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPolicies))
|
||||
}
|
||||
// Info policy IAM latest
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
||||
// List policies latest
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListBucketPolicies)).Queries("bucket", "{bucket:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPolicies))
|
||||
|
||||
// Remove policy IAM
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}")
|
||||
@ -153,11 +142,11 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(httpTraceHdrs(adminAPI.RemoveUser)).Queries("accessKey", "{accessKey:.*}")
|
||||
|
||||
// List users
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListBucketUsers)).Queries("bucket", "{bucket:.*}")
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListUsers))
|
||||
|
||||
// User info
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(httpTraceHdrs(adminAPI.GetUserInfo)).Queries("accessKey", "{accessKey:.*}")
|
||||
|
||||
// Add/Remove members from group
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(httpTraceHdrs(adminAPI.UpdateGroupMembers))
|
||||
|
||||
|
94
cmd/iam.go
94
cmd/iam.go
@ -747,7 +747,7 @@ func (sys *IAMSys) InfoPolicy(policyName string) (iampolicy.Policy, error) {
|
||||
}
|
||||
|
||||
// ListPolicies - lists all canned policies.
|
||||
func (sys *IAMSys) ListPolicies() (map[string]iampolicy.Policy, error) {
|
||||
func (sys *IAMSys) ListPolicies(bucketName string) (map[string]iampolicy.Policy, error) {
|
||||
if !sys.Initialized() {
|
||||
return nil, errServerNotInitialized
|
||||
}
|
||||
@ -759,7 +759,11 @@ func (sys *IAMSys) ListPolicies() (map[string]iampolicy.Policy, error) {
|
||||
|
||||
policyDocsMap := make(map[string]iampolicy.Policy, len(sys.iamPolicyDocsMap))
|
||||
for k, v := range sys.iamPolicyDocsMap {
|
||||
policyDocsMap[k] = v
|
||||
if bucketName != "" && v.MatchResource(bucketName) {
|
||||
policyDocsMap[k] = v
|
||||
} else {
|
||||
policyDocsMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return policyDocsMap, nil
|
||||
@ -921,16 +925,60 @@ func (sys *IAMSys) SetTempUser(accessKey string, cred auth.Credentials, policyNa
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListBucketUsers - list all users who can access this 'bucket'
|
||||
func (sys *IAMSys) ListBucketUsers(bucket string) (map[string]madmin.UserInfo, error) {
|
||||
if bucket == "" {
|
||||
return nil, errInvalidArgument
|
||||
}
|
||||
|
||||
sys.store.rlock()
|
||||
defer sys.store.runlock()
|
||||
|
||||
var users = make(map[string]madmin.UserInfo)
|
||||
|
||||
for k, v := range sys.iamUsersMap {
|
||||
if v.IsTemp() || v.IsServiceAccount() {
|
||||
continue
|
||||
}
|
||||
var policies []string
|
||||
mp, ok := sys.iamUserPolicyMap[k]
|
||||
if ok {
|
||||
policies = append(policies, mp.toSlice()...)
|
||||
for _, group := range sys.iamUserGroupMemberships[k].ToSlice() {
|
||||
if nmp, ok := sys.iamGroupPolicyMap[group]; ok {
|
||||
policies = append(policies, nmp.toSlice()...)
|
||||
}
|
||||
}
|
||||
}
|
||||
var matchesPolices []string
|
||||
for _, p := range policies {
|
||||
if sys.iamPolicyDocsMap[p].MatchResource(bucket) {
|
||||
matchesPolices = append(matchesPolices, p)
|
||||
}
|
||||
}
|
||||
if len(matchesPolices) > 0 {
|
||||
users[k] = madmin.UserInfo{
|
||||
PolicyName: strings.Join(matchesPolices, ","),
|
||||
Status: func() madmin.AccountStatus {
|
||||
if v.IsValid() {
|
||||
return madmin.AccountEnabled
|
||||
}
|
||||
return madmin.AccountDisabled
|
||||
}(),
|
||||
MemberOf: sys.iamUserGroupMemberships[k].ToSlice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// ListUsers - list all users.
|
||||
func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
|
||||
if !sys.Initialized() {
|
||||
return nil, errServerNotInitialized
|
||||
}
|
||||
|
||||
if sys.usersSysType != MinIOUsersSysType {
|
||||
return nil, errIAMActionNotAllowed
|
||||
}
|
||||
|
||||
<-sys.configLoaded
|
||||
|
||||
sys.store.rlock()
|
||||
@ -948,6 +996,16 @@ func (sys *IAMSys) ListUsers() (map[string]madmin.UserInfo, error) {
|
||||
}
|
||||
return madmin.AccountDisabled
|
||||
}(),
|
||||
MemberOf: sys.iamUserGroupMemberships[k].ToSlice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sys.usersSysType == LDAPUsersSysType {
|
||||
for k, v := range sys.iamUserPolicyMap {
|
||||
users[k] = madmin.UserInfo{
|
||||
PolicyName: v.Policies,
|
||||
Status: madmin.AccountEnabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1013,15 +1071,21 @@ func (sys *IAMSys) GetUserInfo(name string) (u madmin.UserInfo, err error) {
|
||||
sys.store.rlock()
|
||||
// If the user has a mapped policy or is a member of a group, we
|
||||
// return that info. Otherwise we return error.
|
||||
mappedPolicy, ok1 := sys.iamUserPolicyMap[name]
|
||||
memberships, ok2 := sys.iamUserGroupMemberships[name]
|
||||
var groups []string
|
||||
for _, v := range sys.iamUsersMap {
|
||||
if v.ParentUser == name {
|
||||
groups = v.Groups
|
||||
break
|
||||
}
|
||||
}
|
||||
mappedPolicy, ok := sys.iamUserPolicyMap[name]
|
||||
sys.store.runlock()
|
||||
if !ok1 && !ok2 {
|
||||
if !ok {
|
||||
return u, errNoSuchUser
|
||||
}
|
||||
return madmin.UserInfo{
|
||||
PolicyName: mappedPolicy.Policies,
|
||||
MemberOf: memberships.ToSlice(),
|
||||
MemberOf: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -1741,10 +1805,6 @@ func (sys *IAMSys) ListGroups() (r []string, err error) {
|
||||
return r, errServerNotInitialized
|
||||
}
|
||||
|
||||
if sys.usersSysType != MinIOUsersSysType {
|
||||
return nil, errIAMActionNotAllowed
|
||||
}
|
||||
|
||||
<-sys.configLoaded
|
||||
|
||||
sys.store.rlock()
|
||||
@ -1755,6 +1815,12 @@ func (sys *IAMSys) ListGroups() (r []string, err error) {
|
||||
r = append(r, k)
|
||||
}
|
||||
|
||||
if sys.usersSysType == LDAPUsersSysType {
|
||||
for k := range sys.iamGroupPolicyMap {
|
||||
r = append(r, k)
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,16 @@ type Policy struct {
|
||||
Statements []Statement `json:"Statement"`
|
||||
}
|
||||
|
||||
// MatchResource matches resource with match resource patterns
|
||||
func (iamp Policy) MatchResource(resource string) bool {
|
||||
for _, statement := range iamp.Statements {
|
||||
if statement.Resources.MatchResource(resource) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAllowed - checks given policy args is allowed to continue the Rest API.
|
||||
func (iamp Policy) IsAllowed(args Args) bool {
|
||||
// Check all deny statements. If any one statement denies, return false.
|
||||
|
@ -48,7 +48,12 @@ func (r Resource) IsValid() bool {
|
||||
return r.Pattern != ""
|
||||
}
|
||||
|
||||
// Match - matches object name with resource pattern.
|
||||
// MatchResource matches object name with resource pattern only.
|
||||
func (r Resource) MatchResource(resource string) bool {
|
||||
return r.Match(resource, nil)
|
||||
}
|
||||
|
||||
// Match - matches object name with resource pattern, including specific conditionals.
|
||||
func (r Resource) Match(resource string, conditionValues map[string][]string) bool {
|
||||
pattern := r.Pattern
|
||||
for _, key := range condition.CommonKeys {
|
||||
|
@ -99,6 +99,16 @@ func (resourceSet ResourceSet) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(resources)
|
||||
}
|
||||
|
||||
// MatchResource matches object name with resource patterns only.
|
||||
func (resourceSet ResourceSet) MatchResource(resource string) bool {
|
||||
for r := range resourceSet {
|
||||
if r.MatchResource(resource) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Match - matches object name with anyone of resource pattern in resource set.
|
||||
func (resourceSet ResourceSet) Match(resource string, conditionValues map[string][]string) bool {
|
||||
for r := range resourceSet {
|
||||
|
Loading…
Reference in New Issue
Block a user