2021-04-18 15:41:13 -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/>.
|
2019-11-09 12:27:23 -05:00
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
2021-05-06 11:52:02 -04:00
|
|
|
"bytes"
|
2019-11-09 12:27:23 -05:00
|
|
|
"encoding/json"
|
2021-03-23 20:39:20 -04:00
|
|
|
"errors"
|
2019-11-09 12:27:23 -05:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
2021-02-09 12:53:07 -05:00
|
|
|
"sort"
|
2019-11-09 12:27:23 -05:00
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
2021-05-06 11:52:02 -04:00
|
|
|
"github.com/minio/madmin-go"
|
2021-10-06 19:36:31 -04:00
|
|
|
"github.com/minio/minio/internal/auth"
|
2021-06-01 17:59:40 -04:00
|
|
|
"github.com/minio/minio/internal/config/dns"
|
|
|
|
"github.com/minio/minio/internal/logger"
|
2021-05-30 00:16:42 -04:00
|
|
|
iampolicy "github.com/minio/pkg/iam/policy"
|
2019-11-09 12:27:23 -05:00
|
|
|
)
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "RemoveUser")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DeleteUserAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
accessKey := vars["accessKey"]
|
|
|
|
|
2021-02-25 16:49:59 -05:00
|
|
|
ok, _, err := globalIAMSys.IsTempUser(accessKey)
|
2019-12-19 17:21:21 -05:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if ok {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
if err := globalIAMSys.DeleteUser(ctx, accessKey); err != nil {
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other MinIO peers to delete user.
|
|
|
|
for _, nerr := range globalNotificationSys.DeleteUser(accessKey) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-27 13:15:02 -04:00
|
|
|
// 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))
|
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
2021-05-27 13:15:02 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// ListUsers - GET /minio/admin/v3/list-users
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "ListUsers")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ListUsersAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-03-23 17:06:00 -04:00
|
|
|
password := cred.SecretKey
|
|
|
|
|
2019-11-09 12:27:23 -05:00
|
|
|
allCredentials, err := globalIAMSys.ListUsers()
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// GetUserInfo - GET /minio/admin/v3/user-info
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "GetUserInfo")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
vars := mux.Vars(r)
|
|
|
|
name := vars["accessKey"]
|
|
|
|
|
|
|
|
// Get current object layer instance.
|
|
|
|
objectAPI := newObjectLayerFn()
|
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
2019-11-09 12:27:23 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-15 21:52:01 -04:00
|
|
|
accessKey := cred.ParentUser
|
|
|
|
if accessKey == "" {
|
|
|
|
accessKey = cred.AccessKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// For temporary credentials always
|
|
|
|
// the temporary credentials to check
|
|
|
|
// policy without implicit permissions.
|
|
|
|
if cred.IsTemp() && cred.ParentUser == globalActiveCred.AccessKey {
|
|
|
|
accessKey = cred.AccessKey
|
2020-11-27 20:23:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
implicitPerm := name == accessKey
|
|
|
|
if !implicitPerm {
|
|
|
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
|
|
|
AccountName: accessKey,
|
2021-03-23 18:15:51 -04:00
|
|
|
Groups: cred.Groups,
|
2020-11-27 20:23:57 -05:00
|
|
|
Action: iampolicy.GetUserAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", accessKey, claims),
|
|
|
|
IsOwner: owner,
|
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
userInfo, err := globalIAMSys.GetUserInfo(ctx, name)
|
2019-11-09 12:27:23 -05:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := json.Marshal(userInfo)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeSuccessResponseJSON(w, data)
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// UpdateGroupMembers - PUT /minio/admin/v3/update-group-members
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "UpdateGroupMembers")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AddUserToGroupAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var updReq madmin.GroupAddRemove
|
|
|
|
err = json.Unmarshal(data, &updReq)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if updReq.IsRemove {
|
2021-11-15 17:14:22 -05:00
|
|
|
err = globalIAMSys.RemoveUsersFromGroup(ctx, updReq.Group, updReq.Members)
|
2019-11-09 12:27:23 -05:00
|
|
|
} else {
|
2021-11-15 17:14:22 -05:00
|
|
|
err = globalIAMSys.AddUsersToGroup(ctx, updReq.Group, updReq.Members)
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other MinIO peers to load group.
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadGroup(updReq.Group) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// GetGroup - /minio/admin/v3/group?group=mygroup1
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) GetGroup(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "GetGroup")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetGroupAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
group := vars["group"]
|
|
|
|
|
|
|
|
gdesc, err := globalIAMSys.GetGroupDescription(group)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := json.Marshal(gdesc)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeSuccessResponseJSON(w, body)
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// ListGroups - GET /minio/admin/v3/groups
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) ListGroups(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "ListGroups")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListGroupsAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
groups, err := globalIAMSys.ListGroups(ctx)
|
2019-11-09 12:27:23 -05:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := json.Marshal(groups)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeSuccessResponseJSON(w, body)
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// SetGroupStatus - PUT /minio/admin/v3/set-group-status?group=mygroup1&status=enabled
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "SetGroupStatus")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.EnableGroupAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
group := vars["group"]
|
|
|
|
status := vars["status"]
|
|
|
|
|
|
|
|
var err error
|
|
|
|
if status == statusEnabled {
|
2021-11-15 17:14:22 -05:00
|
|
|
err = globalIAMSys.SetGroupStatus(ctx, group, true)
|
2019-11-09 12:27:23 -05:00
|
|
|
} else if status == statusDisabled {
|
2021-11-15 17:14:22 -05:00
|
|
|
err = globalIAMSys.SetGroupStatus(ctx, group, false)
|
2019-11-09 12:27:23 -05:00
|
|
|
} else {
|
|
|
|
err = errInvalidArgument
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other MinIO peers to reload user.
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadGroup(group) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// SetUserStatus - PUT /minio/admin/v3/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "SetUserStatus")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.EnableUserAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
accessKey := vars["accessKey"]
|
|
|
|
status := vars["status"]
|
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
// This API is not allowed to lookup accessKey user status
|
2019-11-09 12:27:23 -05:00
|
|
|
if accessKey == globalActiveCred.AccessKey {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
if err := globalIAMSys.SetUserStatus(ctx, accessKey, madmin.AccountStatus(status)); err != nil {
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other MinIO peers to reload user.
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// AddUser - PUT /minio/admin/v3/add-user?accessKey=<access_key>
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AddUser")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
vars := mux.Vars(r)
|
2021-04-15 19:32:13 -04:00
|
|
|
accessKey := vars["accessKey"]
|
2020-11-27 20:23:57 -05:00
|
|
|
|
|
|
|
// Get current object layer instance.
|
|
|
|
objectAPI := newObjectLayerFn()
|
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
2019-11-09 12:27:23 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
|
2020-12-19 16:24:21 -05:00
|
|
|
// Not allowed to add a user with same access key as root credential
|
|
|
|
if owner && accessKey == cred.AccessKey {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
2020-11-27 20:23:57 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-12-19 16:24:21 -05:00
|
|
|
if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
|
|
|
|
// Incoming access key matches parent user then we should
|
|
|
|
// reject password change requests.
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserInvalidArgument), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
implicitPerm := accessKey == cred.AccessKey
|
|
|
|
if !implicitPerm {
|
2020-12-19 16:24:21 -05:00
|
|
|
parentUser := cred.ParentUser
|
|
|
|
if parentUser == "" {
|
|
|
|
parentUser = cred.AccessKey
|
|
|
|
}
|
2021-06-15 21:52:01 -04:00
|
|
|
// For temporary credentials always
|
|
|
|
// the temporary credentials to check
|
|
|
|
// policy without implicit permissions.
|
|
|
|
if cred.IsTemp() && cred.ParentUser == globalActiveCred.AccessKey {
|
|
|
|
parentUser = cred.AccessKey
|
|
|
|
}
|
2020-11-27 20:23:57 -05:00
|
|
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
2020-12-19 16:24:21 -05:00
|
|
|
AccountName: parentUser,
|
2021-03-23 18:15:51 -04:00
|
|
|
Groups: cred.Groups,
|
2020-11-27 20:23:57 -05:00
|
|
|
Action: iampolicy.CreateUserAdminAction,
|
2020-12-19 16:24:21 -05:00
|
|
|
ConditionValues: getConditionValues(r, "", parentUser, claims),
|
2021-03-02 20:02:29 -05:00
|
|
|
IsOwner: owner,
|
2020-11-27 20:23:57 -05:00
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-02 18:35:50 -05:00
|
|
|
if implicitPerm && !globalIAMSys.IsAllowed(iampolicy.Args{
|
|
|
|
AccountName: accessKey,
|
2021-03-23 18:15:51 -04:00
|
|
|
Groups: cred.Groups,
|
2021-03-02 18:35:50 -05:00
|
|
|
Action: iampolicy.CreateUserAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", accessKey, claims),
|
2021-03-02 20:02:29 -05:00
|
|
|
IsOwner: owner,
|
2021-03-02 18:35:50 -05:00
|
|
|
Claims: claims,
|
|
|
|
DenyOnly: true, // check if changing password is explicitly denied.
|
|
|
|
}) {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-09 12:27:23 -05:00
|
|
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
|
|
|
// More than maxConfigSize bytes were available
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-19 05:03:18 -05:00
|
|
|
password := cred.SecretKey
|
2019-11-09 12:27:23 -05:00
|
|
|
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
|
|
|
if err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var uinfo madmin.UserInfo
|
|
|
|
if err = json.Unmarshal(configBytes, &uinfo); err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
if err = globalIAMSys.CreateUser(ctx, accessKey, uinfo); err != nil {
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other Minio peers to reload user
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadUser(accessKey, false) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-14 14:28:56 -04:00
|
|
|
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
2020-03-17 13:36:13 -04:00
|
|
|
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AddServiceAccount")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2020-04-14 14:28:56 -04:00
|
|
|
// Get current object layer instance.
|
2020-10-09 12:59:52 -04:00
|
|
|
objectAPI := newObjectLayerFn()
|
2020-10-19 12:54:40 -04:00
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
2020-04-14 14:28:56 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
2020-03-17 13:36:13 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
2020-04-14 14:28:56 -04:00
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
2020-03-17 13:36:13 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
password := cred.SecretKey
|
2020-04-24 15:10:09 -04:00
|
|
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
2020-03-17 13:36:13 -04:00
|
|
|
if err != nil {
|
2020-04-14 14:28:56 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
2020-03-17 13:36:13 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var createReq madmin.AddServiceAccountReq
|
2020-04-24 15:10:09 -04:00
|
|
|
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
2020-04-14 14:28:56 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
2020-03-17 13:36:13 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
var (
|
|
|
|
targetUser string
|
|
|
|
targetGroups []string
|
|
|
|
)
|
|
|
|
|
2021-09-20 17:28:19 -04:00
|
|
|
// If the request did not set a TargetUser, the service account is
|
|
|
|
// created for the request sender.
|
2021-04-15 01:51:14 -04:00
|
|
|
targetUser = createReq.TargetUser
|
2021-09-20 17:28:19 -04:00
|
|
|
if targetUser == "" {
|
|
|
|
targetUser = cred.AccessKey
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := newServiceAccountOpts{
|
|
|
|
accessKey: createReq.AccessKey,
|
|
|
|
secretKey: createReq.SecretKey,
|
2021-10-05 14:49:33 -04:00
|
|
|
claims: make(map[string]interface{}),
|
2021-09-20 17:28:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find the user for the request sender (as it may be sent via a service
|
|
|
|
// account or STS account):
|
|
|
|
requestorUser := cred.AccessKey
|
|
|
|
requestorParentUser := cred.AccessKey
|
|
|
|
requestorGroups := cred.Groups
|
|
|
|
requestorIsDerivedCredential := false
|
|
|
|
if cred.IsServiceAccount() || cred.IsTemp() {
|
|
|
|
requestorParentUser = cred.ParentUser
|
|
|
|
requestorIsDerivedCredential = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we are creating svc account for request sender.
|
|
|
|
isSvcAccForRequestor := false
|
|
|
|
if targetUser == requestorUser || targetUser == requestorParentUser {
|
|
|
|
isSvcAccForRequestor = true
|
|
|
|
}
|
2021-04-15 01:51:14 -04:00
|
|
|
|
2021-09-20 17:28:19 -04:00
|
|
|
// If we are creating svc account for request sender, ensure
|
|
|
|
// that targetUser is a real user (i.e. not derived
|
|
|
|
// credentials).
|
|
|
|
if isSvcAccForRequestor {
|
2021-11-15 09:57:52 -05:00
|
|
|
// Check if adding service account is explicitly denied.
|
|
|
|
//
|
|
|
|
// This allows turning off service accounts for request sender,
|
|
|
|
// if there is no deny statement this call is implicitly enabled.
|
|
|
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
|
|
|
AccountName: requestorUser,
|
|
|
|
Action: iampolicy.CreateServiceAccountAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
|
|
|
IsOwner: owner,
|
|
|
|
Claims: claims,
|
|
|
|
DenyOnly: true,
|
|
|
|
}) {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-09-20 17:28:19 -04:00
|
|
|
if requestorIsDerivedCredential {
|
|
|
|
if requestorParentUser == "" {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
|
|
|
errors.New("service accounts cannot be generated for temporary credentials without parent")), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
targetUser = requestorParentUser
|
|
|
|
}
|
|
|
|
targetGroups = requestorGroups
|
|
|
|
|
2021-10-05 14:49:33 -04:00
|
|
|
// In case of LDAP/OIDC we need to set `opts.claims` to ensure
|
|
|
|
// it is associated with the LDAP/OIDC user properly.
|
|
|
|
for k, v := range cred.Claims {
|
|
|
|
if k == expClaim {
|
|
|
|
continue
|
2021-09-20 17:28:19 -04:00
|
|
|
}
|
2021-10-05 14:49:33 -04:00
|
|
|
opts.claims[k] = v
|
2021-09-20 17:28:19 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Need permission if we are creating a service acccount for a
|
|
|
|
// user <> to the request sender
|
2021-04-15 01:51:14 -04:00
|
|
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
2021-09-20 17:28:19 -04:00
|
|
|
AccountName: requestorUser,
|
2021-04-15 01:51:14 -04:00
|
|
|
Action: iampolicy.CreateServiceAccountAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
|
|
|
IsOwner: owner,
|
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2020-04-28 15:49:56 -04:00
|
|
|
|
2021-09-20 17:28:19 -04:00
|
|
|
// In case of LDAP we need to resolve the targetUser to a DN and
|
|
|
|
// query their groups:
|
|
|
|
if globalLDAPConfig.Enabled {
|
2021-10-05 14:49:33 -04:00
|
|
|
opts.claims[ldapUserN] = targetUser // simple username
|
2021-09-20 17:28:19 -04:00
|
|
|
targetUser, targetGroups, err = globalLDAPConfig.LookupUserDN(targetUser)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
2021-04-29 16:01:42 -04:00
|
|
|
return
|
|
|
|
}
|
2021-10-05 14:49:33 -04:00
|
|
|
opts.claims[ldapUser] = targetUser // username DN
|
2021-05-13 00:25:07 -04:00
|
|
|
}
|
2021-09-20 17:28:19 -04:00
|
|
|
|
|
|
|
// NOTE: if not using LDAP, then internal IDP or open ID is
|
|
|
|
// being used - in the former, group info is enforced when
|
|
|
|
// generated credentials are used to make requests, and in the
|
|
|
|
// latter, a group notion is not supported.
|
2021-04-15 01:51:14 -04:00
|
|
|
}
|
|
|
|
|
2021-05-06 11:52:02 -04:00
|
|
|
var sp *iampolicy.Policy
|
|
|
|
if len(createReq.Policy) > 0 {
|
|
|
|
sp, err = iampolicy.ParseConfig(bytes.NewReader(createReq.Policy))
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-20 17:28:19 -04:00
|
|
|
opts.sessionPolicy = sp
|
2021-04-15 01:51:14 -04:00
|
|
|
newCred, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
2020-03-17 13:36:13 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
// Notify all other Minio peers to reload user the service account
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadServiceAccount(newCred.AccessKey) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2020-03-17 13:36:13 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-06 19:36:31 -04:00
|
|
|
// Call hook for cluster-replication.
|
|
|
|
//
|
|
|
|
// FIXME: This wont work in an OpenID situation as the parent credential
|
|
|
|
// may not be present on peer clusters to provide inherited policies.
|
|
|
|
// Also, we should not be replicating root user's service account - as
|
|
|
|
// they are not authenticated by a common external IDP, so we skip when
|
|
|
|
// opts.ldapUser == "".
|
|
|
|
if _, isLDAPAccount := opts.claims[ldapUserN]; isLDAPAccount {
|
|
|
|
err = globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemSvcAcc,
|
|
|
|
SvcAccChange: &madmin.SRSvcAccChange{
|
|
|
|
Create: &madmin.SRSvcAccCreate{
|
|
|
|
Parent: newCred.ParentUser,
|
|
|
|
AccessKey: newCred.AccessKey,
|
|
|
|
SecretKey: newCred.SecretKey,
|
|
|
|
Groups: newCred.Groups,
|
|
|
|
Claims: opts.claims,
|
|
|
|
SessionPolicy: createReq.Policy,
|
|
|
|
Status: auth.AccountOn,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-17 13:36:13 -04:00
|
|
|
var createResp = madmin.AddServiceAccountResp{
|
2021-05-06 11:52:02 -04:00
|
|
|
Credentials: madmin.Credentials{
|
2020-04-24 15:10:09 -04:00
|
|
|
AccessKey: newCred.AccessKey,
|
|
|
|
SecretKey: newCred.SecretKey,
|
2020-04-14 14:28:56 -04:00
|
|
|
},
|
2020-03-17 13:36:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
data, err := json.Marshal(createResp)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
encryptedData, err := madmin.EncryptData(password, data)
|
2020-03-17 13:36:13 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
writeSuccessResponseJSON(w, encryptedData)
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
// UpdateServiceAccount - POST /minio/admin/v3/update-service-account
|
|
|
|
func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "UpdateServiceAccount")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
|
|
|
// Get current object layer instance.
|
|
|
|
objectAPI := newObjectLayerFn()
|
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
accessKey := mux.Vars(r)["accessKey"]
|
|
|
|
if accessKey == "" {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
|
|
|
AccountName: cred.AccessKey,
|
|
|
|
Action: iampolicy.UpdateServiceAccountAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
|
|
|
IsOwner: owner,
|
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
requestUser := cred.AccessKey
|
|
|
|
if cred.ParentUser != "" {
|
|
|
|
requestUser = cred.ParentUser
|
|
|
|
}
|
|
|
|
|
|
|
|
if requestUser != svcAccount.ParentUser {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
password := cred.SecretKey
|
|
|
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var updateReq madmin.UpdateServiceAccountReq
|
|
|
|
if err = json.Unmarshal(reqBytes, &updateReq); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-06 11:52:02 -04:00
|
|
|
var sp *iampolicy.Policy
|
|
|
|
if len(updateReq.NewPolicy) > 0 {
|
|
|
|
sp, err = iampolicy.ParseConfig(bytes.NewReader(updateReq.NewPolicy))
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
opts := updateServiceAccountOpts{
|
|
|
|
secretKey: updateReq.NewSecretKey,
|
|
|
|
status: updateReq.NewStatus,
|
|
|
|
sessionPolicy: sp,
|
|
|
|
}
|
2021-04-15 01:51:14 -04:00
|
|
|
err = globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other Minio peers to reload user the service account
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadServiceAccount(accessKey) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2021-04-15 01:51:14 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-06 19:36:31 -04:00
|
|
|
// Call site replication hook. Only LDAP accounts are supported for
|
|
|
|
// replication operations.
|
|
|
|
svcAccClaims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, accessKey)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, isLDAPAccount := svcAccClaims[ldapUserN]; isLDAPAccount {
|
|
|
|
err = globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemSvcAcc,
|
|
|
|
SvcAccChange: &madmin.SRSvcAccChange{
|
|
|
|
Update: &madmin.SRSvcAccUpdate{
|
|
|
|
AccessKey: accessKey,
|
|
|
|
SecretKey: opts.secretKey,
|
|
|
|
Status: opts.status,
|
|
|
|
SessionPolicy: updateReq.NewPolicy,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2021-04-15 01:51:14 -04:00
|
|
|
writeSuccessNoContent(w)
|
|
|
|
}
|
|
|
|
|
|
|
|
// InfoServiceAccount - GET /minio/admin/v3/info-service-account
|
|
|
|
func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "InfoServiceAccount")
|
|
|
|
|
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
|
|
|
|
|
|
// Get current object layer instance.
|
|
|
|
objectAPI := newObjectLayerFn()
|
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
accessKey := mux.Vars(r)["accessKey"]
|
|
|
|
if accessKey == "" {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
svcAccount, policy, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
|
|
|
AccountName: cred.AccessKey,
|
|
|
|
Action: iampolicy.ListServiceAccountsAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
|
|
|
IsOwner: owner,
|
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
requestUser := cred.AccessKey
|
|
|
|
if cred.ParentUser != "" {
|
|
|
|
requestUser = cred.ParentUser
|
|
|
|
}
|
|
|
|
|
|
|
|
if requestUser != svcAccount.ParentUser {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var svcAccountPolicy iampolicy.Policy
|
|
|
|
|
|
|
|
impliedPolicy := policy == nil
|
|
|
|
|
|
|
|
// If policy is empty, check for policy of the parent user
|
|
|
|
if !impliedPolicy {
|
2021-04-15 17:47:58 -04:00
|
|
|
svcAccountPolicy = svcAccountPolicy.Merge(*policy)
|
2021-04-15 01:51:14 -04:00
|
|
|
} else {
|
2021-04-21 21:12:02 -04:00
|
|
|
policiesNames, err := globalIAMSys.PolicyDBGet(svcAccount.ParentUser, false)
|
2021-04-15 01:51:14 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2021-04-15 17:47:58 -04:00
|
|
|
svcAccountPolicy = svcAccountPolicy.Merge(globalIAMSys.GetCombinedPolicy(policiesNames...))
|
2021-04-15 01:51:14 -04:00
|
|
|
}
|
|
|
|
|
2021-05-29 12:22:22 -04:00
|
|
|
policyJSON, err := json.MarshalIndent(svcAccountPolicy, "", " ")
|
2021-04-15 01:51:14 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var infoResp = madmin.InfoServiceAccountResp{
|
|
|
|
ParentUser: svcAccount.ParentUser,
|
|
|
|
AccountStatus: svcAccount.Status,
|
|
|
|
ImpliedPolicy: impliedPolicy,
|
|
|
|
Policy: string(policyJSON),
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := json.Marshal(infoResp)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeSuccessResponseJSON(w, encryptedData)
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
// ListServiceAccounts - GET /minio/admin/v3/list-service-accounts
|
|
|
|
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "ListServiceAccounts")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
// Get current object layer instance.
|
2020-10-09 12:59:52 -04:00
|
|
|
objectAPI := newObjectLayerFn()
|
2020-10-19 12:54:40 -04:00
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
2020-04-24 15:10:09 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
2020-04-24 15:10:09 -04:00
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
var targetAccount string
|
|
|
|
|
2021-08-08 01:43:01 -04:00
|
|
|
user := r.Form.Get("user")
|
2021-04-15 01:51:14 -04:00
|
|
|
if user != "" {
|
|
|
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
|
|
|
AccountName: cred.AccessKey,
|
|
|
|
Action: iampolicy.ListServiceAccountsAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
|
|
|
IsOwner: owner,
|
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
targetAccount = user
|
|
|
|
} else {
|
|
|
|
targetAccount = cred.AccessKey
|
|
|
|
if cred.ParentUser != "" {
|
|
|
|
targetAccount = cred.ParentUser
|
|
|
|
}
|
2020-05-01 11:05:14 -04:00
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, targetAccount)
|
2020-04-24 15:10:09 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
var serviceAccountsNames []string
|
|
|
|
|
|
|
|
for _, svc := range serviceAccounts {
|
|
|
|
serviceAccountsNames = append(serviceAccountsNames, svc.AccessKey)
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
var listResp = madmin.ListServiceAccountsResp{
|
2021-04-15 01:51:14 -04:00
|
|
|
Accounts: serviceAccountsNames,
|
2020-04-24 15:10:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
data, err := json.Marshal(listResp)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeSuccessResponseJSON(w, encryptedData)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteServiceAccount - DELETE /minio/admin/v3/delete-service-account
|
|
|
|
func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "DeleteServiceAccount")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
// Get current object layer instance.
|
2020-10-09 12:59:52 -04:00
|
|
|
objectAPI := newObjectLayerFn()
|
2020-10-19 12:54:40 -04:00
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
2020-04-24 15:10:09 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
2020-04-24 15:10:09 -04:00
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
serviceAccount := mux.Vars(r)["accessKey"]
|
|
|
|
if serviceAccount == "" {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
svcAccount, _, err := globalIAMSys.GetServiceAccount(ctx, serviceAccount)
|
2020-04-24 15:10:09 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
adminPrivilege := globalIAMSys.IsAllowed(iampolicy.Args{
|
|
|
|
AccountName: cred.AccessKey,
|
|
|
|
Action: iampolicy.RemoveServiceAccountAdminAction,
|
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
|
|
|
IsOwner: owner,
|
|
|
|
Claims: claims,
|
|
|
|
})
|
2020-05-01 11:05:14 -04:00
|
|
|
|
2021-04-15 01:51:14 -04:00
|
|
|
if !adminPrivilege {
|
|
|
|
parentUser := cred.AccessKey
|
|
|
|
if cred.ParentUser != "" {
|
|
|
|
parentUser = cred.ParentUser
|
|
|
|
}
|
|
|
|
if parentUser != svcAccount.ParentUser {
|
|
|
|
// The service account belongs to another user but return not
|
|
|
|
// found error to mitigate brute force attacks. or the
|
|
|
|
// serviceAccount doesn't exist.
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminServiceAccountNotFound), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2020-04-24 15:10:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
err = globalIAMSys.DeleteServiceAccount(ctx, serviceAccount)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-15 00:28:53 -04:00
|
|
|
for _, nerr := range globalNotificationSys.DeleteServiceAccount(serviceAccount) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-06 19:36:31 -04:00
|
|
|
// Call site replication hook. Only LDAP accounts are supported for
|
|
|
|
// replication operations.
|
|
|
|
svcAccClaims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, serviceAccount)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, isLDAPAccount := svcAccClaims[ldapUserN]; isLDAPAccount {
|
|
|
|
err = globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemSvcAcc,
|
|
|
|
SvcAccChange: &madmin.SRSvcAccChange{
|
|
|
|
Delete: &madmin.SRSvcAccDelete{
|
|
|
|
AccessKey: serviceAccount,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:10:09 -04:00
|
|
|
writeSuccessNoContent(w)
|
2020-03-17 13:36:13 -04:00
|
|
|
}
|
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
// AccountInfoHandler returns usage
|
|
|
|
func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AccountInfo")
|
2020-05-15 21:16:45 -04:00
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-15 21:16:45 -04:00
|
|
|
|
|
|
|
// Get current object layer instance.
|
2020-10-09 12:59:52 -04:00
|
|
|
objectAPI := newObjectLayerFn()
|
2020-10-19 12:54:40 -04:00
|
|
|
if objectAPI == nil || globalNotificationSys == nil {
|
2020-05-15 21:16:45 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
|
|
|
|
if s3Err != ErrNone {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set prefix value for "s3:prefix" policy conditionals.
|
|
|
|
r.Header.Set("prefix", "")
|
|
|
|
|
|
|
|
// Set delimiter value for "s3:delimiter" policy conditionals.
|
|
|
|
r.Header.Set("delimiter", SlashSeparator)
|
|
|
|
|
2021-07-14 11:51:10 -04:00
|
|
|
// Check if we are asked to return prefix usage
|
2021-08-08 01:43:01 -04:00
|
|
|
enablePrefixUsage := r.Form.Get("prefix-usage") == "true"
|
2021-07-14 11:51:10 -04:00
|
|
|
|
2020-05-15 21:16:45 -04:00
|
|
|
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
|
|
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
2021-07-11 20:39:52 -04:00
|
|
|
AccountName: cred.AccessKey,
|
2021-03-23 18:15:51 -04:00
|
|
|
Groups: cred.Groups,
|
2020-05-15 21:16:45 -04:00
|
|
|
Action: iampolicy.ListBucketAction,
|
|
|
|
BucketName: bucketName,
|
2021-07-11 20:39:52 -04:00
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
2020-05-15 21:16:45 -04:00
|
|
|
IsOwner: owner,
|
|
|
|
ObjectName: "",
|
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
rd = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
2021-07-11 20:39:52 -04:00
|
|
|
AccountName: cred.AccessKey,
|
2021-03-23 18:15:51 -04:00
|
|
|
Groups: cred.Groups,
|
2020-05-15 21:16:45 -04:00
|
|
|
Action: iampolicy.PutObjectAction,
|
|
|
|
BucketName: bucketName,
|
2021-07-11 20:39:52 -04:00
|
|
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
2020-05-15 21:16:45 -04:00
|
|
|
IsOwner: owner,
|
|
|
|
ObjectName: "",
|
|
|
|
Claims: claims,
|
|
|
|
}) {
|
|
|
|
wr = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return rd, wr
|
|
|
|
}
|
|
|
|
|
2021-09-18 16:31:35 -04:00
|
|
|
var dataUsageInfo DataUsageInfo
|
2021-07-09 13:35:09 -04:00
|
|
|
var err error
|
|
|
|
if !globalIsGateway {
|
|
|
|
// Load the latest calculated data usage
|
|
|
|
dataUsageInfo, err = loadDataUsageFromBackend(ctx, objectAPI)
|
|
|
|
if err != nil {
|
|
|
|
// log the error, continue with the accounting response
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
}
|
2020-05-15 21:16:45 -04:00
|
|
|
}
|
|
|
|
|
2021-02-09 12:53:07 -05:00
|
|
|
// If etcd, dns federation configured list buckets from etcd.
|
|
|
|
var buckets []BucketInfo
|
|
|
|
if globalDNSConfig != nil && globalBucketFederation {
|
|
|
|
dnsBuckets, err := globalDNSConfig.List()
|
|
|
|
if err != nil && !IsErrIgnored(err,
|
|
|
|
dns.ErrNoEntriesFound,
|
|
|
|
dns.ErrDomainMissing) {
|
2021-06-15 21:52:01 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
2021-02-09 12:53:07 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, dnsRecords := range dnsBuckets {
|
|
|
|
buckets = append(buckets, BucketInfo{
|
|
|
|
Name: dnsRecords[0].Key,
|
|
|
|
Created: dnsRecords[0].CreationDate,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
sort.Slice(buckets, func(i, j int) bool {
|
|
|
|
return buckets[i].Name < buckets[j].Name
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
buckets, err = objectAPI.ListBuckets(ctx)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-15 21:16:45 -04:00
|
|
|
accountName := cred.AccessKey
|
2021-03-23 20:39:20 -04:00
|
|
|
var policies []string
|
|
|
|
switch globalIAMSys.usersSysType {
|
|
|
|
case MinIOUsersSysType:
|
|
|
|
policies, err = globalIAMSys.PolicyDBGet(accountName, false)
|
|
|
|
case LDAPUsersSysType:
|
2021-03-25 03:38:15 -04:00
|
|
|
parentUser := accountName
|
|
|
|
if cred.ParentUser != "" {
|
|
|
|
parentUser = cred.ParentUser
|
|
|
|
}
|
|
|
|
policies, err = globalIAMSys.PolicyDBGet(parentUser, false, cred.Groups...)
|
2021-03-23 20:39:20 -04:00
|
|
|
default:
|
2021-04-29 16:01:42 -04:00
|
|
|
err = errors.New("should never happen")
|
2021-03-23 20:39:20 -04:00
|
|
|
}
|
2020-11-27 20:23:57 -05:00
|
|
|
if err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-29 12:22:22 -04:00
|
|
|
buf, err := json.MarshalIndent(globalIAMSys.GetCombinedPolicy(policies...), "", " ")
|
2021-05-06 11:52:02 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-27 20:23:57 -05:00
|
|
|
acctInfo := madmin.AccountInfo{
|
2020-05-15 21:16:45 -04:00
|
|
|
AccountName: accountName,
|
2021-07-16 17:37:06 -04:00
|
|
|
Server: objectAPI.BackendInfo(),
|
2021-05-06 11:52:02 -04:00
|
|
|
Policy: buf,
|
2020-05-15 21:16:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, bucket := range buckets {
|
|
|
|
rd, wr := isAllowedAccess(bucket.Name)
|
|
|
|
if rd || wr {
|
|
|
|
// Fetch the data usage of the current bucket
|
2021-07-14 11:51:10 -04:00
|
|
|
var size uint64
|
2021-09-17 18:02:21 -04:00
|
|
|
var objectsCount uint64
|
|
|
|
var objectsHist map[string]uint64
|
2020-05-27 09:45:43 -04:00
|
|
|
if !dataUsageInfo.LastUpdate.IsZero() {
|
|
|
|
size = dataUsageInfo.BucketsUsage[bucket.Name].Size
|
2021-09-17 18:02:21 -04:00
|
|
|
objectsCount = dataUsageInfo.BucketsUsage[bucket.Name].ObjectsCount
|
|
|
|
objectsHist = dataUsageInfo.BucketsUsage[bucket.Name].ObjectSizesHistogram
|
2020-05-15 21:16:45 -04:00
|
|
|
}
|
2021-07-14 11:51:10 -04:00
|
|
|
// Fetch the prefix usage of the current bucket
|
|
|
|
var prefixUsage map[string]uint64
|
|
|
|
if enablePrefixUsage {
|
|
|
|
if pu, err := loadPrefixUsageFromBackend(ctx, objectAPI, bucket.Name); err == nil {
|
|
|
|
prefixUsage = pu
|
|
|
|
} else {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
}
|
|
|
|
}
|
2021-09-17 18:02:21 -04:00
|
|
|
|
|
|
|
lcfg, _ := globalBucketObjectLockSys.Get(bucket.Name)
|
|
|
|
quota, _ := globalBucketQuotaSys.Get(bucket.Name)
|
|
|
|
rcfg, _ := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket.Name)
|
|
|
|
tcfg, _ := globalBucketMetadataSys.GetTaggingConfig(bucket.Name)
|
|
|
|
|
2021-04-03 12:03:42 -04:00
|
|
|
acctInfo.Buckets = append(acctInfo.Buckets, madmin.BucketAccessInfo{
|
2021-09-17 18:02:21 -04:00
|
|
|
Name: bucket.Name,
|
|
|
|
Created: bucket.Created,
|
|
|
|
Size: size,
|
|
|
|
Objects: objectsCount,
|
|
|
|
ObjectSizesHistogram: objectsHist,
|
|
|
|
PrefixUsage: prefixUsage,
|
|
|
|
Details: &madmin.BucketDetails{
|
|
|
|
Versioning: globalBucketVersioningSys.Enabled(bucket.Name),
|
|
|
|
VersioningSuspended: globalBucketVersioningSys.Suspended(bucket.Name),
|
|
|
|
Replication: rcfg != nil,
|
|
|
|
Locking: lcfg.LockEnabled,
|
|
|
|
Quota: quota,
|
|
|
|
Tagging: tcfg,
|
|
|
|
},
|
2020-05-15 21:16:45 -04:00
|
|
|
Access: madmin.AccountAccess{
|
|
|
|
Read: rd,
|
|
|
|
Write: wr,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
usageInfoJSON, err := json.Marshal(acctInfo)
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
writeSuccessResponseJSON(w, usageInfoJSON)
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// InfoCannedPolicy - GET /minio/admin/v3/info-canned-policy?name={policyName}
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "InfoCannedPolicy")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetPolicyAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
policy, err := globalIAMSys.InfoPolicy(mux.Vars(r)["name"])
|
2019-11-09 12:27:23 -05:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-29 12:22:22 -04:00
|
|
|
buf, err := json.MarshalIndent(policy, "", " ")
|
|
|
|
if err != nil {
|
2020-09-27 13:24:04 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2021-05-29 12:22:22 -04:00
|
|
|
w.Write(buf)
|
2020-04-07 22:30:59 -04:00
|
|
|
}
|
|
|
|
|
2021-05-27 13:15:02 -04:00
|
|
|
// 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")
|
2020-04-07 22:30:59 -04:00
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
2020-04-07 22:30:59 -04:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-27 13:15:02 -04:00
|
|
|
bucket := mux.Vars(r)["bucket"]
|
2021-11-15 17:14:22 -05:00
|
|
|
policies, err := globalIAMSys.ListPolicies(ctx, bucket)
|
2020-04-07 22:30:59 -04:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-27 13:15:02 -04:00
|
|
|
var newPolicies = make(map[string]iampolicy.Policy)
|
|
|
|
for name, p := range policies {
|
|
|
|
_, err = json.Marshal(p)
|
2020-04-07 22:30:59 -04:00
|
|
|
if err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
continue
|
|
|
|
}
|
2021-05-27 13:15:02 -04:00
|
|
|
newPolicies[name] = p
|
2020-04-07 22:30:59 -04:00
|
|
|
}
|
2021-05-27 13:15:02 -04:00
|
|
|
if err = json.NewEncoder(w).Encode(newPolicies); err != nil {
|
2020-04-07 22:30:59 -04:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// ListCannedPolicies - GET /minio/admin/v3/list-canned-policies
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) ListCannedPolicies(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "ListCannedPolicies")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ListUserPoliciesAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
policies, err := globalIAMSys.ListPolicies(ctx, "")
|
2019-11-09 12:27:23 -05:00
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:16:40 -04:00
|
|
|
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(newPolicies); err != nil {
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// RemoveCannedPolicy - DELETE /minio/admin/v3/remove-canned-policy?name=<policy_name>
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "RemoveCannedPolicy")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DeletePolicyAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
policyName := vars["name"]
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
if err := globalIAMSys.DeletePolicy(ctx, policyName); err != nil {
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other MinIO peers to delete policy
|
|
|
|
for _, nerr := range globalNotificationSys.DeletePolicy(policyName) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
|
|
|
}
|
2021-10-06 19:36:31 -04:00
|
|
|
|
|
|
|
// Call cluster-replication policy creation hook to replicate policy deletion to
|
|
|
|
// other minio clusters.
|
|
|
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemPolicy,
|
|
|
|
Name: policyName,
|
|
|
|
}); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// AddCannedPolicy - PUT /minio/admin/v3/add-canned-policy?name=<policy_name>
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "AddCannedPolicy")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.CreatePolicyAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
policyName := vars["name"]
|
|
|
|
|
|
|
|
// Error out if Content-Length is missing.
|
|
|
|
if r.ContentLength <= 0 {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error out if Content-Length is beyond allowed size.
|
|
|
|
if r.ContentLength > maxBucketPolicySize {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-10-06 19:36:31 -04:00
|
|
|
iamPolicyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
|
|
|
if err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
iamPolicy, err := iampolicy.ParseConfig(bytes.NewReader(iamPolicyBytes))
|
2019-11-09 12:27:23 -05:00
|
|
|
if err != nil {
|
2020-01-03 14:28:52 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
2019-11-09 12:27:23 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Version in policy must not be empty
|
|
|
|
if iamPolicy.Version == "" {
|
|
|
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
if err = globalIAMSys.SetPolicy(ctx, policyName, *iamPolicy); err != nil {
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other MinIO peers to reload policy
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadPolicy(policyName) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
}
|
2021-10-06 19:36:31 -04:00
|
|
|
|
|
|
|
// Call cluster-replication policy creation hook to replicate policy to
|
|
|
|
// other minio clusters.
|
|
|
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemPolicy,
|
|
|
|
Name: policyName,
|
|
|
|
Policy: iamPolicyBytes,
|
|
|
|
}); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
|
2020-04-07 22:30:59 -04:00
|
|
|
// SetPolicyForUserOrGroup - PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group]
|
2019-11-09 12:27:23 -05:00
|
|
|
func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := newContext(r, w, "SetPolicyForUserOrGroup")
|
|
|
|
|
2021-01-26 16:21:51 -05:00
|
|
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
2020-05-11 13:34:08 -04:00
|
|
|
|
2021-08-09 21:14:38 -04:00
|
|
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
|
2019-11-09 12:27:23 -05:00
|
|
|
if objectAPI == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
policyName := vars["policyName"]
|
|
|
|
entityName := vars["userOrGroup"]
|
|
|
|
isGroup := vars["isGroup"] == "true"
|
|
|
|
|
2019-12-19 17:21:21 -05:00
|
|
|
if !isGroup {
|
2021-02-25 16:49:59 -05:00
|
|
|
ok, _, err := globalIAMSys.IsTempUser(entityName)
|
2020-04-09 04:04:08 -04:00
|
|
|
if err != nil && err != errNoSuchUser {
|
2019-12-19 17:21:21 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if ok {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-15 17:14:22 -05:00
|
|
|
if err := globalIAMSys.PolicyDBSet(ctx, entityName, policyName, isGroup); err != nil {
|
2019-11-09 12:27:23 -05:00
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify all other MinIO peers to reload policy
|
2021-10-20 06:22:35 -04:00
|
|
|
if !globalIAMSys.HasWatcher() {
|
|
|
|
for _, nerr := range globalNotificationSys.LoadPolicyMapping(entityName, isGroup) {
|
|
|
|
if nerr.Err != nil {
|
|
|
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
|
|
|
logger.LogIf(ctx, nerr.Err)
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|
|
|
|
}
|
2021-10-06 19:36:31 -04:00
|
|
|
|
|
|
|
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
|
|
|
Type: madmin.SRIAMItemPolicyMapping,
|
|
|
|
PolicyMapping: &madmin.SRPolicyMapping{
|
|
|
|
UserOrGroup: entityName,
|
|
|
|
IsGroup: isGroup,
|
|
|
|
Policy: policyName,
|
|
|
|
},
|
|
|
|
}); err != nil {
|
|
|
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
|
|
return
|
|
|
|
}
|
2019-11-09 12:27:23 -05:00
|
|
|
}
|