158 lines
5.7 KiB
Go
158 lines
5.7 KiB
Go
package modules
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/config"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var generatePresignedURLCmd = &cobra.Command{
|
|
Use: "generate-presigned-url",
|
|
Short: "Generate presigned url using AWS credentials",
|
|
Long: `Generate presigned url using AWS credentials.Credentials must be placed in ~/.aws/credentials.
|
|
You provide profile to load using --profile flag or explicitly provide credentials and region using
|
|
--aws-access-key-id, --aws-secret-access-key, --region.
|
|
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 --header 'Content-Type: text/plain'`,
|
|
RunE: runGeneratePresignedURLCmd,
|
|
}
|
|
|
|
const defaultPresignedLifetime = 12 * time.Hour
|
|
|
|
const (
|
|
endpointFlag = "endpoint"
|
|
bucketFlag = "bucket"
|
|
objectFlag = "object"
|
|
methodFlag = "method"
|
|
profileFlag = "profile"
|
|
regionFlag = "region"
|
|
awsAccessKeyIDFlag = "aws-access-key-id"
|
|
awsSecretAccessKeyFlag = "aws-secret-access-key"
|
|
headerFlag = "header"
|
|
sigV4AFlag = "sigv4a"
|
|
)
|
|
|
|
func initGeneratePresignedURLCmd() {
|
|
generatePresignedURLCmd.Flags().Duration(lifetimeFlag, defaultPresignedLifetime, "Lifetime of presigned URL. For example 50h30m (note: max time unit is an hour so to set a day you should use 24h).\nIt will be ceil rounded to the nearest amount of epoch.")
|
|
generatePresignedURLCmd.Flags().String(endpointFlag, "", "S3 gateway endpoint")
|
|
generatePresignedURLCmd.Flags().String(bucketFlag, "", "Bucket name to perform action")
|
|
generatePresignedURLCmd.Flags().String(objectFlag, "", "Object name to perform action")
|
|
generatePresignedURLCmd.Flags().String(methodFlag, "", "HTTP method to perform action")
|
|
generatePresignedURLCmd.Flags().String(profileFlag, "", "AWS profile to load")
|
|
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(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.Flags().Bool(sigV4AFlag, false, "Use SigV4A for signing request")
|
|
|
|
_ = generatePresignedURLCmd.MarkFlagRequired(endpointFlag)
|
|
_ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag)
|
|
_ = generatePresignedURLCmd.MarkFlagRequired(objectFlag)
|
|
}
|
|
|
|
func runGeneratePresignedURLCmd(cmd *cobra.Command, _ []string) error {
|
|
var (
|
|
region string
|
|
creds aws.Credentials
|
|
)
|
|
|
|
profile := viper.GetString(profileFlag)
|
|
|
|
if profile == "" {
|
|
cfg, err := config.LoadDefaultConfig(cmd.Context())
|
|
if err != nil {
|
|
return wrapPreparationError(err)
|
|
}
|
|
region = cfg.Region
|
|
if creds, err = cfg.Credentials.Retrieve(cmd.Context()); err != nil {
|
|
return wrapPreparationError(fmt.Errorf("couldn't get default aws credentials: %w", err))
|
|
}
|
|
} else {
|
|
cfg, err := config.LoadSharedConfigProfile(cmd.Context(), viper.GetString(profileFlag))
|
|
if err != nil {
|
|
return wrapPreparationError(fmt.Errorf("couldn't get '%s' aws credentials: %w", viper.GetString(profileFlag), err))
|
|
}
|
|
region = cfg.Region
|
|
creds = cfg.Credentials
|
|
}
|
|
|
|
accessKeyIDArg := viper.GetString(awsAccessKeyIDFlag)
|
|
secretAccessKeyArg := viper.GetString(awsSecretAccessKeyFlag)
|
|
if accessKeyIDArg != "" && secretAccessKeyArg != "" {
|
|
creds.AccessKeyID = accessKeyIDArg
|
|
creds.SecretAccessKey = secretAccessKeyArg
|
|
}
|
|
|
|
if regionArg := viper.GetString(regionFlag); regionArg != "" {
|
|
region = regionArg
|
|
}
|
|
|
|
reqData := auth.RequestData{
|
|
Method: viper.GetString(methodFlag),
|
|
Endpoint: viper.GetString(endpointFlag),
|
|
Bucket: viper.GetString(bucketFlag),
|
|
Object: viper.GetString(objectFlag),
|
|
}
|
|
presignData := auth.PresignData{
|
|
Service: "s3",
|
|
Region: region,
|
|
Lifetime: viper.GetDuration(lifetimeFlag),
|
|
SignTime: time.Now().UTC(),
|
|
}
|
|
|
|
headers, err := parseHeaders()
|
|
if err != nil {
|
|
return wrapPreparationError(fmt.Errorf("failed to parse headers: %s", err))
|
|
}
|
|
presignData.Headers = headers
|
|
|
|
var req *http.Request
|
|
if viper.GetBool(sigV4AFlag) {
|
|
req, err = auth.PresignRequestV4a(creds, reqData, presignData)
|
|
} else {
|
|
req, err = auth.PresignRequest(cmd.Context(), creds, reqData, presignData)
|
|
}
|
|
if err != nil {
|
|
return wrapBusinessLogicError(err)
|
|
}
|
|
|
|
res := &struct{ URL string }{
|
|
URL: req.URL.String(),
|
|
}
|
|
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetIndent("", " ")
|
|
enc.SetEscapeHTML(false)
|
|
err = enc.Encode(res)
|
|
if err != nil {
|
|
return wrapBusinessLogicError(err)
|
|
}
|
|
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
|
|
}
|