add list/delete API service accounts admin API (#9402)

master
Anis Elleuch 5 years ago committed by GitHub
parent e8160c9fae
commit 20766069a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 145
      cmd/admin-handlers-users.go
  2. 2
      cmd/admin-router.go
  3. 17
      cmd/api-errors.go
  4. 99
      cmd/iam.go
  5. 30
      cmd/notification.go
  6. 26
      cmd/peer-rest-client.go
  7. 2
      cmd/peer-rest-common.go
  8. 68
      cmd/peer-rest-server.go
  9. 3
      cmd/typed-errors.go
  10. 19
      pkg/madmin/examples/service-accounts.go
  11. 75
      pkg/madmin/user-commands.go

@ -397,38 +397,21 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
} }
password := cred.SecretKey password := cred.SecretKey
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength)) reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
return return
} }
var createReq madmin.AddServiceAccountReq var createReq madmin.AddServiceAccountReq
if err = json.Unmarshal(configBytes, &createReq); err != nil { if err = json.Unmarshal(reqBytes, &createReq); err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
return return
} }
if createReq.Parent == "" { // Disallow creating service accounts by root user.
apiErr := APIError{ if owner {
Code: "XMinioAdminInvalidArgument", writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
Description: "Service account parent cannot be empty",
HTTPStatusCode: http.StatusBadRequest,
}
writeErrorResponseJSON(ctx, w, apiErr, r.URL)
return
}
// Disallow creating service accounts by root user as well.
if createReq.Parent == globalActiveCred.AccessKey {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddServiceAccountInvalidArgument), r.URL)
return
}
// Disallow creating service accounts by users who are not the requested parent.
// this restriction is not required for Owner account i.e root user.
if createReq.Parent != cred.AccessKey && !owner {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddServiceAccountInvalidParent), r.URL)
return return
} }
@ -438,14 +421,14 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
return return
} }
creds, err := globalIAMSys.NewServiceAccount(ctx, createReq.Parent, createReq.Policy) newCred, err := globalIAMSys.NewServiceAccount(ctx, cred.AccessKey, createReq.Policy)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
// Notify all other Minio peers to reload user // Notify all other Minio peers to reload user the service account
for _, nerr := range globalNotificationSys.LoadUser(creds.AccessKey, false) { for _, nerr := range globalNotificationSys.LoadServiceAccount(newCred.AccessKey) {
if nerr.Err != nil { if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err) logger.LogIf(ctx, nerr.Err)
@ -454,8 +437,8 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
var createResp = madmin.AddServiceAccountResp{ var createResp = madmin.AddServiceAccountResp{
Credentials: auth.Credentials{ Credentials: auth.Credentials{
AccessKey: creds.AccessKey, AccessKey: newCred.AccessKey,
SecretKey: creds.SecretKey, SecretKey: newCred.SecretKey,
}, },
} }
@ -465,13 +448,117 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
return return
} }
econfigData, err := madmin.EncryptData(password, data) encryptedData, err := madmin.EncryptData(password, data)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
writeSuccessResponseJSON(w, econfigData) writeSuccessResponseJSON(w, encryptedData)
}
// ListServiceAccounts - GET /minio/admin/v3/list-service-accounts
func (a adminAPIHandlers) ListServiceAccounts(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListServiceAccounts")
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
cred, _, owner, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
// Disallow creating service accounts by root user.
if owner {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
return
}
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, cred.AccessKey)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
var listResp = madmin.ListServiceAccountsResp{
Accounts: serviceAccounts,
}
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")
// Get current object layer instance.
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
cred, _, owner, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
// Disallow creating service accounts by root user.
if owner {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminAccountNotEligible), r.URL)
return
}
// Deny if WORM is enabled
if globalWORMEnabled {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
serviceAccount := mux.Vars(r)["accessKey"]
if serviceAccount == "" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
return
}
user, err := globalIAMSys.GetServiceAccountParent(ctx, serviceAccount)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if cred.AccessKey != user {
// The service account belongs to another user but return not found error to mitigate brute force attacks.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServiceAccountNotFound), r.URL)
return
}
err = globalIAMSys.DeleteServiceAccount(ctx, serviceAccount)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessNoContent(w)
} }
// InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName} // InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName}

