github.com/minio/console@v1.3.0/api/user_buckets.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/base64" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "strings" 26 "time" 27 28 "github.com/minio/minio-go/v7" 29 30 "github.com/minio/madmin-go/v3" 31 "github.com/minio/mc/cmd" 32 "github.com/minio/mc/pkg/probe" 33 "github.com/minio/minio-go/v7/pkg/sse" 34 "github.com/minio/minio-go/v7/pkg/tags" 35 36 "github.com/go-openapi/runtime/middleware" 37 "github.com/go-openapi/swag" 38 "github.com/minio/console/api/operations" 39 bucketApi "github.com/minio/console/api/operations/bucket" 40 "github.com/minio/console/models" 41 "github.com/minio/console/pkg/auth/token" 42 "github.com/minio/minio-go/v7/pkg/policy" 43 "github.com/minio/minio-go/v7/pkg/replication" 44 minioIAMPolicy "github.com/minio/pkg/v2/policy" 45 ) 46 47 func registerBucketsHandlers(api *operations.ConsoleAPI) { 48 // list buckets 49 api.BucketListBucketsHandler = bucketApi.ListBucketsHandlerFunc(func(params bucketApi.ListBucketsParams, session *models.Principal) middleware.Responder { 50 listBucketsResponse, err := getListBucketsResponse(session, params) 51 if err != nil { 52 return bucketApi.NewListBucketsDefault(err.Code).WithPayload(err.APIError) 53 } 54 return bucketApi.NewListBucketsOK().WithPayload(listBucketsResponse) 55 }) 56 // make bucket 57 api.BucketMakeBucketHandler = bucketApi.MakeBucketHandlerFunc(func(params bucketApi.MakeBucketParams, session *models.Principal) middleware.Responder { 58 makeBucketResponse, err := getMakeBucketResponse(session, params) 59 if err != nil { 60 return bucketApi.NewMakeBucketDefault(err.Code).WithPayload(err.APIError) 61 } 62 return bucketApi.NewMakeBucketOK().WithPayload(makeBucketResponse) 63 }) 64 // delete bucket 65 api.BucketDeleteBucketHandler = bucketApi.DeleteBucketHandlerFunc(func(params bucketApi.DeleteBucketParams, session *models.Principal) middleware.Responder { 66 if err := getDeleteBucketResponse(session, params); err != nil { 67 return bucketApi.NewMakeBucketDefault(err.Code).WithPayload(err.APIError) 68 } 69 return bucketApi.NewDeleteBucketNoContent() 70 }) 71 // get bucket info 72 api.BucketBucketInfoHandler = bucketApi.BucketInfoHandlerFunc(func(params bucketApi.BucketInfoParams, session *models.Principal) middleware.Responder { 73 bucketInfoResp, err := getBucketInfoResponse(session, params) 74 if err != nil { 75 return bucketApi.NewBucketInfoDefault(err.Code).WithPayload(err.APIError) 76 } 77 78 return bucketApi.NewBucketInfoOK().WithPayload(bucketInfoResp) 79 }) 80 // set bucket policy 81 api.BucketBucketSetPolicyHandler = bucketApi.BucketSetPolicyHandlerFunc(func(params bucketApi.BucketSetPolicyParams, session *models.Principal) middleware.Responder { 82 bucketSetPolicyResp, err := getBucketSetPolicyResponse(session, params) 83 if err != nil { 84 return bucketApi.NewBucketSetPolicyDefault(err.Code).WithPayload(err.APIError) 85 } 86 return bucketApi.NewBucketSetPolicyOK().WithPayload(bucketSetPolicyResp) 87 }) 88 // set bucket tags 89 api.BucketPutBucketTagsHandler = bucketApi.PutBucketTagsHandlerFunc(func(params bucketApi.PutBucketTagsParams, session *models.Principal) middleware.Responder { 90 err := getPutBucketTagsResponse(session, params) 91 if err != nil { 92 return bucketApi.NewPutBucketTagsDefault(err.Code).WithPayload(err.APIError) 93 } 94 return bucketApi.NewPutBucketTagsOK() 95 }) 96 // get bucket versioning 97 api.BucketGetBucketVersioningHandler = bucketApi.GetBucketVersioningHandlerFunc(func(params bucketApi.GetBucketVersioningParams, session *models.Principal) middleware.Responder { 98 getBucketVersioning, err := getBucketVersionedResponse(session, params) 99 if err != nil { 100 return bucketApi.NewGetBucketVersioningDefault(err.Code).WithPayload(err.APIError) 101 } 102 return bucketApi.NewGetBucketVersioningOK().WithPayload(getBucketVersioning) 103 }) 104 // update bucket versioning 105 api.BucketSetBucketVersioningHandler = bucketApi.SetBucketVersioningHandlerFunc(func(params bucketApi.SetBucketVersioningParams, session *models.Principal) middleware.Responder { 106 err := setBucketVersioningResponse(session, params) 107 if err != nil { 108 return bucketApi.NewSetBucketVersioningDefault(err.Code).WithPayload(err.APIError) 109 } 110 return bucketApi.NewSetBucketVersioningCreated() 111 }) 112 // get bucket replication 113 api.BucketGetBucketReplicationHandler = bucketApi.GetBucketReplicationHandlerFunc(func(params bucketApi.GetBucketReplicationParams, session *models.Principal) middleware.Responder { 114 getBucketReplication, err := getBucketReplicationResponse(session, params) 115 if err != nil { 116 return bucketApi.NewGetBucketReplicationDefault(err.Code).WithPayload(err.APIError) 117 } 118 return bucketApi.NewGetBucketReplicationOK().WithPayload(getBucketReplication) 119 }) 120 // get single bucket replication rule 121 api.BucketGetBucketReplicationRuleHandler = bucketApi.GetBucketReplicationRuleHandlerFunc(func(params bucketApi.GetBucketReplicationRuleParams, session *models.Principal) middleware.Responder { 122 getBucketReplicationRule, err := getBucketReplicationRuleResponse(session, params) 123 if err != nil { 124 return bucketApi.NewGetBucketReplicationRuleDefault(err.Code).WithPayload(err.APIError) 125 } 126 return bucketApi.NewGetBucketReplicationRuleOK().WithPayload(getBucketReplicationRule) 127 }) 128 129 // enable bucket encryption 130 api.BucketEnableBucketEncryptionHandler = bucketApi.EnableBucketEncryptionHandlerFunc(func(params bucketApi.EnableBucketEncryptionParams, session *models.Principal) middleware.Responder { 131 if err := enableBucketEncryptionResponse(session, params); err != nil { 132 return bucketApi.NewEnableBucketEncryptionDefault(err.Code).WithPayload(err.APIError) 133 } 134 return bucketApi.NewEnableBucketEncryptionOK() 135 }) 136 // disable bucket encryption 137 api.BucketDisableBucketEncryptionHandler = bucketApi.DisableBucketEncryptionHandlerFunc(func(params bucketApi.DisableBucketEncryptionParams, session *models.Principal) middleware.Responder { 138 if err := disableBucketEncryptionResponse(session, params); err != nil { 139 return bucketApi.NewDisableBucketEncryptionDefault(err.Code).WithPayload(err.APIError) 140 } 141 return bucketApi.NewDisableBucketEncryptionOK() 142 }) 143 // get bucket encryption info 144 api.BucketGetBucketEncryptionInfoHandler = bucketApi.GetBucketEncryptionInfoHandlerFunc(func(params bucketApi.GetBucketEncryptionInfoParams, session *models.Principal) middleware.Responder { 145 response, err := getBucketEncryptionInfoResponse(session, params) 146 if err != nil { 147 return bucketApi.NewGetBucketEncryptionInfoDefault(err.Code).WithPayload(err.APIError) 148 } 149 return bucketApi.NewGetBucketEncryptionInfoOK().WithPayload(response) 150 }) 151 // set bucket retention config 152 api.BucketSetBucketRetentionConfigHandler = bucketApi.SetBucketRetentionConfigHandlerFunc(func(params bucketApi.SetBucketRetentionConfigParams, session *models.Principal) middleware.Responder { 153 if err := getSetBucketRetentionConfigResponse(session, params); err != nil { 154 return bucketApi.NewSetBucketRetentionConfigDefault(err.Code).WithPayload(err.APIError) 155 } 156 return bucketApi.NewSetBucketRetentionConfigOK() 157 }) 158 // get bucket retention config 159 api.BucketGetBucketRetentionConfigHandler = bucketApi.GetBucketRetentionConfigHandlerFunc(func(params bucketApi.GetBucketRetentionConfigParams, session *models.Principal) middleware.Responder { 160 response, err := getBucketRetentionConfigResponse(session, params) 161 if err != nil { 162 return bucketApi.NewGetBucketRetentionConfigDefault(err.Code).WithPayload(err.APIError) 163 } 164 return bucketApi.NewGetBucketRetentionConfigOK().WithPayload(response) 165 }) 166 // get bucket object locking status 167 api.BucketGetBucketObjectLockingStatusHandler = bucketApi.GetBucketObjectLockingStatusHandlerFunc(func(params bucketApi.GetBucketObjectLockingStatusParams, session *models.Principal) middleware.Responder { 168 getBucketObjectLockingStatus, err := getBucketObjectLockingResponse(session, params) 169 if err != nil { 170 return bucketApi.NewGetBucketObjectLockingStatusDefault(err.Code).WithPayload(err.APIError) 171 } 172 return bucketApi.NewGetBucketObjectLockingStatusOK().WithPayload(getBucketObjectLockingStatus) 173 }) 174 // get objects rewind for a bucket 175 api.BucketGetBucketRewindHandler = bucketApi.GetBucketRewindHandlerFunc(func(params bucketApi.GetBucketRewindParams, session *models.Principal) middleware.Responder { 176 getBucketRewind, err := getBucketRewindResponse(session, params) 177 if err != nil { 178 return bucketApi.NewGetBucketRewindDefault(err.Code).WithPayload(err.APIError) 179 } 180 return bucketApi.NewGetBucketRewindOK().WithPayload(getBucketRewind) 181 }) 182 // get max allowed share link expiration time 183 api.BucketGetMaxShareLinkExpHandler = bucketApi.GetMaxShareLinkExpHandlerFunc(func(params bucketApi.GetMaxShareLinkExpParams, session *models.Principal) middleware.Responder { 184 val, err := getMaxShareLinkExpirationResponse(session, params) 185 if err != nil { 186 return bucketApi.NewGetMaxShareLinkExpDefault(err.Code).WithPayload(err.APIError) 187 } 188 return bucketApi.NewGetMaxShareLinkExpOK().WithPayload(val) 189 }) 190 } 191 192 type VersionState string 193 194 const ( 195 VersionEnable VersionState = "enable" 196 VersionSuspend VersionState = "suspend" 197 ) 198 199 // removeBucket deletes a bucket 200 func doSetVersioning(ctx context.Context, client MCClient, state VersionState, excludePrefix []string, excludeFolders bool) error { 201 err := client.setVersioning(ctx, string(state), excludePrefix, excludeFolders) 202 if err != nil { 203 return err.Cause 204 } 205 206 return nil 207 } 208 209 func setBucketVersioningResponse(session *models.Principal, params bucketApi.SetBucketVersioningParams) *CodedAPIError { 210 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 211 defer cancel() 212 213 bucketName := params.BucketName 214 s3Client, err := newS3BucketClient(session, bucketName, "", getClientIP(params.HTTPRequest)) 215 if err != nil { 216 return ErrorWithContext(ctx, err) 217 } 218 // create a mc S3Client interface implementation 219 // defining the client to be used 220 amcClient := mcClient{client: s3Client} 221 222 versioningState := VersionSuspend 223 224 if params.Body.Enabled { 225 versioningState = VersionEnable 226 } 227 228 var excludePrefixes []string 229 230 if params.Body.ExcludePrefixes != nil { 231 excludePrefixes = params.Body.ExcludePrefixes 232 } 233 234 excludeFolders := params.Body.ExcludeFolders 235 236 if err := doSetVersioning(ctx, amcClient, versioningState, excludePrefixes, excludeFolders); err != nil { 237 return ErrorWithContext(ctx, fmt.Errorf("error setting versioning for bucket: %s", err)) 238 } 239 return nil 240 } 241 242 func getBucketReplicationResponse(session *models.Principal, params bucketApi.GetBucketReplicationParams) (*models.BucketReplicationResponse, *CodedAPIError) { 243 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 244 defer cancel() 245 246 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 247 if err != nil { 248 return nil, ErrorWithContext(ctx, err) 249 } 250 // create a minioClient interface implementation 251 // defining the client to be used 252 minioClient := minioClient{client: mClient} 253 254 // we will tolerate this call failing 255 res, err := minioClient.getBucketReplication(ctx, params.BucketName) 256 if err != nil { 257 ErrorWithContext(ctx, err) 258 } 259 260 var rules []*models.BucketReplicationRule 261 262 for _, rule := range res.Rules { 263 repDelMarkerStatus := false 264 if rule.DeleteMarkerReplication.Status == "enable" { 265 repDelMarkerStatus = true 266 } 267 repDelStatus := false 268 if rule.DeleteReplication.Status == "enable" { 269 repDelMarkerStatus = true 270 } 271 272 rules = append(rules, &models.BucketReplicationRule{ 273 DeleteMarkerReplication: repDelMarkerStatus, 274 DeletesReplication: repDelStatus, 275 Destination: &models.BucketReplicationDestination{Bucket: rule.Destination.Bucket}, 276 Tags: rule.Tags(), 277 Prefix: rule.Prefix(), 278 ID: rule.ID, 279 Priority: int32(rule.Priority), 280 Status: string(rule.Status), 281 StorageClass: rule.Destination.StorageClass, 282 }) 283 } 284 285 // serialize output 286 bucketRResponse := &models.BucketReplicationResponse{ 287 Rules: rules, 288 } 289 return bucketRResponse, nil 290 } 291 292 func getBucketReplicationRuleResponse(session *models.Principal, params bucketApi.GetBucketReplicationRuleParams) (*models.BucketReplicationRule, *CodedAPIError) { 293 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 294 defer cancel() 295 296 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 297 if err != nil { 298 return nil, ErrorWithContext(ctx, err) 299 } 300 // create a minioClient interface implementation 301 302 // defining the client to be used 303 minioClient := minioClient{client: mClient} 304 305 replicationRules, err := minioClient.getBucketReplication(ctx, params.BucketName) 306 if err != nil { 307 return nil, ErrorWithContext(ctx, err) 308 } 309 310 var foundRule replication.Rule 311 found := false 312 313 for i := range replicationRules.Rules { 314 if replicationRules.Rules[i].ID == params.RuleID { 315 foundRule = replicationRules.Rules[i] 316 found = true 317 break 318 } 319 } 320 321 if !found { 322 return nil, ErrorWithContext(ctx, errors.New("no rule is set with this ID")) 323 } 324 325 repDelMarkerStatus := false 326 if foundRule.DeleteMarkerReplication.Status == "Enabled" { 327 repDelMarkerStatus = true 328 } 329 repDelStatus := false 330 if foundRule.DeleteReplication.Status == "Enabled" { 331 repDelStatus = true 332 } 333 existingObjects := false 334 if foundRule.ExistingObjectReplication.Status == "Enabled" { 335 existingObjects = true 336 } 337 metadataModifications := false 338 if foundRule.SourceSelectionCriteria.ReplicaModifications.Status == "Enabled" { 339 metadataModifications = true 340 } 341 342 returnRule := &models.BucketReplicationRule{ 343 DeleteMarkerReplication: repDelMarkerStatus, 344 DeletesReplication: repDelStatus, 345 Destination: &models.BucketReplicationDestination{Bucket: foundRule.Destination.Bucket}, 346 Tags: foundRule.Tags(), 347 Prefix: foundRule.Prefix(), 348 ID: foundRule.ID, 349 Priority: int32(foundRule.Priority), 350 Status: string(foundRule.Status), 351 StorageClass: foundRule.Destination.StorageClass, 352 ExistingObjects: existingObjects, 353 MetadataReplication: metadataModifications, 354 } 355 356 return returnRule, nil 357 } 358 359 func getBucketVersionedResponse(session *models.Principal, params bucketApi.GetBucketVersioningParams) (*models.BucketVersioningResponse, *CodedAPIError) { 360 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 361 defer cancel() 362 363 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 364 if err != nil { 365 return nil, ErrorWithContext(ctx, err) 366 } 367 368 // create a minioClient interface implementation 369 // defining the client to be used 370 minioClient := minioClient{client: mClient} 371 372 // we will tolerate this call failing 373 res, err := minioClient.getBucketVersioning(ctx, params.BucketName) 374 if err != nil { 375 ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err)) 376 } 377 378 excludedPrefixes := make([]*models.BucketVersioningResponseExcludedPrefixesItems0, len(res.ExcludedPrefixes)) 379 for i, v := range res.ExcludedPrefixes { 380 excludedPrefixes[i] = &models.BucketVersioningResponseExcludedPrefixesItems0{ 381 Prefix: v.Prefix, 382 } 383 } 384 385 // serialize output 386 bucketVResponse := &models.BucketVersioningResponse{ 387 ExcludeFolders: res.ExcludeFolders, 388 ExcludedPrefixes: excludedPrefixes, 389 MFADelete: res.MFADelete, 390 Status: res.Status, 391 } 392 return bucketVResponse, nil 393 } 394 395 // getAccountBuckets fetches a list of all buckets allowed to that particular client from MinIO Servers 396 func getAccountBuckets(ctx context.Context, client MinioAdmin) ([]*models.Bucket, error) { 397 info, err := client.AccountInfo(ctx) 398 if err != nil { 399 return []*models.Bucket{}, err 400 } 401 bucketInfos := []*models.Bucket{} 402 for _, bucket := range info.Buckets { 403 bucketElem := &models.Bucket{ 404 CreationDate: bucket.Created.Format(time.RFC3339), 405 Details: &models.BucketDetails{ 406 Quota: nil, 407 }, 408 RwAccess: &models.BucketRwAccess{ 409 Read: bucket.Access.Read, 410 Write: bucket.Access.Write, 411 }, 412 Name: swag.String(bucket.Name), 413 Objects: int64(bucket.Objects), 414 Size: int64(bucket.Size), 415 } 416 417 if bucket.Details != nil { 418 if bucket.Details.Tagging != nil { 419 bucketElem.Details.Tags = bucket.Details.Tagging.ToMap() 420 } 421 422 bucketElem.Details.Locking = bucket.Details.Locking 423 bucketElem.Details.Replication = bucket.Details.Replication 424 bucketElem.Details.Versioning = bucket.Details.Versioning 425 bucketElem.Details.VersioningSuspended = bucket.Details.VersioningSuspended 426 if bucket.Details.Quota != nil { 427 bucketElem.Details.Quota = &models.BucketDetailsQuota{ 428 Quota: int64(bucket.Details.Quota.Quota), 429 Type: string(bucket.Details.Quota.Type), 430 } 431 } 432 } 433 434 bucketInfos = append(bucketInfos, bucketElem) 435 } 436 return bucketInfos, nil 437 } 438 439 // getListBucketsResponse performs listBuckets() and serializes it to the handler's output 440 func getListBucketsResponse(session *models.Principal, params bucketApi.ListBucketsParams) (*models.ListBucketsResponse, *CodedAPIError) { 441 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 442 defer cancel() 443 444 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 445 if err != nil { 446 return nil, ErrorWithContext(ctx, err) 447 } 448 // create a minioClient interface implementation 449 // defining the client to be used 450 adminClient := AdminClient{Client: mAdmin} 451 buckets, err := getAccountBuckets(ctx, adminClient) 452 if err != nil { 453 return nil, ErrorWithContext(ctx, err) 454 } 455 456 // serialize output 457 listBucketsResponse := &models.ListBucketsResponse{ 458 Buckets: buckets, 459 Total: int64(len(buckets)), 460 } 461 return listBucketsResponse, nil 462 } 463 464 // makeBucket creates a bucket for an specific minio client 465 func makeBucket(ctx context.Context, client MinioClient, bucketName string, objectLocking bool) error { 466 // creates a new bucket with bucketName with a context to control cancellations and timeouts. 467 return client.makeBucketWithContext(ctx, bucketName, "", objectLocking) 468 } 469 470 // getMakeBucketResponse performs makeBucket() to create a bucket with its access policy 471 func getMakeBucketResponse(session *models.Principal, params bucketApi.MakeBucketParams) (*models.MakeBucketsResponse, *CodedAPIError) { 472 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 473 defer cancel() 474 // bucket request needed to proceed 475 br := params.Body 476 if br == nil { 477 return nil, ErrorWithContext(ctx, ErrBucketBodyNotInRequest) 478 } 479 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 480 if err != nil { 481 return nil, ErrorWithContext(ctx, err) 482 } 483 // create a minioClient interface implementation 484 // defining the client to be used 485 minioClient := minioClient{client: mClient} 486 487 // if we need retention, then object locking needs to be enabled 488 if br.Retention != nil { 489 br.Locking = true 490 } 491 492 if err := makeBucket(ctx, minioClient, *br.Name, br.Locking); err != nil { 493 return nil, ErrorWithContext(ctx, err) 494 } 495 496 // make sure to delete bucket if an errors occurs after bucket was created 497 defer func() { 498 if err != nil { 499 ErrorWithContext(ctx, fmt.Errorf("error creating bucket: %v", err)) 500 if err := removeBucket(minioClient, *br.Name); err != nil { 501 ErrorWithContext(ctx, fmt.Errorf("error removing bucket: %v", err)) 502 } 503 } 504 }() 505 506 versioningEnabled := false 507 508 if br.Versioning != nil && br.Versioning.Enabled { 509 versioningEnabled = true 510 } 511 512 // enable versioning if indicated or retention enabled 513 if versioningEnabled || br.Retention != nil { 514 s3Client, err := newS3BucketClient(session, *br.Name, "", getClientIP(params.HTTPRequest)) 515 if err != nil { 516 return nil, ErrorWithContext(ctx, err) 517 } 518 // create a mc S3Client interface implementation 519 // defining the client to be used 520 amcClient := mcClient{client: s3Client} 521 522 excludePrefixes := []string{} 523 excludeFolders := false 524 525 if br.Versioning.ExcludeFolders && !br.Locking { 526 excludeFolders = true 527 } 528 529 if br.Versioning.ExcludePrefixes != nil && !br.Locking { 530 excludePrefixes = br.Versioning.ExcludePrefixes 531 } 532 533 if err = doSetVersioning(ctx, amcClient, VersionEnable, excludePrefixes, excludeFolders); err != nil { 534 return nil, ErrorWithContext(ctx, fmt.Errorf("error setting versioning for bucket: %s", err)) 535 } 536 } 537 538 // if it has support for 539 if br.Quota != nil && br.Quota.Enabled != nil && *br.Quota.Enabled { 540 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 541 if err != nil { 542 return nil, ErrorWithContext(ctx, err) 543 } 544 // create a minioClient interface implementation 545 // defining the client to be used 546 adminClient := AdminClient{Client: mAdmin} 547 // we will tolerate this call failing 548 if err := setBucketQuota(ctx, &adminClient, br.Name, br.Quota); err != nil { 549 ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err)) 550 } 551 } 552 553 // Set Bucket Retention Configuration if defined 554 if br.Retention != nil { 555 err = setBucketRetentionConfig(ctx, minioClient, *br.Name, *br.Retention.Mode, *br.Retention.Unit, br.Retention.Validity) 556 if err != nil { 557 return nil, ErrorWithContext(ctx, err) 558 } 559 } 560 return &models.MakeBucketsResponse{BucketName: *br.Name}, nil 561 } 562 563 // setBucketAccessPolicy set the access permissions on an existing bucket. 564 func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName string, access models.BucketAccess, policyDefinition string) error { 565 if strings.TrimSpace(bucketName) == "" { 566 return fmt.Errorf("error: bucket name not present") 567 } 568 if strings.TrimSpace(string(access)) == "" { 569 return fmt.Errorf("error: bucket access not present") 570 } 571 // Prepare policyJSON corresponding to the access type 572 if access != models.BucketAccessPRIVATE && access != models.BucketAccessPUBLIC && access != models.BucketAccessCUSTOM { 573 return fmt.Errorf("access: `%s` not supported", access) 574 } 575 576 bucketAccessPolicy := policy.BucketAccessPolicy{Version: minioIAMPolicy.DefaultVersion} 577 if access == models.BucketAccessCUSTOM { 578 err := client.setBucketPolicyWithContext(ctx, bucketName, policyDefinition) 579 if err != nil { 580 return err 581 } 582 return nil 583 } 584 bucketPolicy := consoleAccess2policyAccess(access) 585 bucketAccessPolicy.Statements = policy.SetPolicy(bucketAccessPolicy.Statements, 586 bucketPolicy, bucketName, "") 587 policyJSON, err := json.Marshal(bucketAccessPolicy) 588 if err != nil { 589 return err 590 } 591 return client.setBucketPolicyWithContext(ctx, bucketName, string(policyJSON)) 592 } 593 594 // getBucketSetPolicyResponse calls setBucketAccessPolicy() to set a access policy to a bucket 595 // and returns the serialized output. 596 func getBucketSetPolicyResponse(session *models.Principal, params bucketApi.BucketSetPolicyParams) (*models.Bucket, *CodedAPIError) { 597 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 598 defer cancel() 599 600 // get updated bucket details and return it 601 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 602 if err != nil { 603 return nil, ErrorWithContext(ctx, err) 604 } 605 // create a minioClient interface implementation 606 // defining the client to be used 607 minioClient := minioClient{client: mClient} 608 609 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 610 if err != nil { 611 return nil, ErrorWithContext(ctx, err) 612 } 613 // create a minioClient interface implementation 614 // defining the client to be used 615 adminClient := AdminClient{Client: mAdmin} 616 bucketName := params.Name 617 req := params.Body 618 if err := setBucketAccessPolicy(ctx, minioClient, bucketName, *req.Access, req.Definition); err != nil { 619 return nil, ErrorWithContext(ctx, err) 620 } 621 // set bucket access policy 622 bucket, err := getBucketInfo(ctx, minioClient, adminClient, bucketName) 623 if err != nil { 624 return nil, ErrorWithContext(ctx, err) 625 } 626 return bucket, nil 627 } 628 629 // putBucketTags sets tags for a bucket 630 func getPutBucketTagsResponse(session *models.Principal, params bucketApi.PutBucketTagsParams) *CodedAPIError { 631 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 632 defer cancel() 633 634 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 635 if err != nil { 636 return ErrorWithContext(ctx, err) 637 } 638 // create a minioClient interface implementation 639 // defining the client to be used 640 minioClient := minioClient{client: mClient} 641 642 req := params.Body 643 bucketName := params.BucketName 644 645 newTagSet, err := tags.NewTags(req.Tags, true) 646 if err != nil { 647 return ErrorWithContext(ctx, err) 648 } 649 650 err = minioClient.SetBucketTagging(ctx, bucketName, newTagSet) 651 if err != nil { 652 return ErrorWithContext(ctx, err) 653 } 654 return nil 655 } 656 657 // removeBucket deletes a bucket 658 func removeBucket(client MinioClient, bucketName string) error { 659 return client.removeBucket(context.Background(), bucketName) 660 } 661 662 // getDeleteBucketResponse performs removeBucket() to delete a bucket 663 func getDeleteBucketResponse(session *models.Principal, params bucketApi.DeleteBucketParams) *CodedAPIError { 664 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 665 defer cancel() 666 if params.Name == "" { 667 return ErrorWithContext(ctx, ErrBucketNameNotInRequest) 668 } 669 bucketName := params.Name 670 671 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 672 if err != nil { 673 return ErrorWithContext(ctx, err) 674 } 675 // create a minioClient interface implementation 676 // defining the client to be used 677 minioClient := minioClient{client: mClient} 678 if err := removeBucket(minioClient, bucketName); err != nil { 679 resp := ErrorWithContext(ctx, err) 680 errResp := minio.ToErrorResponse(err) 681 if errResp.Code == "NoSuchBucket" { 682 resp.Code = 404 683 } 684 return resp 685 } 686 return nil 687 } 688 689 // getBucketInfo return bucket information including name, policy access, size and creation date 690 func getBucketInfo(ctx context.Context, client MinioClient, adminClient MinioAdmin, bucketName string) (*models.Bucket, error) { 691 var bucketAccess models.BucketAccess 692 policyStr, err := client.getBucketPolicy(context.Background(), bucketName) 693 if err != nil { 694 // we can tolerate this errors 695 ErrorWithContext(ctx, fmt.Errorf("error getting bucket policy: %v", err)) 696 } 697 698 if policyStr == "" { 699 bucketAccess = models.BucketAccessPRIVATE 700 } else { 701 var p policy.BucketAccessPolicy 702 if err = json.Unmarshal([]byte(policyStr), &p); err != nil { 703 return nil, err 704 } 705 policyAccess := policy.GetPolicy(p.Statements, bucketName, "") 706 if len(p.Statements) > 0 && policyAccess == policy.BucketPolicyNone { 707 bucketAccess = models.BucketAccessCUSTOM 708 } else { 709 bucketAccess = policyAccess2consoleAccess(policyAccess) 710 } 711 } 712 bucketTags, err := client.GetBucketTagging(ctx, bucketName) 713 if err != nil { 714 // we can tolerate this errors 715 ErrorWithContext(ctx, fmt.Errorf("error getting bucket tags: %v", err)) 716 } 717 bucketDetails := &models.BucketDetails{} 718 if bucketTags != nil { 719 bucketDetails.Tags = bucketTags.ToMap() 720 } 721 722 info, err := adminClient.AccountInfo(ctx) 723 if err != nil { 724 return nil, err 725 } 726 727 var bucketInfo madmin.BucketAccessInfo 728 729 for _, bucket := range info.Buckets { 730 if bucket.Name == bucketName { 731 bucketInfo = bucket 732 } 733 } 734 735 return &models.Bucket{ 736 Name: &bucketName, 737 Access: &bucketAccess, 738 Definition: policyStr, 739 CreationDate: bucketInfo.Created.Format(time.RFC3339), 740 Size: int64(bucketInfo.Size), 741 Details: bucketDetails, 742 Objects: int64(bucketInfo.Objects), 743 }, nil 744 } 745 746 // getBucketInfoResponse calls getBucketInfo() to get the bucket's info 747 func getBucketInfoResponse(session *models.Principal, params bucketApi.BucketInfoParams) (*models.Bucket, *CodedAPIError) { 748 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 749 defer cancel() 750 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 751 if err != nil { 752 return nil, ErrorWithContext(ctx, err) 753 } 754 // create a minioClient interface implementation 755 // defining the client to be used 756 minioClient := minioClient{client: mClient} 757 758 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 759 if err != nil { 760 return nil, ErrorWithContext(ctx, err) 761 } 762 // create a minioClient interface implementation 763 // defining the client to be used 764 adminClient := AdminClient{Client: mAdmin} 765 766 bucket, err := getBucketInfo(ctx, minioClient, adminClient, params.Name) 767 if err != nil { 768 return nil, ErrorWithContext(ctx, err) 769 } 770 return bucket, nil 771 } 772 773 // policyAccess2consoleAccess gets the equivalent of policy.BucketPolicy to models.BucketAccess 774 func policyAccess2consoleAccess(bucketPolicy policy.BucketPolicy) (bucketAccess models.BucketAccess) { 775 switch bucketPolicy { 776 case policy.BucketPolicyReadWrite: 777 bucketAccess = models.BucketAccessPUBLIC 778 case policy.BucketPolicyNone: 779 bucketAccess = models.BucketAccessPRIVATE 780 default: 781 bucketAccess = models.BucketAccessCUSTOM 782 } 783 return bucketAccess 784 } 785 786 // consoleAccess2policyAccess gets the equivalent of models.BucketAccess to policy.BucketPolicy 787 func consoleAccess2policyAccess(bucketAccess models.BucketAccess) (bucketPolicy policy.BucketPolicy) { 788 switch bucketAccess { 789 case models.BucketAccessPUBLIC: 790 bucketPolicy = policy.BucketPolicyReadWrite 791 case models.BucketAccessPRIVATE: 792 bucketPolicy = policy.BucketPolicyNone 793 } 794 return bucketPolicy 795 } 796 797 // enableBucketEncryption will enable bucket encryption based on two encryption algorithms, sse-s3 (server side encryption with external KMS) or sse-kms (aws s3 kms key) 798 func enableBucketEncryption(ctx context.Context, client MinioClient, bucketName string, encryptionType models.BucketEncryptionType, kmsKeyID string) error { 799 var config *sse.Configuration 800 switch encryptionType { 801 case models.BucketEncryptionTypeSseDashKms: 802 config = sse.NewConfigurationSSEKMS(kmsKeyID) 803 case models.BucketEncryptionTypeSseDashS3: 804 config = sse.NewConfigurationSSES3() 805 default: 806 return ErrInvalidEncryptionAlgorithm 807 } 808 return client.setBucketEncryption(ctx, bucketName, config) 809 } 810 811 // enableBucketEncryptionResponse calls enableBucketEncryption() to create new encryption configuration for provided bucket name 812 func enableBucketEncryptionResponse(session *models.Principal, params bucketApi.EnableBucketEncryptionParams) *CodedAPIError { 813 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 814 defer cancel() 815 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 816 if err != nil { 817 return ErrorWithContext(ctx, err) 818 } 819 // create a minioClient interface implementation 820 // defining the client to be used 821 minioClient := minioClient{client: mClient} 822 if err := enableBucketEncryption(ctx, minioClient, params.BucketName, *params.Body.EncType, params.Body.KmsKeyID); err != nil { 823 return ErrorWithContext(ctx, err) 824 } 825 return nil 826 } 827 828 // disableBucketEncryption will disable bucket for the provided bucket name 829 func disableBucketEncryption(ctx context.Context, client MinioClient, bucketName string) error { 830 return client.removeBucketEncryption(ctx, bucketName) 831 } 832 833 // disableBucketEncryptionResponse calls disableBucketEncryption() 834 func disableBucketEncryptionResponse(session *models.Principal, params bucketApi.DisableBucketEncryptionParams) *CodedAPIError { 835 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 836 defer cancel() 837 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 838 if err != nil { 839 return ErrorWithContext(ctx, err) 840 } 841 // create a minioClient interface implementation 842 // defining the client to be used 843 minioClient := minioClient{client: mClient} 844 if err := disableBucketEncryption(ctx, minioClient, params.BucketName); err != nil { 845 return ErrorWithContext(ctx, err) 846 } 847 return nil 848 } 849 850 func getBucketEncryptionInfo(ctx context.Context, client MinioClient, bucketName string) (*models.BucketEncryptionInfo, error) { 851 bucketInfo, err := client.getBucketEncryption(ctx, bucketName) 852 if err != nil { 853 return nil, err 854 } 855 if len(bucketInfo.Rules) == 0 { 856 return nil, ErrDefault 857 } 858 return &models.BucketEncryptionInfo{Algorithm: bucketInfo.Rules[0].Apply.SSEAlgorithm, KmsMasterKeyID: bucketInfo.Rules[0].Apply.KmsMasterKeyID}, nil 859 } 860 861 func getBucketEncryptionInfoResponse(session *models.Principal, params bucketApi.GetBucketEncryptionInfoParams) (*models.BucketEncryptionInfo, *CodedAPIError) { 862 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 863 defer cancel() 864 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 865 if err != nil { 866 return nil, ErrorWithContext(ctx, err) 867 } 868 // create a minioClient interface implementation 869 // defining the client to be used 870 minioClient := minioClient{client: mClient} 871 bucketInfo, err := getBucketEncryptionInfo(ctx, minioClient, params.BucketName) 872 if err != nil { 873 return nil, ErrorWithContext(ctx, ErrSSENotConfigured, err) 874 } 875 return bucketInfo, nil 876 } 877 878 // setBucketRetentionConfig sets object lock configuration on a bucket 879 func setBucketRetentionConfig(ctx context.Context, client MinioClient, bucketName string, mode models.ObjectRetentionMode, unit models.ObjectRetentionUnit, validity *int32) error { 880 if validity == nil { 881 return errors.New("retention validity can't be nil") 882 } 883 884 var retentionMode minio.RetentionMode 885 switch mode { 886 case models.ObjectRetentionModeGovernance: 887 retentionMode = minio.Governance 888 case models.ObjectRetentionModeCompliance: 889 retentionMode = minio.Compliance 890 default: 891 return errors.New("invalid retention mode") 892 } 893 894 var retentionUnit minio.ValidityUnit 895 switch unit { 896 case models.ObjectRetentionUnitDays: 897 retentionUnit = minio.Days 898 case models.ObjectRetentionUnitYears: 899 retentionUnit = minio.Years 900 default: 901 return errors.New("invalid retention unit") 902 } 903 904 retentionValidity := uint(*validity) 905 return client.setObjectLockConfig(ctx, bucketName, &retentionMode, &retentionValidity, &retentionUnit) 906 } 907 908 func getSetBucketRetentionConfigResponse(session *models.Principal, params bucketApi.SetBucketRetentionConfigParams) *CodedAPIError { 909 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 910 defer cancel() 911 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 912 if err != nil { 913 return ErrorWithContext(ctx, err) 914 } 915 // create a minioClient interface implementation 916 // defining the client to be used 917 minioClient := minioClient{client: mClient} 918 err = setBucketRetentionConfig(ctx, minioClient, params.BucketName, *params.Body.Mode, *params.Body.Unit, params.Body.Validity) 919 if err != nil { 920 return ErrorWithContext(ctx, err) 921 } 922 return nil 923 } 924 925 func getBucketRetentionConfig(ctx context.Context, client MinioClient, bucketName string) (*models.GetBucketRetentionConfig, error) { 926 m, v, u, err := client.getBucketObjectLockConfig(ctx, bucketName) 927 if err != nil { 928 errResp := minio.ToErrorResponse(probe.NewError(err).ToGoError()) 929 if errResp.Code == "ObjectLockConfigurationNotFoundError" { 930 return &models.GetBucketRetentionConfig{}, nil 931 } 932 return nil, err 933 } 934 935 // These values can be empty when all are empty, it means 936 // object was created with object locking enabled but 937 // does not have any default object locking configuration. 938 if m == nil && v == nil && u == nil { 939 return &models.GetBucketRetentionConfig{}, nil 940 } 941 942 var mode models.ObjectRetentionMode 943 var unit models.ObjectRetentionUnit 944 945 if m != nil { 946 switch *m { 947 case minio.Governance: 948 mode = models.ObjectRetentionModeGovernance 949 case minio.Compliance: 950 mode = models.ObjectRetentionModeCompliance 951 default: 952 return nil, errors.New("invalid retention mode") 953 } 954 } 955 956 if u != nil { 957 switch *u { 958 case minio.Days: 959 unit = models.ObjectRetentionUnitDays 960 case minio.Years: 961 unit = models.ObjectRetentionUnitYears 962 default: 963 return nil, errors.New("invalid retention unit") 964 } 965 } 966 967 var validity int32 968 if v != nil { 969 validity = int32(*v) 970 } 971 972 config := &models.GetBucketRetentionConfig{ 973 Mode: mode, 974 Unit: unit, 975 Validity: validity, 976 } 977 return config, nil 978 } 979 980 func getBucketRetentionConfigResponse(session *models.Principal, params bucketApi.GetBucketRetentionConfigParams) (*models.GetBucketRetentionConfig, *CodedAPIError) { 981 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 982 defer cancel() 983 bucketName := params.BucketName 984 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 985 if err != nil { 986 return nil, ErrorWithContext(ctx, err) 987 } 988 989 // create a minioClient interface implementation 990 // defining the client to be used 991 minioClient := minioClient{client: mClient} 992 993 config, err := getBucketRetentionConfig(ctx, minioClient, bucketName) 994 if err != nil { 995 return nil, ErrorWithContext(ctx, err) 996 } 997 return config, nil 998 } 999 1000 func getBucketObjectLockingResponse(session *models.Principal, params bucketApi.GetBucketObjectLockingStatusParams) (*models.BucketObLockingResponse, *CodedAPIError) { 1001 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 1002 defer cancel() 1003 bucketName := params.BucketName 1004 mClient, err := newMinioClient(session, getClientIP(params.HTTPRequest)) 1005 if err != nil { 1006 return nil, ErrorWithContext(ctx, fmt.Errorf("error creating MinIO Client: %v", err)) 1007 } 1008 // create a minioClient interface implementation 1009 // defining the client to be used 1010 minioClient := minioClient{client: mClient} 1011 1012 // we will tolerate this call failing 1013 _, _, _, _, err = minioClient.getObjectLockConfig(ctx, bucketName) 1014 if err != nil { 1015 if minio.ToErrorResponse(err).Code == "ObjectLockConfigurationNotFoundError" { 1016 return &models.BucketObLockingResponse{ 1017 ObjectLockingEnabled: false, 1018 }, nil 1019 } 1020 return nil, ErrorWithContext(ctx, err) 1021 } 1022 1023 // serialize output 1024 return &models.BucketObLockingResponse{ 1025 ObjectLockingEnabled: true, 1026 }, nil 1027 } 1028 1029 func getBucketRewindResponse(session *models.Principal, params bucketApi.GetBucketRewindParams) (*models.RewindResponse, *CodedAPIError) { 1030 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 1031 defer cancel() 1032 prefix := "" 1033 if params.Prefix != nil { 1034 encodedPrefix := SanitizeEncodedPrefix(*params.Prefix) 1035 decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) 1036 if err != nil { 1037 return nil, ErrorWithContext(ctx, err) 1038 } 1039 prefix = string(decodedPrefix) 1040 } 1041 s3Client, err := newS3BucketClient(session, params.BucketName, prefix, getClientIP(params.HTTPRequest)) 1042 if err != nil { 1043 return nil, ErrorWithContext(ctx, fmt.Errorf("error creating S3Client: %v", err)) 1044 } 1045 1046 // create a mc S3Client interface implementation 1047 // defining the client to be used 1048 mcClient := mcClient{client: s3Client} 1049 1050 parsedDate, errDate := time.Parse(time.RFC3339, params.Date) 1051 1052 if errDate != nil { 1053 return nil, ErrorWithContext(ctx, errDate) 1054 } 1055 1056 var rewindItems []*models.RewindItem 1057 1058 for content := range mcClient.client.List(ctx, cmd.ListOptions{TimeRef: parsedDate, WithDeleteMarkers: true}) { 1059 // build object name 1060 name := strings.ReplaceAll(content.URL.Path, fmt.Sprintf("/%s/", params.BucketName), "") 1061 1062 listElement := &models.RewindItem{ 1063 LastModified: content.Time.Format(time.RFC3339), 1064 Size: content.Size, 1065 VersionID: content.VersionID, 1066 DeleteFlag: content.IsDeleteMarker, 1067 Action: "", 1068 Name: name, 1069 } 1070 1071 rewindItems = append(rewindItems, listElement) 1072 } 1073 1074 return &models.RewindResponse{ 1075 Objects: rewindItems, 1076 }, nil 1077 } 1078 1079 func getMaxShareLinkExpirationResponse(session *models.Principal, params bucketApi.GetMaxShareLinkExpParams) (*models.MaxShareLinkExpResponse, *CodedAPIError) { 1080 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 1081 defer cancel() 1082 1083 maxShareLinkExpSeconds, err := getMaxShareLinkExpirationSeconds(session) 1084 if err != nil { 1085 return nil, ErrorWithContext(ctx, err) 1086 } 1087 return &models.MaxShareLinkExpResponse{Exp: swag.Int64(maxShareLinkExpSeconds)}, nil 1088 } 1089 1090 // getMaxShareLinkExpirationSeconds returns the max share link expiration time in seconds which is the sts token expiration time 1091 func getMaxShareLinkExpirationSeconds(session *models.Principal) (int64, error) { 1092 creds := getConsoleCredentialsFromSession(session) 1093 1094 val, err := creds.Get() 1095 if err != nil { 1096 return 0, err 1097 } 1098 1099 if val.SignerType.IsAnonymous() { 1100 return 0, ErrAccessDenied 1101 } 1102 maxShareLinkExp := token.GetConsoleSTSDuration() 1103 1104 return int64(maxShareLinkExp.Seconds()), nil 1105 }