github.com/minio/console@v1.3.0/api/service_accounts_handlers.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "time" 24 25 "github.com/go-openapi/runtime/middleware" 26 "github.com/minio/console/api/operations" 27 saApi "github.com/minio/console/api/operations/service_account" 28 userApi "github.com/minio/console/api/operations/user" 29 "github.com/minio/console/models" 30 "github.com/minio/console/pkg/utils" 31 "github.com/minio/madmin-go/v3" 32 iampolicy "github.com/minio/pkg/v2/policy" 33 ) 34 35 func registerServiceAccountsHandlers(api *operations.ConsoleAPI) { 36 // Create Service Account 37 api.ServiceAccountCreateServiceAccountHandler = saApi.CreateServiceAccountHandlerFunc(func(params saApi.CreateServiceAccountParams, session *models.Principal) middleware.Responder { 38 creds, err := getCreateServiceAccountResponse(session, params) 39 if err != nil { 40 return saApi.NewCreateServiceAccountDefault(err.Code).WithPayload(err.APIError) 41 } 42 return saApi.NewCreateServiceAccountCreated().WithPayload(creds) 43 }) 44 // Create User Service Account 45 api.UserCreateAUserServiceAccountHandler = userApi.CreateAUserServiceAccountHandlerFunc(func(params userApi.CreateAUserServiceAccountParams, session *models.Principal) middleware.Responder { 46 creds, err := getCreateAUserServiceAccountResponse(session, params) 47 if err != nil { 48 return saApi.NewCreateServiceAccountDefault(err.Code).WithPayload(err.APIError) 49 } 50 return userApi.NewCreateAUserServiceAccountCreated().WithPayload(creds) 51 }) 52 // Create User Service Account 53 api.UserCreateServiceAccountCredentialsHandler = userApi.CreateServiceAccountCredentialsHandlerFunc(func(params userApi.CreateServiceAccountCredentialsParams, session *models.Principal) middleware.Responder { 54 creds, err := getCreateAUserServiceAccountCredsResponse(session, params) 55 if err != nil { 56 return saApi.NewCreateServiceAccountDefault(err.Code).WithPayload(err.APIError) 57 } 58 return userApi.NewCreateServiceAccountCredentialsCreated().WithPayload(creds) 59 }) 60 api.ServiceAccountCreateServiceAccountCredsHandler = saApi.CreateServiceAccountCredsHandlerFunc(func(params saApi.CreateServiceAccountCredsParams, session *models.Principal) middleware.Responder { 61 creds, err := getCreateServiceAccountCredsResponse(session, params) 62 if err != nil { 63 return saApi.NewCreateServiceAccountDefault(err.Code).WithPayload(err.APIError) 64 } 65 return userApi.NewCreateServiceAccountCredentialsCreated().WithPayload(creds) 66 }) 67 // List Service Accounts for User 68 api.ServiceAccountListUserServiceAccountsHandler = saApi.ListUserServiceAccountsHandlerFunc(func(params saApi.ListUserServiceAccountsParams, session *models.Principal) middleware.Responder { 69 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 70 defer cancel() 71 serviceAccounts, err := getUserServiceAccountsResponse(ctx, session, "") 72 if err != nil { 73 return saApi.NewListUserServiceAccountsDefault(err.Code).WithPayload(err.APIError) 74 } 75 return saApi.NewListUserServiceAccountsOK().WithPayload(serviceAccounts) 76 }) 77 78 // Delete a User's service account 79 api.ServiceAccountDeleteServiceAccountHandler = saApi.DeleteServiceAccountHandlerFunc(func(params saApi.DeleteServiceAccountParams, session *models.Principal) middleware.Responder { 80 if err := getDeleteServiceAccountResponse(session, params); err != nil { 81 return saApi.NewDeleteServiceAccountDefault(err.Code).WithPayload(err.APIError) 82 } 83 return saApi.NewDeleteServiceAccountNoContent() 84 }) 85 86 // List Service Accounts for User 87 api.UserListAUserServiceAccountsHandler = userApi.ListAUserServiceAccountsHandlerFunc(func(params userApi.ListAUserServiceAccountsParams, session *models.Principal) middleware.Responder { 88 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 89 defer cancel() 90 serviceAccounts, err := getUserServiceAccountsResponse(ctx, session, params.Name) 91 if err != nil { 92 return saApi.NewListUserServiceAccountsDefault(err.Code).WithPayload(err.APIError) 93 } 94 return saApi.NewListUserServiceAccountsOK().WithPayload(serviceAccounts) 95 }) 96 97 api.ServiceAccountGetServiceAccountHandler = saApi.GetServiceAccountHandlerFunc(func(params saApi.GetServiceAccountParams, session *models.Principal) middleware.Responder { 98 serviceAccounts, err := getServiceAccountInfo(session, params) 99 if err != nil { 100 return saApi.NewGetServiceAccountDefault(err.Code).WithPayload(err.APIError) 101 } 102 return saApi.NewGetServiceAccountOK().WithPayload(serviceAccounts) 103 }) 104 105 api.ServiceAccountUpdateServiceAccountHandler = saApi.UpdateServiceAccountHandlerFunc(func(params saApi.UpdateServiceAccountParams, session *models.Principal) middleware.Responder { 106 err := updateSetServiceAccountResponse(session, params) 107 if err != nil { 108 return saApi.NewUpdateServiceAccountDefault(err.Code).WithPayload(err.APIError) 109 } 110 return saApi.NewUpdateServiceAccountOK() 111 }) 112 113 // Delete multiple service accounts 114 api.ServiceAccountDeleteMultipleServiceAccountsHandler = saApi.DeleteMultipleServiceAccountsHandlerFunc(func(params saApi.DeleteMultipleServiceAccountsParams, session *models.Principal) middleware.Responder { 115 if err := getDeleteMultipleServiceAccountsResponse(session, params); err != nil { 116 return saApi.NewDeleteMultipleServiceAccountsDefault(err.Code).WithPayload(err.APIError) 117 } 118 return saApi.NewDeleteMultipleServiceAccountsNoContent() 119 }) 120 } 121 122 // createServiceAccount adds a service account to the userClient and assigns a policy to him if defined. 123 func createServiceAccount(ctx context.Context, userClient MinioAdmin, policy string, name string, description string, expiry *time.Time, comment string) (*models.ServiceAccountCreds, error) { 124 creds, err := userClient.addServiceAccount(ctx, policy, "", "", "", name, description, expiry, comment) 125 if err != nil { 126 return nil, err 127 } 128 return &models.ServiceAccountCreds{AccessKey: creds.AccessKey, SecretKey: creds.SecretKey, URL: getMinIOServer()}, nil 129 } 130 131 // createServiceAccount adds a service account with the given credentials to the 132 // userClient and assigns a policy to him if defined. 133 func createServiceAccountCreds(ctx context.Context, userClient MinioAdmin, policy string, accessKey string, secretKey string, name string, description string, expiry *time.Time, comment string) (*models.ServiceAccountCreds, error) { 134 creds, err := userClient.addServiceAccount(ctx, policy, "", accessKey, secretKey, name, description, expiry, comment) 135 if err != nil { 136 return nil, err 137 } 138 return &models.ServiceAccountCreds{AccessKey: creds.AccessKey, SecretKey: creds.SecretKey, URL: getMinIOServer()}, nil 139 } 140 141 // getCreateServiceAccountResponse creates a service account with the defined policy for the user that 142 // is requesting, it first gets the credentials of the user and creates a client which is going to 143 // make the call to create the Service Account 144 func getCreateServiceAccountResponse(session *models.Principal, params saApi.CreateServiceAccountParams) (*models.ServiceAccountCreds, *CodedAPIError) { 145 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 146 defer cancel() 147 148 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 149 if err != nil { 150 return nil, ErrorWithContext(ctx, err) 151 } 152 // create a MinIO user Admin Client interface implementation 153 // defining the client to be used 154 userAdminClient := AdminClient{Client: userAdmin} 155 156 var parsedExpiry time.Time 157 if params.Body.Expiry != "" { 158 parsedExpiry, err = time.Parse(time.RFC3339, params.Body.Expiry) 159 if err != nil { 160 return nil, ErrorWithContext(ctx, err) 161 } 162 } 163 saCreds, err := createServiceAccount(ctx, userAdminClient, params.Body.Policy, params.Body.Name, params.Body.Description, &parsedExpiry, params.Body.Comment) 164 if err != nil { 165 return nil, ErrorWithContext(ctx, err) 166 } 167 return saCreds, nil 168 } 169 170 // createServiceAccount adds a service account to a given user and assigns a policy to him if defined. 171 func createAUserServiceAccount(ctx context.Context, userClient MinioAdmin, policy string, user string, name string, description string, expiry *time.Time, comment string) (*models.ServiceAccountCreds, error) { 172 creds, err := userClient.addServiceAccount(ctx, policy, user, "", "", name, description, expiry, comment) 173 if err != nil { 174 return nil, err 175 } 176 return &models.ServiceAccountCreds{AccessKey: creds.AccessKey, SecretKey: creds.SecretKey, URL: getMinIOServer()}, nil 177 } 178 179 func createAUserServiceAccountCreds(ctx context.Context, userClient MinioAdmin, policy string, user string, accessKey string, secretKey string, name string, description string, expiry *time.Time, comment string) (*models.ServiceAccountCreds, error) { 180 creds, err := userClient.addServiceAccount(ctx, policy, user, accessKey, secretKey, name, description, expiry, comment) 181 if err != nil { 182 return nil, err 183 } 184 return &models.ServiceAccountCreds{AccessKey: creds.AccessKey, SecretKey: creds.SecretKey, URL: getMinIOServer()}, nil 185 } 186 187 // getCreateServiceAccountResponse creates a service account with the defined policy for the user that 188 // is requesting it ,it first gets the credentials of the user and creates a client which is going to 189 // make the call to create the Service Account 190 func getCreateAUserServiceAccountResponse(session *models.Principal, params userApi.CreateAUserServiceAccountParams) (*models.ServiceAccountCreds, *CodedAPIError) { 191 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 192 defer cancel() 193 194 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 195 if err != nil { 196 return nil, ErrorWithContext(ctx, err) 197 } 198 // create a MinIO user Admin Client interface implementation 199 // defining the client to be used 200 userAdminClient := AdminClient{Client: userAdmin} 201 name, err := utils.DecodeBase64(params.Name) 202 if err != nil { 203 return nil, ErrorWithContext(ctx, err) 204 } 205 206 var parsedExpiry time.Time 207 if params.Body.Expiry != "" { 208 parsedExpiry, err = time.Parse(time.RFC3339, params.Body.Expiry) 209 if err != nil { 210 return nil, ErrorWithContext(ctx, err) 211 } 212 } 213 saCreds, err := createAUserServiceAccount(ctx, userAdminClient, params.Body.Policy, name, params.Body.Name, params.Body.Description, &parsedExpiry, params.Body.Comment) 214 if err != nil { 215 return nil, ErrorWithContext(ctx, err) 216 } 217 return saCreds, nil 218 } 219 220 // getCreateServiceAccountCredsResponse creates a service account with the defined policy for the user that 221 // is requesting it, and with the credentials provided 222 func getCreateAUserServiceAccountCredsResponse(session *models.Principal, params userApi.CreateServiceAccountCredentialsParams) (*models.ServiceAccountCreds, *CodedAPIError) { 223 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 224 defer cancel() 225 226 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 227 if err != nil { 228 return nil, ErrorWithContext(ctx, err) 229 } 230 // create a MinIO user Admin Client interface implementation 231 // defining the client to be used 232 userAdminClient := AdminClient{Client: userAdmin} 233 serviceAccount := params.Body 234 user, err := utils.DecodeBase64(params.Name) 235 if err != nil { 236 return nil, ErrorWithContext(ctx, err) 237 } 238 if user == serviceAccount.AccessKey { 239 return nil, ErrorWithContext(ctx, errors.New("Access Key already in use")) 240 } 241 accounts, err := userAdminClient.listServiceAccounts(ctx, user) 242 if err != nil { 243 return nil, ErrorWithContext(ctx, err) 244 } 245 for i := 0; i < len(accounts.Accounts); i++ { 246 if accounts.Accounts[i].AccessKey == serviceAccount.AccessKey { 247 return nil, ErrorWithContext(ctx, errors.New("Access Key already in use")) 248 } 249 } 250 251 var parsedExpiry time.Time 252 if serviceAccount.Expiry != "" { 253 parsedExpiry, err = time.Parse(time.RFC3339, serviceAccount.Expiry) 254 if err != nil { 255 return nil, ErrorWithContext(ctx, err) 256 } 257 } 258 saCreds, err := createAUserServiceAccountCreds(ctx, userAdminClient, serviceAccount.Policy, user, serviceAccount.AccessKey, serviceAccount.SecretKey, serviceAccount.Name, serviceAccount.Description, &parsedExpiry, serviceAccount.Comment) 259 if err != nil { 260 return nil, ErrorWithContext(ctx, err) 261 } 262 return saCreds, nil 263 } 264 265 func getCreateServiceAccountCredsResponse(session *models.Principal, params saApi.CreateServiceAccountCredsParams) (*models.ServiceAccountCreds, *CodedAPIError) { 266 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 267 defer cancel() 268 serviceAccount := params.Body 269 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 270 if err != nil { 271 return nil, ErrorWithContext(ctx, err) 272 } 273 // create a MinIO user Admin Client interface implementation 274 // defining the client to be used 275 userAdminClient := AdminClient{Client: userAdmin} 276 277 if session.AccountAccessKey == serviceAccount.AccessKey { 278 return nil, ErrorWithContext(ctx, errors.New("Access Key already in use")) 279 } 280 281 accounts, err := userAdminClient.listServiceAccounts(ctx, "") 282 if err != nil { 283 return nil, ErrorWithContext(ctx, err) 284 } 285 286 for i := 0; i < len(accounts.Accounts); i++ { 287 if accounts.Accounts[i].AccessKey == serviceAccount.AccessKey { 288 return nil, ErrorWithContext(ctx, errors.New("Access Key already in use")) 289 } 290 } 291 292 var parsedExpiry time.Time 293 if params.Body.Expiry != "" { 294 parsedExpiry, err = time.Parse(time.RFC3339, params.Body.Expiry) 295 if err != nil { 296 return nil, ErrorWithContext(ctx, err) 297 } 298 } 299 300 saCreds, err := createServiceAccountCreds(ctx, userAdminClient, serviceAccount.Policy, serviceAccount.AccessKey, serviceAccount.SecretKey, params.Body.Name, params.Body.Description, &parsedExpiry, params.Body.Comment) 301 if err != nil { 302 return nil, ErrorWithContext(ctx, err) 303 } 304 return saCreds, nil 305 } 306 307 // getUserServiceAccount gets list of the user's service accounts 308 func getUserServiceAccounts(ctx context.Context, userClient MinioAdmin, user string) (models.ServiceAccounts, error) { 309 listServAccs, err := userClient.listServiceAccounts(ctx, user) 310 if err != nil { 311 return nil, err 312 } 313 saList := models.ServiceAccounts{} 314 315 for _, acc := range listServAccs.Accounts { 316 aInfo, err := userClient.infoServiceAccount(ctx, acc.AccessKey) 317 if err != nil { 318 continue 319 } 320 expiry := "" 321 if aInfo.Expiration != nil { 322 expiry = aInfo.Expiration.Format(time.RFC3339) 323 } 324 325 saList = append(saList, &models.ServiceAccountsItems0{ 326 AccountStatus: aInfo.AccountStatus, 327 Description: aInfo.Description, 328 Expiration: expiry, 329 Name: aInfo.Name, 330 AccessKey: acc.AccessKey, 331 }) 332 } 333 return saList, nil 334 } 335 336 // getUserServiceAccountsResponse authenticates the user and calls 337 // getUserServiceAccounts to list the user's service accounts 338 func getUserServiceAccountsResponse(ctx context.Context, session *models.Principal, user string) (models.ServiceAccounts, *CodedAPIError) { 339 userAdmin, err := NewMinioAdminClient(ctx, session) 340 if err != nil { 341 return nil, ErrorWithContext(ctx, err) 342 } 343 // create a MinIO user Admin Client interface implementation 344 // defining the client to be used 345 userAdminClient := AdminClient{Client: userAdmin} 346 user, err = utils.DecodeBase64(user) 347 if err != nil { 348 return nil, ErrorWithContext(ctx, err) 349 } 350 serviceAccounts, err := getUserServiceAccounts(ctx, userAdminClient, user) 351 if err != nil { 352 return nil, ErrorWithContext(ctx, err) 353 } 354 return serviceAccounts, nil 355 } 356 357 // deleteServiceAccount calls delete service account api 358 func deleteServiceAccount(ctx context.Context, userClient MinioAdmin, accessKey string) error { 359 return userClient.deleteServiceAccount(ctx, accessKey) 360 } 361 362 // getDeleteServiceAccountResponse authenticates the user and calls deleteServiceAccount 363 func getDeleteServiceAccountResponse(session *models.Principal, params saApi.DeleteServiceAccountParams) *CodedAPIError { 364 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 365 defer cancel() 366 accessKey, err := utils.DecodeBase64(params.AccessKey) 367 if err != nil { 368 return ErrorWithContext(ctx, err) 369 } 370 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 371 if err != nil { 372 return ErrorWithContext(ctx, err) 373 } 374 // create a MinIO user Admin Client interface implementation 375 // defining the client to be used 376 userAdminClient := AdminClient{Client: userAdmin} 377 if err := deleteServiceAccount(ctx, userAdminClient, accessKey); err != nil { 378 return ErrorWithContext(ctx, err) 379 } 380 return nil 381 } 382 383 // getServiceAccountDetails gets policy for a service account 384 func getServiceAccountDetails(ctx context.Context, userClient MinioAdmin, accessKey string) (*models.ServiceAccount, error) { 385 saInfo, err := userClient.infoServiceAccount(ctx, accessKey) 386 if err != nil { 387 return nil, err 388 } 389 390 var policyJSON string 391 var policy iampolicy.Policy 392 json.Unmarshal([]byte(saInfo.Policy), &policy) 393 if policy.Statements == nil { 394 policyJSON = "" 395 } else { 396 policyJSON = saInfo.Policy 397 } 398 399 expiry := "" 400 if saInfo.Expiration != nil { 401 expiry = saInfo.Expiration.Format(time.RFC3339) 402 } 403 404 sa := models.ServiceAccount{ 405 AccountStatus: saInfo.AccountStatus, 406 Description: saInfo.Description, 407 Expiration: expiry, 408 ImpliedPolicy: saInfo.ImpliedPolicy, 409 Name: saInfo.Name, 410 ParentUser: saInfo.ParentUser, 411 Policy: policyJSON, 412 } 413 return &sa, nil 414 } 415 416 // getServiceAccountInfo authenticates the user and calls 417 // getServiceAccountInfo to get the policy for a service account 418 func getServiceAccountInfo(session *models.Principal, params saApi.GetServiceAccountParams) (*models.ServiceAccount, *CodedAPIError) { 419 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 420 defer cancel() 421 accessKey, err := utils.DecodeBase64(params.AccessKey) 422 if err != nil { 423 return nil, ErrorWithContext(ctx, err) 424 } 425 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 426 if err != nil { 427 return nil, ErrorWithContext(ctx, err) 428 } 429 // create a MinIO user Admin Client interface implementation 430 // defining the client to be used 431 userAdminClient := AdminClient{Client: userAdmin} 432 433 serviceAccount, err := getServiceAccountDetails(ctx, userAdminClient, accessKey) 434 if err != nil { 435 return nil, ErrorWithContext(ctx, err) 436 } 437 438 return serviceAccount, nil 439 } 440 441 // setServiceAccountPolicy sets policy for a service account 442 func updateServiceAccountDetails(ctx context.Context, userClient MinioAdmin, accessKey string, policy string, expiry time.Time, name string, description string, status string, secretKey string) error { 443 req := madmin.UpdateServiceAccountReq{ 444 NewPolicy: json.RawMessage(policy), 445 NewSecretKey: secretKey, 446 NewStatus: status, 447 NewName: name, 448 NewDescription: description, 449 NewExpiration: &expiry, 450 } 451 452 err := userClient.updateServiceAccount(ctx, accessKey, req) 453 return err 454 } 455 456 // updateSetServiceAccountResponse authenticates the user and calls 457 // getSetServiceAccountPolicy to set the policy for a service account 458 func updateSetServiceAccountResponse(session *models.Principal, params saApi.UpdateServiceAccountParams) *CodedAPIError { 459 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 460 defer cancel() 461 accessKey, err := utils.DecodeBase64(params.AccessKey) 462 if err != nil { 463 return ErrorWithContext(ctx, err) 464 } 465 policy := *params.Body.Policy 466 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 467 if err != nil { 468 return ErrorWithContext(ctx, err) 469 } 470 // create a MinIO user Admin Client interface implementation 471 // defining the client to be used 472 userAdminClient := AdminClient{Client: userAdmin} 473 474 var parsedExpiry time.Time 475 if params.Body.Expiry != "" { 476 parsedExpiry, err = time.Parse(time.RFC3339, params.Body.Expiry) 477 if err != nil { 478 return ErrorWithContext(ctx, err) 479 } 480 } 481 err = updateServiceAccountDetails(ctx, userAdminClient, accessKey, policy, parsedExpiry, params.Body.Name, params.Body.Description, params.Body.Status, params.Body.SecretKey) 482 if err != nil { 483 return ErrorWithContext(ctx, err) 484 } 485 return nil 486 } 487 488 // getDeleteMultipleServiceAccountsResponse authenticates the user and calls deleteServiceAccount for each account listed in selectedSAs 489 func getDeleteMultipleServiceAccountsResponse(session *models.Principal, params saApi.DeleteMultipleServiceAccountsParams) *CodedAPIError { 490 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 491 defer cancel() 492 selectedSAs := params.SelectedSA 493 userAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 494 if err != nil { 495 return ErrorWithContext(ctx, err) 496 } 497 // create a MinIO user Admin Client interface implementation 498 // defining the client to be used 499 userAdminClient := AdminClient{Client: userAdmin} 500 for _, sa := range selectedSAs { 501 if err := deleteServiceAccount(ctx, userAdminClient, sa); err != nil { 502 return ErrorWithContext(ctx, err) 503 } 504 } 505 return nil 506 }