Add support for multiple admins (#8487)

Also define IAM policies for administering
MinIO server
master
poornas 5 years ago committed by kannappanr
parent 13a3d17321
commit 929951fd49
  1. 3
      cmd/admin-handlers-config-kv.go
  2. 44
      cmd/admin-handlers-users.go
  3. 42
      cmd/admin-handlers.go
  4. 34
      cmd/auth-handler.go
  5. 3
      cmd/auth-handler_test.go
  6. 114
      docs/multi-user/admin/README.md
  7. 6
      pkg/iam/policy/action.go
  8. 154
      pkg/iam/policy/admin-action.go
  9. 24
      pkg/iam/policy/statement.go
  10. 20
      pkg/iam/policy/statement_test.go
  11. 11
      pkg/policy/condition/key.go

@ -29,6 +29,7 @@ import (
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin"
)
@ -41,7 +42,7 @@ func validateAdminReqConfigKV(ctx context.Context, w http.ResponseWriter, r *htt
}
// Validate request signature.
adminAPIErr := checkAdminRequestAuthType(ctx, r, "")
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.ConfigUpdateAdminAction, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return nil

@ -25,33 +25,37 @@ import (
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin"
)
func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer {
func validateAdminUsersReq(ctx context.Context, w http.ResponseWriter, r *http.Request, action iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
var cred auth.Credentials
var adminAPIErr APIErrorCode
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return nil
return nil, cred
}
// Validate request signature.
adminAPIErr := checkAdminRequestAuthType(ctx, r, "")
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return nil
return nil, cred
}
return objectAPI
return objectAPI, cred
}
// RemoveUser - DELETE /minio/admin/v2/remove-user?accessKey=<access_key>
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RemoveUser")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
if objectAPI == nil {
return
}
@ -83,7 +87,7 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListUsers")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUsersAdminAction)
if objectAPI == nil {
return
}
@ -114,7 +118,7 @@ func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetUserInfo")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetUserAdminAction)
if objectAPI == nil {
return
}
@ -141,7 +145,7 @@ func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "UpdateGroupMembers")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
if objectAPI == nil {
return
}
@ -184,7 +188,7 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetGroup")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetGroupAdminAction)
if objectAPI == nil {
return
}
@ -211,7 +215,7 @@ func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListGroups")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
if objectAPI == nil {
return
}
@ -235,7 +239,7 @@ func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetGroupStatus")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
if objectAPI == nil {
return
}
@ -270,7 +274,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetUserStatus")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.EnableUserAdminAction)
if objectAPI == nil {
return
}
@ -309,7 +313,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddUser")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.CreateUserAdminAction)
if objectAPI == nil {
return
}
@ -335,7 +339,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
return
}
password := globalActiveCred.SecretKey
password := cred.SecretKey
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
logger.LogIf(ctx, err)
@ -368,7 +372,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "InfoCannedPolicy")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
if objectAPI == nil {
return
}
@ -387,7 +391,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListCannedPolicies")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
if objectAPI == nil {
return
}
@ -410,7 +414,7 @@ func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "RemoveCannedPolicy")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
if objectAPI == nil {
return
}
@ -442,7 +446,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddCannedPolicy")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
if objectAPI == nil {
return
}
@ -498,7 +502,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
objectAPI := validateAdminUsersReq(ctx, w, r)
objectAPI, _ := validateAdminUsersReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
if objectAPI == nil {
return
}

