forked from TrueCloudLab/frostfs-s3-gw
[#339] sigv4a: Support presign
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
cc9a68401f
commit
8da71c3ae0
6 changed files with 56 additions and 26 deletions
|
@ -9,7 +9,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,7 +23,6 @@ import (
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
"github.com/aws/smithy-go/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -426,8 +424,6 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *
|
||||||
case signaturePreambleSigV4A:
|
case signaturePreambleSigV4A:
|
||||||
signer := v4a.NewSigner(func(options *v4a.SignerOptions) {
|
signer := v4a.NewSigner(func(options *v4a.SignerOptions) {
|
||||||
options.DisableURIPathEscaping = true
|
options.DisableURIPathEscaping = true
|
||||||
options.LogSigning = true
|
|
||||||
options.Logger = logging.NewStandardLogger(os.Stdout)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
credAdapter := v4a.SymmetricCredentialAdaptor{
|
credAdapter := v4a.SymmetricCredentialAdaptor{
|
||||||
|
@ -439,13 +435,16 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *
|
||||||
return fmt.Errorf("failed to derive assymetric key from credentials: %w", err)
|
return fmt.Errorf("failed to derive assymetric key from credentials: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if authHeader.IsPresigned {
|
if !authHeader.IsPresigned {
|
||||||
|
return signer.VerifySignature(creds, request, authHeader.PayloadHash, authHeader.Service,
|
||||||
|
strings.Split(authHeader.Region, ","), signatureDateTime, authHeader.Signature)
|
||||||
|
}
|
||||||
|
|
||||||
if err = checkPresignedDate(authHeader, signatureDateTime); err != nil {
|
if err = checkPresignedDate(authHeader, signatureDateTime); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return signer.VerifySignature(creds, request, authHeader.PayloadHash, authHeader.Service,
|
return signer.VerifyPresigned(creds, request, authHeader.PayloadHash, authHeader.Service,
|
||||||
strings.Split(authHeader.Region, ","), signatureDateTime, authHeader.Signature)
|
strings.Split(authHeader.Region, ","), signatureDateTime, authHeader.Signature)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid preamble: %s", authHeader.Preamble)
|
return fmt.Errorf("invalid preamble: %s", authHeader.Preamble)
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
|
||||||
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
||||||
|
@ -24,10 +24,9 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||||
|
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
||||||
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
@ -66,6 +65,7 @@ func TestAuthHeaderParse(t *testing.T) {
|
||||||
Signature: "2811ccb9e242f41426738fb1f",
|
Signature: "2811ccb9e242f41426738fb1f",
|
||||||
SignedFields: []string{"host", "x-amz-content-sha256", "x-amz-date"},
|
SignedFields: []string{"host", "x-amz-content-sha256", "x-amz-date"},
|
||||||
Date: "20210809",
|
Date: "20210809",
|
||||||
|
Preamble: signaturePreambleSigV4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,11 +39,10 @@ func PresignRequest(creds *credentials.Credentials, reqData RequestData, presign
|
||||||
return nil, fmt.Errorf("failed to create new request: %w", err)
|
return nil, fmt.Errorf("failed to create new request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
|
||||||
|
|
||||||
for k, v := range presignData.Headers {
|
for k, v := range presignData.Headers {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v) // maybe we should filter system header (or keep responsibility on caller)
|
||||||
}
|
}
|
||||||
|
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
||||||
|
|
||||||
signer := v4.NewSigner(creds)
|
signer := v4.NewSigner(creds)
|
||||||
signer.DisableURIPathEscaping = true
|
signer.DisableURIPathEscaping = true
|
||||||
|
@ -63,8 +62,11 @@ func PresignRequestV4a(credProvider credentialsv2.StaticCredentialsProvider, req
|
||||||
return nil, fmt.Errorf("failed to create new request: %w", err)
|
return nil, fmt.Errorf("failed to create new request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for k, v := range presignData.Headers {
|
||||||
|
req.Header.Set(k, v) // maybe we should filter system header (or keep responsibility on caller)
|
||||||
|
}
|
||||||
|
|
||||||
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
||||||
req.Header.Set(ContentTypeHdr, "text/plain")
|
|
||||||
req.Header.Set(AmzExpires, strconv.Itoa(int(presignData.Lifetime.Seconds())))
|
req.Header.Set(AmzExpires, strconv.Itoa(int(presignData.Lifetime.Seconds())))
|
||||||
|
|
||||||
signer := v4a.NewSigner(func(options *v4a.SignerOptions) {
|
signer := v4a.NewSigner(func(options *v4a.SignerOptions) {
|
||||||
|
|
|
@ -75,6 +75,9 @@ func TestCheckSign(t *testing.T) {
|
||||||
Region: "spb",
|
Region: "spb",
|
||||||
Lifetime: 10 * time.Minute,
|
Lifetime: 10 * time.Minute,
|
||||||
SignTime: time.Now().UTC(),
|
SignTime: time.Now().UTC(),
|
||||||
|
Headers: map[string]string{
|
||||||
|
ContentTypeHdr: "text/plain",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := PresignRequest(awsCreds, reqData, presignData)
|
req, err := PresignRequest(awsCreds, reqData, presignData)
|
||||||
|
@ -120,11 +123,16 @@ func TestCheckSignV4a(t *testing.T) {
|
||||||
Region: "spb",
|
Region: "spb",
|
||||||
Lifetime: 10 * time.Minute,
|
Lifetime: 10 * time.Minute,
|
||||||
SignTime: time.Now().UTC(),
|
SignTime: time.Now().UTC(),
|
||||||
|
Headers: map[string]string{
|
||||||
|
ContentTypeHdr: "text/plain",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := PresignRequestV4a(awsCreds, reqData, presignData)
|
req, err := PresignRequestV4a(awsCreds, reqData, presignData)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req.Header.Set(ContentTypeHdr, "text/plain")
|
||||||
|
|
||||||
expBox := &accessbox.Box{
|
expBox := &accessbox.Box{
|
||||||
Gate: &accessbox.GateData{
|
Gate: &accessbox.GateData{
|
||||||
SecretKey: secretKey,
|
SecretKey: secretKey,
|
||||||
|
@ -171,9 +179,7 @@ func TestPresignRequestV4a(t *testing.T) {
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://localhost:8084/bucket/object", nil)
|
req, err := http.NewRequest("GET", "http://localhost:8084/bucket/object", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
//req.Header.Set(AmzRegionSet, strings.Join(regionSet, ","))
|
req.Header.Set(AmzExpires, "600")
|
||||||
//req.Header.Set(AmzDate, signingTime.Format("20060102T150405Z"))
|
|
||||||
//req.Header.Set(AmzAlgorithm, signaturePreambleSigV4A)
|
|
||||||
|
|
||||||
presignedURL, hdr, err := signer.PresignHTTP(req.Context(), creds, req, "", service, regionSet, signingTime)
|
presignedURL, hdr, err := signer.PresignHTTP(req.Context(), creds, req, "", service, regionSet, signingTime)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -185,7 +191,10 @@ func TestPresignRequestV4a(t *testing.T) {
|
||||||
|
|
||||||
r, err := http.NewRequest("GET", presignedURL, nil)
|
r, err := http.NewRequest("GET", presignedURL, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
query := r.URL.Query()
|
||||||
|
query.Del(AmzSignature)
|
||||||
|
r.URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
err = signer.VerifySignature(creds, r, "", service, regionSet, signingTime, signature)
|
err = signer.VerifyPresigned(creds, r, "", service, regionSet, signingTime, signature)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,6 +202,15 @@ func (s *Signer) SignHTTP(ctx context.Context, credentials Credentials, r *http.
|
||||||
|
|
||||||
// VerifySignature checks sigv4a.
|
// VerifySignature checks sigv4a.
|
||||||
func (s *Signer) VerifySignature(credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, signature string, optFns ...func(*SignerOptions)) error {
|
func (s *Signer) VerifySignature(credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, signature string, optFns ...func(*SignerOptions)) error {
|
||||||
|
return s.verifySignature(credentials, r, payloadHash, service, regionSet, signingTime, signature, false, optFns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPresigned checks sigv4a.
|
||||||
|
func (s *Signer) VerifyPresigned(credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, signature string, optFns ...func(*SignerOptions)) error {
|
||||||
|
return s.verifySignature(credentials, r, payloadHash, service, regionSet, signingTime, signature, true, optFns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Signer) verifySignature(credentials Credentials, r *http.Request, payloadHash string, service string, regionSet []string, signingTime time.Time, signature string, isPresigned bool, optFns ...func(*SignerOptions)) error {
|
||||||
options := s.options
|
options := s.options
|
||||||
for _, fn := range optFns {
|
for _, fn := range optFns {
|
||||||
fn(&options)
|
fn(&options)
|
||||||
|
@ -214,6 +223,7 @@ func (s *Signer) VerifySignature(credentials Credentials, r *http.Request, paylo
|
||||||
RegionSet: regionSet,
|
RegionSet: regionSet,
|
||||||
Credentials: credentials,
|
Credentials: credentials,
|
||||||
Time: signingTime.UTC(),
|
Time: signingTime.UTC(),
|
||||||
|
IsPreSign: isPresigned,
|
||||||
DisableHeaderHoisting: options.DisableHeaderHoisting,
|
DisableHeaderHoisting: options.DisableHeaderHoisting,
|
||||||
DisableURIPathEscaping: options.DisableURIPathEscaping,
|
DisableURIPathEscaping: options.DisableURIPathEscaping,
|
||||||
}
|
}
|
||||||
|
@ -465,11 +475,6 @@ func (s *httpSigner) buildCanonicalHeaders(host string, rule v4Internal.Rule, he
|
||||||
continue // ignored header
|
continue // ignored header
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.EqualFold(k, contentLengthHeader) {
|
|
||||||
// prevent signing already handled content-length header.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lowerCaseKey := strings.ToLower(k)
|
lowerCaseKey := strings.ToLower(k)
|
||||||
if _, ok := signed[lowerCaseKey]; ok {
|
if _, ok := signed[lowerCaseKey]; ok {
|
||||||
// include additional values
|
// include additional values
|
||||||
|
|
|
@ -3,11 +3,13 @@ package modules
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
|
credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
@ -38,6 +40,7 @@ const (
|
||||||
awsAccessKeyIDFlag = "aws-access-key-id"
|
awsAccessKeyIDFlag = "aws-access-key-id"
|
||||||
awsSecretAccessKeyFlag = "aws-secret-access-key"
|
awsSecretAccessKeyFlag = "aws-secret-access-key"
|
||||||
headerFlag = "header"
|
headerFlag = "header"
|
||||||
|
sigV4AFlag = "sigv4a"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initGeneratePresignedURLCmd() {
|
func initGeneratePresignedURLCmd() {
|
||||||
|
@ -51,6 +54,7 @@ func initGeneratePresignedURLCmd() {
|
||||||
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.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(endpointFlag)
|
||||||
_ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag)
|
_ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag)
|
||||||
|
@ -101,7 +105,18 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error {
|
||||||
}
|
}
|
||||||
presignData.Headers = headers
|
presignData.Headers = headers
|
||||||
|
|
||||||
req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData)
|
var req *http.Request
|
||||||
|
if viper.GetBool(sigV4AFlag) {
|
||||||
|
val, err := sess.Config.Credentials.Get()
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
awsCreds := credentialsv2.NewStaticCredentialsProvider(val.AccessKeyID, val.SecretAccessKey, "")
|
||||||
|
req, err = auth.PresignRequestV4a(awsCreds, reqData, presignData)
|
||||||
|
} else {
|
||||||
|
req, err = auth.PresignRequest(sess.Config.Credentials, reqData, presignData)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapBusinessLogicError(err)
|
return wrapBusinessLogicError(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue