forked from TrueCloudLab/frostfs-s3-gw
697 lines
22 KiB
Go
697 lines
22 KiB
Go
package auth
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
|
"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/creds/accessbox"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
|
frostfsErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
"github.com/aws/smithy-go/logging"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap/zaptest"
|
|
)
|
|
|
|
func TestAuthHeaderParse(t *testing.T) {
|
|
defaultHeader := "AWS4-HMAC-SHA256 Credential=oid0cid/20210809/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=2811ccb9e242f41426738fb1f"
|
|
|
|
center := &Center{
|
|
reg: NewRegexpMatcher(authorizationFieldRegexp),
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
header string
|
|
err error
|
|
expected *AuthHeader
|
|
}{
|
|
{
|
|
header: defaultHeader,
|
|
err: nil,
|
|
expected: &AuthHeader{
|
|
AccessKeyID: "oid0cid",
|
|
Service: "s3",
|
|
Region: "us-east-1",
|
|
Signature: "2811ccb9e242f41426738fb1f",
|
|
SignedFields: []string{"host", "x-amz-content-sha256", "x-amz-date"},
|
|
Date: "20210809",
|
|
Preamble: signaturePreambleSigV4,
|
|
},
|
|
},
|
|
{
|
|
header: strings.ReplaceAll(defaultHeader, "Signature=2811ccb9e242f41426738fb1f", ""),
|
|
err: errors.GetAPIError(errors.ErrAuthorizationHeaderMalformed),
|
|
expected: nil,
|
|
},
|
|
{
|
|
header: strings.ReplaceAll(defaultHeader, "oid0cid", "oidcid"),
|
|
err: errors.GetAPIError(errors.ErrInvalidAccessKeyID),
|
|
expected: nil,
|
|
},
|
|
} {
|
|
authHeader, err := center.parseAuthHeader(tc.header, nil)
|
|
require.ErrorIs(t, err, tc.err, tc.header)
|
|
require.Equal(t, tc.expected, authHeader, tc.header)
|
|
}
|
|
}
|
|
|
|
func TestAuthHeaderGetAddress(t *testing.T) {
|
|
defaulErr := errors.GetAPIError(errors.ErrInvalidAccessKeyID)
|
|
|
|
for _, tc := range []struct {
|
|
authHeader *AuthHeader
|
|
err error
|
|
}{
|
|
{
|
|
authHeader: &AuthHeader{
|
|
AccessKeyID: "vWqF8cMDRbJcvnPLALoQGnABPPhw8NyYMcGsfDPfZJM0HrgjonN8CgFvCZ3kh9BUXw4W2tJ5E7EAGhueSF122HB",
|
|
},
|
|
err: nil,
|
|
},
|
|
{
|
|
authHeader: &AuthHeader{
|
|
AccessKeyID: "vWqF8cMDRbJcvnPLALoQGnABPPhw8NyYMcGsfDPfZJMHrgjonN8CgFvCZ3kh9BUXw4W2tJ5E7EAGhueSF122HB",
|
|
},
|
|
err: defaulErr,
|
|
},
|
|
{
|
|
authHeader: &AuthHeader{
|
|
AccessKeyID: "oid0cid",
|
|
},
|
|
err: defaulErr,
|
|
},
|
|
{
|
|
authHeader: &AuthHeader{
|
|
AccessKeyID: "oidcid",
|
|
},
|
|
err: defaulErr,
|
|
},
|
|
} {
|
|
_, err := getAddress(tc.authHeader.AccessKeyID)
|
|
require.ErrorIs(t, err, tc.err, tc.authHeader.AccessKeyID)
|
|
}
|
|
}
|
|
|
|
func TestSignature(t *testing.T) {
|
|
secret := "66be461c3cd429941c55daf42fad2b8153e5a2016ba89c9494d97677cc9d3872"
|
|
strToSign := "eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLAogICJjb25kaXRpb25zIjogWwogICAgeyJidWNrZXQiOiAiYWNsIn0sCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwKICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL2xvY2FsaG9zdDo4MDg0L2FjbCJ9LAogICAgWyJzdGFydHMtd2l0aCIsICIkQ29udGVudC1UeXBlIiwgImltYWdlLyJdLAogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwKICAgIFsic3RhcnRzLXdpdGgiLCAiJHgtYW16LW1ldGEtdGFnIiwgIiJdLAoKICAgIHsiWC1BbXotQ3JlZGVudGlhbCI6ICI4Vmk0MVBIbjVGMXNzY2J4OUhqMXdmMUU2aERUYURpNndxOGhxTU05NllKdTA1QzVDeUVkVlFoV1E2aVZGekFpTkxXaTlFc3BiUTE5ZDRuR3pTYnZVZm10TS8yMDE1MTIyOS91cy1lYXN0LTEvczMvYXdzNF9yZXF1ZXN0In0sCiAgICB7IngtYW16LWFsZ29yaXRobSI6ICJBV1M0LUhNQUMtU0hBMjU2In0sCiAgICB7IlgtQW16LURhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfSwKICAgIHsieC1pZ25vcmUtdG1wIjogInNvbWV0aGluZyIgfQogIF0KfQ=="
|
|
|
|
signTime, err := time.Parse("20060102T150405Z", "20151229T000000Z")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
signature := SignStr(secret, "s3", "us-east-1", signTime, strToSign)
|
|
require.Equal(t, "dfbe886241d9e369cf4b329ca0f15eb27306c97aa1022cc0bb5a914c4ef87634", signature)
|
|
}
|
|
|
|
func TestSignatureV4A(t *testing.T) {
|
|
accessKeyID := "2XEbqH4M3ym7a3E3esxfZ2gRLnMwDXrCN4y1SkQg5fHa09sThVmVL3EE6xeKsyMzaqu5jPi41YCaVbnwbwCTF3bx1"
|
|
secretKey := "00637f53f842573aaa06c2164c598973cd986880987111416cf71f1619def537"
|
|
|
|
signer := v4a.NewSigner(func(options *v4a.SignerOptions) {
|
|
options.DisableURIPathEscaping = true
|
|
options.Logger = logging.NewStandardLogger(os.Stdout)
|
|
options.LogSigning = true
|
|
})
|
|
|
|
credAdapter := v4a.SymmetricCredentialAdaptor{
|
|
SymmetricProvider: credentials.NewStaticCredentialsProvider(accessKeyID, secretKey, ""),
|
|
}
|
|
|
|
bodyStr := `
|
|
1b;chunk-signature=3045022100b63692a1b20759bdabd342011823427a8952df75c93174d98ad043abca8052e002201695228a91ba986171b8d0ad20856d3d94ca3614d0a90a50a531ba8e52447b9b**
|
|
Testing with the {sdk-java}
|
|
0;chunk-signature=30440220455885a2d4e9f705256ca6b0a5a22f7f784780ccbd1c0a371e5db3059c91745b022073259dd44746cbd63261d628a04d25be5a32a974c077c5c2d83c8157fb323b9f****
|
|
|
|
`
|
|
body := bytes.NewBufferString(bodyStr)
|
|
|
|
req, err := http.NewRequest("PUT", "http://localhost:8084/test/tmp", body)
|
|
require.NoError(t, err)
|
|
req.Header.Set("Amz-Sdk-Invocation-Id", "ca3a3cde-7d26-fce6-ed9c-82f7a0573824")
|
|
req.Header.Set("Amz-Sdk-Request", "attempt=2; max=2")
|
|
req.Header.Set("Authorization", "AWS4-ECDSA-P256-SHA256 Credential=2XEbqH4M3ym7a3E3esxfZ2gRLnMwDXrCN4y1SkQg5fHa09sThVmVL3EE6xeKsyMzaqu5jPi41YCaVbnwbwCTF3bx1/20240904/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-region-set, Signature=30440220574244c5ff5deba388c4e3b0541a42113179b6839b3e6b4212d255a118fa9089022056f7b9b72c93f67dbcd25fe9ca67950b5913fc00bb7a62bc276c21e828c0b6c7")
|
|
req.Header.Set("Content-Length", "360")
|
|
req.Header.Set("Content-Type", "text/plain; charset=UTF-8")
|
|
req.Header.Set("X-Amz-Content-Sha256", "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD")
|
|
req.Header.Set("X-Amz-Date", "20240904T133253Z")
|
|
req.Header.Set("X-Amz-Decoded-Content-Length", "27")
|
|
req.Header.Set("X-Amz-Region-Set", "us-east-1")
|
|
|
|
service := "s3"
|
|
regionSet := []string{"us-east-1"}
|
|
signature := "30440220574244c5ff5deba388c4e3b0541a42113179b6839b3e6b4212d255a118fa9089022056f7b9b72c93f67dbcd25fe9ca67950b5913fc00bb7a62bc276c21e828c0b6c7"
|
|
signingTime, err := time.Parse("20060102T150405Z", "20240904T133253Z")
|
|
require.NoError(t, err)
|
|
creds, err := credAdapter.RetrievePrivateKey(req.Context())
|
|
require.NoError(t, err)
|
|
|
|
err = signer.VerifySignature(creds, req, "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD", service, regionSet, signingTime, signature)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestCheckFormatContentSHA256(t *testing.T) {
|
|
defaultErr := errors.GetAPIError(errors.ErrContentSHA256Mismatch)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
hash string
|
|
error error
|
|
}{
|
|
{
|
|
name: "invalid hash format: length and character",
|
|
hash: "invalid-hash",
|
|
error: defaultErr,
|
|
},
|
|
{
|
|
name: "invalid hash format: length (63 characters)",
|
|
hash: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f7",
|
|
error: defaultErr,
|
|
},
|
|
{
|
|
name: "invalid hash format: character",
|
|
hash: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f7s",
|
|
error: defaultErr,
|
|
},
|
|
{
|
|
name: "invalid hash format: hash size",
|
|
hash: "5aadb45520dcd8726b2822a7a78bb53d794f557199d5d4abdedd2c55a4bd6ca73607605c558de3db80c8e86c3196484566163ed1327e82e8b6757d1932113cb8",
|
|
error: defaultErr,
|
|
},
|
|
{
|
|
name: "unsigned payload",
|
|
hash: "UNSIGNED-PAYLOAD",
|
|
error: nil,
|
|
},
|
|
{
|
|
name: "no hash",
|
|
hash: "",
|
|
error: nil,
|
|
},
|
|
{
|
|
name: "correct hash format",
|
|
hash: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73",
|
|
error: nil,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := checkFormatHashContentSHA256(tc.hash)
|
|
require.ErrorIs(t, err, tc.error)
|
|
})
|
|
}
|
|
}
|
|
|
|
type frostFSMock struct {
|
|
objects map[oid.Address]*object.Object
|
|
}
|
|
|
|
func newFrostFSMock() *frostFSMock {
|
|
return &frostFSMock{
|
|
objects: map[oid.Address]*object.Object{},
|
|
}
|
|
}
|
|
|
|
func (f *frostFSMock) GetCredsObject(_ context.Context, address oid.Address) (*object.Object, error) {
|
|
obj, ok := f.objects[address]
|
|
if !ok {
|
|
return nil, fmt.Errorf("not found")
|
|
}
|
|
|
|
return obj, nil
|
|
}
|
|
|
|
func (f *frostFSMock) CreateObject(context.Context, tokens.PrmObjectCreate) (oid.ID, error) {
|
|
return oid.ID{}, fmt.Errorf("the mock method is not implemented")
|
|
}
|
|
|
|
func TestAuthenticate(t *testing.T) {
|
|
ctx := context.Background()
|
|
key, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
cfg := &cache.Config{
|
|
Size: 10,
|
|
Lifetime: 24 * time.Hour,
|
|
Logger: zaptest.NewLogger(t),
|
|
}
|
|
|
|
gateData := []*accessbox.GateData{{
|
|
BearerToken: &bearer.Token{},
|
|
GateKey: key.PublicKey(),
|
|
}}
|
|
|
|
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"))
|
|
require.NoError(t, err)
|
|
data, err := accessBox.Marshal()
|
|
require.NoError(t, err)
|
|
|
|
var obj object.Object
|
|
obj.SetPayload(data)
|
|
addr := oidtest.Address()
|
|
obj.SetContainerID(addr.Container())
|
|
obj.SetID(addr.Object())
|
|
|
|
frostfs := newFrostFSMock()
|
|
frostfs.objects[addr] = &obj
|
|
|
|
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
|
|
|
awsCreds := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: secret.SecretKey}
|
|
defaultSigner := v4.NewSigner()
|
|
|
|
service, region := "s3", "default"
|
|
invalidValue := "invalid-value"
|
|
|
|
bigConfig := tokens.Config{
|
|
FrostFS: frostfs,
|
|
Key: key,
|
|
CacheConfig: cfg,
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
prefixes []string
|
|
request *http.Request
|
|
err bool
|
|
errCode errors.ErrorCode
|
|
}{
|
|
{
|
|
name: "valid sign",
|
|
prefixes: []string{addr.Container().String()},
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
},
|
|
{
|
|
name: "no authorization header",
|
|
request: func() *http.Request {
|
|
return httptest.NewRequest(http.MethodPost, "/", nil)
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "invalid authorization header",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
r.Header.Set(AuthorizationHdr, invalidValue)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrAuthorizationHeaderMalformed,
|
|
},
|
|
{
|
|
name: "invalid access key id format",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
cred := aws.Credentials{AccessKeyID: addr.Object().String(), SecretAccessKey: secret.SecretKey}
|
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrInvalidAccessKeyID,
|
|
},
|
|
{
|
|
name: "not allowed access key id",
|
|
prefixes: []string{addr.Object().String()},
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrAccessDenied,
|
|
},
|
|
{
|
|
name: "invalid access key id value",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
cred := aws.Credentials{AccessKeyID: accessKeyID[:len(accessKeyID)-4], SecretAccessKey: secret.SecretKey}
|
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrInvalidAccessKeyID,
|
|
},
|
|
{
|
|
name: "unknown access key id",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
cred := aws.Credentials{AccessKeyID: addr.Object().String() + "0" + addr.Container().String(), SecretAccessKey: secret.SecretKey}
|
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "invalid signature",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
cred := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: "secret"}
|
|
err = v4.NewSigner().SignHTTP(ctx, cred, r, "", service, region, time.Now())
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrSignatureDoesNotMatch,
|
|
},
|
|
{
|
|
name: "invalid signature - AmzDate",
|
|
prefixes: []string{addr.Container().String()},
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
|
r.Header.Set(AmzDate, invalidValue)
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "invalid AmzContentSHA256",
|
|
prefixes: []string{addr.Container().String()},
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
|
r.Header.Set(AmzContentSHA256, invalidValue)
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "valid presign",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
r.Header.Set(AmzExpires, "60")
|
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
|
require.NoError(t, err)
|
|
r.URL, err = url.ParseRequestURI(signedURI)
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
},
|
|
{
|
|
name: "presign, bad X-Amz-Credential",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
query := url.Values{
|
|
AmzAlgorithm: []string{"AWS4-HMAC-SHA256"},
|
|
AmzCredential: []string{invalidValue},
|
|
}
|
|
r.URL.RawQuery = query.Encode()
|
|
return r
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "presign, bad X-Amz-Expires",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
r.Header.Set(AmzExpires, invalidValue)
|
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now())
|
|
require.NoError(t, err)
|
|
r.URL, err = url.ParseRequestURI(signedURI)
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrMalformedExpires,
|
|
},
|
|
{
|
|
name: "presign, expired",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
r.Header.Set(AmzExpires, "60")
|
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now().Add(-time.Minute))
|
|
require.NoError(t, err)
|
|
r.URL, err = url.ParseRequestURI(signedURI)
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrExpiredPresignRequest,
|
|
},
|
|
{
|
|
name: "presign, signature from future",
|
|
request: func() *http.Request {
|
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
r.Header.Set(AmzExpires, "60")
|
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now().Add(time.Minute))
|
|
require.NoError(t, err)
|
|
r.URL, err = url.ParseRequestURI(signedURI)
|
|
require.NoError(t, err)
|
|
return r
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrBadRequest,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
creds := tokens.New(bigConfig)
|
|
cntr := New(creds, tc.prefixes)
|
|
box, err := cntr.Authenticate(tc.request)
|
|
|
|
if tc.err {
|
|
require.Error(t, err)
|
|
if tc.errCode > 0 {
|
|
err = frostfsErrors.UnwrapErr(err)
|
|
require.Equal(t, errors.GetAPIError(tc.errCode), err)
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, accessKeyID, box.AuthHeaders.AccessKeyID)
|
|
require.Equal(t, region, box.AuthHeaders.Region)
|
|
require.Equal(t, secret.SecretKey, box.AccessBox.Gate.SecretKey)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHTTPPostAuthenticate(t *testing.T) {
|
|
const (
|
|
policyBase64 = "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXQpdfQ=="
|
|
invalidValue = "invalid-value"
|
|
defaultFieldName = "file"
|
|
service = "s3"
|
|
region = "default"
|
|
)
|
|
|
|
key, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
cfg := &cache.Config{
|
|
Size: 10,
|
|
Lifetime: 24 * time.Hour,
|
|
Logger: zaptest.NewLogger(t),
|
|
}
|
|
|
|
gateData := []*accessbox.GateData{{
|
|
BearerToken: &bearer.Token{},
|
|
GateKey: key.PublicKey(),
|
|
}}
|
|
|
|
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"))
|
|
require.NoError(t, err)
|
|
data, err := accessBox.Marshal()
|
|
require.NoError(t, err)
|
|
|
|
var obj object.Object
|
|
obj.SetPayload(data)
|
|
addr := oidtest.Address()
|
|
obj.SetContainerID(addr.Container())
|
|
obj.SetID(addr.Object())
|
|
|
|
frostfs := newFrostFSMock()
|
|
frostfs.objects[addr] = &obj
|
|
|
|
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
|
invalidAccessKeyID := oidtest.Address().String() + "0" + oidtest.Address().Object().String()
|
|
|
|
timeToSign := time.Now()
|
|
timeToSignStr := timeToSign.Format("20060102T150405Z")
|
|
|
|
bigConfig := tokens.Config{
|
|
FrostFS: frostfs,
|
|
Key: key,
|
|
CacheConfig: cfg,
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
prefixes []string
|
|
request *http.Request
|
|
err bool
|
|
errCode errors.ErrorCode
|
|
}{
|
|
{
|
|
name: "HTTP POST valid",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(accessKeyID, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, creds, timeToSignStr, sign, defaultFieldName)
|
|
}(),
|
|
},
|
|
{
|
|
name: "HTTP POST valid with custom field name",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(accessKeyID, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, creds, timeToSignStr, sign, "files")
|
|
}(),
|
|
},
|
|
{
|
|
name: "HTTP POST valid with field name with a capital letter",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(accessKeyID, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, creds, timeToSignStr, sign, "File")
|
|
}(),
|
|
},
|
|
{
|
|
name: "HTTP POST invalid multipart form",
|
|
request: func() *http.Request {
|
|
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
req.Header.Set(ContentTypeHdr, "multipart/form-data")
|
|
|
|
return req
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrInvalidArgument,
|
|
},
|
|
{
|
|
name: "HTTP POST invalid signature date time",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(accessKeyID, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, creds, invalidValue, sign, defaultFieldName)
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "HTTP POST invalid creds",
|
|
request: func() *http.Request {
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, invalidValue, timeToSignStr, sign, defaultFieldName)
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrAuthorizationHeaderMalformed,
|
|
},
|
|
{
|
|
name: "HTTP POST missing policy",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(accessKeyID, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, "", creds, timeToSignStr, sign, defaultFieldName)
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "HTTP POST invalid accessKeyId",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(invalidValue, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, creds, timeToSignStr, sign, defaultFieldName)
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "HTTP POST invalid accessKeyId - a non-existent box",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(invalidAccessKeyID, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, policyBase64)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, creds, timeToSignStr, sign, defaultFieldName)
|
|
}(),
|
|
err: true,
|
|
},
|
|
{
|
|
name: "HTTP POST invalid signature",
|
|
request: func() *http.Request {
|
|
creds := getCredsStr(accessKeyID, timeToSignStr, region, service)
|
|
sign := SignStr(secret.SecretKey, service, region, timeToSign, invalidValue)
|
|
|
|
return getRequestWithMultipartForm(t, policyBase64, creds, timeToSignStr, sign, defaultFieldName)
|
|
}(),
|
|
err: true,
|
|
errCode: errors.ErrSignatureDoesNotMatch,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
creds := tokens.New(bigConfig)
|
|
cntr := New(creds, tc.prefixes)
|
|
box, err := cntr.Authenticate(tc.request)
|
|
|
|
if tc.err {
|
|
require.Error(t, err)
|
|
if tc.errCode > 0 {
|
|
err = frostfsErrors.UnwrapErr(err)
|
|
require.Equal(t, errors.GetAPIError(tc.errCode), err)
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, secret.SecretKey, box.AccessBox.Gate.SecretKey)
|
|
require.Equal(t, accessKeyID, box.AuthHeaders.AccessKeyID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func getCredsStr(accessKeyID, timeToSign, region, service string) string {
|
|
return accessKeyID + "/" + timeToSign + "/" + region + "/" + service + "/aws4_request"
|
|
}
|
|
|
|
func getRequestWithMultipartForm(t *testing.T, policy, creds, date, sign, fieldName string) *http.Request {
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
defer writer.Close()
|
|
|
|
err := writer.WriteField("policy", policy)
|
|
require.NoError(t, err)
|
|
err = writer.WriteField(AmzCredential, creds)
|
|
require.NoError(t, err)
|
|
err = writer.WriteField(AmzDate, date)
|
|
require.NoError(t, err)
|
|
err = writer.WriteField(AmzSignature, sign)
|
|
require.NoError(t, err)
|
|
_, err = writer.CreateFormFile(fieldName, "test.txt")
|
|
require.NoError(t, err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/", body)
|
|
req.Header.Set(ContentTypeHdr, writer.FormDataContentType())
|
|
|
|
return req
|
|
}
|