@ -39,8 +39,10 @@ import (
"github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/cpu"
"github.com/minio/minio/pkg/handlers"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/mem"
xnet "github.com/minio/minio/pkg/net"
@ -98,7 +100,7 @@ func updateServer(updateURL, sha256Hex string, latestReleaseTime time.Time) (us
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ServerUpdate")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerUpdateAdminAction)
if objectAPI == nil {
return
}
@ -180,7 +182,7 @@ func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Re
vars := mux.Vars(r)
action := vars["action"]
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, "")
if objectAPI == nil {
return
}
@ -265,7 +267,7 @@ type ServerInfo struct {
// Get server information
func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ServerInfo")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -306,7 +308,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
// Get server information
func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "StorageInfo")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -360,7 +362,7 @@ type ServerNetReadPerfInfo struct {
func (a adminAPIHandlers) PerfInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PerfInfo")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -531,7 +533,7 @@ type PeerLocks struct {
func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "TopLocks")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -576,7 +578,7 @@ type StartProfilingResult struct {
func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "StartProfiling")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -658,7 +660,7 @@ func (f dummyFileInfo) Sys() interface{} { return f.sys }
func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DownloadProfiling")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -750,7 +752,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "Heal")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
if objectAPI == nil {
return
}
@ -894,7 +896,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "HealBackgroundStatus")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
if objectAPI == nil {
return
}
@ -934,22 +936,24 @@ func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *
w.(http.Flusher).Flush()
}
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request) ObjectLayer {
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, action iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
var cred auth.Credentials
var adminAPIErr APIErrorCode
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return nil
return nil, cred
}
// Validate request signature.
adminAPIErr := checkAdminRequestAuthType(ctx, r, "")
cred, adminAPIErr = checkAdminRequestAuthType(ctx, r, action, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return nil
return nil, cred
}
return objectAPI
return objectAPI, cred
}
// AdminError - is a generic error for all admin APIs.
@ -1040,7 +1044,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
trcErr := r.URL.Query().Get("err") == "true"
// Validate request signature.
adminAPIErr := checkAdminRequestAuthType(ctx, r, "")
_, adminAPIErr := checkAdminRequestAuthType(ctx, r, iampolicy.ListServerInfoAdminAction, "")
if adminAPIErr != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
return
@ -1094,7 +1098,7 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ConsoleLog")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -1165,7 +1169,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "KMSKeyStatusHandler")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}
@ -1250,7 +1254,7 @@ func (a adminAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Req
func (a adminAPIHandlers) ServerHardwareInfoHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "HardwareInfo")
objectAPI := validateAdminReq(ctx, w, r)
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListServerInfoAdminAction)
if objectAPI == nil {
return
}

@ -120,20 +120,16 @@ func getRequestAuthType(r *http.Request) authType {
// checkAdminRequestAuthType checks whether the request is a valid signature V2 or V4 request.
// It does not accept presigned or JWT or anonymous requests.
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, region string) APIErrorCode {
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
var cred auth.Credentials
var owner bool
s3Err := ErrAccessDenied
if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
getRequestAuthType(r) == authTypeSigned && !skipContentSha256Cksum(r) {
// We only support admin credentials to access admin APIs.
var owner bool
_, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
if s3Err != ErrNone {
return s3Err
}
if !owner {
return ErrAccessDenied
return cred, s3Err
}
// we only support V4 (no presign) with auth body
@ -144,7 +140,25 @@ func checkAdminRequestAuthType(ctx context.Context, r *http.Request, region stri
ctx := logger.SetReqInfo(ctx, reqInfo)
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
}
return s3Err
var claims map[string]interface{}
claims, s3Err = checkClaimsFromToken(r, cred)
if s3Err != ErrNone {
return cred, s3Err
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Action: iampolicy.Action(action),
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
IsOwner: owner,
Claims: claims,
}) {
// Request is allowed return the appropriate access key.
return cred, ErrNone
}
return cred, ErrAccessDenied
}
// Fetch the security token set by the client.

