[#505] authmate: Add flag for headers in generate-presigned-url cmd #526

Merged
alexvanin merged 2 commits from mbiryukova/frostfs-s3-gw:feature/configure_content_type into master 2024-11-02 08:53:55 +00:00
3 changed files with 52 additions and 2 deletions

View file

@ -23,6 +23,7 @@ type PresignData struct {
Region string Region string
Lifetime time.Duration Lifetime time.Duration
SignTime time.Time SignTime time.Time
Headers map[string]string
} }
// PresignRequest forms pre-signed request to access objects without aws credentials. // PresignRequest forms pre-signed request to access objects without aws credentials.
@ -34,7 +35,10 @@ func PresignRequest(creds *credentials.Credentials, reqData RequestData, presign
} }
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z")) req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
req.Header.Set(ContentTypeHdr, "text/plain")
for k, v := range presignData.Headers {
req.Header.Set(k, v)
}
signer := v4.NewSigner(creds) signer := v4.NewSigner(creds)
signer.DisableURIPathEscaping = true signer.DisableURIPathEscaping = true

View file

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
@ -21,7 +22,7 @@ var generatePresignedURLCmd = &cobra.Command{
You provide profile to load using --profile flag or explicitly provide credentials and region using You provide profile to load using --profile flag or explicitly provide credentials and region using
--aws-access-key-id, --aws-secret-access-key, --region. --aws-access-key-id, --aws-secret-access-key, --region.
Note to override credentials you must provide both access key and secret key.`, Note to override credentials you must provide both access key and secret key.`,
Example: `frostfs-s3-authmate generate-presigned-url --method put --bucket my-bucket --object my-object --endpoint http://localhost:8084 --lifetime 12h --region ru --aws-access-key-id ETaA2CadPcA7bAkLsML2PbTudXY8uRt2PDjCCwkvRv9s0FDCxWDXYc1SA1vKv8KbyCNsLY2AmAjJ92Vz5rgvsFCy --aws-secret-access-key c2d65ef2980f03f4f495bdebedeeae760496697880d61d106bb9a4e5cd2e0607`, Example: `frostfs-s3-authmate generate-presigned-url --method put --bucket my-bucket --object my-object --endpoint http://localhost:8084 --lifetime 12h --region ru --aws-access-key-id ETaA2CadPcA7bAkLsML2PbTudXY8uRt2PDjCCwkvRv9s0FDCxWDXYc1SA1vKv8KbyCNsLY2AmAjJ92Vz5rgvsFCy --aws-secret-access-key c2d65ef2980f03f4f495bdebedeeae760496697880d61d106bb9a4e5cd2e0607 --header 'Content-Type: text/plain'`,
RunE: runGeneratePresignedURLCmd, RunE: runGeneratePresignedURLCmd,
} }
@ -36,6 +37,7 @@ const (
regionFlag = "region" regionFlag = "region"
awsAccessKeyIDFlag = "aws-access-key-id" awsAccessKeyIDFlag = "aws-access-key-id"
awsSecretAccessKeyFlag = "aws-secret-access-key" awsSecretAccessKeyFlag = "aws-secret-access-key"
dkirillov marked this conversation as resolved Outdated

It's better to use more general flag like header to be able to pass any headers we want

It's better to use more general flag like `header` to be able to pass any headers we want
headerFlag = "header"
) )
func initGeneratePresignedURLCmd() { func initGeneratePresignedURLCmd() {
@ -48,6 +50,7 @@ func initGeneratePresignedURLCmd() {
generatePresignedURLCmd.Flags().String(regionFlag, "", "AWS region to use in signature (default is taken from ~/.aws/config)") generatePresignedURLCmd.Flags().String(regionFlag, "", "AWS region to use in signature (default is taken from ~/.aws/config)")
generatePresignedURLCmd.Flags().String(awsAccessKeyIDFlag, "", "AWS access key id to sign the URL (default is taken from ~/.aws/credentials)") generatePresignedURLCmd.Flags().String(awsAccessKeyIDFlag, "", "AWS access key id to sign the URL (default is taken from ~/.aws/credentials)")
generatePresignedURLCmd.Flags().String(awsSecretAccessKeyFlag, "", "AWS secret access key to sign the URL (default is taken from ~/.aws/credentials)") generatePresignedURLCmd.Flags().String(awsSecretAccessKeyFlag, "", "AWS secret access key to sign the URL (default is taken from ~/.aws/credentials)")
generatePresignedURLCmd.Flags().StringSlice(headerFlag, nil, "Header in form of 'Key: value' to use in presigned URL (use flags repeatedly for multiple headers or separate them by comma)")
_ = generatePresignedURLCmd.MarkFlagRequired(endpointFlag) _ = generatePresignedURLCmd.MarkFlagRequired(endpointFlag)
_ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag) _ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag)
@ -92,6 +95,12 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error {
SignTime: time.Now().UTC(), SignTime: time.Now().UTC(),
} }
headers, err := parseHeaders()
if err != nil {
return wrapPreparationError(fmt.Errorf("failed to parse headers: %s", err))
}
presignData.Headers = headers
req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData) req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData)
if err != nil { if err != nil {
return wrapBusinessLogicError(err) return wrapBusinessLogicError(err)
@ -110,3 +119,21 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error {
} }
return nil return nil
} }
func parseHeaders() (map[string]string, error) {
headers := viper.GetStringSlice(headerFlag)
if len(headers) == 0 {
return nil, nil
}
result := make(map[string]string, len(headers))
for _, header := range headers {
k, v, found := strings.Cut(header, ":")
if !found {
return nil, fmt.Errorf("invalid header format: %s", header)
}
result[strings.Trim(k, " ")] = strings.Trim(v, " ")
}
return result, nil
}

View file

@ -349,6 +349,25 @@ $ frostfs-s3-authmate generate-presigned-url --endpoint http://localhost:8084 \
} }
``` ```
### Upload file with presigned URL
1. Generate presigned URL to upload object `obj` to bucket `presigned`
```shell
$ frostfs-s3-authmate generate-presigned-url --endpoint http://localhost:8084 \
--method put --bucket presigned --object obj --lifetime 30s
{
"URL": "http://localhost:8084/presigned/obj?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CYfbvKwSC9VNvttj5snyEZ5Ttr2VaBabpw7mRuEzNXyw09ewUERj6MGDKfyckfg5VZ39GfXbwLwz62UPVeRxhJDet%2F20241029%2Fdefault%2Fs3%2Faws4_request&X-Amz-Date=20241029T145726Z&X-Amz-Expires=30&X-Amz-SignedHeaders=host&X-Amz-Signature=2bb13b3e6448968219ad95147debe49e37bce5ce3ed1344c4015f43cb444a956"
}
```
2. Upload file using `curl`
```shell
curl --upload-file /path/to/file 'http://localhost:8084/presigned/obj?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CYfbvKwSC9VNvttj5snyEZ5Ttr2VaBabpw7mRuEzNXyw09ewUERj6MGDKfyckfg5VZ39GfXbwLwz62UPVeRxhJDet%2F20241029%2Fdefault%2Fs3%2Faws4_request&X-Amz-Date=20241029T145726Z&X-Amz-Expires=30&X-Amz-SignedHeaders=host&X-Amz-Signature=2bb13b3e6448968219ad95147debe49e37bce5ce3ed1344c4015f43cb444a956'
```
### AWS CLI ### AWS CLI
You can also can get the presigned URL (only for GET) using aws cli v2: You can also can get the presigned URL (only for GET) using aws cli v2: