diff --git a/api/auth/center.go b/api/auth/center.go index fb610da..18a07ef 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -291,6 +291,7 @@ func cloneRequest(r *http.Request, authHeader *authHeader) *http.Request { func (c *center) checkSign(authHeader *authHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error { awsCreds := credentials.NewStaticCredentials(authHeader.AccessKeyID, box.Gate.AccessKey, "") signer := v4.NewSigner(awsCreds) + signer.DisableURIPathEscaping = true var signature string if authHeader.IsPresigned { @@ -306,7 +307,6 @@ func (c *center) checkSign(authHeader *authHeader, box *accessbox.Box, request * } signature = request.URL.Query().Get(AmzSignature) } else { - signer.DisableURIPathEscaping = true if _, err := signer.Sign(request, nil, authHeader.Service, authHeader.Region, signatureDateTime); err != nil { return fmt.Errorf("failed to sign temporary HTTP request: %w", err) } diff --git a/api/auth/presign.go b/api/auth/presign.go new file mode 100644 index 0000000..755ada9 --- /dev/null +++ b/api/auth/presign.go @@ -0,0 +1,46 @@ +package auth + +import ( + "fmt" + "net/http" + "strings" + "time" + + v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/private/protocol/rest" +) + +type RequestData struct { + Method string + Endpoint string + Bucket string + Object string +} + +type PresignData struct { + Service string + Region string + Lifetime time.Duration + SignTime time.Time +} + +// PresignRequest forms pre-signed request to access objects without aws credentials. +func PresignRequest(creds *credentials.Credentials, reqData RequestData, presignData PresignData) (*http.Request, error) { + urlStr := fmt.Sprintf("%s/%s/%s", reqData.Endpoint, rest.EscapePath(reqData.Bucket, false), rest.EscapePath(reqData.Object, false)) + req, err := http.NewRequest(strings.ToUpper(reqData.Method), urlStr, nil) + if err != nil { + return nil, fmt.Errorf("failed to create new request: %w", err) + } + + req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z")) + + signer := v4.NewSigner(creds) + signer.DisableURIPathEscaping = true + + if _, err = signer.Presign(req, nil, presignData.Service, presignData.Region, presignData.Lifetime, presignData.SignTime); err != nil { + return nil, fmt.Errorf("presign: %w", err) + } + + return req, nil +} diff --git a/api/auth/presign_test.go b/api/auth/presign_test.go new file mode 100644 index 0000000..898a6e9 --- /dev/null +++ b/api/auth/presign_test.go @@ -0,0 +1,91 @@ +package auth + +import ( + "context" + "strings" + "testing" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/stretchr/testify/require" +) + +var _ tokens.Credentials = (*credentialsMock)(nil) + +type credentialsMock struct { + boxes map[string]*accessbox.Box +} + +func newTokensFrostfsMock() *credentialsMock { + return &credentialsMock{ + boxes: make(map[string]*accessbox.Box), + } +} + +func (m credentialsMock) addBox(addr oid.Address, box *accessbox.Box) { + m.boxes[addr.String()] = box +} + +func (m credentialsMock) GetBox(_ context.Context, addr oid.Address) (*accessbox.Box, error) { + box, ok := m.boxes[addr.String()] + if !ok { + return nil, apistatus.ObjectNotFound{} + } + + return box, nil +} + +func (m credentialsMock) Put(context.Context, cid.ID, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error) { + return oid.Address{}, nil +} + +func TestCheckSign(t *testing.T) { + var accessKeyAddr oid.Address + err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto") + require.NoError(t, err) + + accessKeyID := strings.ReplaceAll(accessKeyAddr.String(), "/", "0") + secretKey := "713d0a0b9efc7d22923e17b0402a6a89b4273bc711c8bacb2da1b643d0006aeb" + awsCreds := credentials.NewStaticCredentials(accessKeyID, secretKey, "") + + reqData := RequestData{ + Method: "GET", + Endpoint: "http://localhost:8084", + Bucket: "my-bucket", + Object: "@obj/name", + } + presignData := PresignData{ + Service: "s3", + Region: "spb", + Lifetime: 10 * time.Minute, + SignTime: time.Now().UTC(), + } + + req, err := PresignRequest(awsCreds, reqData, presignData) + require.NoError(t, err) + + expBox := &accessbox.Box{ + Gate: &accessbox.GateData{ + AccessKey: secretKey, + }, + } + + mock := newTokensFrostfsMock() + mock.addBox(accessKeyAddr, expBox) + + c := ¢er{ + cli: mock, + reg: NewRegexpMatcher(authorizationFieldRegexp), + postReg: NewRegexpMatcher(postPolicyCredentialRegexp), + } + box, err := c.Authenticate(req) + require.NoError(t, err) + require.EqualValues(t, expBox, box.AccessBox) +} diff --git a/cmd/s3-authmate/main.go b/cmd/s3-authmate/main.go index 3ac0f4c..26a0599 100644 --- a/cmd/s3-authmate/main.go +++ b/cmd/s3-authmate/main.go @@ -5,7 +5,6 @@ import ( "crypto/ecdsa" "encoding/json" "fmt" - "net/http" "os" "os/signal" "runtime" @@ -14,6 +13,7 @@ import ( "time" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version" @@ -23,7 +23,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" - v4 "github.com/aws/aws-sdk-go/aws/signer/v4" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/spf13/viper" "github.com/urfave/cli/v2" @@ -482,17 +481,22 @@ It will be ceil rounded to the nearest amount of epoch.`, return fmt.Errorf("couldn't get credentials: %w", err) } - signer := v4.NewSigner(sess.Config.Credentials) - req, err := http.NewRequest(strings.ToUpper(methodFlag), fmt.Sprintf("%s/%s/%s", endpointFlag, bucketFlag, objectFlag), nil) - if err != nil { - return fmt.Errorf("failed to create new request: %w", err) + reqData := auth.RequestData{ + Method: methodFlag, + Endpoint: endpointFlag, + Bucket: bucketFlag, + Object: objectFlag, + } + presignData := auth.PresignData{ + Service: "s3", + Region: *sess.Config.Region, + Lifetime: lifetimeFlag, + SignTime: time.Now().UTC(), } - date := time.Now().UTC() - req.Header.Set(api.AmzDate, date.Format("20060102T150405Z")) - - if _, err = signer.Presign(req, nil, "s3", *sess.Config.Region, lifetimeFlag, date); err != nil { - return fmt.Errorf("presign: %w", err) + req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData) + if err != nil { + return err } res := &struct{ URL string }{