@ -28,6 +28,7 @@ import (
"time"
"github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy"
)
// Test get request auth type.
@ -419,7 +420,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
}
ctx := context.Background()
for i, testCase := range testCases {
if s3Error := checkAdminRequestAuthType(ctx, testCase.Request, globalServerRegion); s3Error != testCase.ErrCode {
if _, s3Error := checkAdminRequestAuthType(ctx, testCase.Request, iampolicy.AllAdminActions, globalServerRegion); s3Error != testCase.ErrCode {
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
}
}

@ -0,0 +1,114 @@
# MinIO Admin Multi-user Quickstart Guide [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)
MinIO supports multiple admin users in addition to default operator credential created during server startup. New admins can be added after server starts up, and server can be configured to deny or allow access to different admin operations for these users. This document explains how to add/remove admin users and modify their access rights.
## Get started
In this document we will explain in detail on how to configure admin users.
### 1. Prerequisites
- Install mc - [MinIO Client Quickstart Guide](https://docs.min.io/docs/minio-client-quickstart-guide.html)
- Install MinIO - [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide)
### 2. Create a new admin user with CreateUser, DeleteUser and ConfigUpdate permissions
Use [`mc admin policy`](https://docs.min.io/docs/minio-admin-complete-guide.html#policies) to create custom admin policies.
Create new canned policy file `adminManageUser.json`. This policy enables admin user to
manage other users.
```json
cat > adminManageUser.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"admin:CreateUser",
"admin:DeleteUser",
"admin:ConfigUpdate"
],
"Effect": "Allow",
"Sid": ""
},
{
"Action": [
"s3:*"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::*"
],
"Sid": ""
}
]
}
EOF
```
Create new canned policy by name `userManager` using `userManager.json` policy file.
```
mc admin policy add myminio userManager adminManageUser.json
```
Create a new admin user `admin1` on MinIO use `mc admin user`.
```
mc admin user add myminio admin1 admin123
```
Once the user is successfully created you can now apply the `userManage` policy for this user.
```
mc admin policy set myminio userManager user=admin1
```
This admin user will then be allowed to perform create/delete user operations via `mc admin user`
### 3. Configure `mc` and create another user user1 with attached policy user1policy
```
mc config host add myminio-admin1 http://localhost:9000 admin1 admin123 --api s3v4
mc admin user add myminio-admin1 user1 user123
mc admin policy add myminio-admin1 user1policy ~/user1policy.json
mc admin policy set myminio-admin1 user1policy user=user1
```
### 4. List of permissions defined for admin operations
#### Config management permissions
- admin:ConfigUpdate
#### User management permissions
- admin:CreateUser
- admin:DeleteUser
- admin:ListUsers
- admin:EnableUser
- admin:DisableUser
- admin:GetUser
#### Service management permissions
- admin:ListServerInfo
- admin:ServerUpdate
#### User/Group management permissions
- admin:AddUserToGroup
- admin:RemoveUserFromGroup
- admin:GetGroup
- admin:ListGroups
- admin:EnableGroup
- admin:DisableGroup
#### Policy management permissions
- admin:CreatePolicy
- admin:DeletePolicy
- admin:GetPolicy
- admin:AttachUserOrGroupPolicy
- admin:ListUserPolicies
#### Give full admin permissions
- admin:*
### 5. Using an external IDP for admin users
Admin users can also be externally managed by an IDP by configuring admin policy with
special permissions listed above. Follow [MinIO STS Quickstart Guide](https://docs.min.io/docs/minio-sts-quickstart-guide) to manage users with an IDP.
## Explore Further
- [MinIO Client Complete Guide](https://docs.min.io/docs/minio-client-complete-guide)
- [MinIO STS Quickstart Guide](https://docs.min.io/docs/minio-sts-quickstart-guide)
- [MinIO Admin Complete Guide](https://docs.min.io/docs/minio-admin-complete-guide.html)
- [The MinIO documentation website](https://docs.min.io)

@ -145,7 +145,7 @@ func (action Action) IsValid() bool {
// MarshalJSON - encodes Action to JSON data.
func (action Action) MarshalJSON() ([]byte, error) {
if action.IsValid() {
if action.IsValid() || AdminAction(action).IsValid() {
return json.Marshal(string(action))
}
@ -171,6 +171,10 @@ func (action *Action) UnmarshalJSON(data []byte) error {
}
func parseAction(s string) (Action, error) {
adminAction, err := parseAdminAction(s)
if err == nil {
return Action(adminAction), nil
}
action := Action(s)
if action.IsValid() {

@ -0,0 +1,154 @@
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package iampolicy
import (
"fmt"
"github.com/minio/minio/pkg/policy/condition"
)
// AdminAction - admin policy action.
type AdminAction string
const (
// HealAdminAction - allows heal command
HealAdminAction = "admin:Heal"
// Service Actions
// ListServerInfoAdminAction - allow listing server info
ListServerInfoAdminAction = "admin:ListServerInfo"
// ServerUpdateAdminAction - allow MinIO binary update
ServerUpdateAdminAction = "admin:ServerUpdate"
//Config Actions
// ConfigUpdateAdminAction - allow MinIO config management
ConfigUpdateAdminAction = "admin:ConfigUpdate"
// User Actions
// CreateUserAdminAction - allow creating MinIO user
CreateUserAdminAction = "admin:CreateUser"
// DeleteUserAdminAction - allow deleting MinIO user
DeleteUserAdminAction = "admin:DeleteUser"
// ListUsersAdminAction - allow list users permission
ListUsersAdminAction = "admin:ListUsers"
// EnableUserAdminAction - allow enable user permission
EnableUserAdminAction = "admin:EnableUser"
// DisableUserAdminAction - allow disable user permission
DisableUserAdminAction = "admin:DisableUser"
// GetUserAdminAction - allows GET permission on user info
GetUserAdminAction = "admin:GetUser"
// Group Actions
// AddUserToGroupAdminAction - allow adding user to group permission
AddUserToGroupAdminAction = "admin:AddUserToGroup"
// RemoveUserFromGroupAdminAction - allow removing user to group permission
RemoveUserFromGroupAdminAction = "admin:RemoveUserFromGroup"
// GetGroupAdminAction - allow getting group info
GetGroupAdminAction = "admin:GetGroup"
// ListGroupsAdminAction - allow list groups permission
ListGroupsAdminAction = "admin:ListGroups"
// EnableGroupAdminAction - allow enable group permission
EnableGroupAdminAction = "admin:EnableGroup"
// DisableGroupAdminAction - allow disable group permission
DisableGroupAdminAction = "admin:DisableGroup"
// Policy Actions
// CreatePolicyAdminAction - allow create policy permission
CreatePolicyAdminAction = "admin:CreatePolicy"
// DeletePolicyAdminAction - allow delete policy permission
DeletePolicyAdminAction = "admin:DeletePolicy"
// GetPolicyAdminAction - allow get policy permission
GetPolicyAdminAction = "admin:GetPolicy"
// AttachPolicyAdminAction - allows attaching a policy to a user/group
AttachPolicyAdminAction = "admin:AttachUserOrGroupPolicy"
// ListUserPoliciesAdminAction - allows listing user policies
ListUserPoliciesAdminAction = "admin:ListUserPolicies"
// AllAdminActions - provides all admin permissions
AllAdminActions = "admin:*"
)
// List of all supported admin actions.
var supportedAdminActions = map[AdminAction]struct{}{
AllAdminActions: {},
HealAdminAction: {},
ListServerInfoAdminAction: {},
ServerUpdateAdminAction: {},
ConfigUpdateAdminAction: {},
CreateUserAdminAction: {},
DeleteUserAdminAction: {},
ListUsersAdminAction: {},
EnableUserAdminAction: {},
DisableUserAdminAction: {},
GetUserAdminAction: {},
AddUserToGroupAdminAction: {},
RemoveUserFromGroupAdminAction: {},
ListGroupsAdminAction: {},
EnableGroupAdminAction: {},
DisableGroupAdminAction: {},
CreatePolicyAdminAction: {},
DeletePolicyAdminAction: {},
GetPolicyAdminAction: {},
AttachPolicyAdminAction: {},
ListUserPoliciesAdminAction: {},
}
func parseAdminAction(s string) (AdminAction, error) {
action := AdminAction(s)
if action.IsValid() {
return action, nil
}
return action, fmt.Errorf("unsupported action '%v'", s)
}
// IsValid - checks if action is valid or not.
func (action AdminAction) IsValid() bool {
_, ok := supportedAdminActions[action]
return ok
}
// adminActionConditionKeyMap - holds mapping of supported condition key for an action.
var adminActionConditionKeyMap = map[Action]condition.KeySet{
AllAdminActions: condition.NewKeySet(condition.AllSupportedAdminKeys...),
HealAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
ListServerInfoAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
ServerUpdateAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
ConfigUpdateAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
CreateUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
DeleteUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
ListUsersAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
EnableUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
DisableUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
GetUserAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
AddUserToGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
RemoveUserFromGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
ListGroupsAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
EnableGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
DisableGroupAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
CreatePolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
DeletePolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
GetPolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
AttachPolicyAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
ListUserPoliciesAdminAction: condition.NewKeySet(condition.AllSupportedAdminKeys...),
}

@ -30,7 +30,7 @@ type Statement struct {
SID policy.ID `json:"Sid,omitempty"`
Effect policy.Effect `json:"Effect"`
Actions ActionSet `json:"Action"`
Resources ResourceSet `json:"Resource"`
Resources ResourceSet `json:"Resource,omitempty"`
Conditions condition.Functions `json:"Condition,omitempty"`
}
@ -52,7 +52,8 @@ func (statement Statement) IsAllowed(args Args) bool {
resource += "/"
}
if !statement.Resources.Match(resource, args.ConditionValues) {
// For admin statements, resource match can be ignored.
if !statement.Resources.Match(resource, args.ConditionValues) && !statement.isAdmin() {
return false
}
@ -61,6 +62,14 @@ func (statement Statement) IsAllowed(args Args) bool {
return statement.Effect.IsAllowed(check())
}
func (statement Statement) isAdmin() bool {
for action := range statement.Actions {
if !AdminAction(action).IsValid() {
return false
}
}
return true
}
// isValid - checks whether statement is valid or not.
func (statement Statement) isValid() error {
@ -72,6 +81,17 @@ func (statement Statement) isValid() error {
return fmt.Errorf("Action must not be empty")
}
if statement.isAdmin() {
for action := range statement.Actions {
keys := statement.Conditions.Keys()
keyDiff := keys.Difference(adminActionConditionKeyMap[action])
if !keyDiff.IsEmpty() {
return fmt.Errorf("unsupported condition keys '%v' used for action '%v'", keyDiff, action)
}
}
return nil
}
if len(statement.Resources) == 0 {
return fmt.Errorf("Resource must not be empty")
}

@ -183,6 +183,14 @@ func TestStatementIsValid(t *testing.T) {
t.Fatalf("unexpected error. %v\n", err)
}
func3, err := condition.NewStringEqualsFunc(
condition.AWSUserAgent,
"NSPlayer",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
statement Statement
expectErr bool
@ -233,6 +241,18 @@ func TestStatementIsValid(t *testing.T) {
NewResourceSet(NewResource("mybucket", "myobject*")),
condition.NewFunctions(func1),
), false},
{NewStatement(
policy.Allow,
NewActionSet(CreateUserAdminAction, DeleteUserAdminAction),
nil,
condition.NewFunctions(func2, func3),
), true},
{NewStatement(
policy.Allow,
NewActionSet(CreateUserAdminAction, DeleteUserAdminAction),
nil,
condition.NewFunctions(),
), false},
}
for i, testCase := range testCases {

@ -272,3 +272,14 @@ func NewKeySet(keys ...Key) KeySet {
return set
}
// AllSupportedAdminKeys - is list of all admin supported keys.
var AllSupportedAdminKeys = []Key{
AWSReferer,
AWSSourceIP,
AWSUserAgent,
AWSSecureTransport,
AWSCurrentTime,
AWSEpochTime,
// Add new supported condition keys.
}

Loading…
Cancel
Save