@ -121,6 +121,8 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
// Service accounts ops // Service accounts ops
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(httpTraceHdrs(adminAPI.AddServiceAccount)) adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(httpTraceHdrs(adminAPI.AddServiceAccount))
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(httpTraceHdrs(adminAPI.ListServiceAccounts))
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(httpTraceHdrs(adminAPI.DeleteServiceAccount)).Queries("accessKey", "{accessKey:.*}")
if adminVersion == adminAPIVersionV2Prefix { if adminVersion == adminAPIVersionV2Prefix {
// Info policy IAM v2 // Info policy IAM v2

@ -334,8 +334,8 @@ const (
ErrAdminProfilerNotEnabled ErrAdminProfilerNotEnabled
ErrInvalidDecompressedSize ErrInvalidDecompressedSize
ErrAddUserInvalidArgument ErrAddUserInvalidArgument
ErrAddServiceAccountInvalidArgument ErrAdminAccountNotEligible
ErrAddServiceAccountInvalidParent ErrServiceAccountNotFound
ErrPostPolicyConditionInvalidFormat ErrPostPolicyConditionInvalidFormat
) )
@ -1599,17 +1599,16 @@ var errorCodes = errorCodeMap{
Description: "User is not allowed to be same as admin access key", Description: "User is not allowed to be same as admin access key",
HTTPStatusCode: http.StatusConflict, HTTPStatusCode: http.StatusConflict,
}, },
ErrAddServiceAccountInvalidArgument: { ErrAdminAccountNotEligible: {
Code: "XMinioInvalidIAMCredentials", Code: "XMinioInvalidIAMCredentials",
Description: "Creating service accounts for admin access key is not allowed", Description: "The administrator key is not eligible for this operation",
HTTPStatusCode: http.StatusConflict, HTTPStatusCode: http.StatusConflict,
}, },
ErrAddServiceAccountInvalidParent: { ErrServiceAccountNotFound: {
Code: "XMinioInvalidIAMCredentialsParent", Code: "XMinioInvalidIAMCredentials",
Description: "Creating service accounts for other users is not allowed", Description: "The specified service account is not found",
HTTPStatusCode: http.StatusConflict, HTTPStatusCode: http.StatusNotFound,
}, },
ErrPostPolicyConditionInvalidFormat: { ErrPostPolicyConditionInvalidFormat: {
Code: "PostPolicyInvalidKeyName", Code: "PostPolicyInvalidKeyName",
Description: "Invalid according to Policy: Policy Condition failed", Description: "Invalid according to Policy: Policy Condition failed",

@ -354,6 +354,25 @@ func (sys *IAMSys) LoadUser(objAPI ObjectLayer, accessKey string, userType IAMUs
return nil return nil
} }
// LoadServiceAccount - reloads a specific service account from backend disks or etcd.
func (sys *IAMSys) LoadServiceAccount(accessKey string) error {
if sys == nil || sys.store == nil {
return errServerNotInitialized
}
sys.store.lock()
defer sys.store.unlock()
if globalEtcdClient == nil {
err := sys.store.loadUser(accessKey, srvAccUser, sys.iamUsersMap)
if err != nil {
return err
}
}
// When etcd is set, we use watch APIs so this code is not needed.
return nil
}
// Perform IAM configuration migration. // Perform IAM configuration migration.
func (sys *IAMSys) doIAMConfigMigration(ctx context.Context) error { func (sys *IAMSys) doIAMConfigMigration(ctx context.Context) error {
return sys.store.migrateBackendFormat(ctx) return sys.store.migrateBackendFormat(ctx)
@ -832,6 +851,86 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, ses
return cred, nil return cred, nil
} }
// ListServiceAccounts - lists all services accounts associated to a specific user
func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([]string, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
return nil, errServerNotInitialized
}
sys.store.rlock()
defer sys.store.runlock()
if sys.usersSysType != MinIOUsersSysType {
return nil, errIAMActionNotAllowed
}
var serviceAccounts []string
for k, v := range sys.iamUsersMap {
if v.IsServiceAccount() && v.ParentUser == accessKey {
serviceAccounts = append(serviceAccounts, k)
}
}
return serviceAccounts, nil
}
// GetServiceAccountParent - gets information about a service account
func (sys *IAMSys) GetServiceAccountParent(ctx context.Context, accessKey string) (string, error) {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
return "", errServerNotInitialized
}
sys.store.rlock()
defer sys.store.runlock()
if sys.usersSysType != MinIOUsersSysType {
return "", errIAMActionNotAllowed
}
sa, ok := sys.iamUsersMap[accessKey]
if !ok || !sa.IsServiceAccount() {
return "", errNoSuchServiceAccount
}
return sa.ParentUser, nil
}
// DeleteServiceAccount - delete a service account
func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string) error {
objectAPI := newObjectLayerWithoutSafeModeFn()
if objectAPI == nil || sys == nil || sys.store == nil {
return errServerNotInitialized
}
sys.store.lock()
defer sys.store.unlock()
if sys.usersSysType != MinIOUsersSysType {
return errIAMActionNotAllowed
}
sa, ok := sys.iamUsersMap[accessKey]
if !ok || !sa.IsServiceAccount() {
return errNoSuchServiceAccount
}
// It is ok to ignore deletion error on the mapped policy
err := sys.store.deleteUserIdentity(accessKey, srvAccUser)
if err != nil {
// ignore if user is already deleted.
if err == errNoSuchUser {
return nil
}
return err
}
delete(sys.iamUsersMap, accessKey)
return nil
}
// SetUser - set user credentials and policy. // SetUser - set user credentials and policy.
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error { func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
objectAPI := newObjectLayerWithoutSafeModeFn() objectAPI := newObjectLayerWithoutSafeModeFn()

@ -243,6 +243,36 @@ func (sys *NotificationSys) LoadGroup(group string) []NotificationPeerErr {
return ng.Wait() return ng.Wait()
} }
// DeleteServiceAccount - deletes a specific service account across all peers
func (sys *NotificationSys) DeleteServiceAccount(accessKey string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(GlobalContext, func() error {
return client.DeleteServiceAccount(accessKey)
}, idx, *client.host)
}
return ng.Wait()
}
// LoadServiceAccount - reloads a specific service account across all peers
func (sys *NotificationSys) LoadServiceAccount(accessKey string) []NotificationPeerErr {
ng := WithNPeers(len(sys.peerClients))
for idx, client := range sys.peerClients {
if client == nil {
continue
}
client := client
ng.Go(GlobalContext, func() error {
return client.LoadServiceAccount(accessKey)
}, idx, *client.host)
}
return ng.Wait()
}
// BackgroundHealStatus - returns background heal status of all peers // BackgroundHealStatus - returns background heal status of all peers
func (sys *NotificationSys) BackgroundHealStatus() []madmin.BgHealState { func (sys *NotificationSys) BackgroundHealStatus() []madmin.BgHealState {
states := make([]madmin.BgHealState, len(sys.peerClients)) states := make([]madmin.BgHealState, len(sys.peerClients))

@ -770,6 +770,19 @@ func (client *peerRESTClient) DeleteUser(accessKey string) (err error) {
return nil return nil
} }
// DeleteServiceAccount - delete a specific service account.
func (client *peerRESTClient) DeleteServiceAccount(accessKey string) (err error) {
values := make(url.Values)
values.Set(peerRESTUser, accessKey)
respBody, err := client.call(peerRESTMethodDeleteServiceAccount, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadUser - reload a specific user. // LoadUser - reload a specific user.
func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error) { func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error) {
values := make(url.Values) values := make(url.Values)
@ -784,6 +797,19 @@ func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error)
return nil return nil
} }
// LoadServiceAccount - reload a specific service account.
func (client *peerRESTClient) LoadServiceAccount(accessKey string) (err error) {
values := make(url.Values)
values.Set(peerRESTUser, accessKey)
respBody, err := client.call(peerRESTMethodLoadServiceAccount, values, nil, -1)
if err != nil {
return
}
defer http.DrainBody(respBody)
return nil
}
// LoadGroup - send load group command to peers. // LoadGroup - send load group command to peers.
func (client *peerRESTClient) LoadGroup(group string) error { func (client *peerRESTClient) LoadGroup(group string) error {
values := make(url.Values) values := make(url.Values)

@ -40,7 +40,9 @@ const (
peerRESTMethodGetLocks = "/getlocks" peerRESTMethodGetLocks = "/getlocks"
peerRESTMethodBucketPolicyRemove = "/removebucketpolicy" peerRESTMethodBucketPolicyRemove = "/removebucketpolicy"
peerRESTMethodLoadUser = "/loaduser" peerRESTMethodLoadUser = "/loaduser"
peerRESTMethodLoadServiceAccount = "/loadserviceaccount"
peerRESTMethodDeleteUser = "/deleteuser" peerRESTMethodDeleteUser = "/deleteuser"
peerRESTMethodDeleteServiceAccount = "/deleteserviceaccount"
peerRESTMethodLoadPolicy = "/loadpolicy" peerRESTMethodLoadPolicy = "/loadpolicy"
peerRESTMethodLoadPolicyMapping = "/loadpolicymapping" peerRESTMethodLoadPolicyMapping = "/loadpolicymapping"
peerRESTMethodDeletePolicy = "/deletepolicy" peerRESTMethodDeletePolicy = "/deletepolicy"

@ -184,6 +184,72 @@ func (s *peerRESTServer) LoadPolicyMappingHandler(w http.ResponseWriter, r *http
w.(http.Flusher).Flush() w.(http.Flusher).Flush()
} }
// DeleteServiceAccountHandler - deletes a service account on the server.
func (s *peerRESTServer) DeleteServiceAccountHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
if globalIAMSys == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
accessKey := vars[peerRESTUser]
if accessKey == "" {
s.writeErrorResponse(w, errors.New("service account name is missing"))
return
}
if err := globalIAMSys.DeleteServiceAccount(context.Background(), accessKey); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// LoadServiceAccountHandler - reloads a service account on the server.
func (s *peerRESTServer) LoadServiceAccountHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
objAPI := newObjectLayerWithoutSafeModeFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
if globalIAMSys == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return
}
vars := mux.Vars(r)
accessKey := vars[peerRESTUser]
if accessKey == "" {
s.writeErrorResponse(w, errors.New("service account parameter is missing"))
return
}
if err := globalIAMSys.LoadServiceAccount(accessKey); err != nil {
s.writeErrorResponse(w, err)
return
}
w.(http.Flusher).Flush()
}
// DeleteUserHandler - deletes a user on the server. // DeleteUserHandler - deletes a user on the server.
func (s *peerRESTServer) DeleteUserHandler(w http.ResponseWriter, r *http.Request) { func (s *peerRESTServer) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) { if !s.IsValid(w, r) {
@ -1165,7 +1231,9 @@ func registerPeerRESTHandlers(router *mux.Router) {
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...) subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadPolicy).HandlerFunc(httpTraceAll(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadPolicyMapping).HandlerFunc(httpTraceAll(server.LoadPolicyMappingHandler)).Queries(restQueries(peerRESTUserOrGroup)...) subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadPolicyMapping).HandlerFunc(httpTraceAll(server.LoadPolicyMappingHandler)).Queries(restQueries(peerRESTUserOrGroup)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.DeleteUserHandler)).Queries(restQueries(peerRESTUser)...) subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeleteUser).HandlerFunc(httpTraceAll(server.DeleteUserHandler)).Queries(restQueries(peerRESTUser)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeleteServiceAccount).HandlerFunc(httpTraceAll(server.DeleteServiceAccountHandler)).Queries(restQueries(peerRESTUser)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser, peerRESTUserTemp)...) subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadUser).HandlerFunc(httpTraceAll(server.LoadUserHandler)).Queries(restQueries(peerRESTUser, peerRESTUserTemp)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadServiceAccount).HandlerFunc(httpTraceAll(server.LoadServiceAccountHandler)).Queries(restQueries(peerRESTUser)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadGroup).HandlerFunc(httpTraceAll(server.LoadGroupHandler)).Queries(restQueries(peerRESTGroup)...) subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadGroup).HandlerFunc(httpTraceAll(server.LoadGroupHandler)).Queries(restQueries(peerRESTGroup)...)
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodStartProfiling).HandlerFunc(httpTraceAll(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...) subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodStartProfiling).HandlerFunc(httpTraceAll(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...)

@ -77,6 +77,9 @@ var errInvalidDecompressedSize = errors.New("Invalid Decompressed Size")
// error returned in IAM subsystem when user doesn't exist. // error returned in IAM subsystem when user doesn't exist.
var errNoSuchUser = errors.New("Specified user does not exist") var errNoSuchUser = errors.New("Specified user does not exist")
// error returned in IAM subsystem when the service account doesn't exist.
var errNoSuchServiceAccount = errors.New("Specified service account does not exist")
// error returned in IAM subsystem when groups doesn't exist. // error returned in IAM subsystem when groups doesn't exist.
var errNoSuchGroup = errors.New("Specified group does not exist") var errNoSuchGroup = errors.New("Specified group does not exist")

@ -46,7 +46,7 @@ func main() {
p := iampolicy.Policy{ p := iampolicy.Policy{
Version: iampolicy.DefaultVersion, Version: iampolicy.DefaultVersion,
Statements: []Statement{ Statements: []iampolicy.Statement{
iampolicy.NewStatement( iampolicy.NewStatement(
policy.Allow, policy.Allow,
iampolicy.NewActionSet(iampolicy.GetObjectAction), iampolicy.NewActionSet(iampolicy.GetObjectAction),
@ -55,10 +55,23 @@ func main() {
)}, )},
} }
creds, err := madmClnt.AddServiceAccount(context.Background(), "parentuser", &p) // Create a new service account
creds, err := madmClnt.AddServiceAccount(context.Background(), &p)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
fmt.Println(creds) fmt.Println(creds)
// List all services accounts
list, err := madmClnt.ListServiceAccounts(context.Background())
if err != nil {
log.Fatalln(err)
}
fmt.Println(list)
// Delete a service account
err = madmClnt.DeleteServiceAccount(context.Background(), list.Accounts[0])
if err != nil {
log.Fatalln(err)
}
} }

@ -215,7 +215,6 @@ func (adm *AdminClient) SetUserStatus(ctx context.Context, accessKey string, sta
// AddServiceAccountReq is the request body of the add service account admin call // AddServiceAccountReq is the request body of the add service account admin call
type AddServiceAccountReq struct { type AddServiceAccountReq struct {
Parent string `json:"parent"`
Policy *iampolicy.Policy `json:"policy,omitempty"` Policy *iampolicy.Policy `json:"policy,omitempty"`
} }
@ -224,13 +223,9 @@ type AddServiceAccountResp struct {
Credentials auth.Credentials `json:"credentials"` Credentials auth.Credentials `json:"credentials"`
} }
// AddServiceAccount - creates a new service account belonging to the given parent user // AddServiceAccount - creates a new service account belonging to the user sending
// while restricting the service account permission by the given policy document. // the request while restricting the service account permission by the given policy document.
func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string, policy *iampolicy.Policy) (auth.Credentials, error) { func (adm *AdminClient) AddServiceAccount(ctx context.Context, policy *iampolicy.Policy) (auth.Credentials, error) {
if !auth.IsAccessKeyValid(parentUser) {
return auth.Credentials{}, auth.ErrInvalidAccessKeyLength
}
if policy != nil { if policy != nil {
if err := policy.Validate(); err != nil { if err := policy.Validate(); err != nil {
return auth.Credentials{}, err return auth.Credentials{}, err
@ -238,7 +233,6 @@ func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string
} }
data, err := json.Marshal(AddServiceAccountReq{ data, err := json.Marshal(AddServiceAccountReq{
Parent: parentUser,
Policy: policy, Policy: policy,
}) })
if err != nil { if err != nil {
@ -277,3 +271,66 @@ func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string
} }
return serviceAccountResp.Credentials, nil return serviceAccountResp.Credentials, nil
} }
// ListServiceAccountsResp is the response body of the list service accounts call
type ListServiceAccountsResp struct {
Accounts []string `json:"accounts"`
}
// ListServiceAccounts - list service accounts belonging to the specified user
func (adm *AdminClient) ListServiceAccounts(ctx context.Context) (ListServiceAccountsResp, error) {
reqData := requestData{
relPath: adminAPIPrefix + "/list-service-accounts",
}
// Execute GET on /minio/admin/v3/list-service-accounts
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
defer closeResponse(resp)
if err != nil {
return ListServiceAccountsResp{}, err
}
if resp.StatusCode != http.StatusOK {
return ListServiceAccountsResp{}, httpRespToErrorResponse(resp)
}
data, err := DecryptData(adm.getSecretKey(), resp.Body)
if err != nil {
return ListServiceAccountsResp{}, err
}
var listResp ListServiceAccountsResp
if err = json.Unmarshal(data, &listResp); err != nil {
return ListServiceAccountsResp{}, err
}
return listResp, nil
}
// DeleteServiceAccount - delete a specified service account. The server will reject
// the request if the service account does not belong to the user initiating the request
func (adm *AdminClient) DeleteServiceAccount(ctx context.Context, serviceAccount string) error {
if !auth.IsAccessKeyValid(serviceAccount) {
return auth.ErrInvalidAccessKeyLength
}
queryValues := url.Values{}
queryValues.Set("accessKey", serviceAccount)
reqData := requestData{
relPath: adminAPIPrefix + "/delete-service-account",
queryValues: queryValues,
}
// Execute DELETE on /minio/admin/v3/delete-service-account
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
defer closeResponse(resp)
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return httpRespToErrorResponse(resp)
}
return nil
}

Loading…
Cancel
Save