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