github.com/minio/madmin-go@v1.7.5/user-commands.go (about)

     1  //
     2  // MinIO Object Storage (c) 2021 MinIO, Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //      http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package madmin
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"net/url"
    25  	"time"
    26  
    27  	"github.com/minio/minio-go/v7/pkg/tags"
    28  )
    29  
    30  // AccountAccess contains information about
    31  type AccountAccess struct {
    32  	Read  bool `json:"read"`
    33  	Write bool `json:"write"`
    34  }
    35  
    36  // BucketDetails provides information about features currently
    37  // turned-on per bucket.
    38  type BucketDetails struct {
    39  	Versioning          bool         `json:"versioning"`
    40  	VersioningSuspended bool         `json:"versioningSuspended"`
    41  	Locking             bool         `json:"locking"`
    42  	Replication         bool         `json:"replication"`
    43  	Tagging             *tags.Tags   `json:"tags"`
    44  	Quota               *BucketQuota `json:"quota"`
    45  }
    46  
    47  // BucketAccessInfo represents bucket usage of a bucket, and its relevant
    48  // access type for an account
    49  type BucketAccessInfo struct {
    50  	Name                 string            `json:"name"`
    51  	Size                 uint64            `json:"size"`
    52  	Objects              uint64            `json:"objects"`
    53  	ObjectSizesHistogram map[string]uint64 `json:"objectHistogram"`
    54  	Details              *BucketDetails    `json:"details"`
    55  	PrefixUsage          map[string]uint64 `json:"prefixUsage"`
    56  	Created              time.Time         `json:"created"`
    57  	Access               AccountAccess     `json:"access"`
    58  }
    59  
    60  // AccountInfo represents the account usage info of an
    61  // account across buckets.
    62  type AccountInfo struct {
    63  	AccountName string
    64  	Server      BackendInfo
    65  	Policy      json.RawMessage // Use iam/policy.Parse to parse the result, to be done by the caller.
    66  	Buckets     []BucketAccessInfo
    67  }
    68  
    69  // AccountOpts allows for configurable behavior with "prefix-usage"
    70  type AccountOpts struct {
    71  	PrefixUsage bool
    72  }
    73  
    74  // AccountInfo returns the usage info for the authenticating account.
    75  func (adm *AdminClient) AccountInfo(ctx context.Context, opts AccountOpts) (AccountInfo, error) {
    76  	q := make(url.Values)
    77  	if opts.PrefixUsage {
    78  		q.Set("prefix-usage", "true")
    79  	}
    80  	resp, err := adm.executeMethod(ctx, http.MethodGet,
    81  		requestData{
    82  			relPath:     adminAPIPrefix + "/accountinfo",
    83  			queryValues: q,
    84  		},
    85  	)
    86  	defer closeResponse(resp)
    87  	if err != nil {
    88  		return AccountInfo{}, err
    89  	}
    90  
    91  	// Check response http status code
    92  	if resp.StatusCode != http.StatusOK {
    93  		return AccountInfo{}, httpRespToErrorResponse(resp)
    94  	}
    95  
    96  	// Unmarshal the server's json response
    97  	var accountInfo AccountInfo
    98  
    99  	respBytes, err := ioutil.ReadAll(resp.Body)
   100  	if err != nil {
   101  		return AccountInfo{}, err
   102  	}
   103  
   104  	err = json.Unmarshal(respBytes, &accountInfo)
   105  	if err != nil {
   106  		return AccountInfo{}, err
   107  	}
   108  
   109  	return accountInfo, nil
   110  }
   111  
   112  // AccountStatus - account status.
   113  type AccountStatus string
   114  
   115  // Account status per user.
   116  const (
   117  	AccountEnabled  AccountStatus = "enabled"
   118  	AccountDisabled AccountStatus = "disabled"
   119  )
   120  
   121  // UserInfo carries information about long term users.
   122  type UserInfo struct {
   123  	SecretKey  string        `json:"secretKey,omitempty"`
   124  	PolicyName string        `json:"policyName,omitempty"`
   125  	Status     AccountStatus `json:"status"`
   126  	MemberOf   []string      `json:"memberOf,omitempty"`
   127  	UpdatedAt  time.Time     `json:"updatedAt,omitempty"`
   128  }
   129  
   130  // RemoveUser - remove a user.
   131  func (adm *AdminClient) RemoveUser(ctx context.Context, accessKey string) error {
   132  	queryValues := url.Values{}
   133  	queryValues.Set("accessKey", accessKey)
   134  
   135  	reqData := requestData{
   136  		relPath:     adminAPIPrefix + "/remove-user",
   137  		queryValues: queryValues,
   138  	}
   139  
   140  	// Execute DELETE on /minio/admin/v3/remove-user to remove a user.
   141  	resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
   142  
   143  	defer closeResponse(resp)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	if resp.StatusCode != http.StatusOK {
   149  		return httpRespToErrorResponse(resp)
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  // ListUsers - list all users.
   156  func (adm *AdminClient) ListUsers(ctx context.Context) (map[string]UserInfo, error) {
   157  	reqData := requestData{
   158  		relPath: adminAPIPrefix + "/list-users",
   159  	}
   160  
   161  	// Execute GET on /minio/admin/v3/list-users
   162  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   163  
   164  	defer closeResponse(resp)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	if resp.StatusCode != http.StatusOK {
   170  		return nil, httpRespToErrorResponse(resp)
   171  	}
   172  
   173  	data, err := DecryptData(adm.getSecretKey(), resp.Body)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	users := make(map[string]UserInfo)
   179  	if err = json.Unmarshal(data, &users); err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	return users, nil
   184  }
   185  
   186  // GetUserInfo - get info on a user
   187  func (adm *AdminClient) GetUserInfo(ctx context.Context, name string) (u UserInfo, err error) {
   188  	queryValues := url.Values{}
   189  	queryValues.Set("accessKey", name)
   190  
   191  	reqData := requestData{
   192  		relPath:     adminAPIPrefix + "/user-info",
   193  		queryValues: queryValues,
   194  	}
   195  
   196  	// Execute GET on /minio/admin/v3/user-info
   197  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   198  
   199  	defer closeResponse(resp)
   200  	if err != nil {
   201  		return u, err
   202  	}
   203  
   204  	if resp.StatusCode != http.StatusOK {
   205  		return u, httpRespToErrorResponse(resp)
   206  	}
   207  
   208  	b, err := ioutil.ReadAll(resp.Body)
   209  	if err != nil {
   210  		return u, err
   211  	}
   212  
   213  	if err = json.Unmarshal(b, &u); err != nil {
   214  		return u, err
   215  	}
   216  
   217  	return u, nil
   218  }
   219  
   220  // AddOrUpdateUserReq allows to update user details such as secret key and
   221  // account status.
   222  type AddOrUpdateUserReq struct {
   223  	SecretKey string        `json:"secretKey,omitempty"`
   224  	Status    AccountStatus `json:"status"`
   225  }
   226  
   227  // SetUser - update user secret key or account status.
   228  func (adm *AdminClient) SetUser(ctx context.Context, accessKey, secretKey string, status AccountStatus) error {
   229  	data, err := json.Marshal(AddOrUpdateUserReq{
   230  		SecretKey: secretKey,
   231  		Status:    status,
   232  	})
   233  	if err != nil {
   234  		return err
   235  	}
   236  	econfigBytes, err := EncryptData(adm.getSecretKey(), data)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	queryValues := url.Values{}
   242  	queryValues.Set("accessKey", accessKey)
   243  
   244  	reqData := requestData{
   245  		relPath:     adminAPIPrefix + "/add-user",
   246  		queryValues: queryValues,
   247  		content:     econfigBytes,
   248  	}
   249  
   250  	// Execute PUT on /minio/admin/v3/add-user to set a user.
   251  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   252  
   253  	defer closeResponse(resp)
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	if resp.StatusCode != http.StatusOK {
   259  		return httpRespToErrorResponse(resp)
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  // AddUser - adds a user.
   266  func (adm *AdminClient) AddUser(ctx context.Context, accessKey, secretKey string) error {
   267  	return adm.SetUser(ctx, accessKey, secretKey, AccountEnabled)
   268  }
   269  
   270  // SetUserStatus - adds a status for a user.
   271  func (adm *AdminClient) SetUserStatus(ctx context.Context, accessKey string, status AccountStatus) error {
   272  	queryValues := url.Values{}
   273  	queryValues.Set("accessKey", accessKey)
   274  	queryValues.Set("status", string(status))
   275  
   276  	reqData := requestData{
   277  		relPath:     adminAPIPrefix + "/set-user-status",
   278  		queryValues: queryValues,
   279  	}
   280  
   281  	// Execute PUT on /minio/admin/v3/set-user-status to set status.
   282  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   283  
   284  	defer closeResponse(resp)
   285  	if err != nil {
   286  		return err
   287  	}
   288  
   289  	if resp.StatusCode != http.StatusOK {
   290  		return httpRespToErrorResponse(resp)
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  // AddServiceAccountReq is the request options of the add service account admin call
   297  type AddServiceAccountReq struct {
   298  	Policy     json.RawMessage `json:"policy,omitempty"` // Parsed value from iam/policy.Parse()
   299  	TargetUser string          `json:"targetUser,omitempty"`
   300  	AccessKey  string          `json:"accessKey,omitempty"`
   301  	SecretKey  string          `json:"secretKey,omitempty"`
   302  }
   303  
   304  // AddServiceAccountResp is the response body of the add service account admin call
   305  type AddServiceAccountResp struct {
   306  	Credentials Credentials `json:"credentials"`
   307  }
   308  
   309  // AddServiceAccount - creates a new service account belonging to the user sending
   310  // the request while restricting the service account permission by the given policy document.
   311  func (adm *AdminClient) AddServiceAccount(ctx context.Context, opts AddServiceAccountReq) (Credentials, error) {
   312  	data, err := json.Marshal(opts)
   313  	if err != nil {
   314  		return Credentials{}, err
   315  	}
   316  
   317  	econfigBytes, err := EncryptData(adm.getSecretKey(), data)
   318  	if err != nil {
   319  		return Credentials{}, err
   320  	}
   321  
   322  	reqData := requestData{
   323  		relPath: adminAPIPrefix + "/add-service-account",
   324  		content: econfigBytes,
   325  	}
   326  
   327  	// Execute PUT on /minio/admin/v3/add-service-account to set a user.
   328  	resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
   329  	defer closeResponse(resp)
   330  	if err != nil {
   331  		return Credentials{}, err
   332  	}
   333  
   334  	if resp.StatusCode != http.StatusOK {
   335  		return Credentials{}, httpRespToErrorResponse(resp)
   336  	}
   337  
   338  	data, err = DecryptData(adm.getSecretKey(), resp.Body)
   339  	if err != nil {
   340  		return Credentials{}, err
   341  	}
   342  
   343  	var serviceAccountResp AddServiceAccountResp
   344  	if err = json.Unmarshal(data, &serviceAccountResp); err != nil {
   345  		return Credentials{}, err
   346  	}
   347  	return serviceAccountResp.Credentials, nil
   348  }
   349  
   350  // UpdateServiceAccountReq is the request options of the edit service account admin call
   351  type UpdateServiceAccountReq struct {
   352  	NewPolicy    json.RawMessage `json:"newPolicy,omitempty"` // Parsed policy from iam/policy.Parse
   353  	NewSecretKey string          `json:"newSecretKey,omitempty"`
   354  	NewStatus    string          `json:"newStatus,omitempty"`
   355  }
   356  
   357  // UpdateServiceAccount - edit an existing service account
   358  func (adm *AdminClient) UpdateServiceAccount(ctx context.Context, accessKey string, opts UpdateServiceAccountReq) error {
   359  	data, err := json.Marshal(opts)
   360  	if err != nil {
   361  		return err
   362  	}
   363  
   364  	econfigBytes, err := EncryptData(adm.getSecretKey(), data)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	queryValues := url.Values{}
   370  	queryValues.Set("accessKey", accessKey)
   371  
   372  	reqData := requestData{
   373  		relPath:     adminAPIPrefix + "/update-service-account",
   374  		content:     econfigBytes,
   375  		queryValues: queryValues,
   376  	}
   377  
   378  	// Execute POST on /minio/admin/v3/update-service-account to edit a service account
   379  	resp, err := adm.executeMethod(ctx, http.MethodPost, reqData)
   380  	defer closeResponse(resp)
   381  	if err != nil {
   382  		return err
   383  	}
   384  
   385  	if resp.StatusCode != http.StatusNoContent {
   386  		return httpRespToErrorResponse(resp)
   387  	}
   388  
   389  	return nil
   390  }
   391  
   392  // ListServiceAccountsResp is the response body of the list service accounts call
   393  type ListServiceAccountsResp struct {
   394  	Accounts []string `json:"accounts"`
   395  }
   396  
   397  // ListServiceAccounts - list service accounts belonging to the specified user
   398  func (adm *AdminClient) ListServiceAccounts(ctx context.Context, user string) (ListServiceAccountsResp, error) {
   399  	queryValues := url.Values{}
   400  	queryValues.Set("user", user)
   401  
   402  	reqData := requestData{
   403  		relPath:     adminAPIPrefix + "/list-service-accounts",
   404  		queryValues: queryValues,
   405  	}
   406  
   407  	// Execute GET on /minio/admin/v3/list-service-accounts
   408  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   409  	defer closeResponse(resp)
   410  	if err != nil {
   411  		return ListServiceAccountsResp{}, err
   412  	}
   413  
   414  	if resp.StatusCode != http.StatusOK {
   415  		return ListServiceAccountsResp{}, httpRespToErrorResponse(resp)
   416  	}
   417  
   418  	data, err := DecryptData(adm.getSecretKey(), resp.Body)
   419  	if err != nil {
   420  		return ListServiceAccountsResp{}, err
   421  	}
   422  
   423  	var listResp ListServiceAccountsResp
   424  	if err = json.Unmarshal(data, &listResp); err != nil {
   425  		return ListServiceAccountsResp{}, err
   426  	}
   427  	return listResp, nil
   428  }
   429  
   430  // InfoServiceAccountResp is the response body of the info service account call
   431  type InfoServiceAccountResp struct {
   432  	ParentUser    string `json:"parentUser"`
   433  	AccountStatus string `json:"accountStatus"`
   434  	ImpliedPolicy bool   `json:"impliedPolicy"`
   435  	Policy        string `json:"policy"`
   436  }
   437  
   438  // InfoServiceAccount - returns the info of service account belonging to the specified user
   439  func (adm *AdminClient) InfoServiceAccount(ctx context.Context, accessKey string) (InfoServiceAccountResp, error) {
   440  	queryValues := url.Values{}
   441  	queryValues.Set("accessKey", accessKey)
   442  
   443  	reqData := requestData{
   444  		relPath:     adminAPIPrefix + "/info-service-account",
   445  		queryValues: queryValues,
   446  	}
   447  
   448  	// Execute GET on /minio/admin/v3/info-service-account
   449  	resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
   450  	defer closeResponse(resp)
   451  	if err != nil {
   452  		return InfoServiceAccountResp{}, err
   453  	}
   454  
   455  	if resp.StatusCode != http.StatusOK {
   456  		return InfoServiceAccountResp{}, httpRespToErrorResponse(resp)
   457  	}
   458  
   459  	data, err := DecryptData(adm.getSecretKey(), resp.Body)
   460  	if err != nil {
   461  		return InfoServiceAccountResp{}, err
   462  	}
   463  
   464  	var infoResp InfoServiceAccountResp
   465  	if err = json.Unmarshal(data, &infoResp); err != nil {
   466  		return InfoServiceAccountResp{}, err
   467  	}
   468  	return infoResp, nil
   469  }
   470  
   471  // DeleteServiceAccount - delete a specified service account. The server will reject
   472  // the request if the service account does not belong to the user initiating the request
   473  func (adm *AdminClient) DeleteServiceAccount(ctx context.Context, serviceAccount string) error {
   474  	queryValues := url.Values{}
   475  	queryValues.Set("accessKey", serviceAccount)
   476  
   477  	reqData := requestData{
   478  		relPath:     adminAPIPrefix + "/delete-service-account",
   479  		queryValues: queryValues,
   480  	}
   481  
   482  	// Execute DELETE on /minio/admin/v3/delete-service-account
   483  	resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
   484  	defer closeResponse(resp)
   485  	if err != nil {
   486  		return err
   487  	}
   488  
   489  	if resp.StatusCode != http.StatusNoContent {
   490  		return httpRespToErrorResponse(resp)
   491  	}
   492  
   493  	return nil
   494  }