2023-06-23 13:11:57 +00:00
package modules
import (
2024-11-27 08:31:53 +00:00
"context"
2023-06-23 13:11:57 +00:00
"encoding/json"
"fmt"
2024-04-23 11:49:34 +00:00
"net/http"
2023-06-23 13:11:57 +00:00
"os"
2024-10-29 13:26:01 +00:00
"strings"
2023-06-23 13:11:57 +00:00
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
2024-09-04 14:35:39 +00:00
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
2023-06-23 13:11:57 +00:00
"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 . ` ,
2024-10-29 13:26:01 +00:00
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' ` ,
2023-06-23 13:11:57 +00:00
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"
2024-10-29 13:26:01 +00:00
headerFlag = "header"
2024-04-23 11:49:34 +00:00
sigV4AFlag = "sigv4a"
2023-06-23 13:11:57 +00:00
)
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)" )
2024-10-29 13:26:01 +00:00
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)" )
2024-04-23 11:49:34 +00:00
generatePresignedURLCmd . Flags ( ) . Bool ( sigV4AFlag , false , "Use SigV4A for signing request" )
2023-06-23 13:11:57 +00:00
_ = generatePresignedURLCmd . MarkFlagRequired ( endpointFlag )
_ = generatePresignedURLCmd . MarkFlagRequired ( bucketFlag )
_ = generatePresignedURLCmd . MarkFlagRequired ( objectFlag )
}
2024-09-04 14:35:39 +00:00
func runGeneratePresignedURLCmd ( cmd * cobra . Command , _ [ ] string ) error {
2024-11-27 08:31:53 +00:00
ctx , cancel := context . WithTimeout ( cmd . Context ( ) , viper . GetDuration ( timeoutFlag ) )
defer cancel ( )
log := getLogger ( )
2024-09-04 14:35:39 +00:00
var (
region string
creds aws . Credentials
)
2023-06-23 13:11:57 +00:00
2024-09-04 14:35:39 +00:00
profile := viper . GetString ( profileFlag )
if profile == "" {
2024-11-27 08:31:53 +00:00
cfg , err := config . LoadDefaultConfig ( ctx )
2024-09-04 14:35:39 +00:00
if err != nil {
return wrapPreparationError ( err )
}
region = cfg . Region
2024-11-27 08:31:53 +00:00
if creds , err = cfg . Credentials . Retrieve ( ctx ) ; err != nil {
2024-09-04 14:35:39 +00:00
return wrapPreparationError ( fmt . Errorf ( "couldn't get default aws credentials: %w" , err ) )
}
} else {
2024-11-27 08:31:53 +00:00
cfg , err := config . LoadSharedConfigProfile ( ctx , viper . GetString ( profileFlag ) )
2024-09-04 14:35:39 +00:00
if err != nil {
return wrapPreparationError ( fmt . Errorf ( "couldn't get '%s' aws credentials: %w" , viper . GetString ( profileFlag ) , err ) )
}
region = cfg . Region
creds = cfg . Credentials
2023-06-23 13:11:57 +00:00
}
2024-09-04 14:35:39 +00:00
accessKeyIDArg := viper . GetString ( awsAccessKeyIDFlag )
secretAccessKeyArg := viper . GetString ( awsSecretAccessKeyFlag )
if accessKeyIDArg != "" && secretAccessKeyArg != "" {
creds . AccessKeyID = accessKeyIDArg
creds . SecretAccessKey = secretAccessKeyArg
2023-06-23 13:11:57 +00:00
}
2024-09-04 14:35:39 +00:00
if regionArg := viper . GetString ( regionFlag ) ; regionArg != "" {
region = regionArg
2023-06-23 13:11:57 +00:00
}
reqData := auth . RequestData {
Method : viper . GetString ( methodFlag ) ,
Endpoint : viper . GetString ( endpointFlag ) ,
Bucket : viper . GetString ( bucketFlag ) ,
Object : viper . GetString ( objectFlag ) ,
}
presignData := auth . PresignData {
Service : "s3" ,
2024-09-04 14:35:39 +00:00
Region : region ,
2023-06-23 13:11:57 +00:00
Lifetime : viper . GetDuration ( lifetimeFlag ) ,
SignTime : time . Now ( ) . UTC ( ) ,
}
2024-10-29 13:26:01 +00:00
headers , err := parseHeaders ( )
if err != nil {
return wrapPreparationError ( fmt . Errorf ( "failed to parse headers: %s" , err ) )
}
presignData . Headers = headers
2024-04-23 11:49:34 +00:00
var req * http . Request
if viper . GetBool ( sigV4AFlag ) {
2024-11-27 08:31:53 +00:00
req , err = auth . PresignRequestV4a ( creds , reqData , presignData , log )
2024-04-23 11:49:34 +00:00
} else {
2024-11-27 08:31:53 +00:00
req , err = auth . PresignRequest ( ctx , creds , reqData , presignData , log )
2024-04-23 11:49:34 +00:00
}
2023-06-23 13:11:57 +00:00
if err != nil {
2023-09-04 18:01:56 +00:00
return wrapBusinessLogicError ( err )
2023-06-23 13:11:57 +00:00
}
res := & struct { URL string } {
URL : req . URL . String ( ) ,
}
enc := json . NewEncoder ( os . Stdout )
enc . SetIndent ( "" , " " )
enc . SetEscapeHTML ( false )
2023-09-04 18:01:56 +00:00
err = enc . Encode ( res )
if err != nil {
return wrapBusinessLogicError ( err )
}
return nil
2023-06-23 13:11:57 +00:00
}
2024-10-29 13:26:01 +00:00
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
}