// Copyright 2023 Versity Software // This file is licensed under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package integration import ( "bytes" "context" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/xml" "errors" "fmt" "hash" "hash/crc32" "hash/crc64" "io" "math/bits" "net/http" "net/url" "regexp" "strings" "sync" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aws/smithy-go" "github.com/versity/versitygw/s3err" "golang.org/x/sync/errgroup" ) var ( shortTimeout = 30 * time.Second longTimeout = 60 * time.Second iso8601Format = "20060102T150405Z" timefmt = "Mon, 02 Jan 2006 15:04:05 GMT" nullVersionId = "null" ) func Authentication_invalid_auth_header(s *S3Conf) error { testName := "Authentication_invalid_auth_header" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { req.Header.Set("Authorization", "invalid_header") resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMissingFields)); err != nil { return err } return nil }) } func Authentication_unsupported_signature_version(s *S3Conf) error { testName := "Authentication_unsupported_signature_version" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") authHdr = strings.Replace(authHdr, "AWS4-HMAC-SHA256", "AWS2-HMAC-SHA1", 1) req.Header.Set("Authorization", authHdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureVersionNotSupported)); err != nil { return err } return nil }) } func Authentication_malformed_credentials(s *S3Conf) error { testName := "Authentication_malformed_credentials" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") regExp := regexp.MustCompile("Credential=[^,]+,") hdr := regExp.ReplaceAllString(authHdr, "Credential-access/32234/us-east-1/s3/aws4_request,") req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil { return err } return nil }) } func Authentication_malformed_credentials_invalid_parts(s *S3Conf) error { testName := "Authentication_malformed_credentials_invalid_parts" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") regExp := regexp.MustCompile("Credential=[^,]+,") hdr := regExp.ReplaceAllString(authHdr, "Credential=access/32234/us-east-1/s3,") req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil { return err } return nil }) } func Authentication_credentials_terminated_string(s *S3Conf) error { testName := "Authentication_credentials_terminated_string" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") regExp := regexp.MustCompile("Credential=[^,]+,") hdr := regExp.ReplaceAllString(authHdr, "Credential=access/32234/us-east-1/s3/aws_request,") req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureTerminationStr)); err != nil { return err } return nil }) } func Authentication_credentials_incorrect_service(s *S3Conf) error { testName := "Authentication_credentials_incorrect_service" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "ec2", date: time.Now(), }, func(req *http.Request) error { resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureIncorrService)); err != nil { return err } return nil }) } func Authentication_credentials_incorrect_region(s *S3Conf) error { testName := "Authentication_credentials_incorrect_region" cfg := *s if cfg.awsRegion == "us-east-1" { cfg.awsRegion = "us-west-1" } else { cfg.awsRegion = "us-east-1" } return authHandler(&cfg, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { apiErr := s3err.APIError{ Code: "SignatureDoesNotMatch", Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", cfg.awsRegion), HTTPStatusCode: http.StatusForbidden, } resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, apiErr); err != nil { return err } return nil }) } func Authentication_credentials_invalid_date(s *S3Conf) error { testName := "Authentication_credentials_invalid_date" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") regExp := regexp.MustCompile("Credential=[^,]+,") hdr := regExp.ReplaceAllString(authHdr, "Credential=access/3223423234/us-east-1/s3/aws4_request,") req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { return err } return nil }) } func Authentication_credentials_future_date(s *S3Conf) error { testName := "Authentication_credentials_future_date" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now().Add(time.Duration(5) * 24 * time.Hour), }, func(req *http.Request) error { resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return err } var errResp s3err.APIErrorResponse err = xml.Unmarshal(body, &errResp) if err != nil { return err } if resp.StatusCode != http.StatusForbidden { return fmt.Errorf("expected response status code to be %v, instead got %v", http.StatusForbidden, resp.StatusCode) } if errResp.Code != "RequestTimeTooSkewed" { return fmt.Errorf("expected error code to be %v, instead got %v", "RequestTimeTooSkewed", errResp.Code) } return nil }) } func Authentication_credentials_past_date(s *S3Conf) error { testName := "Authentication_credentials_past_date" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now().Add(time.Duration(-5) * 24 * time.Hour), }, func(req *http.Request) error { resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return err } var errResp s3err.APIErrorResponse err = xml.Unmarshal(body, &errResp) if err != nil { return err } if resp.StatusCode != http.StatusForbidden { return fmt.Errorf("expected response status code to be %v, instead got %v", http.StatusForbidden, resp.StatusCode) } if errResp.Code != "RequestTimeTooSkewed" { return fmt.Errorf("expected error code to be %v, instead got %v", "RequestTimeTooSkewed", errResp.Code) } return nil }) } func Authentication_credentials_non_existing_access_key(s *S3Conf) error { testName := "Authentication_credentials_non_existing_access_key" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") regExp := regexp.MustCompile("Credential=([^/]+)") hdr := regExp.ReplaceAllString(authHdr, "Credential=a_rarely_existing_access_key_id_a7s86df78as6df89790a8sd7f") req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)); err != nil { return err } return nil }) } func Authentication_invalid_signed_headers(s *S3Conf) error { testName := "Authentication_invalid_signed_headers" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") regExp := regexp.MustCompile("SignedHeaders=[^,]+,") hdr := regExp.ReplaceAllString(authHdr, "SignedHeaders-host;x-amz-content-sha256;x-amz-date,") req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { return err } return nil }) } func Authentication_missing_date_header(s *S3Conf) error { testName := "Authentication_missing_date_header" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { req.Header.Set("X-Amz-Date", "") resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMissingDateHeader)); err != nil { return err } return nil }) } func Authentication_invalid_date_header(s *S3Conf) error { testName := "Authentication_invalid_date_header" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { req.Header.Set("X-Amz-Date", "03032006") resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMalformedDate)); err != nil { return err } return nil }) } func Authentication_date_mismatch(s *S3Conf) error { testName := "Authentication_date_mismatch" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { req.Header.Set("X-Amz-Date", "20220830T095525Z") resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { return err } return nil }) } func Authentication_invalid_sha256_payload_hash(s *S3Conf) error { testName := "Authentication_invalid_sha256_payload_hash" return authHandler(s, &authConfig{ testName: testName, method: http.MethodPut, body: nil, service: "s3", date: time.Now(), path: "bucket/object", }, func(req *http.Request) error { req.Header.Set("X-Amz-Content-Sha256", "invalid_sha256") resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidSHA256Paylod)); err != nil { return err } return nil }) } func Authentication_incorrect_payload_hash(s *S3Conf) error { testName := "Authentication_incorrect_payload_hash" return authHandler(s, &authConfig{ testName: testName, method: http.MethodPut, body: nil, service: "s3", date: time.Now(), path: "bucket/object?tagging", }, func(req *http.Request) error { req.Header.Set("X-Amz-Content-Sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b854") resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrContentSHA256Mismatch)); err != nil { return err } return nil }) } func Authentication_incorrect_md5(s *S3Conf) error { testName := "Authentication_incorrect_md5" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { req.Header.Set("Content-Md5", "sadfasdf87sad6f87==") resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidDigest)); err != nil { return err } return nil }) } func Authentication_signature_error_incorrect_secret_key(s *S3Conf) error { testName := "Authentication_signature_error_incorrect_secret_key" cfg := *s cfg.awsSecret = s.awsSecret + "a" return authHandler(&cfg, &authConfig{ testName: testName, method: http.MethodGet, body: nil, service: "s3", date: time.Now(), }, func(req *http.Request) error { resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)); err != nil { return err } return nil }) } func PresignedAuth_unsupported_algorithm(s *S3Conf) error { testName := "PresignedAuth_unsupported_algorithm" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } uri := strings.Replace(v4req.URL, "AWS4-HMAC-SHA256", "AWS4-SHA256", 1) req, err := http.NewRequest(v4req.Method, uri, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQuerySignatureAlgo)); err != nil { return err } return nil }) } func PresignedAuth_missing_credentials_query_param(s *S3Conf) error { testName := "PresignedAuth_missing_credentials_query_param" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Del("X-Amz-Credential") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { return err } return nil }) } func PresignedAuth_malformed_creds_invalid_parts(s *S3Conf) error { testName := "PresignedAuth_malformed_creds_invalid_parts" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Set("X-Amz-Credential", "access/hello/world") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil { return err } return nil }) } func PresignedAuth_creds_invalid_terminator(s *S3Conf) error { testName := "PresignedAuth_creds_invalid_terminator" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } uri, err := changeAuthCred(v4req.URL, "aws5_request", credTerminator) if err != nil { return err } req, err := http.NewRequest(v4req.Method, uri, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureTerminationStr)); err != nil { return err } return nil }) } func PresignedAuth_creds_incorrect_service(s *S3Conf) error { testName := "PresignedAuth_creds_incorrect_service" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } uri, err := changeAuthCred(v4req.URL, "sns", credService) if err != nil { return err } req, err := http.NewRequest(v4req.Method, uri, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureIncorrService)); err != nil { return err } return nil }) } func PresignedAuth_creds_incorrect_region(s *S3Conf) error { testName := "PresignedAuth_creds_incorrect_region" return presignedAuthHandler(s, testName, func(_ *s3.PresignClient, bucket string) error { cfg := *s if cfg.awsRegion == "us-east-1" { cfg.awsRegion = "us-west-1" } else { cfg.awsRegion = "us-east-1" } client := cfg.GetPresignClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } req, err := http.NewRequest(v4req.Method, v4req.URL, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.APIError{ Code: "SignatureDoesNotMatch", Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", cfg.awsRegion), HTTPStatusCode: http.StatusForbidden, }); err != nil { return err } return nil }) } func PresignedAuth_creds_invalid_date(s *S3Conf) error { testName := "PresignedAuth_creds_invalid_date" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } uri, err := changeAuthCred(v4req.URL, "32234Z34", credDate) if err != nil { return err } req, err := http.NewRequest(v4req.Method, uri, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { return err } return nil }) } func PresignedAuth_non_existing_access_key_id(s *S3Conf) error { testName := "PresignedAuth_non_existing_access_key_id" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } uri, err := changeAuthCred(v4req.URL, "a_rarely_existing_access_key_id890asd6f807as6ydf870say", credAccess) if err != nil { return err } req, err := http.NewRequest(v4req.Method, uri, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)); err != nil { return err } return nil }) } func PresignedAuth_missing_date_query(s *S3Conf) error { testName := "PresignedAuth_missing_date_query" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Del("X-Amz-Date") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { return err } return nil }) } func PresignedAuth_dates_mismatch(s *S3Conf) error { testName := "PresignedAuth_dates_mismatch" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } uri, err := changeAuthCred(v4req.URL, "20060102", credDate) if err != nil { return err } req, err := http.NewRequest(v4req.Method, uri, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { return err } return nil }) } func PresignedAuth_missing_signed_headers_query_param(s *S3Conf) error { testName := "PresignedAuth_missing_signed_headers_query_param" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Del("X-Amz-SignedHeaders") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { return err } return nil }) } func PresignedAuth_missing_expiration_query_param(s *S3Conf) error { testName := "PresignedAuth_missing_expiration_query_param" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Del("X-Amz-Expires") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { return err } return nil }) } func PresignedAuth_invalid_expiration_query_param(s *S3Conf) error { testName := "PresignedAuth_invalid_expiration_query_param" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Set("X-Amz-Expires", "invalid_value") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMalformedExpires)); err != nil { return err } return nil }) } func PresignedAuth_negative_expiration_query_param(s *S3Conf) error { testName := "PresignedAuth_negative_expiration_query_param" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Set("X-Amz-Expires", "-3") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrNegativeExpires)); err != nil { return err } return nil }) } func PresignedAuth_exceeding_expiration_query_param(s *S3Conf) error { testName := "PresignedAuth_exceeding_expiration_query_param" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } queries := urlParsed.Query() queries.Set("X-Amz-Expires", "60580000") urlParsed.RawQuery = queries.Encode() req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMaximumExpires)); err != nil { return err } return nil }) } func PresignedAuth_expired_request(s *S3Conf) error { testName := "PresignedAuth_expired_request" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } urlParsed, err := url.Parse(v4req.URL) if err != nil { return err } expDate := time.Now().AddDate(0, -1, 0).Format(iso8601Format) queries := urlParsed.Query() queries.Set("X-Amz-Date", expDate) urlParsed.RawQuery = queries.Encode() uri, err := changeAuthCred(urlParsed.String(), expDate[:8], credDate) if err != nil { return err } req, err := http.NewRequest(v4req.Method, uri, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrExpiredPresignRequest)); err != nil { return err } return nil }) } func PresignedAuth_incorrect_secret_key(s *S3Conf) error { testName := "PresignedAuth_incorrect_secret_key" return presignedAuthHandler(s, testName, func(_ *s3.PresignClient, bucket string) error { cfg := *s cfg.awsSecret += "x" client := cfg.GetPresignClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) cancel() if err != nil { return err } req, err := http.NewRequest(v4req.Method, v4req.URL, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)); err != nil { return err } return nil }) } func PresignedAuth_PutObject_success(s *S3Conf) error { testName := "PresignedAuth_PutObject_success" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignPutObject(ctx, &s3.PutObjectInput{Bucket: &bucket, Key: getPtr("my-obj")}) cancel() if err != nil { return err } req, err := http.NewRequest(http.MethodPut, v4req.URL, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("expected my-obj to be successfully uploaded and get 200 response status, instead got %v", resp.StatusCode) } return nil }) } func PresignedAuth_Put_GetObject_with_data(s *S3Conf) error { testName := "PresignedAuth_Put_GetObject_with_data" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { obj := "my-obj" data := "Hello world" body := strings.NewReader(data) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignPutObject(ctx, &s3.PutObjectInput{Bucket: &bucket, Key: &obj, Body: body}) cancel() if err != nil { return err } req, err := http.NewRequest(v4req.Method, v4req.URL, body) if err != nil { return err } req.Header = v4req.SignedHeader resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("expected my-obj to be successfully uploaded and get %v response status, instead got %v", http.StatusOK, resp.StatusCode) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) v4GetReq, err := client.PresignGetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &obj}) cancel() if err != nil { return err } req, err = http.NewRequest(v4GetReq.Method, v4GetReq.URL, nil) if err != nil { return err } resp, err = s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("expected get object response status to be %v, instead got %v", http.StatusOK, resp.StatusCode) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("read get object response body %w", err) } if string(respBody) != data { return fmt.Errorf("expected get object response body to be %v, instead got %s", data, respBody) } return nil }) } func PresignedAuth_Put_GetObject_with_UTF8_chars(s *S3Conf) error { testName := "PresignedAuth_Put_GetObject_with_UTF8_chars" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { obj := "my-$%^&*;" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignPutObject(ctx, &s3.PutObjectInput{Bucket: &bucket, Key: &obj}) cancel() if err != nil { return err } req, err := http.NewRequest(v4req.Method, v4req.URL, nil) if err != nil { return err } req.Header = v4req.SignedHeader resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("expected my-obj to be successfully uploaded and get %v response status, instead got %v", http.StatusOK, resp.StatusCode) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) v4GetReq, err := client.PresignGetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &obj}) cancel() if err != nil { return err } req, err = http.NewRequest(v4GetReq.Method, v4GetReq.URL, nil) if err != nil { return err } resp, err = s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("expected get object response status to be %v, instead got %v", http.StatusOK, resp.StatusCode) } return nil }) } func PresignedAuth_UploadPart(s *S3Conf) error { testName := "PresignedAuth_UploadPart" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { key, partNumber := "my-mp", int32(1) clt := s.GetClient() mp, err := createMp(clt, bucket, key) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignUploadPart(ctx, &s3.UploadPartInput{Bucket: &bucket, Key: &key, UploadId: mp.UploadId, PartNumber: &partNumber}) cancel() if err != nil { return err } req, err := http.NewRequest(v4req.Method, v4req.URL, nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("expected response status code to be %v, instead got %v", http.StatusOK, resp.StatusCode) } etag := resp.Header.Get("Etag") ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := clt.ListParts(ctx, &s3.ListPartsInput{Bucket: &bucket, Key: &key, UploadId: mp.UploadId}) cancel() if err != nil { return err } if len(out.Parts) != 1 { return fmt.Errorf("expected mp upload parts length to be 1, instead got %v", len(out.Parts)) } if getString(out.Parts[0].ETag) != etag { return fmt.Errorf("expected uploaded part etag to be %v, instead got %v", etag, getString(out.Parts[0].ETag)) } if out.Parts[0].PartNumber == nil { return fmt.Errorf("expected uploaded part part-number to be not nil") } if *out.Parts[0].PartNumber != partNumber { return fmt.Errorf("expected uploaded part part-number to be %v, instead got %v", partNumber, *out.Parts[0].PartNumber) } return nil }) } func CreateBucket_invalid_bucket_name(s *S3Conf) error { testName := "CreateBucket_invalid_bucket_name" runF(testName) err := setup(s, "aa") if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } err = setup(s, ".gitignore") if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } err = setup(s, "my-bucket.") if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } err = setup(s, "bucket-%") if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func CreateBucket_as_user(s *S3Conf) error { testName := "CreateBucket_as_user" runF(testName) cfg := *s cfg.awsID = testuser1.access cfg.awsSecret = testuser1.secret err := createUsers(s, []user{testuser1}) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } err = setup(&cfg, getBucketName()) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func CreateBucket_existing_bucket(s *S3Conf) error { testName := "CreateBucket_existing_bucket" runF(testName) bucket := getBucketName() if err := createUsers(s, []user{testadmin}); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } adminCfg := *s adminCfg.awsID = testadmin.access adminCfg.awsSecret = testadmin.secret err := setup(&adminCfg, bucket) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } err = setup(s, bucket) var bne *types.BucketAlreadyExists if !errors.As(err, &bne) { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } err = teardown(s, bucket) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func CreateBucket_owned_by_you(s *S3Conf) error { testName := "CreateBucket_owned_by_you" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{ Bucket: &bucket, }) cancel() var bErr *types.BucketAlreadyOwnedByYou if !errors.As(err, &bErr) { return fmt.Errorf("expected error to be %w, instead got %w", s3err.GetAPIError(s3err.ErrBucketAlreadyOwnedByYou), err) } return nil }) } func CreateBucket_invalid_ownership(s *S3Conf) error { testName := "CreateBucket_invalid_ownership" runF(testName) invalidOwnership := types.ObjectOwnership("invalid_ownership") err := setup(s, getBucketName(), withOwnership(invalidOwnership)) if err := checkApiErr(err, s3err.APIError{ Code: "InvalidArgument", Description: fmt.Sprintf("Invalid x-amz-object-ownership header: %v", invalidOwnership), HTTPStatusCode: http.StatusBadRequest, }); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func CreateBucket_ownership_with_acl(s *S3Conf) error { testName := "CreateBucket_ownership_with_acl" runF(testName) client := s.GetClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := client.CreateBucket(ctx, &s3.CreateBucketInput{ Bucket: getPtr(getBucketName()), ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced, ACL: types.BucketCannedACLPublicRead, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketAclWithObjectOwnership)); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func CreateBucket_default_acl(s *S3Conf) error { testName := "CreateBucket_default_acl" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket}) cancel() if err != nil { return err } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected bucket owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } if len(out.Grants) != 1 { return fmt.Errorf("expected grants length to be 1, instead got %v", len(out.Grants)) } grt := out.Grants[0] if grt.Permission != types.PermissionFullControl { return fmt.Errorf("expected the grantee to have full-control permission, instead got %v", grt.Permission) } if getString(grt.Grantee.ID) != s.awsID { return fmt.Errorf("expected the grantee id to be %v, instead got %v", s.awsID, getString(grt.Grantee.ID)) } return nil }) } func CreateBucket_non_default_acl(s *S3Conf) error { testName := "CreateBucket_non_default_acl" runF(testName) err := createUsers(s, []user{ {"grt1", "grt1secret", "user"}, {"grt2", "grt2secret", "user"}, {"grt3", "grt3secret", "user"}, }) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } grants := []types.Grant{ { Grantee: &types.Grantee{ ID: &s.awsID, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, { Grantee: &types.Grantee{ ID: getPtr("grt1"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, { Grantee: &types.Grantee{ ID: getPtr("grt2"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionReadAcp, }, { Grantee: &types.Grantee{ ID: getPtr("grt3"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionWrite, }, } bucket := getBucketName() client := s.GetClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = client.CreateBucket(ctx, &s3.CreateBucketInput{ Bucket: &bucket, GrantFullControl: getPtr("grt1"), GrantReadACP: getPtr("grt2"), GrantWrite: getPtr("grt3"), ObjectOwnership: types.ObjectOwnershipBucketOwnerPreferred, }) cancel() if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket}) cancel() if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } if !compareGrants(out.Grants, grants) { failF("%v: expected bucket acl grants to be %v, instead got %v", testName, grants, out.Grants) return fmt.Errorf("%v: expected bucket acl grants to be %v, instead got %v", testName, grants, out.Grants) } err = teardown(s, bucket) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func CreateBucket_default_object_lock(s *S3Conf) error { testName := "CreateBucket_default_object_lock" runF(testName) bucket := getBucketName() lockEnabled := true client := s.GetClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := client.CreateBucket(ctx, &s3.CreateBucketInput{ Bucket: &bucket, ObjectLockEnabledForBucket: &lockEnabled, }) cancel() if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: &bucket, }) cancel() if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } if resp.ObjectLockConfiguration.ObjectLockEnabled != types.ObjectLockEnabledEnabled { failF("%v: expected object lock to be enabled", testName) return fmt.Errorf("%v: expected object lock to be enabled", testName) } err = teardown(s, bucket) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func HeadBucket_non_existing_bucket(s *S3Conf) error { testName := "HeadBucket_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { bcktName := getBucketName() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{ Bucket: &bcktName, }) cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }) } func HeadBucket_success(s *S3Conf) error { testName := "HeadBucket_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if resp.AccessPointAlias != nil && *resp.AccessPointAlias { return fmt.Errorf("expected bucket access point alias to be false") } if getString(resp.BucketRegion) != s.awsRegion { return fmt.Errorf("expected bucket region to be %v, instead got %v", s.awsRegion, getString(resp.BucketRegion)) } return nil }) } func GetBucketLocation_success(s *S3Conf) error { testName := "GetBucketLocation_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.GetBucketLocation(ctx, &s3.GetBucketLocationInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if string(resp.LocationConstraint) != s.awsRegion { return fmt.Errorf("expected bucket region to be %v, instead got %v", s.awsRegion, resp.LocationConstraint) } return nil }) } func GetBucketLocation_non_exist(s *S3Conf) error { testName := "GetBucketLocation_non_exist" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) invalidBucket := "bucket-no-exist" resp, err := s3client.GetBucketLocation(ctx, &s3.GetBucketLocationInput{ Bucket: &invalidBucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } if resp != nil && resp.LocationConstraint != "" { return fmt.Errorf("expected empty location constraint, instead got %v", resp.LocationConstraint) } return nil }) } func GetBucketLocation_no_access(s *S3Conf) error { testName := "GetBucketLocation_no_access" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) resp, err := userClient.GetBucketLocation(ctx, &s3.GetBucketLocationInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } if resp != nil && resp.LocationConstraint != "" { return fmt.Errorf("expected empty location constraint, instead got %v", resp.LocationConstraint) } return nil }) } func ListBuckets_as_user(s *S3Conf) error { testName := "ListBuckets_as_user" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { buckets := []types.Bucket{{Name: &bucket, BucketRegion: &s.awsRegion}} for range 6 { bckt := getBucketName() err := setup(s, bckt) if err != nil { return err } buckets = append(buckets, types.Bucket{ Name: &bckt, BucketRegion: &s.awsRegion, }) } err := createUsers(s, []user{testuser1}) if err != nil { return err } bckts := []string{} for i := range 3 { bckts = append(bckts, *buckets[i].Name) } err = changeBucketsOwner(s, bckts, testuser1.access) if err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := userClient.ListBuckets(ctx, &s3.ListBucketsInput{}) cancel() if err != nil { return err } if getString(out.Owner.ID) != testuser1.access { return fmt.Errorf("expected buckets owner to be %v, instead got %v", testuser1.access, getString(out.Owner.ID)) } if !compareBuckets(out.Buckets, buckets[:3]) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets[:3], out.Buckets) } for _, elem := range buckets[1:] { err = teardown(s, *elem.Name) if err != nil { return err } } return nil }) } func ListBuckets_as_admin(s *S3Conf) error { testName := "ListBuckets_as_admin" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { buckets := []types.Bucket{{Name: &bucket, BucketRegion: &s.awsRegion}} for range 6 { bckt := getBucketName() err := setup(s, bckt) if err != nil { return err } buckets = append(buckets, types.Bucket{ Name: &bckt, BucketRegion: &s.awsRegion, }) } err := createUsers(s, []user{testuser1, testadmin}) if err != nil { return err } bckts := []string{} for i := range 3 { bckts = append(bckts, *buckets[i].Name) } err = changeBucketsOwner(s, bckts, testuser1.access) if err != nil { return err } adminClient := s.getUserClient(testadmin) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := adminClient.ListBuckets(ctx, &s3.ListBucketsInput{}) cancel() if err != nil { return err } if getString(out.Owner.ID) != testadmin.access { return fmt.Errorf("expected buckets owner to be %v, instead got %v", testadmin.access, getString(out.Owner.ID)) } if !compareBuckets(out.Buckets, buckets) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets, out.Buckets) } for _, elem := range buckets[1:] { err = teardown(s, *elem.Name) if err != nil { return err } } return nil }) } func ListBuckets_with_prefix(s *S3Conf) error { testName := "ListBuckets_with_prefix" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { prefix := "my-prefix-" allBuckets, prefixedBuckets := []types.Bucket{{Name: &bucket, BucketRegion: &s.awsRegion}}, []types.Bucket{} for i := range 5 { bckt := getBucketName() if i%2 == 0 { bckt = prefix + bckt } err := setup(s, bckt) if err != nil { return err } allBuckets = append(allBuckets, types.Bucket{ Name: &bckt, BucketRegion: &s.awsRegion, }) if i%2 == 0 { prefixedBuckets = append(prefixedBuckets, types.Bucket{ Name: &bckt, BucketRegion: &s.awsRegion, }) } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{ Prefix: &prefix, }) cancel() if err != nil { return err } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } if getString(out.Prefix) != prefix { return fmt.Errorf("expected prefix to be %v, instead got %v", prefix, getString(out.Prefix)) } if !compareBuckets(out.Buckets, prefixedBuckets) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", prefixedBuckets, out.Buckets) } for _, elem := range allBuckets[1:] { err = teardown(s, *elem.Name) if err != nil { return err } } return nil }) } func ListBuckets_invalid_max_buckets(s *S3Conf) error { testName := "ListBuckets_invalid_max_buckets" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { listBuckets := func(maxBuckets int32) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{ MaxBuckets: &maxBuckets, }) cancel() return err } invMaxBuckets := int32(-3) err := listBuckets(invMaxBuckets) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMaxBuckets)); err != nil { return err } invMaxBuckets = 2000000 err = listBuckets(invMaxBuckets) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMaxBuckets)); err != nil { return err } return nil }) } func ListBuckets_truncated(s *S3Conf) error { testName := "ListBuckets_truncated" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { buckets := []types.Bucket{{Name: &bucket, BucketRegion: &s.awsRegion}} for range 5 { bckt := getBucketName() err := setup(s, bckt) if err != nil { return err } buckets = append(buckets, types.Bucket{ Name: &bckt, BucketRegion: &s.awsRegion, }) } maxBuckets := int32(3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{ MaxBuckets: &maxBuckets, }) cancel() if err != nil { return err } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } if !compareBuckets(out.Buckets, buckets[:maxBuckets]) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets[:maxBuckets], out.Buckets) } if getString(out.ContinuationToken) != getString(buckets[maxBuckets-1].Name) { return fmt.Errorf("expected ContinuationToken to be %v, instead got %v", getString(buckets[maxBuckets-1].Name), getString(out.ContinuationToken)) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err = s3client.ListBuckets(ctx, &s3.ListBucketsInput{ ContinuationToken: out.ContinuationToken, }) cancel() if err != nil { return err } if !compareBuckets(out.Buckets, buckets[maxBuckets:]) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets[maxBuckets:], out.Buckets) } if out.ContinuationToken != nil { return fmt.Errorf("expected nil continuation token, instead got %v", *out.ContinuationToken) } if out.Prefix != nil { return fmt.Errorf("expected nil prefix, instead got %v", *out.Prefix) } for _, elem := range buckets[1:] { err = teardown(s, *elem.Name) if err != nil { return err } } return nil }) } func ListBuckets_empty_success(s *S3Conf) error { testName := "ListBuckets_empty_success" return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{}) cancel() if err != nil { return err } if len(out.Buckets) > 0 { return fmt.Errorf("expected list buckets result to be %v, instead got %v", []types.Bucket{}, out.Buckets) } return nil }) } func ListBuckets_success(s *S3Conf) error { testName := "ListBuckets_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { buckets := []types.Bucket{{Name: &bucket, BucketRegion: &s.awsRegion}} for range 5 { bckt := getBucketName() err := setup(s, bckt) if err != nil { return err } buckets = append(buckets, types.Bucket{ Name: &bckt, BucketRegion: &s.awsRegion, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{}) cancel() if err != nil { return err } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } if !compareBuckets(out.Buckets, buckets) { return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets, out.Buckets) } for _, elem := range buckets[1:] { err = teardown(s, *elem.Name) if err != nil { return err } } return nil }) } func CreateDeleteBucket_success(s *S3Conf) error { testName := "CreateBucket_success" runF(testName) bucket := getBucketName() err := setup(s, bucket) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } err = teardown(s, bucket) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func DeleteBucket_non_existing_bucket(s *S3Conf) error { testName := "DeleteBucket_non_existing_bucket" runF(testName) bucket := getBucketName() s3client := s.GetClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func DeleteBucket_non_empty_bucket(s *S3Conf) error { testName := "DeleteBucket_non_empty_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketNotEmpty)); err != nil { return err } return nil }) } func DeleteBucket_success_status_code(s *S3Conf) error { testName := "DeleteBucket_success_status_code" runF(testName) bucket := getBucketName() err := setup(s, bucket) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } req, err := createSignedReq(http.MethodDelete, s.endpoint, bucket, s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), nil) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } resp, err := s.httpClient.Do(req) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } if resp.StatusCode != http.StatusNoContent { failF("%v: expected response status to be %v, instead got %v", testName, http.StatusNoContent, resp.StatusCode) return fmt.Errorf("%v: expected response status to be %v, instead got %v", testName, http.StatusNoContent, resp.StatusCode) } passF(testName) return nil } func PutBucketOwnershipControls_non_existing_bucket(s *S3Conf) error { testName := "PutBucketOwnershipControls_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: getPtr(getBucketName()), OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipBucketOwnerPreferred, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutBucketOwnershipControls_multiple_rules(s *S3Conf) error { testName := "PutBucketOwnershipControls_multiple_rules" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipBucketOwnerPreferred, }, { ObjectOwnership: types.ObjectOwnershipObjectWriter, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutBucketOwnershipControls_invalid_ownership(s *S3Conf) error { testName := "PutBucketOwnershipControls_invalid_ownership" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnership("invalid_ownership"), }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutBucketOwnershipControls_success(s *S3Conf) error { testName := "PutBucketOwnershipControls_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipObjectWriter, }, }, }, }) cancel() if err != nil { return err } return nil }) } func GetBucketOwnershipControls_non_existing_bucket(s *S3Conf) error { testName := "GetBucketOwnershipControls_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetBucketOwnershipControls_default_ownership(s *S3Conf) error { testName := "GetBucketOwnershipControls_default_ownership" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if len(resp.OwnershipControls.Rules) != 1 { return fmt.Errorf("expected ownership control rules length to be 1, instead got %v", len(resp.OwnershipControls.Rules)) } if resp.OwnershipControls.Rules[0].ObjectOwnership != types.ObjectOwnershipBucketOwnerEnforced { return fmt.Errorf("expected the bucket ownership to be %v, instead got %v", types.ObjectOwnershipBucketOwnerEnforced, resp.OwnershipControls.Rules[0].ObjectOwnership) } return nil }) } func GetBucketOwnershipControls_success(s *S3Conf) error { testName := "GetBucketOwnershipControls_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipObjectWriter, }, }, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if len(resp.OwnershipControls.Rules) != 1 { return fmt.Errorf("expected ownership control rules length to be 1, instead got %v", len(resp.OwnershipControls.Rules)) } if resp.OwnershipControls.Rules[0].ObjectOwnership != types.ObjectOwnershipObjectWriter { return fmt.Errorf("expected the bucket ownership to be %v, instead got %v", types.ObjectOwnershipObjectWriter, resp.OwnershipControls.Rules[0].ObjectOwnership) } return nil }) } func DeleteBucketOwnershipControls_non_existing_bucket(s *S3Conf) error { testName := "DeleteBucketOwnershipControls_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func DeleteBucketOwnershipControls_success(s *S3Conf) error { testName := "DeleteBucketOwnershipControls_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrOwnershipControlsNotFound)); err != nil { return err } return nil }) } func PutBucketTagging_non_existing_bucket(s *S3Conf) error { testName := "PutBucketTagging_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: getPtr(getBucketName()), Tagging: &types.Tagging{TagSet: []types.Tag{}}, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutBucketTagging_long_tags(s *S3Conf) error { testName := "PutBucketTagging_long_tags" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr(genRandString(200)), Value: getPtr("val")}}} ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &tagging}) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTagKey)); err != nil { return err } tagging = types.Tagging{TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr(genRandString(300))}}} ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &tagging}) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTagValue)); err != nil { return err } return nil }) } func PutBucketTagging_duplicate_keys(s *S3Conf) error { testName := "PutBucketTagging_duplicate_keys" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagging := types.Tagging{ TagSet: []types.Tag{ {Key: getPtr("key"), Value: getPtr("value")}, {Key: getPtr("key"), Value: getPtr("value-1")}, {Key: getPtr("key-1"), Value: getPtr("value-2")}, {Key: getPtr("key-2"), Value: getPtr("value-3")}, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &tagging, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDuplicateTagKey)); err != nil { return err } return nil }) } func PutBucketTagging_tag_count_limit(s *S3Conf) error { testName := "PutBucketTagging_tag_count_limit" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagSet := []types.Tag{} for i := range 51 { tagSet = append(tagSet, types.Tag{ Key: getPtr(fmt.Sprintf("key-%v", i)), Value: getPtr(genRandString(10)), }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &types.Tagging{ TagSet: tagSet, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketTaggingLimited)) }) } func PutBucketTagging_success(s *S3Conf) error { testName := "PutBucketTagging_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}}} ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &tagging}) cancel() if err != nil { return err } return nil }) } func PutBucketTagging_success_status(s *S3Conf) error { testName := "PutBucketTagging_success_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagging := types.Tagging{ TagSet: []types.Tag{ { Key: getPtr("key"), Value: getPtr("val"), }, }, } taggingParsed, err := xml.Marshal(tagging) if err != nil { return fmt.Errorf("err parsing tagging: %w", err) } req, err := createSignedReq(http.MethodPut, s.endpoint, fmt.Sprintf("%v?tagging=", bucket), s.awsID, s.awsSecret, "s3", s.awsRegion, taggingParsed, time.Now(), nil) if err != nil { return fmt.Errorf("err signing the request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("err sending request: %w", err) } if resp.StatusCode != http.StatusNoContent { return fmt.Errorf("expected the response status code to be %v, instad got %v", http.StatusNoContent, resp.StatusCode) } return nil }) } func GetBucketTagging_non_existing_bucket(s *S3Conf) error { testName := "GetBucketTagging_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetBucketTagging_unset_tags(s *S3Conf) error { testName := "GetBucketTagging_unset_tags" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)); err != nil { return err } return nil }) } func GetBucketTagging_success(s *S3Conf) error { testName := "GetBucketTagging_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}}} ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &tagging}) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) cancel() if err != nil { return nil } if !areTagsSame(out.TagSet, tagging.TagSet) { return fmt.Errorf("expected %v instead got %v", tagging.TagSet, out.TagSet) } return nil }) } func DeleteBucketTagging_non_existing_object(s *S3Conf) error { testName := "DeleteBucketTagging_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func DeleteBucketTagging_success_status(s *S3Conf) error { testName := "DeleteBucketTagging_success_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagging := types.Tagging{ TagSet: []types.Tag{ { Key: getPtr("Hello"), Value: getPtr("World"), }, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &tagging, }) cancel() if err != nil { return err } req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v?tagging", bucket), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusNoContent { return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode) } return nil }) } func DeleteBucketTagging_success(s *S3Conf) error { testName := "DeleteBucketTagging_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}}} ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &tagging}) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) cancel() if err != nil { return nil } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) cancel() if err != nil { return nil } if len(out.TagSet) > 0 { return fmt.Errorf("expected empty tag set, instead got %v", out.TagSet) } return nil }) } func PutObject_non_existing_bucket(s *S3Conf) error { testName := "PutObject_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"my-obj"}, "non-existing-bucket") if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutObject_special_chars(s *S3Conf) error { testName := "PutObject_special_chars" objnames := []string{ "my!key", "my-key", "my_key", "my.key", "my'key", "my(key", "my)key", "my&key", "my@key", "my=key", "my;key", "my:key", "my key", "my,key", "my?key", "my^key", "my{}key", "my%key", "my`key", "my[]key", "my~key", "my<>key", "my|key", "my#key", } if !s.azureTests { // azure currently can't handle backslashes in object names objnames = append(objnames, "my\\key") } return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs, err := putObjects(s3client, objnames, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareObjects(objs, res.Contents) { return fmt.Errorf("expected the objects to be %vß, instead got %v", objStrings(objs), objStrings(res.Contents)) } return nil }) } func PutObject_tagging(s *S3Conf) error { testName := "PutObject_tagging" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" testTagging := func(taggging string, result map[string]string, expectedErr error) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, Tagging: &taggging, }) cancel() if err == nil && expectedErr != nil { return fmt.Errorf("expected err %w, instead got nil", expectedErr) } if err != nil { if expectedErr == nil { return err } switch eErr := expectedErr.(type) { case s3err.APIError: return checkApiErr(err, eErr) default: return fmt.Errorf("invalid err provided: %w", expectedErr) } } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if len(res.TagSet) != len(result) { return fmt.Errorf("tag lengths are not equal: (expected): %v, (got): %v", len(result), len(res.TagSet)) } for _, tag := range res.TagSet { val, ok := result[getString(tag.Key)] if !ok { return fmt.Errorf("tag key not found: %v", getString(tag.Key)) } if val != getString(tag.Value) { return fmt.Errorf("expected the %v tag value to be %v, instead got %v", getString(tag.Key), val, getString(tag.Value)) } } return nil } for _, el := range []struct { tagging string result map[string]string expectedErr error }{ // success cases {"&", map[string]string{}, nil}, {"&&&", map[string]string{}, nil}, {"key", map[string]string{"key": ""}, nil}, {"key&", map[string]string{"key": ""}, nil}, {"key=&", map[string]string{"key": ""}, nil}, {"key=val&", map[string]string{"key": "val"}, nil}, {"key1&key2", map[string]string{"key1": "", "key2": ""}, nil}, {"key1=val1&key2=val2", map[string]string{"key1": "val1", "key2": "val2"}, nil}, // invalid url-encoded {"=", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, {"key%", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, // duplicate keys {"key=val&key=val", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, // invalid tag keys {"key?=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key(=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key*=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key$=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key#=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key@=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key!=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, // invalid tag values {"key=val?", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val(", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val*", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val$", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val#", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val@", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val!", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, // success special chars {"key-key_key.key/key=value-value_value.value/value", map[string]string{"key-key_key.key/key": "value-value_value.value/value"}, nil}, // should handle supported encoded characters {"key%2E=value%2F", map[string]string{"key.": "value/"}, nil}, {"key%2D=value%2B", map[string]string{"key-": "value+"}, nil}, {"key++key=value++value", map[string]string{"key key": "value value"}, nil}, {"key%20key=value%20value", map[string]string{"key key": "value value"}, nil}, {"key%5Fkey=value%5Fvalue", map[string]string{"key_key": "value_value"}, nil}, } { err := testTagging(el.tagging, el.result, el.expectedErr) if err != nil { return err } } return nil }) } func PutObject_missing_object_lock_retention_config(s *S3Conf) error { testName := "PutObject_missing_object_lock_retention_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &key, ObjectLockMode: types.ObjectLockModeCompliance, }) cancel() if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } // client sdk regression issue prevents getting full error message, // change back to below once this is fixed: // https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil { // return err // } retainDate := time.Now().Add(time.Hour * 48) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &key, ObjectLockRetainUntilDate: &retainDate, }) cancel() if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } // client sdk regression issue prevents getting full error message, // change back to below once this is fixed: // https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil { // return err // } return nil }) } func PutObject_with_object_lock(s *S3Conf) error { testName := "PutObject_with_object_lock" runF(testName) bucket, obj, lockStatus := getBucketName(), "my-obj", true client := s.GetClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := client.CreateBucket(ctx, &s3.CreateBucketInput{ Bucket: &bucket, ObjectLockEnabledForBucket: &lockStatus, }) cancel() if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } retainDate := time.Now().Add(time.Hour * 48) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, ObjectLockMode: types.ObjectLockModeCompliance, ObjectLockRetainUntilDate: &retainDate, }) cancel() if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } if out.ObjectLockMode != types.ObjectLockModeCompliance { failF("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockModeCompliance, out.ObjectLockMode) return fmt.Errorf("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockModeCompliance, out.ObjectLockMode) } if out.ObjectLockLegalHoldStatus != types.ObjectLockLegalHoldStatusOn { failF("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockLegalHoldStatusOn, out.ObjectLockLegalHoldStatus) return fmt.Errorf("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockLegalHoldStatusOn, out.ObjectLockLegalHoldStatus) } if err := changeBucketObjectLockStatus(client, bucket, false); err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } err = teardown(s, bucket) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func PutObject_invalid_legal_hold(s *S3Conf) error { testName := "PutObject_invalid_legal_hold" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: getPtr("foo"), ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatus("invalid_status"), }, s3client) return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidLegalHoldStatus)) }, withLock()) } func PutObject_invalid_object_lock_mode(s *S3Conf) error { testName := "PutObject_invalid_object_lock_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { rDate := time.Now().Add(time.Hour * 10) _, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: getPtr("foo"), ObjectLockRetainUntilDate: &rDate, ObjectLockMode: types.ObjectLockMode("invalid_mode"), }, s3client) return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidObjectLockMode)) }, withLock()) } func PutObject_checksum_algorithm_and_header_mismatch(s *S3Conf) error { testName := "PutObject_checksum_algorithm_and_header_mismatch" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumAlgorithm: types.ChecksumAlgorithmCrc32, ChecksumCRC32C: getPtr("m0cB1Q=="), }) cancel() // FIXME: The error message for PutObject is not properly serialized by the sdk // References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { // return err // } if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } return nil }) } func PutObject_multiple_checksum_headers(s *S3Conf) error { testName := "PutObject_multiple_checksum_headers" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumSHA1: getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0="), ChecksumCRC32C: getPtr("m0cB1Q=="), }, s3client) // FIXME: The error message for PutObject is not properly serialized by the sdk // References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { // return err // } if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } // Empty checksums case _, err = putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumSHA1: getPtr(""), ChecksumCRC32C: getPtr(""), }, s3client) // FIXME: The error message for PutObject is not properly serialized by the sdk // References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { // return err // } if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } return nil }) } func PutObject_invalid_checksum_header(s *S3Conf) error { testName := "PutObject_invalid_checksum_header" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for i, el := range []struct { algo string crc32 *string crc32c *string sha1 *string sha256 *string crc64nvme *string }{ // CRC32 tests { algo: "crc32", crc32: getPtr(""), }, { algo: "crc32", crc32: getPtr("invalid_base64!"), // invalid base64 }, { algo: "crc32", crc32: getPtr("YXNrZGpoZ2tqYXNo"), // valid base64 but not crc32 }, // CRC32C tests { algo: "crc32c", crc32c: getPtr(""), }, { algo: "crc32c", crc32c: getPtr("invalid_base64!"), // invalid base64 }, { algo: "crc32c", crc32c: getPtr("c2RhZnNhZGZzZGFm"), // valid base64 but not crc32c }, // SHA1 tests { algo: "sha1", sha1: getPtr(""), }, { algo: "sha1", sha1: getPtr("invalid_base64!"), // invalid base64 }, { algo: "sha1", sha1: getPtr("c2RhZmRhc2Zkc2Fmc2RhZnNhZGZzYWRm"), // valid base64 but not sha1 }, // SHA256 tests { algo: "sha256", sha256: getPtr(""), }, { algo: "sha256", sha256: getPtr("invalid_base64!"), // invalid base64 }, { algo: "sha256", sha256: getPtr("ZGZnbmRmZ2hoZmRoZmdkaA=="), // valid base64 but not sha56 }, // CRC64Nvme tests { algo: "crc64nvme", sha256: getPtr(""), }, { algo: "crc64nvme", sha256: getPtr("invalid_base64!"), // invalid base64 }, { algo: "crc64nvme", sha256: getPtr("ZHNhZmRzYWZzZGFmZHNhZg=="), // valid base64 but not crc64nvme }, } { _, err := putObjectWithData(int64(i*100), &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumCRC32: el.crc32, ChecksumCRC32C: el.crc32c, ChecksumSHA1: el.sha1, ChecksumSHA256: el.sha256, ChecksumCRC64NVME: el.crc64nvme, }, s3client) // FIXME: The error message for PutObject is not properly serialized by the sdk // References to aws sdk issue https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { // return err // } if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } } return nil }) } func PutObject_incorrect_checksums(s *S3Conf) error { testName := "PutObject_incorrect_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for i, el := range []struct { algo types.ChecksumAlgorithm crc32 *string crc32c *string sha1 *string sha256 *string crc64nvme *string }{ { algo: types.ChecksumAlgorithmCrc32, crc32: getPtr("DUoRhQ=="), }, { algo: types.ChecksumAlgorithmCrc32c, crc32c: getPtr("yZRlqg=="), }, { algo: types.ChecksumAlgorithmSha1, sha1: getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0="), }, { algo: types.ChecksumAlgorithmSha256, sha256: getPtr("uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="), }, { algo: types.ChecksumAlgorithmCrc64nvme, crc64nvme: getPtr("sV264W+gYBI="), }, } { _, err := putObjectWithData(int64(i*100), &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumCRC32: el.crc32, ChecksumCRC32C: el.crc32c, ChecksumSHA1: el.sha1, ChecksumSHA256: el.sha256, ChecksumCRC64NVME: el.crc64nvme, }, s3client) if err := checkApiErr(err, s3err.GetChecksumBadDigestErr(el.algo)); err != nil { return err } } return nil }) } func PutObject_default_checksum(s *S3Conf) error { testName := "PutObject_default_checksum" return actionHandler(s, testName, func(_ *s3.Client, bucket string) error { customClient := s3.NewFromConfig(s.Config(), func(o *s3.Options) { o.RequestChecksumCalculation = aws.RequestChecksumCalculationUnset }) obj := "my-obj" out, err := putObjectWithData(100, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, customClient) if err != nil { return err } if out.res.ChecksumCRC64NVME == nil { return fmt.Errorf("expected non nil default crc64nvme checksum") } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := customClient.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, ChecksumMode: types.ChecksumModeEnabled, }) cancel() if err != nil { return err } if getString(res.ChecksumCRC64NVME) != getString(out.res.ChecksumCRC64NVME) { return fmt.Errorf("expected the object crc64nvme checksum to be %v, instead got %v", getString(res.ChecksumCRC64NVME), getString(out.res.ChecksumCRC64NVME)) } return nil }) } func PutObject_checksums_success(s *S3Conf) error { testName := "PutObject_checksums_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for i, algo := range types.ChecksumAlgorithmCrc32.Values() { res, err := putObjectWithData(int64(i*200), &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumAlgorithm: algo, }, s3client) if err != nil { return err } if res.res.ChecksumType != types.ChecksumTypeFullObject { return fmt.Errorf("expected the object checksum type to be %v, instead got %v", types.ChecksumTypeFullObject, res.res.ChecksumType) } switch algo { case types.ChecksumAlgorithmCrc32: if res.res.ChecksumCRC32 == nil { return fmt.Errorf("expected non empty crc32 checksum in the response") } case types.ChecksumAlgorithmCrc32c: if res.res.ChecksumCRC32C == nil { return fmt.Errorf("expected non empty crc32c checksum in the response") } case types.ChecksumAlgorithmSha1: if res.res.ChecksumSHA1 == nil { return fmt.Errorf("expected non empty sha1 checksum in the response") } case types.ChecksumAlgorithmSha256: if res.res.ChecksumSHA256 == nil { return fmt.Errorf("expected non empty sha256 checksum in the response") } case types.ChecksumAlgorithmCrc64nvme: if res.res.ChecksumCRC64NVME == nil { return fmt.Errorf("expected non empty crc64nvme checksum in the response") } } } return nil }) } func PutObject_racey_success(s *S3Conf) error { testName := "PutObject_racey_success" runF(testName) bucket, obj, lockStatus := getBucketName(), "my-obj", true client := s.GetClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := client.CreateBucket(ctx, &s3.CreateBucketInput{ Bucket: &bucket, ObjectLockEnabledForBucket: &lockStatus, }) cancel() if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } eg := errgroup.Group{} for range 10 { eg.Go(func() error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() return err }) } err = eg.Wait() if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } err = teardown(s, bucket) if err != nil { failF("%v: %v", err) return fmt.Errorf("%v: %w", testName, err) } passF(testName) return nil } func PutObject_success(s *S3Conf) error { testName := "PutObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"my-obj"}, bucket) if err != nil { return err } return nil }) } func PutObject_invalid_credentials(s *S3Conf) error { testName := "PutObject_invalid_credentials" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { newconf := *s newconf.awsSecret = newconf.awsSecret + "badpassword" client := newconf.GetClient() _, err := putObjects(client, []string{"my-obj"}, bucket) return checkApiErr(err, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)) }) } func PutObject_invalid_object_names(s *S3Conf) error { testName := "PutObject_invalid_object_names" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, obj := range []string{ ".", "..", "./", "/.", "//", "../", "/..", "/..", "../.", "../../../.", "../../../etc/passwd", "../../../../tmp/foo", "for/../../bar/", "a/a/a/../../../../../etc/passwd", "/a/../../b/../../c/../../../etc/passwd", } { _, err := putObjects(s3client, []string{obj}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBadRequest)); err != nil { return err } } return nil }) } func PutObject_false_negative_object_names(s *S3Conf) error { testName := "PutObject_false_negative_object_names" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs := []string{ "%252e%252e%252fetc/passwd", // double encoding "%2e%2e/%2e%2e/%2e%2e/.ssh/id_rsa", // double URL-encoded "%u002e%u002e/%u002e%u002e/etc/passwd", // unicode escape "..%2f..%2f..%2fsecret/file.txt", // URL-encoded "..%c0%af..%c0%afetc/passwd", // UTF-8 overlong trick ".../.../.../target.txt", "..\\u2215..\\u2215etc/passwd", // Unicode division slash "dir/%20../file.txt", // encoded space "dir/%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd", // overlong UTF-8 encoding "logs/latest -> /etc/passwd", // symlink attacks //TODO: add this test case in advanced routing // "/etc/passwd" // absolute path injection } _, err := putObjects(s3client, objs, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if len(res.Contents) != len(objs) { return fmt.Errorf("expected %v objects, instead got %v", len(objs), len(res.Contents)) } for i, obj := range res.Contents { if *obj.Key != objs[i] { return fmt.Errorf("expected the %vth object name to be %s, instead got %s", i+1, objs[i], *obj.Key) } } return nil }) } func HeadObject_non_existing_object(s *S3Conf) error { testName := "HeadObject_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }) } func HeadObject_invalid_part_number(s *S3Conf) error { testName := "HeadObject_invalid_part_number" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { partNumber := int32(-3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: getPtr("my-obj"), PartNumber: &partNumber, }) cancel() if err := checkSdkApiErr(err, "BadRequest"); err != nil { return err } return nil }) } func HeadObject_non_existing_mp(s *S3Conf) error { testName := "HeadObject_non_existing_mp" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { partNumber := int32(4) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: getPtr("my-obj"), PartNumber: &partNumber, }) cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }) } func HeadObject_mp_success(s *S3Conf) error { testName := "HeadObject_mp_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" partCount, partSize := int64(5), int64(1024) partNumber := int32(3) mp, err := createMp(s3client, bucket, obj) if err != nil { return err } parts, _, err := uploadParts(s3client, partCount*partSize, partCount, bucket, obj, *mp.UploadId) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, PartNumber: &partNumber, }) cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected non nil content length") } if *out.ContentLength != int64(partSize) { return fmt.Errorf("expected content length to be %v, instead got %v", partSize, *out.ContentLength) } if getString(out.ETag) != getString(parts[partNumber-1].ETag) { return fmt.Errorf("expected ETag to be %v, instead got %v", getString(parts[partNumber-1].ETag), getString(out.ETag)) } if out.PartsCount == nil { return fmt.Errorf("expected non nil parts count") } if *out.PartsCount != int32(partCount) { return fmt.Errorf("expected part count to be %v, instead got %v", partCount, *out.PartsCount) } if out.StorageClass != types.StorageClassStandard { return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, out.StorageClass) } return nil }) } func HeadObject_non_existing_dir_object(s *S3Conf) error { testName := "HeadObject_non_existing_dir_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, dataLen := "my-obj", int64(1234567) meta := map[string]string{ "key1": "val1", "key2": "val2", } _, err := putObjectWithData(dataLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, Metadata: meta, }, s3client) if err != nil { return err } obj = "my-obj/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }) } func HeadObject_directory_object_noslash(s *S3Conf) error { testName := "HeadObject_directory_object_noslash" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } obj = "my-obj" ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }) } const defaultContentType = "binary/octet-stream" func HeadObject_not_enabled_checksum_mode(s *S3Conf) error { testName := "HeadObject_not_enabled_checksum_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjectWithData(500, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumAlgorithm: types.ChecksumAlgorithmSha1, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if res.ChecksumCRC32 != nil { return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.ChecksumCRC32) } if res.ChecksumCRC32C != nil { return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.ChecksumCRC32C) } if res.ChecksumSHA1 != nil { return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.ChecksumSHA1) } if res.ChecksumSHA256 != nil { return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.ChecksumSHA256) } return nil }) } func HeadObject_checksums(s *S3Conf) error { testName := "HeadObject_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs := []struct { key string checksumAlgo types.ChecksumAlgorithm }{ { key: "obj-1", checksumAlgo: types.ChecksumAlgorithmCrc32, }, { key: "obj-2", checksumAlgo: types.ChecksumAlgorithmCrc32c, }, { key: "obj-3", checksumAlgo: types.ChecksumAlgorithmSha1, }, { key: "obj-4", checksumAlgo: types.ChecksumAlgorithmSha256, }, { key: "obj-5", checksumAlgo: types.ChecksumAlgorithmCrc64nvme, }, } for i, el := range objs { out, err := putObjectWithData(int64(i*200), &s3.PutObjectInput{ Bucket: &bucket, Key: &el.key, ChecksumAlgorithm: el.checksumAlgo, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &el.key, ChecksumMode: types.ChecksumModeEnabled, }) cancel() if err != nil { return err } if res.ChecksumType != types.ChecksumTypeFullObject { return fmt.Errorf("expected the %v object checksum type to be %v, instaed got %v", el.key, types.ChecksumTypeFullObject, res.ChecksumType) } if getString(res.ChecksumCRC32) != getString(out.res.ChecksumCRC32) { return fmt.Errorf("expected crc32 checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32), getString(res.ChecksumCRC32)) } if getString(res.ChecksumCRC32C) != getString(out.res.ChecksumCRC32C) { return fmt.Errorf("expected crc32c checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32C), getString(res.ChecksumCRC32C)) } if getString(res.ChecksumSHA1) != getString(out.res.ChecksumSHA1) { return fmt.Errorf("expected sha1 checksum to be %v, instead got %v", getString(out.res.ChecksumSHA1), getString(res.ChecksumSHA1)) } if getString(res.ChecksumSHA256) != getString(out.res.ChecksumSHA256) { return fmt.Errorf("expected sha256 checksum to be %v, instead got %v", getString(out.res.ChecksumSHA256), getString(res.ChecksumSHA256)) } if getString(res.ChecksumCRC64NVME) != getString(out.res.ChecksumCRC64NVME) { return fmt.Errorf("expected crc64nvme checksum to be %v, instead got %v", getString(out.res.ChecksumCRC64NVME), getString(res.ChecksumCRC64NVME)) } } return nil }) } func HeadObject_invalid_parent_dir(s *S3Conf) error { testName := "HeadObject_invalid_parent_dir" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, dataLen := "not-a-dir", int64(1) _, err := putObjectWithData(dataLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } obj = "not-a-dir/bad-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }) } func HeadObject_with_range(s *S3Conf) error { testName := "HeadObject_with_range" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, objLength := "my-obj", int64(100) _, err := putObjectWithData(objLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } testRange := func(rg, contentRange string, cLength int64, expectErr bool) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, Range: &rg, }) cancel() if err == nil && expectErr { return fmt.Errorf("%v: expected err 'RequestedRangeNotSatisfiable' error, instead got nil", rg) } if err != nil { if !expectErr { return err } var ae smithy.APIError if errors.As(err, &ae) { if ae.ErrorCode() != "RequestedRangeNotSatisfiable" { return fmt.Errorf("%v: expected RequestedRangeNotSatisfiable, instead got %v", rg, ae.ErrorCode()) } if ae.ErrorMessage() != "Requested Range Not Satisfiable" { return fmt.Errorf("%v: expected the error message to be 'Requested Range Not Satisfiable', instead got %v", rg, ae.ErrorMessage()) } return nil } return fmt.Errorf("%v: invalid error got %w", rg, err) } if getString(res.AcceptRanges) != "bytes" { return fmt.Errorf("%v: expected accept ranges to be 'bytes', instead got %v", rg, getString(res.AcceptRanges)) } if res.ContentLength == nil { return fmt.Errorf("%v: expected non nil content-length", rg) } if *res.ContentLength != cLength { return fmt.Errorf("%v: expected content-length to be %v, instead got %v", rg, cLength, *res.ContentLength) } if getString(res.ContentRange) != contentRange { return fmt.Errorf("%v: expected content-range to be %v, instead got %v", rg, contentRange, getString(res.ContentRange)) } return nil } // Reference server expectations for a 100-byte object. for _, el := range []struct { objRange string contentRange string contentLength int64 expectedErr bool }{ // The following inputs should NOT produce an error and return the full object with empty Content-Range. {"bytes=,", "", objLength, false}, {"bytes= -1", "", objLength, false}, {"bytes=--1", "", objLength, false}, {"bytes=0 -1", "", objLength, false}, {"bytes=0--1", "", objLength, false}, {"bytes=10-5", "", objLength, false}, // start > end treated as invalid {"bytes=abc", "", objLength, false}, {"bytes=a-z", "", objLength, false}, {"foo=0-1", "", objLength, false}, // unsupported unit {"bytes=00-01", "bytes 0-1/100", 2, false}, // valid numeric despite leading zeros {"bytes=abc-xyz", "", objLength, false}, // retain legacy invalid pattern {"bytes=100-x", "", objLength, false}, {"bytes=0-0,1-2", "", objLength, false}, // multiple ranges unsupported -> ignore // Valid suffix ranges (negative forms) {"bytes=-1", "bytes 99-99/100", 1, false}, {"bytes=-2", "bytes 98-99/100", 2, false}, {"bytes=-10", "bytes 90-99/100", 10, false}, {"bytes=-100", "bytes 0-99/100", objLength, false}, {"bytes=-101", "bytes 0-99/100", objLength, false}, // larger than object -> entire object // Standard byte ranges {"bytes=0-0", "bytes 0-0/100", 1, false}, {"bytes=0-99", "bytes 0-99/100", objLength, false}, {"bytes=0-100", "bytes 0-99/100", objLength, false}, // end past object -> trimmed {"bytes=0-999999", "bytes 0-99/100", objLength, false}, {"bytes=1-99", "bytes 1-99/100", objLength - 1, false}, {"bytes=50-99", "bytes 50-99/100", 50, false}, {"bytes=50-", "bytes 50-99/100", 50, false}, {"bytes=0-", "bytes 0-99/100", objLength, false}, {"bytes=99-99", "bytes 99-99/100", 1, false}, // Ranges expected to produce RequestedRangeNotSatisfiable {"bytes=-0", "", 0, true}, {"bytes=100-100", "", 0, true}, {"bytes=100-110", "", 0, true}, } { if err := testRange(el.objRange, el.contentRange, el.contentLength, el.expectedErr); err != nil { return err } } return nil }) } func HeadObject_zero_len_with_range(s *S3Conf) error { testName := "HeadObject_zero_len_with_range" return headObject_zero_len_with_range_helper(testName, "my-obj", s) } func HeadObject_dir_with_range(s *S3Conf) error { testName := "HeadObject_dir_with_range" return headObject_zero_len_with_range_helper(testName, "my-dir/", s) } func HeadObject_success(s *S3Conf) error { testName := "HeadObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, dataLen := "my-obj", int64(1234567) meta := map[string]string{ "key1": "val1", "key2": "val2", } ctype, cDisp, cEnc, cLang := defaultContentType, "cont-desp", "json", "eng" cacheControl, expires := "cache-ctrl", time.Now().Add(time.Hour*2) _, err := putObjectWithData(dataLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, Metadata: meta, ContentType: &ctype, ContentDisposition: &cDisp, ContentEncoding: &cEnc, ContentLanguage: &cLang, CacheControl: &cacheControl, Expires: &expires, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err != nil { return err } if !areMapsSame(out.Metadata, meta) { return fmt.Errorf("incorrect object metadata") } contentLength := int64(0) if out.ContentLength != nil { contentLength = *out.ContentLength } if contentLength != dataLen { return fmt.Errorf("expected data length %v, instead got %v", dataLen, contentLength) } if getString(out.ContentType) != defaultContentType { return fmt.Errorf("expected Content-Type %v, instead got %v", defaultContentType, getString(out.ContentType)) } if getString(out.ContentDisposition) != cDisp { return fmt.Errorf("expected Content-Disposition %v, instead got %v", cDisp, getString(out.ContentDisposition)) } if getString(out.ContentEncoding) != cEnc { return fmt.Errorf("expected Content-Encoding %v, instead got %v", cEnc, getString(out.ContentEncoding)) } if getString(out.ContentLanguage) != cLang { return fmt.Errorf("expected Content-Language %v, instead got %v", cLang, getString(out.ContentLanguage)) } if getString(out.ExpiresString) != expires.UTC().Format(timefmt) { return fmt.Errorf("expected Expiress %v, instead got %v", expires.UTC().Format(timefmt), getString(out.ExpiresString)) } if getString(out.CacheControl) != cacheControl { return fmt.Errorf("expected Cache-Control %v, instead got %v", cacheControl, getString(out.CacheControl)) } if out.StorageClass != types.StorageClassStandard { return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, out.StorageClass) } return nil }) } func GetObjectAttributes_non_existing_bucket(s *S3Conf) error { testName := "GetObjectAttributes_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: getPtr(getBucketName()), Key: getPtr("my-obj"), ObjectAttributes: []types.ObjectAttributes{}, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetObjectAttributes_non_existing_object(s *S3Conf) error { testName := "GetObjectAttributes_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: getPtr("my-obj"), ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) cancel() if err := checkSdkApiErr(err, "NoSuchKey"); err != nil { return err } return nil }) } func GetObjectAttributes_invalid_attrs(s *S3Conf) error { testName := "GetObjectAttributes_invalid_attrs" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, types.ObjectAttributes("Invalid_argument"), }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidObjectAttributes)); err != nil { return err } return nil }) } func GetObjectAttributes_invalid_parent(s *S3Conf) error { testName := "GetObjectAttributes_invalid_parent" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "not-a-dir" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } obj = "not-a-dir/bad-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) cancel() var bae *types.NoSuchKey if !errors.As(err, &bae) { return err } return nil }) } func GetObjectAttributes_invalid_single_attribute(s *S3Conf) error { testName := "GetObjectAttributes_invalid_single_attribute" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributes("invalid_attr"), }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidObjectAttributes)); err != nil { return err } return nil }) } func GetObjectAttributes_empty_attrs(s *S3Conf) error { testName := "GetObjectAttributes_empty_attrs" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, ObjectAttributes: []types.ObjectAttributes{}, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectAttributesInvalidHeader)); err != nil { return err } return nil }) } func GetObjectAttributes_existing_object(s *S3Conf) error { testName := "GetObjectAttributes_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, data_len := "my-obj", int64(45679) data := make([]byte, data_len) _, err := rand.Read(data) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, Body: bytes.NewReader(data), }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, types.ObjectAttributesObjectSize, types.ObjectAttributesStorageClass, }, }) cancel() if err != nil { return err } if resp.ETag == nil || out.ETag == nil { return fmt.Errorf("nil ETag output") } if strings.Trim(*resp.ETag, "\"") != *out.ETag { return fmt.Errorf("expected ETag to be %v, instead got %v", strings.Trim(*resp.ETag, "\""), *out.ETag) } if out.ObjectSize == nil { return fmt.Errorf("nil object size output") } if *out.ObjectSize != data_len { return fmt.Errorf("expected object size to be %v, instead got %v", data_len, *out.ObjectSize) } if out.Checksum != nil { return fmt.Errorf("expected checksum to be nil, instead got %v", *out.Checksum) } if out.StorageClass != types.StorageClassStandard { return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, out.StorageClass) } if out.LastModified == nil { return fmt.Errorf("expected non nil LastModified") } return nil }) } func GetObjectAttributes_checksums(s *S3Conf) error { testName := "GetObjectAttributes_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs := []struct { key string checksumAlgo types.ChecksumAlgorithm }{ { key: "obj-1", checksumAlgo: types.ChecksumAlgorithmCrc32, }, { key: "obj-2", checksumAlgo: types.ChecksumAlgorithmCrc32c, }, { key: "obj-3", checksumAlgo: types.ChecksumAlgorithmSha1, }, { key: "obj-4", checksumAlgo: types.ChecksumAlgorithmSha256, }, { key: "obj-5", checksumAlgo: types.ChecksumAlgorithmCrc64nvme, }, } for i, el := range objs { out, err := putObjectWithData(int64(i*120), &s3.PutObjectInput{ Bucket: &bucket, Key: &el.key, ChecksumAlgorithm: el.checksumAlgo, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &el.key, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesChecksum, }, }) cancel() if err != nil { return err } if res.Checksum == nil { return fmt.Errorf("expected non-nil checksum in the response") } if res.Checksum.ChecksumType != types.ChecksumTypeFullObject { return fmt.Errorf("expected the %v object checksum type to be %v, instaed got %v", el.key, types.ChecksumTypeFullObject, res.Checksum.ChecksumType) } if getString(res.Checksum.ChecksumCRC32) != getString(out.res.ChecksumCRC32) { return fmt.Errorf("expected crc32 checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32), getString(res.Checksum.ChecksumCRC32)) } if getString(res.Checksum.ChecksumCRC32C) != getString(out.res.ChecksumCRC32C) { return fmt.Errorf("expected crc32c checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32C), getString(res.Checksum.ChecksumCRC32C)) } if getString(res.Checksum.ChecksumSHA1) != getString(out.res.ChecksumSHA1) { return fmt.Errorf("expected sha1 checksum to be %v, instead got %v", getString(out.res.ChecksumSHA1), getString(res.Checksum.ChecksumSHA1)) } if getString(res.Checksum.ChecksumSHA256) != getString(out.res.ChecksumSHA256) { return fmt.Errorf("expected sha256 checksum to be %v, instead got %v", getString(out.res.ChecksumSHA256), getString(res.Checksum.ChecksumSHA256)) } if getString(res.Checksum.ChecksumCRC64NVME) != getString(out.res.ChecksumCRC64NVME) { return fmt.Errorf("expected crc64nvme checksum to be %v, instead got %v", getString(out.res.ChecksumCRC64NVME), getString(res.Checksum.ChecksumCRC64NVME)) } } return nil }) } func GetObject_non_existing_key(s *S3Conf) error { testName := "GetObject_non_existing_key" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: getPtr("non-existing-key"), }) cancel() var bae *types.NoSuchKey if !errors.As(err, &bae) { return err } return nil }) } func GetObject_directory_object_noslash(s *S3Conf) error { testName := "GetObject_directory_object_noslash" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } obj = "my-obj" ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() var bae *types.NoSuchKey if !errors.As(err, &bae) { return err } return nil }) } func GetObject_with_range(s *S3Conf) error { testName := "GetObject_with_range" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { // Match HeadObject_with_range: 100-byte object obj, objLength := "my-obj", int64(100) res, err := putObjectWithData(objLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } testGetObjectRange := func(rng, contentRange string, cLength int64, expData []byte, expErr error) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) defer cancel() out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, Range: &rng, }) if err == nil && expErr != nil { return fmt.Errorf("expected err %v, instead got nil", expErr) } if err != nil { if expErr == nil { return err } parsedErr, ok := expErr.(s3err.APIError) if !ok { return fmt.Errorf("invalid error type provided, expected s3err.APIError") } return checkApiErr(err, parsedErr) } if out.ContentLength == nil { return fmt.Errorf("expected non nil content-length") } if *out.ContentLength != cLength { return fmt.Errorf("expected content-length to be %v, instead got %v", cLength, *out.ContentLength) } if getString(out.AcceptRanges) != "bytes" { return fmt.Errorf("expected accept-ranges to be 'bytes', instead got %v", getString(out.AcceptRanges)) } if getString(out.ContentRange) != contentRange { return fmt.Errorf("expected content-range to be %v, instead got %v", contentRange, getString(out.ContentRange)) } outData, err := io.ReadAll(out.Body) if err != nil { return fmt.Errorf("read object data: %w", err) } out.Body.Close() if !isSameData(outData, expData) { return fmt.Errorf("incorrect data retrieved") } return nil } for _, el := range []struct { rng string contentRange string cLength int64 expData []byte expErr error }{ // Invalid / ignored ranges (return full object, empty Content-Range) {"bytes=,", "", objLength, res.data, nil}, {"bytes= -1", "", objLength, res.data, nil}, {"bytes=--1", "", objLength, res.data, nil}, {"bytes=0 -1", "", objLength, res.data, nil}, {"bytes=0--1", "", objLength, res.data, nil}, {"bytes=10-5", "", objLength, res.data, nil}, {"bytes=abc", "", objLength, res.data, nil}, {"bytes=a-z", "", objLength, res.data, nil}, {"foo=0-1", "", objLength, res.data, nil}, {"bytes=abc-xyz", "", objLength, res.data, nil}, {"bytes=100-x", "", objLength, res.data, nil}, {"bytes=0-0,1-2", "", objLength, res.data, nil}, {fmt.Sprintf("bytes=%v-%v", objLength+2, objLength-100), "", objLength, res.data, nil}, // Valid numeric with leading zeros {"bytes=00-01", "bytes 0-1/100", 2, res.data[0:2], nil}, // Suffix ranges {"bytes=-1", "bytes 99-99/100", 1, res.data[99:], nil}, {"bytes=-2", "bytes 98-99/100", 2, res.data[98:], nil}, {"bytes=-10", "bytes 90-99/100", 10, res.data[90:], nil}, {"bytes=-100", "bytes 0-99/100", objLength, res.data, nil}, {"bytes=-101", "bytes 0-99/100", objLength, res.data, nil}, // Standard byte ranges {"bytes=0-0", "bytes 0-0/100", 1, res.data[0:1], nil}, {"bytes=0-99", "bytes 0-99/100", objLength, res.data, nil}, {"bytes=0-100", "bytes 0-99/100", objLength, res.data, nil}, {"bytes=0-999999", "bytes 0-99/100", objLength, res.data, nil}, {"bytes=1-99", "bytes 1-99/100", 99, res.data[1:], nil}, {"bytes=50-99", "bytes 50-99/100", 50, res.data[50:], nil}, {"bytes=50-", "bytes 50-99/100", 50, res.data[50:], nil}, {"bytes=0-", "bytes 0-99/100", objLength, res.data, nil}, {"bytes=99-99", "bytes 99-99/100", 1, res.data[99:], nil}, // Unsatisfiable -> error {"bytes=-0", "", 0, nil, s3err.GetAPIError(s3err.ErrInvalidRange)}, {"bytes=100-100", "", 0, nil, s3err.GetAPIError(s3err.ErrInvalidRange)}, {"bytes=100-110", "", 0, nil, s3err.GetAPIError(s3err.ErrInvalidRange)}, } { if err := testGetObjectRange(el.rng, el.contentRange, el.cLength, el.expData, el.expErr); err != nil { return err } } return nil }) } func GetObject_zero_len_with_range(s *S3Conf) error { testName := "GetObject_zero_len_with_range" return getObject_zero_len_with_range_helper(testName, "my-obj", s) } func GetObject_dir_with_range(s *S3Conf) error { testName := "GetObject_dir_with_range" return getObject_zero_len_with_range_helper(testName, "my-dir/", s) } func GetObject_invalid_parent(s *S3Conf) error { testName := "GetObject_invalid_parent" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dataLength, obj := int64(1234567), "not-a-dir" _, err := putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: getPtr("not-a-dir/bad-obj"), }) cancel() var bae *types.NoSuchKey if !errors.As(err, &bae) { return err } return nil }) } func GetObject_checksums(s *S3Conf) error { testName := "GetObject_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs := []struct { key string checksumAlgo types.ChecksumAlgorithm }{ { key: "obj-1", checksumAlgo: types.ChecksumAlgorithmCrc32, }, { key: "obj-2", checksumAlgo: types.ChecksumAlgorithmCrc32c, }, { key: "obj-3", checksumAlgo: types.ChecksumAlgorithmSha1, }, { key: "obj-4", checksumAlgo: types.ChecksumAlgorithmSha256, }, { key: "obj-5", checksumAlgo: types.ChecksumAlgorithmCrc64nvme, }, } for i, el := range objs { out, err := putObjectWithData(int64(i*120), &s3.PutObjectInput{ Bucket: &bucket, Key: &el.key, ChecksumAlgorithm: el.checksumAlgo, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &el.key, ChecksumMode: types.ChecksumModeEnabled, }) cancel() if err != nil { return err } if res.ChecksumType != types.ChecksumTypeFullObject { return fmt.Errorf("expected the %v object checksum type to be %v, instaed got %v", el.key, types.ChecksumTypeFullObject, res.ChecksumType) } if getString(res.ChecksumCRC32) != getString(out.res.ChecksumCRC32) { return fmt.Errorf("expected crc32 checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32), getString(res.ChecksumCRC32)) } if getString(res.ChecksumCRC32C) != getString(out.res.ChecksumCRC32C) { return fmt.Errorf("expected crc32c checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32C), getString(res.ChecksumCRC32C)) } if getString(res.ChecksumSHA1) != getString(out.res.ChecksumSHA1) { return fmt.Errorf("expected sha1 checksum to be %v, instead got %v", getString(out.res.ChecksumSHA1), getString(res.ChecksumSHA1)) } if getString(res.ChecksumSHA256) != getString(out.res.ChecksumSHA256) { return fmt.Errorf("expected sha256 checksum to be %v, instead got %v", getString(out.res.ChecksumSHA256), getString(res.ChecksumSHA256)) } if getString(res.ChecksumCRC64NVME) != getString(out.res.ChecksumCRC64NVME) { return fmt.Errorf("expected crc64nvme checksum to be %v, instead got %v", getString(out.res.ChecksumCRC64NVME), getString(res.ChecksumCRC64NVME)) } } return nil }) } func GetObject_large_object(s *S3Conf) error { testName := "GetObject_large_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { //FIXME: make the object size larger after // resolving the context deadline exceeding issue // in the github actions dataLength, obj := int64(100*1024*1024), "my-obj" ctype := defaultContentType r, err := putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ContentType: &ctype, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), longTimeout) out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected non nil content length") } if *out.ContentLength != dataLength { return fmt.Errorf("expected content-length %v, instead got %v", dataLength, out.ContentLength) } bdy, err := io.ReadAll(out.Body) if err != nil { return err } defer out.Body.Close() outCsum := sha256.Sum256(bdy) if outCsum != r.csum { return fmt.Errorf("expected the output data checksum to be %v, instead got %v", r.csum, outCsum) } return nil }) } func GetObject_success(s *S3Conf) error { testName := "GetObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dataLength, obj := int64(1234567), "my-obj" ctype, cDisp, cEnc, cLang := defaultContentType, "cont-desp", "json", "eng" cacheControl, expires := "cache-ctrl", time.Now().Add(time.Hour*2) meta := map[string]string{ "foo": "bar", "baz": "quxx", } r, err := putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ContentType: &ctype, ContentDisposition: &cDisp, ContentEncoding: &cEnc, ContentLanguage: &cLang, Expires: &expires, CacheControl: &cacheControl, Metadata: meta, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err != nil { return err } if *out.ContentLength != dataLength { return fmt.Errorf("expected content-length %v, instead got %v", dataLength, out.ContentLength) } if getString(out.ContentType) != defaultContentType { return fmt.Errorf("expected Content-Type %v, instead got %v", defaultContentType, getString(out.ContentType)) } if getString(out.ContentDisposition) != cDisp { return fmt.Errorf("expected Content-Disposition %v, instead got %v", cDisp, getString(out.ContentDisposition)) } if getString(out.ContentEncoding) != cEnc { return fmt.Errorf("expected Content-Encoding %v, instead got %v", cEnc, getString(out.ContentEncoding)) } if getString(out.ContentLanguage) != cLang { return fmt.Errorf("expected Content-Language %v, instead got %v", cLang, getString(out.ContentLanguage)) } if getString(out.ExpiresString) != expires.UTC().Format(timefmt) { return fmt.Errorf("expected Expiress %v, instead got %v", expires.UTC().Format(timefmt), getString(out.ExpiresString)) } if getString(out.CacheControl) != cacheControl { return fmt.Errorf("expected Cache-Control %v, instead got %v", cacheControl, getString(out.CacheControl)) } if out.StorageClass != types.StorageClassStandard { return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, out.StorageClass) } if !areMapsSame(out.Metadata, meta) { return fmt.Errorf("expected the object metadata to be %v, instead got %v", meta, out.Metadata) } bdy, err := io.ReadAll(out.Body) if err != nil { return err } defer out.Body.Close() outCsum := sha256.Sum256(bdy) if outCsum != r.csum { return fmt.Errorf("invalid object data") } return nil }) } const directoryContentType = "application/x-directory" func GetObject_directory_success(s *S3Conf) error { testName := "GetObject_directory_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dataLength, obj := int64(0), "my-dir/" _, err := putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected non nil content length") } if *out.ContentLength != dataLength { return fmt.Errorf("expected content-length %v, instead got %v", dataLength, out.ContentLength) } if getString(out.ContentType) != directoryContentType { return fmt.Errorf("expected content type %v, instead got %v", directoryContentType, getString(out.ContentType)) } if out.StorageClass != types.StorageClassStandard { return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, out.StorageClass) } out.Body.Close() return nil }) } func GetObject_by_range_resp_status(s *S3Conf) error { testName := "GetObject_by_range_resp_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, dLen := "my-obj", int64(4000) _, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } req, err := createSignedReq( http.MethodGet, s.endpoint, fmt.Sprintf("%v/%v", bucket, obj), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), map[string]string{ "Range": "bytes=100-200", }, ) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusPartialContent { return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusPartialContent, resp.StatusCode) } return nil }) } func GetObject_non_existing_dir_object(s *S3Conf) error { testName := "GetObject_non_existing_dir_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dataLength, obj := int64(1234567), "my-obj" _, err := putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } obj = "my-obj/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err := checkSdkApiErr(err, "NoSuchKey"); err != nil { return err } return nil }) } func ListObjects_non_existing_bucket(s *S3Conf) error { testName := "ListObjects_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { bckt := getBucketName() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bckt, }) cancel() if err := checkSdkApiErr(err, "NoSuchBucket"); err != nil { return err } return nil }) } func ListObjects_with_prefix(s *S3Conf) error { testName := "ListObjects_with_prefix" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { prefix := "obj" objWithPrefix := []string{prefix + "/bar", prefix + "/baz/bla", prefix + "/foo"} contents, err := putObjects(s3client, append(objWithPrefix, []string{"azy/csf", "hell"}...), bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, Prefix: &prefix, }) cancel() if err != nil { return err } if getString(out.Prefix) != prefix { return fmt.Errorf("expected prefix %v, instead got %v", prefix, getString(out.Prefix)) } if !compareObjects(contents[2:], out.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents[2:], out.Contents) } return nil }) } func ListObjects_paginated(s *S3Conf) error { testName := "ListObjects_paginated" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"dir1/subdir/file.txt", "dir1/subdir.ext", "dir1/subdir1.ext", "dir1/subdir2.ext"}, bucket) if err != nil { return err } objs, prefixes, err := listObjects(s3client, bucket, "dir1/", "/", 2) if err != nil { return err } expected := []string{"dir1/subdir.ext", "dir1/subdir1.ext", "dir1/subdir2.ext"} if !hasObjNames(objs, expected) { return fmt.Errorf("expected objects %v, instead got %v", expected, objStrings(objs)) } expectedPrefix := []string{"dir1/subdir/"} if !hasPrefixName(prefixes, expectedPrefix) { return fmt.Errorf("expected prefixes %v, instead got %v", expectedPrefix, pfxStrings(prefixes)) } return nil }) } func ListObjects_truncated(s *S3Conf) error { testName := "ListObjects_truncated" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { maxKeys := int32(2) contents, err := putObjects(s3client, []string{"foo", "bar", "baz"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out1, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err != nil { return err } if out1.IsTruncated == nil || !*out1.IsTruncated { return fmt.Errorf("expected output to be truncated") } if out1.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *out1.MaxKeys != maxKeys { return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, out1.MaxKeys) } if out1.NextMarker == nil { return fmt.Errorf("expected non nil next marker") } if *out1.NextMarker != "baz" { return fmt.Errorf("expected next-marker to be baz, instead got %v", *out1.NextMarker) } if !compareObjects(contents[:2], out1.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents[:2], out1.Contents) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out2, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, Marker: out1.NextMarker, }) cancel() if err != nil { return err } if out2.IsTruncated == nil { return fmt.Errorf("expected non nil is-truncated") } if *out2.IsTruncated { return fmt.Errorf("expected output not to be truncated") } if getString(out2.Marker) != getString(out1.NextMarker) { return fmt.Errorf("expected marker to be %v, instead got %v", getString(out1.NextMarker), getString(out2.Marker)) } if !compareObjects(contents[2:], out2.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents[2:], out2.Contents) } return nil }) } func ListObjects_invalid_max_keys(s *S3Conf) error { testName := "ListObjects_invalid_max_keys" maxKeys := int32(-5) return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMaxKeys)); err != nil { return err } return nil }) } func ListObjects_max_keys_0(s *S3Conf) error { testName := "ListObjects_max_keys_0" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objects := []string{"foo", "bar", "baz"} _, err := putObjects(s3client, objects, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) maxKeys := int32(0) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err != nil { return nil } if len(out.Contents) > 0 { return fmt.Errorf("unexpected output for list objects with max-keys 0") } return nil }) } func ListObjects_exceeding_max_keys(s *S3Conf) error { testName := "ListObjects_exceeding_max_keys" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) maxKeys := int32(233333333) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err != nil { return nil } if out.MaxKeys == nil { return fmt.Errorf("unexpected nil max-keys") } if *out.MaxKeys != 1000 { return fmt.Errorf("expected the max-keys to be %v, instaed got %v", 1000, *out.MaxKeys) } return nil }) } func ListObjects_delimiter(s *S3Conf) error { testName := "ListObjects_delimiter" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo/bar/baz", "foo/bar/xyzzy", "quux/thud", "asdf"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, Delimiter: getPtr("/"), }) cancel() if err != nil { return err } if getString(out.Delimiter) != "/" { return fmt.Errorf("expected delimiter to be /, instead got %v", getString(out.Delimiter)) } if len(out.Contents) != 1 || getString(out.Contents[0].Key) != "asdf" { return fmt.Errorf("expected result [\"asdf\"], instead got %v", out.Contents) } if !comparePrefixes([]string{"foo/", "quux/"}, out.CommonPrefixes) { return fmt.Errorf("expected common prefixes to be %v, instead got %v", []string{"foo/", "quux/"}, out.CommonPrefixes) } return nil }) } func ListObjects_max_keys_none(s *S3Conf) error { testName := "ListObjects_max_keys_none" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo", "bar", "baz"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if out.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *out.MaxKeys != 1000 { return fmt.Errorf("expected max-keys to be 1000, instead got %v", out.MaxKeys) } return nil }) } func ListObjects_marker_not_from_obj_list(s *S3Conf) error { testName := "ListObjects_marker_not_from_obj_list" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"foo", "bar", "baz", "qux", "hello", "xyz"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, Marker: getPtr("ceil"), }) cancel() if err != nil { return err } if !compareObjects(contents[2:], out.Contents) { return fmt.Errorf("expected output to be %v, instead got %v", contents, out.Contents) } return nil }) } func ListObjects_with_checksum(s *S3Conf) error { testName := "ListObjects_with_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents := []types.Object{} for i, el := range types.ChecksumAlgorithmCrc32.Values() { key := fmt.Sprintf("obj-%v", i) size := int64(i * 30) out, err := putObjectWithData(size, &s3.PutObjectInput{ Bucket: &bucket, Key: &key, ChecksumAlgorithm: el, }, s3client) if err != nil { return err } contents = append(contents, types.Object{ Key: &key, ETag: out.res.ETag, Size: &size, StorageClass: types.ObjectStorageClassStandard, ChecksumAlgorithm: []types.ChecksumAlgorithm{ el, }, ChecksumType: out.res.ChecksumType, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareObjects(contents, res.Contents) { return fmt.Errorf("expected the objects list to be %v, instead got %v", contents, res.Contents) } return nil }) } func ListObjects_list_all_objs(s *S3Conf) error { testName := "ListObjects_list_all_objs" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"foo", "bar", "baz", "quxx/ceil", "ceil", "hello/world"}, bucket) if err != nil { return err } // Test 1: List all objects without pagination ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if out.Marker != nil { return fmt.Errorf("expected the Marker to be nil, instead got %v", *out.Marker) } if out.NextMarker != nil { return fmt.Errorf("expected the NextMarker to be nil, instead got %v", *out.NextMarker) } if out.Delimiter != nil { return fmt.Errorf("expected the Delimiter to be nil, instead got %v", *out.Delimiter) } if out.Prefix != nil { return fmt.Errorf("expected the Prefix to be nil, instead got %v", *out.Prefix) } if !compareObjects(contents, out.Contents) { return fmt.Errorf("expected the contents to be %v, instead got %v", contents, out.Contents) } // Test 2: List all objects with pagination using ListObjectsV2 var marker *string var allObjects []types.Object maxKeys := int32(2) for { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, MaxKeys: &maxKeys, Marker: marker, }) cancel() if err != nil { return err } allObjects = append(allObjects, out.Contents...) if out.NextMarker == nil || !*out.IsTruncated { break } marker = out.NextMarker } if !compareObjects(contents, allObjects) { return fmt.Errorf("expected the contents to be %v, instead got %v", contents, allObjects) } return nil }) } func ListObjects_nested_dir_file_objs(s *S3Conf) error { testName := "ListObjects_nested_dir_file_objs" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"foo/bar/", "foo/bar/baz", "foo/bar/quxx"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareObjects(contents, res.Contents) { return fmt.Errorf("expected the objects list to be %+v, instead got %+v", contents, res.Contents) } // Clean up the nested objects to avoid `ErrDirectoryNotEmpty` error on teardown for _, obj := range []string{"foo/bar/baz", "foo/bar/quxx"} { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } } return nil }) } func ListObjects_check_owner(s *S3Conf) error { testName := "ListObjects_check_owner" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs, err := putObjects(s3client, []string{"foo", "bar/baz", "quxx/xyz/eee", "abc/", "bcc"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } for i := range res.Contents { res.Contents[i].Owner = &types.Owner{ ID: &s.awsID, } } if !compareObjects(objs, res.Contents) { return fmt.Errorf("expected the contents to be %v, instead got %v", objs, res.Contents) } return nil }) } func ListObjects_non_truncated_common_prefixes(s *S3Conf) error { testName := "ListObjects_non_truncated_common_prefixes" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"asdf", "boo/bar", "boo/baz/xyzzy", "cquux/thud", "cquux/bla"}, bucket) if err != nil { return err } delim, marker, maxKeys := "/", "boo/", int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, Marker: &marker, Delimiter: &delim, MaxKeys: &maxKeys, }) cancel() if err != nil { return err } if res.IsTruncated == nil { return fmt.Errorf("expected non-nil istruncated") } if *res.IsTruncated { return fmt.Errorf("expected non-truncated result") } if res.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *res.MaxKeys != maxKeys { return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, *res.MaxKeys) } if getString(res.Delimiter) != delim { return fmt.Errorf("expected delimiter to be %v, instead got %v", delim, getString(res.Delimiter)) } if getString(res.Marker) != marker { return fmt.Errorf("expected marker to be %v, instead got %v", getString(res.Marker), marker) } if len(res.Contents) != 0 { return fmt.Errorf("expected empty contents, instead got %+v", res.Contents) } cPrefs := []string{"cquux/"} if !comparePrefixes(cPrefs, res.CommonPrefixes) { return fmt.Errorf("expected common prefixes to be %v, instead got %+v", cPrefs, res.CommonPrefixes) } return nil }) } func ListObjectsV2_start_after(s *S3Conf) error { testName := "ListObjectsV2_start_after" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"foo", "bar", "baz"}, bucket) if err != nil { return err } startAfter := "bar" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, StartAfter: &startAfter, }) cancel() if err != nil { return err } if getString(out.StartAfter) != startAfter { return fmt.Errorf("expected StartAfter to be %v, insted got %v", startAfter, getString(out.StartAfter)) } if !compareObjects(contents[1:], out.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents, out.Contents) } return nil }) } func ListObjectsV2_both_start_after_and_continuation_token(s *S3Conf) error { testName := "ListObjectsV2_both_start_after_and_continuation_token" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"foo", "bar", "baz", "quxx"}, bucket) if err != nil { return err } var maxKeys int32 = 1 ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err != nil { return err } if out.IsTruncated == nil || !*out.IsTruncated { return fmt.Errorf("expected output to be truncated") } if out.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *out.MaxKeys != maxKeys { return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, out.MaxKeys) } if getString(out.NextContinuationToken) != "bar" { return fmt.Errorf("expected next-marker to be baz, instead got %v", getString(out.NextContinuationToken)) } if !compareObjects(contents[:1], out.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents[:1], out.Contents) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, ContinuationToken: out.NextContinuationToken, StartAfter: getPtr("baz"), }) cancel() if err != nil { return err } if !compareObjects(contents[2:], resp.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents[2:], resp.Contents) } return nil }) } func ListObjectsV2_start_after_not_in_list(s *S3Conf) error { testName := "ListObjectsV2_start_after_not_in_list" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"foo", "bar", "baz", "quxx"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, StartAfter: getPtr("blah"), }) cancel() if err != nil { return err } if !compareObjects(contents[2:], out.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents[2:], out.Contents) } return nil }) } func ListObjectsV2_start_after_empty_result(s *S3Conf) error { testName := "ListObjectsV2_start_after_empty_result" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo", "bar", "baz", "quxx"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, StartAfter: getPtr("zzz"), }) cancel() if err != nil { return err } if len(out.Contents) != 0 { return fmt.Errorf("expected empty output instead got %v", out.Contents) } return nil }) } func ListObjectsV2_both_delimiter_and_prefix(s *S3Conf) error { testName := "ListObjectsV2_both_delimiter_and_prefix" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{ "sample.jpg", "photos/2006/January/sample.jpg", "photos/2006/February/sample2.jpg", "photos/2006/February/sample3.jpg", "photos/2006/February/sample4.jpg", }, bucket) if err != nil { return err } delim, prefix := "/", "photos/2006/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, Delimiter: &delim, Prefix: &prefix, }) cancel() if err != nil { return err } if res.Delimiter == nil || *res.Delimiter != delim { return fmt.Errorf("expected the delimiter to be %v", delim) } if res.Prefix == nil || *res.Prefix != prefix { return fmt.Errorf("expected the prefix to be %v", prefix) } if !comparePrefixes([]string{"photos/2006/February/", "photos/2006/January/"}, res.CommonPrefixes) { return fmt.Errorf("expected the common prefixes to be %v, instead got %v", []string{"photos/2006/February/", "photos/2006/January/"}, res.CommonPrefixes) } if len(res.Contents) != 0 { return fmt.Errorf("expected empty objects list, instead got %v", res.Contents) } return nil }) } func ListObjectsV2_single_dir_object_with_delim_and_prefix(s *S3Conf) error { testName := "ListObjectsV2_single_dir_object_with_delim_and_prefix" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"a/"}, bucket) if err != nil { return err } delim, prefix := "/", "a" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, Delimiter: &delim, Prefix: &prefix, }) cancel() if err != nil { return err } if !comparePrefixes([]string{"a/"}, res.CommonPrefixes) { return fmt.Errorf("expected the common prefixes to be %v, instead got %v", []string{"a/"}, res.CommonPrefixes) } if len(res.Contents) != 0 { return fmt.Errorf("expected empty objects list, instead got %v", res.Contents) } prefix = "a/" ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err = s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, Delimiter: &delim, Prefix: &prefix, }) cancel() if err != nil { return err } if !compareObjects(contents, res.Contents) { return fmt.Errorf("expected the object list to be %v, instead got %v", []string{"a/"}, res.Contents) } if len(res.CommonPrefixes) != 0 { return fmt.Errorf("expected empty common prefixes, instead got %v", res.CommonPrefixes) } return nil }) } func ListObjectsV2_truncated_common_prefixes(s *S3Conf) error { testName := "ListObjectsV2_truncated_common_prefixes" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"d1/f1", "d2/f2", "d3/f3", "d4/f4"}, bucket) if err != nil { return err } delim, maxKeys := "/", int32(3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, Delimiter: &delim, MaxKeys: &maxKeys, }) cancel() if err != nil { return err } if !comparePrefixes([]string{"d1/", "d2/", "d3/"}, out.CommonPrefixes) { return fmt.Errorf("expected the common prefixes to be %v, instead got %v", []string{"d1/", "d2/", "d3/"}, out.CommonPrefixes) } if out.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *out.MaxKeys != maxKeys { return fmt.Errorf("expected the max-keys to be %v, instead got %v", maxKeys, *out.MaxKeys) } if getString(out.Delimiter) != delim { return fmt.Errorf("expected the delimiter to be %v, instead got %v", delim, getString(out.Delimiter)) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err = s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, Delimiter: &delim, ContinuationToken: out.NextContinuationToken, }) cancel() if err != nil { return err } if !comparePrefixes([]string{"d4/"}, out.CommonPrefixes) { return fmt.Errorf("expected the common prefixes to be %v, instead got %v", []string{"d4/"}, out.CommonPrefixes) } if getString(out.Delimiter) != delim { return fmt.Errorf("expected the delimiter to be %v, instead got %v", delim, getString(out.Delimiter)) } return nil }) } func ListObjectsV2_non_truncated_common_prefixes(s *S3Conf) error { testName := "ListObjectsV2_non_truncated_common_prefixes" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"asdf", "boo/bar", "boo/baz/xyzzy", "cquux/thud", "cquux/bla"}, bucket) if err != nil { return err } delim, marker, maxKeys := "/", "boo/", int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, StartAfter: &marker, Delimiter: &delim, MaxKeys: &maxKeys, }) cancel() if err != nil { return err } if res.IsTruncated == nil { return fmt.Errorf("expected non-nil istruncated") } if *res.IsTruncated { return fmt.Errorf("expected non-truncated result") } if res.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *res.MaxKeys != maxKeys { return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, *res.MaxKeys) } if getString(res.Delimiter) != delim { return fmt.Errorf("expected delimiter to be %v, instead got %v", delim, getString(res.Delimiter)) } if len(res.Contents) != 0 { return fmt.Errorf("expected empty contents, instead got %+v", res.Contents) } cPrefs := []string{"cquux/"} if !comparePrefixes(cPrefs, res.CommonPrefixes) { return fmt.Errorf("expected common prefixes to be %v, instead got %+v", cPrefs, res.CommonPrefixes) } return nil }) } func ListObjectsV2_all_objs_max_keys(s *S3Conf) error { testName := "ListObjectsV2_all_objs_max_keys" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"bar", "baz", "foo"}, bucket) if err != nil { return err } maxKeys := int32(3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err != nil { return err } if out.IsTruncated == nil || *out.IsTruncated { return fmt.Errorf("expected the output not to be truncated") } if getString(out.NextContinuationToken) != "" { return fmt.Errorf("expected empty NextContinuationToken, instead got %v", getString(out.NextContinuationToken)) } if out.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *out.MaxKeys != maxKeys { return fmt.Errorf("expected the max-keys to be %v, instead got %v", maxKeys, *out.MaxKeys) } if !compareObjects(contents, out.Contents) { return fmt.Errorf("expected the objects list to be %v, instead got %v", contents, out.Contents) } return nil }) } func ListObjectsV2_exceeding_max_keys(s *S3Conf) error { testName := "ListObjectsV2_exceeding_max_keys" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) maxKeys := int32(233453333) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err != nil { return nil } if out.MaxKeys == nil { return fmt.Errorf("unexpected nil max-keys") } if *out.MaxKeys != 1000 { return fmt.Errorf("expected the max-keys to be %v, instaed got %v", 1000, *out.MaxKeys) } return nil }) } func ListObjectsV2_list_all_objs(s *S3Conf) error { testName := "ListObjectsV2_list_all_objs" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"a", "aa", "aaa", "aaaa", "bar", "baz", "foo", "obj1", "hello/world", "xyzz/quxx"}, bucket) if err != nil { return err } // Test 1: List all objects without pagination ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) cancel() if err != nil { return err } if out.StartAfter != nil { return fmt.Errorf("expected the StartAfter to be nil, instead got %v", *out.StartAfter) } if out.ContinuationToken != nil { return fmt.Errorf("expected the ContinuationToken to be nil, instead got %v", *out.ContinuationToken) } if out.NextContinuationToken != nil { return fmt.Errorf("expected the NextContinuationToken to be nil, instead got %v", *out.NextContinuationToken) } if out.Delimiter != nil { return fmt.Errorf("expected the Delimiter to be nil, instead got %v", *out.Delimiter) } if out.Prefix != nil { return fmt.Errorf("expected the Prefix to be nil, instead got %v", *out.Prefix) } if !compareObjects(contents, out.Contents) { return fmt.Errorf("expected the contents to be %v, instead got %v", contents, out.Contents) } // Test 2: List all objects with pagination using ListObjectsV2 var continuationToken *string var allObjects []types.Object maxKeys := int32(2) for { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, MaxKeys: &maxKeys, ContinuationToken: continuationToken, }) cancel() if err != nil { return err } allObjects = append(allObjects, out.Contents...) if out.NextContinuationToken == nil || !*out.IsTruncated { break } continuationToken = out.NextContinuationToken } if !compareObjects(contents, allObjects) { return fmt.Errorf("expected the paginated contents to be %v, instead got %v", contents, allObjects) } return nil }) } func ListObjectsV2_with_owner(s *S3Conf) error { testName := "ListObjectsV2_with_owner" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs, err := putObjects(s3client, []string{"foo", "bar/baz", "quxx/xyz/eee", "abc/", "bcc"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, FetchOwner: getBoolPtr(true), }) cancel() if err != nil { return err } for i := range res.Contents { res.Contents[i].Owner = &types.Owner{ ID: &s.awsID, } } if !compareObjects(objs, res.Contents) { return fmt.Errorf("expected the contents to be %v, instead got %v", objs, res.Contents) } return nil }) } func ListObjectsV2_with_checksum(s *S3Conf) error { testName := "ListObjectsV2_with_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents := []types.Object{} for i, el := range types.ChecksumAlgorithmCrc32.Values() { key := fmt.Sprintf("obj-%v", i) size := int64(i * 100) out, err := putObjectWithData(size, &s3.PutObjectInput{ Bucket: &bucket, Key: &key, ChecksumAlgorithm: el, }, s3client) if err != nil { return err } contents = append(contents, types.Object{ Key: &key, ETag: out.res.ETag, Size: &size, StorageClass: types.ObjectStorageClassStandard, ChecksumAlgorithm: []types.ChecksumAlgorithm{ el, }, ChecksumType: out.res.ChecksumType, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareObjects(res.Contents, contents) { return fmt.Errorf("expected the objects list to be %v, instead got %v", contents, res.Contents) } return nil }) } func ListObjectsV2_invalid_parent_prefix(s *S3Conf) error { testName := "ListObjectsV2_invalid_parent_prefix" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"file"}, bucket) if err != nil { return err } delim, maxKeys := "/", int32(100) prefix := "file/file/file" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, Delimiter: &delim, MaxKeys: &maxKeys, Prefix: &prefix, }) cancel() if err != nil { return err } if len(out.CommonPrefixes) > 0 { return fmt.Errorf("expected the common prefixes to be %v, instead got %v", []string{""}, out.CommonPrefixes) } if out.MaxKeys == nil { return fmt.Errorf("expected non nil max-keys") } if *out.MaxKeys != maxKeys { return fmt.Errorf("expected the max-keys to be %v, instead got %v", maxKeys, *out.MaxKeys) } if getString(out.Delimiter) != delim { return fmt.Errorf("expected the delimiter to be %v, instead got %v", delim, getString(out.Delimiter)) } if len(out.Contents) > 0 { return fmt.Errorf("expected the objects to be %v, instead got %v", []types.Object{}, out.Contents) } return nil }) } func ListObjectVersions_VD_success(s *S3Conf) error { testName := "ListObjectVersions_VD_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { versions := []types.ObjectVersion{} for i := range 5 { dLgth := int64(i * 100) key := fmt.Sprintf("my-obj-%v", i) out, err := putObjectWithData(dLgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &key, }, s3client) if err != nil { return err } versions = append(versions, types.ObjectVersion{ ETag: out.res.ETag, IsLatest: getBoolPtr(true), Key: &key, Size: &dLgth, VersionId: getPtr("null"), StorageClass: types.ObjectVersionStorageClassStandard, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected object versions output to be %v, instead got %v", versions, res.Versions) } return nil }) } func DeleteObject_non_existing_object(s *S3Conf) error { testName := "DeleteObject_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() return err }) } func DeleteObject_directory_object_noslash(s *S3Conf) error { testName := "DeleteObject_directory_object_noslash" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } obj = "my-obj" ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } // the delete above should succeed, but the object should not be deleted // since it should not correctly match the directory name // so the below head object should also succeed obj = "my-obj/" ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() return err }) } func DeleteObject_non_empty_dir_obj(s *S3Conf) error { testName := "DeleteObject_non_empty_dir_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objToDel := "foo/" nestedObj := objToDel + "bar" _, err := putObjects(s3client, []string{nestedObj, objToDel}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &objToDel, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if len(res.Contents) != 1 { return fmt.Errorf("expected the object list length to be 1, instead got %v", len(res.Contents)) } if getString(res.Contents[0].Key) != nestedObj { return fmt.Errorf("expected the object key to be %v, instead got %v", nestedObj, getString(res.Contents[0].Key)) } return nil }) } func DeleteObject_directory_not_empty(s *S3Conf) error { testName := "DeleteObject_directory_not_empty" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "dir/my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } obj = "dir/" ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() // object servers will return no error, but the posix backend returns // a non-standard directory not empty. This test is a posix only test // to validate the specific error response. if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDirectoryNotEmpty)); err != nil { return err } return nil }) } func DeleteObject_non_existing_dir_object(s *S3Conf) error { testName := "DeleteObject_non_existing_dir_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } obj = "my-obj/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() return err }) } func DeleteObject_directory_object(s *S3Conf) error { testName := "DeleteObject_directory_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "foo/bar/" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() return err }) } func DeleteObject_success(s *S3Conf) error { testName := "DeleteObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err := checkSdkApiErr(err, "NoSuchKey"); err != nil { return err } return nil }) } func DeleteObject_success_status_code(s *S3Conf) error { testName := "DeleteObject_success_status_code" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v/%v", bucket, obj), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusNoContent { return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode) } return nil }) } func DeleteObjects_empty_input(s *S3Conf) error { testName := "DeleteObjects_empty_input" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { contents, err := putObjects(s3client, []string{"foo", "bar", "baz"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{}, }, }) cancel() if err != nil { return err } if len(out.Deleted) != 0 { return fmt.Errorf("expected deleted object count 0, instead got %v", len(out.Deleted)) } if len(out.Errors) != 0 { return fmt.Errorf("expected 0 errors, instead got %v", len(out.Errors)) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareObjects(contents, res.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents, res.Contents) } return nil }) } func DeleteObjects_non_existing_objects(s *S3Conf) error { testName := "DeleteObjects_empty_input" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { delObjects := []types.ObjectIdentifier{{Key: getPtr("obj1")}, {Key: getPtr("obj2")}} ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: delObjects, }, }) cancel() if err != nil { return err } if len(out.Deleted) != 2 { return fmt.Errorf("expected deleted object count 2, instead got %v", len(out.Deleted)) } if len(out.Errors) != 0 { return fmt.Errorf("expected 0 errors, instead got %v, %v", len(out.Errors), out.Errors) } return nil }) } func DeleteObjects_success(s *S3Conf) error { testName := "DeleteObjects_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objects, objToDel := []string{"obj1", "obj2", "obj3"}, []string{"foo", "bar", "baz"} contents, err := putObjects(s3client, append(objToDel, objects...), bucket) if err != nil { return err } delObjects := []types.ObjectIdentifier{} delResult := []types.DeletedObject{} for _, key := range objToDel { k := key delObjects = append(delObjects, types.ObjectIdentifier{Key: &k}) delResult = append(delResult, types.DeletedObject{Key: &k}) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: delObjects, }, }) cancel() if err != nil { return err } if len(out.Deleted) != 3 { return fmt.Errorf("expected deleted object count 3, instead got %v", len(out.Deleted)) } if len(out.Errors) != 0 { return fmt.Errorf("expected 2 errors, instead got %v", len(out.Errors)) } if !compareDelObjects(delResult, out.Deleted) { return fmt.Errorf("unexpected deleted output") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareObjects(contents[3:], res.Contents) { return fmt.Errorf("expected the output to be %v, instead got %v", contents[3:], res.Contents) } return nil }) } func CopyObject_non_existing_dst_bucket(s *S3Conf) error { testName := "CopyObject_non_existing_dst_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr("bucket/obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func CopyObject_not_owned_source_bucket(s *S3Conf) error { testName := "CopyObject_not_owned_source_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj := "my-obj" _, err := putObjects(s3client, []string{srcObj}, bucket) if err != nil { return err } userClient := s.getUserClient(testuser1) err = createUsers(s, []user{testuser1}) if err != nil { return err } dstBucket := getBucketName() err = setup(s, dstBucket) if err != nil { return err } err = changeBucketsOwner(s, []string{bucket}, testuser1.access) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &dstBucket, Key: getPtr("obj-1"), CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } err = teardown(s, dstBucket) if err != nil { return err } return nil }) } func CopyObject_copy_to_itself(s *S3Conf) error { testName := "CopyObject_copy_to_itself" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidCopyDest)); err != nil { return err } return nil }) } func CopyObject_copy_to_itself_invalid_directive(s *S3Conf) error { testName := "CopyObject_copy_to_itself_invalid_directive" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), MetadataDirective: types.MetadataDirective("invalid"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMetadataDirective)); err != nil { return err } return nil }) } func CopyObject_invalid_tagging_directive(s *S3Conf) error { testName := "CopyObject_invalid_tagging_directive" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), TaggingDirective: types.TaggingDirective("invalid"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTaggingDirective)); err != nil { return err } return nil }) } func CopyObject_should_copy_tagging(s *S3Conf) error { testName := "CopyObject_should_copy_tagging" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstObj := "source-object", "dest-object" tagging := "foo=bar&baz=quxx" _, err := putObjectWithData(100, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, Tagging: &tagging, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &dstObj, }) cancel() if err != nil { return err } expectedTagSet := []types.Tag{ {Key: getPtr("foo"), Value: getPtr("bar")}, {Key: getPtr("baz"), Value: getPtr("quxx")}, } if !areTagsSame(res.TagSet, expectedTagSet) { return fmt.Errorf("expected the tag set to be %v, instead got %v", expectedTagSet, res.TagSet) } return nil }) } func CopyObject_should_replace_tagging(s *S3Conf) error { testName := "CopyObject_should_replace_tagging" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, Tagging: getPtr("key=value&key1=value1"), }, s3client) if err != nil { return err } testTagging := func(taggging string, result map[string]string, expectedErr error) error { dstObj := "destination-object" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, Tagging: &taggging, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), TaggingDirective: types.TaggingDirectiveReplace, }) cancel() if err == nil && expectedErr != nil { return fmt.Errorf("expected err %w, instead got nil", expectedErr) } if err != nil { if expectedErr == nil { return err } switch eErr := expectedErr.(type) { case s3err.APIError: return checkApiErr(err, eErr) default: return fmt.Errorf("invalid err provided: %w", expectedErr) } } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &dstObj, }) cancel() if err != nil { return err } if len(res.TagSet) != len(result) { return fmt.Errorf("tag lengths are not equal: (expected): %v, (got): %v", len(result), len(res.TagSet)) } for _, tag := range res.TagSet { val, ok := result[getString(tag.Key)] if !ok { return fmt.Errorf("tag key not found: %v", getString(tag.Key)) } if val != getString(tag.Value) { return fmt.Errorf("expected the %v tag value to be %v, instead got %v", getString(tag.Key), val, getString(tag.Value)) } } return nil } for _, el := range []struct { tagging string result map[string]string expectedErr error }{ // success cases {"&", map[string]string{}, nil}, {"&&&", map[string]string{}, nil}, {"key", map[string]string{"key": ""}, nil}, {"key&", map[string]string{"key": ""}, nil}, {"key=&", map[string]string{"key": ""}, nil}, {"key=val&", map[string]string{"key": "val"}, nil}, {"key1&key2", map[string]string{"key1": "", "key2": ""}, nil}, {"key1=val1&key2=val2", map[string]string{"key1": "val1", "key2": "val2"}, nil}, // invalid url-encoded {"=", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, {"key%", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, // duplicate keys {"key=val&key=val", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, // invalid tag keys {"key?=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key(=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key*=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key$=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key#=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key@=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key!=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, // invalid tag values {"key=val?", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val(", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val*", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val$", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val#", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val@", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val!", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, // success special chars {"key-key_key.key/key=value-value_value.value/value", map[string]string{"key-key_key.key/key": "value-value_value.value/value"}, nil}, // should handle supported encoded characters {"key%2E=value%2F", map[string]string{"key.": "value/"}, nil}, {"key%2D=value%2B", map[string]string{"key-": "value+"}, nil}, {"key++key=value++value", map[string]string{"key key": "value value"}, nil}, {"key%20key=value%20value", map[string]string{"key key": "value value"}, nil}, {"key%5Fkey=value%5Fvalue", map[string]string{"key_key": "value_value"}, nil}, } { err := testTagging(el.tagging, el.result, el.expectedErr) if err != nil { return err } } return nil }) } func CopyObject_to_itself_with_new_metadata(s *S3Conf) error { testName := "CopyObject_to_itself_with_new_metadata" meta := map[string]string{ "Hello": "World", } return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), Metadata: meta, MetadataDirective: types.MetadataDirectiveReplace, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if !areMapsSame(resp.Metadata, meta) { return fmt.Errorf("expected uploaded object metadata to be %v, instead got %v", meta, resp.Metadata) } // verify updating metadata has correct meta meta = map[string]string{ "New": "Metadata", } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), Metadata: meta, MetadataDirective: types.MetadataDirectiveReplace, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if !areMapsSame(resp.Metadata, meta) { return fmt.Errorf("expected uploaded object metadata to be %v, instead got %v", meta, resp.Metadata) } return nil }) } func CopyObject_copy_source_starting_with_slash(s *S3Conf) error { testName := "CopyObject_CopySource_starting_with_slash" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dataLength, obj := int64(1234567), "src-obj" dstBucket := getBucketName() if err := setup(s, dstBucket); err != nil { return err } r, err := putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &dstBucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("/%v/%v", bucket, obj)), }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &dstBucket, Key: &obj, }) defer cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected content-length to be set, instead got nil") } if *out.ContentLength != dataLength { return fmt.Errorf("expected content-length %v, instead got %v", dataLength, *out.ContentLength) } defer out.Body.Close() bdy, err := io.ReadAll(out.Body) if err != nil { return err } outCsum := sha256.Sum256(bdy) if outCsum != r.csum { return fmt.Errorf("invalid object data") } if err := teardown(s, dstBucket); err != nil { return err } return nil }) } func CopyObject_invalid_copy_source(s *S3Conf) error { testName := "CopyObject_invalid_copy_source" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, test := range []struct { copySource string expectedErr s3err.APIError }{ // invalid encoding { // Invalid hex digits copySource: "bucket/%ZZ", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Ends with incomplete escape copySource: "100%/foo/bar/baz", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Only one digit after % copySource: "bucket/%A/bar", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // 'G' is not a hex digit copySource: "bucket/%G1/", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Just a single percent sign copySource: "%", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Only one hex digit copySource: "bucket/%1", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Incomplete multibyte UTF-8 copySource: "bucket/%C3%", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, // invalid bucket name { // ip v4 address copySource: "192.168.1.1/foo", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket), }, { // ip v6 address copySource: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/something", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket), }, { // some special chars copySource: "my-buc@k&()t/obj", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket), }, // invalid object key { // object is missing copySource: "bucket", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { // object is missing copySource: "bucket/", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, // directory navigation object keys { copySource: "bucket/.", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { copySource: "bucket/..", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { copySource: "bucket/../", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { copySource: "bucket/foo/ba/../../../r/baz", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: getPtr("obj"), CopySource: &test.copySource, }) cancel() if err := checkApiErr(err, test.expectedErr); err != nil { return err } } return nil }) } func CopyObject_non_existing_dir_object(s *S3Conf) error { testName := "CopyObject_non_existing_dir_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dataLength, obj := int64(1234567), "my-obj" dstBucket := getBucketName() err := setup(s, dstBucket) if err != nil { return err } _, err = putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } obj = "my-obj/" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &dstBucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } err = teardown(s, dstBucket) if err != nil { return nil } return nil }) } func CopyObject_should_copy_meta_props(s *S3Conf) error { testName := "CopyObject_should_copy_meta_props" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstObj := "source-object", "dest-object" cType, cEnc, cDesp, cLang, cLength := "application/json", "base64", "test-desp", "us", int64(100) cacheControl, expires := "no-cache", time.Now().Add(time.Hour*10) meta := map[string]string{ "foo": "bar", "baz": "quxx", } _, err := putObjectWithData(cLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, ContentDisposition: &cDesp, ContentEncoding: &cEnc, ContentLanguage: &cLang, ContentType: &cType, CacheControl: &cacheControl, Expires: &expires, Metadata: meta, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(bucket + "/" + srcObj), }) cancel() if err != nil { return err } return checkObjectMetaProps(s3client, bucket, dstObj, ObjectMetaProps{ ContentLength: cLength, ContentType: cType, ContentEncoding: cEnc, ContentDisposition: cDesp, ContentLanguage: cLang, CacheControl: cacheControl, ExpiresString: expires.UTC().Format(timefmt), Metadata: meta, }) }) } func CopyObject_should_replace_meta_props(s *S3Conf) error { testName := "CopyObject_should_replace_meta_props" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstObj := "source-object", "dest-object" expire := time.Now().Add(time.Minute * 10) contentLength := int64(200) _, err := putObjectWithData(contentLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, ContentDisposition: getPtr("test"), ContentEncoding: getPtr("test"), ContentLanguage: getPtr("test"), ContentType: getPtr("test"), CacheControl: getPtr("test"), Expires: &expire, Metadata: map[string]string{ "key": "val", }, }, s3client) if err != nil { return err } cType, cEnc, cDesp, cLang := "application/binary", "hex", "desp", "mex" cacheControl, expires := "no-cache", time.Now().Add(time.Hour*10) meta := map[string]string{ "foo": "bar", "baz": "quxx", } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(bucket + "/" + srcObj), MetadataDirective: types.MetadataDirectiveReplace, ContentDisposition: &cDesp, ContentEncoding: &cEnc, ContentLanguage: &cLang, ContentType: &cType, CacheControl: &cacheControl, Expires: &expires, Metadata: meta, }) cancel() if err != nil { return err } return checkObjectMetaProps(s3client, bucket, dstObj, ObjectMetaProps{ ContentLength: contentLength, ContentType: cType, ContentEncoding: cEnc, ContentDisposition: cDesp, ContentLanguage: cLang, CacheControl: cacheControl, ExpiresString: expires.UTC().Format(timefmt), Metadata: meta, }) }) } func CopyObject_invalid_legal_hold(s *S3Conf) error { testName := "CopyObject_invalid_legal_hold" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstObj := "source-object", "dst-object" _, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatus("invalid_status"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidLegalHoldStatus)) }, withLock()) } func CopyObject_invalid_object_lock_mode(s *S3Conf) error { testName := "CopyObject_invalid_object_lock_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstObj := "source-object", "dst-object" _, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, }, s3client) if err != nil { return err } rDate := time.Now().Add(time.Hour * 20) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), ObjectLockRetainUntilDate: &rDate, ObjectLockMode: types.ObjectLockMode("invalid_mode"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidObjectLockMode)) }, withLock()) } func CopyObject_with_legal_hold(s *S3Conf) error { testName := "CopyObject_with_legal_hold" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstObj := "source-object", "dst-object" _, err := putObjectWithData(100, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: &dstObj, }) cancel() if err != nil { return err } if res.LegalHold.Status != types.ObjectLockLegalHoldStatusOn { return fmt.Errorf("expected the copied object legal hold status to be %v, instead got %v", types.ObjectLockLegalHoldStatusOn, res.LegalHold.Status) } err = changeBucketObjectLockStatus(s3client, bucket, false) if err != nil { return err } return nil }, withLock()) } func CopyObject_with_retention_lock(s *S3Conf) error { testName := "CopyObject_with_retention_lock" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstObj := "source-object", "dst-object" _, err := putObjectWithData(200, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, }, s3client) if err != nil { return err } retDate := time.Now().Add(time.Hour * 7) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), ObjectLockMode: types.ObjectLockModeGovernance, ObjectLockRetainUntilDate: &retDate, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: &dstObj, }) cancel() if err != nil { return err } if res.Retention.Mode != types.ObjectLockRetentionModeGovernance { return fmt.Errorf("expected the copied object retention mode to be %v, instead got %v", types.ObjectLockRetentionModeGovernance, res.Retention.Mode) } if res.Retention.RetainUntilDate.UTC().Unix() != retDate.UTC().Unix() { return fmt.Errorf("expected the retention date to be %v, instead got %v", retDate.Format(time.RFC1123), res.Retention.RetainUntilDate.Format(time.RFC1123)) } err = changeBucketObjectLockStatus(s3client, bucket, false) if err != nil { return err } return nil }, withLock()) } func CopyObject_invalid_checksum_algorithm(s *S3Conf) error { testName := "CopyObject_invalid_checksum_algorithm" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), ChecksumAlgorithm: types.ChecksumAlgorithm("invalid_checksum_algorithm"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidChecksumAlgorithm)); err != nil { return err } return nil }) } func CopyObject_create_checksum_on_copy(s *S3Conf) error { testName := "CopyObject_create_checksum_on_copy" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj := "source-object" dstObj := "destination-object" _, err := putObjectWithData(300, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), ChecksumAlgorithm: types.ChecksumAlgorithmSha256, }) cancel() if err != nil { return err } if getString(res.CopyObjectResult.ChecksumSHA256) == "" { return fmt.Errorf("expected non nil sha256 checksum") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &dstObj, ChecksumMode: types.ChecksumModeEnabled, }) cancel() if err != nil { return err } if getString(out.ChecksumSHA256) != getString(res.CopyObjectResult.ChecksumSHA256) { return fmt.Errorf("expected the sha256 checksum to be %v, instead got %v", getString(res.CopyObjectResult.ChecksumSHA256), getString(out.ChecksumSHA256)) } return nil }) } func CopyObject_should_copy_the_existing_checksum(s *S3Conf) error { testName := "CopyObject_should_copy_the_existing_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj := "source-object" dstObj := "destination-object" out, err := putObjectWithData(100, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, ChecksumAlgorithm: types.ChecksumAlgorithmCrc32c, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), }) cancel() if err != nil { return err } if res.CopyObjectResult.ChecksumCRC32C == nil { return fmt.Errorf("expected non empty crc32c checksum") } if getString(res.CopyObjectResult.ChecksumCRC32C) != getString(out.res.ChecksumCRC32C) { return fmt.Errorf("expected crc32c checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32C), getString(res.CopyObjectResult.ChecksumCRC32C)) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &dstObj, ChecksumMode: types.ChecksumModeEnabled, }) cancel() if err != nil { return err } if getString(resp.ChecksumCRC32C) != getString(res.CopyObjectResult.ChecksumCRC32C) { return fmt.Errorf("expected crc32c checksum to be %v, instead got %v", getString(res.CopyObjectResult.ChecksumCRC32C), getString(resp.ChecksumCRC32C)) } return nil }) } func CopyObject_should_replace_the_existing_checksum(s *S3Conf) error { testName := "CopyObject_should_replace_the_existing_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj := "source-object" dstObj := "destination-object" _, err := putObjectWithData(100, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, ChecksumAlgorithm: types.ChecksumAlgorithmCrc32, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), ChecksumAlgorithm: types.ChecksumAlgorithmSha1, // replace crc32 with sha1 }) cancel() if err != nil { return err } if res.CopyObjectResult.ChecksumSHA1 == nil { return fmt.Errorf("expected non empty sha1 checksum") } if res.CopyObjectResult.ChecksumCRC32 != nil { return fmt.Errorf("expected empty crc32 checksum, instead got %v", *res.CopyObjectResult.ChecksumCRC32) } return nil }) } func CopyObject_to_itself_by_replacing_the_checksum(s *S3Conf) error { testName := "CopyObject_to_itself_by_replacing_the_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjectWithData(400, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, ChecksumAlgorithm: types.ChecksumAlgorithmSha256, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), ChecksumAlgorithm: types.ChecksumAlgorithmCrc32, // replace sh256 with crc32 MetadataDirective: types.MetadataDirectiveReplace, }) cancel() if err != nil { return err } if out.CopyObjectResult.ChecksumCRC32 == nil { return fmt.Errorf("expected non empty crc32 checksum") } if out.CopyObjectResult.ChecksumCRC32C != nil { return fmt.Errorf("expected empty crc32c checksum") } if out.CopyObjectResult.ChecksumSHA1 != nil { return fmt.Errorf("expected empty sha1 checksum") } if out.CopyObjectResult.ChecksumSHA256 != nil { return fmt.Errorf("expected empty sha256 checksum") } if out.CopyObjectResult.ChecksumCRC64NVME != nil { return fmt.Errorf("expected empty crc64nvme checksum") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, ChecksumMode: types.ChecksumModeEnabled, }) cancel() if err != nil { return err } if res.ChecksumCRC32 == nil { return fmt.Errorf("expected non empty crc32 checksum") } if res.ChecksumCRC32C != nil { return fmt.Errorf("expected empty crc32c checksum") } if res.ChecksumSHA1 != nil { return fmt.Errorf("expected empty sha1 checksum") } if res.ChecksumSHA256 != nil { return fmt.Errorf("expected empty sha256 checksum") } if res.ChecksumCRC64NVME != nil { return fmt.Errorf("expected empty crc64nvme checksum") } return nil }) } func CopyObject_success(s *S3Conf) error { testName := "CopyObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dataLength, obj := int64(1234567), "my obj with spaces" dstBucket := getBucketName() err := setup(s, dstBucket) if err != nil { return err } r, err := putObjectWithData(dataLength, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &dstBucket, Key: &obj, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)), }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &dstBucket, Key: &obj, }) defer cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected content-length to be set, instead got nil") } if *out.ContentLength != dataLength { return fmt.Errorf("expected content-length %v, instead got %v", dataLength, *out.ContentLength) } bdy, err := io.ReadAll(out.Body) if err != nil { return err } defer out.Body.Close() outCsum := sha256.Sum256(bdy) if outCsum != r.csum { return fmt.Errorf("invalid object data") } err = teardown(s, dstBucket) if err != nil { return nil } return nil }) } func PutObjectTagging_non_existing_object(s *S3Conf) error { testName := "PutObjectTagging_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: getPtr("my-obj"), Tagging: &types.Tagging{TagSet: []types.Tag{}}}) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }) } func PutObjectTagging_long_tags(s *S3Conf) error { testName := "PutObjectTagging_long_tags" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" tagging := types.Tagging{TagSet: []types.Tag{ {Key: getPtr(genRandString(129)), Value: getPtr("val")}, }} _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging}) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTagKey)); err != nil { return err } tagging = types.Tagging{TagSet: []types.Tag{ {Key: getPtr("key"), Value: getPtr(genRandString(257))}, }} ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging}) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTagValue)); err != nil { return err } return nil }) } func PutObjectTagging_duplicate_keys(s *S3Conf) error { testName := "PutObjectTagging_duplicate_keys" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } tagging := types.Tagging{ TagSet: []types.Tag{ {Key: getPtr("key-1"), Value: getPtr("value-1")}, {Key: getPtr("key-2"), Value: getPtr("value-2")}, {Key: getPtr("same-key"), Value: getPtr("value-3")}, {Key: getPtr("same-key"), Value: getPtr("value-4")}, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDuplicateTagKey)); err != nil { return err } return nil }) } func PutObjectTagging_tag_count_limit(s *S3Conf) error { testName := "PutObjectTagging_tag_count_limit" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } tagSet := []types.Tag{} for i := range 11 { tagSet = append(tagSet, types.Tag{ Key: getPtr(fmt.Sprintf("key-%v", i)), Value: getPtr(genRandString(15)), }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &types.Tagging{ TagSet: tagSet, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectTaggingLimited)) }) } func PutObjectTagging_success(s *S3Conf) error { testName := "PutObjectTagging_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" tagging := types.Tagging{TagSet: []types.Tag{ {Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}, }} _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging}) cancel() if err != nil { return err } return nil }) } func GetObjectTagging_non_existing_object(s *S3Conf) error { testName := "GetObjectTagging_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }) } func GetObjectTagging_unset_tags(s *S3Conf) error { testName := "GetObjectTagging_unset_tags" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &obj, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)); err != nil { return err } return nil }) } func GetObjectTagging_invalid_parent(s *S3Conf) error { testName := "GetObjectTagging_invalid_parent" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "not-a-dir" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } obj = "not-a-dir/bad-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &obj, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }) } func GetObjectTagging_success(s *S3Conf) error { testName := "PutObjectTagging_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" tagging := types.Tagging{TagSet: []types.Tag{ {Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}, }} _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging}) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return nil } if !areTagsSame(out.TagSet, tagging.TagSet) { return fmt.Errorf("expected %v instead got %v", tagging.TagSet, out.TagSet) } return nil }) } func DeleteObjectTagging_non_existing_object(s *S3Conf) error { testName := "DeleteObjectTagging_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }) } func DeleteObjectTagging_success_status(s *S3Conf) error { testName := "DeleteObjectTagging_success_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } tagging := types.Tagging{ TagSet: []types.Tag{ { Key: getPtr("Hello"), Value: getPtr("World"), }, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging, }) cancel() if err != nil { return err } req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v/%v?tagging", bucket, obj), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusNoContent { return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode) } return nil }) } func DeleteObjectTagging_success(s *S3Conf) error { testName := "DeleteObjectTagging_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" tagging := types.Tagging{TagSet: []types.Tag{ {Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}, }} _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging}) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return nil } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return nil } if len(out.TagSet) > 0 { return fmt.Errorf("expected empty tag set, instead got %v", out.TagSet) } return nil }) } func CreateMultipartUpload_non_existing_bucket(s *S3Conf) error { testName := "CreateMultipartUpload_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { bucketName := getBucketName() _, err := createMp(s3client, bucketName, "my-obj") if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func CreateMultipartUpload_with_metadata(s *S3Conf) error { testName := "CreateMultipartUpload_with_metadata" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" meta := map[string]string{ "prop1": "val1", "prop2": "val2", } cType, cEnc, cDesp, cLang := "application/text", "testenc", "testdesp", "sp" cacheControl, expires := "no-cache", time.Now().Add(time.Hour*5) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, Metadata: meta, ContentType: &cType, ContentEncoding: &cEnc, ContentDisposition: &cDesp, ContentLanguage: &cLang, CacheControl: &cacheControl, Expires: &expires, }) cancel() if err != nil { return err } parts, _, err := uploadParts(s3client, 100, 1, bucket, obj, *out.UploadId) if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if !areMapsSame(resp.Metadata, meta) { return fmt.Errorf("expected uploaded object metadata to be %v, instead got %v", meta, resp.Metadata) } if getString(resp.ContentType) != cType { return fmt.Errorf("expected uploaded object content-type to be %v, instead got %v", cType, getString(resp.ContentType)) } if getString(resp.ContentEncoding) != cEnc { return fmt.Errorf("expected uploaded object content-encoding to be %v, instead got %v", cEnc, getString(resp.ContentEncoding)) } if getString(resp.ContentLanguage) != cLang { return fmt.Errorf("expected uploaded object content-language to be %v, instead got %v", cLang, getString(resp.ContentLanguage)) } if getString(resp.ContentDisposition) != cDesp { return fmt.Errorf("expected uploaded object content-disposition to be %v, instead got %v", cDesp, getString(resp.ContentDisposition)) } if getString(resp.CacheControl) != cacheControl { return fmt.Errorf("expected uploaded object cache-control to be %v, instead got %v", cacheControl, getString(resp.CacheControl)) } if getString(resp.ExpiresString) != expires.UTC().Format(timefmt) { return fmt.Errorf("expected uploaded object content-encoding to be %v, instead got %v", expires.UTC().Format(timefmt), getString(resp.ExpiresString)) } return nil }) } func CreateMultipartUpload_with_object_lock(s *S3Conf) error { testName := "CreateMultipartUpload_with_object_lock" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" retainUntilDate := time.Now().Add(24 * time.Hour) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, ObjectLockMode: types.ObjectLockModeGovernance, ObjectLockRetainUntilDate: &retainUntilDate, }) cancel() if err != nil { return err } parts, _, err := uploadParts(s3client, 100, 1, bucket, obj, *out.UploadId) if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if resp.ObjectLockLegalHoldStatus != types.ObjectLockLegalHoldStatusOn { return fmt.Errorf("expected uploaded object legal hold status to be %v, instead got %v", types.ObjectLockLegalHoldStatusOn, resp.ObjectLockLegalHoldStatus) } if resp.ObjectLockMode != types.ObjectLockModeGovernance { return fmt.Errorf("expected uploaded object lock mode to be %v, instead got %v", types.ObjectLockModeGovernance, resp.ObjectLockMode) } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func CreateMultipartUpload_with_object_lock_not_enabled(s *S3Conf) error { testName := "CreateMultipartUpload_with_object_lock_not_enabled" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { return err } return nil }) } func CreateMultipartUpload_with_object_lock_invalid_retention(s *S3Conf) error { testName := "CreateMultipartUpload_with_object_lock_invalid_retention" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" retentionDate := time.Now().Add(24 * time.Hour) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, ObjectLockMode: types.ObjectLockModeGovernance, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, ObjectLockRetainUntilDate: &retentionDate, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil { return err } return nil }) } func CreateMultipartUpload_past_retain_until_date(s *S3Conf) error { testName := "CreateMultipartUpload_past_retain_until_date" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" rDate := time.Now().Add(-5 * time.Hour) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, ObjectLockMode: types.ObjectLockModeGovernance, ObjectLockRetainUntilDate: &rDate, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate)); err != nil { return err } return nil }) } func CreateMultipartUpload_invalid_legal_hold(s *S3Conf) error { testName := "CreateMultipartUpload_invalid_legal_hold" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: getPtr("foo"), ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatus("invalid_status"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidLegalHoldStatus)) }, withLock()) } func CreateMultipartUpload_invalid_object_lock_mode(s *S3Conf) error { testName := "CreateMultipartUpload_invalid_object_lock_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { rDate := time.Now().Add(time.Hour * 10) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: getPtr("foo"), ObjectLockMode: types.ObjectLockMode("invalid_mode"), ObjectLockRetainUntilDate: &rDate, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidObjectLockMode)) }, withLock()) } func CreateMultipartUpload_invalid_checksum_algorithm(s *S3Conf) error { testName := "CreateMultipartUpload_invalid_checksum_algorithm" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: getPtr("my-obj"), ChecksumAlgorithm: types.ChecksumAlgorithm("invalid_checksum_algorithm"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidChecksumAlgorithm)); err != nil { return err } return nil }) } func CreateMultipartUpload_invalid_checksum_type(s *S3Conf) error { testName := "CreateMultipartUpload_invalid_checksum_type" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := createMp(s3client, bucket, "my-mp", withChecksumType(types.ChecksumType("invalid_checksum_type"))) if err := checkApiErr(err, s3err.GetInvalidChecksumHeaderErr("x-amz-checksum-type")); err != nil { return err } return nil }) } func CreateMultipartUpload_empty_checksum_algorithm_with_checksum_type(s *S3Conf) error { testName := "CreateMultipartUpload_empty_checksum_algorithm_with_checksum_type" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, el := range types.ChecksumTypeComposite.Values() { _, err := createMp(s3client, bucket, "my-mp", withChecksumType(el)) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrChecksumTypeWithAlgo)); err != nil { return err } } return nil }) } func CreateMultipartUpload_valid_algo_type(s *S3Conf) error { testName := "CreateMultipartUpload_valid_algo_type" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for _, test := range []struct { chType types.ChecksumType chAlgo types.ChecksumAlgorithm }{ // composite type {types.ChecksumTypeComposite, types.ChecksumAlgorithmCrc32}, {types.ChecksumTypeComposite, types.ChecksumAlgorithmCrc32c}, {types.ChecksumTypeComposite, types.ChecksumAlgorithmSha1}, {types.ChecksumTypeComposite, types.ChecksumAlgorithmSha256}, // full object type {types.ChecksumTypeFullObject, types.ChecksumAlgorithmCrc64nvme}, {types.ChecksumTypeFullObject, types.ChecksumAlgorithmCrc32}, {types.ChecksumTypeFullObject, types.ChecksumAlgorithmCrc32c}, } { randChType := types.ChecksumType(randomizeCase(string(test.chType))) randChAlgo := types.ChecksumAlgorithm(randomizeCase(string(test.chAlgo))) out, err := createMp(s3client, bucket, obj, withChecksum(randChAlgo), withChecksumType(randChType)) if err != nil { return err } if out.ChecksumAlgorithm != test.chAlgo { return fmt.Errorf("expected the checksum algorithm to be %v, instead got %v", test.chAlgo, out.ChecksumAlgorithm) } if out.ChecksumType != test.chType { return fmt.Errorf("expected the checksum type to be %v, instead got %v", test.chType, out.ChecksumType) } } return nil }) } func CreateMultipartUpload_with_tagging(s *S3Conf) error { testName := "CreateMultipartUpload_with_tagging" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" testTagging := func(tagging string, result map[string]string, expectedErr error) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) mp, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, Tagging: &tagging, }) cancel() if err == nil && expectedErr != nil { return fmt.Errorf("expected err %w, instead got nil", expectedErr) } if err != nil { if expectedErr == nil { return err } switch eErr := expectedErr.(type) { case s3err.APIError: return checkApiErr(err, eErr) default: return fmt.Errorf("invalid err provided: %w", expectedErr) } } parts, _, err := uploadParts(s3client, 5*1024*1024, 1, bucket, obj, *mp.UploadId) if err != nil { return err } cParts := []types.CompletedPart{ { ETag: parts[0].ETag, PartNumber: parts[0].PartNumber, ChecksumCRC32: parts[0].ChecksumCRC32, }, } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if len(res.TagSet) != len(result) { return fmt.Errorf("tag lengths are not equal: (expected): %v, (got): %v", len(result), len(res.TagSet)) } for _, tag := range res.TagSet { val, ok := result[getString(tag.Key)] if !ok { return fmt.Errorf("tag key not found: %v", getString(tag.Key)) } if val != getString(tag.Value) { return fmt.Errorf("expected the %v tag value to be %v, instead got %v", getString(tag.Key), val, getString(tag.Value)) } } return nil } for _, el := range []struct { tagging string result map[string]string expectedErr error }{ // success cases {"&", map[string]string{}, nil}, {"&&&", map[string]string{}, nil}, {"key", map[string]string{"key": ""}, nil}, {"key&", map[string]string{"key": ""}, nil}, {"key=&", map[string]string{"key": ""}, nil}, {"key=val&", map[string]string{"key": "val"}, nil}, {"key1&key2", map[string]string{"key1": "", "key2": ""}, nil}, {"key1=val1&key2=val2", map[string]string{"key1": "val1", "key2": "val2"}, nil}, // invalid url-encoded {"=", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, {"key%", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, // duplicate keys {"key=val&key=val", nil, s3err.GetAPIError(s3err.ErrInvalidURLEncodedTagging)}, // invalid tag keys {"key?=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key(=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key*=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key$=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key#=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key@=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, {"key!=val", nil, s3err.GetAPIError(s3err.ErrInvalidTagKey)}, // invalid tag values {"key=val?", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val(", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val*", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val$", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val#", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val@", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, {"key=val!", nil, s3err.GetAPIError(s3err.ErrInvalidTagValue)}, // success special chars {"key-key_key.key/key=value-value_value.value/value", map[string]string{"key-key_key.key/key": "value-value_value.value/value"}, nil}, // should handle supported encoded characters {"key%2E=value%2F", map[string]string{"key.": "value/"}, nil}, {"key%2D=value%2B", map[string]string{"key-": "value+"}, nil}, {"key++key=value++value", map[string]string{"key key": "value value"}, nil}, {"key%20key=value%20value", map[string]string{"key key": "value value"}, nil}, {"key%5Fkey=value%5Fvalue", map[string]string{"key_key": "value_value"}, nil}, } { err := testTagging(el.tagging, el.result, el.expectedErr) if err != nil { return err } } return nil }) } func CreateMultipartUpload_success(s *S3Conf) error { testName := "CreateMultipartUpload_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } if out.Bucket == nil { return fmt.Errorf("expected bucket name to be not nil") } if out.Key == nil { return fmt.Errorf("expected object name to be not nil") } if *out.Bucket != bucket { return fmt.Errorf("expected bucket name %v, instead got %v", bucket, *out.Bucket) } if *out.Key != obj { return fmt.Errorf("expected object name %v, instead got %v", obj, *out.Key) } return nil }) } func UploadPart_non_existing_bucket(s *S3Conf) error { testName := "UploadPart_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { bucketName := getBucketName() partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucketName, Key: getPtr("my-obj"), UploadId: getPtr("uploadId"), PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func UploadPart_invalid_part_number(s *S3Conf) error { testName := "UploadPart_invalid_part_number" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := "my-obj" mp, err := createMp(s3client, bucket, key) if err != nil { return err } for _, el := range []int32{0, -1, 10001, 2300000} { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &key, UploadId: mp.UploadId, PartNumber: &el, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPartNumber)); err != nil { return err } } return nil }) } func UploadPart_non_existing_mp_upload(s *S3Conf) error { testName := "UploadPart_non_existing_mp_upload" partNumber := int32(1) return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: getPtr("my-obj"), UploadId: getPtr("uploadId"), PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil { return err } return nil }) } func UploadPart_checksum_algorithm_and_header_mismatch(s *S3Conf) error { testName := "UploadPart_checksum_algorithm_and_header_mismatch" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, ChecksumAlgorithm: types.ChecksumAlgorithmCrc32, ChecksumCRC32C: getPtr("m0cB1Q=="), PartNumber: &partNumber, UploadId: mp.UploadId, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { return err } return nil }) } func UploadPart_multiple_checksum_headers(s *S3Conf) error { testName := "UploadPart_multiple_checksum_headers" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32c)) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, ChecksumSHA1: getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0="), ChecksumCRC32C: getPtr("m0cB1Q=="), UploadId: mp.UploadId, PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { return err } // multiple empty checksums ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, ChecksumSHA1: getPtr(""), ChecksumCRC32C: getPtr(""), UploadId: mp.UploadId, PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { return err } return nil }) } func UploadPart_invalid_checksum_header(s *S3Conf) error { testName := "UploadPart_invalid_checksum_header" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } partNumber := int32(1) for _, el := range []struct { algo string crc32 *string crc32c *string sha1 *string sha256 *string crc64nvme *string }{ // CRC32 tests { algo: "crc32", crc32: getPtr(""), }, { algo: "crc32", crc32: getPtr("invalid_base64!"), // invalid base64 }, { algo: "crc32", crc32: getPtr("YXNrZGpoZ2tqYXNo"), // valid base64 but not crc32 }, // CRC32C tests { algo: "crc32c", crc32c: getPtr(""), }, { algo: "crc32c", crc32c: getPtr("invalid_base64!"), // invalid base64 }, { algo: "crc32c", crc32c: getPtr("c2RhZnNhZGZzZGFm"), // valid base64 but not crc32c }, // SHA1 tests { algo: "sha1", sha1: getPtr(""), }, { algo: "sha1", sha1: getPtr("invalid_base64!"), // invalid base64 }, { algo: "sha1", sha1: getPtr("c2RhZmRhc2Zkc2Fmc2RhZnNhZGZzYWRm"), // valid base64 but not sha1 }, // SHA256 tests { algo: "sha256", sha256: getPtr(""), }, { algo: "sha256", sha256: getPtr("invalid_base64!"), // invalid base64 }, { algo: "sha256", sha256: getPtr("ZGZnbmRmZ2hoZmRoZmdkaA=="), // valid base64 but not sha56 }, // CRC64NVME tests { algo: "crc64nvme", crc64nvme: getPtr(""), }, { algo: "crc64nvme", crc64nvme: getPtr("invalid_base64!"), // invalid base64 }, { algo: "crc64nvme", crc64nvme: getPtr("ZHNhZmRzYWZzZGFmZHNhZg=="), // valid base64 but not crc64nvme }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, ChecksumCRC32: el.crc32, ChecksumCRC32C: el.crc32c, ChecksumSHA1: el.sha1, ChecksumSHA256: el.sha256, ChecksumCRC64NVME: el.crc64nvme, PartNumber: &partNumber, UploadId: mp.UploadId, }) cancel() if err := checkApiErr(err, s3err.GetInvalidChecksumHeaderErr(fmt.Sprintf("x-amz-checksum-%v", el.algo))); err != nil { return err } } return nil }) } func UploadPart_checksum_algorithm_mistmatch_on_initialization(s *S3Conf) error { testName := "UploadPart_checksum_algorithm_mistmatch_on_initialization" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, PartNumber: &partNumber, ChecksumAlgorithm: types.ChecksumAlgorithmSha1, }) cancel() if err := checkApiErr(err, s3err.GetChecksumTypeMismatchErr(types.ChecksumAlgorithmCrc32, types.ChecksumAlgorithmSha1)); err != nil { return err } return nil }) } func UploadPart_checksum_algorithm_mistmatch_on_initialization_with_value(s *S3Conf) error { testName := "UploadPart_checksum_algorithm_mistmatch_on_initialization_with_value" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, PartNumber: &partNumber, ChecksumSHA256: getPtr("uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="), }) cancel() if err := checkApiErr(err, s3err.GetChecksumTypeMismatchErr(types.ChecksumAlgorithmCrc32, types.ChecksumAlgorithmSha256)); err != nil { return err } return nil }) } func UploadPart_incorrect_checksums(s *S3Conf) error { testName := "UploadPart_incorrect_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for _, el := range []struct { algo types.ChecksumAlgorithm crc32 *string crc32c *string sha1 *string sha256 *string crc64nvme *string }{ { algo: types.ChecksumAlgorithmCrc32, crc32: getPtr("DUoRhQ=="), }, { algo: types.ChecksumAlgorithmCrc32c, crc32c: getPtr("yZRlqg=="), }, { algo: types.ChecksumAlgorithmSha1, sha1: getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0="), }, { algo: types.ChecksumAlgorithmSha256, sha256: getPtr("uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="), }, { algo: types.ChecksumAlgorithmCrc64nvme, crc64nvme: getPtr("MN2ofvMjpIQ="), }, } { mp, err := createMp(s3client, bucket, obj, withChecksum(el.algo)) if err != nil { return err } body := strings.NewReader("random string body") partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, ChecksumCRC32: el.crc32, ChecksumCRC32C: el.crc32c, ChecksumSHA1: el.sha1, ChecksumSHA256: el.sha256, ChecksumCRC64NVME: el.crc64nvme, UploadId: mp.UploadId, PartNumber: &partNumber, Body: body, }) cancel() if err := checkApiErr(err, s3err.GetChecksumBadDigestErr(el.algo)); err != nil { return err } } return nil }) } func UploadPart_no_checksum_with_full_object_checksum_type(s *S3Conf) error { testName := "UploadPart_no_checksum_with_full_object_checksum_type" return actionHandler(s, testName, func(_ *s3.Client, bucket string) error { customClient := s3.NewFromConfig(s.Config(), func(o *s3.Options) { o.RequestChecksumCalculation = aws.RequestChecksumCalculationUnset }) obj := "my-obj" for _, algo := range []types.ChecksumAlgorithm{ types.ChecksumAlgorithmCrc32, types.ChecksumAlgorithmCrc32c, types.ChecksumAlgorithmCrc64nvme, } { mp, err := createMp(customClient, bucket, obj, withChecksum(algo), withChecksumType(types.ChecksumTypeFullObject)) if err != nil { return err } var hashRdr hash.Hash switch algo { case types.ChecksumAlgorithmCrc32: hashRdr = crc32.NewIEEE() case types.ChecksumAlgorithmCrc32c: hashRdr = crc32.New(crc32.MakeTable(crc32.Castagnoli)) case types.ChecksumAlgorithmCrc64nvme: hashRdr = crc64.New(crc64.MakeTable(bits.Reverse64(0xad93d23594c93659))) default: return fmt.Errorf("invalid checksum algorithm provided: %s", algo) } partBuffer := make([]byte, 5*1024*1024) rand.Read(partBuffer) hashRdr.Write(partBuffer) partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := customClient.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, Body: bytes.NewReader(partBuffer), PartNumber: &partNumber, }) cancel() if err != nil { return err } csum := base64.StdEncoding.EncodeToString(hashRdr.Sum(nil)) switch algo { case types.ChecksumAlgorithmCrc32: if getString(res.ChecksumCRC32) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", algo, csum, getString(res.ChecksumCRC32)) } case types.ChecksumAlgorithmCrc32c: if getString(res.ChecksumCRC32C) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", algo, csum, getString(res.ChecksumCRC32C)) } case types.ChecksumAlgorithmCrc64nvme: if getString(res.ChecksumCRC64NVME) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", algo, csum, getString(res.ChecksumCRC64NVME)) } } } return nil }) } func UploadPart_no_checksum_with_composite_checksum_type(s *S3Conf) error { testName := "UploadPart_no_checksum_with_composite_checksum_type" return actionHandler(s, testName, func(_ *s3.Client, bucket string) error { customClient := s3.NewFromConfig(s.Config(), func(o *s3.Options) { o.RequestChecksumCalculation = aws.RequestChecksumCalculationUnset }) obj := "my-obj" for _, algo := range []types.ChecksumAlgorithm{ types.ChecksumAlgorithmCrc32, types.ChecksumAlgorithmCrc32c, types.ChecksumAlgorithmSha1, types.ChecksumAlgorithmSha256, } { mp, err := createMp(customClient, bucket, obj, withChecksum(algo), withChecksumType(types.ChecksumTypeComposite)) if err != nil { return err } _, _, err = uploadParts(customClient, 10, 1, bucket, obj, *mp.UploadId) if err := checkApiErr(err, s3err.GetChecksumTypeMismatchErr(algo, "null")); err != nil { return err } } return nil }) } func UploadPart_should_calculate_checksum_if_only_algorithm_is_provided(s *S3Conf) error { testName := "UploadPart_should_calculate_checksum_if_only_algorithm_is_provided" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { customClient := s3.NewFromConfig(s.Config(), func(o *s3.Options) { o.RequestChecksumCalculation = aws.RequestChecksumCalculationUnset }) obj := "my-obj" for _, test := range []struct { chType types.ChecksumType chAlgo types.ChecksumAlgorithm }{ {types.ChecksumTypeFullObject, types.ChecksumAlgorithmCrc32}, {types.ChecksumTypeFullObject, types.ChecksumAlgorithmCrc32c}, {types.ChecksumTypeFullObject, types.ChecksumAlgorithmCrc64nvme}, {types.ChecksumTypeComposite, types.ChecksumAlgorithmCrc32}, {types.ChecksumTypeComposite, types.ChecksumAlgorithmCrc32c}, {types.ChecksumTypeComposite, types.ChecksumAlgorithmSha1}, {types.ChecksumTypeComposite, types.ChecksumAlgorithmSha256}, } { mp, err := createMp(customClient, bucket, obj, withChecksum(test.chAlgo), withChecksumType(test.chType)) if err != nil { return err } parts, csum, err := uploadParts(customClient, 5*1024*1024, 1, bucket, obj, *mp.UploadId, withChecksum(test.chAlgo)) if err != nil { return err } if len(parts) != 1 { return fmt.Errorf("expected 1 uploaded part, instaed got %d", len(parts)) } part := parts[0] switch test.chAlgo { case types.ChecksumAlgorithmCrc32: if getString(part.ChecksumCRC32) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", test.chAlgo, csum, getString(part.ChecksumCRC32)) } case types.ChecksumAlgorithmCrc32c: if getString(part.ChecksumCRC32C) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", test.chAlgo, csum, getString(part.ChecksumCRC32C)) } case types.ChecksumAlgorithmCrc64nvme: if getString(part.ChecksumCRC64NVME) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", test.chAlgo, csum, getString(part.ChecksumCRC64NVME)) } case types.ChecksumAlgorithmSha1: if getString(part.ChecksumSHA1) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", test.chAlgo, csum, getString(part.ChecksumSHA1)) } case types.ChecksumAlgorithmSha256: if getString(part.ChecksumSHA256) != csum { return fmt.Errorf("expected the uploaded part checksum %s to be %s, instead got %s", test.chAlgo, csum, getString(part.ChecksumSHA256)) } } } return nil }) } func UploadPart_with_checksums_success(s *S3Conf) error { testName := "UploadPart_with_checksums_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for i, algo := range types.ChecksumAlgorithmCrc32.Values() { mp, err := createMp(s3client, bucket, obj, withChecksum(algo)) if err != nil { return err } partNumber := int32(1) data := make([]byte, i*100) rand.Read(data) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, ChecksumAlgorithm: algo, UploadId: mp.UploadId, PartNumber: &partNumber, Body: bytes.NewReader(data), }) cancel() if err != nil { return err } switch algo { case types.ChecksumAlgorithmCrc32: if res.ChecksumCRC32 == nil { return fmt.Errorf("expected non empty crc32 checksum in the response") } case types.ChecksumAlgorithmCrc32c: if res.ChecksumCRC32C == nil { return fmt.Errorf("expected non empty crc32c checksum in the response") } case types.ChecksumAlgorithmSha1: if res.ChecksumSHA1 == nil { return fmt.Errorf("expected non empty sha1 checksum in the response") } case types.ChecksumAlgorithmSha256: if res.ChecksumSHA256 == nil { return fmt.Errorf("expected non empty sha256 checksum in the response") } case types.ChecksumAlgorithmCrc64nvme: if res.ChecksumCRC64NVME == nil { return fmt.Errorf("expected non empty crc64nvme checksum in the response") } } } return nil }) } func UploadPart_non_existing_key(s *S3Conf) error { testName := "UploadPart_non_existing_key" partNumber := int32(1) return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: getPtr("non-existing-object-key"), UploadId: out.UploadId, PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil { return err } return nil }) } func UploadPart_success(s *S3Conf) error { testName := "UploadPart_success" partNumber := int32(1) return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, PartNumber: &partNumber, }) cancel() if err != nil { return err } if getString(res.ETag) == "" { return fmt.Errorf("expected a valid etag, instead got empty") } return nil }) } func UploadPartCopy_non_existing_bucket(s *S3Conf) error { testName := "UploadPartCopy_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { bucketName := getBucketName() partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucketName, CopySource: getPtr("Copy-Source"), UploadId: getPtr("uploadId"), Key: getPtr("my-obj"), PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func UploadPartCopy_incorrect_uploadId(s *S3Conf) error { testName := "UploadPartCopy_incorrect_uploadId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj" err := setup(s, srcBucket) if err != nil { return err } _, err = putObjects(s3client, []string{srcObj}, srcBucket) if err != nil { return err } _, err = createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/" + srcObj), UploadId: getPtr("incorrect-upload-id"), Key: &obj, PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil { return err } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_incorrect_object_key(s *S3Conf) error { testName := "UploadPartCopy_incorrect_object_key" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj" err := setup(s, srcBucket) if err != nil { return err } _, err = putObjects(s3client, []string{srcObj}, srcBucket) if err != nil { return err } out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/" + srcObj), UploadId: out.UploadId, Key: getPtr("non-existing-object-key"), PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil { return err } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_invalid_part_number(s *S3Conf) error { testName := "UploadPartCopy_invalid_part_number" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(-10) _, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr("bucket/key"), UploadId: getPtr("uploadId"), Key: getPtr("non-existing-object-key"), PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPartNumber)); err != nil { return err } return nil }) } func UploadPartCopy_invalid_copy_source(s *S3Conf) error { testName := "UploadPartCopy_invalid_copy_source" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { partNumber := int32(1) for _, test := range []struct { copySource string expectedErr s3err.APIError }{ // invalid encoding { // Invalid hex digits copySource: "bucket/%ZZ", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Ends with incomplete escape copySource: "100%/foo/bar/baz", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Only one digit after % copySource: "bucket/%A/bar", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // 'G' is not a hex digit copySource: "bucket/%G1/", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Just a single percent sign copySource: "%", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Only one hex digit copySource: "bucket/%1", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, { // Incomplete multibyte UTF-8 copySource: "bucket/%C3%", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding), }, // invalid bucket name { // ip v4 address copySource: "192.168.1.1/foo", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket), }, { // ip v6 address copySource: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/something", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket), }, { // some special chars copySource: "my-buc@k&()t/obj", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket), }, // invalid object key { // object is missing copySource: "bucket", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { // object is missing copySource: "bucket/", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, // directory navigation object keys { copySource: "bucket/.", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { copySource: "bucket/..", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { copySource: "bucket/../", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, { copySource: "bucket/foo/ba/../../../r/baz", expectedErr: s3err.GetAPIError(s3err.ErrInvalidCopySourceObject), }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: getPtr("obj"), UploadId: getPtr("mock-upload-id"), CopySource: &test.copySource, PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, test.expectedErr); err != nil { return err } } return nil }) } func UploadPartCopy_non_existing_source_bucket(s *S3Conf) error { testName := "UploadPartCopy_non_existing_source_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr("src/bucket/src/obj"), UploadId: out.UploadId, Key: &obj, PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func UploadPartCopy_non_existing_source_object_key(s *S3Conf) error { testName := "UploadPartCopy_non_existing_source_object_key" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket := "my-obj", getBucketName() err := setup(s, srcBucket) if err != nil { return nil } out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/non/existing/obj/key"), UploadId: out.UploadId, Key: &obj, PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_success(s *S3Conf) error { testName := "UploadPartCopy_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj" err := setup(s, srcBucket) if err != nil { return err } objSize := 5 * 1024 * 1024 _, err = putObjectWithData(int64(objSize), &s3.PutObjectInput{ Bucket: &srcBucket, Key: &srcObj, }, s3client) if err != nil { return err } out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) copyOut, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/" + srcObj), UploadId: out.UploadId, Key: &obj, PartNumber: &partNumber, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, }) cancel() if err != nil { return err } if len(res.Parts) != 1 { return fmt.Errorf("expected parts to be 1, instead got %v", len(res.Parts)) } if res.Parts[0].PartNumber == nil || *res.Parts[0].PartNumber != 1 { return fmt.Errorf("expected part-number to be 1, instead got %v", res.Parts[0].PartNumber) } if res.Parts[0].Size == nil || *res.Parts[0].Size != int64(objSize) { return fmt.Errorf("expected part size to be %v, instead got %v", objSize, res.Parts[0].Size) } if getString(res.Parts[0].ETag) != getString(copyOut.CopyPartResult.ETag) { return fmt.Errorf("expected part etag to be %v, instead got %v", getString(copyOut.CopyPartResult.ETag), getString(res.Parts[0].ETag)) } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_by_range_invalid_ranges(s *S3Conf) error { testName := "UploadPartCopy_by_range_invalid_ranges" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj" err := setup(s, srcBucket) if err != nil { return err } objSize := int64(5 * 1024 * 1024) _, err = putObjectWithData(objSize, &s3.PutObjectInput{ Bucket: &srcBucket, Key: &srcObj, }, s3client) if err != nil { return err } out, err := createMp(s3client, bucket, obj) if err != nil { return err } uploadPartCopy := func(csRange string, ptNumber int32) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/" + srcObj), UploadId: out.UploadId, Key: &obj, PartNumber: &ptNumber, CopySourceRange: &csRange, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidCopySourceRange)); err != nil { return err } return nil } for i, rg := range []string{ "byte=100-200", "bytes=invalid-range", "bytes=200-100", "bytes=-2-300", "bytes=aa-12", "bytes=12-aa", "bytes=bb-", } { err := uploadPartCopy(rg, int32(i+1)) if err != nil { return err } } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_exceeding_copy_source_range(s *S3Conf) error { testName := "UploadPartCopy_exceeding_copy_source_range" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj" err := setup(s, srcBucket) if err != nil { return err } objSize := int64(1000) _, err = putObjectWithData(objSize, &s3.PutObjectInput{ Bucket: &srcBucket, Key: &srcObj, }, s3client) if err != nil { return err } out, err := createMp(s3client, bucket, obj) if err != nil { return err } uploadPartCopy := func(csRange string, ptNumber int32) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/" + srcObj), UploadId: out.UploadId, Key: &obj, PartNumber: &ptNumber, CopySourceRange: &csRange, }) cancel() return checkApiErr(err, s3err.CreateExceedingRangeErr(objSize)) } for i, rg := range []string{ "bytes=100-1005", "bytes=1250-3000", "bytes=100-1000", } { err := uploadPartCopy(rg, int32(i+1)) if err != nil { return err } } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_greater_range_than_obj_size(s *S3Conf) error { testName := "UploadPartCopy_greater_range_than_obj_size" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj" err := setup(s, srcBucket) if err != nil { return err } srcObjSize := 5 * 1024 * 1024 _, err = putObjectWithData(int64(srcObjSize), &s3.PutObjectInput{ Bucket: &srcBucket, Key: &srcObj, }, s3client) if err != nil { return err } out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/" + srcObj), UploadId: out.UploadId, Key: &obj, CopySourceRange: getPtr(fmt.Sprintf("bytes=0-%v", srcObjSize+50)), // The specified range is greater than the actual object size PartNumber: &partNumber, }) cancel() if err := checkApiErr(err, s3err.CreateExceedingRangeErr(int64(srcObjSize))); err != nil { return err } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_by_range_success(s *S3Conf) error { testName := "UploadPartCopy_by_range_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj" err := setup(s, srcBucket) if err != nil { return err } objSize := 5 * 1024 * 1024 _, err = putObjectWithData(int64(objSize), &s3.PutObjectInput{ Bucket: &srcBucket, Key: &srcObj, }, s3client) if err != nil { return err } out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) copyOut, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, CopySource: getPtr(srcBucket + "/" + srcObj), CopySourceRange: getPtr("bytes=100-200"), UploadId: out.UploadId, Key: &obj, PartNumber: &partNumber, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, }) cancel() if err != nil { return err } if len(res.Parts) != 1 { return fmt.Errorf("expected parts to be 1, instead got %v", len(res.Parts)) } if res.Parts[0].PartNumber == nil { return fmt.Errorf("expected part-number to be 1, instead got nil") } if *res.Parts[0].PartNumber != 1 { return fmt.Errorf("expected part-number to be 1, instead got %v", res.Parts[0].PartNumber) } if res.Parts[0].Size == nil { return fmt.Errorf("expected part size to be non nil, instead got nil") } if *res.Parts[0].Size != 101 { return fmt.Errorf("expected part size to be %v, instead got %v", 101, res.Parts[0].Size) } if getString(res.Parts[0].ETag) != getString(copyOut.CopyPartResult.ETag) { return fmt.Errorf("expected part etag to be %v, instead got %v", getString(copyOut.CopyPartResult.ETag), getString(res.Parts[0].ETag)) } err = teardown(s, srcBucket) if err != nil { return err } return nil }) } func UploadPartCopy_should_copy_the_checksum(s *S3Conf) error { testName := "UploadPartCopy_should_copy_the_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" srcObj := "source-object" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } out, err := putObjectWithData(300, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, ChecksumAlgorithm: types.ChecksumAlgorithmCrc32, }, s3client) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, PartNumber: &partNumber, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), }) cancel() if err != nil { return err } if getString(res.CopyPartResult.ChecksumCRC32) != getString(out.res.ChecksumCRC32) { return fmt.Errorf("expected crc32 checksum to be %v, instead got %v", getString(out.res.ChecksumCRC32), getString(res.CopyPartResult.ChecksumCRC32)) } if res.CopyPartResult.ChecksumCRC32C != nil { return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.CopyPartResult.ChecksumCRC32C) } if res.CopyPartResult.ChecksumSHA1 != nil { return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.CopyPartResult.ChecksumSHA1) } if res.CopyPartResult.ChecksumSHA256 != nil { return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.CopyPartResult.ChecksumSHA256) } if res.CopyPartResult.ChecksumCRC64NVME != nil { return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.CopyPartResult.ChecksumCRC64NVME) } return nil }) } func UploadPartCopy_should_not_copy_the_checksum(s *S3Conf) error { testName := "UploadPartCopy_should_not_copy_the_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" srcObj := "source-object" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } _, err = putObjectWithData(300, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, ChecksumAlgorithm: types.ChecksumAlgorithmSha1, }, s3client) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, PartNumber: &partNumber, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), }) cancel() if err != nil { return err } if res.CopyPartResult.ChecksumCRC32 != nil { return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.CopyPartResult.ChecksumCRC32) } if res.CopyPartResult.ChecksumCRC32C != nil { return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.CopyPartResult.ChecksumCRC32C) } if res.CopyPartResult.ChecksumSHA1 != nil { return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.CopyPartResult.ChecksumSHA1) } if res.CopyPartResult.ChecksumSHA256 != nil { return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.CopyPartResult.ChecksumSHA256) } if res.CopyPartResult.ChecksumCRC64NVME != nil { return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.CopyPartResult.ChecksumCRC64NVME) } return nil }) } func UploadPartCopy_should_calculate_the_checksum(s *S3Conf) error { testName := "UploadPartCopy_should_calculate_the_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" srcObj := "source-object" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmSha256)) if err != nil { return err } _, err = putObjectWithData(300, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, ChecksumAlgorithm: types.ChecksumAlgorithmSha1, // different from the mp checksum (sha256) }, s3client) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, PartNumber: &partNumber, CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)), }) cancel() if err != nil { return err } if res.CopyPartResult.ChecksumCRC32 != nil { return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.CopyPartResult.ChecksumCRC32) } if res.CopyPartResult.ChecksumCRC32C != nil { return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.CopyPartResult.ChecksumCRC32C) } if res.CopyPartResult.ChecksumCRC64NVME != nil { return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.CopyPartResult.ChecksumCRC64NVME) } if res.CopyPartResult.ChecksumSHA1 != nil { return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.CopyPartResult.ChecksumSHA1) } if getString(res.CopyPartResult.ChecksumSHA256) == "" { return fmt.Errorf("expected non empty sha256 checksum") } return nil }) } func ListParts_incorrect_uploadId(s *S3Conf) error { testName := "ListParts_incorrect_uploadId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: getPtr("my-obj"), UploadId: getPtr("invalid uploadId"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil { return err } return nil }) } func ListParts_incorrect_object_key(s *S3Conf) error { testName := "ListParts_incorrect_object_key" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: getPtr("incorrect-object-key"), UploadId: out.UploadId, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil { return err } return nil }) } func ListParts_invalid_max_parts(s *S3Conf) error { testName := "ListParts_invalid_max_parts" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } invMaxParts := int32(-3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MaxParts: &invMaxParts, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMaxParts)); err != nil { return err } return nil }) } func ListParts_default_max_parts(s *S3Conf) error { testName := "ListParts_default_max_parts" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, }) cancel() if err != nil { return err } if res.MaxParts == nil { return fmt.Errorf("unexpected nil max-parts") } if *res.MaxParts != 1000 { return fmt.Errorf("expected max parts to be 1000, instead got %v", *res.MaxParts) } return nil }) } func ListParts_exceeding_max_parts(s *S3Conf) error { testName := "ListParts_exceeding_max_parts" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, UploadId: mp.UploadId, Key: &obj, }) cancel() if err != nil { return err } if res.MaxParts == nil { return fmt.Errorf("unexpected nil max-parts") } if *res.MaxParts != 1000 { return fmt.Errorf("expected max-parts to be %v, instead got %v", 1000, *res.MaxParts) } return nil }) } func ListParts_truncated(s *S3Conf) error { testName := "ListParts_truncated" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } parts, _, err := uploadParts(s3client, 25*1024*1024, 5, bucket, obj, *out.UploadId) if err != nil { return err } maxParts := int32(3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MaxParts: &maxParts, }) cancel() if err != nil { return err } if res.IsTruncated == nil { return fmt.Errorf("unexpected nil is-truncated") } if res.MaxParts == nil { return fmt.Errorf("unexpected nil max-parts") } if !*res.IsTruncated { return fmt.Errorf("expected the result to be truncated") } if *res.MaxParts != maxParts { return fmt.Errorf("expected max-parts to be %v, instead got %v", maxParts, *res.MaxParts) } if getString(res.NextPartNumberMarker) != fmt.Sprint(*parts[2].PartNumber) { return fmt.Errorf("expected next part number marker to be %v, instead got %v", fmt.Sprint(*parts[2].PartNumber), getString(res.NextPartNumberMarker)) } if !compareParts(parts[:3], res.Parts) { return fmt.Errorf("expected the parts data to be %v, instead got %v", parts[:3], res.Parts) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res2, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, PartNumberMarker: res.NextPartNumberMarker, }) cancel() if err != nil { return err } if getString(res2.PartNumberMarker) != getString(res.NextPartNumberMarker) { return fmt.Errorf("expected part number marker to be %v, instead got %v", getString(res.NextPartNumberMarker), getString(res2.PartNumberMarker)) } if !compareParts(parts[3:], res2.Parts) { return fmt.Errorf("expected the parts data to be %v, instead got %v", parts[3:], res2.Parts) } return nil }) } func ListParts_with_checksums(s *S3Conf) error { testName := "ListParts_with_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for i, algo := range types.ChecksumAlgorithmCrc32.Values() { mp, err := createMp(s3client, bucket, obj, withChecksum(algo)) if err != nil { return err } parts, _, err := uploadParts(s3client, int64((i+1)*5*1024*1024), int64(i+1), bucket, obj, *mp.UploadId, withChecksum(algo)) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, }) cancel() if err != nil { return err } if !compareParts(parts, res.Parts) { return fmt.Errorf("expected the mp parts to be %v, instead got %v", parts, res.Parts) } } return nil }) } func ListParts_null_checksums(s *S3Conf) error { testName := "ListParts_null_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } _, _, err = uploadParts(s3client, 20*1024*1024, 3, bucket, obj, *mp.UploadId) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, }) cancel() if err != nil { return err } if res.ChecksumType != types.ChecksumType("null") { return fmt.Errorf("expected the checksum type to be null, instead got %v", res.ChecksumType) } if res.ChecksumAlgorithm != types.ChecksumAlgorithm("null") { return fmt.Errorf("expected the checksum algorithm to be null, instead got %v", res.ChecksumAlgorithm) } return nil }) } func ListParts_success(s *S3Conf) error { testName := "ListParts_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } parts, _, err := uploadParts(s3client, 5*1024*1024, 5, bucket, obj, *out.UploadId) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, }) cancel() if err != nil { return err } if res.StorageClass != types.StorageClassStandard { return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, res.StorageClass) } if ok := compareParts(parts, res.Parts); !ok { return fmt.Errorf("expected parts %+v, instead got %+v", parts, res.Parts) } return nil }) } func ListMultipartUploads_non_existing_bucket(s *S3Conf) error { testName := "ListMultipartUploads_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { bucketName := getBucketName() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucketName, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func ListMultipartUploads_empty_result(s *S3Conf) error { testName := "ListMultipartUploads_empty_result" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if len(out.Uploads) != 0 { return fmt.Errorf("expected empty uploads, instead got %+v", out.Uploads) } return nil }) } func ListMultipartUploads_invalid_max_uploads(s *S3Conf) error { testName := "ListMultipartUploads_invalid_max_uploads" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { maxUploads := int32(-3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, MaxUploads: &maxUploads, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMaxUploads)); err != nil { return err } return nil }) } func ListMultipartUploads_max_uploads(s *S3Conf) error { testName := "ListMultipartUploads_max_uploads" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { uploads := []types.MultipartUpload{} for i := 1; i < 6; i++ { out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i)) if err != nil { return err } uploads = append(uploads, types.MultipartUpload{ UploadId: out.UploadId, Key: out.Key, StorageClass: types.StorageClassStandard, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) maxUploads := int32(2) out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, MaxUploads: &maxUploads, }) cancel() if err != nil { return err } if out.IsTruncated == nil { return fmt.Errorf("unexpected nil is-truncated") } if out.MaxUploads == nil { return fmt.Errorf("unexpected nil max-uploads") } if !*out.IsTruncated { return fmt.Errorf("expected the output to be truncated") } if *out.MaxUploads != 2 { return fmt.Errorf("expected max-uploads to be 2, instead got %v", out.MaxUploads) } if ok := compareMultipartUploads(out.Uploads, uploads[:2]); !ok { return fmt.Errorf("expected multipart uploads to be %v, instead got %v", uploads[:2], out.Uploads) } if getString(out.NextKeyMarker) != getString(uploads[1].Key) { return fmt.Errorf("expected next-key-marker to be %v, instead got %v", getString(uploads[1].Key), getString(out.NextKeyMarker)) } if getString(out.NextUploadIdMarker) != getString(uploads[1].UploadId) { return fmt.Errorf("expected next-upload-id-marker to be %v, instead got %v", getString(uploads[1].UploadId), getString(out.NextUploadIdMarker)) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, KeyMarker: out.NextKeyMarker, }) cancel() if err != nil { return err } if ok := compareMultipartUploads(out.Uploads, uploads[2:]); !ok { return fmt.Errorf("expected multipart uploads to be %v, instead got %v", uploads[2:], out.Uploads) } return nil }) } func ListMultipartUploads_exceeding_max_uploads(s *S3Conf) error { testName := "ListMultipartUploads_exceeding_max_uploads" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { maxUploads := int32(1343235) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, MaxUploads: &maxUploads, }) cancel() if err != nil { return err } if res.MaxUploads == nil { return fmt.Errorf("unexpected nil max-uploads") } if *res.MaxUploads != 1000 { return fmt.Errorf("expected max-uploads to be %v, instaed got %v", 1000, *res.MaxUploads) } return nil }) } func ListMultipartUploads_incorrect_next_key_marker(s *S3Conf) error { testName := "ListMultipartUploads_incorrect_next_key_marker" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for i := 1; i < 6; i++ { _, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i)) if err != nil { return err } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, KeyMarker: getPtr("wrong_object_key"), }) cancel() if err != nil { return err } if len(out.Uploads) != 0 { return fmt.Errorf("expected empty list of multipart uploads, instead got %v", out.Uploads) } return nil }) } func ListMultipartUploads_ignore_upload_id_marker(s *S3Conf) error { testName := "ListMultipartUploads_ignore_upload_id_marker" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { uploads := []types.MultipartUpload{} for i := 1; i < 6; i++ { out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i)) if err != nil { return err } uploads = append(uploads, types.MultipartUpload{ UploadId: out.UploadId, Key: out.Key, StorageClass: types.StorageClassStandard, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, UploadIdMarker: uploads[2].UploadId, }) cancel() if err != nil { return err } if ok := compareMultipartUploads(out.Uploads, uploads); !ok { return fmt.Errorf("expected multipart uploads to be %v, instead got %v", uploads, out.Uploads) } return nil }) } func ListMultipartUploads_with_checksums(s *S3Conf) error { testName := "ListMultipartUploads_with_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { uploads := []types.MultipartUpload{} for _, el := range []struct { obj string algo types.ChecksumAlgorithm t types.ChecksumType }{ { obj: "obj-1", algo: types.ChecksumAlgorithmCrc32, t: types.ChecksumTypeComposite, }, { obj: "obj-2", algo: types.ChecksumAlgorithmCrc32c, t: types.ChecksumTypeFullObject, }, { obj: "obj-3", algo: types.ChecksumAlgorithmSha1, t: types.ChecksumTypeComposite, }, { obj: "obj-4", algo: types.ChecksumAlgorithmSha256, t: types.ChecksumTypeComposite, }, { obj: "obj-5", algo: types.ChecksumAlgorithmCrc64nvme, t: types.ChecksumTypeFullObject, }, } { key := el.obj mp, err := createMp(s3client, bucket, key, withChecksum(el.algo), withChecksumType(el.t)) if err != nil { return err } uploads = append(uploads, types.MultipartUpload{ Key: &key, UploadId: mp.UploadId, StorageClass: types.StorageClassStandard, ChecksumAlgorithm: el.algo, ChecksumType: el.t, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareMultipartUploads(res.Uploads, uploads) { return fmt.Errorf("expected the final multipart uploads to be %v, instead got %v", uploads, res.Uploads) } return nil }) } func ListMultipartUploads_success(s *S3Conf) error { testName := "ListMultipartUploads_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj1, obj2 := "my-obj-1", "my-obj-2" out1, err := createMp(s3client, bucket, obj1) if err != nil { return err } out2, err := createMp(s3client, bucket, obj2) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } expected := []types.MultipartUpload{ { Key: &obj1, UploadId: out1.UploadId, StorageClass: types.StorageClassStandard, }, { Key: &obj2, UploadId: out2.UploadId, StorageClass: types.StorageClassStandard, }, } if len(out.Uploads) != 2 { return fmt.Errorf("expected 2 upload, instead got %v", len(out.Uploads)) } if ok := compareMultipartUploads(out.Uploads, expected); !ok { return fmt.Errorf("expected uploads %v, instead got %v", expected, out.Uploads) } return nil }) } func AbortMultipartUpload_non_existing_bucket(s *S3Conf) error { testName := "AbortMultipartUpload_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ Bucket: getPtr("incorrect-bucket"), Key: getPtr("my-obj"), UploadId: getPtr("uploadId"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func AbortMultipartUpload_incorrect_uploadId(s *S3Conf) error { testName := "AbortMultipartUpload_incorrect_uploadId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ Bucket: &bucket, Key: getPtr("my-obj"), UploadId: getPtr("invalid uploadId"), }) cancel() if err := checkSdkApiErr(err, "NoSuchUpload"); err != nil { return err } return nil }) } func AbortMultipartUpload_incorrect_object_key(s *S3Conf) error { testName := "AbortMultipartUpload_incorrect_object_key" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ Bucket: &bucket, Key: getPtr("incorrect-object-key"), UploadId: out.UploadId, }) cancel() if err := checkSdkApiErr(err, "NoSuchUpload"); err != nil { return err } return nil }) } func AbortMultipartUpload_success(s *S3Conf) error { testName := "AbortMultipartUpload_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if len(res.Uploads) != 0 { return fmt.Errorf("expected 0 upload, instead got %v", len(res.Uploads)) } return nil }) } func AbortMultipartUpload_success_status_code(s *S3Conf) error { testName := "AbortMultipartUpload_success_status_code" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v/%v?uploadId=%v", bucket, obj, *out.UploadId), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusNoContent { return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode) } return nil }) } func CompletedMultipartUpload_non_existing_bucket(s *S3Conf) error { testName := "CompletedMultipartUpload_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ Bucket: getPtr("non-existing-bucket"), Key: getPtr("some/key"), UploadId: getPtr("uploadId"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func CompleteMultipartUpload_incorrect_part_number(s *S3Conf) error { testName := "CompleteMultipartUpload_incorrect_part_number" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) res, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, PartNumber: &partNumber, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) partNumber = int32(5) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: []types.CompletedPart{ { ETag: res.ETag, PartNumber: &partNumber, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPart)); err != nil { return err } return nil }) } func CompleteMultipartUpload_invalid_ETag(s *S3Conf) error { testName := "CompleteMultipartUpload_invalid_ETag" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) partNumber := int32(1) _, err = s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, PartNumber: &partNumber, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: []types.CompletedPart{ { ETag: getPtr("invalidETag"), PartNumber: &partNumber, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPart)); err != nil { return err } return nil }) } func CompleteMultipartUpload_invalid_checksum_type(s *S3Conf) error { testName := "CompleteMultipartUpload_invalid_checksum_type" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32), withChecksumType(types.ChecksumTypeFullObject)) if err != nil { return err } parts, _, err := uploadParts(s3client, 20*1024*1024, 4, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: types.ChecksumType("invalid_type"), }) cancel() if err := checkApiErr(err, s3err.GetInvalidChecksumHeaderErr("x-amz-checksum-type")); err != nil { return err } return nil }) } func CompleteMultipartUpload_invalid_checksum_part(s *S3Conf) error { testName := "CompleteMultipartUpload_invalid_checksum_part" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32), withChecksumType(types.ChecksumTypeFullObject)) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } cParts := []types.CompletedPart{} for i, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32, }) if i == 0 { cParts[0].ChecksumCRC32 = getPtr("invalid_checksum") } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: types.ChecksumTypeFullObject, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidChecksumPart)); err != nil { return err } return nil }) } func CompleteMultipartUpload_multiple_checksum_part(s *S3Conf) error { testName := "CompleteMultipartUpload_multiple_checksum_part" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32), withChecksumType(types.ChecksumTypeComposite)) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } cParts := []types.CompletedPart{} for i, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32, }) if i == 0 { cParts[0].ChecksumSHA1 = getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0=") } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: types.ChecksumTypeComposite, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidChecksumPart)); err != nil { return err } return nil }) } func CompleteMultipartUpload_incorrect_checksum_part(s *S3Conf) error { testName := "CompleteMultipartUpload_incorrect_checksum_part" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmSha256), withChecksumType(types.ChecksumTypeComposite)) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmSha256)) if err != nil { return err } cParts := []types.CompletedPart{} for i, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumSHA256: el.ChecksumSHA256, }) if i == 0 { cParts[0].ChecksumSHA256 = getPtr("n2alat9FhKiZXkZO18V2LLcZFM3IT8R7DjSMvK//7WU=") } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: types.ChecksumTypeComposite, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPart)); err != nil { return err } return nil }) } func CompleteMultipartUpload_different_checksum_part(s *S3Conf) error { testName := "CompleteMultipartUpload_different_checksum_part" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32c), withChecksumType(types.ChecksumTypeFullObject)) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmCrc32c)) if err != nil { return err } cParts := []types.CompletedPart{} for i, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32C: el.ChecksumCRC32C, }) if i == 0 { cParts[0].ChecksumSHA256 = getPtr("n2alat9FhKiZXkZO18V2LLcZFM3IT8R7DjSMvK//7WU=") cParts[0].ChecksumCRC32C = nil } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: types.ChecksumTypeFullObject, }) cancel() if err := checkApiErr(err, s3err.APIError{ Code: "BadDigest", Description: "The sha256 you specified for part 1 did not match what we received.", HTTPStatusCode: http.StatusBadRequest, }); err != nil { return err } return nil }) } func CompleteMultipartUpload_missing_part_checksum(s *S3Conf) error { testName := "CompleteMultipartUpload_missing_part_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmSha1), withChecksumType(types.ChecksumTypeComposite)) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmSha1)) if err != nil { return err } cParts := []types.CompletedPart{} for i, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumSHA1: el.ChecksumSHA1, }) if i == 0 { cParts[0].ChecksumSHA1 = nil } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: types.ChecksumTypeComposite, }) cancel() if err := checkApiErr(err, s3err.APIError{ Code: "InvalidRequest", Description: "The upload was created using a sha1 checksum. The complete request must include the checksum for each part. It was missing for part 1 in the request.", HTTPStatusCode: http.StatusBadRequest, }); err != nil { return err } return nil }) } func CompleteMultipartUpload_multiple_final_checksums(s *S3Conf) error { testName := "CompleteMultipartUpload_multiple_final_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } parts, _, err := uploadParts(s3client, 5*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmCrc32), withChecksumType(types.ChecksumTypeFullObject)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32C, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumCRC32: getPtr("sGc9Hg=="), ChecksumCRC32C: getPtr("/2NsFg=="), ChecksumType: types.ChecksumTypeFullObject, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMultipleChecksumHeaders)); err != nil { return err } return nil }) } func CompleteMultipartUpload_invalid_final_checksums(s *S3Conf) error { testName := "CompleteMultipartUpload_invalid_final_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for _, el := range []struct { algo types.ChecksumAlgorithm t types.ChecksumType }{ { algo: types.ChecksumAlgorithmCrc32, t: types.ChecksumTypeComposite, }, { algo: types.ChecksumAlgorithmCrc32c, t: types.ChecksumTypeFullObject, }, { algo: types.ChecksumAlgorithmSha1, t: types.ChecksumTypeComposite, }, { algo: types.ChecksumAlgorithmSha256, t: types.ChecksumTypeComposite, }, { algo: types.ChecksumAlgorithmCrc64nvme, t: types.ChecksumTypeFullObject, }, } { mp, err := createMp(s3client, bucket, obj, withChecksum(el.algo), withChecksumType(el.t)) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(el.algo)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32C, ChecksumCRC32C: el.ChecksumCRC32C, ChecksumSHA1: el.ChecksumSHA1, ChecksumSHA256: el.ChecksumSHA256, ChecksumCRC64NVME: el.ChecksumCRC64NVME, }) } mpInput := &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: el.t, } switch el.algo { case types.ChecksumAlgorithmCrc32: mpInput.ChecksumCRC32 = getPtr("invalid_crc32") case types.ChecksumAlgorithmCrc32c: mpInput.ChecksumCRC32C = getPtr("invalid_crc32c") case types.ChecksumAlgorithmSha1: mpInput.ChecksumSHA1 = getPtr("invalid_sha1") case types.ChecksumAlgorithmSha256: mpInput.ChecksumSHA256 = getPtr("invalid_sha256") case types.ChecksumAlgorithmCrc64nvme: mpInput.ChecksumCRC64NVME = getPtr("invalid_crc64nvme") } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, mpInput) cancel() if err := checkApiErr(err, s3err.GetInvalidChecksumHeaderErr(fmt.Sprintf("x-amz-checksum-%v", strings.ToLower(string(el.algo))))); err != nil { return err } } return nil }) } func CompleteMultipartUpload_incorrect_final_checksums(s *S3Conf) error { testName := "CompleteMultipartUpload_incorrect_final_checksums" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for _, el := range []struct { algo types.ChecksumAlgorithm t types.ChecksumType }{ { algo: types.ChecksumAlgorithmCrc32, t: types.ChecksumTypeComposite, }, { algo: types.ChecksumAlgorithmCrc32c, t: types.ChecksumTypeFullObject, }, { algo: types.ChecksumAlgorithmSha1, t: types.ChecksumTypeComposite, }, { algo: types.ChecksumAlgorithmSha256, t: types.ChecksumTypeComposite, }, { algo: types.ChecksumAlgorithmCrc64nvme, t: types.ChecksumTypeFullObject, }, } { mp, err := createMp(s3client, bucket, obj, withChecksum(el.algo), withChecksumType(el.t)) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(el.algo)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32, ChecksumCRC32C: el.ChecksumCRC32C, ChecksumSHA1: el.ChecksumSHA1, ChecksumSHA256: el.ChecksumSHA256, ChecksumCRC64NVME: el.ChecksumCRC64NVME, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, // Provide one of the parts checksum. In any case // the final checksum will differ from one of the parts checksum ChecksumCRC32: cParts[0].ChecksumCRC32, ChecksumCRC32C: cParts[0].ChecksumCRC32C, ChecksumSHA1: cParts[0].ChecksumSHA1, ChecksumSHA256: cParts[0].ChecksumSHA256, ChecksumCRC64NVME: cParts[0].ChecksumCRC64NVME, ChecksumType: el.t, }) cancel() if err := checkApiErr(err, s3err.GetChecksumBadDigestErr(el.algo)); err != nil { return err } } return nil }) } func CompleteMultipartUpload_should_calculate_the_final_checksum_full_object(s *S3Conf) error { testName := "CompleteMultipartUpload_should_calculate_the_final_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for _, el := range []struct { algo types.ChecksumAlgorithm t types.ChecksumType }{ { algo: types.ChecksumAlgorithmCrc32, t: types.ChecksumTypeFullObject, }, { algo: types.ChecksumAlgorithmCrc32c, t: types.ChecksumTypeFullObject, }, { algo: types.ChecksumAlgorithmCrc64nvme, t: types.ChecksumTypeFullObject, }, } { mp, err := createMp(s3client, bucket, obj, withChecksum(el.algo), withChecksumType(el.t)) if err != nil { return err } parts, csum, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(el.algo)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32, ChecksumCRC32C: el.ChecksumCRC32C, ChecksumSHA1: el.ChecksumSHA1, ChecksumSHA256: el.ChecksumSHA256, ChecksumCRC64NVME: el.ChecksumCRC64NVME, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: el.t, }) cancel() if err != nil { return err } switch el.algo { case types.ChecksumAlgorithmCrc32: if getString(res.ChecksumCRC32) != csum { return fmt.Errorf("expected the final crc32 checksum to be %v, instead got %v", csum, getString(res.ChecksumCRC32)) } case types.ChecksumAlgorithmCrc32c: if getString(res.ChecksumCRC32C) != csum { return fmt.Errorf("expected the final crc32c checksum to be %v, instead got %v", csum, getString(res.ChecksumCRC32C)) } case types.ChecksumAlgorithmCrc64nvme: if getString(res.ChecksumCRC64NVME) != csum { return fmt.Errorf("expected the final crc64nvme checksum to be %v, instead got %v", csum, getString(res.ChecksumCRC64NVME)) } } } return nil }) } func CompleteMultipartUpload_should_verify_the_final_checksum(s *S3Conf) error { testName := "CompleteMultipartUpload_should_verify_the_final_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" for _, el := range []struct { algo types.ChecksumAlgorithm t types.ChecksumType }{ { algo: types.ChecksumAlgorithmCrc32, t: types.ChecksumTypeFullObject, }, { algo: types.ChecksumAlgorithmCrc32c, t: types.ChecksumTypeFullObject, }, { algo: types.ChecksumAlgorithmCrc64nvme, t: types.ChecksumTypeFullObject, }, } { mp, err := createMp(s3client, bucket, obj, withChecksum(el.algo), withChecksumType(el.t)) if err != nil { return err } parts, csum, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *mp.UploadId, withChecksum(el.algo)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32, ChecksumCRC32C: el.ChecksumCRC32C, ChecksumSHA1: el.ChecksumSHA1, ChecksumSHA256: el.ChecksumSHA256, ChecksumCRC64NVME: el.ChecksumCRC64NVME, }) } mpInput := &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: el.t, } switch el.algo { case types.ChecksumAlgorithmCrc32: mpInput.ChecksumCRC32 = &csum case types.ChecksumAlgorithmCrc32c: mpInput.ChecksumCRC32C = &csum case types.ChecksumAlgorithmCrc64nvme: mpInput.ChecksumCRC64NVME = &csum } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CompleteMultipartUpload(ctx, mpInput) cancel() if err != nil { return err } switch el.algo { case types.ChecksumAlgorithmCrc32: if getString(res.ChecksumCRC32) != csum { return fmt.Errorf("expected the final crc32 checksum to be %v, instead got %v", csum, getString(res.ChecksumCRC32)) } case types.ChecksumAlgorithmCrc32c: if getString(res.ChecksumCRC32C) != csum { return fmt.Errorf("expected the final crc32c checksum to be %v, instead got %v", csum, getString(res.ChecksumCRC32C)) } case types.ChecksumAlgorithmCrc64nvme: if getString(res.ChecksumCRC64NVME) != csum { return fmt.Errorf("expected the final crc64nvme checksum to be %v, instead got %v", csum, getString(res.ChecksumCRC64NVME)) } } } return nil }) } func CompleteMultipartUpload_checksum_type_mismatch(s *S3Conf) error { testName := "CompleteMultipartUpload_checksum_type_mismatch" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc32), withChecksumType(types.ChecksumTypeFullObject)) if err != nil { return err } parts, _, err := uploadParts(s3client, 20*1024*1024, 4, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmCrc32)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC32: el.ChecksumCRC32, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumType: types.ChecksumTypeComposite, }) cancel() if err := checkApiErr(err, s3err.GetChecksumTypeMismatchOnMpErr(types.ChecksumTypeFullObject)); err != nil { return err } return nil }) } func CompleteMultipartUpload_should_ignore_the_final_checksum(s *S3Conf) error { testName := "CompleteMultipartUpload_should_ignore_the_final_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } parts, _, err := uploadParts(s3client, 20*1024*1024, 4, bucket, obj, *mp.UploadId) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, ChecksumSHA1: getPtr("Kq5sNclPz7QV2+lfQIuc6R7oRu0="), // should ignore this }) cancel() if err != nil { return err } if res.ChecksumCRC32 != nil { return fmt.Errorf("expected nil crc32 checksum, insted got %v", *res.ChecksumCRC32) } if res.ChecksumCRC32C != nil { return fmt.Errorf("expected nil crc32c checksum, insted got %v", *res.ChecksumCRC32C) } if res.ChecksumSHA1 != nil { return fmt.Errorf("expected nil sha1 checksum, insted got %v", *res.ChecksumSHA1) } if res.ChecksumSHA256 != nil { return fmt.Errorf("expected nil sha256 checksum, insted got %v", *res.ChecksumSHA256) } if res.ChecksumCRC64NVME != nil { return fmt.Errorf("expected nil crc64nvme checksum, insted got %v", *res.ChecksumSHA256) } return nil }) } func CompleteMultipartUpload_should_succeed_without_final_checksum_type(s *S3Conf) error { testName := "CompleteMultipartUpload_should_succeed_without_final_checksum_type" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj, withChecksum(types.ChecksumAlgorithmCrc64nvme), withChecksumType(types.ChecksumTypeFullObject)) if err != nil { return err } parts, _, err := uploadParts(s3client, 20*1024*1024, 4, bucket, obj, *mp.UploadId, withChecksum(types.ChecksumAlgorithmCrc64nvme)) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, ChecksumCRC64NVME: el.ChecksumCRC64NVME, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, }) cancel() if err != nil { return err } if res.ChecksumType != types.ChecksumTypeFullObject { return fmt.Errorf("expected the final checksum type to be %v, instead got %v", types.ChecksumTypeFullObject, res.ChecksumType) } if getString(res.ChecksumCRC64NVME) == "" { return fmt.Errorf("expected non empty crc64nvme checksum") } return nil }) } func CompleteMultipartUpload_small_upload_size(s *S3Conf) error { testName := "CompleteMultipartUpload_small_upload_size" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } // The uploaded parts size is 256 < 5 Mib (the minimum allowed size) parts, _, err := uploadParts(s3client, 1024, 4, bucket, obj, *mp.UploadId) if err != nil { return err } cParts := []types.CompletedPart{} for _, el := range parts { cParts = append(cParts, types.CompletedPart{ PartNumber: el.PartNumber, ETag: el.ETag, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: cParts, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrEntityTooSmall)); err != nil { return err } return nil }) } func CompleteMultipartUpload_empty_parts(s *S3Conf) error { testName := "CompleteMultipartUpload_empty_parts" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } _, _, err = uploadParts(s3client, 5*1024*1024, 1, bucket, obj, *mp.UploadId) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: []types.CompletedPart{}, // empty parts list }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrEmptyParts)); err != nil { return err } return nil }) } func CompleteMultipartUpload_incorrect_parts_order(s *S3Conf) error { testName := "CompleteMultipartUpload_incorrect_parts_order" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } parts, _, err := uploadParts(s3client, 15*1024*1024, 3, bucket, obj, *out.UploadId) if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } compParts[0], compParts[1] = compParts[1], compParts[0] ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPartOrder)); err != nil { return err } return nil }) } func CompleteMultipartUpload_mpu_object_size(s *S3Conf) error { testName := "CompleteMultipartUpload_mpu_object_size" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" mp, err := createMp(s3client, bucket, obj) if err != nil { return err } mpuSize := int64(23 * 1024 * 1024) // 23 mib parts, _, err := uploadParts(s3client, mpuSize, 4, bucket, obj, *mp.UploadId) if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } invMpuSize := int64(-1) // invalid MpuObjectSize // Initially provide invalid MpuObjectSize: -3 input := &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, MpuObjectSize: &invMpuSize, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, input) cancel() if err := checkApiErr(err, s3err.GetNegatvieMpObjectSizeErr(invMpuSize)); err != nil { return err } incorMpuSize := int64(213123) // incorrect object size input.MpuObjectSize = &incorMpuSize ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, input) cancel() if err := checkApiErr(err, s3err.GetIncorrectMpObjectSizeErr(mpuSize, incorMpuSize)); err != nil { return err } // Correct value for MpuObjectSize input.MpuObjectSize = &mpuSize ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, input) cancel() if err != nil { return err } // Make sure the object has been uploaded with proper size ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if res.ContentLength == nil { return fmt.Errorf("expected non nil Content-Length") } if *res.ContentLength != mpuSize { return fmt.Errorf("expected the uploaded object size to be %v, instead got %v", mpuSize, *res.ContentLength) } return nil }) } func CompleteMultipartUpload_invalid_part_number(s *S3Conf) error { testName := "CompleteMultipartUpload_invalid_part_number" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } parts, _, err := uploadParts(s3client, 5*1024*1024, 1, bucket, obj, *out.UploadId) if err != nil { return err } invPartNumber := int32(-4) compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: &invPartNumber, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidCompleteMpPartNumber)); err != nil { return err } return nil }) } func CompleteMultipartUpload_success(s *S3Conf) error { testName := "CompleteMultipartUpload_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } objSize := int64(25 * 1024 * 1024) parts, csum, err := uploadParts(s3client, objSize, 5, bucket, obj, *out.UploadId) if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, }) cancel() if err != nil { return err } if getString(res.Key) != obj { return fmt.Errorf("expected object key to be %v, instead got %v", obj, *res.Key) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if getString(resp.ETag) != getString(res.ETag) { return fmt.Errorf("expected the uploaded object etag to be %v, instead got %v", getString(res.ETag), getString(resp.ETag)) } if resp.ContentLength == nil { return fmt.Errorf("expected (head object) non nil Content-Length") } if *resp.ContentLength != int64(objSize) { return fmt.Errorf("expected the uploaded object size to be %v, instead got %v", objSize, resp.ContentLength) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) defer cancel() rget, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) if err != nil { return err } if rget.ContentLength == nil { return fmt.Errorf("expected (get object) non nil Content-Length") } if *rget.ContentLength != int64(objSize) { return fmt.Errorf("expected the object content-length to be %v, instead got %v", objSize, *rget.ContentLength) } bdy, err := io.ReadAll(rget.Body) if err != nil { return err } defer rget.Body.Close() sum := sha256.Sum256(bdy) getsum := hex.EncodeToString(sum[:]) if csum != getsum { return fmt.Errorf("expected the object checksum to be %v, instead got %v", csum, getsum) } return nil }) } type mpinfo struct { uploadId *string parts []types.CompletedPart } func CompleteMultipartUpload_racey_success(s *S3Conf) error { testName := "CompleteMultipartUpload_racey_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" var mu sync.RWMutex uploads := make([]mpinfo, 10) sums := make([]string, 10) objSize := int64(25 * 1024 * 1024) eg := errgroup.Group{} for i := range 10 { func(i int) { eg.Go(func() error { out, err := createMp(s3client, bucket, obj) if err != nil { return err } parts, csum, err := uploadParts(s3client, objSize, 5, bucket, obj, *out.UploadId) mu.Lock() sums[i] = csum mu.Unlock() if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } mu.Lock() uploads[i] = mpinfo{ uploadId: out.UploadId, parts: compParts, } mu.Unlock() return nil }) }(i) } err := eg.Wait() if err != nil { return err } eg = errgroup.Group{} for i := range 10 { func(i int) { eg.Go(func() error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) mu.RLock() res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: uploads[i].uploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: uploads[i].parts, }, }) mu.RUnlock() cancel() if err != nil { fmt.Println("GOT ERROR: ", err) return err } if getString(res.Key) != obj { return fmt.Errorf("expected object key to be %v, instead got %v", obj, getString(res.Key)) } return nil }) }(i) } err = eg.Wait() if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) defer cancel() out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected (get object) non nil Content-Length") } if *out.ContentLength != int64(objSize) { return fmt.Errorf("expected the object content-length to be %v, instead got %v", objSize, *out.ContentLength) } bdy, err := io.ReadAll(out.Body) if err != nil { return err } defer out.Body.Close() sum := sha256.Sum256(bdy) csum := hex.EncodeToString(sum[:]) mu.RLock() defer mu.RUnlock() for _, s := range sums { if csum == s { return nil } } return fmt.Errorf("expected the object checksum to be one of %v, instead got %v", sums, csum) }) } func PutBucketAcl_non_existing_bucket(s *S3Conf) error { testName := "PutBucketAcl_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutBucketAcl_disabled(s *S3Conf) error { testName := "PutBucketAcl_disabled" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicRead, GrantRead: &s.awsID, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAclNotSupported)); err != nil { return err } return nil }) } func PutBucketAcl_none_of_the_options_specified(s *S3Conf) error { testName := "PutBucketAcl_none_of_the_options_specified" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingSecurityHeader)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_acl_canned_and_acp(s *S3Conf) error { testName := "PutBucketAcl_invalid_acl_canned_and_acp" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPrivate, GrantRead: getPtr("testuser1"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBothCannedAndHeaderGrants)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_acl_canned_and_grants(s *S3Conf) error { testName := "PutBucketAcl_invalid_acl_canned_and_grants" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPrivate, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ ID: getPtr("awsID"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, }, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrUnexpectedContent)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_acl_acp_and_grants(s *S3Conf) error { testName := "PutBucketAcl_invalid_acl_acp_and_grants" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, GrantFullControl: getPtr("userAccess"), AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ ID: getPtr("awsID"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, }, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrUnexpectedContent)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_owner(s *S3Conf) error { testName := "PutBucketAcl_invalid_owner" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := createUsers(s, []user{testuser1}); err != nil { return err } if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ ID: getPtr(testuser1.access), Type: types.TypeCanonicalUser, }, Permission: types.PermissionRead, }, }, Owner: &types.Owner{ ID: getPtr("invalidOwner"), }, }, }) cancel() if err := checkApiErr(err, s3err.APIError{ Code: "InvalidArgument", Description: "Invalid id", HTTPStatusCode: http.StatusBadRequest, }); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_owner_not_in_body(s *S3Conf) error { testName := "PutBucketAcl_invalid_owner_not_in_body" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ Type: types.TypeCanonicalUser, ID: getPtr("grt1"), }, Permission: types.PermissionRead, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedACL)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_empty_owner_id_in_body(s *S3Conf) error { testName := "PutBucketAcl_invalid_empty_owner_id_in_body" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ Type: types.TypeCanonicalUser, ID: getPtr("grt1"), }, Permission: types.PermissionRead, }, }, // Empty owner ID Owner: &types.Owner{}, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedACL)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_permission_in_body(s *S3Conf) error { testName := "PutBucketAcl_invalid_permission_in_body" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ Type: types.TypeCanonicalUser, ID: getPtr("grt1"), }, Permission: types.Permission("invalid_permission"), }, }, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedACL)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_invalid_grantee_type_in_body(s *S3Conf) error { testName := "PutBucketAcl_invalid_grantee_type_in_body" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ Type: types.Type("invalid_type"), ID: getPtr("grt1"), }, Permission: types.PermissionRead, }, }, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedACL)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_empty_grantee_ID_in_body(s *S3Conf) error { testName := "PutBucketAcl_empty_grantee_ID_in_body" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ Type: types.TypeCanonicalUser, }, Permission: types.PermissionRead, }, }, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedACL)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_success_access_denied(s *S3Conf) error { testName := "PutBucketAcl_success_access_denied" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ ID: getPtr(testuser1.access), Type: types.TypeCanonicalUser, }, Permission: types.PermissionRead, }, }, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_success_canned_acl(s *S3Conf) error { testName := "PutBucketAcl_success_canned_acl" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicReadWrite, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_success_acp(s *S3Conf) error { testName := "PutBucketAcl_success_acp" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, GrantRead: &testuser1.access, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.HeadBucket(ctx, &s3.HeadBucketInput{ Bucket: &bucket, }) cancel() if err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketAcl_success_grants(s *S3Conf) error { testName := "PutBucketAcl_success_grants" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: []types.Grant{ { Grantee: &types.Grantee{ ID: &testuser1.access, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, }, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func GetBucketAcl_non_existing_bucket(s *S3Conf) error { testName := "GetBucketAcl_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetBucketAcl_translation_canned_public_read(s *S3Conf) error { testName := "GetBucketAcl_translation_canned_public_read" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { grants := []types.Grant{ { Grantee: &types.Grantee{ ID: &s.awsID, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, { Grantee: &types.Grantee{ ID: getPtr("all-users"), Type: types.TypeGroup, }, Permission: types.PermissionRead, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicRead, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if ok := compareGrants(out.Grants, grants); !ok { return fmt.Errorf("expected grants to be %v, instead got %v", grants, out.Grants) } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected bucket owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func GetBucketAcl_translation_canned_public_read_write(s *S3Conf) error { testName := "GetBucketAcl_translation_canned_public_read_write" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { grants := []types.Grant{ { Grantee: &types.Grantee{ ID: &s.awsID, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, { Grantee: &types.Grantee{ ID: getPtr("all-users"), Type: types.TypeGroup, }, Permission: types.PermissionRead, }, { Grantee: &types.Grantee{ ID: getPtr("all-users"), Type: types.TypeGroup, }, Permission: types.PermissionWrite, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicReadWrite, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if ok := compareGrants(out.Grants, grants); !ok { return fmt.Errorf("expected grants to be %v, instead got %v", grants, out.Grants) } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected bucket owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func GetBucketAcl_translation_canned_private(s *S3Conf) error { testName := "GetBucketAcl_translation_canned_private" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { grants := []types.Grant{ { Grantee: &types.Grantee{ ID: &s.awsID, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPrivate, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if ok := compareGrants(out.Grants, grants); !ok { return fmt.Errorf("expected grants to be %v, instead got %v", grants, out.Grants) } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected bucket owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func GetBucketAcl_access_denied(s *S3Conf) error { testName := "GetBucketAcl_access_denied" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func GetBucketAcl_success(s *S3Conf) error { testName := "GetBucketAcl_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{ {"grt1", "grt1secret", "user"}, {"grt2", "grt2secret", "user"}, {"grt3", "grt3secret", "user"}, }) if err != nil { return err } grants := []types.Grant{ { Grantee: &types.Grantee{ ID: getPtr("grt1"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, { Grantee: &types.Grantee{ ID: getPtr("grt2"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionReadAcp, }, { Grantee: &types.Grantee{ ID: getPtr("grt3"), Type: types.TypeCanonicalUser, }, Permission: types.PermissionWrite, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, AccessControlPolicy: &types.AccessControlPolicy{ Grants: grants, Owner: &types.Owner{ ID: &s.awsID, }, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: &bucket, }) cancel() if err != nil { return err } grants = append([]types.Grant{ { Grantee: &types.Grantee{ ID: &s.awsID, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, }, grants...) if ok := compareGrants(out.Grants, grants); !ok { return fmt.Errorf("expected grants to be %v, instead got %v", grants, out.Grants) } if getString(out.Owner.ID) != s.awsID { return fmt.Errorf("expected bucket owner to be %v, instead got %v", s.awsID, getString(out.Owner.ID)) } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func PutBucketPolicy_non_existing_bucket(s *S3Conf) error { testName := "PutBucketPolicy_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) doc := genPolicyDoc("Allow", `"*"`, `"s3:*"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: getPtr("non-existing-bucket"), Policy: &doc, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutBucketPolicy_invalid_json(s *S3Conf) error { testName := "PutBucketPolicy_invalid_json" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, doc := range []string{ "{true}", "{asdfsdaf", `{"Principal": "*" `, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("This policy contains invalid Json")); err != nil { return err } } for _, doc := range []string{ "false", "invalid_json", "bucketPolicy", `"Statement": []}`, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Policies must be valid JSON and the first byte must be '{'")); err != nil { return err } } return nil }) } func PutBucketPolicy_statement_not_provided(s *S3Conf) error { testName := "PutBucketPolicy_statement_not_provided" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := `{}` ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Missing required field Statement")); err != nil { return err } return nil }) } func PutBucketPolicy_empty_statement(s *S3Conf) error { testName := "PutBucketPolicy_empty_statement" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := `{"Statement": []}` ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Could not parse the policy: Statement is empty!")); err != nil { return err } return nil }) } func PutBucketPolicy_invalid_effect(s *S3Conf) error { testName := "PutBucketPolicy_invalid_effect" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("invalid_effect", `"*"`, `"s3:*"`, `"arn:aws:s3:::*"`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Invalid effect: invalid_effect")); err != nil { return err } return nil }) } func PutBucketPolicy_invalid_action(s *S3Conf) error { testName := "PutBucketPolicy_invalid_action" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } for _, action := range []string{ // empty actions `""`, "[]", // completely invalid action `"invalid_action"`, `["invalid_action"]`, // only prefix `"s3"`, `"s3:"`, // malformed prefix `"s4:GetObject"`, `"ss3:ListBucket"`, `"s3x:PutBucketAcl"`, `":GetObject"`, `"s3GetObject"`, // bad separator `"s3::GetObject"`, `"s3:Put-Object"`, `"s3:GetObject:"`, `"s3:Put(Object)"`, // wildcard abuse `"s3:*Obj??ect*"`, `"s3:????"`, `"s3:*:"`, `"*GetObject"`, `"???PutObject"`, `"s3:Abort?"`, `"s3:??Abort*"`, } { doc := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), action, fmt.Sprintf(`"arn:aws:s3:::%s"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Policy has invalid action")); err != nil { return err } } return nil }) } func PutBucketPolicy_empty_principals_string(s *S3Conf) error { testName := "PutBucketPolicy_empty_principals_string" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `""`, `"s3:*"`, `"arn:aws:s3:::*"`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Invalid principal in policy")); err != nil { return err } return nil }) } func PutBucketPolicy_empty_principals_array(s *S3Conf) error { testName := "PutBucketPolicy_empty_principals_array" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `[]`, `"s3:*"`, `"arn:aws:s3:::*"`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Invalid principal in policy")); err != nil { return err } return nil }) } func PutBucketPolicy_principals_aws_struct_empty_string(s *S3Conf) error { testName := "PutBucketPolicy_principals_aws_struct_empty_string" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `{"AWS": ""}`, `"s3:*"`, `"arn:aws:s3:::*"`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Invalid principal in policy")); err != nil { return err } return nil }) } func PutBucketPolicy_principals_aws_struct_empty_string_slice(s *S3Conf) error { testName := "PutBucketPolicy_principals_aws_struct_empty_string_slice" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `{"AWS": []}`, `"s3:*"`, `"arn:aws:s3:::*"`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Invalid principal in policy")); err != nil { return err } return nil }) } func PutBucketPolicy_principals_incorrect_wildcard_usage(s *S3Conf) error { testName := "PutBucketPolicy_principals_incorrect_wildcard_usage" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `["*", "grt1"]`, `"s3:*"`, `"arn:aws:s3:::*"`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Invalid principal in policy")); err != nil { return err } return nil }) } func PutBucketPolicy_non_existing_principals(s *S3Conf) error { testName := "PutBucketPolicy_non_existing_principals" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `["a_rarely_existing_user_account_1", "a_rarely_existing_user_account_2"]`, `"s3:*"`, `"arn:aws:s3:::*"`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Invalid principal in policy")); err != nil { return err } return nil }) } func PutBucketPolicy_empty_resources_string(s *S3Conf) error { testName := "PutBucketPolicy_empty_resources_string" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `["*"]`, `"s3:*"`, `""`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Policy has invalid resource")); err != nil { return err } return nil }) } func PutBucketPolicy_empty_resources_array(s *S3Conf) error { testName := "PutBucketPolicy_empty_resources_array" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `["*"]`, `"s3:*"`, `[]`) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Policy has invalid resource")); err != nil { return err } return nil }) } func PutBucketPolicy_invalid_resource_prefix(s *S3Conf) error { testName := "PutBucketPolicy_invalid_resource_prefix" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { resource := fmt.Sprintf(`"arn:aws:iam:::%v"`, bucket) doc := genPolicyDoc("Allow", `["*"]`, `"s3:*"`, resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Policy has invalid resource")); err != nil { return err } return nil }) } func PutBucketPolicy_invalid_resource_with_starting_slash(s *S3Conf) error { testName := "PutBucketPolicy_invalid_resource_with_starting_slash" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { resource := fmt.Sprintf(`"arn:aws:s3:::/%v"`, bucket) doc := genPolicyDoc("Allow", `["*"]`, `"s3:*"`, resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Policy has invalid resource")); err != nil { return err } return nil }) } func PutBucketPolicy_duplicate_resource(s *S3Conf) error { testName := "PutBucketPolicy_duplicate_resource" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { resource := fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket) doc := genPolicyDoc("Allow", `["*"]`, `"s3:*"`, fmt.Sprintf("[%v, %v]", resource, resource)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } return nil }) } func PutBucketPolicy_incorrect_bucket_name(s *S3Conf) error { testName := "PutBucketPolicy_incorrect_bucket_name" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { resource := fmt.Sprintf(`"arn:aws:s3:::prefix-%v"`, bucket) doc := genPolicyDoc("Allow", `["*"]`, `"s3:*"`, resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Policy has invalid resource")); err != nil { return err } return nil }) } func PutBucketPolicy_action_resource_mismatch(s *S3Conf) error { testName := "PutBucketPolicy_action_resource_mismatch" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { bucketResource := fmt.Sprintf(`"arn:aws:s3:::%s"`, bucket) objectResource := fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket) for _, test := range []struct { resource string action string }{ // bucket resources {bucketResource, `"s3:GetObject"`}, {bucketResource, `"s3:PutObjectTagging"`}, {bucketResource, `"s3:GetObjec?"`}, {bucketResource, `"s3:Abort*"`}, {bucketResource, `"s3:*Multipart*"`}, {bucketResource, `"s3:???Object"`}, // object resources {objectResource, `"s3:ListBucket"`}, {objectResource, `"s3:GetBucketTagging"`}, {objectResource, `"s3:???BucketVersioning"`}, {objectResource, `"s3:*Bucket*"`}, {objectResource, `"s3:GetBucket*"`}, } { doc := genPolicyDoc("Allow", `["*"]`, test.action, test.resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err := checkApiErr(err, getMalformedPolicyError("Action does not apply to any resource(s) in statement")); err != nil { return err } } return nil }) } func PutBucketPolicy_explicit_deny(s *S3Conf) error { testName := "PutBucketPolicy_explicit_deny" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } resource := fmt.Sprintf("arn:aws:s3:::%v", bucket) resourceWildCard := fmt.Sprintf("%v/*", resource) resourcePrefix := fmt.Sprintf("%v/someprefix/*", resource) policy := fmt.Sprintf(`{ "Statement": [ { "Action": [ "s3:*" ], "Effect": "Allow", "Principal": [ "grt1" ], "Resource": [ "%v", "%v" ] }, { "Action": [ "s3:*" ], "Effect": "Allow", "Principal": [ "grt2" ], "Resource": [ "%v", "%v" ] }, { "Action": [ "s3:*" ], "Effect": "Deny", "Principal": [ "grt2" ], "Resource": "%v" } ] }`, resourcePrefix, resource, resourceWildCard, resource, resourcePrefix) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser2) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: getPtr("someprefix/hello"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func PutBucketPolicy_multi_wildcard_resource(s *S3Conf) error { testName := "PutBucketPolicy_multi_wildcard_resource" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := createUsers(s, []user{testuser1}); err != nil { return err } resource := fmt.Sprintf(`["arn:aws:s3:::%v/*/*", "arn:aws:s3:::%v"]`, bucket, bucket) principal := fmt.Sprintf("\"%v\"", testuser1.access) doc := genPolicyDoc("Allow", principal, `"s3:*"`, resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"foo"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } _, err = putObjects(userClient, []string{"bar/quxx", "foo/bar/baz", "foo/bar/xyz/quxx"}, bucket) if err != nil { return err } return nil }) } func PutBucketPolicy_any_char_match(s *S3Conf) error { testName := "PutBucketPolicy_any_char_match" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := createUsers(s, []user{testuser1}); err != nil { return err } resource := fmt.Sprintf(`["arn:aws:s3:::%v/m?-obj/*"]`, bucket) principal := fmt.Sprintf("\"%v\"", testuser1.access) doc := genPolicyDoc("Allow", principal, `"s3:*"`, resource) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"myy-obj/hello", "rand/foo", "my-objj/bar"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } _, err = putObjects(userClient, []string{"my-obj/hello", "mk-obj/foo", "m--obj/bar"}, bucket) if err != nil { return err } return nil }) } func PutBucketPolicy_success(s *S3Conf) error { testName := "PutBucketPolicy_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } bucketResource := fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket) objectResource := fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket) for _, doc := range []string{ genPolicyDoc("Allow", `["grt1", "grt2"]`, `["s3:DeleteBucket", "s3:GetBucketAcl"]`, bucketResource), genPolicyDoc("Allow", `{"AWS": ["grt1", "grt2"]}`, `["s3:DeleteBucket", "s3:GetBucketAcl"]`, bucketResource), genPolicyDoc("Deny", `"*"`, `"s3:DeleteBucket"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)), genPolicyDoc("Deny", `{"AWS": "*"}`, `"s3:DeleteBucket"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)), genPolicyDoc("Allow", `"grt1"`, `["s3:PutBucketVersioning", "s3:ListMultipartUploadParts", "s3:ListBucket"]`, fmt.Sprintf(`[%v, %v]`, bucketResource, objectResource)), genPolicyDoc("Allow", `"*"`, `"s3:*"`, fmt.Sprintf(`[%v, %v]`, bucketResource, objectResource)), genPolicyDoc("Allow", `"*"`, `"s3:Get*"`, objectResource), genPolicyDoc("Deny", `"*"`, `"s3:Create*"`, fmt.Sprintf(`[%v, %v]`, bucketResource, objectResource)), } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } } return nil }) } func GetBucketPolicy_non_existing_bucket(s *S3Conf) error { testName := "GetBucketPolicy_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{ Bucket: getPtr("non-existing-bucket"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetBucketPolicy_not_set(s *S3Conf) error { testName := "GetBucketPolicy_not_set" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)); err != nil { return err } return nil }) } func GetBucketPolicy_success(s *S3Conf) error { testName := "GetBucketPolicy_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `"*"`, `["s3:DeleteBucket", "s3:GetBucketTagging"]`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if out.Policy == nil { return fmt.Errorf("expected non nil policy result") } if *out.Policy != doc { return fmt.Errorf("expected the bucket policy to be %v, instead got %v", doc, *out.Policy) } return nil }) } func GetBucketPolicyStatus_non_existing_bucket(s *S3Conf) error { testName := "GetBucketPolicyStatus_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketPolicyStatus(ctx, &s3.GetBucketPolicyStatusInput{ Bucket: getPtr("non-existing-bucket"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)) }) } func GetBucketPolicyStatus_no_such_bucket_policy(s *S3Conf) error { testName := "GetBucketPolicyStatus_no_such_bucket_policy" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketPolicyStatus(ctx, &s3.GetBucketPolicyStatusInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)) }) } func GetBucketPolicyStatus_success(s *S3Conf) error { testName := "GetBucketPolicyStatus_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } for _, test := range []struct { policy string status bool }{ { policy: genPolicyDoc("Allow", `"grt1"`, `["s3:DeleteBucket", "s3:GetBucketTagging"]`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)), status: false, }, { policy: genPolicyDoc("Allow", `"grt2"`, `"s3:GetObject"`, fmt.Sprintf(`"arn:aws:s3:::%v/obj"`, bucket)), status: false, }, { policy: genPolicyDoc("Allow", `"*"`, `"s3:PutObject"`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)), status: true, }, { policy: genPolicyDoc("Allow", `"*"`, `"s3:ListBucket"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)), status: true, }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &test.policy, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetBucketPolicyStatus(ctx, &s3.GetBucketPolicyStatusInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if res.PolicyStatus.IsPublic == nil { return fmt.Errorf("expected non nil policy status") } if *res.PolicyStatus.IsPublic != test.status { return fmt.Errorf("expected the policy public status to be %v, instead got %v", test.status, *res.PolicyStatus.IsPublic) } } return nil }) } func DeleteBucketPolicy_non_existing_bucket(s *S3Conf) error { testName := "DeleteBucketPolicy_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{ Bucket: getPtr("non-existing-bucket"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func DeleteBucketPolicy_remove_before_setting(s *S3Conf) error { testName := "DeleteBucketPolicy_remove_before_setting" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{ Bucket: &bucket, }) cancel() return err }) } func DeleteBucketPolicy_success(s *S3Conf) error { testName := "DeleteBucketPolicy_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { doc := genPolicyDoc("Allow", `"*"`, `["s3:DeleteBucket", "s3:GetBucketTagging"]`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{ Bucket: &bucket, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)); err != nil { return err } return nil }) } // Bucket CORS tests func PutBucketCors_non_existing_bucket(s *S3Conf) error { testName := "PutBucketCors_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: getPtr("non-existing-bucket"), CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://origin.com"}, AllowedMethods: []string{http.MethodGet}, }, }, }, }) return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)) }) } func PutBucketCors_empty_cors_rules(s *S3Conf) error { testName := "PutBucketCors_empty_cors_rules" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{}, }, }) return checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)) }) } func PutBucketCors_invalid_method(s *S3Conf) error { testName := "PutBucketCors_invalid_method" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, test := range []struct { invalidMethod string allowedMethods []string }{ {"get", []string{"get"}}, {"put", []string{"put"}}, {"post", []string{"post"}}, {"head", []string{"head"}}, {"delete", []string{"delete"}}, {http.MethodPatch, []string{http.MethodGet, http.MethodPatch}}, {http.MethodOptions, []string{http.MethodPost, http.MethodOptions}}, {"invalid_method", []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodDelete, "invalid_method"}}, } { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://origin.com"}, AllowedMethods: test.allowedMethods, AllowedHeaders: []string{"X-Amz-Date"}, ExposeHeaders: []string{"Authorization"}, }, }, }, }) if err := checkApiErr(err, s3err.GetUnsopportedCORSMethodErr(test.invalidMethod)); err != nil { return err } } return nil }) } func PutBucketCors_invalid_header(s *S3Conf) error { testName := "PutBucketCors_invalid_header" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, test := range []struct { invalidHeader string headers []string }{ {"invalid header", []string{"X-Amz-Date", "X-Amz-Content-Sha256", "invalid header"}}, {"X-Custom:Header", []string{"Authorization", "X-Custom:Header"}}, {"X(Custom)", []string{"Content-Length", "X(Custom)"}}, {"Bad/Header", []string{"Content-Encoding", "Bad/Header"}}, {"X[Key]", []string{"Date", "X[Key]"}}, {"Bad=Name", []string{"X-Amz-Custome-Header", "Bad=Name"}}, {`X"Quote"`, []string{`X"Quote"`}}, } { // first check for allowed headers cfg := &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://origin.com"}, AllowedMethods: []string{http.MethodPost}, AllowedHeaders: test.headers, ExposeHeaders: []string{"Authorization"}, }, }, } err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: cfg, }) if err := checkApiErr(err, s3err.GetInvalidCORSHeaderErr(test.invalidHeader)); err != nil { return err } // second check for expose headers cfg.CORSRules[0].AllowedHeaders = []string{"X-Amz-Date"} // set to any valid header cfg.CORSRules[0].ExposeHeaders = test.headers err = putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: cfg, }) if err := checkApiErr(err, s3err.GetInvalidCORSHeaderErr(test.invalidHeader)); err != nil { return err } } return nil }) } // TODO: report a bug for this case func PutBucketCors_invalid_content_md5(s *S3Conf) error { testName := "PutBucketCors_invalid_content_md5" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { return nil }) } func PutBucketCors_incorrect_content_md5(s *S3Conf) error { testName := "PutBucketCors_incorrect_content_md5" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { return nil }) } func PutBucketCors_success(s *S3Conf) error { testName := "PutBucketCors_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { maxAgePositive, maxAgeNegative := int32(3000), int32(-100) return putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://origin.com"}, AllowedMethods: []string{http.MethodPost, http.MethodPut}, AllowedHeaders: []string{"X-Amz-Date"}, ExposeHeaders: []string{"Authorization"}, // weirdely negative max age seconds are also considered valid MaxAgeSeconds: &maxAgeNegative, }, { AllowedOrigins: []string{"*"}, AllowedMethods: []string{http.MethodDelete, http.MethodGet, http.MethodHead}, AllowedHeaders: []string{"Content-Type", "Content-Encoding", "Content-MD5"}, ExposeHeaders: []string{"Authorization", "X-Amz-Date", "X-Amz-Conten-Sha256"}, ID: getPtr("id"), MaxAgeSeconds: &maxAgePositive, }, { AllowedOrigins: []string{"http://example.com", "https://something.net", "http://*origin.com"}, AllowedMethods: []string{http.MethodGet}, }, }, }, }) }) } func GetBucketCors_non_existing_bucket(s *S3Conf) error { testName := "GetBucketCors_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{ Bucket: getPtr("non-existing-bucket"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)) }) } func GetBucketCors_no_such_bucket_cors(s *S3Conf) error { testName := "GetBucketCors_no_such_bucket_cors" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchCORSConfiguration)) }) } func GetBucketCors_success(s *S3Conf) error { testName := "GetBucketCors_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { cfg := &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://origin.com", "helloworld.net"}, AllowedMethods: []string{http.MethodPost, http.MethodPut, http.MethodHead}, AllowedHeaders: []string{"X-Amz-Date", "X-Amz-Meta-Something"}, ExposeHeaders: []string{"Authorization", "Content-Disposition"}, MaxAgeSeconds: getPtr(int32(125)), }, { AllowedOrigins: []string{"*"}, AllowedMethods: []string{http.MethodDelete, http.MethodGet, http.MethodHead}, AllowedHeaders: []string{"Content-*"}, ExposeHeaders: []string{"Authorization", "X-Amz-Date", "X-Amz-Conten-Sha256"}, ID: getPtr("my_extra_unique_id"), MaxAgeSeconds: getPtr(int32(-200)), }, }, } err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: cfg, }) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } return compareCorsConfig(cfg.CORSRules, res.CORSRules) }) } func DeleteBucketCors_non_existing_bucket(s *S3Conf) error { testName := "DeleteBucketCors_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketCors(ctx, &s3.DeleteBucketCorsInput{ Bucket: getPtr("non-existing-bucket"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)) }) } func DeleteBucketCors_success(s *S3Conf) error { testName := "DeleteBucketCors_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { deletebucketcors := func() error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketCors(ctx, &s3.DeleteBucketCorsInput{ Bucket: &bucket, }) cancel() return err } // should not return error when deleting unset bucket CORS err := deletebucketcors() if err != nil { return err } err = putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://origin.com"}, AllowedMethods: []string{http.MethodPost}, AllowedHeaders: []string{"X-Amz-Meta-Header"}, ExposeHeaders: []string{"Content-Disposition"}, MaxAgeSeconds: getPtr(int32(5000)), }, }, }, }) if err != nil { return err } err = deletebucketcors() if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchCORSConfiguration)) }) } func PreflightOPTIONS_non_existing_bucket(s *S3Conf) error { testName := "PreflightOPTIONS_non_existing_bucket" return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error { res, err := makeOPTIONSRequest(s, "non-existing-bucket", "http://localhost:7070", http.MethodPost, "X-Amz-Date") if err != nil { return err } return checkApiErr(res.err, s3err.GetAPIError(s3err.ErrNoSuchBucket)) }) } func PreflightOPTIONS_missing_origin(s *S3Conf) error { testName := "PreflightOPTIONS_missing_origin" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { res, err := makeOPTIONSRequest(s, bucket, "", http.MethodGet, "X-Custom-Header") if err != nil { return err } return checkApiErr(res.err, s3err.GetAPIError(s3err.ErrMissingCORSOrigin)) }) } func PreflightOPTIONS_invalid_request_method(s *S3Conf) error { testName := "PreflightOPTIONS_invalid_request_method" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, method := range []string{ // should be case sensitive, all with capital letters "get", "Get", "GEt", "geT", "post", "Post", "POSt", "posT", "put", "Put", "pUt", "puT", "head", "Head", "HEAd", "heAD", // unsupported methods "PATCH", "CONNECT", "OPTIONS", // nonsense strings "something", "invalid_method", "method", } { res, err := makeOPTIONSRequest(s, bucket, "www.my-origin.com", method, "X-Custom-Header") if err != nil { return err } if err := checkApiErr(res.err, s3err.GetInvalidCORSMethodErr(method)); err != nil { return err } } return nil }) } func PreflightOPTIONS_invalid_request_headers(s *S3Conf) error { testName := "PreflightOPTIONS_invalid_request_headers" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { for _, test := range []struct { invalidHeader string headers string }{ {"invalid header", "X-Amz-Date,X-Amz-Content-Sha256,invalid header"}, // invalid 'space' in header name {"X-Custom:Header", "Authorization,X-Custom:Header"}, // invalid char : {"X(Custom)", "Content-Length,X(Custom)"}, // invalid char () {" Bad/Header", "Content-Encoding, Bad/Header"}, // extra 'space', invalid char / {"X[Key]", "Date,X[Key]"}, // invalid char '[]' {"Bad=Name", "X-Amz-Custome-Header,Bad=Name"}, // invalid char = {`X"Quote"`, `X"Quote"`}, // invalid quote " {"NonAsciiŁ", "Content-Length,NonAsciiŁ"}, // non-ASCII character {"Emoji😀", "X-Emoji,Emoji😀"}, // emoji invalid {"bad@char", "Accept-Encoding,bad@char"}, // @ is invalid {"tab\tchar", "tab\tchar,X-Something-Valid"}, // invalid encodign \t } { res, err := makeOPTIONSRequest(s, bucket, "www.my-origin.com", http.MethodGet, test.headers) if err != nil { return err } if err := checkApiErr(res.err, s3err.GetInvalidCORSRequestHeaderErr(test.invalidHeader)); err != nil { return err } } return nil }) } func PreflightOPTIONS_unset_bucket_cors(s *S3Conf) error { testName := "PreflightOPTIONS_unset_bucket_cors" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { res, err := makeOPTIONSRequest(s, bucket, "http://example.com", http.MethodPost, "X-Amz-Date,Date") if err != nil { return err } return checkApiErr(res.err, s3err.GetAPIError(s3err.ErrCORSIsNotEnabled)) }) } func PreflightOPTIONS_access_forbidden(s *S3Conf) error { testName := "PreflightOPTIONS_access_forbidden" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://example.com", "https://example.com"}, AllowedMethods: []string{http.MethodGet}, AllowedHeaders: []string{"X-Amz-Date", "X-Amz-Content-Sha256"}, }, { AllowedOrigins: []string{"*"}, AllowedMethods: []string{http.MethodHead}, }, { AllowedOrigins: []string{"http://origin*"}, AllowedMethods: []string{http.MethodPost}, AllowedHeaders: []string{"Authorization"}, }, { AllowedOrigins: []string{"http://something.com"}, AllowedMethods: []string{http.MethodPut}, AllowedHeaders: []string{"X-Amz-*"}, }, }, }, }) if err != nil { return err } for _, test := range []struct { origin string method string headers string }{ // origin deson't match {"http://non-matching-origin.net", http.MethodGet, "X-Amz-Date"}, // method doesn't match {"http://example.com", http.MethodPut, "X-Amz-Content-Sha256"}, // header doesn't match {"http://example.com", http.MethodGet, "X-Amz-Expected-Bucket-Owner"}, // extra header {"http://example.com", http.MethodGet, "X-Amz-Date,X-Amz-Content-Sha256,Extra-Header"}, // extra header (2nd rule) {"https://any-origin.com", http.MethodHead, "X-Amz-Extra-Header"}, // origin match, method not (2nd rule) {"https://any-origin.com", http.MethodPost, ""}, // third rule: headers doesn't match {"https://origin.com", http.MethodPost, "Content-Length"}, // third rule: extra header {"https://origin.com", http.MethodPost, "Authorization,Content-Disposition"}, // third rule: origin doesn't match {"https://www.origin.com", http.MethodPost, "Authorization"}, // forth rule: header doesn't match the wildcard {"https://something.com", http.MethodPut, "Authorization"}, {"https://something.com", http.MethodPut, "X-Amz"}, {"https://something.com", http.MethodPut, "X-Amz-Date,Content-Length"}, } { res, err := makeOPTIONSRequest(s, bucket, test.origin, test.method, test.headers) if err != nil { return err } if err := checkApiErr(res.err, s3err.GetAPIError(s3err.ErrCORSForbidden)); err != nil { return err } } return nil }) } func PreflightOPTIONS_access_granted(s *S3Conf) error { testName := "PreflightOPTIONS_access_granted" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://example.com", "https://example.com"}, AllowedMethods: []string{http.MethodGet, http.MethodHead}, AllowedHeaders: []string{"X-Amz-Date", "X-Amz-Content-Sha256"}, ExposeHeaders: []string{"Content-Type", "Content-Length"}, MaxAgeSeconds: getPtr(int32(100)), }, { AllowedOrigins: []string{"*"}, AllowedMethods: []string{http.MethodHead}, AllowedHeaders: []string{"X-Amz-Meta-Something"}, }, { AllowedOrigins: []string{"something.net"}, AllowedMethods: []string{http.MethodPost, http.MethodPut}, AllowedHeaders: []string{"Authorization"}, ExposeHeaders: []string{"Content-Disposition", "Content-Encoding"}, MaxAgeSeconds: getPtr(int32(3000)), ID: getPtr("unique_id"), }, { AllowedOrigins: []string{"http://www*"}, AllowedMethods: []string{http.MethodGet}, AllowedHeaders: []string{"x-amz-server-side-encryption"}, ExposeHeaders: []string{"X-Amz-Expected-Bucket-Owner"}, MaxAgeSeconds: getPtr(int32(5000)), }, { AllowedOrigins: []string{"http://uniquie-origin.net"}, AllowedMethods: []string{http.MethodPost, http.MethodPut}, AllowedHeaders: []string{"X-Amz-*-Suffix"}, ExposeHeaders: []string{"Authorization", "Content-Type"}, MaxAgeSeconds: getPtr(int32(2000)), }, }, }, }) if err != nil { return err } varyHdr := "Origin, Access-Control-Request-Headers, Access-Control-Request-Method" for _, test := range []struct { origin string method string headers string result PreflightResult }{ // first rule matches {"http://example.com", http.MethodGet, "X-Amz-Date", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-date", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, {"http://example.com", http.MethodGet, "X-Amz-Content-Sha256", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-content-sha256", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, {"http://example.com", http.MethodHead, "", PreflightResult{"http://example.com", "GET, HEAD", "", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, {"https://example.com", http.MethodGet, "X-Amz-Date,X-Amz-Content-Sha256", PreflightResult{"https://example.com", "GET, HEAD", "x-amz-date, x-amz-content-sha256", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, // second rule matches: origin is a wildcard {"http://anything.com", http.MethodHead, "X-Amz-Meta-Something", PreflightResult{"*", "HEAD", "x-amz-meta-something", "", "", "false", varyHdr, nil}}, {"hello.com", http.MethodHead, "", PreflightResult{"*", "HEAD", "", "", "", "false", varyHdr, nil}}, // third rule matches {"something.net", http.MethodPut, "Authorization", PreflightResult{"something.net", "POST, PUT", "authorization", "Content-Disposition, Content-Encoding", "3000", "true", varyHdr, nil}}, {"something.net", http.MethodPost, "", PreflightResult{"something.net", "POST, PUT", "", "Content-Disposition, Content-Encoding", "3000", "true", varyHdr, nil}}, // forth rule matches: origin contains wildcard {"http://www.hello.world.com", http.MethodGet, "", PreflightResult{"http://www.hello.world.com", "GET", "", "X-Amz-Expected-Bucket-Owner", "5000", "true", varyHdr, nil}}, {"http://www.example.com", http.MethodGet, "x-amz-server-side-encryption", PreflightResult{"http://www.example.com", "GET", "x-amz-server-side-encryption", "X-Amz-Expected-Bucket-Owner", "5000", "true", varyHdr, nil}}, // fifth rule matches: allowed headers contains wildcard {"http://uniquie-origin.net", http.MethodPost, "X-Amz-anything-Suffix", PreflightResult{"http://uniquie-origin.net", "POST, PUT", "x-amz-anything-suffix", "Authorization, Content-Type", "2000", "true", varyHdr, nil}}, {"http://uniquie-origin.net", http.MethodPut, "X-Amz-yyy-xxx-Suffix", PreflightResult{"http://uniquie-origin.net", "POST, PUT", "x-amz-yyy-xxx-suffix", "Authorization, Content-Type", "2000", "true", varyHdr, nil}}, } { err := testOPTIONSEdnpoint(s, bucket, test.origin, test.method, test.headers, &test.result) if err != nil { return err } } return nil }) } func CORSMiddleware_invalid_method(s *S3Conf) error { testName := "CORSMiddleware_invalid_method" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://www.example.com"}, AllowedMethods: []string{http.MethodPut}, }, }, }, }) if err != nil { return err } // create a PutObject signed request req, err := createSignedReq(http.MethodPut, s.endpoint, bucket+"/my-obj", s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), map[string]string{ "Origin": "http://www.example.com", "Access-Control-Request-Method": "invalid_method", }) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } result, err := extractCORSHeaders(resp) if err != nil { return err } return checkApiErr(result.err, s3err.GetInvalidCORSMethodErr("invalid_method")) }) } func CORSMiddleware_invalid_headers(s *S3Conf) error { testName := "CORSMiddleware_invalid_headers" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://www.example.com"}, AllowedMethods: []string{http.MethodPut}, }, }, }, }) if err != nil { return err } // create a PutObject signed request req, err := createSignedReq(http.MethodPut, s.endpoint, bucket+"/my-obj", s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), map[string]string{ "Origin": "http://www.example.com", "Access-Control-Request-Headers": "invalid header", }) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } result, err := extractCORSHeaders(resp) if err != nil { return err } return checkApiErr(result.err, s3err.GetInvalidCORSRequestHeaderErr("invalid header")) }) } func CORSMiddleware_access_forbidden(s *S3Conf) error { testName := "CORSMiddleware_access_forbidden" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://example.com", "https://example.com"}, AllowedMethods: []string{http.MethodGet}, AllowedHeaders: []string{"X-Amz-Date", "X-Amz-Content-Sha256"}, }, { AllowedOrigins: []string{"*"}, AllowedMethods: []string{http.MethodHead}, }, }, }, }) if err != nil { return err } for _, test := range []struct { origin string method string headers string }{ // origin deson't match {"http://non-matching-origin.net", http.MethodGet, "X-Amz-Date"}, // method doesn't match {"http://example.com", http.MethodPut, "X-Amz-Content-Sha256"}, // header doesn't match {"http://example.com", http.MethodGet, "X-Amz-Expected-Bucket-Owner"}, // extra header {"http://example.com", http.MethodGet, "X-Amz-Date,X-Amz-Content-Sha256,Extra-Header"}, // extra header (2nd rule) {"https://any-origin.com", http.MethodHead, "X-Amz-Extra-Header"}, // origin match, method not (2nd rule) {"https://any-origin.com", http.MethodPost, ""}, } { req, err := createSignedReq(http.MethodPut, s.endpoint, bucket+"/my-obj", s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), map[string]string{ "Origin": test.origin, "Access-Control-Request-Headers": test.headers, "Access-Control-Request-Method": test.method, }) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } res, err := extractCORSHeaders(resp) if err != nil { return err } // no error expected, all the headers should be empty if err := comparePreflightResult(&PreflightResult{}, res); err != nil { return err } } return nil }) } func CORSMiddleware_access_granted(s *S3Conf) error { testName := "CORSMiddleware_access_granted" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketCors(s3client, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedOrigins: []string{"http://example.com", "https://example.com"}, AllowedMethods: []string{http.MethodGet, http.MethodHead}, AllowedHeaders: []string{"X-Amz-Date", "X-Amz-Content-Sha256"}, ExposeHeaders: []string{"Content-Type", "Content-Length"}, MaxAgeSeconds: getPtr(int32(100)), }, { AllowedOrigins: []string{"*"}, AllowedMethods: []string{http.MethodHead}, AllowedHeaders: []string{"X-Amz-Meta-Something"}, }, { AllowedOrigins: []string{"something.net"}, AllowedMethods: []string{http.MethodPost, http.MethodPut}, AllowedHeaders: []string{"Authorization"}, ExposeHeaders: []string{"Content-Disposition", "Content-Encoding"}, MaxAgeSeconds: getPtr(int32(3000)), ID: getPtr("unique_id"), }, }, }, }) if err != nil { return err } varyHdr := "Origin, Access-Control-Request-Headers, Access-Control-Request-Method" for _, test := range []struct { origin string method string headers string result PreflightResult }{ // first rule matches {"http://example.com", http.MethodGet, "X-Amz-Date", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-date, x-amz-content-sha256", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, {"http://example.com", http.MethodGet, "X-Amz-Content-Sha256", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-date, x-amz-content-sha256", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, {"http://example.com", http.MethodHead, "", PreflightResult{"http://example.com", "GET, HEAD", "", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, {"https://example.com", http.MethodGet, "X-Amz-Date,X-Amz-Content-Sha256", PreflightResult{"https://example.com", "GET, HEAD", "x-amz-date, x-amz-content-sha256", "Content-Type, Content-Length", "100", "true", varyHdr, nil}}, // second rule matches {"http://anything.com", http.MethodHead, "X-Amz-Meta-Something", PreflightResult{"*", "HEAD", "x-amz-meta-something", "", "", "false", varyHdr, nil}}, {"hello.com", http.MethodHead, "", PreflightResult{"*", "HEAD", "", "", "", "false", varyHdr, nil}}, // third rule matches {"something.net", http.MethodPut, "Authorization", PreflightResult{"something.net", "POST, PUT", "authorization", "Content-Disposition, Content-Encoding", "3000", "true", varyHdr, nil}}, {"something.net", http.MethodPost, "", PreflightResult{"something.net", "POST, PUT", "", "Content-Disposition, Content-Encoding", "3000", "true", varyHdr, nil}}, } { req, err := createSignedReq(http.MethodPut, s.endpoint, bucket+"/my-obj", s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), map[string]string{ "Origin": test.origin, "Access-Control-Request-Headers": test.headers, "Access-Control-Request-Method": test.method, }) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } res, err := extractCORSHeaders(resp) if err != nil { return err } if err := comparePreflightResult(&test.result, res); err != nil { return err } } return nil }) } // Object lock tests func PutObjectLockConfiguration_non_existing_bucket(s *S3Conf) error { testName := "PutObjectLockConfiguration_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutObjectLockConfiguration_empty_config(s *S3Conf) error { testName := "PutObjectLockConfiguration_empty_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutObjectLockConfiguration_not_enabled_on_bucket_creation(s *S3Conf) error { testName := "PutObjectLockConfiguration_not_enabled_on_bucket_creation" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 12 ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: &days, Mode: types.ObjectLockRetentionModeCompliance, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotAllowed)); err != nil { return err } return nil }) } func PutObjectLockConfiguration_invalid_status(s *S3Conf) error { testName := "PutObjectLockConfiguration_invalid_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 12 ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabled("invalid_status"), Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: &days, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutObjectLockConfiguration_invalid_mode(s *S3Conf) error { testName := "PutObjectLockConfiguration_invalid_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 12 ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: &days, Mode: types.ObjectLockRetentionMode("invalid_mode"), }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutObjectLockConfiguration_both_years_and_days(s *S3Conf) error { testName := "PutObjectLockConfiguration_both_years_and_days" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days, years int32 = 12, 24 ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: &days, Years: &years, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutObjectLockConfiguration_invalid_years_days(s *S3Conf) error { testName := "PutObjectLockConfiguration_invalid_years" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days, years int32 = -3, -5 ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: &days, Mode: types.ObjectLockRetentionModeCompliance, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidRetentionPeriod)); err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Years: &years, Mode: types.ObjectLockRetentionModeCompliance, }, }, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidRetentionPeriod)); err != nil { return err } return nil }) } func PutObjectLockConfiguration_success(s *S3Conf) error { testName := "PutObjectLockConfiguration_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, }, }) cancel() if err != nil { return err } return nil }, withLock()) } func GetObjectLockConfiguration_non_existing_bucket(s *S3Conf) error { testName := "GetObjectLockConfiguration_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetObjectLockConfiguration_unset_config(s *S3Conf) error { testName := "GetObjectLockConfiguration_unset_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)); err != nil { return err } return nil }) } func GetObjectLockConfiguration_success(s *S3Conf) error { testName := "GetObjectLockConfiguration_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 20 config := types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Mode: types.ObjectLockRetentionModeCompliance, Days: &days, }, }, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &config, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if resp.ObjectLockConfiguration == nil { return fmt.Errorf("got nil object lock configuration") } respConfig := resp.ObjectLockConfiguration if respConfig.ObjectLockEnabled != config.ObjectLockEnabled { return fmt.Errorf("expected lock status to be %v, instead got %v", config.ObjectLockEnabled, respConfig.ObjectLockEnabled) } if respConfig.Rule == nil { return fmt.Errorf("got nil object lock rule") } if respConfig.Rule.DefaultRetention == nil { return fmt.Errorf("got nil object lock default retention") } if respConfig.Rule.DefaultRetention.Days == nil { return fmt.Errorf("expected lock config days to be not nil") } if *respConfig.Rule.DefaultRetention.Days != *config.Rule.DefaultRetention.Days { return fmt.Errorf("expected lock config days to be %v, instead got %v", *config.Rule.DefaultRetention.Days, *respConfig.Rule.DefaultRetention.Days) } if respConfig.Rule.DefaultRetention.Mode != config.Rule.DefaultRetention.Mode { return fmt.Errorf("expected lock config mode to be %v, instead got %v", config.Rule.DefaultRetention.Mode, respConfig.Rule.DefaultRetention.Mode) } return nil }, withLock()) } func PutObjectRetention_non_existing_bucket(s *S3Conf) error { testName := "PutObjectRetention_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: getPtr(getBucketName()), Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutObjectRetention_non_existing_object(s *S3Conf) error { testName := "PutObjectRetention_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } date := time.Now().Add(time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: getPtr("my-obj"), Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }, withLock()) } func PutObjectRetention_unset_bucket_object_lock_config(s *S3Conf) error { testName := "PutObjectRetention_unset_bucket_object_lock_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { date := time.Now().Add(time.Hour * 3) key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &key, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { return err } return nil }) } func PutObjectRetention_disabled_bucket_object_lock_config(s *S3Conf) error { testName := "PutObjectRetention_disabled_bucket_object_lock_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{}, }) cancel() if err != nil { return err } date := time.Now().Add(time.Hour * 3) key := "my-obj" _, err = putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &key, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { return err } return nil }, withLock()) } func PutObjectRetention_expired_retain_until_date(s *S3Conf) error { testName := "PutObjectRetention_expired_retain_until_date" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } date := time.Now().Add(-time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: getPtr("my-obj"), Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate)); err != nil { return err } return nil }, withLock()) } func PutObjectRetention_invalid_mode(s *S3Conf) error { testName := "PutObjectRetention_invalid_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { date := time.Now().Add(time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: getPtr("my-obj"), Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionMode("invalid_mode"), RetainUntilDate: &date, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }, withLock()) } func PutObjectRetention_overwrite_compliance_mode(s *S3Conf) error { testName := "PutObjectRetention_overwrite_compliance_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { date := time.Now().Add(time.Hour * 3) obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &date, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func PutObjectRetention_overwrite_governance_without_bypass_specified(s *S3Conf) error { testName := "PutObjectRetention_overwrite_governance_without_bypass_specified" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { date := time.Now().Add(time.Hour * 3) obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func PutObjectRetention_overwrite_governance_with_permission(s *S3Conf) error { testName := "PutObjectRetention_overwrite_governance_with_permission" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { date := time.Now().Add(time.Hour * 3) obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } policy := genPolicyDoc("Allow", `"*"`, `["s3:BypassGovernanceRetention"]`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)) bypass := true ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, BypassGovernanceRetention: &bypass, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func PutObjectRetention_success(s *S3Conf) error { testName := "PutObjectRetention_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } date := time.Now().Add(time.Hour * 3) key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &key, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func GetObjectRetention_non_existing_bucket(s *S3Conf) error { testName := "GetObjectRetention_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: getPtr(getBucketName()), Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetObjectRetention_non_existing_object(s *S3Conf) error { testName := "GetObjectRetention_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }) } func GetObjectRetention_disabled_lock(s *S3Conf) error { testName := "GetObjectRetention_disabled_lock" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: &key, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { return err } return nil }) } func GetObjectRetention_unset_config(s *S3Conf) error { testName := "GetObjectRetention_unset_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: &key, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)); err != nil { return err } return nil }, withLock()) } func GetObjectRetention_success(s *S3Conf) error { testName := "GetObjectRetention_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } date := time.Now().Add(time.Hour * 3) retention := types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &key, Retention: &retention, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: &key, }) cancel() if err != nil { return err } if resp.Retention == nil { return fmt.Errorf("got nil object lock retention") } ret := resp.Retention if ret.Mode != retention.Mode { return fmt.Errorf("expected retention mode to be %v, instead got %v", retention.Mode, ret.Mode) } // FIXME: There's a problem with storing retainUnitDate, most probably SDK changes the date before sending // if ret.RetainUntilDate.Format(iso8601Format)[:8] != retention.RetainUntilDate.Format(iso8601Format)[:8] { // return fmt.Errorf("expected retain until date to be %v, instead got %v", retention.RetainUntilDate.Format(iso8601Format), ret.RetainUntilDate.Format(iso8601Format)) // } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func PutObjectLegalHold_non_existing_bucket(s *S3Conf) error { testName := "PutObjectLegalHold_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: getPtr(getBucketName()), Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutObjectLegalHold_non_existing_object(s *S3Conf) error { testName := "PutObjectLegalHold_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("my-obj"), LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }, withLock()) } func PutObjectLegalHold_invalid_body(s *S3Conf) error { testName := "PutObjectLegalHold_invalid_body" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutObjectLegalHold_invalid_status(s *S3Conf) error { testName := "PutObjectLegalHold_invalid_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("my-obj"), LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatus("invalid_status"), }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutObjectLegalHold_unset_bucket_object_lock_config(s *S3Conf) error { testName := "PutObjectLegalHold_unset_bucket_object_lock_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &key, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { return err } return nil }) } func PutObjectLegalHold_disabled_bucket_object_lock_config(s *S3Conf) error { testName := "PutObjectLegalHold_disabled_bucket_object_lock_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{}, }) cancel() if err != nil { return err } key := "my-obj" _, err = putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &key, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { return err } return nil }, withLock()) } func PutObjectLegalHold_success(s *S3Conf) error { testName := "PutObjectLegalHold_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &key, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func GetObjectLegalHold_non_existing_bucket(s *S3Conf) error { testName := "GetObjectLegalHold_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: getPtr(getBucketName()), Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetObjectLegalHold_non_existing_object(s *S3Conf) error { testName := "GetObjectLegalHold_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil { return err } return nil }) } func GetObjectLegalHold_disabled_lock(s *S3Conf) error { testName := "GetObjectLegalHold_disabled_lock" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: &key, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { return err } return nil }) } func GetObjectLegalHold_unset_config(s *S3Conf) error { testName := "GetObjectLegalHold_unset_config" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: &key, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)); err != nil { return err } return nil }, withLock()) } func GetObjectLegalHold_success(s *S3Conf) error { testName := "GetObjectLegalHold_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } key := "my-obj" _, err := putObjects(s3client, []string{key}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &key, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: &key, }) cancel() if err != nil { return err } if resp.LegalHold.Status != types.ObjectLockLegalHoldStatusOn { return fmt.Errorf("expected legal hold status to be On, instead got %v", resp.LegalHold.Status) } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func PutBucketAnalyticsConfiguration_not_implemented(s *S3Conf) error { testName := "PutBucketAnalyticsConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAnalyticsConfiguration(ctx, &s3.PutBucketAnalyticsConfigurationInput{ Bucket: &bucket, Id: getPtr("uniquie_id"), AnalyticsConfiguration: &types.AnalyticsConfiguration{ Id: getPtr("my-id"), StorageClassAnalysis: &types.StorageClassAnalysis{ DataExport: &types.StorageClassAnalysisDataExport{ OutputSchemaVersion: types.StorageClassAnalysisSchemaVersionV1, Destination: &types.AnalyticsExportDestination{ S3BucketDestination: &types.AnalyticsS3BucketDestination{ Bucket: &bucket, Format: types.AnalyticsS3ExportFileFormatCsv, }, }, }, }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketAnalyticsConfiguration_not_implemented(s *S3Conf) error { testName := "GetBucketAnalyticsConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketAnalyticsConfiguration(ctx, &s3.GetBucketAnalyticsConfigurationInput{ Bucket: &bucket, Id: getPtr("uniquie_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func ListBucketAnalyticsConfiguration_not_implemented(s *S3Conf) error { testName := "ListBucketAnalyticsConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListBucketAnalyticsConfigurations(ctx, &s3.ListBucketAnalyticsConfigurationsInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketAnalyticsConfiguration_not_implemented(s *S3Conf) error { testName := "DeleteBucketAnalyticsConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketAnalyticsConfiguration(ctx, &s3.DeleteBucketAnalyticsConfigurationInput{ Bucket: &bucket, Id: getPtr("uniquie_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketEncryption_not_implemented(s *S3Conf) error { testName := "PutBucketEncryption_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketEncryption(ctx, &s3.PutBucketEncryptionInput{ Bucket: &bucket, ServerSideEncryptionConfiguration: &types.ServerSideEncryptionConfiguration{ Rules: []types.ServerSideEncryptionRule{ { ApplyServerSideEncryptionByDefault: &types.ServerSideEncryptionByDefault{ SSEAlgorithm: types.ServerSideEncryptionAes256, }, }, }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketEncryption_not_implemented(s *S3Conf) error { testName := "GetBucketEncryption_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketEncryption(ctx, &s3.GetBucketEncryptionInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketEncryption_not_implemented(s *S3Conf) error { testName := "DeleteBucketEncryption_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketEncryption(ctx, &s3.DeleteBucketEncryptionInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketIntelligentTieringConfiguration_not_implemented(s *S3Conf) error { testName := "PutBucketIntelligentTieringConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { days := int32(32) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketIntelligentTieringConfiguration(ctx, &s3.PutBucketIntelligentTieringConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), IntelligentTieringConfiguration: &types.IntelligentTieringConfiguration{ Id: getPtr("my-id"), Status: types.IntelligentTieringStatusEnabled, Tierings: []types.Tiering{ { AccessTier: types.IntelligentTieringAccessTierArchiveAccess, Days: &days, }, }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketIntelligentTieringConfiguration_not_implemented(s *S3Conf) error { testName := "GetBucketIntelligentTieringConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketIntelligentTieringConfiguration(ctx, &s3.GetBucketIntelligentTieringConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func ListBucketIntelligentTieringConfiguration_not_implemented(s *S3Conf) error { testName := "ListBucketIntelligentTieringConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListBucketIntelligentTieringConfigurations(ctx, &s3.ListBucketIntelligentTieringConfigurationsInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketIntelligentTieringConfiguration_not_implemented(s *S3Conf) error { testName := "DeleteBucketIntelligentTieringConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketIntelligentTieringConfiguration(ctx, &s3.DeleteBucketIntelligentTieringConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketInventoryConfiguration_not_implemented(s *S3Conf) error { testName := "PutBucketInventoryConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { enabled := true ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketInventoryConfiguration(ctx, &s3.PutBucketInventoryConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), InventoryConfiguration: &types.InventoryConfiguration{ Destination: &types.InventoryDestination{ S3BucketDestination: &types.InventoryS3BucketDestination{ Bucket: &bucket, Format: types.InventoryFormatCsv, Encryption: &types.InventoryEncryption{ SSEKMS: &types.SSEKMS{ KeyId: getPtr("my-key-id"), }, }, }, }, Id: getPtr("my-id"), IncludedObjectVersions: types.InventoryIncludedObjectVersionsAll, IsEnabled: &enabled, Schedule: &types.InventorySchedule{ Frequency: types.InventoryFrequencyDaily, }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketInventoryConfiguration_not_implemented(s *S3Conf) error { testName := "GetBucketInventoryConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketInventoryConfiguration(ctx, &s3.GetBucketInventoryConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func ListBucketInventoryConfiguration_not_implemented(s *S3Conf) error { testName := "ListBucketInventoryConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListBucketInventoryConfigurations(ctx, &s3.ListBucketInventoryConfigurationsInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketInventoryConfiguration_not_implemented(s *S3Conf) error { testName := "DeleteBucketInventoryConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketInventoryConfiguration(ctx, &s3.DeleteBucketInventoryConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketLifecycleConfiguration_not_implemented(s *S3Conf) error { testName := "PutBucketLifecycleConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAnalyticsConfiguration(ctx, &s3.PutBucketAnalyticsConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), AnalyticsConfiguration: &types.AnalyticsConfiguration{ Id: getPtr("my-id"), StorageClassAnalysis: &types.StorageClassAnalysis{ DataExport: &types.StorageClassAnalysisDataExport{ Destination: &types.AnalyticsExportDestination{ S3BucketDestination: &types.AnalyticsS3BucketDestination{ Bucket: &bucket, Format: types.AnalyticsS3ExportFileFormatCsv, }, }, OutputSchemaVersion: types.StorageClassAnalysisSchemaVersionV1, }, }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketLifecycleConfiguration_not_implemented(s *S3Conf) error { testName := "GetBucketLifecycleConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketLifecycleConfiguration(ctx, &s3.GetBucketLifecycleConfigurationInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketLifecycle_not_implemented(s *S3Conf) error { testName := "DeleteBucketLifecycle_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketLifecycle(ctx, &s3.DeleteBucketLifecycleInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketLogging_not_implemented(s *S3Conf) error { testName := "PutBucketLogging_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketLogging(ctx, &s3.PutBucketLoggingInput{ Bucket: &bucket, BucketLoggingStatus: &types.BucketLoggingStatus{ LoggingEnabled: &types.LoggingEnabled{ TargetBucket: &bucket, TargetGrants: []types.TargetGrant{ { Grantee: &types.Grantee{ Type: types.TypeCanonicalUser, ID: getPtr("grt1"), }, Permission: types.BucketLogsPermissionRead, }, }, TargetObjectKeyFormat: &types.TargetObjectKeyFormat{ SimplePrefix: &types.SimplePrefix{}, }, TargetPrefix: getPtr("prefix"), }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketLogging_not_implemented(s *S3Conf) error { testName := "GetBucketLogging_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketLogging(ctx, &s3.GetBucketLoggingInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketRequestPayment_not_implemented(s *S3Conf) error { testName := "PutBucketRequestPayment_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketRequestPayment(ctx, &s3.PutBucketRequestPaymentInput{ Bucket: &bucket, RequestPaymentConfiguration: &types.RequestPaymentConfiguration{ Payer: types.PayerBucketOwner, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketRequestPayment_not_implemented(s *S3Conf) error { testName := "GetBucketRequestPayment_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketRequestPayment(ctx, &s3.GetBucketRequestPaymentInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketMetricsConfiguration_not_implemented(s *S3Conf) error { testName := "PutBucketMetricsConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketMetricsConfiguration(ctx, &s3.PutBucketMetricsConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), MetricsConfiguration: &types.MetricsConfiguration{ Id: getPtr("EntireBucket"), }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketMetricsConfiguration_not_implemented(s *S3Conf) error { testName := "GetBucketMetricsConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketMetricsConfiguration(ctx, &s3.GetBucketMetricsConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func ListBucketMetricsConfigurations_not_implemented(s *S3Conf) error { testName := "ListBucketMetricsConfigurations_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListBucketMetricsConfigurations(ctx, &s3.ListBucketMetricsConfigurationsInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketMetricsConfiguration_not_implemented(s *S3Conf) error { testName := "DeleteBucketMetricsConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketMetricsConfiguration(ctx, &s3.DeleteBucketMetricsConfigurationInput{ Bucket: &bucket, Id: getPtr("unique_id"), }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketReplication_not_implemented(s *S3Conf) error { testName := "PutBucketReplication_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketReplication(ctx, &s3.PutBucketReplicationInput{ Bucket: &bucket, ReplicationConfiguration: &types.ReplicationConfiguration{ Role: getPtr("arn:aws:iam::35667example:role/CrossRegionReplicationRoleForS3"), Rules: []types.ReplicationRule{ { Destination: &types.Destination{ Bucket: &bucket, AccessControlTranslation: &types.AccessControlTranslation{ Owner: types.OwnerOverrideDestination, }, Account: getPtr("grt1"), }, Status: types.ReplicationRuleStatusEnabled, }, }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketReplication_not_implemented(s *S3Conf) error { testName := "GetBucketReplication_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketReplication(ctx, &s3.GetBucketReplicationInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketReplication_not_implemented(s *S3Conf) error { testName := "DeleteBucketReplication_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketReplication(ctx, &s3.DeleteBucketReplicationInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutPublicAccessBlock_not_implemented(s *S3Conf) error { testName := "PutPublicAccessBlock_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutPublicAccessBlock(ctx, &s3.PutPublicAccessBlockInput{ Bucket: &bucket, PublicAccessBlockConfiguration: &types.PublicAccessBlockConfiguration{ BlockPublicPolicy: getPtr(true), }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetPublicAccessBlock_not_implemented(s *S3Conf) error { testName := "GetPublicAccessBlock_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetPublicAccessBlock(ctx, &s3.GetPublicAccessBlockInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeletePublicAccessBlock_not_implemented(s *S3Conf) error { testName := "DeletePublicAccessBlock_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeletePublicAccessBlock(ctx, &s3.DeletePublicAccessBlockInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketNotificationConfiguratio_not_implemented(s *S3Conf) error { testName := "PutBucketNotificationConfiguratio_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketNotificationConfiguration(ctx, &s3.PutBucketNotificationConfigurationInput{ Bucket: &bucket, NotificationConfiguration: &types.NotificationConfiguration{ EventBridgeConfiguration: &types.EventBridgeConfiguration{}, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketNotificationConfiguratio_not_implemented(s *S3Conf) error { testName := "GetBucketNotificationConfiguratio_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketNotificationConfiguration(ctx, &s3.GetBucketNotificationConfigurationInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketAccelerateConfiguration_not_implemented(s *S3Conf) error { testName := "PutBucketAccelerateConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketAccelerateConfiguration(ctx, &s3.PutBucketAccelerateConfigurationInput{ Bucket: &bucket, AccelerateConfiguration: &types.AccelerateConfiguration{ Status: types.BucketAccelerateStatusEnabled, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketAccelerateConfiguration_not_implemented(s *S3Conf) error { testName := "GetBucketAccelerateConfiguration_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketAccelerateConfiguration(ctx, &s3.GetBucketAccelerateConfigurationInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func PutBucketWebsite_not_implemented(s *S3Conf) error { testName := "PutBucketWebsite_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketWebsite(ctx, &s3.PutBucketWebsiteInput{ Bucket: &bucket, WebsiteConfiguration: &types.WebsiteConfiguration{ IndexDocument: &types.IndexDocument{ Suffix: getPtr("suffix"), }, }, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func GetBucketWebsite_not_implemented(s *S3Conf) error { testName := "GetBucketWebsite_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketWebsite(ctx, &s3.GetBucketWebsiteInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func DeleteBucketWebsite_not_implemented(s *S3Conf) error { testName := "DeleteBucketWebsite_not_implemented" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteBucketWebsite(ctx, &s3.DeleteBucketWebsiteInput{ Bucket: &bucket, }) cancel() return checkApiErr(err, s3err.GetAPIError(s3err.ErrNotImplemented)) }) } func WORMProtection_bucket_object_lock_configuration_compliance_mode(s *S3Conf) error { testName := "WORMProtection_bucket_object_lock_configuration_compliance_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 10 object := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Mode: types.ObjectLockRetentionModeCompliance, Days: &days, }, }, }, }) cancel() if err != nil { return err } _, err = putObjects(s3client, []string{object}, bucket) if err != nil { return err } if err := checkWORMProtection(s3client, bucket, object); err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_bucket_object_lock_configuration_governance_mode(s *S3Conf) error { testName := "WORMProtection_bucket_object_lock_configuration_governance_mode" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 10 object := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Mode: types.ObjectLockRetentionModeGovernance, Days: &days, }, }, }, }) cancel() if err != nil { return err } _, err = putObjects(s3client, []string{object}, bucket) if err != nil { return err } if err := checkWORMProtection(s3client, bucket, object); err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_bucket_object_lock_governance_bypass_delete(s *S3Conf) error { testName := "WORMProtection_bucket_object_lock_governance_bypass_delete" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 10 object := "my-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Mode: types.ObjectLockRetentionModeGovernance, Days: &days, }, }, }, }) cancel() if err != nil { return err } _, err = putObjects(s3client, []string{object}, bucket) if err != nil { return err } policy := genPolicyDoc("Allow", `"*"`, `["s3:BypassGovernanceRetention"]`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)) bypass := true ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &object, BypassGovernanceRetention: &bypass, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_bucket_object_lock_governance_bypass_delete_multiple(s *S3Conf) error { testName := "WORMProtection_bucket_object_lock_governance_bypass_delete_multiple" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { var days int32 = 10 obj1, obj2, obj3 := "my-obj-1", "my-obj-2", "my-obj-3" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Mode: types.ObjectLockRetentionModeGovernance, Days: &days, }, }, }, }) cancel() if err != nil { return err } _, err = putObjects(s3client, []string{obj1, obj2, obj3}, bucket) if err != nil { return err } policy := genPolicyDoc("Allow", `"*"`, `["s3:BypassGovernanceRetention"]`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)) bypass := true ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, BypassGovernanceRetention: &bypass, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ { Key: &obj1, }, { Key: &obj2, }, { Key: &obj3, }, }, }, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_object_lock_retention_compliance_locked(s *S3Conf) error { testName := "WORMProtection_object_lock_retention_compliance_locked" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { object := "my-obj" _, err := putObjects(s3client, []string{object}, bucket) if err != nil { return err } date := time.Now().Add(time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &object, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } if err := checkWORMProtection(s3client, bucket, object); err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_object_lock_retention_governance_locked(s *S3Conf) error { testName := "WORMProtection_object_lock_retention_governance_locked" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { object := "my-obj" _, err := putObjects(s3client, []string{object}, bucket) if err != nil { return err } date := time.Now().Add(time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &object, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } if err := checkWORMProtection(s3client, bucket, object); err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_object_lock_retention_governance_bypass_overwrite(s *S3Conf) error { testName := "WORMProtection_object_lock_retention_governance_bypass_overwrite" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { object := "my-obj" _, err := putObjects(s3client, []string{object}, bucket) if err != nil { return err } date := time.Now().Add(time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &object, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } policy := genPolicyDoc("Allow", `"*"`, `["s3:BypassGovernanceRetention"]`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &object, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_object_lock_retention_governance_bypass_delete(s *S3Conf) error { testName := "WORMProtection_object_lock_retention_governance_bypass_delete" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { object := "my-obj" _, err := putObjects(s3client, []string{object}, bucket) if err != nil { return err } date := time.Now().Add(time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &object, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } policy := genPolicyDoc("Allow", `"*"`, `["s3:BypassGovernanceRetention"]`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)) bypass := true ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &object, BypassGovernanceRetention: &bypass, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_object_lock_retention_governance_bypass_delete_mul(s *S3Conf) error { testName := "WORMProtection_object_lock_retention_governance_bypass_delete_mul" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs := []string{"my-obj-1", "my-obj2", "my-obj-3"} _, err := putObjects(s3client, objs, bucket) if err != nil { return err } for _, obj := range objs { o := obj date := time.Now().Add(time.Hour * 3) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &o, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &date, }, }) cancel() if err != nil { return err } } policy := genPolicyDoc("Allow", `"*"`, `["s3:BypassGovernanceRetention"]`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)) bypass := true ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, BypassGovernanceRetention: &bypass, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ { Key: &objs[0], }, { Key: &objs[1], }, { Key: &objs[2], }, }, }, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_object_lock_legal_hold_locked(s *S3Conf) error { testName := "WORMProtection_object_lock_legal_hold_locked" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := changeBucketObjectLockStatus(s3client, bucket, true); err != nil { return err } object := "my-obj" _, err := putObjects(s3client, []string{object}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &object, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err != nil { return err } _, err = putObjects(s3client, []string{object}, bucket) if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } // client sdk regression issue prevents getting full error message, // change back to below once this is fixed: // https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil { // return err // } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } func WORMProtection_root_bypass_governance_retention_delete_object(s *S3Conf) error { testName := "WORMProtection_root_bypass_governance_retention_delete_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } retDate := time.Now().Add(time.Hour * 48) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &retDate, }, }) cancel() if err != nil { return err } if err := checkWORMProtection(s3client, bucket, obj); err != nil { return err } policy := genPolicyDoc("Allow", fmt.Sprintf(`"%v"`, s.awsID), `["s3:BypassGovernanceRetention"]`, fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket)) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } bypass := true ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, BypassGovernanceRetention: &bypass, }) cancel() if err != nil { return err } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock()) } // Access control tests (with bucket ACLs and Policies) func AccessControl_default_ACL_user_access_denied(s *S3Conf) error { testName := "AccessControl_default_ACL_user_access_denied" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1}) if err != nil { return err } userClient := s.getUserClient(testuser1) _, err = putObjects(userClient, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func AccessControl_default_ACL_userplus_access_denied(s *S3Conf) error { testName := "AccessControl_default_ACL_userplus_access_denied" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuserplus}) if err != nil { return err } client := s.getUserClient(testuserplus) _, err = putObjects(client, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func AccessControl_default_ACL_admin_successful_access(s *S3Conf) error { testName := "AccessControl_default_ACL_admin_successful_access" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testadmin}) if err != nil { return err } adminClient := s.getUserClient(testadmin) _, err = putObjects(adminClient, []string{"my-obj"}, bucket) if err != nil { return err } return nil }) } func AccessControl_bucket_resource_single_action(s *S3Conf) error { testName := "AccessControl_bucket_resource_single_action" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } doc := genPolicyDoc("Allow", `["grt1"]`, `"s3:PutBucketTagging"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } testuser1Client := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = testuser1Client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = testuser1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } user2Client := s.getUserClient(testuser2) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = user2Client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func AccessControl_bucket_resource_all_action(s *S3Conf) error { testName := "AccessControl_bucket_resource_all_action" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuser1, testuser2}) if err != nil { return err } bucketResource := fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket) objectResource := fmt.Sprintf(`"arn:aws:s3:::%v/*"`, bucket) doc := genPolicyDoc("Allow", `["grt1"]`, `"s3:*"`, fmt.Sprintf(`[%v, %v]`, bucketResource, objectResource)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } testuser1Client := s.getUserClient(testuser1) _, err = putObjects(testuser1Client, []string{"my-obj"}, bucket) if err != nil { return err } user2Client := s.getUserClient(testuser2) _, err = putObjects(user2Client, []string{"my-obj"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func AccessControl_single_object_resource_actions(s *S3Conf) error { testName := "AccessControl_single_object_resource_actions" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj/nested-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } err = createUsers(s, []user{testuser1}) if err != nil { return err } doc := genPolicyDoc("Allow", `["grt1"]`, `"s3:*"`, fmt.Sprintf(`"arn:aws:s3:::%v/%v"`, bucket, obj)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } testuser1Client := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = testuser1Client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = testuser1Client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func AccessControl_multi_statement_policy(s *S3Conf) error { testName := "AccessControl_multi_statement_policy" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { policy := fmt.Sprintf(`{ "Statement": [ { "Effect": "Deny", "Principal": ["grt1"], "Action": "s3:DeleteBucket", "Resource": "arn:aws:s3:::%s" }, { "Effect": "Allow", "Principal": "grt1", "Action": "s3:*", "Resource": ["arn:aws:s3:::%s", "arn:aws:s3:::%s/*"] } ] }`, bucket, bucket, bucket) err := createUsers(s, []user{testuser1}) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.DeleteBucket(ctx, &s3.DeleteBucketInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } return nil }) } func AccessControl_bucket_ownership_to_user(s *S3Conf) error { testName := "AccessControl_bucket_ownership_to_user" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := createUsers(s, []user{testuser1}); err != nil { return err } if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := userClient.HeadBucket(ctx, &s3.HeadBucketInput{ Bucket: &bucket, }) cancel() if err != nil { return err } return nil }) } func AccessControl_root_PutBucketAcl(s *S3Conf) error { testName := "AccessControl_root_PutBucketAcl" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := createUsers(s, []user{testuser1}); err != nil { return err } if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPrivate, }) cancel() if err != nil { return err } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func AccessControl_user_PutBucketAcl_with_policy_access(s *S3Conf) error { testName := "AccessControl_user_PutBucketAcl_with_policy_access" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := createUsers(s, []user{testuser1}); err != nil { return err } policy := genPolicyDoc("Allow", fmt.Sprintf(`"%v"`, testuser1.access), `"s3:PutBucketAcl"`, fmt.Sprintf(`"arn:aws:s3:::%v"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &policy, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicRead, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: &bucket, }) cancel() if err != nil { return err } expectedGrants := []types.Grant{ { Grantee: &types.Grantee{ ID: &s.awsID, Type: types.TypeCanonicalUser, }, Permission: types.PermissionFullControl, }, { Grantee: &types.Grantee{ ID: getPtr("all-users"), Type: types.TypeGroup, }, Permission: types.PermissionRead, }, } if !compareGrants(res.Grants, expectedGrants) { return fmt.Errorf("expected the resulting grants to be %v, instead got %v", expectedGrants, res.Grants) } return nil }, withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } func AccessControl_copy_object_with_starting_slash_for_user(s *S3Conf) error { testName := "AccessControl_copy_object_with_starting_slash_for_user" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjects(s3client, []string{obj}, bucket) if err != nil { return err } if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } copySource := fmt.Sprintf("/%v/%v", bucket, obj) meta := map[string]string{ "key1": "val1", } userClient := s.getUserClient(testuser1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &obj, CopySource: ©Source, Metadata: meta, MetadataDirective: types.MetadataDirectiveReplace, }) cancel() if err != nil { return err } return nil }) } // Public bucket tests func PublicBucket_default_privet_bucket(s *S3Conf) error { testName := "PublicBucket_default_privet_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { partNumber := int32(1) for _, test := range []PublicBucketTestCase{ { Action: "ListBuckets", Call: func(ctx context.Context) error { _, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "HeadBucket", Call: func(ctx context.Context) error { _, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CreateBucket", Call: func(ctx context.Context) error { _, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: getPtr("new-bucket")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "DeleteBucket", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutBucketVersioning", Call: func(ctx context.Context) error { _, err := s3client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{ Bucket: &bucket, VersioningConfiguration: &types.VersioningConfiguration{ Status: types.BucketVersioningStatusSuspended, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketVersioning", Call: func(ctx context.Context) error { _, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{Bucket: &bucket, Policy: getPtr("{}")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "GetBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousGetBucketOwnership), }, { Action: "DeleteBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "PutBucketCors", Call: func(ctx context.Context) error { _, err := s3client.PutBucketCors(ctx, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedMethods: []string{http.MethodPut}, AllowedOrigins: []string{"my origin"}, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketCors", Call: func(ctx context.Context) error { _, err := s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketCors", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketCors(ctx, &s3.DeleteBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CreateMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCreateMp), }, { Action: "CompleteMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "AbortMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "ListMultipartUploads", Call: func(ctx context.Context) error { _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "ListParts", Call: func(ctx context.Context) error { _, err := s3client.ListParts(ctx, &s3.ListPartsInput{Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "UploadPart", Call: func(ctx context.Context) error { _, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id"), PartNumber: &partNumber, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "UploadPartCopy", Call: func(ctx context.Context) error { _, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id"), PartNumber: &partNumber, CopySource: getPtr("source-bucket/source-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObject", Call: func(ctx context.Context) error { _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "HeadObject", Call: func(ctx context.Context) error { _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObject", Call: func(ctx context.Context) error { _, err := s3client.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAcl(ctx, &s3.GetObjectAclInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectAttributes", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: getPtr("object-key"), ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CopyObject", Call: func(ctx context.Context) error { _, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: getPtr("copy-key"), CopySource: getPtr("bucket-name/object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCopyObject), }, { Action: "ListObjects", Call: func(ctx context.Context) error { _, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "ListObjectsV2", Call: func(ctx context.Context) error { _, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteObject", Call: func(ctx context.Context) error { _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteObjects", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ {Key: getPtr("object-key")}, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.PutObjectAcl(ctx, &s3.PutObjectAclInput{ Bucket: &bucket, Key: getPtr("object-key"), ACL: types.ObjectCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "ListObjectVersions", Call: func(ctx context.Context) error { _, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "RestoreObject", Call: func(ctx context.Context) error { _, err := s3client.RestoreObject(ctx, &s3.RestoreObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), RestoreRequest: &types.RestoreRequest{ Days: aws.Int32(1), GlacierJobParameters: &types.GlacierJobParameters{ Tier: types.TierStandard, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "SelectObjectContent", Call: func(ctx context.Context) error { _, err := s3client.SelectObjectContent(ctx, &s3.SelectObjectContentInput{ Bucket: &bucket, Key: getPtr("object-key"), ExpressionType: types.ExpressionTypeSql, Expression: getPtr("SELECT * FROM S3Object"), InputSerialization: &types.InputSerialization{ CSV: &types.CSVInput{}, }, OutputSerialization: &types.OutputSerialization{ CSV: &types.CSVOutput{}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "GetBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: aws.Int32(1), Mode: types.ObjectLockRetentionModeCompliance, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: getPtr("object-key"), Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: aws.Time(time.Now().Add(24 * time.Hour)), }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("object-key"), LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) err := test.Call(ctx) cancel() if err == nil && test.ExpectedErr != nil { return fmt.Errorf("%v: expected err %v, instead got successful response", test.Action, test.ExpectedErr) } if err != nil { if test.ExpectedErr == nil { return fmt.Errorf("%v: expected no error, instead got %v", test.Action, err) } apiErr, ok := test.ExpectedErr.(s3err.APIError) if !ok { return fmt.Errorf("invalid error type provided in the test, expected s3err.APIError") } // The head requests doesn't have request body, thus only the status needs to be checked if test.Action == "HeadBucket" || test.Action == "HeadObject" { if err := checkSdkApiErr(err, http.StatusText(apiErr.HTTPStatusCode)); err != nil { return err } continue } if err := checkApiErr(err, apiErr); err != nil { return err } } } return nil }, withAnonymousClient()) } func PublicBucket_public_bucket_policy(s *S3Conf) error { testName := "PublicBucket_public_bucket_policy" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { rootClient := s.GetClient() // Grant public access to the bucket for bucket operations err := grantPublicBucketPolicy(rootClient, bucket, policyTypeBucket) if err != nil { return err } partNumber := int32(1) for _, test := range []PublicBucketTestCase{ { Action: "ListBuckets", Call: func(ctx context.Context) error { _, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "HeadBucket", Call: func(ctx context.Context) error { _, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, { Action: "GetBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, { Action: "CreateBucket", Call: func(ctx context.Context) error { _, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: getPtr("new-bucket")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{Bucket: &bucket, Policy: getPtr("{}")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrMethodNotAllowed), }, { Action: "GetBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrMethodNotAllowed), }, { Action: "DeleteBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrMethodNotAllowed), }, { Action: "PutBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "GetBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousGetBucketOwnership), }, { Action: "DeleteBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "PutBucketCors", Call: func(ctx context.Context) error { _, err := s3client.PutBucketCors(ctx, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedMethods: []string{http.MethodPut}, AllowedOrigins: []string{"my origin"}, }, }, }, }) return err }, ExpectedErr: nil, }, { Action: "GetBucketCors", Call: func(ctx context.Context) error { _, err := s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, { Action: "DeleteBucketCors", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketCors(ctx, &s3.DeleteBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, { Action: "CreateMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCreateMp), }, { Action: "CompleteMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "AbortMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "ListMultipartUploads", Call: func(ctx context.Context) error { _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, { Action: "ListParts", Call: func(ctx context.Context) error { _, err := s3client.ListParts(ctx, &s3.ListPartsInput{Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "UploadPart", Call: func(ctx context.Context) error { _, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id"), PartNumber: &partNumber, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "UploadPartCopy", Call: func(ctx context.Context) error { _, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id"), PartNumber: &partNumber, CopySource: getPtr("source-bucket/source-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObject", Call: func(ctx context.Context) error { _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "HeadObject", Call: func(ctx context.Context) error { _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObject", Call: func(ctx context.Context) error { _, err := s3client.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAcl(ctx, &s3.GetObjectAclInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectAttributes", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: getPtr("object-key"), ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CopyObject", Call: func(ctx context.Context) error { _, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: getPtr("copy-key"), CopySource: getPtr("bucket-name/object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCopyObject), }, { Action: "ListObjects", Call: func(ctx context.Context) error { _, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "ListObjectsV2", Call: func(ctx context.Context) error { _, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "DeleteObject", Call: func(ctx context.Context) error { _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteObjects", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ {Key: getPtr("object-key")}, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.PutObjectAcl(ctx, &s3.PutObjectAclInput{ Bucket: &bucket, Key: getPtr("object-key"), ACL: types.ObjectCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "ListObjectVersions", Call: func(ctx context.Context) error { _, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "RestoreObject", Call: func(ctx context.Context) error { _, err := s3client.RestoreObject(ctx, &s3.RestoreObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), RestoreRequest: &types.RestoreRequest{ Days: aws.Int32(1), GlacierJobParameters: &types.GlacierJobParameters{ Tier: types.TierStandard, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "SelectObjectContent", Call: func(ctx context.Context) error { _, err := s3client.SelectObjectContent(ctx, &s3.SelectObjectContentInput{ Bucket: &bucket, Key: getPtr("object-key"), ExpressionType: types.ExpressionTypeSql, Expression: getPtr("SELECT * FROM S3Object"), InputSerialization: &types.InputSerialization{ CSV: &types.CSVInput{}, }, OutputSerialization: &types.OutputSerialization{ CSV: &types.CSVOutput{}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: nil, }, { Action: "GetBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "DeleteBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "GetObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: aws.Int32(1), Mode: types.ObjectLockRetentionModeGovernance, }, }, }, }) return err }, ExpectedErr: nil, }, { Action: "GetObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "PutObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: getPtr("object-key"), Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: aws.Time(time.Now().Add(24 * time.Hour)), }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("object-key"), LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucket", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) err := test.Call(ctx) cancel() if err == nil && test.ExpectedErr != nil { return fmt.Errorf("%v: expected err %v, instead got successful response", test.Action, test.ExpectedErr) } if err != nil { if test.ExpectedErr == nil { return fmt.Errorf("%v: expected no error, instead got %v", test.Action, err) } apiErr, ok := test.ExpectedErr.(s3err.APIError) if !ok { return fmt.Errorf("invalid error type provided in the test, expected s3err.APIError") } // The head requests doesn't have request body, thus only the status needs to be checked if test.Action == "HeadBucket" || test.Action == "HeadObject" { if err := checkSdkApiErr(err, http.StatusText(apiErr.HTTPStatusCode)); err != nil { return err } continue } if err := checkApiErr(err, apiErr); err != nil { return err } } } return nil }, withAnonymousClient(), withLock(), withSkipTearDown()) } func PublicBucket_public_object_policy(s *S3Conf) error { testName := "PublicBucket_public_object_policy" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { rootClient := s.GetClient() // Grant public access to the bucket for bucket operations err := grantPublicBucketPolicy(rootClient, bucket, policyTypeObject) if err != nil { return err } mpKey := "my-mp" mp1, err := createMp(rootClient, bucket, mpKey) if err != nil { return err } mp2, err := createMp(rootClient, bucket, mpKey) if err != nil { return err } partNumber := int32(1) var partEtag *string for _, test := range []PublicBucketTestCase{ { Action: "ListBuckets", Call: func(ctx context.Context) error { _, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "HeadBucket", Call: func(ctx context.Context) error { _, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CreateBucket", Call: func(ctx context.Context) error { _, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: getPtr("new-bucket")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{Bucket: &bucket, Policy: getPtr("{}")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "GetBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousGetBucketOwnership), }, { Action: "DeleteBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "PutBucketCors", Call: func(ctx context.Context) error { _, err := s3client.PutBucketCors(ctx, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedMethods: []string{http.MethodPut}, AllowedOrigins: []string{"my origin"}, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketCors", Call: func(ctx context.Context) error { _, err := s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketCors", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketCors(ctx, &s3.DeleteBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CreateMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCreateMp), }, { Action: "AbortMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ Bucket: &bucket, Key: &mpKey, UploadId: mp1.UploadId, }) return err }, ExpectedErr: nil, }, { Action: "ListMultipartUploads", Call: func(ctx context.Context) error { _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "ListParts", Call: func(ctx context.Context) error { _, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &bucket, Key: &mpKey, UploadId: mp2.UploadId, }) return err }, ExpectedErr: nil, }, { Action: "UploadPart", Call: func(ctx context.Context) error { partBuffer := make([]byte, 5*1024*1024) rand.Read(partBuffer) res, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &mpKey, UploadId: mp2.UploadId, PartNumber: &partNumber, Body: bytes.NewReader(partBuffer), }) if err == nil { partEtag = res.ETag } return err }, ExpectedErr: nil, }, //FIXME: should be fixed after implementing the source bucket public access check // return AccessDenied for now { Action: "UploadPartCopy", Call: func(ctx context.Context) error { _, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: &mpKey, UploadId: mp2.UploadId, PartNumber: &partNumber, CopySource: getPtr("source-bucket/source-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CompleteMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &mpKey, UploadId: mp2.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: []types.CompletedPart{ { ETag: partEtag, PartNumber: &partNumber, }, }, }, }) return err }, ExpectedErr: nil, }, { Action: "PutObject", Call: func(ctx context.Context) error { _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &mpKey, }) return err }, ExpectedErr: nil, }, { Action: "HeadObject", Call: func(ctx context.Context) error { _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{Bucket: &bucket, Key: &mpKey}) return err }, ExpectedErr: nil, }, { Action: "GetObject", Call: func(ctx context.Context) error { _, err := s3client.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &mpKey}) return err }, ExpectedErr: nil, }, { Action: "GetObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAcl(ctx, &s3.GetObjectAclInput{ Bucket: &bucket, Key: &mpKey, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrNotImplemented), }, { Action: "GetObjectAttributes", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &mpKey, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) return err }, ExpectedErr: nil, }, { Action: "CopyObject", Call: func(ctx context.Context) error { _, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: getPtr("copy-key"), CopySource: getPtr("bucket-name/object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCopyObject), }, { Action: "ListObjects", Call: func(ctx context.Context) error { _, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "ListObjectsV2", Call: func(ctx context.Context) error { _, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, // FIXME: should be fixed with https://github.com/versity/versitygw/issues/1327 { Action: "DeleteObjects", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ {Key: getPtr("object-key")}, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.PutObjectAcl(ctx, &s3.PutObjectAclInput{ Bucket: &bucket, Key: getPtr("object-key"), ACL: types.ObjectCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "ListObjectVersions", Call: func(ctx context.Context) error { _, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "RestoreObject", Call: func(ctx context.Context) error { _, err := s3client.RestoreObject(ctx, &s3.RestoreObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), RestoreRequest: &types.RestoreRequest{ Days: aws.Int32(1), GlacierJobParameters: &types.GlacierJobParameters{ Tier: types.TierStandard, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrNotImplemented), }, { Action: "SelectObjectContent", Call: func(ctx context.Context) error { _, err := s3client.SelectObjectContent(ctx, &s3.SelectObjectContentInput{ Bucket: &bucket, Key: getPtr("object-key"), ExpressionType: types.ExpressionTypeSql, Expression: getPtr("SELECT * FROM S3Object"), InputSerialization: &types.InputSerialization{ CSV: &types.CSVInput{}, }, OutputSerialization: &types.OutputSerialization{ CSV: &types.CSVOutput{}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: &mpKey, Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: nil, }, { Action: "GetObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: &mpKey, }) return err }, ExpectedErr: nil, }, { Action: "DeleteObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{ Bucket: &bucket, Key: &mpKey, }) return err }, ExpectedErr: nil, }, { Action: "PutObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: aws.Int32(1), Mode: types.ObjectLockRetentionModeGovernance, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &mpKey, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: aws.Time(time.Now().Add(24 * time.Hour)), }, }) return err }, ExpectedErr: nil, }, { Action: "GetObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: &mpKey, }) return err }, ExpectedErr: nil, }, { Action: "PutObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &mpKey, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOff, }, }) return err }, ExpectedErr: nil, }, { Action: "GetObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: &mpKey, }) return err }, ExpectedErr: nil, }, { Action: "DeleteObject", Call: func(ctx context.Context) error { _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &mpKey, BypassGovernanceRetention: getBoolPtr(true), }) return err }, ExpectedErr: nil, }, { Action: "DeleteBucket", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) err := test.Call(ctx) cancel() if err == nil && test.ExpectedErr != nil { return fmt.Errorf("%v: expected err %v, instead got successful response", test.Action, test.ExpectedErr) } if err != nil { if test.ExpectedErr == nil { return fmt.Errorf("%v: expected no error, instead got %v", test.Action, err) } apiErr, ok := test.ExpectedErr.(s3err.APIError) if !ok { return fmt.Errorf("invalid error type provided in the test, expected s3err.APIError") } // The head requests doesn't have request body, thus only the status needs to be checked if test.Action == "HeadBucket" || test.Action == "HeadObject" { if err := checkSdkApiErr(err, http.StatusText(apiErr.HTTPStatusCode)); err != nil { return err } continue } if err := checkApiErr(err, apiErr); err != nil { return err } } } // disable object lock on the bucket err = changeBucketObjectLockStatus(rootClient, bucket, false) if err != nil { return err } return nil }, withAnonymousClient(), withLock()) } func PublicBucket_public_acl(s *S3Conf) error { testName := "PublicBucket_public_acl" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { partNumber := int32(1) var etag *string obj := "my-obj" // grant public access with acl rootClient := s.GetClient() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := rootClient.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicReadWrite, }) cancel() if err != nil { return err } mp, err := createMp(rootClient, bucket, obj) if err != nil { return err } for _, test := range []PublicBucketTestCase{ { Action: "ListBuckets", Call: func(ctx context.Context) error { _, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "HeadBucket", Call: func(ctx context.Context) error { _, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, { Action: "GetBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CreateBucket", Call: func(ctx context.Context) error { _, err := s3client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: getPtr("new-bucket")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "PutBucketAcl", Call: func(ctx context.Context) error { _, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{ Bucket: &bucket, ACL: types.BucketCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "DeleteBucket", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, //FIXME: implement tests for versioning enabled gateway // { // Action: "PutBucketVersioning", // Call: func(ctx context.Context) error { // _, err := s3client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{ // Bucket: &bucket, // VersioningConfiguration: &types.VersioningConfiguration{ // Status: types.BucketVersioningStatusSuspended, // }, // }) // return err // }, // ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), // }, // { // Action: "GetBucketVersioning", // Call: func(ctx context.Context) error { // _, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{Bucket: &bucket}) // return err // }, // ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), // }, { Action: "PutBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{Bucket: &bucket, Policy: getPtr("{}")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketPolicy", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{ Bucket: &bucket, OwnershipControls: &types.OwnershipControls{ Rules: []types.OwnershipControlsRule{ { ObjectOwnership: types.ObjectOwnershipBucketOwnerEnforced, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "GetBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousGetBucketOwnership), }, { Action: "DeleteBucketOwnershipControls", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousPutBucketOwnership), }, { Action: "PutBucketCors", Call: func(ctx context.Context) error { _, err := s3client.PutBucketCors(ctx, &s3.PutBucketCorsInput{ Bucket: &bucket, CORSConfiguration: &types.CORSConfiguration{ CORSRules: []types.CORSRule{ { AllowedMethods: []string{http.MethodPut}, AllowedOrigins: []string{"my origin"}, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetBucketCors", Call: func(ctx context.Context) error { _, err := s3client.GetBucketCors(ctx, &s3.GetBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketCors", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketCors(ctx, &s3.DeleteBucketCorsInput{Bucket: &bucket}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CreateMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{Bucket: &bucket, Key: getPtr("object-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCreateMp), }, { Action: "AbortMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "ListMultipartUploads", Call: func(ctx context.Context) error { _, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{Bucket: &bucket}) return err }, ExpectedErr: nil, }, { Action: "ListParts", Call: func(ctx context.Context) error { _, err := s3client.ListParts(ctx, &s3.ListPartsInput{Bucket: &bucket, Key: getPtr("object-key"), UploadId: getPtr("upload-id")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "UploadPart", Call: func(ctx context.Context) error { partBuffer := make([]byte, 5*1024*1024) rand.Read(partBuffer) res, err := s3client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, PartNumber: &partNumber, Body: bytes.NewReader(partBuffer), }) if err == nil { etag = res.ETag } return err }, ExpectedErr: nil, }, //FIXME: should be fixed after implementing the source bucket public access check // return AccessDenied for now { Action: "UploadPartCopy", Call: func(ctx context.Context) error { _, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, PartNumber: &partNumber, CopySource: getPtr("source-bucket/source-key")}) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "CompleteMultipartUpload", Call: func(ctx context.Context) error { _, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: mp.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: []types.CompletedPart{ { ETag: etag, PartNumber: &partNumber, }, }, }, }) return err }, ExpectedErr: nil, }, { Action: "PutObject", Call: func(ctx context.Context) error { _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }) return err }, ExpectedErr: nil, }, { Action: "HeadObject", Call: func(ctx context.Context) error { _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{Bucket: &bucket, Key: &obj}) return err }, ExpectedErr: nil, }, { Action: "GetObject", Call: func(ctx context.Context) error { _, err := s3client.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &obj}) return err }, ExpectedErr: nil, }, { Action: "GetObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAcl(ctx, &s3.GetObjectAclInput{ Bucket: &bucket, Key: &obj, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectAttributes", Call: func(ctx context.Context) error { _, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) return err }, ExpectedErr: nil, }, { Action: "CopyObject", Call: func(ctx context.Context) error { _, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: getPtr("copy-key"), CopySource: getPtr("bucket-name/object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousCopyObject), }, { Action: "ListObjects", Call: func(ctx context.Context) error { _, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "ListObjectsV2", Call: func(ctx context.Context) error { _, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "DeleteObject", Call: func(ctx context.Context) error { _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) return err }, ExpectedErr: nil, }, // FIXME: should be fixed with https://github.com/versity/versitygw/issues/1327 { Action: "DeleteObjects", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ {Key: getPtr("object-key")}, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectAcl", Call: func(ctx context.Context) error { _, err := s3client.PutObjectAcl(ctx, &s3.PutObjectAclInput{ Bucket: &bucket, Key: getPtr("object-key"), ACL: types.ObjectCannedACLPublicRead, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "ListObjectVersions", Call: func(ctx context.Context) error { _, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) return err }, ExpectedErr: nil, }, { Action: "RestoreObject", Call: func(ctx context.Context) error { _, err := s3client.RestoreObject(ctx, &s3.RestoreObjectInput{ Bucket: &bucket, Key: getPtr("object-key"), RestoreRequest: &types.RestoreRequest{ Days: aws.Int32(1), GlacierJobParameters: &types.GlacierJobParameters{ Tier: types.TierStandard, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "SelectObjectContent", Call: func(ctx context.Context) error { _, err := s3client.SelectObjectContent(ctx, &s3.SelectObjectContentInput{ Bucket: &bucket, Key: getPtr("object-key"), ExpressionType: types.ExpressionTypeSql, Expression: getPtr("SELECT * FROM S3Object"), InputSerialization: &types.InputSerialization{ CSV: &types.CSVInput{}, }, OutputSerialization: &types.OutputSerialization{ CSV: &types.CSVOutput{}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousRequest), }, { Action: "GetBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{ Bucket: &bucket, Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteBucketTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteBucketTagging(ctx, &s3.DeleteBucketTaggingInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), Tagging: &types.Tagging{ TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}}, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "DeleteObjectTagging", Call: func(ctx context.Context) error { _, err := s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{ Bucket: &bucket, ObjectLockConfiguration: &types.ObjectLockConfiguration{ ObjectLockEnabled: types.ObjectLockEnabledEnabled, Rule: &types.ObjectLockRule{ DefaultRetention: &types.DefaultRetention{ Days: aws.Int32(1), Mode: types.ObjectLockRetentionModeCompliance, }, }, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectLockConfiguration", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{ Bucket: &bucket, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: getPtr("object-key"), Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: aws.Time(time.Now().Add(24 * time.Hour)), }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectRetention", Call: func(ctx context.Context) error { _, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "PutObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("object-key"), LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, { Action: "GetObjectLegalHold", Call: func(ctx context.Context) error { _, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: getPtr("object-key"), }) return err }, ExpectedErr: s3err.GetAPIError(s3err.ErrAccessDenied), }, } { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) err := test.Call(ctx) cancel() if err == nil && test.ExpectedErr != nil { return fmt.Errorf("%v: expected err %v, instead got successful response", test.Action, test.ExpectedErr) } if err != nil { if test.ExpectedErr == nil { return fmt.Errorf("%v: expected no error, instead got %v", test.Action, err) } apiErr, ok := test.ExpectedErr.(s3err.APIError) if !ok { return fmt.Errorf("invalid error type provided in the test, expected s3err.APIError") } // The head requests doesn't have request body, thus only the status needs to be checked if test.Action == "HeadBucket" || test.Action == "HeadObject" { if err := checkSdkApiErr(err, http.StatusText(apiErr.HTTPStatusCode)); err != nil { return fmt.Errorf("%v: %w", test.Action, err) } continue } if err := checkApiErr(err, apiErr); err != nil { return fmt.Errorf("%v: %w", test.Action, err) } } } return nil }, withAnonymousClient(), withOwnership(types.ObjectOwnershipBucketOwnerPreferred)) } // IAM related tests // multi-user iam tests func IAM_user_access_denied(s *S3Conf) error { testName := "IAM_user_access_denied" runF(testName) err := createUsers(s, []user{testuser1}) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } out, err := execCommand(s.getAdminCommand("-a", testuser1.access, "-s", testuser1.secret, "-er", s.endpoint, "delete-user", "-a", "random_access")...) if err == nil { failF("%v: expected cmd error", testName) return fmt.Errorf("%v: expected cmd error", testName) } if !strings.Contains(string(out), s3err.GetAPIError(s3err.ErrAdminAccessDenied).Code) { failF("%v: expected response error message to be %v, instead got %s", testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out) return fmt.Errorf("%v: expected response error message to be %v, instead got %s", testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out) } passF(testName) return nil } func IAM_userplus_access_denied(s *S3Conf) error { testName := "IAM_userplus_access_denied" runF(testName) err := createUsers(s, []user{testuserplus}) if err != nil { failF("%v: %v", testName, err) return fmt.Errorf("%v: %w", testName, err) } out, err := execCommand(s.getAdminCommand("-a", testuserplus.access, "-s", testuserplus.secret, "-er", s.endpoint, "delete-user", "-a", "random_access")...) if err == nil { failF("%v: expected cmd error", testName) return fmt.Errorf("%v: expected cmd error", testName) } if !strings.Contains(string(out), s3err.GetAPIError(s3err.ErrAdminAccessDenied).Code) { failF("%v: expected response error message to be %v, instead got %s", testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out) return fmt.Errorf("%v: expected response error message to be %v, instead got %s", testName, s3err.GetAPIError(s3err.ErrAdminAccessDenied).Error(), out) } passF(testName) return nil } func IAM_userplus_CreateBucket(s *S3Conf) error { testName := "IAM_userplus_CreateBucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testuserplus}) if err != nil { return err } cfg := *s cfg.awsID = testuserplus.access cfg.awsSecret = testuserplus.secret bckt := getBucketName() err = setup(&cfg, bckt) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bckt}) cancel() if err != nil { return err } err = teardown(&cfg, bckt) if err != nil { return err } return nil }) } func IAM_admin_ChangeBucketOwner(s *S3Conf) error { testName := "IAM_admin_ChangeBucketOwner" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := createUsers(s, []user{testadmin, testuser1}) if err != nil { return err } err = changeBucketsOwner(s, []string{bucket}, testuser1.access) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{Bucket: &bucket}) cancel() if err != nil { return err } if getString(resp.Owner.ID) != testuser1.access { return fmt.Errorf("expected the bucket owner to be %v, instead got %v", testuser1.access, getString(resp.Owner.ID)) } return nil }) } func IAM_ChangeBucketOwner_back_to_root(s *S3Conf) error { testName := "IAM_ChangeBucketOwner_back_to_root" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { if err := createUsers(s, []user{testuser1}); err != nil { return err } // Change the bucket ownership to a random user if err := changeBucketsOwner(s, []string{bucket}, testuser1.access); err != nil { return err } // Change the bucket ownership back to the root user if err := changeBucketsOwner(s, []string{bucket}, s.awsID); err != nil { return err } return nil }) } func IAM_ListBuckets(s *S3Conf) error { testName := "IAM_ListBuckets" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := listBuckets(s) if err != nil { return err } return nil }) } // Posix related tests func PutObject_overwrite_dir_obj(s *S3Conf) error { testName := "PutObject_overwrite_dir_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo/", "foo"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrExistingObjectIsDirectory)); err != nil { return err } return nil }) } func PutObject_overwrite_file_obj(s *S3Conf) error { testName := "PutObject_overwrite_file_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo", "foo/"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectParentIsFile)); err != nil { return err } return nil }) } func PutObject_overwrite_file_obj_with_nested_obj(s *S3Conf) error { testName := "PutObject_overwrite_file_obj_with_nested_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo", "foo/bar"}, bucket) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectParentIsFile)); err != nil { return err } return nil }) } func PutObject_dir_obj_with_data(s *S3Conf) error { testName := "PutObject_dir_obj_with_data" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjectWithData(int64(20), &s3.PutObjectInput{ Bucket: &bucket, Key: getPtr("obj/"), }, s3client) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDirectoryObjectContainsData)); err != nil { return err } return nil }) } func PutObject_with_slashes(s *S3Conf) error { testName := "PutObject_with_slashes" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { objs, err := putObjects(s3client, []string{ "/obj", "foo//bar", "/foo/baz/bar", "////////bar", "foo//////quxx", }, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ Bucket: &bucket, }) cancel() if err != nil { return err } // it's en expected bahvior in posix to normalize the object pahts, // by removing multiple slashes normalizedObjs := []string{ "bar", "foo/bar", "foo/baz/bar", "foo/quxx", "obj", } for i := range objs { objs[i].Key = &normalizedObjs[i] } if !compareObjects(objs, res.Contents) { return fmt.Errorf("expected the objects to be %vß, instead got %v", objStrings(objs), objStrings(res.Contents)) } return nil }) } func CreateMultipartUpload_dir_obj(s *S3Conf) error { testName := "CreateMultipartUpload_dir_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := createMp(s3client, bucket, "obj/") if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDirectoryObjectContainsData)); err != nil { return err } return nil }) } func PutObject_name_too_long(s *S3Conf) error { testName := "PutObject_name_too_long" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { key := genRandString(300) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &key, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrKeyTooLong)); err != nil { return err } return nil }) } func HeadObject_name_too_long(s *S3Conf) error { testName := "HeadObject_name_too_long" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: getPtr(genRandString(300)), }) cancel() if err := checkSdkApiErr(err, "BadRequest"); err != nil { return err } return nil }) } func DeleteObject_name_too_long(s *S3Conf) error { testName := "DeleteObject_name_too_long" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: getPtr(genRandString(300)), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrKeyTooLong)); err != nil { return err } return nil }) } func CopyObject_overwrite_same_dir_object(s *S3Conf) error { testName := "CopyObject_overwrite_same_dir_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo/"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: getPtr("foo"), CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, "foo/")), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrExistingObjectIsDirectory)); err != nil { return err } return nil }) } func CopyObject_overwrite_same_file_object(s *S3Conf) error { testName := "CopyObject_overwrite_same_file_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { _, err := putObjects(s3client, []string{"foo"}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: getPtr("foo/"), CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, "foo")), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectParentIsFile)); err != nil { return err } return nil }) } // Versioning tests func PutBucketVersioning_non_existing_bucket(s *S3Conf) error { testName := "PutBucketVersioning_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketVersioningStatus(s3client, getBucketName(), types.BucketVersioningStatusSuspended) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func PutBucketVersioning_invalid_status(s *S3Conf) error { testName := "PutBucketVersioning_invalid_status" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatus("invalid_status")) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML)); err != nil { return err } return nil }) } func PutBucketVersioning_success_enabled(s *S3Conf) error { testName := "PutBucketVersioning_success_enabled" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } return nil }) } func PutBucketVersioning_success_suspended(s *S3Conf) error { testName := "PutBucketVersioning_success_suspended" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended) if err != nil { return err } return nil }) } func GetBucketVersioning_non_existing_bucket(s *S3Conf) error { testName := "GetBucketVersioning_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }) } func GetBucketVersioning_empty_response(s *S3Conf) error { testName := "GetBucketVersioning_empty_response" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if res.Status != "" { return fmt.Errorf("expected empty versioning status, instead got %v", res.Status) } if res.MFADelete != "" { return fmt.Errorf("expected empty mfa delete status, instead got %v", res.MFADelete) } return nil }) } func GetBucketVersioning_success(s *S3Conf) error { testName := "GetBucketVersioning_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if res.Status != types.BucketVersioningStatusEnabled { return fmt.Errorf("expected bucket versioning status to be %v, instead got %v", types.BucketVersioningStatusEnabled, res.Status) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_DeleteBucket_not_empty(s *S3Conf) error { testName := "Versioning_DeleteBucket_not_empty" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := createObjVersions(s3client, bucket, obj, 2) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersionedBucketNotEmpty)); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_PutObject_suspended_null_versionId_obj(s *S3Conf) error { testName := "Versioning_PutObject_suspended_null_versionId_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := putObjectWithData(1222, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } if getString(out.res.VersionId) != nullVersionId { return fmt.Errorf("expected the uploaded object versionId to be %v, instead got %v", nullVersionId, getString(out.res.VersionId)) } return nil }, withVersioning(types.BucketVersioningStatusSuspended)) } func Versioning_PutObject_null_versionId_obj(s *S3Conf) error { testName := "Versioning_PutObject_null_versionId_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, lgth := "my-obj", int64(1234) out, err := putObjectWithData(lgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } // Enable bucket versioning err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } versions, err := createObjVersions(s3client, bucket, obj, 4) if err != nil { return err } versions = append(versions, types.ObjectVersion{ ETag: out.res.ETag, IsLatest: getBoolPtr(false), Key: &obj, Size: &lgth, VersionId: &nullVersionId, StorageClass: types.ObjectVersionStorageClassStandard, }) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the listed versions to be %v, instead got %v", versions, res.Versions) } return nil }) } func Versioning_PutObject_overwrite_null_versionId_obj(s *S3Conf) error { testName := "Versioning_PutObject_overwrite_null_versionId_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjectWithData(int64(1233), &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } // Enable bucket versioning err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } versions, err := createObjVersions(s3client, bucket, obj, 4) if err != nil { return err } // Set bucket versioning status to Suspended err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended) if err != nil { return err } lgth := int64(3200) out, err := putObjectWithData(lgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } if getString(out.res.VersionId) != nullVersionId { return fmt.Errorf("expected the uploaded object versionId to be %v, insted got %v", nullVersionId, getString(out.res.VersionId)) } versions[0].IsLatest = getBoolPtr(false) versions = append([]types.ObjectVersion{ { ETag: out.res.ETag, IsLatest: getBoolPtr(true), Key: &obj, Size: &lgth, VersionId: &nullVersionId, StorageClass: types.ObjectVersionStorageClassStandard, ChecksumType: out.res.ChecksumType, }, }, versions...) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the listed versions to be %v, instead got %v", versions, res.Versions) } return nil }) } func Versioning_PutObject_success(s *S3Conf) error { testName := "Versioning_PutObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: getPtr("my-obj"), }) cancel() if err != nil { return err } if res.VersionId == nil || *res.VersionId == "" { return fmt.Errorf("expected the versionId to be returned") } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_CopyObject_success(s *S3Conf) error { testName := "Versioning_CopyObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dstObj := "dst-obj" srcBucket, srcObj := getBucketName(), "src-obj" if err := setup(s, srcBucket); err != nil { return err } dstObjVersions, err := createObjVersions(s3client, bucket, dstObj, 1) if err != nil { return err } srcObjLen := int64(2345) _, err = putObjectWithData(srcObjLen, &s3.PutObjectInput{ Bucket: &srcBucket, Key: &srcObj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v", srcBucket, srcObj)), }) cancel() if err != nil { return err } if err := teardown(s, srcBucket); err != nil { return err } if out.VersionId == nil || *out.VersionId == "" { return fmt.Errorf("expected non empty versionId in the result") } dstObjVersions[0].IsLatest = getBoolPtr(false) versions := append([]types.ObjectVersion{ { ETag: out.CopyObjectResult.ETag, IsLatest: getBoolPtr(true), Key: &dstObj, Size: &srcObjLen, VersionId: out.VersionId, StorageClass: types.ObjectVersionStorageClassStandard, ChecksumType: out.CopyObjectResult.ChecksumType, }, }, dstObjVersions...) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the resulting versions to be %v, instead got %v", versions, res.Versions) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_CopyObject_non_existing_version_id(s *S3Conf) error { testName := "Versioning_CopyObject_non_existing_version_id" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dstBucket, dstObj := getBucketName(), "my-obj" srcObj := "my-obj" if err := setup(s, dstBucket); err != nil { return err } _, err := createObjVersions(s3client, bucket, srcObj, 1) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &dstBucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=invalid_versionId", bucket, srcObj)), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil { return err } if err := teardown(s, dstBucket); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_CopyObject_from_an_object_version(s *S3Conf) error { testName := "Versioning_CopyObject_from_an_object_version" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcBucket, srcObj, dstObj := getBucketName(), "my-obj", "my-dst-obj" if err := setup(s, srcBucket, withVersioning(types.BucketVersioningStatusEnabled)); err != nil { return err } srcObjVersions, err := createObjVersions(s3client, srcBucket, srcObj, 1) if err != nil { return err } srcObjVersion := srcObjVersions[0] ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=%v", srcBucket, srcObj, *srcObjVersion.VersionId)), }) cancel() if err != nil { return err } if err := teardown(s, srcBucket); err != nil { return err } if out.VersionId == nil || *out.VersionId == "" { return fmt.Errorf("expected non empty versionId") } if out.CopySourceVersionId == nil { return fmt.Errorf("expected non nil CopySourceVersionId") } if *out.CopySourceVersionId != *srcObjVersion.VersionId { return fmt.Errorf("expected the SourceVersionId to be %v, instead got %v", *srcObjVersion.VersionId, *out.CopySourceVersionId) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &dstObj, VersionId: out.VersionId, }) cancel() if err != nil { return err } if res.ContentLength == nil { return fmt.Errorf("expected non nil ContentLength") } if res.VersionId == nil { return fmt.Errorf("expected non nil VersionId") } if *res.ContentLength != *srcObjVersion.Size { return fmt.Errorf("expected the copied object size to be %v, instead got %v", *srcObjVersion.Size, *res.ContentLength) } if *res.VersionId != *out.VersionId { return fmt.Errorf("expected the copied object versionId to be %v, instead got %v", *out.VersionId, *res.VersionId) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_CopyObject_special_chars(s *S3Conf) error { testName := "Versioning_CopyObject_special_chars" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstBucket, dstObj := "foo?bar", getBucketName(), "bar&foo" err := setup(s, dstBucket) if err != nil { return err } srcObjVersions, err := createObjVersions(s3client, bucket, srcObj, 1) if err != nil { return err } srcObjVersionId := *srcObjVersions[0].VersionId ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{ Bucket: &bucket, Key: &dstObj, CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=%v", bucket, srcObj, srcObjVersionId)), }) cancel() if err != nil { return err } if res.VersionId == nil || *res.VersionId == "" { return fmt.Errorf("expected non empty versionId") } if res.CopySourceVersionId == nil { return fmt.Errorf("expected non nil CopySourceVersionId") } if *res.CopySourceVersionId != srcObjVersionId { return fmt.Errorf("expected the SourceVersionId to be %v, instead got %v", srcObjVersionId, *res.CopySourceVersionId) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &dstObj, VersionId: res.VersionId, }) cancel() if err != nil { return err } if out.VersionId == nil { return fmt.Errorf("expected non nil VersionId") } if *out.VersionId != *res.VersionId { return fmt.Errorf("expected the copied object versionId to be %v, instead got %v", *res.VersionId, *out.VersionId) } err = teardown(s, dstBucket) if err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_HeadObject_invalid_versionId(s *S3Conf) error { testName := "Versioning_HeadObject_invalid_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dLen := int64(2000) obj := "my-obj" _, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr("invalid_version_id"), }) cancel() if err := checkSdkApiErr(err, "BadRequest"); err != nil { return err } return nil }) } func Versioning_HeadObject_invalid_parent(s *S3Conf) error { testName := "Versioning_HeadObject_invalid_parent" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dLen := int64(2000) obj := "not-a-dir" r, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } obj = "not-a-dir/bad-obj" ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, VersionId: r.res.VersionId, }) cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }) } func Versioning_HeadObject_success(s *S3Conf) error { testName := "Versioning_HeadObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dLen := int64(2000) obj := "my-obj" r, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, VersionId: r.res.VersionId, }) cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected non nil ContentLength") } if out.VersionId == nil { return fmt.Errorf("expected non nil VersionId") } if *out.ContentLength != dLen { return fmt.Errorf("expected the object content-length to be %v, instead got %v", dLen, *out.ContentLength) } if *out.VersionId != *r.res.VersionId { return fmt.Errorf("expected the versionId to be %v, instead got %v", *r.res.VersionId, *out.VersionId) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_HeadObject_without_versionId(s *S3Conf) error { testName := "Versioning_HeadObject_without_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" versions, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } lastVersion := versions[0] ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if getString(res.VersionId) != *lastVersion.VersionId { return fmt.Errorf("expected versionId to be %v, instead got %v", *lastVersion.VersionId, getString(res.VersionId)) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_HeadObject_delete_marker(s *S3Conf) error { testName := "Versioning_HeadObject_delete_marker" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dLen := int64(2000) obj := "my-obj" _, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if out.VersionId == nil || *out.VersionId == "" { return fmt.Errorf("expected non empty versionId") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, VersionId: out.VersionId, }) cancel() if err := checkSdkApiErr(err, "MethodNotAllowed"); err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err := checkSdkApiErr(err, "NotFound"); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_GetObject_invalid_versionId(s *S3Conf) error { testName := "Versioning_GetObject_invalid_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dLen := int64(2000) obj := "my-obj" _, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr("invalid_version_id"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidVersionId)); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_GetObject_success(s *S3Conf) error { testName := "Versioning_GetObject_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dLen := int64(2000) obj := "my-obj" r, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } // Get the object by versionId ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, VersionId: r.res.VersionId, }) defer cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected non nil ContentLength") } if out.VersionId == nil { return fmt.Errorf("expected non nil VersionId") } if *out.ContentLength != dLen { return fmt.Errorf("expected the object content-length to be %v, instead got %v", dLen, *out.ContentLength) } if *out.VersionId != *r.res.VersionId { return fmt.Errorf("expected the versionId to be %v, instead got %v", *r.res.VersionId, *out.VersionId) } bdy, err := io.ReadAll(out.Body) if err != nil { return err } out.Body.Close() outCsum := sha256.Sum256(bdy) if outCsum != r.csum { return fmt.Errorf("incorrect output content") } // Get the object without versionId ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) defer cancel() if err != nil { return err } if out.ContentLength == nil { return fmt.Errorf("expected non nil ContentLength") } if out.VersionId == nil { return fmt.Errorf("expected non nil VersionId") } if *out.ContentLength != dLen { return fmt.Errorf("expected the object content-length to be %v, instead got %v", dLen, *out.ContentLength) } if *out.VersionId != *r.res.VersionId { return fmt.Errorf("expected the versionId to be %v, instead got %v", *r.res.VersionId, *out.VersionId) } bdy, err = io.ReadAll(out.Body) if err != nil { return err } out.Body.Close() outCsum = sha256.Sum256(bdy) if outCsum != r.csum { return fmt.Errorf("incorrect output content") } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_GetObject_delete_marker_without_versionId(s *S3Conf) error { testName := "Versioning_GetObject_delete_marker_without_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := putObjectWithData(1234, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err := checkSdkApiErr(err, "NoSuchKey"); err != nil { return err } return nil }) } func Versioning_GetObject_delete_marker(s *S3Conf) error { testName := "Versioning_GetObject_delete_marker" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dLen := int64(2000) obj := "my-obj" _, err := putObjectWithData(dLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if out.VersionId == nil || *out.VersionId == "" { return fmt.Errorf("expected non empty versionId") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, VersionId: out.VersionId, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_GetObject_null_versionId_obj(s *S3Conf) error { testName := "Versioning_GetObject_null_versionId_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, lgth := "my-obj", int64(234) out, err := putObjectWithData(lgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, VersionId: &nullVersionId, }) cancel() if err != nil { return err } if res.ContentLength == nil { return fmt.Errorf("expected non nil ContentLength") } if res.VersionId == nil { return fmt.Errorf("expected non nil VersionId") } if res.ETag == nil { return fmt.Errorf("expected non nil ETag") } if *res.ContentLength != lgth { return fmt.Errorf("expected the Content-Length to be %v, instead got %v", lgth, *res.ContentLength) } if *res.VersionId != nullVersionId { return fmt.Errorf("expected the versionId to be %v, insted got %v", nullVersionId, *res.VersionId) } if *res.ETag != *out.res.ETag { return fmt.Errorf("expecte the ETag to be %v, instead got %v", *out.res.ETag, *res.ETag) } return nil }) } func Versioning_GetObjectAttributes_object_version(s *S3Conf) error { testName := "Versioning_GetObjectAttributes_object_version" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" versions, err := createObjVersions(s3client, bucket, obj, 1) if err != nil { return err } version := versions[0] getObjAttrs := func(versionId *string) (*s3.GetObjectAttributesOutput, error) { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, VersionId: versionId, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) cancel() return res, err } // By specifying the versionId res, err := getObjAttrs(version.VersionId) if err != nil { return err } if getString(res.ETag) != strings.Trim(*version.ETag, "\"") { return fmt.Errorf("expected the uploaded object ETag to be %v, instead got %v", strings.Trim(*version.ETag, "\""), getString(res.ETag)) } if getString(res.VersionId) != *version.VersionId { return fmt.Errorf("expected the uploaded versionId to be %v, instead got %v", *version.VersionId, getString(res.VersionId)) } // Without versionId res, err = getObjAttrs(nil) if err != nil { return err } if getString(res.ETag) != strings.Trim(*version.ETag, "\"") { return fmt.Errorf("expected the uploaded object ETag to be %v, instead got %v", strings.Trim(*version.ETag, "\""), getString(res.ETag)) } if getString(res.VersionId) != *version.VersionId { return fmt.Errorf("expected the uploaded object versionId to be %v, instead got %v", *version.VersionId, getString(res.VersionId)) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_GetObjectAttributes_delete_marker(s *S3Conf) error { testName := "Versioning_GetObjectAttributes_delete_marker" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := createObjVersions(s3client, bucket, obj, 1) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{ Bucket: &bucket, Key: &obj, VersionId: res.VersionId, ObjectAttributes: []types.ObjectAttributes{ types.ObjectAttributesEtag, }, }) cancel() if err := checkSdkApiErr(err, "NoSuchKey"); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_DeleteObject_delete_object_version(s *S3Conf) error { testName := "Versioning_DeleteObject_delete_object_version" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { oLen := int64(1000) obj := "my-obj" r, err := putObjectWithData(oLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } versionId := r.res.VersionId if versionId == nil || *versionId == "" { return fmt.Errorf("expected non empty versionId") } _, err = putObjects(s3client, []string{obj}, bucket) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: versionId, }) cancel() if err != nil { return err } if out.VersionId == nil { return fmt.Errorf("expected non nil versionId") } if *out.VersionId != *versionId { return fmt.Errorf("expected deleted object versionId to be %v, instead got %v", *versionId, *out.VersionId) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_DeleteObject_non_existing_object(s *S3Conf) error { testName := "Versioning_DeleteObject_non_existing_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" ctx, canel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) canel() if err != nil { return err } ctx, canel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr("non_existing_version_id"), }) canel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidVersionId)); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_DeleteObject_delete_a_delete_marker(s *S3Conf) error { testName := "Versioning_DeleteObject_delete_a_delete_marker" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { oLen := int64(1000) obj := "my-obj" _, err := putObjectWithData(oLen, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if out.DeleteMarker == nil || !*out.DeleteMarker { return fmt.Errorf("expected the response DeleteMarker to be true") } if out.VersionId == nil || *out.VersionId == "" { return fmt.Errorf("expected non empty versionId") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: out.VersionId, }) cancel() if err != nil { return err } if res.DeleteMarker == nil || !*res.DeleteMarker { return fmt.Errorf("expected the response DeleteMarker to be true") } if res.VersionId == nil { return fmt.Errorf("expected non empty versionId") } if *res.VersionId != *out.VersionId { return fmt.Errorf("expected the versionId to be %v, instead got %v", *out.VersionId, *res.VersionId) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_Delete_null_versionId_object(s *S3Conf) error { testName := "Versioning_Delete_null_versionId_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, nObjLgth := "my-obj", int64(3211) _, err := putObjectWithData(nObjLgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } _, err = createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr(nullVersionId), }) cancel() if err != nil { return err } if getString(res.VersionId) != nullVersionId { return fmt.Errorf("expected the versionId to be %v, instead got %v", nullVersionId, getString(res.VersionId)) } return nil }) } func Versioning_DeleteObject_nested_dir_object(s *S3Conf) error { testName := "Versioning_DeleteObject_nested_dir_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "foo/bar/baz" out, err := putObjectWithData(1000, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: out.res.VersionId, }) cancel() if err != nil { return err } if getString(res.VersionId) != getString(out.res.VersionId) { return fmt.Errorf("expected the versionId to be %v, instead got %v", getString(out.res.VersionId), getString(res.VersionId)) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{ Bucket: &bucket, }) cancel() if err != nil { return err } // Then create the bucket back to not get error on teardown if err := setup(s, bucket); err != nil { return err } return nil }, withLock()) } func Versioning_DeleteObject_suspended(s *S3Conf) error { testName := "Versioning_DeleteObject_suspended" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" versions, err := createObjVersions(s3client, bucket, obj, 1) if err != nil { return err } versions[0].IsLatest = getBoolPtr(false) err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended) if err != nil { return err } for range 5 { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } if res.DeleteMarker == nil { return fmt.Errorf("expected the delete marker to be true") } if !*res.DeleteMarker { return fmt.Errorf("expected the delete marker to be true, instead got %v", *res.DeleteMarker) } if res.VersionId == nil { return fmt.Errorf("expected non nil versionId") } if *res.VersionId != nullVersionId { return fmt.Errorf("expected the versionId to be %v, instead got %v", nullVersionId, *res.VersionId) } } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } delMarkers := []types.DeleteMarkerEntry{ { IsLatest: getBoolPtr(true), Key: &obj, VersionId: &nullVersionId, }, } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the versions to be %v, instead got %v", versions, res.Versions) } if !compareDelMarkers(res.DeleteMarkers, delMarkers) { return fmt.Errorf("expected the delete markers to be %v, instead got %v", delMarkers, res.DeleteMarkers) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_DeleteObjects_success(s *S3Conf) error { testName := "Versioning_DeleteObjects_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj1, obj2, obj3 := "foo", "bar", "baz" obj1Version, err := createObjVersions(s3client, bucket, obj1, 1) if err != nil { return err } obj2Version, err := createObjVersions(s3client, bucket, obj2, 1) if err != nil { return err } obj3Version, err := createObjVersions(s3client, bucket, obj3, 1) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ { Key: obj1Version[0].Key, VersionId: obj1Version[0].VersionId, }, { Key: obj2Version[0].Key, }, { Key: obj3Version[0].Key, }, }, }, }) cancel() if err != nil { return err } delResult := []types.DeletedObject{ { Key: obj1Version[0].Key, VersionId: obj1Version[0].VersionId, DeleteMarker: getBoolPtr(false), }, { Key: obj2Version[0].Key, DeleteMarker: getBoolPtr(true), }, { Key: obj3Version[0].Key, DeleteMarker: getBoolPtr(true), }, } if len(out.Errors) != 0 { return fmt.Errorf("errors occurred during the deletion: %v", out.Errors) } if !compareDelObjects(delResult, out.Deleted) { return fmt.Errorf("expected the deleted objects to be %v, instead got %v", delResult, out.Deleted) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } obj2Version[0].IsLatest = getBoolPtr(false) obj3Version[0].IsLatest = getBoolPtr(false) versions := append(obj2Version, obj3Version...) delMarkers := []types.DeleteMarkerEntry{ { IsLatest: getBoolPtr(true), Key: out.Deleted[1].Key, VersionId: out.Deleted[1].DeleteMarkerVersionId, }, { IsLatest: getBoolPtr(true), Key: out.Deleted[2].Key, VersionId: out.Deleted[2].DeleteMarkerVersionId, }, } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the resulting versions to be %v, instead got %v", versions, res.Versions) } if !compareDelMarkers(delMarkers, res.DeleteMarkers) { return fmt.Errorf("expected the resulting delete markers to be %v, instead got %v", delMarkers, res.DeleteMarkers) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_DeleteObjects_delete_deleteMarkers(s *S3Conf) error { testName := "Versioning_DeleteObjects_delete_deleteMarkers" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj1, obj2 := "foo", "bar" obj1Version, err := createObjVersions(s3client, bucket, obj1, 1) if err != nil { return err } obj2Version, err := createObjVersions(s3client, bucket, obj2, 1) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ { Key: obj1Version[0].Key, }, { Key: obj2Version[0].Key, }, }, }, }) cancel() if err != nil { return err } delResult := []types.DeletedObject{ { Key: obj1Version[0].Key, DeleteMarker: getBoolPtr(true), }, { Key: obj2Version[0].Key, DeleteMarker: getBoolPtr(true), }, } if len(out.Errors) != 0 { return fmt.Errorf("errors occurred during the deletion: %v", out.Errors) } if !compareDelObjects(delResult, out.Deleted) { return fmt.Errorf("expected the deleted objects to be %v, instead got %v", delResult, out.Deleted) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ Bucket: &bucket, Delete: &types.Delete{ Objects: []types.ObjectIdentifier{ { Key: out.Deleted[0].Key, VersionId: out.Deleted[0].VersionId, }, { Key: out.Deleted[1].Key, VersionId: out.Deleted[1].VersionId, }, }, }, }) cancel() if err != nil { return err } if len(out.Errors) != 0 { return fmt.Errorf("errors occurred during the deletion: %v", out.Errors) } delResult = []types.DeletedObject{ { Key: out.Deleted[0].Key, DeleteMarker: getBoolPtr(true), DeleteMarkerVersionId: out.Deleted[0].VersionId, VersionId: out.Deleted[0].VersionId, }, { Key: out.Deleted[1].Key, DeleteMarker: getBoolPtr(true), DeleteMarkerVersionId: out.Deleted[1].VersionId, VersionId: out.Deleted[1].VersionId, }, } if !compareDelObjects(delResult, res.Deleted) { return fmt.Errorf("expected the deleted objects to be %v, instead got %v", delResult, res.Deleted) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func ListObjectVersions_non_existing_bucket(s *S3Conf) error { testName := "ListObjectVersions_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: getPtr(getBucketName()), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func ListObjectVersions_list_single_object_versions(s *S3Conf) error { testName := "ListObjectVersions_list_single_object_versions" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { object := "my-obj" versions, err := createObjVersions(s3client, bucket, object, 5) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, out.Versions) { return fmt.Errorf("expected the resulting versions to be %v, instead got %v", versions, out.Versions) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func ListObjectVersions_list_multiple_object_versions(s *S3Conf) error { testName := "ListObjectVersions_list_multiple_object_versions" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj1, obj2, obj3 := "foo", "bar", "baz" obj1Versions, err := createObjVersions(s3client, bucket, obj1, 4) if err != nil { return err } obj2Versions, err := createObjVersions(s3client, bucket, obj2, 3) if err != nil { return err } obj3Versions, err := createObjVersions(s3client, bucket, obj3, 5) if err != nil { return err } versions := append(append(obj2Versions, obj3Versions...), obj1Versions...) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, out.Versions) { return fmt.Errorf("expected the resulting versions to be %v, instead got %v", versions, out.Versions) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func ListObjectVersions_multiple_object_versions_truncated(s *S3Conf) error { testName := "ListObjectVersions_multiple_object_versions_truncated" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj1, obj2, obj3 := "foo", "bar", "baz" obj1Versions, err := createObjVersions(s3client, bucket, obj1, 4) if err != nil { return err } obj2Versions, err := createObjVersions(s3client, bucket, obj2, 3) if err != nil { return err } obj3Versions, err := createObjVersions(s3client, bucket, obj3, 5) if err != nil { return err } versions := append(append(obj2Versions, obj3Versions...), obj1Versions...) maxKeys := int32(5) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, MaxKeys: &maxKeys, }) cancel() if err != nil { return err } if out.Name == nil { return fmt.Errorf("expected the bucket name to be %v, instead got nil", bucket) } if *out.Name != bucket { return fmt.Errorf("expected the bucket name to be %v, instead got %v", bucket, *out.Name) } if out.IsTruncated == nil || !*out.IsTruncated { return fmt.Errorf("expected the output to be truncated") } if out.MaxKeys == nil { return fmt.Errorf("expected the max-keys to be %v, instead got nil", maxKeys) } if *out.MaxKeys != maxKeys { return fmt.Errorf("expected the max-keys to be %v, instead got %v", maxKeys, *out.MaxKeys) } if getString(out.NextKeyMarker) != getString(versions[maxKeys-1].Key) { return fmt.Errorf("expected the NextKeyMarker to be %v, instead got %v", getString(versions[maxKeys-1].Key), getString(out.NextKeyMarker)) } if getString(out.NextVersionIdMarker) != getString(versions[maxKeys-1].VersionId) { return fmt.Errorf("expected the NextVersionIdMarker to be %v, instead got %v", getString(versions[maxKeys-1].VersionId), getString(out.NextVersionIdMarker)) } if !compareVersions(versions[:maxKeys], out.Versions) { return fmt.Errorf("expected the resulting object versions to be %v, instead got %v", versions[:maxKeys], out.Versions) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) out, err = s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, KeyMarker: out.NextKeyMarker, VersionIdMarker: out.NextVersionIdMarker, }) cancel() if err != nil { return err } if out.Name == nil { return fmt.Errorf("expected the bucket name to be %v, instead got nil", bucket) } if *out.Name != bucket { return fmt.Errorf("expected the bucket name to be %v, instead got %v", bucket, *out.Name) } if out.IsTruncated != nil && *out.IsTruncated { return fmt.Errorf("expected the output not to be truncated") } if getString(out.KeyMarker) != getString(versions[maxKeys-1].Key) { return fmt.Errorf("expected the KeyMarker to be %v, instead got %v", getString(versions[maxKeys-1].Key), getString(out.KeyMarker)) } if getString(out.VersionIdMarker) != getString(versions[maxKeys-1].VersionId) { return fmt.Errorf("expected the VersionIdMarker to be %v, instead got %v", getString(versions[maxKeys-1].VersionId), getString(out.VersionIdMarker)) } if !compareVersions(versions[maxKeys:], out.Versions) { return fmt.Errorf("expected the resulting object versions to be %v, instead got %v", versions[maxKeys:], out.Versions) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func ListObjectVersions_with_delete_markers(s *S3Conf) error { testName := "ListObjectVersions_with_delete_markers" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" versions, err := createObjVersions(s3client, bucket, obj, 1) if err != nil { return err } versions[0].IsLatest = getBoolPtr(false) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } delMarkers := []types.DeleteMarkerEntry{} delMarkers = append(delMarkers, types.DeleteMarkerEntry{ Key: &obj, VersionId: out.VersionId, IsLatest: getBoolPtr(true), }) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the resulting versions to be %v, instead got %v", versions, res.Versions) } if !compareDelMarkers(res.DeleteMarkers, delMarkers) { return fmt.Errorf("expected the resulting delete markers to be %v, instead got %v", delMarkers, res.DeleteMarkers) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func ListObjectVersions_containing_null_versionId_obj(s *S3Conf) error { testName := "ListObjectVersions_containing_null_versionId_obj" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" versions, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended) if err != nil { return err } objLgth := int64(543) out, err := putObjectWithData(objLgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } if getString(out.res.VersionId) != nullVersionId { return fmt.Errorf("expected the uploaded object versionId to be %v, instead got %v", nullVersionId, getString(out.res.VersionId)) } versions[0].IsLatest = getBoolPtr(false) versions = append([]types.ObjectVersion{ { ETag: out.res.ETag, IsLatest: getBoolPtr(false), Key: &obj, Size: &objLgth, VersionId: &nullVersionId, StorageClass: types.ObjectVersionStorageClassStandard, }, }, versions...) err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } newVersions, err := createObjVersions(s3client, bucket, obj, 4) if err != nil { return err } versions = append(newVersions, versions...) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the listed object versions to be %v, instead got %v", versions, res.Versions) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func ListObjectVersions_single_null_versionId_object(s *S3Conf) error { testName := "ListObjectVersions_single_null_versionId_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj, objLgth := "my-obj", int64(890) out, err := putObjectWithData(objLgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { return err } versions := []types.ObjectVersion{ { ETag: out.res.ETag, Key: &obj, StorageClass: types.ObjectVersionStorageClassStandard, IsLatest: getBoolPtr(false), Size: &objLgth, VersionId: &nullVersionId, }, } delMarkers := []types.DeleteMarkerEntry{ { IsLatest: getBoolPtr(true), Key: &obj, VersionId: res.VersionId, }, } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareDelMarkers(resp.DeleteMarkers, delMarkers) { return fmt.Errorf("expected the delete markers list to be %v, instaed got %v", delMarkers, resp.DeleteMarkers) } if !compareVersions(versions, resp.Versions) { return fmt.Errorf("expected the object versions list to be %v, instead got %v", versions, resp.Versions) } return nil }) } func ListObjectVersions_checksum(s *S3Conf) error { testName := "ListObjectVersions_checksum" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { versions := []types.ObjectVersion{} for i, algo := range types.ChecksumAlgorithmCrc32.Values() { vers, err := createObjVersions(s3client, bucket, fmt.Sprintf("obj-%v", i), 1, withChecksumAlgo(algo)) if err != nil { return err } versions = append(versions, vers...) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if !compareVersions(versions, res.Versions) { return fmt.Errorf("expected the versions to be %+v, instead got %+v", versions, res.Versions) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_Multipart_Upload_success(s *S3Conf) error { testName := "Versioning_Multipart_Upload_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" out, err := createMp(s3client, bucket, obj) if err != nil { return err } objSize := int64(25 * 1024 * 1024) parts, _, err := uploadParts(s3client, objSize, 5, bucket, obj, *out.UploadId) if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, }) cancel() if err != nil { return err } if res.Key == nil { return fmt.Errorf("expected the object key to be %v, instead got nil", obj) } if *res.Key != obj { return fmt.Errorf("expected object key to be %v, instead got %v", obj, *res.Key) } if res.Bucket == nil { return fmt.Errorf("expected the bucket name to be %v, instead got nil", bucket) } if *res.Bucket != bucket { return fmt.Errorf("expected the bucket name to be %v, instead got %v", bucket, *res.Bucket) } if res.ETag == nil || *res.ETag == "" { return fmt.Errorf("expected non-empty ETag") } if res.VersionId == nil || *res.VersionId == "" { return fmt.Errorf("expected non-empty versionId") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, VersionId: res.VersionId, }) cancel() if err != nil { return err } if resp.ETag == nil || *resp.ETag == "" { return fmt.Errorf("expected (head object) non-empty ETag") } if *resp.ETag != *res.ETag { return fmt.Errorf("expected the uploaded object etag to be %v, instead got %v", *res.ETag, *resp.ETag) } if resp.ContentLength == nil { return fmt.Errorf("expected (head object) non nil content length") } if *resp.ContentLength != int64(objSize) { return fmt.Errorf("expected the uploaded object size to be %v, instead got %v", objSize, resp.ContentLength) } if resp.VersionId == nil { return fmt.Errorf("expected (head object) non nil versionId") } if *resp.VersionId != *res.VersionId { return fmt.Errorf("expected the versionId to be %v, instead got %v", *res.VersionId, *resp.VersionId) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_Multipart_Upload_overwrite_an_object(s *S3Conf) error { testName := "Versioning_Multipart_Upload_overwrite_an_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objVersions, err := createObjVersions(s3client, bucket, obj, 2) if err != nil { return err } out, err := createMp(s3client, bucket, obj) if err != nil { return err } objSize := int64(25 * 1024 * 1024) parts, _, err := uploadParts(s3client, objSize, 5, bucket, obj, *out.UploadId) if err != nil { return err } compParts := []types.CompletedPart{} for _, el := range parts { compParts = append(compParts, types.CompletedPart{ ETag: el.ETag, PartNumber: el.PartNumber, }) } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: &bucket, Key: &obj, UploadId: out.UploadId, MultipartUpload: &types.CompletedMultipartUpload{ Parts: compParts, }, }) cancel() if err != nil { return err } if res.Key == nil { return fmt.Errorf("expected the object key to be %v, instead got nil", obj) } if *res.Key != obj { return fmt.Errorf("expected object key to be %v, instead got %v", obj, *res.Key) } if res.Bucket == nil { return fmt.Errorf("expected the bucket name to be %v, instead got nil", bucket) } if *res.Bucket != bucket { return fmt.Errorf("expected the bucket name to be %v, instead got %v", bucket, *res.Bucket) } if res.ETag == nil || *res.ETag == "" { return fmt.Errorf("expected non-empty ETag") } if res.VersionId == nil || *res.VersionId == "" { return fmt.Errorf("expected non-empty versionId") } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } size := int64(objSize) objVersions[0].IsLatest = getBoolPtr(false) versions := append([]types.ObjectVersion{ { Key: &obj, VersionId: res.VersionId, ETag: res.ETag, IsLatest: getBoolPtr(true), Size: &size, StorageClass: types.ObjectVersionStorageClassStandard, }, }, objVersions...) if !compareVersions(versions, resp.Versions) { return fmt.Errorf("expected the resulting versions to be %v, instead got %v", versions, resp.Versions) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_UploadPartCopy_non_existing_versionId(s *S3Conf) error { testName := "Versioning_UploadPartCopy_non_existing_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { dstBucket, dstObj, srcObj := getBucketName(), "dst-obj", "src-obj" lgth := int64(100) _, err := putObjectWithData(lgth, &s3.PutObjectInput{ Bucket: &bucket, Key: &srcObj, }, s3client) if err != nil { return err } if err := setup(s, dstBucket); err != nil { return err } mp, err := createMp(s3client, dstBucket, dstObj) if err != nil { return err } pNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &dstBucket, Key: &dstObj, UploadId: mp.UploadId, PartNumber: &pNumber, CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=invalid_versionId", bucket, srcObj)), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil { return err } if err := teardown(s, dstBucket); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_UploadPartCopy_from_an_object_version(s *S3Conf) error { testName := "Versioning_UploadPartCopy_from_an_object_version" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { srcObj, dstBucket, obj := "my-obj", getBucketName(), "dst-obj" err := setup(s, dstBucket) if err != nil { return err } srcObjVersions, err := createObjVersions(s3client, bucket, srcObj, 1) if err != nil { return err } srcObjVersion := srcObjVersions[0] out, err := createMp(s3client, dstBucket, obj) if err != nil { return err } partNumber := int32(1) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) copyOut, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{ Bucket: &dstBucket, CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=%v", bucket, srcObj, *srcObjVersion.VersionId)), UploadId: out.UploadId, Key: &obj, PartNumber: &partNumber, }) cancel() if err != nil { return err } if getString(copyOut.CopySourceVersionId) != getString(srcObjVersion.VersionId) { return fmt.Errorf("expected the copy-source-version-id to be %v, instead got %v", getString(srcObjVersion.VersionId), getString(copyOut.CopySourceVersionId)) } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListParts(ctx, &s3.ListPartsInput{ Bucket: &dstBucket, Key: &obj, UploadId: out.UploadId, }) cancel() if err != nil { return err } if len(res.Parts) != 1 { return fmt.Errorf("expected parts to be 1, instead got %v", len(res.Parts)) } if res.Parts[0].PartNumber == nil { return fmt.Errorf("expected part-number to be non nil") } if *res.Parts[0].PartNumber != partNumber { return fmt.Errorf("expected part-number to be %v, instead got %v", partNumber, res.Parts[0].PartNumber) } if res.Parts[0].Size == nil { return fmt.Errorf("expected part size to be non nil") } if *res.Parts[0].Size != *srcObjVersion.Size { return fmt.Errorf("expected part size to be %v, instead got %v", *srcObjVersion.Size, res.Parts[0].Size) } if getString(res.Parts[0].ETag) != getString(copyOut.CopyPartResult.ETag) { return fmt.Errorf("expected part etag to be %v, instead got %v", getString(copyOut.CopyPartResult.ETag), getString(res.Parts[0].ETag)) } if err := teardown(s, dstBucket); err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_Enable_object_lock(s *S3Conf) error { testName := "Versioning_Enable_object_lock" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if res.Status != types.BucketVersioningStatusEnabled { return fmt.Errorf("expected the bucket versioning status to be %v, instead got %v", types.BucketVersioningStatusEnabled, res.Status) } return nil }, withLock()) } func Versioning_status_switch_to_suspended_with_object_lock(s *S3Conf) error { testName := "Versioning_status_switch_to_suspended_with_object_lock" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrSuspendedVersioningNotAllowed)); err != nil { return err } return nil }, withLock()) } func Versioning_PutObjectRetention_invalid_versionId(s *S3Conf) error { testName := "Versioning_PutObjectRetention_invalid_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } rDate := time.Now().Add(time.Hour * 48) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr("invalid_versionId"), Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &rDate, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidVersionId)); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_GetObjectRetention_invalid_versionId(s *S3Conf) error { testName := "Versioning_GetObjectRetention_invalid_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr("invalid_versionId"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidVersionId)); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_Put_GetObjectRetention_success(s *S3Conf) error { testName := "Versioning_Put_GetObjectRetention_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objVersions, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } objVersion := objVersions[1] rDate := time.Now().Add(time.Hour * 48) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, VersionId: objVersion.VersionId, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &rDate, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{ Bucket: &bucket, Key: &obj, VersionId: objVersion.VersionId, }) cancel() if err != nil { return err } if res.Retention.Mode != types.ObjectLockRetentionModeGovernance { return fmt.Errorf("expected the object retention mode to be %v, instead got %v", types.ObjectLockRetentionModeGovernance, res.Retention.Mode) } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_PutObjectLegalHold_invalid_versionId(s *S3Conf) error { testName := "Versioning_PutObjectLegalHold_invalid_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr("invalid_versionId"), LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidVersionId)); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_GetObjectLegalHold_invalid_versionId(s *S3Conf) error { testName := "Versioning_GetObjectLegalHold_invalid_versionId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" _, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: &obj, VersionId: getPtr("invalid_versionId"), }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidVersionId)); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_Put_GetObjectLegalHold_success(s *S3Conf) error { testName := "Versioning_Put_GetObjectLegalHold_success" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objVersions, err := createObjVersions(s3client, bucket, obj, 3) if err != nil { return err } objVersion := objVersions[1] ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &obj, VersionId: objVersion.VersionId, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{ Bucket: &bucket, Key: &obj, VersionId: objVersion.VersionId, }) cancel() if err != nil { return err } if res.LegalHold.Status != types.ObjectLockLegalHoldStatusOn { return fmt.Errorf("expected the object version legal hold status to be %v, instead got %v", types.ObjectLockLegalHoldStatusOn, res.LegalHold.Status) } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_WORM_obj_version_locked_with_legal_hold(s *S3Conf) error { testName := "Versioning_WORM_obj_version_locked_with_legal_hold" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objVersions, err := createObjVersions(s3client, bucket, obj, 2) if err != nil { return err } version := objVersions[1] ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: &bucket, Key: &obj, VersionId: version.VersionId, LegalHold: &types.ObjectLockLegalHold{ Status: types.ObjectLockLegalHoldStatusOn, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: version.VersionId, }) cancel() if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } // client sdk regression issue prevents getting full error message, // change back to below once this is fixed: // https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil { // return err // } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_WORM_obj_version_locked_with_governance_retention(s *S3Conf) error { testName := "Versioning_WORM_obj_version_locked_with_governance_retention" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objVersions, err := createObjVersions(s3client, bucket, obj, 2) if err != nil { return err } version := objVersions[0] rDate := time.Now().Add(time.Hour * 48) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, VersionId: version.VersionId, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeGovernance, RetainUntilDate: &rDate, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: version.VersionId, }) cancel() if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } // client sdk regression issue prevents getting full error message, // change back to below once this is fixed: // https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil { // return err // } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_WORM_obj_version_locked_with_compliance_retention(s *S3Conf) error { testName := "Versioning_WORM_obj_version_locked_with_compliance_retention" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objVersions, err := createObjVersions(s3client, bucket, obj, 2) if err != nil { return err } version := objVersions[0] rDate := time.Now().Add(time.Hour * 48) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{ Bucket: &bucket, Key: &obj, VersionId: version.VersionId, Retention: &types.ObjectLockRetention{ Mode: types.ObjectLockRetentionModeCompliance, RetainUntilDate: &rDate, }, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &bucket, Key: &obj, VersionId: version.VersionId, }) cancel() if err := checkSdkApiErr(err, "InvalidRequest"); err != nil { return err } // client sdk regression issue prevents getting full error message, // change back to below once this is fixed: // https://github.com/aws/aws-sdk-go-v2/issues/2921 // if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil { // return err // } if err := changeBucketObjectLockStatus(s3client, bucket, false); err != nil { return err } return nil }, withLock(), withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_AccessControl_GetObjectVersion(s *S3Conf) error { testName := "Versioning_AccessControl_GetObjectVersion" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objData, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } err = createUsers(s, []user{testuser1}) if err != nil { return err } doc := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObject"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) // querying with versionId should return access denied ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, VersionId: objData.res.VersionId, }) defer cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil { return err } // grant the user s3:GetObjectVersion doc = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObjectVersion"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.GetObject(ctx, &s3.GetObjectInput{ Bucket: &bucket, Key: &obj, VersionId: objData.res.VersionId, }) defer cancel() if err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func Versioning_AccessControl_HeadObjectVersion(s *S3Conf) error { testName := "Versioning_AccessControl_HeadObjectVersion" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" objData, err := putObjectWithData(10, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }, s3client) if err != nil { return err } err = createUsers(s, []user{testuser1}) if err != nil { return err } doc := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObject"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } userClient := s.getUserClient(testuser1) // querying with versionId should return access denied ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, VersionId: objData.res.VersionId, }) cancel() if err := checkSdkApiErr(err, http.StatusText(http.StatusForbidden)); err != nil { return err } // grant the user s3:GetObjectVersion doc = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser1.access), `"s3:GetObjectVersion"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket)) ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{ Bucket: &bucket, Policy: &doc, }) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) _, err = userClient.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: &bucket, Key: &obj, VersionId: objData.res.VersionId, }) cancel() if err != nil { return err } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } func VersioningDisabled_GetBucketVersioning_not_configured(s *S3Conf) error { testName := "VersioningDisabled_GetBucketVersioning_not_configured" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled) if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)); err != nil { return err } return nil }) } func VersioningDisabled_PutBucketVersioning_not_configured(s *S3Conf) error { testName := "VersioningDisabled_PutBucketVersioning_not_configured" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{ Bucket: &bucket, }) cancel() if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)); err != nil { return err } return nil }) } func Versioning_concurrent_upload_object(s *S3Conf) error { testName := "Versioninig_concurrent_upload_object" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" versionCount := 5 // Channel to collect errors errCh := make(chan error, versionCount) uploadVersion := func(wg *sync.WaitGroup) { defer wg.Done() ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &obj, }) cancel() if err != nil { // Send error to the channel errCh <- err return } fmt.Printf("uploaded object successfully: versionId: %v\n", *res.VersionId) } wg := &sync.WaitGroup{} wg.Add(versionCount) for range versionCount { go uploadVersion(wg) } wg.Wait() close(errCh) // Check if there were any errors for err := range errCh { if err != nil { fmt.Printf("error uploading an object: %v\n", err.Error()) return err } } // List object versions after all uploads ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{ Bucket: &bucket, }) cancel() if err != nil { return err } if len(res.Versions) != versionCount { return fmt.Errorf("expected %v object versions, instead got %v", versionCount, len(res.Versions)) } return nil }, withVersioning(types.BucketVersioningStatusEnabled)) } // router tests func RouterPutPartNumberWithoutUploadId(s *S3Conf) error { testName := "RouterPutPartNumberWithoutUploadId" return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error { req, err := http.NewRequest(http.MethodPut, s.endpoint+"/bucket/object", nil) if err != nil { return err } query := req.URL.Query() query.Add("partNumber", "1") req.URL.RawQuery = query.Encode() resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMissingUploadId)); err != nil { return err } return nil }) } func RouterPostRoot(s *S3Conf) error { testName := "RouterPostRoot" return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error { req, err := http.NewRequest(http.MethodPost, s.endpoint+"/", nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil { return err } return nil }) } func RouterPostObjectWithoutQuery(s *S3Conf) error { testName := "RouterPostObjectWithoutQuery" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { req, err := http.NewRequest(http.MethodPost, s.endpoint+"/bucket/object", nil) if err != nil { return err } resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil { return err } return nil }) } func RouterPUTObjectOnlyUploadId(s *S3Conf) error { testName := "RouterPUTObjectOnlyUploadId" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { req, err := http.NewRequest(http.MethodPut, s.endpoint+"/bucket/object", nil) if err != nil { return err } query := req.URL.Query() query.Add("uploadId", "my-upload-id") req.URL.RawQuery = query.Encode() resp, err := s.httpClient.Do(req) if err != nil { return err } if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil { return err } return nil }) }