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 }