mirror of
https://github.com/versity/versitygw.git
synced 2026-08-21 06:36:23 +00:00
feat: Added 20 integration tests for v4 authentication with query params. Fixed few bugs in v4 query params authentication
This commit is contained in:
@@ -22,6 +22,29 @@ func TestAuthentication(s *S3Conf) {
|
||||
Authentication_signature_error_incorrect_secret_key(s)
|
||||
}
|
||||
|
||||
func TestPresignedAuthentication(s *S3Conf) {
|
||||
PresignedAuth_missing_algo_query_param(s)
|
||||
PresignedAuth_unsupported_algorithm(s)
|
||||
PresignedAuth_missing_credentials_query_param(s)
|
||||
PresignedAuth_malformed_creds_invalid_parts(s)
|
||||
PresignedAuth_malformed_creds_invalid_parts(s)
|
||||
PresignedAuth_creds_incorrect_service(s)
|
||||
PresignedAuth_creds_incorrect_region(s)
|
||||
PresignedAuth_creds_invalid_date(s)
|
||||
PresignedAuth_missing_date_query(s)
|
||||
PresignedAuth_dates_mismatch(s)
|
||||
PresignedAuth_non_existing_access_key_id(s)
|
||||
PresignedAuth_missing_signed_headers_query_param(s)
|
||||
PresignedAuth_missing_expiration_query_param(s)
|
||||
PresignedAuth_invalid_expiration_query_param(s)
|
||||
PresignedAuth_negative_expiration_query_param(s)
|
||||
PresignedAuth_exceeding_expiration_query_param(s)
|
||||
PresignedAuth_expired_request(s)
|
||||
PresignedAuth_incorrect_secret_key(s)
|
||||
PresignedAuth_PutObject_success(s)
|
||||
PresignedAuth_Put_GetObject_with_data(s)
|
||||
}
|
||||
|
||||
func TestCreateBucket(s *S3Conf) {
|
||||
CreateBucket_invalid_bucket_name(s)
|
||||
CreateBucket_existing_bucket(s)
|
||||
@@ -219,6 +242,7 @@ func TestGetBucketAcl(s *S3Conf) {
|
||||
|
||||
func TestFullFlow(s *S3Conf) {
|
||||
TestAuthentication(s)
|
||||
TestPresignedAuthentication(s)
|
||||
TestCreateBucket(s)
|
||||
TestHeadBucket(s)
|
||||
TestListBuckets(s)
|
||||
@@ -277,6 +301,26 @@ func GetIntTests() IntTests {
|
||||
"Authentication_incorrect_payload_hash": Authentication_incorrect_payload_hash,
|
||||
"Authentication_incorrect_md5": Authentication_incorrect_md5,
|
||||
"Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key,
|
||||
"PresignedAuth_missing_algo_query_param": PresignedAuth_missing_algo_query_param,
|
||||
"PresignedAuth_unsupported_algorithm": PresignedAuth_unsupported_algorithm,
|
||||
"PresignedAuth_missing_credentials_query_param": PresignedAuth_missing_credentials_query_param,
|
||||
"PresignedAuth_malformed_creds_invalid_parts": PresignedAuth_malformed_creds_invalid_parts,
|
||||
"PresignedAuth_creds_invalid_terminator": PresignedAuth_creds_invalid_terminator,
|
||||
"PresignedAuth_creds_incorrect_service": PresignedAuth_creds_incorrect_service,
|
||||
"PresignedAuth_creds_incorrect_region": PresignedAuth_creds_incorrect_region,
|
||||
"PresignedAuth_creds_invalid_date": PresignedAuth_creds_invalid_date,
|
||||
"PresignedAuth_missing_date_query": PresignedAuth_missing_date_query,
|
||||
"PresignedAuth_dates_mismatch": PresignedAuth_dates_mismatch,
|
||||
"PresignedAuth_non_existing_access_key_id": PresignedAuth_non_existing_access_key_id,
|
||||
"PresignedAuth_missing_signed_headers_query_param": PresignedAuth_missing_signed_headers_query_param,
|
||||
"PresignedAuth_missing_expiration_query_param": PresignedAuth_missing_expiration_query_param,
|
||||
"PresignedAuth_invalid_expiration_query_param": PresignedAuth_invalid_expiration_query_param,
|
||||
"PresignedAuth_negative_expiration_query_param": PresignedAuth_negative_expiration_query_param,
|
||||
"PresignedAuth_exceeding_expiration_query_param": PresignedAuth_exceeding_expiration_query_param,
|
||||
"PresignedAuth_expired_request": PresignedAuth_expired_request,
|
||||
"PresignedAuth_incorrect_secret_key": PresignedAuth_incorrect_secret_key,
|
||||
"PresignedAuth_PutObject_success": PresignedAuth_PutObject_success,
|
||||
"PresignedAuth_Put_GetObject_with_data": PresignedAuth_Put_GetObject_with_data,
|
||||
"CreateBucket_invalid_bucket_name": CreateBucket_invalid_bucket_name,
|
||||
"CreateBucket_existing_bucket": CreateBucket_existing_bucket,
|
||||
"CreateBucket_as_user": CreateBucket_as_user,
|
||||
|
||||
+840
-1
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -19,7 +20,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
shortTimeout = 10 * time.Second
|
||||
shortTimeout = 10 * time.Second
|
||||
iso8601Format = "20060102T150405Z"
|
||||
)
|
||||
|
||||
func Authentication_empty_auth_header(s *S3Conf) error {
|
||||
@@ -624,6 +626,843 @@ func Authentication_signature_error_incorrect_secret_key(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func PresignedAuth_missing_algo_query_param(s *S3Conf) error {
|
||||
testName := "PresignedAuth_missing_algo_query_param"
|
||||
return presignedAuthHandler(s, testName, func(client *s3.PresignClient) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
urlParsed, err := url.Parse(v4req.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queries := urlParsed.Query()
|
||||
queries.Del("X-Amz-Algorithm")
|
||||
urlParsed.RawQuery = queries.Encode()
|
||||
|
||||
req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); 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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrSignatureIncorrService)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func PresignedAuth_creds_incorrect_region(s *S3Conf) error {
|
||||
testName := "PresignedAuth_creds_incorrect_region"
|
||||
cfg := *s
|
||||
|
||||
if cfg.awsRegion == "us-east-1" {
|
||||
cfg.awsRegion = "us-west-1"
|
||||
} else {
|
||||
cfg.awsRegion = "us-east-1"
|
||||
}
|
||||
|
||||
return presignedAuthHandler(&cfg, testName, func(client *s3.PresignClient) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(v4req.Method, v4req.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
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 := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrExpiredPresignRequest)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func PresignedAuth_incorrect_secret_key(s *S3Conf) error {
|
||||
testName := "PresignedAuth_incorrect_secret_key"
|
||||
cfg := *s
|
||||
cfg.awsSecret += "x"
|
||||
return presignedAuthHandler(&cfg, testName, func(client *s3.PresignClient) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: getPtr("my-bucket")})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(v4req.Method, v4req.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkAuthErr(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) error {
|
||||
bucket := getBucketName()
|
||||
err := setup(s, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, v4req.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := 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)
|
||||
}
|
||||
|
||||
err = teardown(s, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) error {
|
||||
bucket, obj := getBucketName(), "my-obj"
|
||||
err := setup(s, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
httpClient := http.Client{
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(v4req.Method, v4req.URL, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header = v4req.SignedHeader
|
||||
|
||||
resp, err := 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 = 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)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Request.Method, resp.ContentLength, string(respBody))
|
||||
|
||||
if string(respBody) != data {
|
||||
return fmt.Errorf("expected get object response body to be %v, instead got %s", data, respBody)
|
||||
}
|
||||
|
||||
err = teardown(s, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateBucket_invalid_bucket_name(s *S3Conf) error {
|
||||
testName := "CreateBucket_invalid_bucket_name"
|
||||
runF(testName)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
rnd "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -150,6 +151,20 @@ func authHandler(s *S3Conf, cfg *authConfig, handler func(req *http.Request) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func presignedAuthHandler(s *S3Conf, testName string, handler func(client *s3.PresignClient) error) error {
|
||||
runF(testName)
|
||||
clt := s3.NewPresignClient(s3.NewFromConfig(s.Config()))
|
||||
|
||||
err := handler(clt)
|
||||
if err != nil {
|
||||
failF("%v: %v", testName, err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
passF(testName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSignedReq(method, endpoint, path, access, secret, service, region string, body []byte, date time.Time) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%v/%v", endpoint, path), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
@@ -551,3 +566,26 @@ func genRandString(length int) string {
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
const (
|
||||
credAccess int = iota
|
||||
credDate
|
||||
credRegion
|
||||
credService
|
||||
credTerminator
|
||||
)
|
||||
|
||||
func changeAuthCred(uri, newVal string, index int) (string, error) {
|
||||
urlParsed, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
queries := urlParsed.Query()
|
||||
creds := strings.Split(queries.Get("X-Amz-Credential"), "/")
|
||||
creds[index] = newVal
|
||||
queries.Set("X-Amz-Credential", strings.Join(creds, "/"))
|
||||
urlParsed.RawQuery = queries.Encode()
|
||||
|
||||
return urlParsed.String(), nil
|
||||
}
|
||||
|
||||
@@ -64,6 +64,6 @@ func VerifyPresignedV4Signature(root RootUserConfig, iam auth.IAMService, logger
|
||||
return sendResponse(ctx, err, logger)
|
||||
}
|
||||
|
||||
return nil
|
||||
return ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -73,14 +74,24 @@ func (pr *PresignedAuthReader) Read(p []byte) (int, error) {
|
||||
|
||||
// CheckPresignedSignature validates presigned request signature
|
||||
func CheckPresignedSignature(ctx *fiber.Ctx, auth AuthData, secret string, debug bool) error {
|
||||
signedHdrs := strings.Split(auth.SignedHeaders, ";")
|
||||
|
||||
var contentLength int64
|
||||
var err error
|
||||
contentLengthStr := ctx.Get("Content-Length")
|
||||
if contentLengthStr != "" {
|
||||
contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64)
|
||||
if err != nil {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new http request instance from fasthttp request
|
||||
req, err := createPresignedHttpRequestFromCtx(ctx)
|
||||
req, err := createPresignedHttpRequestFromCtx(ctx, signedHdrs, contentLength)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create http request from context: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("http request has been created")
|
||||
|
||||
date, _ := time.Parse(iso8601Format, auth.Date)
|
||||
|
||||
signer := v4.NewSigner()
|
||||
@@ -90,10 +101,8 @@ func CheckPresignedSignature(ctx *fiber.Ctx, auth AuthData, secret string, debug
|
||||
}, req, unsignedPayload, service, auth.Region, date, func(options *v4.SignerOptions) {
|
||||
options.DisableURIPathEscaping = true
|
||||
if debug {
|
||||
if debug {
|
||||
options.LogSigning = true
|
||||
options.Logger = logging.NewStandardLogger(os.Stderr)
|
||||
}
|
||||
options.LogSigning = true
|
||||
options.Logger = logging.NewStandardLogger(os.Stderr)
|
||||
}
|
||||
})
|
||||
if signErr != nil {
|
||||
@@ -128,14 +137,17 @@ func ParsePresignedURIParts(ctx *fiber.Ctx) (AuthData, error) {
|
||||
|
||||
// Get and verify algorithm query parameter
|
||||
algo := ctx.Query("X-Amz-Algorithm")
|
||||
if algo == "" {
|
||||
return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams)
|
||||
}
|
||||
if algo != "AWS4-HMAC-SHA256" {
|
||||
return a, s3err.GetAPIError(s3err.ErrSignatureVersionNotSupported)
|
||||
return a, s3err.GetAPIError(s3err.ErrInvalidQuerySignatureAlgo)
|
||||
}
|
||||
|
||||
// Parse and validate credentials query parameter
|
||||
credsQuery := ctx.Query("X-Amz-Credential")
|
||||
if credsQuery == "" {
|
||||
return a, s3err.GetAPIError(s3err.ErrCredMalformed)
|
||||
return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams)
|
||||
}
|
||||
|
||||
creds := strings.Split(credsQuery, "/")
|
||||
@@ -156,7 +168,7 @@ func ParsePresignedURIParts(ctx *fiber.Ctx) (AuthData, error) {
|
||||
// Parse and validate Date query param
|
||||
date := ctx.Query("X-Amz-Date")
|
||||
if date == "" {
|
||||
return a, s3err.GetAPIError(s3err.ErrMissingDateHeader)
|
||||
return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams)
|
||||
}
|
||||
|
||||
tdate, err := time.Parse(iso8601Format, date)
|
||||
@@ -168,25 +180,64 @@ func ParsePresignedURIParts(ctx *fiber.Ctx) (AuthData, error) {
|
||||
return a, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)
|
||||
}
|
||||
|
||||
err = ValidateDate(tdate)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
|
||||
if ctx.Locals("region") != creds[2] {
|
||||
return a, s3err.APIError{
|
||||
Code: "SignatureDoesNotMatch",
|
||||
Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", ctx.Locals("region")),
|
||||
Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", creds[2]),
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
|
||||
a.Signature = ctx.Query("X-Amz-Signature")
|
||||
signature := ctx.Query("X-Amz-Signature")
|
||||
if signature == "" {
|
||||
return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams)
|
||||
}
|
||||
|
||||
signedHdrs := ctx.Query("X-Amz-SignedHeaders")
|
||||
if signedHdrs == "" {
|
||||
return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams)
|
||||
}
|
||||
|
||||
// Validate X-Amz-Expires query param and check if request is expired
|
||||
err = validateExpiration(ctx.Query("X-Amz-Expires"), tdate)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
|
||||
a.Signature = signature
|
||||
a.Access = creds[0]
|
||||
a.Algorithm = algo
|
||||
a.Region = creds[2]
|
||||
a.SignedHeaders = ctx.Query("X-Amz-SignedHeaders")
|
||||
a.SignedHeaders = signedHdrs
|
||||
a.Date = date
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func validateExpiration(str string, date time.Time) error {
|
||||
if str == "" {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidQueryParams)
|
||||
}
|
||||
|
||||
exp, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
return s3err.GetAPIError(s3err.ErrMalformedExpires)
|
||||
}
|
||||
|
||||
if exp < 0 {
|
||||
return s3err.GetAPIError(s3err.ErrNegativeExpires)
|
||||
}
|
||||
|
||||
if exp > 604800 {
|
||||
return s3err.GetAPIError(s3err.ErrMaximumExpires)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
passed := int(now.Sub(date).Seconds())
|
||||
|
||||
if passed > exp {
|
||||
return s3err.GetAPIError(s3err.ErrExpiredPresignRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+17
-2
@@ -97,7 +97,7 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func createPresignedHttpRequestFromCtx(ctx *fiber.Ctx) (*http.Request, error) {
|
||||
func createPresignedHttpRequestFromCtx(ctx *fiber.Ctx, signedHdrs []string, contentLength int64) (*http.Request, error) {
|
||||
req := ctx.Request()
|
||||
var body io.Reader
|
||||
if IsBigDataAction(ctx) {
|
||||
@@ -106,7 +106,7 @@ func createPresignedHttpRequestFromCtx(ctx *fiber.Ctx) (*http.Request, error) {
|
||||
body = bytes.NewReader(req.Body())
|
||||
}
|
||||
|
||||
uri := string(ctx.Request().URI().Host()) + string(ctx.Request().URI().Path())
|
||||
uri := string(ctx.Request().URI().Path())
|
||||
isFirst := true
|
||||
|
||||
ctx.Request().URI().QueryArgs().VisitAll(func(key, value []byte) {
|
||||
@@ -125,6 +125,21 @@ func createPresignedHttpRequestFromCtx(ctx *fiber.Ctx) (*http.Request, error) {
|
||||
if err != nil {
|
||||
return nil, errors.New("error in creating an http request")
|
||||
}
|
||||
// Set the request headers
|
||||
req.Header.VisitAll(func(key, value []byte) {
|
||||
keyStr := string(key)
|
||||
if includeHeader(keyStr, signedHdrs) {
|
||||
httpReq.Header.Add(keyStr, string(value))
|
||||
}
|
||||
})
|
||||
|
||||
// Check if Content-Length in signed headers
|
||||
// If content length is non 0, then the header will be included
|
||||
if !includeHeader("Content-Length", signedHdrs) {
|
||||
httpReq.ContentLength = 0
|
||||
} else {
|
||||
httpReq.ContentLength = contentLength
|
||||
}
|
||||
|
||||
// Set the Host header
|
||||
httpReq.Host = string(req.Header.Host())
|
||||
|
||||
Reference in New Issue
Block a user