forked from TrueCloudLab/frostfs-s3-gw
Compare commits
33 commits
Author | SHA1 | Date | |
---|---|---|---|
17d40245de | |||
979d85b046 | |||
539dab8680 | |||
76008d4ba1 | |||
8bc19725ba | |||
9e64304499 | |||
94504e9746 | |||
a8458dbc27 | |||
424038de6c | |||
3cf27d281d | |||
3c7cb82553 | |||
57b7e83380 | |||
6a90f4e624 | |||
cb3753f286 | |||
81209e308c | |||
b78e55e101 | |||
25c24f5ce6 | |||
09c11262c6 | |||
f120715a37 | |||
aaed083d82 | |||
e35b582fe2 | |||
39fc7aa3ee | |||
da41f47826 | |||
9e5fb4be95 | |||
346243b159 | |||
03481274f0 | |||
c2adbd758a | |||
bc17ab5e47 | |||
9fadfbbc2f | |||
827ea1a41e | |||
968f10a72f | |||
582e6ac642 | |||
99f273f9af |
115 changed files with 3442 additions and 1680 deletions
|
@ -8,9 +8,13 @@ This document outlines major changes between releases.
|
||||||
- Add support for virtual hosted style addressing (#446, #449)
|
- Add support for virtual hosted style addressing (#446, #449)
|
||||||
- Support new param `frostfs.graceful_close_on_switch_timeout` (#475)
|
- Support new param `frostfs.graceful_close_on_switch_timeout` (#475)
|
||||||
- Support patch object method (#479)
|
- Support patch object method (#479)
|
||||||
|
- Add `sign` command to `frostfs-s3-authmate` (#467)
|
||||||
|
- Support custom aws credentials (#509)
|
||||||
|
- Multinet dial support (#521)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Update go version to go1.19 (#470)
|
- Update go version to go1.19 (#470)
|
||||||
|
- Avoid maintenance mode storage node during object operations (#524)
|
||||||
|
|
||||||
## [0.30.0] - Kangshung -2024-07-19
|
## [0.30.0] - Kangshung -2024-07-19
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,11 @@ 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"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"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"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
)
|
)
|
||||||
|
@ -34,6 +35,11 @@ type (
|
||||||
postReg *RegexpSubmatcher
|
postReg *RegexpSubmatcher
|
||||||
cli tokens.Credentials
|
cli tokens.Credentials
|
||||||
allowedAccessKeyIDPrefixes []string // empty slice means all access key ids are allowed
|
allowedAccessKeyIDPrefixes []string // empty slice means all access key ids are allowed
|
||||||
|
settings CenterSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
CenterSettings interface {
|
||||||
|
AccessBoxContainer() (cid.ID, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:revive
|
//nolint:revive
|
||||||
|
@ -50,7 +56,6 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
accessKeyPartsNum = 2
|
|
||||||
authHeaderPartsNum = 6
|
authHeaderPartsNum = 6
|
||||||
maxFormSizeMemory = 50 * 1048576 // 50 MB
|
maxFormSizeMemory = 50 * 1048576 // 50 MB
|
||||||
|
|
||||||
|
@ -82,24 +87,20 @@ var ContentSHA256HeaderStandardValue = map[string]struct{}{
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an instance of AuthCenter.
|
// New creates an instance of AuthCenter.
|
||||||
func New(creds tokens.Credentials, prefixes []string) *Center {
|
func New(creds tokens.Credentials, prefixes []string, settings CenterSettings) *Center {
|
||||||
return &Center{
|
return &Center{
|
||||||
cli: creds,
|
cli: creds,
|
||||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||||
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
|
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
|
||||||
allowedAccessKeyIDPrefixes: prefixes,
|
allowedAccessKeyIDPrefixes: prefixes,
|
||||||
|
settings: settings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
||||||
submatches := c.reg.GetSubmatches(header)
|
submatches := c.reg.GetSubmatches(header)
|
||||||
if len(submatches) != authHeaderPartsNum {
|
if len(submatches) != authHeaderPartsNum {
|
||||||
return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed), header)
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrAuthorizationHeaderMalformed), header)
|
||||||
}
|
|
||||||
|
|
||||||
accessKey := strings.Split(submatches["access_key_id"], "0")
|
|
||||||
if len(accessKey) != accessKeyPartsNum {
|
|
||||||
return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID), accessKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signedFields := strings.Split(submatches["signed_header_fields"], ";")
|
signedFields := strings.Split(submatches["signed_header_fields"], ";")
|
||||||
|
@ -114,15 +115,6 @@ func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAddress(accessKeyID string) (oid.Address, error) {
|
|
||||||
var addr oid.Address
|
|
||||||
if err := addr.DecodeString(strings.ReplaceAll(accessKeyID, "0", "/")); err != nil {
|
|
||||||
return addr, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID), accessKeyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsStandardContentSHA256(key string) bool {
|
func IsStandardContentSHA256(key string) bool {
|
||||||
_, ok := ContentSHA256HeaderStandardValue[key]
|
_, ok := ContentSHA256HeaderStandardValue[key]
|
||||||
return ok
|
return ok
|
||||||
|
@ -181,14 +173,14 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := getAddress(authHdr.AccessKeyID)
|
cnrID, err := c.getAccessBoxContainer(authHdr.AccessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
box, attrs, err := c.cli.GetBox(r.Context(), addr)
|
box, attrs, err := c.cli.GetBox(r.Context(), cnrID, authHdr.AccessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get box '%s': %w", addr, err)
|
return nil, fmt.Errorf("get box by access key '%s': %w", authHdr.AccessKeyID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkFormatHashContentSHA256(r.Header.Get(AmzContentSHA256)); err != nil {
|
if err = checkFormatHashContentSHA256(r.Header.Get(AmzContentSHA256)); err != nil {
|
||||||
|
@ -216,15 +208,29 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Center) getAccessBoxContainer(accessKeyID string) (cid.ID, error) {
|
||||||
|
var addr oid.Address
|
||||||
|
if err := addr.DecodeString(strings.ReplaceAll(accessKeyID, "0", "/")); err == nil {
|
||||||
|
return addr.Container(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cnrID, ok := c.settings.AccessBoxContainer()
|
||||||
|
if ok {
|
||||||
|
return cnrID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cid.ID{}, fmt.Errorf("%w: unknown container for creds '%s'", apierr.GetAPIError(apierr.ErrInvalidAccessKeyID), accessKeyID)
|
||||||
|
}
|
||||||
|
|
||||||
func checkFormatHashContentSHA256(hash string) error {
|
func checkFormatHashContentSHA256(hash string) error {
|
||||||
if !IsStandardContentSHA256(hash) {
|
if !IsStandardContentSHA256(hash) {
|
||||||
hashBinary, err := hex.DecodeString(hash)
|
hashBinary, err := hex.DecodeString(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: decode hash: %s: %s", apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch),
|
return fmt.Errorf("%w: decode hash: %s: %s", apierr.GetAPIError(apierr.ErrContentSHA256Mismatch),
|
||||||
hash, err.Error())
|
hash, err.Error())
|
||||||
}
|
}
|
||||||
if len(hashBinary) != sha256.Size && len(hash) != 0 {
|
if len(hashBinary) != sha256.Size && len(hash) != 0 {
|
||||||
return fmt.Errorf("%w: invalid hash size %d", apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch), len(hashBinary))
|
return fmt.Errorf("%w: invalid hash size %d", apierr.GetAPIError(apierr.ErrContentSHA256Mismatch), len(hashBinary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,12 +248,12 @@ func (c Center) checkAccessKeyID(accessKeyID string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("%w: accesskeyID prefix isn't allowed", apiErrors.GetAPIError(apiErrors.ErrAccessDenied))
|
return fmt.Errorf("%w: accesskeyID prefix isn't allowed", apierr.GetAPIError(apierr.ErrAccessDenied))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
|
func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
|
||||||
if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil {
|
if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil {
|
||||||
return nil, fmt.Errorf("%w: parse multipart form with max size %d", apiErrors.GetAPIError(apiErrors.ErrInvalidArgument), maxFormSizeMemory)
|
return nil, fmt.Errorf("%w: parse multipart form with max size %d", apierr.GetAPIError(apierr.ErrInvalidArgument), maxFormSizeMemory)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := prepareForm(r.MultipartForm); err != nil {
|
if err := prepareForm(r.MultipartForm); err != nil {
|
||||||
|
@ -262,7 +268,7 @@ func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
|
||||||
creds := MultipartFormValue(r, "x-amz-credential")
|
creds := MultipartFormValue(r, "x-amz-credential")
|
||||||
submatches := c.postReg.GetSubmatches(creds)
|
submatches := c.postReg.GetSubmatches(creds)
|
||||||
if len(submatches) != 4 {
|
if len(submatches) != 4 {
|
||||||
return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed), creds)
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrAuthorizationHeaderMalformed), creds)
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureDateTime, err := time.Parse("20060102T150405Z", MultipartFormValue(r, "x-amz-date"))
|
signatureDateTime, err := time.Parse("20060102T150405Z", MultipartFormValue(r, "x-amz-date"))
|
||||||
|
@ -272,14 +278,14 @@ func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
|
||||||
|
|
||||||
accessKeyID := submatches["access_key_id"]
|
accessKeyID := submatches["access_key_id"]
|
||||||
|
|
||||||
addr, err := getAddress(accessKeyID)
|
cnrID, err := c.getAccessBoxContainer(accessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
box, attrs, err := c.cli.GetBox(r.Context(), addr)
|
box, attrs, err := c.cli.GetBox(r.Context(), cnrID, accessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get box '%s': %w", addr, err)
|
return nil, fmt.Errorf("get box by accessKeyID '%s': %w", accessKeyID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secret := box.Gate.SecretKey
|
secret := box.Gate.SecretKey
|
||||||
|
@ -288,7 +294,7 @@ func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
|
||||||
signature := SignStr(secret, service, region, signatureDateTime, policy)
|
signature := SignStr(secret, service, region, signatureDateTime, policy)
|
||||||
reqSignature := MultipartFormValue(r, "x-amz-signature")
|
reqSignature := MultipartFormValue(r, "x-amz-signature")
|
||||||
if signature != reqSignature {
|
if signature != reqSignature {
|
||||||
return nil, fmt.Errorf("%w: %s != %s", apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch),
|
return nil, fmt.Errorf("%w: %s != %s", apierr.GetAPIError(apierr.ErrSignatureDoesNotMatch),
|
||||||
reqSignature, signature)
|
reqSignature, signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,11 +339,11 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *
|
||||||
if authHeader.IsPresigned {
|
if authHeader.IsPresigned {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if signatureDateTime.Add(authHeader.Expiration).Before(now) {
|
if signatureDateTime.Add(authHeader.Expiration).Before(now) {
|
||||||
return fmt.Errorf("%w: expired: now %s, signature %s", apiErrors.GetAPIError(apiErrors.ErrExpiredPresignRequest),
|
return fmt.Errorf("%w: expired: now %s, signature %s", apierr.GetAPIError(apierr.ErrExpiredPresignRequest),
|
||||||
now.Format(time.RFC3339), signatureDateTime.Format(time.RFC3339))
|
now.Format(time.RFC3339), signatureDateTime.Format(time.RFC3339))
|
||||||
}
|
}
|
||||||
if now.Before(signatureDateTime) {
|
if now.Before(signatureDateTime) {
|
||||||
return fmt.Errorf("%w: signature time from the future: now %s, signature %s", apiErrors.GetAPIError(apiErrors.ErrBadRequest),
|
return fmt.Errorf("%w: signature time from the future: now %s, signature %s", apierr.GetAPIError(apierr.ErrBadRequest),
|
||||||
now.Format(time.RFC3339), signatureDateTime.Format(time.RFC3339))
|
now.Format(time.RFC3339), signatureDateTime.Format(time.RFC3339))
|
||||||
}
|
}
|
||||||
if _, err := signer.Presign(request, nil, authHeader.Service, authHeader.Region, authHeader.Expiration, signatureDateTime); err != nil {
|
if _, err := signer.Presign(request, nil, authHeader.Service, authHeader.Region, authHeader.Expiration, signatureDateTime); err != nil {
|
||||||
|
@ -352,7 +358,7 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *
|
||||||
}
|
}
|
||||||
|
|
||||||
if authHeader.SignatureV4 != signature {
|
if authHeader.SignatureV4 != signature {
|
||||||
return fmt.Errorf("%w: %s != %s: headers %v", apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch),
|
return fmt.Errorf("%w: %s != %s: headers %v", apierr.GetAPIError(apierr.ErrSignatureDoesNotMatch),
|
||||||
authHeader.SignatureV4, signature, authHeader.SignedFields)
|
authHeader.SignatureV4, signature, authHeader.SignedFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,9 @@ import (
|
||||||
"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"
|
||||||
frostfsErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
frosterr "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/bearer"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"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"
|
||||||
|
@ -28,11 +29,23 @@ import (
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type centerSettingsMock struct {
|
||||||
|
accessBoxContainer *cid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *centerSettingsMock) AccessBoxContainer() (cid.ID, bool) {
|
||||||
|
if c.accessBoxContainer == nil {
|
||||||
|
return cid.ID{}, false
|
||||||
|
}
|
||||||
|
return *c.accessBoxContainer, true
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthHeaderParse(t *testing.T) {
|
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"
|
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{
|
center := &Center{
|
||||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||||
|
settings: ¢erSettingsMock{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
|
@ -57,11 +70,6 @@ func TestAuthHeaderParse(t *testing.T) {
|
||||||
err: errors.GetAPIError(errors.ErrAuthorizationHeaderMalformed),
|
err: errors.GetAPIError(errors.ErrAuthorizationHeaderMalformed),
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: strings.ReplaceAll(defaultHeader, "oid0cid", "oidcid"),
|
|
||||||
err: errors.GetAPIError(errors.ErrInvalidAccessKeyID),
|
|
||||||
expected: nil,
|
|
||||||
},
|
|
||||||
} {
|
} {
|
||||||
authHeader, err := center.parseAuthHeader(tc.header)
|
authHeader, err := center.parseAuthHeader(tc.header)
|
||||||
require.ErrorIs(t, err, tc.err, tc.header)
|
require.ErrorIs(t, err, tc.err, tc.header)
|
||||||
|
@ -69,43 +77,6 @@ func TestAuthHeaderParse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func TestSignature(t *testing.T) {
|
||||||
secret := "66be461c3cd429941c55daf42fad2b8153e5a2016ba89c9494d97677cc9d3872"
|
secret := "66be461c3cd429941c55daf42fad2b8153e5a2016ba89c9494d97677cc9d3872"
|
||||||
strToSign := "eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLAogICJjb25kaXRpb25zIjogWwogICAgeyJidWNrZXQiOiAiYWNsIn0sCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwKICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL2xvY2FsaG9zdDo4MDg0L2FjbCJ9LAogICAgWyJzdGFydHMtd2l0aCIsICIkQ29udGVudC1UeXBlIiwgImltYWdlLyJdLAogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwKICAgIFsic3RhcnRzLXdpdGgiLCAiJHgtYW16LW1ldGEtdGFnIiwgIiJdLAoKICAgIHsiWC1BbXotQ3JlZGVudGlhbCI6ICI4Vmk0MVBIbjVGMXNzY2J4OUhqMXdmMUU2aERUYURpNndxOGhxTU05NllKdTA1QzVDeUVkVlFoV1E2aVZGekFpTkxXaTlFc3BiUTE5ZDRuR3pTYnZVZm10TS8yMDE1MTIyOS91cy1lYXN0LTEvczMvYXdzNF9yZXF1ZXN0In0sCiAgICB7IngtYW16LWFsZ29yaXRobSI6ICJBV1M0LUhNQUMtU0hBMjU2In0sCiAgICB7IlgtQW16LURhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfSwKICAgIHsieC1pZ25vcmUtdG1wIjogInNvbWV0aGluZyIgfQogIF0KfQ=="
|
strToSign := "eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLAogICJjb25kaXRpb25zIjogWwogICAgeyJidWNrZXQiOiAiYWNsIn0sCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwKICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL2xvY2FsaG9zdDo4MDg0L2FjbCJ9LAogICAgWyJzdGFydHMtd2l0aCIsICIkQ29udGVudC1UeXBlIiwgImltYWdlLyJdLAogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwKICAgIFsic3RhcnRzLXdpdGgiLCAiJHgtYW16LW1ldGEtdGFnIiwgIiJdLAoKICAgIHsiWC1BbXotQ3JlZGVudGlhbCI6ICI4Vmk0MVBIbjVGMXNzY2J4OUhqMXdmMUU2aERUYURpNndxOGhxTU05NllKdTA1QzVDeUVkVlFoV1E2aVZGekFpTkxXaTlFc3BiUTE5ZDRuR3pTYnZVZm10TS8yMDE1MTIyOS91cy1lYXN0LTEvczMvYXdzNF9yZXF1ZXN0In0sCiAgICB7IngtYW16LWFsZ29yaXRobSI6ICJBV1M0LUhNQUMtU0hBMjU2In0sCiAgICB7IlgtQW16LURhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfSwKICAgIHsieC1pZ25vcmUtdG1wIjogInNvbWV0aGluZyIgfQogIF0KfQ=="
|
||||||
|
@ -171,17 +142,17 @@ func TestCheckFormatContentSHA256(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type frostFSMock struct {
|
type frostFSMock struct {
|
||||||
objects map[oid.Address]*object.Object
|
objects map[string]*object.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFrostFSMock() *frostFSMock {
|
func newFrostFSMock() *frostFSMock {
|
||||||
return &frostFSMock{
|
return &frostFSMock{
|
||||||
objects: map[oid.Address]*object.Object{},
|
objects: map[string]*object.Object{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frostFSMock) GetCredsObject(_ context.Context, address oid.Address) (*object.Object, error) {
|
func (f *frostFSMock) GetCredsObject(_ context.Context, prm tokens.PrmGetCredsObject) (*object.Object, error) {
|
||||||
obj, ok := f.objects[address]
|
obj, ok := f.objects[prm.AccessKeyID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not found")
|
return nil, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
|
@ -208,7 +179,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
GateKey: key.PublicKey(),
|
GateKey: key.PublicKey(),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"))
|
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"), false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
data, err := accessBox.Marshal()
|
data, err := accessBox.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -219,10 +190,10 @@ func TestAuthenticate(t *testing.T) {
|
||||||
obj.SetContainerID(addr.Container())
|
obj.SetContainerID(addr.Container())
|
||||||
obj.SetID(addr.Object())
|
obj.SetID(addr.Object())
|
||||||
|
|
||||||
frostfs := newFrostFSMock()
|
accessKeyID := getAccessKeyID(addr)
|
||||||
frostfs.objects[addr] = &obj
|
|
||||||
|
|
||||||
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
frostfs := newFrostFSMock()
|
||||||
|
frostfs.objects[accessKeyID] = &obj
|
||||||
|
|
||||||
awsCreds := credentials.NewStaticCredentials(accessKeyID, secret.SecretKey, "")
|
awsCreds := credentials.NewStaticCredentials(accessKeyID, secret.SecretKey, "")
|
||||||
defaultSigner := v4.NewSigner(awsCreds)
|
defaultSigner := v4.NewSigner(awsCreds)
|
||||||
|
@ -413,13 +384,13 @@ func TestAuthenticate(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
creds := tokens.New(bigConfig)
|
creds := tokens.New(bigConfig)
|
||||||
cntr := New(creds, tc.prefixes)
|
cntr := New(creds, tc.prefixes, ¢erSettingsMock{})
|
||||||
box, err := cntr.Authenticate(tc.request)
|
box, err := cntr.Authenticate(tc.request)
|
||||||
|
|
||||||
if tc.err {
|
if tc.err {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
if tc.errCode > 0 {
|
if tc.errCode > 0 {
|
||||||
err = frostfsErrors.UnwrapErr(err)
|
err = frosterr.UnwrapErr(err)
|
||||||
require.Equal(t, errors.GetAPIError(tc.errCode), err)
|
require.Equal(t, errors.GetAPIError(tc.errCode), err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -455,7 +426,7 @@ func TestHTTPPostAuthenticate(t *testing.T) {
|
||||||
GateKey: key.PublicKey(),
|
GateKey: key.PublicKey(),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"))
|
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"), false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
data, err := accessBox.Marshal()
|
data, err := accessBox.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -466,10 +437,11 @@ func TestHTTPPostAuthenticate(t *testing.T) {
|
||||||
obj.SetContainerID(addr.Container())
|
obj.SetContainerID(addr.Container())
|
||||||
obj.SetID(addr.Object())
|
obj.SetID(addr.Object())
|
||||||
|
|
||||||
frostfs := newFrostFSMock()
|
accessKeyID := getAccessKeyID(addr)
|
||||||
frostfs.objects[addr] = &obj
|
|
||||||
|
frostfs := newFrostFSMock()
|
||||||
|
frostfs.objects[accessKeyID] = &obj
|
||||||
|
|
||||||
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
|
||||||
invalidAccessKeyID := oidtest.Address().String() + "0" + oidtest.Address().Object().String()
|
invalidAccessKeyID := oidtest.Address().String() + "0" + oidtest.Address().Object().String()
|
||||||
|
|
||||||
timeToSign := time.Now()
|
timeToSign := time.Now()
|
||||||
|
@ -590,13 +562,13 @@ func TestHTTPPostAuthenticate(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
creds := tokens.New(bigConfig)
|
creds := tokens.New(bigConfig)
|
||||||
cntr := New(creds, tc.prefixes)
|
cntr := New(creds, tc.prefixes, ¢erSettingsMock{})
|
||||||
box, err := cntr.Authenticate(tc.request)
|
box, err := cntr.Authenticate(tc.request)
|
||||||
|
|
||||||
if tc.err {
|
if tc.err {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
if tc.errCode > 0 {
|
if tc.errCode > 0 {
|
||||||
err = frostfsErrors.UnwrapErr(err)
|
err = frosterr.UnwrapErr(err)
|
||||||
require.Equal(t, errors.GetAPIError(tc.errCode), err)
|
require.Equal(t, errors.GetAPIError(tc.errCode), err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -633,3 +605,7 @@ func getRequestWithMultipartForm(t *testing.T, policy, creds, date, sign, fieldN
|
||||||
|
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAccessKeyID(addr oid.Address) string {
|
||||||
|
return strings.ReplaceAll(addr.EncodeToString(), "/", "0")
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ type PresignData struct {
|
||||||
Region string
|
Region string
|
||||||
Lifetime time.Duration
|
Lifetime time.Duration
|
||||||
SignTime time.Time
|
SignTime time.Time
|
||||||
|
Headers map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PresignRequest forms pre-signed request to access objects without aws credentials.
|
// PresignRequest forms pre-signed request to access objects without aws credentials.
|
||||||
|
@ -34,7 +35,10 @@ func PresignRequest(creds *credentials.Credentials, reqData RequestData, presign
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
req.Header.Set(AmzDate, presignData.SignTime.Format("20060102T150405Z"))
|
||||||
req.Header.Set(ContentTypeHdr, "text/plain")
|
|
||||||
|
for k, v := range presignData.Headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
signer := v4.NewSigner(creds)
|
signer := v4.NewSigner(creds)
|
||||||
signer.DisableURIPathEscaping = true
|
signer.DisableURIPathEscaping = true
|
||||||
|
|
|
@ -29,11 +29,11 @@ func newTokensFrostfsMock() *credentialsMock {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m credentialsMock) addBox(addr oid.Address, box *accessbox.Box) {
|
func (m credentialsMock) addBox(addr oid.Address, box *accessbox.Box) {
|
||||||
m.boxes[addr.String()] = box
|
m.boxes[getAccessKeyID(addr)] = box
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m credentialsMock) GetBox(_ context.Context, addr oid.Address) (*accessbox.Box, []object.Attribute, error) {
|
func (m credentialsMock) GetBox(_ context.Context, _ cid.ID, accessKeyID string) (*accessbox.Box, []object.Attribute, error) {
|
||||||
box, ok := m.boxes[addr.String()]
|
box, ok := m.boxes[accessKeyID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, &apistatus.ObjectNotFound{}
|
return nil, nil, &apistatus.ObjectNotFound{}
|
||||||
}
|
}
|
||||||
|
@ -41,11 +41,11 @@ func (m credentialsMock) GetBox(_ context.Context, addr oid.Address) (*accessbox
|
||||||
return box, nil, nil
|
return box, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m credentialsMock) Put(context.Context, cid.ID, tokens.CredentialsParam) (oid.Address, error) {
|
func (m credentialsMock) Put(context.Context, tokens.CredentialsParam) (oid.Address, error) {
|
||||||
return oid.Address{}, nil
|
return oid.Address{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m credentialsMock) Update(context.Context, oid.Address, tokens.CredentialsParam) (oid.Address, error) {
|
func (m credentialsMock) Update(context.Context, tokens.CredentialsParam) (oid.Address, error) {
|
||||||
return oid.Address{}, nil
|
return oid.Address{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +84,10 @@ func TestCheckSign(t *testing.T) {
|
||||||
mock.addBox(accessKeyAddr, expBox)
|
mock.addBox(accessKeyAddr, expBox)
|
||||||
|
|
||||||
c := &Center{
|
c := &Center{
|
||||||
cli: mock,
|
cli: mock,
|
||||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||||
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
|
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
|
||||||
|
settings: ¢erSettingsMock{},
|
||||||
}
|
}
|
||||||
box, err := c.Authenticate(req)
|
box, err := c.Authenticate(req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
18
api/cache/accessbox.go
vendored
18
api/cache/accessbox.go
vendored
|
@ -30,6 +30,7 @@ type (
|
||||||
Box *accessbox.Box
|
Box *accessbox.Box
|
||||||
Attributes []object.Attribute
|
Attributes []object.Attribute
|
||||||
PutTime time.Time
|
PutTime time.Time
|
||||||
|
Address *oid.Address
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,8 +58,8 @@ func NewAccessBoxCache(config *Config) *AccessBoxCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a cached accessbox.
|
// Get returns a cached accessbox.
|
||||||
func (o *AccessBoxCache) Get(address oid.Address) *AccessBoxCacheValue {
|
func (o *AccessBoxCache) Get(accessKeyID string) *AccessBoxCacheValue {
|
||||||
entry, err := o.cache.Get(address)
|
entry, err := o.cache.Get(accessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -74,16 +75,11 @@ func (o *AccessBoxCache) Get(address oid.Address) *AccessBoxCacheValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores an accessbox to cache.
|
// Put stores an accessbox to cache.
|
||||||
func (o *AccessBoxCache) Put(address oid.Address, box *accessbox.Box, attrs []object.Attribute) error {
|
func (o *AccessBoxCache) Put(accessKeyID string, val *AccessBoxCacheValue) error {
|
||||||
val := &AccessBoxCacheValue{
|
return o.cache.Set(accessKeyID, val)
|
||||||
Box: box,
|
|
||||||
Attributes: attrs,
|
|
||||||
PutTime: time.Now(),
|
|
||||||
}
|
|
||||||
return o.cache.Set(address, val)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes an accessbox from cache.
|
// Delete removes an accessbox from cache.
|
||||||
func (o *AccessBoxCache) Delete(address oid.Address) {
|
func (o *AccessBoxCache) Delete(accessKeyID string) {
|
||||||
o.cache.Remove(address)
|
o.cache.Remove(accessKeyID)
|
||||||
}
|
}
|
||||||
|
|
24
api/cache/cache_test.go
vendored
24
api/cache/cache_test.go
vendored
|
@ -1,13 +1,14 @@
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||||
"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"
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -22,18 +23,21 @@ func TestAccessBoxCacheType(t *testing.T) {
|
||||||
|
|
||||||
addr := oidtest.Address()
|
addr := oidtest.Address()
|
||||||
box := &accessbox.Box{}
|
box := &accessbox.Box{}
|
||||||
var attrs []object.Attribute
|
val := &AccessBoxCacheValue{
|
||||||
|
Box: box,
|
||||||
|
}
|
||||||
|
|
||||||
err := cache.Put(addr, box, attrs)
|
accessKeyID := getAccessKeyID(addr)
|
||||||
|
|
||||||
|
err := cache.Put(accessKeyID, val)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
val := cache.Get(addr)
|
resVal := cache.Get(accessKeyID)
|
||||||
require.Equal(t, box, val.Box)
|
require.Equal(t, box, resVal.Box)
|
||||||
require.Equal(t, attrs, val.Attributes)
|
|
||||||
require.Equal(t, 0, observedLog.Len())
|
require.Equal(t, 0, observedLog.Len())
|
||||||
|
|
||||||
err = cache.cache.Set(addr, "tmp")
|
err = cache.cache.Set(accessKeyID, "tmp")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertInvalidCacheEntry(t, cache.Get(addr), observedLog)
|
assertInvalidCacheEntry(t, cache.Get(accessKeyID), observedLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBucketsCacheType(t *testing.T) {
|
func TestBucketsCacheType(t *testing.T) {
|
||||||
|
@ -230,3 +234,7 @@ func getObservedLogger() (*zap.Logger, *observer.ObservedLogs) {
|
||||||
loggerCore, observedLog := observer.New(zap.WarnLevel)
|
loggerCore, observedLog := observer.New(zap.WarnLevel)
|
||||||
return zap.New(loggerCore), observedLog
|
return zap.New(loggerCore), observedLog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAccessKeyID(addr oid.Address) string {
|
||||||
|
return strings.ReplaceAll(addr.EncodeToString(), "/", "0")
|
||||||
|
}
|
||||||
|
|
|
@ -27,9 +27,10 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
LifecycleExpiration struct {
|
LifecycleExpiration struct {
|
||||||
Date string `xml:"Date,omitempty"`
|
Date string `xml:"Date,omitempty"`
|
||||||
Days *int `xml:"Days,omitempty"`
|
Days *int `xml:"Days,omitempty"`
|
||||||
ExpiredObjectDeleteMarker *bool `xml:"ExpiredObjectDeleteMarker,omitempty"`
|
Epoch *uint64 `xml:"Epoch,omitempty"`
|
||||||
|
ExpiredObjectDeleteMarker *bool `xml:"ExpiredObjectDeleteMarker,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
LifecycleRuleFilter struct {
|
LifecycleRuleFilter struct {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
frosterrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
|
frosterr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -1765,7 +1768,7 @@ var errorCodes = errorCodeMap{
|
||||||
|
|
||||||
// IsS3Error checks if the provided error is a specific s3 error.
|
// IsS3Error checks if the provided error is a specific s3 error.
|
||||||
func IsS3Error(err error, code ErrorCode) bool {
|
func IsS3Error(err error, code ErrorCode) bool {
|
||||||
err = frosterrors.UnwrapErr(err)
|
err = frosterr.UnwrapErr(err)
|
||||||
e, ok := err.(Error)
|
e, ok := err.(Error)
|
||||||
return ok && e.ErrCode == code
|
return ok && e.ErrCode == code
|
||||||
}
|
}
|
||||||
|
@ -1802,6 +1805,26 @@ func GetAPIErrorWithError(code ErrorCode, err error) Error {
|
||||||
return errorCodes.toAPIErrWithErr(code, err)
|
return errorCodes.toAPIErrWithErr(code, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransformToS3Error converts FrostFS error to the corresponding S3 error type.
|
||||||
|
func TransformToS3Error(err error) error {
|
||||||
|
err = frosterr.UnwrapErr(err) // this wouldn't work with errors.Join
|
||||||
|
var s3err Error
|
||||||
|
if errors.As(err, &s3err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, frostfs.ErrAccessDenied) ||
|
||||||
|
errors.Is(err, tree.ErrNodeAccessDenied) {
|
||||||
|
return GetAPIError(ErrAccessDenied)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, frostfs.ErrGatewayTimeout) {
|
||||||
|
return GetAPIError(ErrGatewayTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetAPIError(ErrInternalError)
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectError -- error that is linked to a specific object.
|
// ObjectError -- error that is linked to a specific object.
|
||||||
type ObjectError struct {
|
type ObjectError struct {
|
||||||
Err error
|
Err error
|
||||||
|
|
|
@ -2,7 +2,12 @@ package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkErrCode(b *testing.B) {
|
func BenchmarkErrCode(b *testing.B) {
|
||||||
|
@ -24,3 +29,56 @@ func BenchmarkErrorsIs(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransformS3Errors(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
expected ErrorCode
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple std error to internal error",
|
||||||
|
err: errors.New("some error"),
|
||||||
|
expected: ErrInternalError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "layer access denied error to s3 access denied error",
|
||||||
|
err: frostfs.ErrAccessDenied,
|
||||||
|
expected: ErrAccessDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped layer access denied error to s3 access denied error",
|
||||||
|
err: fmt.Errorf("wrap: %w", frostfs.ErrAccessDenied),
|
||||||
|
expected: ErrAccessDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "layer node access denied error to s3 access denied error",
|
||||||
|
err: tree.ErrNodeAccessDenied,
|
||||||
|
expected: ErrAccessDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "layer gateway timeout error to s3 gateway timeout error",
|
||||||
|
err: frostfs.ErrGatewayTimeout,
|
||||||
|
expected: ErrGatewayTimeout,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "s3 error to s3 error",
|
||||||
|
err: GetAPIError(ErrInvalidPart),
|
||||||
|
expected: ErrInvalidPart,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped s3 error to s3 error",
|
||||||
|
err: fmt.Errorf("wrap: %w", GetAPIError(ErrInvalidPart)),
|
||||||
|
expected: ErrInvalidPart,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := TransformToS3Error(tc.err)
|
||||||
|
s3err, ok := err.(Error)
|
||||||
|
require.True(t, ok, "error must be s3 error")
|
||||||
|
require.Equalf(t, tc.expected, s3err.ErrCode,
|
||||||
|
"expected: '%s', got: '%s'",
|
||||||
|
GetAPIError(tc.expected).Code, GetAPIError(s3err.ErrCode).Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -52,18 +52,18 @@ func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, h.encodeBucketCannedACL(ctx, bktInfo, settings)); err != nil {
|
if err = middleware.EncodeToResponse(w, h.encodeBucketCannedACL(ctx, bktInfo, settings)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,17 +127,18 @@ func getTokenIssuerKey(box *accessbox.Box) (*keys.PublicKey, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,30 +150,30 @@ func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request,
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if errBody := r.Body.Close(); errBody != nil {
|
if errBody := r.Body.Close(); errBody != nil {
|
||||||
h.reqLogger(r.Context()).Warn(logs.CouldNotCloseRequestBody, zap.Error(errBody))
|
h.reqLogger(ctx).Warn(logs.CouldNotCloseRequestBody, zap.Error(errBody))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
written, err := io.Copy(io.Discard, r.Body)
|
written, err := io.Copy(io.Discard, r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't read request body", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't read request body", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if written != 0 || len(r.Header.Get(api.AmzACL)) == 0 {
|
if written != 0 || len(r.Header.Get(api.AmzACL)) == 0 {
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(ctx, w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cannedACL, err := parseCannedACL(r.Header)
|
cannedACL, err := parseCannedACL(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse canned ACL", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse canned ACL", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
chainRules := bucketCannedACLToAPERules(cannedACL, reqInfo, bktInfo.CID)
|
chainRules := bucketCannedACLToAPERules(cannedACL, reqInfo, bktInfo.CID)
|
||||||
if err = h.ape.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil {
|
if err = h.ape.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil {
|
||||||
h.logAndSendError(w, "failed to add morph rule chains", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to add morph rule chains", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +185,7 @@ func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
||||||
h.logAndSendError(w, "couldn't save bucket settings", reqInfo, err,
|
h.logAndSendError(ctx, w, "couldn't save bucket settings", reqInfo, err,
|
||||||
zap.String("container_id", bktInfo.CID.EncodeToString()))
|
zap.String("container_id", bktInfo.CID.EncodeToString()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -198,18 +199,18 @@ func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, h.encodePrivateCannedACL(ctx, bktInfo, settings)); err != nil {
|
if err = middleware.EncodeToResponse(w, h.encodePrivateCannedACL(ctx, bktInfo, settings)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,19 +220,20 @@ func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(ctx)
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
if _, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
if _, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(ctx, w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,13 +242,13 @@ func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Re
|
||||||
if strings.Contains(err.Error(), "not found") {
|
if strings.Contains(err.Error(), "not found") {
|
||||||
err = fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchBucketPolicy), err.Error())
|
err = fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchBucketPolicy), err.Error())
|
||||||
}
|
}
|
||||||
h.logAndSendError(w, "failed to get policy from storage", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to get policy from storage", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var bktPolicy engineiam.Policy
|
var bktPolicy engineiam.Policy
|
||||||
if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
|
if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
|
||||||
h.logAndSendError(w, "could not parse bucket policy", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse bucket policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,17 +265,18 @@ func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, policyStatus); err != nil {
|
if err = middleware.EncodeToResponse(w, policyStatus); err != nil {
|
||||||
h.logAndSendError(w, "encode and write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "encode and write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +285,7 @@ func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
||||||
if strings.Contains(err.Error(), "not found") {
|
if strings.Contains(err.Error(), "not found") {
|
||||||
err = fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchBucketPolicy), err.Error())
|
err = fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchBucketPolicy), err.Error())
|
||||||
}
|
}
|
||||||
h.logAndSendError(w, "failed to get policy from storage", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to get policy from storage", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,22 +293,23 @@ func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
if _, err = w.Write(jsonPolicy); err != nil {
|
if _, err = w.Write(jsonPolicy); err != nil {
|
||||||
h.logAndSendError(w, "write json policy to client", reqInfo, err)
|
h.logAndSendError(ctx, w, "write json policy to client", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
chainIDs := []chain.ID{getBucketChainID(chain.S3, bktInfo), getBucketChainID(chain.Ingress, bktInfo)}
|
chainIDs := []chain.ID{getBucketChainID(chain.S3, bktInfo), getBucketChainID(chain.Ingress, bktInfo)}
|
||||||
if err = h.ape.DeleteBucketPolicy(reqInfo.Namespace, bktInfo.CID, chainIDs); err != nil {
|
if err = h.ape.DeleteBucketPolicy(reqInfo.Namespace, bktInfo.CID, chainIDs); err != nil {
|
||||||
h.logAndSendError(w, "failed to delete policy from storage", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to delete policy from storage", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,40 +328,41 @@ func checkOwner(info *data.BucketInfo, owner string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonPolicy, err := io.ReadAll(r.Body)
|
jsonPolicy, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "read body", reqInfo, err)
|
h.logAndSendError(ctx, w, "read body", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var bktPolicy engineiam.Policy
|
var bktPolicy engineiam.Policy
|
||||||
if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
|
if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
|
||||||
h.logAndSendError(w, "could not parse bucket policy", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse bucket policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, stat := range bktPolicy.Statement {
|
for _, stat := range bktPolicy.Statement {
|
||||||
if len(stat.NotResource) != 0 {
|
if len(stat.NotResource) != 0 {
|
||||||
h.logAndSendError(w, "policy resource mismatched bucket", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicy))
|
h.logAndSendError(ctx, w, "policy resource mismatched bucket", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicy))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(stat.NotPrincipal) != 0 && stat.Effect == engineiam.AllowEffect {
|
if len(stat.NotPrincipal) != 0 && stat.Effect == engineiam.AllowEffect {
|
||||||
h.logAndSendError(w, "invalid NotPrincipal", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicyNotPrincipal))
|
h.logAndSendError(ctx, w, "invalid NotPrincipal", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicyNotPrincipal))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, resource := range stat.Resource {
|
for _, resource := range stat.Resource {
|
||||||
if reqInfo.BucketName != strings.Split(strings.TrimPrefix(resource, arnAwsPrefix), "/")[0] {
|
if reqInfo.BucketName != strings.Split(strings.TrimPrefix(resource, arnAwsPrefix), "/")[0] {
|
||||||
h.logAndSendError(w, "policy resource mismatched bucket", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicy))
|
h.logAndSendError(ctx, w, "policy resource mismatched bucket", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicy))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,7 +370,7 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
s3Chain, err := engineiam.ConvertToS3Chain(bktPolicy, h.frostfsid)
|
s3Chain, err := engineiam.ConvertToS3Chain(bktPolicy, h.frostfsid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not convert s3 policy to chain policy", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not convert s3 policy to chain policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s3Chain.ID = getBucketChainID(chain.S3, bktInfo)
|
s3Chain.ID = getBucketChainID(chain.S3, bktInfo)
|
||||||
|
@ -374,10 +379,10 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
nativeChain.ID = getBucketChainID(chain.Ingress, bktInfo)
|
nativeChain.ID = getBucketChainID(chain.Ingress, bktInfo)
|
||||||
} else if !stderrors.Is(err, engineiam.ErrActionsNotApplicable) {
|
} else if !stderrors.Is(err, engineiam.ErrActionsNotApplicable) {
|
||||||
h.logAndSendError(w, "could not convert s3 policy to native chain policy", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not convert s3 policy to native chain policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
h.reqLogger(r.Context()).Warn(logs.PolicyCouldntBeConvertedToNativeRules)
|
h.reqLogger(ctx).Warn(logs.PolicyCouldntBeConvertedToNativeRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
chainsToSave := []*chain.Chain{s3Chain}
|
chainsToSave := []*chain.Chain{s3Chain}
|
||||||
|
@ -386,7 +391,7 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.ape.PutBucketPolicy(reqInfo.Namespace, bktInfo.CID, jsonPolicy, chainsToSave); err != nil {
|
if err = h.ape.PutBucketPolicy(reqInfo.Namespace, bktInfo.CID, jsonPolicy, chainsToSave); err != nil {
|
||||||
h.logAndSendError(w, "failed to update policy in contract", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to update policy in contract", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
|
@ -28,12 +28,12 @@ func TestPutObjectACLErrorAPE(t *testing.T) {
|
||||||
|
|
||||||
info := createBucket(hc, bktName)
|
info := createBucket(hc, bktName)
|
||||||
|
|
||||||
putObjectWithHeadersAssertS3Error(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPublic}, s3errors.ErrAccessControlListNotSupported)
|
putObjectWithHeadersAssertS3Error(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPublic}, apierr.ErrAccessControlListNotSupported)
|
||||||
putObjectWithHeaders(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate}) // only `private` canned acl is allowed, that is actually ignored
|
putObjectWithHeaders(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate}) // only `private` canned acl is allowed, that is actually ignored
|
||||||
putObjectWithHeaders(hc, bktName, objName, nil)
|
putObjectWithHeaders(hc, bktName, objName, nil)
|
||||||
|
|
||||||
aclBody := &AccessControlPolicy{}
|
aclBody := &AccessControlPolicy{}
|
||||||
putObjectACLAssertS3Error(hc, bktName, objName, info.Box, nil, aclBody, s3errors.ErrAccessControlListNotSupported)
|
putObjectACLAssertS3Error(hc, bktName, objName, info.Box, nil, aclBody, apierr.ErrAccessControlListNotSupported)
|
||||||
|
|
||||||
aclRes := getObjectACL(hc, bktName, objName)
|
aclRes := getObjectACL(hc, bktName, objName)
|
||||||
checkPrivateACL(t, aclRes, info.Key.PublicKey())
|
checkPrivateACL(t, aclRes, info.Key.PublicKey())
|
||||||
|
@ -49,7 +49,7 @@ func TestCreateObjectACLErrorAPE(t *testing.T) {
|
||||||
copyObject(hc, bktName, objName, objNameCopy, CopyMeta{Headers: map[string]string{api.AmzACL: basicACLPublic}}, http.StatusBadRequest)
|
copyObject(hc, bktName, objName, objNameCopy, CopyMeta{Headers: map[string]string{api.AmzACL: basicACLPublic}}, http.StatusBadRequest)
|
||||||
copyObject(hc, bktName, objName, objNameCopy, CopyMeta{Headers: map[string]string{api.AmzACL: basicACLPrivate}}, http.StatusOK)
|
copyObject(hc, bktName, objName, objNameCopy, CopyMeta{Headers: map[string]string{api.AmzACL: basicACLPrivate}}, http.StatusOK)
|
||||||
|
|
||||||
createMultipartUploadAssertS3Error(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPublic}, s3errors.ErrAccessControlListNotSupported)
|
createMultipartUploadAssertS3Error(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPublic}, apierr.ErrAccessControlListNotSupported)
|
||||||
createMultipartUpload(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate})
|
createMultipartUpload(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ func TestBucketACLAPE(t *testing.T) {
|
||||||
info := createBucket(hc, bktName)
|
info := createBucket(hc, bktName)
|
||||||
|
|
||||||
aclBody := &AccessControlPolicy{}
|
aclBody := &AccessControlPolicy{}
|
||||||
putBucketACLAssertS3Error(hc, bktName, info.Box, nil, aclBody, s3errors.ErrAccessControlListNotSupported)
|
putBucketACLAssertS3Error(hc, bktName, info.Box, nil, aclBody, apierr.ErrAccessControlListNotSupported)
|
||||||
|
|
||||||
aclRes := getBucketACL(hc, bktName)
|
aclRes := getBucketACL(hc, bktName)
|
||||||
checkPrivateACL(t, aclRes, info.Key.PublicKey())
|
checkPrivateACL(t, aclRes, info.Key.PublicKey())
|
||||||
|
@ -113,7 +113,7 @@ func TestBucketPolicy(t *testing.T) {
|
||||||
|
|
||||||
createTestBucket(hc, bktName)
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
getBucketPolicy(hc, bktName, s3errors.ErrNoSuchBucketPolicy)
|
getBucketPolicy(hc, bktName, apierr.ErrNoSuchBucketPolicy)
|
||||||
|
|
||||||
newPolicy := engineiam.Policy{
|
newPolicy := engineiam.Policy{
|
||||||
Version: "2012-10-17",
|
Version: "2012-10-17",
|
||||||
|
@ -125,7 +125,7 @@ func TestBucketPolicy(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
putBucketPolicy(hc, bktName, newPolicy, s3errors.ErrMalformedPolicy)
|
putBucketPolicy(hc, bktName, newPolicy, apierr.ErrMalformedPolicy)
|
||||||
|
|
||||||
newPolicy.Statement[0].Resource[0] = arnAwsPrefix + bktName + "/*"
|
newPolicy.Statement[0].Resource[0] = arnAwsPrefix + bktName + "/*"
|
||||||
putBucketPolicy(hc, bktName, newPolicy)
|
putBucketPolicy(hc, bktName, newPolicy)
|
||||||
|
@ -140,7 +140,7 @@ func TestBucketPolicyStatus(t *testing.T) {
|
||||||
|
|
||||||
createTestBucket(hc, bktName)
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
getBucketPolicy(hc, bktName, s3errors.ErrNoSuchBucketPolicy)
|
getBucketPolicy(hc, bktName, apierr.ErrNoSuchBucketPolicy)
|
||||||
|
|
||||||
newPolicy := engineiam.Policy{
|
newPolicy := engineiam.Policy{
|
||||||
Version: "2012-10-17",
|
Version: "2012-10-17",
|
||||||
|
@ -152,7 +152,7 @@ func TestBucketPolicyStatus(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
putBucketPolicy(hc, bktName, newPolicy, s3errors.ErrMalformedPolicyNotPrincipal)
|
putBucketPolicy(hc, bktName, newPolicy, apierr.ErrMalformedPolicyNotPrincipal)
|
||||||
|
|
||||||
newPolicy.Statement[0].NotPrincipal = nil
|
newPolicy.Statement[0].NotPrincipal = nil
|
||||||
newPolicy.Statement[0].Principal = map[engineiam.PrincipalType][]string{engineiam.Wildcard: {}}
|
newPolicy.Statement[0].Principal = map[engineiam.PrincipalType][]string{engineiam.Wildcard: {}}
|
||||||
|
@ -221,7 +221,7 @@ func TestPutBucketPolicy(t *testing.T) {
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBucketPolicy(hc *handlerContext, bktName string, errCode ...s3errors.ErrorCode) engineiam.Policy {
|
func getBucketPolicy(hc *handlerContext, bktName string, errCode ...apierr.ErrorCode) engineiam.Policy {
|
||||||
w, r := prepareTestRequest(hc, bktName, "", nil)
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
||||||
hc.Handler().GetBucketPolicyHandler(w, r)
|
hc.Handler().GetBucketPolicyHandler(w, r)
|
||||||
|
|
||||||
|
@ -231,13 +231,13 @@ func getBucketPolicy(hc *handlerContext, bktName string, errCode ...s3errors.Err
|
||||||
err := json.NewDecoder(w.Result().Body).Decode(&policy)
|
err := json.NewDecoder(w.Result().Body).Decode(&policy)
|
||||||
require.NoError(hc.t, err)
|
require.NoError(hc.t, err)
|
||||||
} else {
|
} else {
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0]))
|
assertS3Error(hc.t, w, apierr.GetAPIError(errCode[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return policy
|
return policy
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBucketPolicyStatus(hc *handlerContext, bktName string, errCode ...s3errors.ErrorCode) PolicyStatus {
|
func getBucketPolicyStatus(hc *handlerContext, bktName string, errCode ...apierr.ErrorCode) PolicyStatus {
|
||||||
w, r := prepareTestRequest(hc, bktName, "", nil)
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
||||||
hc.Handler().GetBucketPolicyStatusHandler(w, r)
|
hc.Handler().GetBucketPolicyStatusHandler(w, r)
|
||||||
|
|
||||||
|
@ -247,13 +247,13 @@ func getBucketPolicyStatus(hc *handlerContext, bktName string, errCode ...s3erro
|
||||||
err := xml.NewDecoder(w.Result().Body).Decode(&policyStatus)
|
err := xml.NewDecoder(w.Result().Body).Decode(&policyStatus)
|
||||||
require.NoError(hc.t, err)
|
require.NoError(hc.t, err)
|
||||||
} else {
|
} else {
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0]))
|
assertS3Error(hc.t, w, apierr.GetAPIError(errCode[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return policyStatus
|
return policyStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketPolicy(hc *handlerContext, bktName string, bktPolicy engineiam.Policy, errCode ...s3errors.ErrorCode) {
|
func putBucketPolicy(hc *handlerContext, bktName string, bktPolicy engineiam.Policy, errCode ...apierr.ErrorCode) {
|
||||||
body, err := json.Marshal(bktPolicy)
|
body, err := json.Marshal(bktPolicy)
|
||||||
require.NoError(hc.t, err)
|
require.NoError(hc.t, err)
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ func putBucketPolicy(hc *handlerContext, bktName string, bktPolicy engineiam.Pol
|
||||||
if len(errCode) == 0 {
|
if len(errCode) == 0 {
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0]))
|
assertS3Error(hc.t, w, apierr.GetAPIError(errCode[0]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,9 +312,9 @@ func createBucket(hc *handlerContext, bktName string) *createBucketInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBucketAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, code s3errors.ErrorCode) {
|
func createBucketAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, code apierr.ErrorCode) {
|
||||||
w := createBucketBase(hc, bktName, box)
|
w := createBucketBase(hc, bktName, box)
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(code))
|
assertS3Error(hc.t, w, apierr.GetAPIError(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBucketBase(hc *handlerContext, bktName string, box *accessbox.Box) *httptest.ResponseRecorder {
|
func createBucketBase(hc *handlerContext, bktName string, box *accessbox.Box) *httptest.ResponseRecorder {
|
||||||
|
@ -330,9 +330,9 @@ func putBucketACL(hc *handlerContext, bktName string, box *accessbox.Box, header
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketACLAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy, code s3errors.ErrorCode) {
|
func putBucketACLAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy, code apierr.ErrorCode) {
|
||||||
w := putBucketACLBase(hc, bktName, box, header, body)
|
w := putBucketACLBase(hc, bktName, box, header, body)
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(code))
|
assertS3Error(hc.t, w, apierr.GetAPIError(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketACLBase(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy) *httptest.ResponseRecorder {
|
func putBucketACLBase(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy) *httptest.ResponseRecorder {
|
||||||
|
@ -360,9 +360,9 @@ func getBucketACLBase(hc *handlerContext, bktName string) *httptest.ResponseReco
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectACLAssertS3Error(hc *handlerContext, bktName, objName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy, code s3errors.ErrorCode) {
|
func putObjectACLAssertS3Error(hc *handlerContext, bktName, objName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy, code apierr.ErrorCode) {
|
||||||
w := putObjectACLBase(hc, bktName, objName, box, header, body)
|
w := putObjectACLBase(hc, bktName, objName, box, header, body)
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(code))
|
assertS3Error(hc.t, w, apierr.GetAPIError(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectACLBase(hc *handlerContext, bktName, objName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy) *httptest.ResponseRecorder {
|
func putObjectACLBase(hc *handlerContext, bktName, objName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy) *httptest.ResponseRecorder {
|
||||||
|
@ -396,9 +396,9 @@ func putObjectWithHeaders(hc *handlerContext, bktName, objName string, headers m
|
||||||
return w.Header()
|
return w.Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectWithHeadersAssertS3Error(hc *handlerContext, bktName, objName string, headers map[string]string, code s3errors.ErrorCode) {
|
func putObjectWithHeadersAssertS3Error(hc *handlerContext, bktName, objName string, headers map[string]string, code apierr.ErrorCode) {
|
||||||
w := putObjectWithHeadersBase(hc, bktName, objName, headers, nil, nil)
|
w := putObjectWithHeadersBase(hc, bktName, objName, headers, nil, nil)
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(code))
|
assertS3Error(hc.t, w, apierr.GetAPIError(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectWithHeadersBase(hc *handlerContext, bktName, objName string, headers map[string]string, box *accessbox.Box, data []byte) *httptest.ResponseRecorder {
|
func putObjectWithHeadersBase(hc *handlerContext, bktName, objName string, headers map[string]string, box *accessbox.Box, data []byte) *httptest.ResponseRecorder {
|
||||||
|
|
|
@ -70,17 +70,18 @@ var validAttributes = map[string]struct{}{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
params, err := parseGetObjectAttributeArgs(r)
|
params, err := parseGetObjectAttributeArgs(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid request", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid request", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,44 +91,44 @@ func (h *handler) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Requ
|
||||||
VersionID: params.VersionID,
|
VersionID: params.VersionID,
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedInfo, err := h.obj.GetExtendedObjectInfo(r.Context(), p)
|
extendedInfo, err := h.obj.GetExtendedObjectInfo(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not fetch object info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not fetch object info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info := extendedInfo.ObjectInfo
|
info := extendedInfo.ObjectInfo
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = encryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(info.Headers)); err != nil {
|
if err = encryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(info.Headers)); err != nil {
|
||||||
h.logAndSendError(w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
h.logAndSendError(ctx, w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkPreconditions(info, params.Conditional, h.cfg.MD5Enabled()); err != nil {
|
if err = checkPreconditions(info, params.Conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
h.logAndSendError(w, "precondition failed", reqInfo, err)
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktSettings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := encodeToObjectAttributesResponse(info, params, h.cfg.MD5Enabled())
|
response, err := encodeToObjectAttributesResponse(info, params, h.cfg.MD5Enabled())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't encode object info to response", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't encode object info to response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeAttributesHeaders(w.Header(), extendedInfo, bktSettings.Unversioned())
|
writeAttributesHeaders(w.Header(), extendedInfo, bktSettings.Unversioned())
|
||||||
if err = middleware.EncodeToResponse(w, response); err != nil {
|
if err = middleware.EncodeToResponse(w, response); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
srcBucket, srcObject, err := path2BucketObject(src)
|
srcBucket, srcObject, err := path2BucketObject(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid source copy", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid source copy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,74 +75,74 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if srcObjPrm.BktInfo, err = h.getBucketAndCheckOwner(r, srcBucket, api.AmzSourceExpectedBucketOwner); err != nil {
|
if srcObjPrm.BktInfo, err = h.getBucketAndCheckOwner(r, srcBucket, api.AmzSourceExpectedBucketOwner); err != nil {
|
||||||
h.logAndSendError(w, "couldn't get source bucket", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get source bucket", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dstBktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
dstBktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get target bucket", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get target bucket", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(ctx, dstBktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, dstBktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cannedACLStatus == aclStatusYes {
|
if cannedACLStatus == aclStatusYes {
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(ctx, w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedSrcObjInfo, err := h.obj.GetExtendedObjectInfo(ctx, srcObjPrm)
|
extendedSrcObjInfo, err := h.obj.GetExtendedObjectInfo(ctx, srcObjPrm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not find object", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not find object", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srcObjInfo := extendedSrcObjInfo.ObjectInfo
|
srcObjInfo := extendedSrcObjInfo.ObjectInfo
|
||||||
|
|
||||||
srcEncryptionParams, err := formCopySourceEncryptionParams(r)
|
srcEncryptionParams, err := formCopySourceEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dstEncryptionParams, err := formEncryptionParams(r)
|
dstEncryptionParams, err := formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = srcEncryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(srcObjInfo.Headers)); err != nil {
|
if err = srcEncryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(srcObjInfo.Headers)); err != nil {
|
||||||
if errors.IsS3Error(err, errors.ErrInvalidEncryptionParameters) || errors.IsS3Error(err, errors.ErrSSEEncryptedObject) ||
|
if errors.IsS3Error(err, errors.ErrInvalidEncryptionParameters) || errors.IsS3Error(err, errors.ErrSSEEncryptedObject) ||
|
||||||
errors.IsS3Error(err, errors.ErrInvalidSSECustomerParameters) {
|
errors.IsS3Error(err, errors.ErrInvalidSSECustomerParameters) {
|
||||||
h.logAndSendError(w, "encryption doesn't match object", reqInfo, err, zap.Error(err))
|
h.logAndSendError(ctx, w, "encryption doesn't match object", reqInfo, err, zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.logAndSendError(w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
h.logAndSendError(ctx, w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var dstSize uint64
|
var dstSize uint64
|
||||||
srcSize, err := layer.GetObjectSize(srcObjInfo)
|
srcSize, err := layer.GetObjectSize(srcObjInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed to get source object size", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to get source object size", reqInfo, err)
|
||||||
return
|
return
|
||||||
} else if srcSize > layer.UploadMaxSize { // https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
|
} else if srcSize > layer.UploadMaxSize { // https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
|
||||||
h.logAndSendError(w, "too bid object to copy with single copy operation, use multipart upload copy instead", reqInfo, errors.GetAPIError(errors.ErrInvalidRequestLargeCopy))
|
h.logAndSendError(ctx, w, "too bid object to copy with single copy operation, use multipart upload copy instead", reqInfo, errors.GetAPIError(errors.ErrInvalidRequestLargeCopy))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dstSize = srcSize
|
dstSize = srcSize
|
||||||
|
|
||||||
args, err := parseCopyObjectArgs(r.Header)
|
args, err := parseCopyObjectArgs(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse request params", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse request params", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCopyingToItselfForbidden(reqInfo, srcBucket, srcObject, settings, args) {
|
if isCopyingToItselfForbidden(reqInfo, srcBucket, srcObject, settings, args) {
|
||||||
h.logAndSendError(w, "copying to itself without changing anything", reqInfo, errors.GetAPIError(errors.ErrInvalidCopyDest))
|
h.logAndSendError(ctx, w, "copying to itself without changing anything", reqInfo, errors.GetAPIError(errors.ErrInvalidCopyDest))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if args.TaggingDirective == replaceDirective {
|
if args.TaggingDirective == replaceDirective {
|
||||||
tagSet, err = parseTaggingHeader(r.Header)
|
tagSet, err = parseTaggingHeader(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse tagging header", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse tagging header", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -168,13 +168,13 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
_, tagSet, err = h.obj.GetObjectTagging(ctx, tagPrm)
|
_, tagSet, err = h.obj.GetObjectTagging(ctx, tagPrm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkPreconditions(srcObjInfo, args.Conditional, h.cfg.MD5Enabled()); err != nil {
|
if err = checkPreconditions(srcObjInfo, args.Conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
h.logAndSendError(w, "precondition failed", reqInfo, errors.GetAPIError(errors.ErrPreconditionFailed))
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, errors.GetAPIError(errors.ErrPreconditionFailed))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,20 +202,20 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, dstBktInfo.LocationConstraint)
|
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, dstBktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
params.Lock, err = formObjectLock(ctx, dstBktInfo, settings.LockConfiguration, r.Header)
|
params.Lock, err = formObjectLock(ctx, dstBktInfo, settings.LockConfiguration, r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not form object lock", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not form object lock", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
additional := []zap.Field{zap.String("src_bucket_name", srcBucket), zap.String("src_object_name", srcObject)}
|
additional := []zap.Field{zap.String("src_bucket_name", srcBucket), zap.String("src_object_name", srcObject)}
|
||||||
extendedDstObjInfo, err := h.obj.CopyObject(ctx, params)
|
extendedDstObjInfo, err := h.obj.CopyObject(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't copy object", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "couldn't copy object", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dstObjInfo := extendedDstObjInfo.ObjectInfo
|
dstObjInfo := extendedDstObjInfo.ObjectInfo
|
||||||
|
@ -224,7 +224,7 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
LastModified: dstObjInfo.Created.UTC().Format(time.RFC3339),
|
LastModified: dstObjInfo.Created.UTC().Format(time.RFC3339),
|
||||||
ETag: data.Quote(dstObjInfo.ETag(h.cfg.MD5Enabled())),
|
ETag: data.Quote(dstObjInfo.ETag(h.cfg.MD5Enabled())),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
NodeVersion: extendedDstObjInfo.NodeVersion,
|
NodeVersion: extendedDstObjInfo.NodeVersion,
|
||||||
}
|
}
|
||||||
if err = h.obj.PutObjectTagging(ctx, tagPrm); err != nil {
|
if err = h.obj.PutObjectTagging(ctx, tagPrm); err != nil {
|
||||||
h.logAndSendError(w, "could not upload object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not upload object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,32 +20,34 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cors, err := h.obj.GetBucketCORS(r.Context(), bktInfo)
|
cors, err := h.obj.GetBucketCORS(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get cors", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get cors", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, cors); err != nil {
|
if err = middleware.EncodeToResponse(w, cors); err != nil {
|
||||||
h.logAndSendError(w, "could not encode cors to response", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not encode cors to response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,32 +59,33 @@ func (h *handler) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutBucketCORS(r.Context(), p); err != nil {
|
if err = h.obj.PutBucketCORS(ctx, p); err != nil {
|
||||||
h.logAndSendError(w, "could not put cors configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not put cors configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
||||||
h.logAndSendError(w, "write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) DeleteBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.DeleteBucketCORS(r.Context(), bktInfo); err != nil {
|
if err = h.obj.DeleteBucketCORS(ctx, bktInfo); err != nil {
|
||||||
h.logAndSendError(w, "could not delete cors", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not delete cors", reqInfo, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
@ -149,21 +152,22 @@ func (h *handler) AppendCORSHeaders(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) Preflight(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) Preflight(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(ctx, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
origin := r.Header.Get(api.Origin)
|
origin := r.Header.Get(api.Origin)
|
||||||
if origin == "" {
|
if origin == "" {
|
||||||
h.logAndSendError(w, "origin request header needed", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
h.logAndSendError(ctx, w, "origin request header needed", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
||||||
}
|
}
|
||||||
|
|
||||||
method := r.Header.Get(api.AccessControlRequestMethod)
|
method := r.Header.Get(api.AccessControlRequestMethod)
|
||||||
if method == "" {
|
if method == "" {
|
||||||
h.logAndSendError(w, "Access-Control-Request-Method request header needed", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
h.logAndSendError(ctx, w, "Access-Control-Request-Method request header needed", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,9 +177,9 @@ func (h *handler) Preflight(w http.ResponseWriter, r *http.Request) {
|
||||||
headers = strings.Split(requestHeaders, ", ")
|
headers = strings.Split(requestHeaders, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
cors, err := h.obj.GetBucketCORS(r.Context(), bktInfo)
|
cors, err := h.obj.GetBucketCORS(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get cors", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get cors", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +208,7 @@ func (h *handler) Preflight(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set(api.AccessControlAllowCredentials, "true")
|
w.Header().Set(api.AccessControlAllowCredentials, "true")
|
||||||
}
|
}
|
||||||
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
||||||
h.logAndSendError(w, "write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -213,7 +217,7 @@ func (h *handler) Preflight(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.logAndSendError(w, "Forbidden", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
h.logAndSendError(ctx, w, "Forbidden", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSubslice(slice []string, subSlice []string) bool {
|
func checkSubslice(slice []string, subSlice []string) bool {
|
||||||
|
|
|
@ -71,19 +71,19 @@ func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInfo, err := h.obj.GetNetworkInfo(ctx)
|
networkInfo, err := h.obj.GetNetworkInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get network info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get network info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,9 +97,9 @@ func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
deletedObject := deletedObjects[0]
|
deletedObject := deletedObjects[0]
|
||||||
if deletedObject.Error != nil {
|
if deletedObject.Error != nil {
|
||||||
if isErrObjectLocked(deletedObject.Error) {
|
if isErrObjectLocked(deletedObject.Error) {
|
||||||
h.logAndSendError(w, "object is locked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
h.logAndSendError(ctx, w, "object is locked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||||
} else {
|
} else {
|
||||||
h.logAndSendError(w, "could not delete object", reqInfo, deletedObject.Error)
|
h.logAndSendError(ctx, w, "could not delete object", reqInfo, deletedObject.Error)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -134,26 +134,26 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
||||||
// Content-Md5 is required and should be set
|
// Content-Md5 is required and should be set
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||||
if _, ok := r.Header[api.ContentMD5]; !ok {
|
if _, ok := r.Header[api.ContentMD5]; !ok {
|
||||||
h.logAndSendError(w, "missing Content-MD5", reqInfo, errors.GetAPIError(errors.ErrMissingContentMD5))
|
h.logAndSendError(ctx, w, "missing Content-MD5", reqInfo, errors.GetAPIError(errors.ErrMissingContentMD5))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content-Length is required and should be non-zero
|
// Content-Length is required and should be non-zero
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||||
if r.ContentLength <= 0 {
|
if r.ContentLength <= 0 {
|
||||||
h.logAndSendError(w, "missing Content-Length", reqInfo, errors.GetAPIError(errors.ErrMissingContentLength))
|
h.logAndSendError(ctx, w, "missing Content-Length", reqInfo, errors.GetAPIError(errors.ErrMissingContentLength))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal list of keys to be deleted.
|
// Unmarshal list of keys to be deleted.
|
||||||
requested := &DeleteObjectsRequest{}
|
requested := &DeleteObjectsRequest{}
|
||||||
if err := h.cfg.NewXMLDecoder(r.Body).Decode(requested); err != nil {
|
if err := h.cfg.NewXMLDecoder(r.Body).Decode(requested); err != nil {
|
||||||
h.logAndSendError(w, "couldn't decode body", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error()))
|
h.logAndSendError(ctx, w, "couldn't decode body", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(requested.Objects) == 0 || len(requested.Objects) > maxObjectsToDelete {
|
if len(requested.Objects) == 0 || len(requested.Objects) > maxObjectsToDelete {
|
||||||
h.logAndSendError(w, "number of objects to delete must be greater than 0 and less or equal to 1000", reqInfo, errors.GetAPIError(errors.ErrMalformedXML))
|
h.logAndSendError(ctx, w, "number of objects to delete must be greater than 0 and less or equal to 1000", reqInfo, errors.GetAPIError(errors.ErrMalformedXML))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,19 +178,19 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInfo, err := h.obj.GetNetworkInfo(ctx)
|
networkInfo, err := h.obj.GetNetworkInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get network info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get network info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,22 +231,23 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, response); err != nil {
|
if err = middleware.EncodeToResponse(w, response); err != nil {
|
||||||
h.logAndSendError(w, "could not write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionToken *session.Container
|
var sessionToken *session.Container
|
||||||
|
|
||||||
boxData, err := middleware.GetBoxData(r.Context())
|
boxData, err := middleware.GetBoxData(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
sessionToken = boxData.Gate.SessionTokenForDelete()
|
sessionToken = boxData.Gate.SessionTokenForDelete()
|
||||||
}
|
}
|
||||||
|
@ -259,12 +260,12 @@ func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.DeleteBucket(r.Context(), &layer.DeleteBucketParams{
|
if err = h.obj.DeleteBucket(ctx, &layer.DeleteBucketParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
SessionToken: sessionToken,
|
SessionToken: sessionToken,
|
||||||
SkipCheck: skipObjCheck,
|
SkipCheck: skipObjCheck,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logAndSendError(w, "couldn't delete bucket", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't delete bucket", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +276,7 @@ func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
getBucketCannedChainID(chain.Ingress, bktInfo.CID),
|
getBucketCannedChainID(chain.Ingress, bktInfo.CID),
|
||||||
}
|
}
|
||||||
if err = h.ape.DeleteBucketPolicy(reqInfo.Namespace, bktInfo.CID, chainIDs); err != nil {
|
if err = h.ape.DeleteBucketPolicy(reqInfo.Namespace, bktInfo.CID, chainIDs); err != nil {
|
||||||
h.logAndSendError(w, "failed to delete policy from storage", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to delete policy from storage", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -131,7 +131,7 @@ func TestDeleteObjectsError(t *testing.T) {
|
||||||
addr.SetContainer(bktInfo.CID)
|
addr.SetContainer(bktInfo.CID)
|
||||||
addr.SetObject(nodeVersion.OID)
|
addr.SetObject(nodeVersion.OID)
|
||||||
|
|
||||||
expectedError := apiErrors.GetAPIError(apiErrors.ErrAccessDenied)
|
expectedError := apierr.GetAPIError(apierr.ErrAccessDenied)
|
||||||
hc.tp.SetObjectError(addr, expectedError)
|
hc.tp.SetObjectError(addr, expectedError)
|
||||||
|
|
||||||
w := deleteObjectsBase(hc, bktName, [][2]string{{objName, nodeVersion.OID.EncodeToString()}})
|
w := deleteObjectsBase(hc, bktName, [][2]string{{objName, nodeVersion.OID.EncodeToString()}})
|
||||||
|
@ -553,9 +553,9 @@ func checkNotFound(t *testing.T, hc *handlerContext, bktName, objName, version s
|
||||||
assertStatus(t, w, http.StatusNotFound)
|
assertStatus(t, w, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func headObjectAssertS3Error(hc *handlerContext, bktName, objName, version string, code apiErrors.ErrorCode) {
|
func headObjectAssertS3Error(hc *handlerContext, bktName, objName, version string, code apierr.ErrorCode) {
|
||||||
w := headObjectBase(hc, bktName, objName, version)
|
w := headObjectBase(hc, bktName, objName, version)
|
||||||
assertS3Error(hc.t, w, apiErrors.GetAPIError(code))
|
assertS3Error(hc.t, w, apierr.GetAPIError(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFound(t *testing.T, hc *handlerContext, bktName, objName, version string) {
|
func checkFound(t *testing.T, hc *handlerContext, bktName, objName, version string) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"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/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ func TestSimpleGetEncrypted(t *testing.T) {
|
||||||
|
|
||||||
objInfo, err := tc.Layer().GetObjectInfo(tc.Context(), &layer.HeadObjectParams{BktInfo: bktInfo, Object: objName})
|
objInfo, err := tc.Layer().GetObjectInfo(tc.Context(), &layer.HeadObjectParams{BktInfo: bktInfo, Object: objName})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
obj, err := tc.MockedPool().GetObject(tc.Context(), layer.PrmObjectGet{Container: bktInfo.CID, Object: objInfo.ID})
|
obj, err := tc.MockedPool().GetObject(tc.Context(), frostfs.PrmObjectGet{Container: bktInfo.CID, Object: objInfo.ID})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
encryptedContent, err := io.ReadAll(obj.Payload)
|
encryptedContent, err := io.ReadAll(obj.Payload)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -129,18 +129,19 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
params *layer.RangeParams
|
params *layer.RangeParams
|
||||||
|
|
||||||
reqInfo = middleware.GetReqInfo(r.Context())
|
ctx = r.Context()
|
||||||
|
reqInfo = middleware.GetReqInfo(ctx)
|
||||||
)
|
)
|
||||||
|
|
||||||
conditional, err := parseConditionalHeaders(r.Header)
|
conditional, err := parseConditionalHeaders(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse request params", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse request params", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,37 +151,37 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedInfo, err := h.obj.GetExtendedObjectInfo(r.Context(), p)
|
extendedInfo, err := h.obj.GetExtendedObjectInfo(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not find object", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not find object", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info := extendedInfo.ObjectInfo
|
info := extendedInfo.ObjectInfo
|
||||||
|
|
||||||
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
h.logAndSendError(w, "precondition failed", reqInfo, err)
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = encryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(info.Headers)); err != nil {
|
if err = encryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(info.Headers)); err != nil {
|
||||||
h.logAndSendError(w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
h.logAndSendError(ctx, w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fullSize, err := layer.GetObjectSize(info)
|
fullSize, err := layer.GetObjectSize(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid size header", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
h.logAndSendError(ctx, w, "invalid size header", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if params, err = fetchRangeHeader(r.Header, fullSize); err != nil {
|
if params, err = fetchRangeHeader(r.Header, fullSize); err != nil {
|
||||||
h.logAndSendError(w, "could not parse range header", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse range header", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,24 +191,24 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
VersionID: info.VersionID(),
|
VersionID: info.VersionID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(r.Context(), t, extendedInfo.NodeVersion)
|
tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(ctx, t, extendedInfo.NodeVersion)
|
||||||
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
h.logAndSendError(w, "could not get object meta data", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get object meta data", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if layer.IsAuthenticatedRequest(r.Context()) {
|
if layer.IsAuthenticatedRequest(ctx) {
|
||||||
overrideResponseHeaders(w.Header(), reqInfo.URL.Query())
|
overrideResponseHeaders(w.Header(), reqInfo.URL.Query())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.setLockingHeaders(bktInfo, lockInfo, w.Header()); err != nil {
|
if err = h.setLockingHeaders(bktInfo, lockInfo, w.Header()); err != nil {
|
||||||
h.logAndSendError(w, "could not get locking info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get locking info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktSettings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,9 +220,9 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Encryption: encryptionParams,
|
Encryption: encryptionParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
objPayload, err := h.obj.GetObject(r.Context(), getPayloadParams)
|
objPayload, err := h.obj.GetObject(ctx, getPayloadParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get object payload", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get object payload", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +234,7 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = objPayload.StreamTo(w); err != nil {
|
if err = objPayload.StreamTo(w); err != nil {
|
||||||
h.logAndSendError(w, "could not stream object payload", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not stream object payload", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
stderrors "errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -89,7 +89,7 @@ func TestPreconditions(t *testing.T) {
|
||||||
name: "IfMatch false",
|
name: "IfMatch false",
|
||||||
info: newInfo(etag, today),
|
info: newInfo(etag, today),
|
||||||
args: &conditionalArgs{IfMatch: etag2},
|
args: &conditionalArgs{IfMatch: etag2},
|
||||||
expected: errors.GetAPIError(errors.ErrPreconditionFailed)},
|
expected: apierr.GetAPIError(apierr.ErrPreconditionFailed)},
|
||||||
{
|
{
|
||||||
name: "IfNoneMatch true",
|
name: "IfNoneMatch true",
|
||||||
info: newInfo(etag, today),
|
info: newInfo(etag, today),
|
||||||
|
@ -99,7 +99,7 @@ func TestPreconditions(t *testing.T) {
|
||||||
name: "IfNoneMatch false",
|
name: "IfNoneMatch false",
|
||||||
info: newInfo(etag, today),
|
info: newInfo(etag, today),
|
||||||
args: &conditionalArgs{IfNoneMatch: etag},
|
args: &conditionalArgs{IfNoneMatch: etag},
|
||||||
expected: errors.GetAPIError(errors.ErrNotModified)},
|
expected: apierr.GetAPIError(apierr.ErrNotModified)},
|
||||||
{
|
{
|
||||||
name: "IfModifiedSince true",
|
name: "IfModifiedSince true",
|
||||||
info: newInfo(etag, today),
|
info: newInfo(etag, today),
|
||||||
|
@ -109,7 +109,7 @@ func TestPreconditions(t *testing.T) {
|
||||||
name: "IfModifiedSince false",
|
name: "IfModifiedSince false",
|
||||||
info: newInfo(etag, yesterday),
|
info: newInfo(etag, yesterday),
|
||||||
args: &conditionalArgs{IfModifiedSince: &today},
|
args: &conditionalArgs{IfModifiedSince: &today},
|
||||||
expected: errors.GetAPIError(errors.ErrNotModified)},
|
expected: apierr.GetAPIError(apierr.ErrNotModified)},
|
||||||
{
|
{
|
||||||
name: "IfUnmodifiedSince true",
|
name: "IfUnmodifiedSince true",
|
||||||
info: newInfo(etag, yesterday),
|
info: newInfo(etag, yesterday),
|
||||||
|
@ -119,7 +119,7 @@ func TestPreconditions(t *testing.T) {
|
||||||
name: "IfUnmodifiedSince false",
|
name: "IfUnmodifiedSince false",
|
||||||
info: newInfo(etag, today),
|
info: newInfo(etag, today),
|
||||||
args: &conditionalArgs{IfUnmodifiedSince: &yesterday},
|
args: &conditionalArgs{IfUnmodifiedSince: &yesterday},
|
||||||
expected: errors.GetAPIError(errors.ErrPreconditionFailed)},
|
expected: apierr.GetAPIError(apierr.ErrPreconditionFailed)},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "IfMatch true, IfUnmodifiedSince false",
|
name: "IfMatch true, IfUnmodifiedSince false",
|
||||||
|
@ -131,19 +131,19 @@ func TestPreconditions(t *testing.T) {
|
||||||
name: "IfMatch false, IfUnmodifiedSince true",
|
name: "IfMatch false, IfUnmodifiedSince true",
|
||||||
info: newInfo(etag, yesterday),
|
info: newInfo(etag, yesterday),
|
||||||
args: &conditionalArgs{IfMatch: etag2, IfUnmodifiedSince: &today},
|
args: &conditionalArgs{IfMatch: etag2, IfUnmodifiedSince: &today},
|
||||||
expected: errors.GetAPIError(errors.ErrPreconditionFailed),
|
expected: apierr.GetAPIError(apierr.ErrPreconditionFailed),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IfNoneMatch false, IfModifiedSince true",
|
name: "IfNoneMatch false, IfModifiedSince true",
|
||||||
info: newInfo(etag, today),
|
info: newInfo(etag, today),
|
||||||
args: &conditionalArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday},
|
args: &conditionalArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday},
|
||||||
expected: errors.GetAPIError(errors.ErrNotModified),
|
expected: apierr.GetAPIError(apierr.ErrNotModified),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IfNoneMatch true, IfModifiedSince false",
|
name: "IfNoneMatch true, IfModifiedSince false",
|
||||||
info: newInfo(etag, yesterday),
|
info: newInfo(etag, yesterday),
|
||||||
args: &conditionalArgs{IfNoneMatch: etag2, IfModifiedSince: &today},
|
args: &conditionalArgs{IfNoneMatch: etag2, IfModifiedSince: &today},
|
||||||
expected: errors.GetAPIError(errors.ErrNotModified),
|
expected: apierr.GetAPIError(apierr.ErrNotModified),
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
@ -151,7 +151,7 @@ func TestPreconditions(t *testing.T) {
|
||||||
if tc.expected == nil {
|
if tc.expected == nil {
|
||||||
require.NoError(t, actual)
|
require.NoError(t, actual)
|
||||||
} else {
|
} else {
|
||||||
require.True(t, stderrors.Is(actual, tc.expected), tc.expected, actual)
|
require.True(t, errors.Is(actual, tc.expected), tc.expected, actual)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -193,8 +193,8 @@ func TestGetObject(t *testing.T) {
|
||||||
hc.tp.SetObjectError(addr, &apistatus.ObjectNotFound{})
|
hc.tp.SetObjectError(addr, &apistatus.ObjectNotFound{})
|
||||||
hc.tp.SetObjectError(objInfo.Address(), &apistatus.ObjectNotFound{})
|
hc.tp.SetObjectError(objInfo.Address(), &apistatus.ObjectNotFound{})
|
||||||
|
|
||||||
getObjectAssertS3Error(hc, bktName, objName, objInfo.VersionID(), errors.ErrNoSuchVersion)
|
getObjectAssertS3Error(hc, bktName, objName, objInfo.VersionID(), apierr.ErrNoSuchVersion)
|
||||||
getObjectAssertS3Error(hc, bktName, objName, emptyVersion, errors.ErrNoSuchKey)
|
getObjectAssertS3Error(hc, bktName, objName, emptyVersion, apierr.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetObjectEnabledMD5(t *testing.T) {
|
func TestGetObjectEnabledMD5(t *testing.T) {
|
||||||
|
@ -236,9 +236,9 @@ func getObjectVersion(tc *handlerContext, bktName, objName, version string) []by
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObjectAssertS3Error(hc *handlerContext, bktName, objName, version string, code errors.ErrorCode) {
|
func getObjectAssertS3Error(hc *handlerContext, bktName, objName, version string, code apierr.ErrorCode) {
|
||||||
w := getObjectBaseResponse(hc, bktName, objName, version)
|
w := getObjectBaseResponse(hc, bktName, objName, version)
|
||||||
assertS3Error(hc.t, w, errors.GetAPIError(code))
|
assertS3Error(hc.t, w, apierr.GetAPIError(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObjectBaseResponse(hc *handlerContext, bktName, objName, version string) *httptest.ResponseRecorder {
|
func getObjectBaseResponse(hc *handlerContext, bktName, objName, version string) *httptest.ResponseRecorder {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
||||||
|
@ -387,7 +388,7 @@ func createTestBucket(hc *handlerContext, bktName string) *data.BucketInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestBucketWithLock(hc *handlerContext, bktName string, conf *data.ObjectLockConfiguration) *data.BucketInfo {
|
func createTestBucketWithLock(hc *handlerContext, bktName string, conf *data.ObjectLockConfiguration) *data.BucketInfo {
|
||||||
res, err := hc.MockedPool().CreateContainer(hc.Context(), layer.PrmContainerCreate{
|
res, err := hc.MockedPool().CreateContainer(hc.Context(), frostfs.PrmContainerCreate{
|
||||||
Creator: hc.owner,
|
Creator: hc.owner,
|
||||||
Name: bktName,
|
Name: bktName,
|
||||||
AdditionalAttributes: [][2]string{{layer.AttributeLockEnabled, "true"}},
|
AdditionalAttributes: [][2]string{{layer.AttributeLockEnabled, "true"}},
|
||||||
|
|
|
@ -27,17 +27,18 @@ func getRangeToDetectContentType(maxSize uint64) *layer.RangeParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conditional, err := parseConditionalHeaders(r.Header)
|
conditional, err := parseConditionalHeaders(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse request params", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse request params", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,26 +48,26 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedInfo, err := h.obj.GetExtendedObjectInfo(r.Context(), p)
|
extendedInfo, err := h.obj.GetExtendedObjectInfo(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not find object", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not find object", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info := extendedInfo.ObjectInfo
|
info := extendedInfo.ObjectInfo
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = encryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(info.Headers)); err != nil {
|
if err = encryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(info.Headers)); err != nil {
|
||||||
h.logAndSendError(w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
h.logAndSendError(ctx, w, "encryption doesn't match object", reqInfo, errors.GetAPIError(errors.ErrBadRequest), zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
h.logAndSendError(w, "precondition failed", reqInfo, err)
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,9 +77,9 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
VersionID: info.VersionID(),
|
VersionID: info.VersionID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(r.Context(), t, extendedInfo.NodeVersion)
|
tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(ctx, t, extendedInfo.NodeVersion)
|
||||||
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
h.logAndSendError(w, "could not get object meta data", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get object meta data", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,15 +92,15 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
BucketInfo: bktInfo,
|
BucketInfo: bktInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
objPayload, err := h.obj.GetObject(r.Context(), getParams)
|
objPayload, err := h.obj.GetObject(ctx, getParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get object", reqInfo, err, zap.Stringer("oid", info.ID))
|
h.logAndSendError(ctx, w, "could not get object", reqInfo, err, zap.Stringer("oid", info.ID))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer, err := io.ReadAll(objPayload)
|
buffer, err := io.ReadAll(objPayload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not partly read payload to detect content type", reqInfo, err, zap.Stringer("oid", info.ID))
|
h.logAndSendError(ctx, w, "could not partly read payload to detect content type", reqInfo, err, zap.Stringer("oid", info.ID))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,13 +109,13 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.setLockingHeaders(bktInfo, lockInfo, w.Header()); err != nil {
|
if err = h.setLockingHeaders(bktInfo, lockInfo, w.Header()); err != nil {
|
||||||
h.logAndSendError(w, "could not get locking info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get locking info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktSettings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,11 +124,12 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +143,7 @@ func (h *handler) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.WriteResponse(w, http.StatusOK, nil, middleware.MimeNone); err != nil {
|
if err = middleware.WriteResponse(w, http.StatusOK, nil, middleware.MimeNone); err != nil {
|
||||||
h.logAndSendError(w, "write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "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-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
|
@ -95,8 +95,8 @@ func TestHeadObject(t *testing.T) {
|
||||||
hc.tp.SetObjectError(addr, &apistatus.ObjectNotFound{})
|
hc.tp.SetObjectError(addr, &apistatus.ObjectNotFound{})
|
||||||
hc.tp.SetObjectError(objInfo.Address(), &apistatus.ObjectNotFound{})
|
hc.tp.SetObjectError(objInfo.Address(), &apistatus.ObjectNotFound{})
|
||||||
|
|
||||||
headObjectAssertS3Error(hc, bktName, objName, objInfo.VersionID(), s3errors.ErrNoSuchVersion)
|
headObjectAssertS3Error(hc, bktName, objName, objInfo.VersionID(), apierr.ErrNoSuchVersion)
|
||||||
headObjectAssertS3Error(hc, bktName, objName, emptyVersion, s3errors.ErrNoSuchKey)
|
headObjectAssertS3Error(hc, bktName, objName, emptyVersion, apierr.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsAvailableToResolve(t *testing.T) {
|
func TestIsAvailableToResolve(t *testing.T) {
|
||||||
|
|
|
@ -7,15 +7,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, LocationResponse{Location: bktInfo.LocationConstraint}); err != nil {
|
if err = middleware.EncodeToResponse(w, LocationResponse{Location: bktInfo.LocationConstraint}); err != nil {
|
||||||
h.logAndSendError(w, "couldn't encode bucket location response", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't encode bucket location response", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -9,9 +12,11 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -26,18 +31,18 @@ func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := h.obj.GetBucketLifecycleConfiguration(ctx, bktInfo)
|
cfg, err := h.obj.GetBucketLifecycleConfiguration(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket lifecycle configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket lifecycle configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, cfg); err != nil {
|
if err = middleware.EncodeToResponse(w, cfg); err != nil {
|
||||||
h.logAndSendError(w, "could not encode GetBucketLifecycle response", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not encode GetBucketLifecycle response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,42 +57,63 @@ func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
|
||||||
// Content-Md5 is required and should be set
|
// Content-Md5 is required and should be set
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
|
||||||
if _, ok := r.Header[api.ContentMD5]; !ok {
|
if _, ok := r.Header[api.ContentMD5]; !ok {
|
||||||
h.logAndSendError(w, "missing Content-MD5", reqInfo, apiErr.GetAPIError(apiErr.ErrMissingContentMD5))
|
h.logAndSendError(ctx, w, "missing Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrMissingContentMD5))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
headerMD5, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := new(data.LifecycleConfiguration)
|
cfg := new(data.LifecycleConfiguration)
|
||||||
if err = h.cfg.NewXMLDecoder(tee).Decode(cfg); err != nil {
|
if err = h.cfg.NewXMLDecoder(tee).Decode(cfg); err != nil {
|
||||||
h.logAndSendError(w, "could not decode body", reqInfo, fmt.Errorf("%w: %s", apiErr.GetAPIError(apiErr.ErrMalformedXML), err.Error()))
|
h.logAndSendError(ctx, w, "could not decode body", reqInfo, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkLifecycleConfiguration(cfg); err != nil {
|
bodyMD5, err := getContentMD5(&buf)
|
||||||
h.logAndSendError(w, "invalid lifecycle configuration", reqInfo, fmt.Errorf("%w: %s", apiErr.GetAPIError(apiErr.ErrMalformedXML), err.Error()))
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "could not get content md5", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(headerMD5, bodyMD5) {
|
||||||
|
h.logAndSendError(ctx, w, "Content-MD5 does not match", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
networkInfo, err := h.obj.GetNetworkInfo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "could not get network info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkLifecycleConfiguration(ctx, cfg, &networkInfo); err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "invalid lifecycle configuration", reqInfo, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
params := &layer.PutBucketLifecycleParams{
|
params := &layer.PutBucketLifecycleParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
LifecycleCfg: cfg,
|
LifecycleCfg: cfg,
|
||||||
LifecycleReader: &buf,
|
|
||||||
MD5Hash: r.Header.Get(api.ContentMD5),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
params.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
params.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutBucketLifecycleConfiguration(ctx, params); err != nil {
|
if err = h.obj.PutBucketLifecycleConfiguration(ctx, params); err != nil {
|
||||||
h.logAndSendError(w, "could not put bucket lifecycle configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not put bucket lifecycle configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,25 +124,27 @@ func (h *handler) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Re
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.DeleteBucketLifecycleConfiguration(ctx, bktInfo); err != nil {
|
if err = h.obj.DeleteBucketLifecycleConfiguration(ctx, bktInfo); err != nil {
|
||||||
h.logAndSendError(w, "could not delete bucket lifecycle configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not delete bucket lifecycle configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLifecycleConfiguration(cfg *data.LifecycleConfiguration) error {
|
func checkLifecycleConfiguration(ctx context.Context, cfg *data.LifecycleConfiguration, ni *netmap.NetworkInfo) error {
|
||||||
|
now := layer.TimeNow(ctx)
|
||||||
|
|
||||||
if len(cfg.Rules) > maxRules {
|
if len(cfg.Rules) > maxRules {
|
||||||
return fmt.Errorf("number of rules cannot be greater than %d", maxRules)
|
return fmt.Errorf("number of rules cannot be greater than %d", maxRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
ids := make(map[string]struct{}, len(cfg.Rules))
|
ids := make(map[string]struct{}, len(cfg.Rules))
|
||||||
for _, rule := range cfg.Rules {
|
for i, rule := range cfg.Rules {
|
||||||
if _, ok := ids[rule.ID]; ok && rule.ID != "" {
|
if _, ok := ids[rule.ID]; ok && rule.ID != "" {
|
||||||
return fmt.Errorf("duplicate 'ID': %s", rule.ID)
|
return fmt.Errorf("duplicate 'ID': %s", rule.ID)
|
||||||
}
|
}
|
||||||
|
@ -160,8 +188,18 @@ func checkLifecycleConfiguration(cfg *data.LifecycleConfiguration) error {
|
||||||
return fmt.Errorf("expiration days must be a positive integer: %d", *rule.Expiration.Days)
|
return fmt.Errorf("expiration days must be a positive integer: %d", *rule.Expiration.Days)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := time.Parse("2006-01-02T15:04:05Z", rule.Expiration.Date); rule.Expiration.Date != "" && err != nil {
|
if rule.Expiration.Date != "" {
|
||||||
return fmt.Errorf("invalid value of expiration date: %s", rule.Expiration.Date)
|
parsedTime, err := time.Parse("2006-01-02T15:04:05Z", rule.Expiration.Date)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid value of expiration date: %s", rule.Expiration.Date)
|
||||||
|
}
|
||||||
|
|
||||||
|
epoch, err := util.TimeToEpoch(ni, now, parsedTime)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convert time to epoch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Rules[i].Expiration.Epoch = &epoch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,3 +271,12 @@ func checkLifecycleRuleFilter(filter *data.LifecycleRuleFilter) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getContentMD5(reader io.Reader) ([]byte, error) {
|
||||||
|
hash := md5.New()
|
||||||
|
_, err := io.Copy(hash, reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hash.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"github.com/mr-tron/base58"
|
"github.com/mr-tron/base58"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -340,7 +341,7 @@ func TestPutBucketLifecycleConfiguration(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if tc.error {
|
if tc.error {
|
||||||
putBucketLifecycleConfigurationErr(hc, bktName, tc.body, apiErrors.GetAPIError(apiErrors.ErrMalformedXML))
|
putBucketLifecycleConfigurationErr(hc, bktName, tc.body, apierr.GetAPIError(apierr.ErrMalformedXML))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +351,7 @@ func TestPutBucketLifecycleConfiguration(t *testing.T) {
|
||||||
require.Equal(t, *tc.body, *cfg)
|
require.Equal(t, *tc.body, *cfg)
|
||||||
|
|
||||||
deleteBucketLifecycleConfiguration(hc, bktName)
|
deleteBucketLifecycleConfiguration(hc, bktName)
|
||||||
getBucketLifecycleConfigurationErr(hc, bktName, apiErrors.GetAPIError(apiErrors.ErrNoSuchLifecycleConfiguration))
|
getBucketLifecycleConfigurationErr(hc, bktName, apierr.GetAPIError(apierr.ErrNoSuchLifecycleConfiguration))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,17 +375,17 @@ func TestPutBucketLifecycleInvalidMD5(t *testing.T) {
|
||||||
|
|
||||||
w, r := prepareTestRequest(hc, bktName, "", lifecycle)
|
w, r := prepareTestRequest(hc, bktName, "", lifecycle)
|
||||||
hc.Handler().PutBucketLifecycleHandler(w, r)
|
hc.Handler().PutBucketLifecycleHandler(w, r)
|
||||||
assertS3Error(hc.t, w, apiErrors.GetAPIError(apiErrors.ErrMissingContentMD5))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMissingContentMD5))
|
||||||
|
|
||||||
w, r = prepareTestRequest(hc, bktName, "", lifecycle)
|
w, r = prepareTestRequest(hc, bktName, "", lifecycle)
|
||||||
r.Header.Set(api.ContentMD5, "")
|
r.Header.Set(api.ContentMD5, "")
|
||||||
hc.Handler().PutBucketLifecycleHandler(w, r)
|
hc.Handler().PutBucketLifecycleHandler(w, r)
|
||||||
assertS3Error(hc.t, w, apiErrors.GetAPIError(apiErrors.ErrInvalidDigest))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||||
|
|
||||||
w, r = prepareTestRequest(hc, bktName, "", lifecycle)
|
w, r = prepareTestRequest(hc, bktName, "", lifecycle)
|
||||||
r.Header.Set(api.ContentMD5, "some-hash")
|
r.Header.Set(api.ContentMD5, "some-hash")
|
||||||
hc.Handler().PutBucketLifecycleHandler(w, r)
|
hc.Handler().PutBucketLifecycleHandler(w, r)
|
||||||
assertS3Error(hc.t, w, apiErrors.GetAPIError(apiErrors.ErrInvalidDigest))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPutBucketLifecycleInvalidXML(t *testing.T) {
|
func TestPutBucketLifecycleInvalidXML(t *testing.T) {
|
||||||
|
@ -393,10 +394,16 @@ func TestPutBucketLifecycleInvalidXML(t *testing.T) {
|
||||||
bktName := "bucket-lifecycle-invalid-xml"
|
bktName := "bucket-lifecycle-invalid-xml"
|
||||||
createBucket(hc, bktName)
|
createBucket(hc, bktName)
|
||||||
|
|
||||||
w, r := prepareTestRequest(hc, bktName, "", &data.CORSConfiguration{})
|
cfg := &data.CORSConfiguration{}
|
||||||
r.Header.Set(api.ContentMD5, "")
|
body, err := xml.Marshal(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
contentMD5, err := getContentMD5(bytes.NewReader(body))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w, r := prepareTestRequest(hc, bktName, "", cfg)
|
||||||
|
r.Header.Set(api.ContentMD5, base64.StdEncoding.EncodeToString(contentMD5))
|
||||||
hc.Handler().PutBucketLifecycleHandler(w, r)
|
hc.Handler().PutBucketLifecycleHandler(w, r)
|
||||||
assertS3Error(hc.t, w, apiErrors.GetAPIError(apiErrors.ErrMalformedXML))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMalformedXML))
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketLifecycleConfiguration(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration) {
|
func putBucketLifecycleConfiguration(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration) {
|
||||||
|
@ -404,7 +411,7 @@ func putBucketLifecycleConfiguration(hc *handlerContext, bktName string, cfg *da
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, err apiErrors.Error) {
|
func putBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, err apierr.Error) {
|
||||||
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg)
|
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg)
|
||||||
assertS3Error(hc.t, w, err)
|
assertS3Error(hc.t, w, err)
|
||||||
}
|
}
|
||||||
|
@ -430,7 +437,7 @@ func getBucketLifecycleConfiguration(hc *handlerContext, bktName string) *data.L
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, err apiErrors.Error) {
|
func getBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, err apierr.Error) {
|
||||||
w := getBucketLifecycleConfigurationBase(hc, bktName)
|
w := getBucketLifecycleConfigurationBase(hc, bktName)
|
||||||
assertS3Error(hc.t, w, err)
|
assertS3Error(hc.t, w, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,13 @@ func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
own user.ID
|
own user.ID
|
||||||
res *ListBucketsResponse
|
res *ListBucketsResponse
|
||||||
reqInfo = middleware.GetReqInfo(r.Context())
|
ctx = r.Context()
|
||||||
|
reqInfo = middleware.GetReqInfo(ctx)
|
||||||
)
|
)
|
||||||
|
|
||||||
list, err := h.obj.ListBuckets(r.Context())
|
list, err := h.obj.ListBuckets(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +44,6 @@ func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, res); err != nil {
|
if err = middleware.EncodeToResponse(w, res); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
)
|
)
|
||||||
|
@ -26,34 +26,35 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bktInfo.ObjectLockEnabled {
|
if !bktInfo.ObjectLockEnabled {
|
||||||
h.logAndSendError(w, "couldn't put object locking configuration", reqInfo,
|
h.logAndSendError(ctx, w, "couldn't put object locking configuration", reqInfo,
|
||||||
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotAllowed))
|
apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotAllowed))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lockingConf := &data.ObjectLockConfiguration{}
|
lockingConf := &data.ObjectLockConfiguration{}
|
||||||
if err = h.cfg.NewXMLDecoder(r.Body).Decode(lockingConf); err != nil {
|
if err = h.cfg.NewXMLDecoder(r.Body).Decode(lockingConf); err != nil {
|
||||||
h.logAndSendError(w, "couldn't parse locking configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't parse locking configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkLockConfiguration(lockingConf); err != nil {
|
if err = checkLockConfiguration(lockingConf); err != nil {
|
||||||
h.logAndSendError(w, "invalid lock configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid lock configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,30 +67,31 @@ func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *htt
|
||||||
Settings: &newSettings,
|
Settings: &newSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutBucketSettings(r.Context(), sp); err != nil {
|
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
||||||
h.logAndSendError(w, "couldn't put bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't put bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bktInfo.ObjectLockEnabled {
|
if !bktInfo.ObjectLockEnabled {
|
||||||
h.logAndSendError(w, "object lock disabled", reqInfo,
|
h.logAndSendError(ctx, w, "object lock disabled", reqInfo,
|
||||||
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,33 +103,34 @@ func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *htt
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, settings.LockConfiguration); err != nil {
|
if err = middleware.EncodeToResponse(w, settings.LockConfiguration); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bktInfo.ObjectLockEnabled {
|
if !bktInfo.ObjectLockEnabled {
|
||||||
h.logAndSendError(w, "object lock disabled", reqInfo,
|
h.logAndSendError(ctx, w, "object lock disabled", reqInfo,
|
||||||
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
legalHold := &data.LegalHold{}
|
legalHold := &data.LegalHold{}
|
||||||
if err = h.cfg.NewXMLDecoder(r.Body).Decode(legalHold); err != nil {
|
if err = h.cfg.NewXMLDecoder(r.Body).Decode(legalHold); err != nil {
|
||||||
h.logAndSendError(w, "couldn't parse legal hold configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't parse legal hold configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if legalHold.Status != legalHoldOn && legalHold.Status != legalHoldOff {
|
if legalHold.Status != legalHoldOn && legalHold.Status != legalHoldOff {
|
||||||
h.logAndSendError(w, "invalid legal hold status", reqInfo,
|
h.logAndSendError(ctx, w, "invalid legal hold status", reqInfo,
|
||||||
fmt.Errorf("invalid status %s", legalHold.Status))
|
fmt.Errorf("invalid status %s", legalHold.Status))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -147,28 +150,29 @@ func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutLockInfo(r.Context(), p); err != nil {
|
if err = h.obj.PutLockInfo(ctx, p); err != nil {
|
||||||
h.logAndSendError(w, "couldn't head put legal hold", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't head put legal hold", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bktInfo.ObjectLockEnabled {
|
if !bktInfo.ObjectLockEnabled {
|
||||||
h.logAndSendError(w, "object lock disabled", reqInfo,
|
h.logAndSendError(ctx, w, "object lock disabled", reqInfo,
|
||||||
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,9 +182,9 @@ func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque
|
||||||
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
}
|
}
|
||||||
|
|
||||||
lockInfo, err := h.obj.GetLockInfo(r.Context(), p)
|
lockInfo, err := h.obj.GetLockInfo(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't head lock object", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't head lock object", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,33 +194,34 @@ func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, legalHold); err != nil {
|
if err = middleware.EncodeToResponse(w, legalHold); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !bktInfo.ObjectLockEnabled {
|
if !bktInfo.ObjectLockEnabled {
|
||||||
h.logAndSendError(w, "object lock disabled", reqInfo,
|
h.logAndSendError(ctx, w, "object lock disabled", reqInfo,
|
||||||
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
retention := &data.Retention{}
|
retention := &data.Retention{}
|
||||||
if err = h.cfg.NewXMLDecoder(r.Body).Decode(retention); err != nil {
|
if err = h.cfg.NewXMLDecoder(r.Body).Decode(retention); err != nil {
|
||||||
h.logAndSendError(w, "couldn't parse object retention", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't parse object retention", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lock, err := formObjectLockFromRetention(r.Context(), retention, r.Header)
|
lock, err := formObjectLockFromRetention(ctx, retention, r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid retention configuration", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid retention configuration", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,28 +236,29 @@ func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Reque
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutLockInfo(r.Context(), p); err != nil {
|
if err = h.obj.PutLockInfo(ctx, p); err != nil {
|
||||||
h.logAndSendError(w, "couldn't put legal hold", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't put legal hold", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bktInfo.ObjectLockEnabled {
|
if !bktInfo.ObjectLockEnabled {
|
||||||
h.logAndSendError(w, "object lock disabled", reqInfo,
|
h.logAndSendError(ctx, w, "object lock disabled", reqInfo,
|
||||||
apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound))
|
apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,14 +268,14 @@ func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Reque
|
||||||
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
||||||
}
|
}
|
||||||
|
|
||||||
lockInfo, err := h.obj.GetLockInfo(r.Context(), p)
|
lockInfo, err := h.obj.GetLockInfo(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't head lock object", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't head lock object", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lockInfo.IsRetentionSet() {
|
if !lockInfo.IsRetentionSet() {
|
||||||
h.logAndSendError(w, "retention lock isn't set", reqInfo, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey))
|
h.logAndSendError(ctx, w, "retention lock isn't set", reqInfo, apierr.GetAPIError(apierr.ErrNoSuchKey))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +288,7 @@ func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, retention); err != nil {
|
if err = middleware.EncodeToResponse(w, retention); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +320,7 @@ func checkLockConfiguration(conf *data.ObjectLockConfiguration) error {
|
||||||
func formObjectLock(ctx context.Context, bktInfo *data.BucketInfo, defaultConfig *data.ObjectLockConfiguration, header http.Header) (*data.ObjectLock, error) {
|
func formObjectLock(ctx context.Context, bktInfo *data.BucketInfo, defaultConfig *data.ObjectLockConfiguration, header http.Header) (*data.ObjectLock, error) {
|
||||||
if !bktInfo.ObjectLockEnabled {
|
if !bktInfo.ObjectLockEnabled {
|
||||||
if existLockHeaders(header) {
|
if existLockHeaders(header) {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound)
|
return nil, apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -346,7 +352,7 @@ func formObjectLock(ctx context.Context, bktInfo *data.BucketInfo, defaultConfig
|
||||||
until := header.Get(api.AmzObjectLockRetainUntilDate)
|
until := header.Get(api.AmzObjectLockRetainUntilDate)
|
||||||
|
|
||||||
if mode != "" && until == "" || mode == "" && until != "" {
|
if mode != "" && until == "" || mode == "" && until != "" {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrObjectLockInvalidHeaders)
|
return nil, apierr.GetAPIError(apierr.ErrObjectLockInvalidHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode != "" {
|
if mode != "" {
|
||||||
|
@ -355,7 +361,7 @@ func formObjectLock(ctx context.Context, bktInfo *data.BucketInfo, defaultConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode != complianceMode && mode != governanceMode {
|
if mode != complianceMode && mode != governanceMode {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrUnknownWORMModeDirective)
|
return nil, apierr.GetAPIError(apierr.ErrUnknownWORMModeDirective)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectLock.Retention.IsCompliance = mode == complianceMode
|
objectLock.Retention.IsCompliance = mode == complianceMode
|
||||||
|
@ -364,7 +370,7 @@ func formObjectLock(ctx context.Context, bktInfo *data.BucketInfo, defaultConfig
|
||||||
if until != "" {
|
if until != "" {
|
||||||
retentionDate, err := time.Parse(time.RFC3339, until)
|
retentionDate, err := time.Parse(time.RFC3339, until)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidRetentionDate)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidRetentionDate)
|
||||||
}
|
}
|
||||||
if objectLock.Retention == nil {
|
if objectLock.Retention == nil {
|
||||||
objectLock.Retention = &data.RetentionLock{}
|
objectLock.Retention = &data.RetentionLock{}
|
||||||
|
@ -382,7 +388,7 @@ func formObjectLock(ctx context.Context, bktInfo *data.BucketInfo, defaultConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if objectLock.Retention.Until.Before(layer.TimeNow(ctx)) {
|
if objectLock.Retention.Until.Before(layer.TimeNow(ctx)) {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrPastObjectLockRetainDate)
|
return nil, apierr.GetAPIError(apierr.ErrPastObjectLockRetainDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,16 +403,16 @@ func existLockHeaders(header http.Header) bool {
|
||||||
|
|
||||||
func formObjectLockFromRetention(ctx context.Context, retention *data.Retention, header http.Header) (*data.ObjectLock, error) {
|
func formObjectLockFromRetention(ctx context.Context, retention *data.Retention, header http.Header) (*data.ObjectLock, error) {
|
||||||
if retention.Mode != governanceMode && retention.Mode != complianceMode {
|
if retention.Mode != governanceMode && retention.Mode != complianceMode {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrMalformedXML)
|
return nil, apierr.GetAPIError(apierr.ErrMalformedXML)
|
||||||
}
|
}
|
||||||
|
|
||||||
retentionDate, err := time.Parse(time.RFC3339, retention.RetainUntilDate)
|
retentionDate, err := time.Parse(time.RFC3339, retention.RetainUntilDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrMalformedXML)
|
return nil, apierr.GetAPIError(apierr.ErrMalformedXML)
|
||||||
}
|
}
|
||||||
|
|
||||||
if retentionDate.Before(layer.TimeNow(ctx)) {
|
if retentionDate.Before(layer.TimeNow(ctx)) {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrPastObjectLockRetainDate)
|
return nil, apierr.GetAPIError(apierr.ErrPastObjectLockRetainDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
var bypass bool
|
var bypass bool
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -270,23 +270,23 @@ func TestPutBucketLockConfigurationHandler(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
bucket string
|
bucket string
|
||||||
expectedError apiErrors.Error
|
expectedError apierr.Error
|
||||||
noError bool
|
noError bool
|
||||||
configuration *data.ObjectLockConfiguration
|
configuration *data.ObjectLockConfiguration
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "bkt not found",
|
name: "bkt not found",
|
||||||
expectedError: apiErrors.GetAPIError(apiErrors.ErrNoSuchBucket),
|
expectedError: apierr.GetAPIError(apierr.ErrNoSuchBucket),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bkt lock disabled",
|
name: "bkt lock disabled",
|
||||||
bucket: bktLockDisabled,
|
bucket: bktLockDisabled,
|
||||||
expectedError: apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotAllowed),
|
expectedError: apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotAllowed),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid configuration",
|
name: "invalid configuration",
|
||||||
bucket: bktLockEnabled,
|
bucket: bktLockEnabled,
|
||||||
expectedError: apiErrors.GetAPIError(apiErrors.ErrInternalError),
|
expectedError: apierr.GetAPIError(apierr.ErrInternalError),
|
||||||
configuration: &data.ObjectLockConfiguration{ObjectLockEnabled: "dummy"},
|
configuration: &data.ObjectLockConfiguration{ObjectLockEnabled: "dummy"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -359,18 +359,18 @@ func TestGetBucketLockConfigurationHandler(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
bucket string
|
bucket string
|
||||||
expectedError apiErrors.Error
|
expectedError apierr.Error
|
||||||
noError bool
|
noError bool
|
||||||
expectedConf *data.ObjectLockConfiguration
|
expectedConf *data.ObjectLockConfiguration
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "bkt not found",
|
name: "bkt not found",
|
||||||
expectedError: apiErrors.GetAPIError(apiErrors.ErrNoSuchBucket),
|
expectedError: apierr.GetAPIError(apierr.ErrNoSuchBucket),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bkt lock disabled",
|
name: "bkt lock disabled",
|
||||||
bucket: bktLockDisabled,
|
bucket: bktLockDisabled,
|
||||||
expectedError: apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound),
|
expectedError: apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bkt lock enabled empty default",
|
name: "bkt lock enabled empty default",
|
||||||
|
@ -407,7 +407,7 @@ func TestGetBucketLockConfigurationHandler(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertS3Error(t *testing.T, w *httptest.ResponseRecorder, expectedError apiErrors.Error) {
|
func assertS3Error(t *testing.T, w *httptest.ResponseRecorder, expectedError apierr.Error) {
|
||||||
actualErrorResponse := &middleware.ErrorResponse{}
|
actualErrorResponse := &middleware.ErrorResponse{}
|
||||||
err := xml.NewDecoder(w.Result().Body).Decode(actualErrorResponse)
|
err := xml.NewDecoder(w.Result().Body).Decode(actualErrorResponse)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -415,7 +415,7 @@ func assertS3Error(t *testing.T, w *httptest.ResponseRecorder, expectedError api
|
||||||
require.Equal(t, expectedError.HTTPStatusCode, w.Code)
|
require.Equal(t, expectedError.HTTPStatusCode, w.Code)
|
||||||
require.Equal(t, expectedError.Code, actualErrorResponse.Code)
|
require.Equal(t, expectedError.Code, actualErrorResponse.Code)
|
||||||
|
|
||||||
if expectedError.ErrCode != apiErrors.ErrInternalError {
|
if expectedError.ErrCode != apierr.ErrInternalError {
|
||||||
require.Equal(t, expectedError.Description, actualErrorResponse.Message)
|
require.Equal(t, expectedError.Description, actualErrorResponse.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -473,33 +473,33 @@ func TestObjectRetention(t *testing.T) {
|
||||||
objName := "obj-for-retention"
|
objName := "obj-for-retention"
|
||||||
createTestObject(hc, bktInfo, objName, encryption.Params{})
|
createTestObject(hc, bktInfo, objName, encryption.Params{})
|
||||||
|
|
||||||
getObjectRetention(hc, bktName, objName, nil, apiErrors.ErrNoSuchKey)
|
getObjectRetention(hc, bktName, objName, nil, apierr.ErrNoSuchKey)
|
||||||
|
|
||||||
retention := &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().Add(time.Minute).UTC().Format(time.RFC3339)}
|
retention := &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().Add(time.Minute).UTC().Format(time.RFC3339)}
|
||||||
putObjectRetention(hc, bktName, objName, retention, false, 0)
|
putObjectRetention(hc, bktName, objName, retention, false, 0)
|
||||||
getObjectRetention(hc, bktName, objName, retention, 0)
|
getObjectRetention(hc, bktName, objName, retention, 0)
|
||||||
|
|
||||||
retention = &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().UTC().Add(time.Minute).Format(time.RFC3339)}
|
retention = &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().UTC().Add(time.Minute).Format(time.RFC3339)}
|
||||||
putObjectRetention(hc, bktName, objName, retention, false, apiErrors.ErrInternalError)
|
putObjectRetention(hc, bktName, objName, retention, false, apierr.ErrInternalError)
|
||||||
|
|
||||||
retention = &data.Retention{Mode: complianceMode, RetainUntilDate: time.Now().Add(time.Minute).UTC().Format(time.RFC3339)}
|
retention = &data.Retention{Mode: complianceMode, RetainUntilDate: time.Now().Add(time.Minute).UTC().Format(time.RFC3339)}
|
||||||
putObjectRetention(hc, bktName, objName, retention, true, 0)
|
putObjectRetention(hc, bktName, objName, retention, true, 0)
|
||||||
getObjectRetention(hc, bktName, objName, retention, 0)
|
getObjectRetention(hc, bktName, objName, retention, 0)
|
||||||
|
|
||||||
putObjectRetention(hc, bktName, objName, retention, true, apiErrors.ErrInternalError)
|
putObjectRetention(hc, bktName, objName, retention, true, apierr.ErrInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObjectRetention(hc *handlerContext, bktName, objName string, retention *data.Retention, errCode apiErrors.ErrorCode) {
|
func getObjectRetention(hc *handlerContext, bktName, objName string, retention *data.Retention, errCode apierr.ErrorCode) {
|
||||||
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
||||||
hc.Handler().GetObjectRetentionHandler(w, r)
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
||||||
if errCode == 0 {
|
if errCode == 0 {
|
||||||
assertRetention(hc.t, w, retention)
|
assertRetention(hc.t, w, retention)
|
||||||
} else {
|
} else {
|
||||||
assertS3Error(hc.t, w, apiErrors.GetAPIError(errCode))
|
assertS3Error(hc.t, w, apierr.GetAPIError(errCode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectRetention(hc *handlerContext, bktName, objName string, retention *data.Retention, byPass bool, errCode apiErrors.ErrorCode) {
|
func putObjectRetention(hc *handlerContext, bktName, objName string, retention *data.Retention, byPass bool, errCode apierr.ErrorCode) {
|
||||||
w, r := prepareTestRequest(hc, bktName, objName, retention)
|
w, r := prepareTestRequest(hc, bktName, objName, retention)
|
||||||
if byPass {
|
if byPass {
|
||||||
r.Header.Set(api.AmzBypassGovernanceRetention, strconv.FormatBool(true))
|
r.Header.Set(api.AmzBypassGovernanceRetention, strconv.FormatBool(true))
|
||||||
|
@ -508,7 +508,7 @@ func putObjectRetention(hc *handlerContext, bktName, objName string, retention *
|
||||||
if errCode == 0 {
|
if errCode == 0 {
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
assertS3Error(hc.t, w, apiErrors.GetAPIError(errCode))
|
assertS3Error(hc.t, w, apierr.GetAPIError(errCode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,37 +572,37 @@ func TestPutLockErrors(t *testing.T) {
|
||||||
createTestBucketWithLock(hc, bktName, nil)
|
createTestBucketWithLock(hc, bktName, nil)
|
||||||
|
|
||||||
headers := map[string]string{api.AmzObjectLockMode: complianceMode}
|
headers := map[string]string{api.AmzObjectLockMode: complianceMode}
|
||||||
putObjectWithLockFailed(t, hc, bktName, objName, headers, apiErrors.ErrObjectLockInvalidHeaders)
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrObjectLockInvalidHeaders)
|
||||||
|
|
||||||
delete(headers, api.AmzObjectLockMode)
|
delete(headers, api.AmzObjectLockMode)
|
||||||
headers[api.AmzObjectLockRetainUntilDate] = time.Now().Add(time.Minute).Format(time.RFC3339)
|
headers[api.AmzObjectLockRetainUntilDate] = time.Now().Add(time.Minute).Format(time.RFC3339)
|
||||||
putObjectWithLockFailed(t, hc, bktName, objName, headers, apiErrors.ErrObjectLockInvalidHeaders)
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrObjectLockInvalidHeaders)
|
||||||
|
|
||||||
headers[api.AmzObjectLockMode] = "dummy"
|
headers[api.AmzObjectLockMode] = "dummy"
|
||||||
putObjectWithLockFailed(t, hc, bktName, objName, headers, apiErrors.ErrUnknownWORMModeDirective)
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrUnknownWORMModeDirective)
|
||||||
|
|
||||||
headers[api.AmzObjectLockMode] = complianceMode
|
headers[api.AmzObjectLockMode] = complianceMode
|
||||||
headers[api.AmzObjectLockRetainUntilDate] = time.Now().Format(time.RFC3339)
|
headers[api.AmzObjectLockRetainUntilDate] = time.Now().Format(time.RFC3339)
|
||||||
putObjectWithLockFailed(t, hc, bktName, objName, headers, apiErrors.ErrPastObjectLockRetainDate)
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrPastObjectLockRetainDate)
|
||||||
|
|
||||||
headers[api.AmzObjectLockRetainUntilDate] = "dummy"
|
headers[api.AmzObjectLockRetainUntilDate] = "dummy"
|
||||||
putObjectWithLockFailed(t, hc, bktName, objName, headers, apiErrors.ErrInvalidRetentionDate)
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrInvalidRetentionDate)
|
||||||
|
|
||||||
putObject(hc, bktName, objName)
|
putObject(hc, bktName, objName)
|
||||||
|
|
||||||
retention := &data.Retention{Mode: governanceMode}
|
retention := &data.Retention{Mode: governanceMode}
|
||||||
putObjectRetentionFailed(t, hc, bktName, objName, retention, apiErrors.ErrMalformedXML)
|
putObjectRetentionFailed(t, hc, bktName, objName, retention, apierr.ErrMalformedXML)
|
||||||
|
|
||||||
retention.Mode = "dummy"
|
retention.Mode = "dummy"
|
||||||
retention.RetainUntilDate = time.Now().Add(time.Minute).UTC().Format(time.RFC3339)
|
retention.RetainUntilDate = time.Now().Add(time.Minute).UTC().Format(time.RFC3339)
|
||||||
putObjectRetentionFailed(t, hc, bktName, objName, retention, apiErrors.ErrMalformedXML)
|
putObjectRetentionFailed(t, hc, bktName, objName, retention, apierr.ErrMalformedXML)
|
||||||
|
|
||||||
retention.Mode = governanceMode
|
retention.Mode = governanceMode
|
||||||
retention.RetainUntilDate = time.Now().UTC().Format(time.RFC3339)
|
retention.RetainUntilDate = time.Now().UTC().Format(time.RFC3339)
|
||||||
putObjectRetentionFailed(t, hc, bktName, objName, retention, apiErrors.ErrPastObjectLockRetainDate)
|
putObjectRetentionFailed(t, hc, bktName, objName, retention, apierr.ErrPastObjectLockRetainDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectWithLockFailed(t *testing.T, hc *handlerContext, bktName, objName string, headers map[string]string, errCode apiErrors.ErrorCode) {
|
func putObjectWithLockFailed(t *testing.T, hc *handlerContext, bktName, objName string, headers map[string]string, errCode apierr.ErrorCode) {
|
||||||
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
||||||
|
|
||||||
for key, val := range headers {
|
for key, val := range headers {
|
||||||
|
@ -610,13 +610,13 @@ func putObjectWithLockFailed(t *testing.T, hc *handlerContext, bktName, objName
|
||||||
}
|
}
|
||||||
|
|
||||||
hc.Handler().PutObjectHandler(w, r)
|
hc.Handler().PutObjectHandler(w, r)
|
||||||
assertS3Error(t, w, apiErrors.GetAPIError(errCode))
|
assertS3Error(t, w, apierr.GetAPIError(errCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectRetentionFailed(t *testing.T, hc *handlerContext, bktName, objName string, retention *data.Retention, errCode apiErrors.ErrorCode) {
|
func putObjectRetentionFailed(t *testing.T, hc *handlerContext, bktName, objName string, retention *data.Retention, errCode apierr.ErrorCode) {
|
||||||
w, r := prepareTestRequest(hc, bktName, objName, retention)
|
w, r := prepareTestRequest(hc, bktName, objName, retention)
|
||||||
hc.Handler().PutObjectRetentionHandler(w, r)
|
hc.Handler().PutObjectRetentionHandler(w, r)
|
||||||
assertS3Error(t, w, apiErrors.GetAPIError(errCode))
|
assertS3Error(t, w, apierr.GetAPIError(errCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertRetentionApproximate(t *testing.T, w *httptest.ResponseRecorder, retention *data.Retention, delta float64) {
|
func assertRetentionApproximate(t *testing.T, w *httptest.ResponseRecorder, retention *data.Retention, delta float64) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
|
@ -103,19 +104,20 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
uploadID := uuid.New()
|
uploadID := uuid.New()
|
||||||
cannedACLStatus := aclHeadersStatus(r)
|
cannedACLStatus := aclHeadersStatus(r)
|
||||||
additional := []zap.Field{zap.String("uploadID", uploadID.String())}
|
additional := []zap.Field{zap.String("uploadID", uploadID.String())}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cannedACLStatus == aclStatusYes {
|
if cannedACLStatus == aclStatusYes {
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(ctx, w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,14 +133,14 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
if len(r.Header.Get(api.AmzTagging)) > 0 {
|
if len(r.Header.Get(api.AmzTagging)) > 0 {
|
||||||
p.Data.TagSet, err = parseTaggingHeader(r.Header)
|
p.Data.TagSet, err = parseTaggingHeader(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse tagging", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not parse tagging", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Info.Encryption, err = formEncryptionParams(r)
|
p.Info.Encryption, err = formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,12 +154,12 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(p.Header, reqInfo.Namespace, bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(p.Header, reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.CreateMultipartUpload(r.Context(), p); err != nil {
|
if err = h.obj.CreateMultipartUpload(ctx, p); err != nil {
|
||||||
h.logAndSendError(w, "could create multipart upload", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could create multipart upload", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,17 +174,18 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, resp); err != nil {
|
if err = middleware.EncodeToResponse(w, resp); err != nil {
|
||||||
h.logAndSendError(w, "could not encode InitiateMultipartUploadResponse to response", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not encode InitiateMultipartUploadResponse to response", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,13 +198,13 @@ func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
partNumber, err := strconv.Atoi(partNumStr)
|
partNumber, err := strconv.Atoi(partNumStr)
|
||||||
if err != nil || partNumber < layer.UploadMinPartNumber || partNumber > layer.UploadMaxPartNumber {
|
if err != nil || partNumber < layer.UploadMinPartNumber || partNumber > layer.UploadMaxPartNumber {
|
||||||
h.logAndSendError(w, "invalid part number", reqInfo, errors.GetAPIError(errors.ErrInvalidPartNumber), additional...)
|
h.logAndSendError(ctx, w, "invalid part number", reqInfo, errors.GetAPIError(errors.ErrInvalidPartNumber), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := h.getBodyReader(r)
|
body, err := h.getBodyReader(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed to get body reader", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "failed to get body reader", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,13 +225,13 @@ func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
p.Info.Encryption, err = formEncryptionParams(r)
|
p.Info.Encryption, err = formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := h.obj.UploadPart(r.Context(), p)
|
hash, err := h.obj.UploadPart(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not upload a part", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not upload a part", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +241,7 @@ func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.Header().Set(api.ETag, data.Quote(hash))
|
w.Header().Set(api.ETag, data.Quote(hash))
|
||||||
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
||||||
h.logAndSendError(w, "write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,7 +259,7 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
partNumber, err := strconv.Atoi(partNumStr)
|
partNumber, err := strconv.Atoi(partNumStr)
|
||||||
if err != nil || partNumber < layer.UploadMinPartNumber || partNumber > layer.UploadMaxPartNumber {
|
if err != nil || partNumber < layer.UploadMinPartNumber || partNumber > layer.UploadMaxPartNumber {
|
||||||
h.logAndSendError(w, "invalid part number", reqInfo, errors.GetAPIError(errors.ErrInvalidPartNumber), additional...)
|
h.logAndSendError(ctx, w, "invalid part number", reqInfo, errors.GetAPIError(errors.ErrInvalidPartNumber), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,26 +270,26 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
srcBucket, srcObject, err := path2BucketObject(src)
|
srcBucket, srcObject, err := path2BucketObject(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid source copy", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid source copy", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
srcRange, err := parseRange(r.Header.Get(api.AmzCopySourceRange))
|
srcRange, err := parseRange(r.Header.Get(api.AmzCopySourceRange))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse copy range", reqInfo,
|
h.logAndSendError(ctx, w, "could not parse copy range", reqInfo,
|
||||||
errors.GetAPIError(errors.ErrInvalidCopyPartRange), additional...)
|
errors.GetAPIError(errors.ErrInvalidCopyPartRange), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
srcBktInfo, err := h.getBucketAndCheckOwner(r, srcBucket, api.AmzSourceExpectedBucketOwner)
|
srcBktInfo, err := h.getBucketAndCheckOwner(r, srcBucket, api.AmzSourceExpectedBucketOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get source bucket info", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not get source bucket info", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get target bucket info", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not get target bucket info", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,35 +302,35 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
srcInfo, err := h.obj.GetObjectInfo(ctx, headPrm)
|
srcInfo, err := h.obj.GetObjectInfo(ctx, headPrm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsS3Error(err, errors.ErrNoSuchKey) && versionID != "" {
|
if errors.IsS3Error(err, errors.ErrNoSuchKey) && versionID != "" {
|
||||||
h.logAndSendError(w, "could not head source object version", reqInfo,
|
h.logAndSendError(ctx, w, "could not head source object version", reqInfo,
|
||||||
errors.GetAPIError(errors.ErrBadRequest), additional...)
|
errors.GetAPIError(errors.ErrBadRequest), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.logAndSendError(w, "could not head source object", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not head source object", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args, err := parseCopyObjectArgs(r.Header)
|
args, err := parseCopyObjectArgs(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse copy object args", reqInfo,
|
h.logAndSendError(ctx, w, "could not parse copy object args", reqInfo,
|
||||||
errors.GetAPIError(errors.ErrInvalidCopyPartRange), additional...)
|
errors.GetAPIError(errors.ErrInvalidCopyPartRange), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkPreconditions(srcInfo, args.Conditional, h.cfg.MD5Enabled()); err != nil {
|
if err = checkPreconditions(srcInfo, args.Conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
h.logAndSendError(w, "precondition failed", reqInfo, errors.GetAPIError(errors.ErrPreconditionFailed),
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, errors.GetAPIError(errors.ErrPreconditionFailed),
|
||||||
additional...)
|
additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
srcEncryptionParams, err := formCopySourceEncryptionParams(r)
|
srcEncryptionParams, err := formCopySourceEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = srcEncryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(srcInfo.Headers)); err != nil {
|
if err = srcEncryptionParams.MatchObjectEncryption(layer.FormEncryptionInfo(srcInfo.Headers)); err != nil {
|
||||||
h.logAndSendError(w, "encryption doesn't match object", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrBadRequest), err), additional...)
|
h.logAndSendError(ctx, w, "encryption doesn't match object", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrBadRequest), err), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,13 +350,13 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
p.Info.Encryption, err = formEncryptionParams(r)
|
p.Info.Encryption, err = formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := h.obj.UploadPartCopy(ctx, p)
|
info, err := h.obj.UploadPartCopy(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not upload part copy", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not upload part copy", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,22 +370,23 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, response); err != nil {
|
if err = middleware.EncodeToResponse(w, response); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err, additional...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,12 +402,12 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.
|
||||||
|
|
||||||
reqBody := new(CompleteMultipartUpload)
|
reqBody := new(CompleteMultipartUpload)
|
||||||
if err = h.cfg.NewXMLDecoder(r.Body).Decode(reqBody); err != nil {
|
if err = h.cfg.NewXMLDecoder(r.Body).Decode(reqBody); err != nil {
|
||||||
h.logAndSendError(w, "could not read complete multipart upload xml", reqInfo,
|
h.logAndSendError(ctx, w, "could not read complete multipart upload xml", reqInfo,
|
||||||
fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error()), additional...)
|
fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error()), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(reqBody.Parts) == 0 {
|
if len(reqBody.Parts) == 0 {
|
||||||
h.logAndSendError(w, "invalid xml with parts", reqInfo, errors.GetAPIError(errors.ErrMalformedXML), additional...)
|
h.logAndSendError(ctx, w, "invalid xml with parts", reqInfo, errors.GetAPIError(errors.ErrMalformedXML), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +421,7 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.
|
||||||
objInfo, err := h.completeMultipartUpload(r, c, bktInfo)
|
objInfo, err := h.completeMultipartUpload(r, c, bktInfo)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "complete multipart error", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "complete multipart error", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,7 +437,7 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, response); err != nil {
|
if err = middleware.EncodeToResponse(w, response); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err, additional...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,11 +496,12 @@ func (h *handler) completeMultipartUpload(r *http.Request, c *layer.CompleteMult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,7 +514,7 @@ func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Req
|
||||||
if maxUploadsStr != "" {
|
if maxUploadsStr != "" {
|
||||||
val, err := strconv.Atoi(maxUploadsStr)
|
val, err := strconv.Atoi(maxUploadsStr)
|
||||||
if err != nil || val < 1 || val > 1000 {
|
if err != nil || val < 1 || val > 1000 {
|
||||||
h.logAndSendError(w, "invalid maxUploads", reqInfo, errors.GetAPIError(errors.ErrInvalidMaxUploads))
|
h.logAndSendError(ctx, w, "invalid maxUploads", reqInfo, errors.GetAPIError(errors.ErrInvalidMaxUploads))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
maxUploads = val
|
maxUploads = val
|
||||||
|
@ -525,23 +530,29 @@ func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Req
|
||||||
UploadIDMarker: queryValues.Get(uploadIDMarkerQueryName),
|
UploadIDMarker: queryValues.Get(uploadIDMarkerQueryName),
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := h.obj.ListMultipartUploads(r.Context(), p)
|
if p.EncodingType != "" && strings.ToLower(p.EncodingType) != urlEncodingType {
|
||||||
|
h.logAndSendError(ctx, w, "invalid encoding type", reqInfo, errors.GetAPIError(errors.ErrInvalidEncodingMethod))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := h.obj.ListMultipartUploads(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not list multipart uploads", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not list multipart uploads", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, encodeListMultipartUploadsToResponse(list, p)); err != nil {
|
if err = middleware.EncodeToResponse(w, encodeListMultipartUploadsToResponse(list, p)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,14 +561,14 @@ func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
queryValues = reqInfo.URL.Query()
|
queryValues = reqInfo.URL.Query()
|
||||||
uploadID = queryValues.Get(uploadIDHeaderName)
|
uploadID = queryValues.Get(uploadIDHeaderName)
|
||||||
additional = []zap.Field{zap.String("uploadID", uploadID), zap.String("Key", reqInfo.ObjectName)}
|
additional = []zap.Field{zap.String("uploadID", uploadID)}
|
||||||
maxParts = layer.MaxSizePartsList
|
maxParts = layer.MaxSizePartsList
|
||||||
)
|
)
|
||||||
|
|
||||||
if queryValues.Get("max-parts") != "" {
|
if queryValues.Get("max-parts") != "" {
|
||||||
val, err := strconv.Atoi(queryValues.Get("max-parts"))
|
val, err := strconv.Atoi(queryValues.Get("max-parts"))
|
||||||
if err != nil || val < 0 {
|
if err != nil || val < 0 {
|
||||||
h.logAndSendError(w, "invalid MaxParts", reqInfo, errors.GetAPIError(errors.ErrInvalidMaxParts), additional...)
|
h.logAndSendError(ctx, w, "invalid MaxParts", reqInfo, errors.GetAPIError(errors.ErrInvalidMaxParts), additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if val < layer.MaxSizePartsList {
|
if val < layer.MaxSizePartsList {
|
||||||
|
@ -567,7 +578,7 @@ func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if queryValues.Get("part-number-marker") != "" {
|
if queryValues.Get("part-number-marker") != "" {
|
||||||
if partNumberMarker, err = strconv.Atoi(queryValues.Get("part-number-marker")); err != nil || partNumberMarker < 0 {
|
if partNumberMarker, err = strconv.Atoi(queryValues.Get("part-number-marker")); err != nil || partNumberMarker < 0 {
|
||||||
h.logAndSendError(w, "invalid PartNumberMarker", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid PartNumberMarker", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -584,32 +595,33 @@ func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
p.Info.Encryption, err = formEncryptionParams(r)
|
p.Info.Encryption, err = formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := h.obj.ListParts(r.Context(), p)
|
list, err := h.obj.ListParts(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not list parts", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not list parts", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, encodeListPartsToResponse(list, p)); err != nil {
|
if err = middleware.EncodeToResponse(w, encodeListPartsToResponse(list, p)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadID := reqInfo.URL.Query().Get(uploadIDHeaderName)
|
uploadID := reqInfo.URL.Query().Get(uploadIDHeaderName)
|
||||||
additional := []zap.Field{zap.String("uploadID", uploadID), zap.String("Key", reqInfo.ObjectName)}
|
additional := []zap.Field{zap.String("uploadID", uploadID)}
|
||||||
|
|
||||||
p := &layer.UploadInfoParams{
|
p := &layer.UploadInfoParams{
|
||||||
UploadID: uploadID,
|
UploadID: uploadID,
|
||||||
|
@ -619,12 +631,12 @@ func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Req
|
||||||
|
|
||||||
p.Encryption, err = formEncryptionParams(r)
|
p.Encryption, err = formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.AbortMultipartUpload(r.Context(), p); err != nil {
|
if err = h.obj.AbortMultipartUpload(ctx, p); err != nil {
|
||||||
h.logAndSendError(w, "could not abort multipart upload", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not abort multipart upload", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,14 +647,14 @@ func encodeListMultipartUploadsToResponse(info *layer.ListMultipartUploadsInfo,
|
||||||
res := ListMultipartUploadsResponse{
|
res := ListMultipartUploadsResponse{
|
||||||
Bucket: params.Bkt.Name,
|
Bucket: params.Bkt.Name,
|
||||||
CommonPrefixes: fillPrefixes(info.Prefixes, params.EncodingType),
|
CommonPrefixes: fillPrefixes(info.Prefixes, params.EncodingType),
|
||||||
Delimiter: params.Delimiter,
|
Delimiter: s3PathEncode(params.Delimiter, params.EncodingType),
|
||||||
EncodingType: params.EncodingType,
|
EncodingType: params.EncodingType,
|
||||||
IsTruncated: info.IsTruncated,
|
IsTruncated: info.IsTruncated,
|
||||||
KeyMarker: params.KeyMarker,
|
KeyMarker: s3PathEncode(params.KeyMarker, params.EncodingType),
|
||||||
MaxUploads: params.MaxUploads,
|
MaxUploads: params.MaxUploads,
|
||||||
NextKeyMarker: info.NextKeyMarker,
|
NextKeyMarker: s3PathEncode(info.NextKeyMarker, params.EncodingType),
|
||||||
NextUploadIDMarker: info.NextUploadIDMarker,
|
NextUploadIDMarker: info.NextUploadIDMarker,
|
||||||
Prefix: params.Prefix,
|
Prefix: s3PathEncode(params.Prefix, params.EncodingType),
|
||||||
UploadIDMarker: params.UploadIDMarker,
|
UploadIDMarker: params.UploadIDMarker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,7 +666,7 @@ func encodeListMultipartUploadsToResponse(info *layer.ListMultipartUploadsInfo,
|
||||||
ID: u.Owner.String(),
|
ID: u.Owner.String(),
|
||||||
DisplayName: u.Owner.String(),
|
DisplayName: u.Owner.String(),
|
||||||
},
|
},
|
||||||
Key: u.Key,
|
Key: s3PathEncode(u.Key, params.EncodingType),
|
||||||
Owner: Owner{
|
Owner: Owner{
|
||||||
ID: u.Owner.String(),
|
ID: u.Owner.String(),
|
||||||
DisplayName: u.Owner.String(),
|
DisplayName: u.Owner.String(),
|
||||||
|
|
|
@ -9,13 +9,14 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3Errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
@ -40,7 +41,7 @@ func TestMultipartUploadInvalidPart(t *testing.T) {
|
||||||
etag1, _ := uploadPart(hc, bktName, objName, multipartUpload.UploadID, 1, partSize)
|
etag1, _ := uploadPart(hc, bktName, objName, multipartUpload.UploadID, 1, partSize)
|
||||||
etag2, _ := uploadPart(hc, bktName, objName, multipartUpload.UploadID, 2, partSize)
|
etag2, _ := uploadPart(hc, bktName, objName, multipartUpload.UploadID, 2, partSize)
|
||||||
w := completeMultipartUploadBase(hc, bktName, objName, multipartUpload.UploadID, []string{etag1, etag2})
|
w := completeMultipartUploadBase(hc, bktName, objName, multipartUpload.UploadID, []string{etag1, etag2})
|
||||||
assertS3Error(hc.t, w, s3Errors.GetAPIError(s3Errors.ErrEntityTooSmall))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrEntityTooSmall))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteMultipartAllParts(t *testing.T) {
|
func TestDeleteMultipartAllParts(t *testing.T) {
|
||||||
|
@ -104,7 +105,7 @@ func TestMultipartReUploadPart(t *testing.T) {
|
||||||
require.Equal(t, etag2, list.Parts[1].ETag)
|
require.Equal(t, etag2, list.Parts[1].ETag)
|
||||||
|
|
||||||
w := completeMultipartUploadBase(hc, bktName, objName, uploadInfo.UploadID, []string{etag1, etag2})
|
w := completeMultipartUploadBase(hc, bktName, objName, uploadInfo.UploadID, []string{etag1, etag2})
|
||||||
assertS3Error(hc.t, w, s3Errors.GetAPIError(s3Errors.ErrEntityTooSmall))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrEntityTooSmall))
|
||||||
|
|
||||||
etag1, data1 := uploadPart(hc, bktName, objName, uploadInfo.UploadID, 1, partSizeFirst)
|
etag1, data1 := uploadPart(hc, bktName, objName, uploadInfo.UploadID, 1, partSizeFirst)
|
||||||
etag2, data2 := uploadPart(hc, bktName, objName, uploadInfo.UploadID, 2, partSizeLast)
|
etag2, data2 := uploadPart(hc, bktName, objName, uploadInfo.UploadID, 2, partSizeLast)
|
||||||
|
@ -252,14 +253,14 @@ func TestListMultipartUploads(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check max uploads", func(t *testing.T) {
|
t.Run("check max uploads", func(t *testing.T) {
|
||||||
listUploads := listMultipartUploadsBase(hc, bktName, "", "", "", "", 2)
|
listUploads := listMultipartUploads(hc, bktName, "", "", "", "", 2)
|
||||||
require.Len(t, listUploads.Uploads, 2)
|
require.Len(t, listUploads.Uploads, 2)
|
||||||
require.Equal(t, uploadInfo1.UploadID, listUploads.Uploads[0].UploadID)
|
require.Equal(t, uploadInfo1.UploadID, listUploads.Uploads[0].UploadID)
|
||||||
require.Equal(t, uploadInfo2.UploadID, listUploads.Uploads[1].UploadID)
|
require.Equal(t, uploadInfo2.UploadID, listUploads.Uploads[1].UploadID)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check prefix", func(t *testing.T) {
|
t.Run("check prefix", func(t *testing.T) {
|
||||||
listUploads := listMultipartUploadsBase(hc, bktName, "/my", "", "", "", -1)
|
listUploads := listMultipartUploads(hc, bktName, "/my", "", "", "", -1)
|
||||||
require.Len(t, listUploads.Uploads, 2)
|
require.Len(t, listUploads.Uploads, 2)
|
||||||
require.Equal(t, uploadInfo1.UploadID, listUploads.Uploads[0].UploadID)
|
require.Equal(t, uploadInfo1.UploadID, listUploads.Uploads[0].UploadID)
|
||||||
require.Equal(t, uploadInfo2.UploadID, listUploads.Uploads[1].UploadID)
|
require.Equal(t, uploadInfo2.UploadID, listUploads.Uploads[1].UploadID)
|
||||||
|
@ -267,7 +268,7 @@ func TestListMultipartUploads(t *testing.T) {
|
||||||
|
|
||||||
t.Run("check markers", func(t *testing.T) {
|
t.Run("check markers", func(t *testing.T) {
|
||||||
t.Run("check only key-marker", func(t *testing.T) {
|
t.Run("check only key-marker", func(t *testing.T) {
|
||||||
listUploads := listMultipartUploadsBase(hc, bktName, "", "", "", objName2, -1)
|
listUploads := listMultipartUploads(hc, bktName, "", "", "", objName2, -1)
|
||||||
require.Len(t, listUploads.Uploads, 1)
|
require.Len(t, listUploads.Uploads, 1)
|
||||||
// If upload-id-marker is not specified, only the keys lexicographically greater than the specified key-marker will be included in the list.
|
// If upload-id-marker is not specified, only the keys lexicographically greater than the specified key-marker will be included in the list.
|
||||||
require.Equal(t, uploadInfo3.UploadID, listUploads.Uploads[0].UploadID)
|
require.Equal(t, uploadInfo3.UploadID, listUploads.Uploads[0].UploadID)
|
||||||
|
@ -278,7 +279,7 @@ func TestListMultipartUploads(t *testing.T) {
|
||||||
if uploadIDMarker > uploadInfo2.UploadID {
|
if uploadIDMarker > uploadInfo2.UploadID {
|
||||||
uploadIDMarker = uploadInfo2.UploadID
|
uploadIDMarker = uploadInfo2.UploadID
|
||||||
}
|
}
|
||||||
listUploads := listMultipartUploadsBase(hc, bktName, "", "", uploadIDMarker, "", -1)
|
listUploads := listMultipartUploads(hc, bktName, "", "", uploadIDMarker, "", -1)
|
||||||
// If key-marker is not specified, the upload-id-marker parameter is ignored.
|
// If key-marker is not specified, the upload-id-marker parameter is ignored.
|
||||||
require.Len(t, listUploads.Uploads, 3)
|
require.Len(t, listUploads.Uploads, 3)
|
||||||
})
|
})
|
||||||
|
@ -286,7 +287,7 @@ func TestListMultipartUploads(t *testing.T) {
|
||||||
t.Run("check key-marker along with upload-id-marker", func(t *testing.T) {
|
t.Run("check key-marker along with upload-id-marker", func(t *testing.T) {
|
||||||
uploadIDMarker := "00000000-0000-0000-0000-000000000000"
|
uploadIDMarker := "00000000-0000-0000-0000-000000000000"
|
||||||
|
|
||||||
listUploads := listMultipartUploadsBase(hc, bktName, "", "", uploadIDMarker, objName3, -1)
|
listUploads := listMultipartUploads(hc, bktName, "", "", uploadIDMarker, objName3, -1)
|
||||||
require.Len(t, listUploads.Uploads, 1)
|
require.Len(t, listUploads.Uploads, 1)
|
||||||
// If upload-id-marker is specified, any multipart uploads for a key equal to the key-marker might also be included,
|
// If upload-id-marker is specified, any multipart uploads for a key equal to the key-marker might also be included,
|
||||||
// provided those multipart uploads have upload IDs lexicographically greater than the specified upload-id-marker.
|
// provided those multipart uploads have upload IDs lexicographically greater than the specified upload-id-marker.
|
||||||
|
@ -521,7 +522,7 @@ func TestUploadPartCheckContentSHA256(t *testing.T) {
|
||||||
r.Header.Set(api.AmzContentSha256, tc.hash)
|
r.Header.Set(api.AmzContentSha256, tc.hash)
|
||||||
hc.Handler().UploadPartHandler(w, r)
|
hc.Handler().UploadPartHandler(w, r)
|
||||||
if tc.error {
|
if tc.error {
|
||||||
assertS3Error(t, w, s3Errors.GetAPIError(s3Errors.ErrContentSHA256Mismatch))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch))
|
||||||
|
|
||||||
list := listParts(hc, bktName, objName, multipartUpload.UploadID, "0", http.StatusOK)
|
list := listParts(hc, bktName, objName, multipartUpload.UploadID, "0", http.StatusOK)
|
||||||
require.Len(t, list.Parts, 1)
|
require.Len(t, list.Parts, 1)
|
||||||
|
@ -653,6 +654,42 @@ func TestUploadPartWithNegativeContentLength(t *testing.T) {
|
||||||
require.Equal(t, partSize, resp.ObjectParts.Parts[0].Size)
|
require.Equal(t, partSize, resp.ObjectParts.Parts[0].Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListMultipartUploadsEncoding(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName := "bucket-to-list-uploads-encoding"
|
||||||
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
|
listAllMultipartUploadsErr(hc, bktName, "invalid", apierr.GetAPIError(apierr.ErrInvalidEncodingMethod))
|
||||||
|
|
||||||
|
objects := []string{"foo()/bar", "foo()/bar/xyzzy", "asdf+b"}
|
||||||
|
for _, objName := range objects {
|
||||||
|
createMultipartUpload(hc, bktName, objName, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
listResponse := listMultipartUploadsURL(hc, bktName, "foo(", ")", "", "", -1)
|
||||||
|
|
||||||
|
require.Len(t, listResponse.CommonPrefixes, 1)
|
||||||
|
require.Equal(t, "foo%28%29", listResponse.CommonPrefixes[0].Prefix)
|
||||||
|
require.Equal(t, "foo%28", listResponse.Prefix)
|
||||||
|
require.Equal(t, "%29", listResponse.Delimiter)
|
||||||
|
require.Equal(t, "url", listResponse.EncodingType)
|
||||||
|
require.Equal(t, maxObjectList, listResponse.MaxUploads)
|
||||||
|
|
||||||
|
listResponse = listMultipartUploads(hc, bktName, "", "", "", "", 1)
|
||||||
|
require.Empty(t, listResponse.EncodingType)
|
||||||
|
|
||||||
|
listResponse = listMultipartUploadsURL(hc, bktName, "", "", "", listResponse.NextKeyMarker, 1)
|
||||||
|
|
||||||
|
require.Len(t, listResponse.CommonPrefixes, 0)
|
||||||
|
require.Len(t, listResponse.Uploads, 1)
|
||||||
|
require.Equal(t, "foo%28%29/bar", listResponse.Uploads[0].Key)
|
||||||
|
require.Equal(t, "asdf%2Bb", listResponse.KeyMarker)
|
||||||
|
require.Equal(t, "foo%28%29/bar", listResponse.NextKeyMarker)
|
||||||
|
require.Equal(t, "url", listResponse.EncodingType)
|
||||||
|
require.Equal(t, 1, listResponse.MaxUploads)
|
||||||
|
}
|
||||||
|
|
||||||
func uploadPartCopy(hc *handlerContext, bktName, objName, uploadID string, num int, srcObj string, start, end int) *UploadPartCopyResponse {
|
func uploadPartCopy(hc *handlerContext, bktName, objName, uploadID string, num int, srcObj string, start, end int) *UploadPartCopyResponse {
|
||||||
return uploadPartCopyBase(hc, bktName, objName, false, uploadID, num, srcObj, start, end)
|
return uploadPartCopyBase(hc, bktName, objName, false, uploadID, num, srcObj, start, end)
|
||||||
}
|
}
|
||||||
|
@ -678,16 +715,42 @@ func uploadPartCopyBase(hc *handlerContext, bktName, objName string, encrypted b
|
||||||
return uploadPartCopyResponse
|
return uploadPartCopyResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func listAllMultipartUploads(hc *handlerContext, bktName string) *ListMultipartUploadsResponse {
|
func listMultipartUploads(hc *handlerContext, bktName, prefix, delimiter, uploadIDMarker, keyMarker string, maxUploads int) *ListMultipartUploadsResponse {
|
||||||
return listMultipartUploadsBase(hc, bktName, "", "", "", "", -1)
|
w := listMultipartUploadsBase(hc, bktName, prefix, delimiter, uploadIDMarker, keyMarker, "", maxUploads)
|
||||||
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
res := &ListMultipartUploadsResponse{}
|
||||||
|
parseTestResponse(hc.t, w, res)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func listMultipartUploadsBase(hc *handlerContext, bktName, prefix, delimiter, uploadIDMarker, keyMarker string, maxUploads int) *ListMultipartUploadsResponse {
|
func listMultipartUploadsURL(hc *handlerContext, bktName, prefix, delimiter, uploadIDMarker, keyMarker string, maxUploads int) *ListMultipartUploadsResponse {
|
||||||
|
w := listMultipartUploadsBase(hc, bktName, prefix, delimiter, uploadIDMarker, keyMarker, urlEncodingType, maxUploads)
|
||||||
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
res := &ListMultipartUploadsResponse{}
|
||||||
|
parseTestResponse(hc.t, w, res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func listAllMultipartUploads(hc *handlerContext, bktName string) *ListMultipartUploadsResponse {
|
||||||
|
w := listMultipartUploadsBase(hc, bktName, "", "", "", "", "", -1)
|
||||||
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
res := &ListMultipartUploadsResponse{}
|
||||||
|
parseTestResponse(hc.t, w, res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func listAllMultipartUploadsErr(hc *handlerContext, bktName, encoding string, err apierr.Error) {
|
||||||
|
w := listMultipartUploadsBase(hc, bktName, "", "", "", "", encoding, -1)
|
||||||
|
assertS3Error(hc.t, w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listMultipartUploadsBase(hc *handlerContext, bktName, prefix, delimiter, uploadIDMarker, keyMarker, encoding string, maxUploads int) *httptest.ResponseRecorder {
|
||||||
query := make(url.Values)
|
query := make(url.Values)
|
||||||
query.Set(prefixQueryName, prefix)
|
query.Set(prefixQueryName, prefix)
|
||||||
query.Set(delimiterQueryName, delimiter)
|
query.Set(delimiterQueryName, delimiter)
|
||||||
query.Set(uploadIDMarkerQueryName, uploadIDMarker)
|
query.Set(uploadIDMarkerQueryName, uploadIDMarker)
|
||||||
query.Set(keyMarkerQueryName, keyMarker)
|
query.Set(keyMarkerQueryName, keyMarker)
|
||||||
|
query.Set(encodingTypeQueryName, encoding)
|
||||||
if maxUploads != -1 {
|
if maxUploads != -1 {
|
||||||
query.Set(maxUploadsQueryName, strconv.Itoa(maxUploads))
|
query.Set(maxUploadsQueryName, strconv.Itoa(maxUploads))
|
||||||
}
|
}
|
||||||
|
@ -695,10 +758,7 @@ func listMultipartUploadsBase(hc *handlerContext, bktName, prefix, delimiter, up
|
||||||
w, r := prepareTestRequestWithQuery(hc, bktName, "", query, nil)
|
w, r := prepareTestRequestWithQuery(hc, bktName, "", query, nil)
|
||||||
|
|
||||||
hc.Handler().ListMultipartUploadsHandler(w, r)
|
hc.Handler().ListMultipartUploadsHandler(w, r)
|
||||||
listPartsResponse := &ListMultipartUploadsResponse{}
|
return w
|
||||||
readResponse(hc.t, w, http.StatusOK, listPartsResponse)
|
|
||||||
|
|
||||||
return listPartsResponse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func listParts(hc *handlerContext, bktName, objName string, uploadID, partNumberMarker string, status int) *ListPartsResponse {
|
func listParts(hc *handlerContext, bktName, objName string, uploadID, partNumberMarker string, status int) *ListPartsResponse {
|
||||||
|
|
|
@ -8,5 +8,5 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) DeleteBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not supported", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
h.logAndSendError(r.Context(), w, "not supported", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
|
@ -16,26 +17,27 @@ import (
|
||||||
|
|
||||||
// ListObjectsV1Handler handles objects listing requests for API version 1.
|
// ListObjectsV1Handler handles objects listing requests for API version 1.
|
||||||
func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
params, err := parseListObjectsArgsV1(reqInfo)
|
params, err := parseListObjectsArgsV1(reqInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed to parse arguments", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to parse arguments", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.BktInfo, err = h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
if params.BktInfo, err = h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := h.obj.ListObjectsV1(r.Context(), params)
|
list, err := h.obj.ListObjectsV1(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, h.encodeV1(params, list)); err != nil {
|
if err = middleware.EncodeToResponse(w, h.encodeV1(params, list)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,26 +62,27 @@ func (h *handler) encodeV1(p *layer.ListObjectsParamsV1, list *layer.ListObjects
|
||||||
|
|
||||||
// ListObjectsV2Handler handles objects listing requests for API version 2.
|
// ListObjectsV2Handler handles objects listing requests for API version 2.
|
||||||
func (h *handler) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
params, err := parseListObjectsArgsV2(reqInfo)
|
params, err := parseListObjectsArgsV2(reqInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed to parse arguments", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to parse arguments", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.BktInfo, err = h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
if params.BktInfo, err = h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := h.obj.ListObjectsV2(r.Context(), params)
|
list, err := h.obj.ListObjectsV2(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, h.encodeV2(params, list)); err != nil {
|
if err = middleware.EncodeToResponse(w, h.encodeV2(params, list)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +156,10 @@ func parseListObjectArgs(reqInfo *middleware.ReqInfo) (*layer.ListObjectsParamsC
|
||||||
res.Delimiter = queryValues.Get("delimiter")
|
res.Delimiter = queryValues.Get("delimiter")
|
||||||
res.Encode = queryValues.Get("encoding-type")
|
res.Encode = queryValues.Get("encoding-type")
|
||||||
|
|
||||||
|
if res.Encode != "" && strings.ToLower(res.Encode) != urlEncodingType {
|
||||||
|
return nil, errors.GetAPIError(errors.ErrInvalidEncodingMethod)
|
||||||
|
}
|
||||||
|
|
||||||
if queryValues.Get("max-keys") == "" {
|
if queryValues.Get("max-keys") == "" {
|
||||||
res.MaxKeys = maxObjectList
|
res.MaxKeys = maxObjectList
|
||||||
} else if res.MaxKeys, err = strconv.Atoi(queryValues.Get("max-keys")); err != nil || res.MaxKeys < 0 {
|
} else if res.MaxKeys, err = strconv.Atoi(queryValues.Get("max-keys")); err != nil || res.MaxKeys < 0 {
|
||||||
|
@ -214,27 +221,28 @@ func fillContents(src []*data.ExtendedNodeVersion, encode string, fetchOwner, md
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
p, err := parseListObjectVersionsRequest(reqInfo)
|
p, err := parseListObjectVersionsRequest(reqInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed to parse request", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to parse request", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.BktInfo, err = h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
if p.BktInfo, err = h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := h.obj.ListObjectVersions(r.Context(), p)
|
info, err := h.obj.ListObjectVersions(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := encodeListObjectVersionsToResponse(p, info, p.BktInfo.Name, h.cfg.MD5Enabled())
|
response := encodeListObjectVersionsToResponse(p, info, p.BktInfo.Name, h.cfg.MD5Enabled())
|
||||||
if err = middleware.EncodeToResponse(w, response); err != nil {
|
if err = middleware.EncodeToResponse(w, response); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,6 +265,10 @@ func parseListObjectVersionsRequest(reqInfo *middleware.ReqInfo) (*layer.ListObj
|
||||||
res.Encode = queryValues.Get("encoding-type")
|
res.Encode = queryValues.Get("encoding-type")
|
||||||
res.VersionIDMarker = queryValues.Get("version-id-marker")
|
res.VersionIDMarker = queryValues.Get("version-id-marker")
|
||||||
|
|
||||||
|
if res.Encode != "" && strings.ToLower(res.Encode) != urlEncodingType {
|
||||||
|
return nil, errors.GetAPIError(errors.ErrInvalidEncodingMethod)
|
||||||
|
}
|
||||||
|
|
||||||
if res.VersionIDMarker != "" && res.KeyMarker == "" {
|
if res.VersionIDMarker != "" && res.KeyMarker == "" {
|
||||||
return nil, errors.GetAPIError(errors.VersionIDMarkerWithoutKeyMarker)
|
return nil, errors.GetAPIError(errors.VersionIDMarkerWithoutKeyMarker)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
@ -755,6 +757,16 @@ func TestListObjectVersionsEncoding(t *testing.T) {
|
||||||
require.Equal(t, 3, listResponse.MaxKeys)
|
require.Equal(t, 3, listResponse.MaxKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListingsWithInvalidEncodingType(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
bktName := "bucket-for-listing-invalid-encoding"
|
||||||
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
|
listObjectsVersionsErr(hc, bktName, "invalid", apierr.GetAPIError(apierr.ErrInvalidEncodingMethod))
|
||||||
|
listObjectsV2Err(hc, bktName, "invalid", apierr.GetAPIError(apierr.ErrInvalidEncodingMethod))
|
||||||
|
listObjectsV1Err(hc, bktName, "invalid", apierr.GetAPIError(apierr.ErrInvalidEncodingMethod))
|
||||||
|
}
|
||||||
|
|
||||||
func checkVersionsNames(t *testing.T, versions *ListObjectsVersionsResponse, names []string) {
|
func checkVersionsNames(t *testing.T, versions *ListObjectsVersionsResponse, names []string) {
|
||||||
for i, v := range versions.Version {
|
for i, v := range versions.Version {
|
||||||
require.Equal(t, names[i], v.Key)
|
require.Equal(t, names[i], v.Key)
|
||||||
|
@ -762,10 +774,19 @@ func checkVersionsNames(t *testing.T, versions *ListObjectsVersionsResponse, nam
|
||||||
}
|
}
|
||||||
|
|
||||||
func listObjectsV2(hc *handlerContext, bktName, prefix, delimiter, startAfter, continuationToken string, maxKeys int) *ListObjectsV2Response {
|
func listObjectsV2(hc *handlerContext, bktName, prefix, delimiter, startAfter, continuationToken string, maxKeys int) *ListObjectsV2Response {
|
||||||
return listObjectsV2Ext(hc, bktName, prefix, delimiter, startAfter, continuationToken, "", maxKeys)
|
w := listObjectsV2Base(hc, bktName, prefix, delimiter, startAfter, continuationToken, "", maxKeys)
|
||||||
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
res := &ListObjectsV2Response{}
|
||||||
|
parseTestResponse(hc.t, w, res)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func listObjectsV2Ext(hc *handlerContext, bktName, prefix, delimiter, startAfter, continuationToken, encodingType string, maxKeys int) *ListObjectsV2Response {
|
func listObjectsV2Err(hc *handlerContext, bktName, encoding string, err apierr.Error) {
|
||||||
|
w := listObjectsV2Base(hc, bktName, "", "", "", "", encoding, -1)
|
||||||
|
assertS3Error(hc.t, w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listObjectsV2Base(hc *handlerContext, bktName, prefix, delimiter, startAfter, continuationToken, encodingType string, maxKeys int) *httptest.ResponseRecorder {
|
||||||
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
||||||
query.Add("fetch-owner", "true")
|
query.Add("fetch-owner", "true")
|
||||||
if len(startAfter) != 0 {
|
if len(startAfter) != 0 {
|
||||||
|
@ -780,10 +801,7 @@ func listObjectsV2Ext(hc *handlerContext, bktName, prefix, delimiter, startAfter
|
||||||
|
|
||||||
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
||||||
hc.Handler().ListObjectsV2Handler(w, r)
|
hc.Handler().ListObjectsV2Handler(w, r)
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
return w
|
||||||
res := &ListObjectsV2Response{}
|
|
||||||
parseTestResponse(hc.t, w, res)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateListV1(t *testing.T, tc *handlerContext, bktName, prefix, delimiter, marker string, maxKeys int,
|
func validateListV1(t *testing.T, tc *handlerContext, bktName, prefix, delimiter, marker string, maxKeys int,
|
||||||
|
@ -843,28 +861,54 @@ func prepareCommonListObjectsQuery(prefix, delimiter string, maxKeys int) url.Va
|
||||||
}
|
}
|
||||||
|
|
||||||
func listObjectsV1(hc *handlerContext, bktName, prefix, delimiter, marker string, maxKeys int) *ListObjectsV1Response {
|
func listObjectsV1(hc *handlerContext, bktName, prefix, delimiter, marker string, maxKeys int) *ListObjectsV1Response {
|
||||||
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
w := listObjectsV1Base(hc, bktName, prefix, delimiter, marker, "", maxKeys)
|
||||||
if len(marker) != 0 {
|
|
||||||
query.Add("marker", marker)
|
|
||||||
}
|
|
||||||
|
|
||||||
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
|
||||||
hc.Handler().ListObjectsV1Handler(w, r)
|
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
res := &ListObjectsV1Response{}
|
res := &ListObjectsV1Response{}
|
||||||
parseTestResponse(hc.t, w, res)
|
parseTestResponse(hc.t, w, res)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listObjectsV1Err(hc *handlerContext, bktName, encoding string, err apierr.Error) {
|
||||||
|
w := listObjectsV1Base(hc, bktName, "", "", "", encoding, -1)
|
||||||
|
assertS3Error(hc.t, w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listObjectsV1Base(hc *handlerContext, bktName, prefix, delimiter, marker, encoding string, maxKeys int) *httptest.ResponseRecorder {
|
||||||
|
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
||||||
|
if len(marker) != 0 {
|
||||||
|
query.Add("marker", marker)
|
||||||
|
}
|
||||||
|
if len(encoding) != 0 {
|
||||||
|
query.Add("encoding-type", encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
||||||
|
hc.Handler().ListObjectsV1Handler(w, r)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
func listObjectsVersions(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int) *ListObjectsVersionsResponse {
|
func listObjectsVersions(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int) *ListObjectsVersionsResponse {
|
||||||
return listObjectsVersionsBase(hc, bktName, prefix, delimiter, keyMarker, versionIDMarker, maxKeys, false)
|
w := listObjectsVersionsBase(hc, bktName, prefix, delimiter, keyMarker, versionIDMarker, "", maxKeys)
|
||||||
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
res := &ListObjectsVersionsResponse{}
|
||||||
|
parseTestResponse(hc.t, w, res)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func listObjectsVersionsURL(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int) *ListObjectsVersionsResponse {
|
func listObjectsVersionsURL(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int) *ListObjectsVersionsResponse {
|
||||||
return listObjectsVersionsBase(hc, bktName, prefix, delimiter, keyMarker, versionIDMarker, maxKeys, true)
|
w := listObjectsVersionsBase(hc, bktName, prefix, delimiter, keyMarker, versionIDMarker, urlEncodingType, maxKeys)
|
||||||
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
res := &ListObjectsVersionsResponse{}
|
||||||
|
parseTestResponse(hc.t, w, res)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func listObjectsVersionsBase(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker string, maxKeys int, encode bool) *ListObjectsVersionsResponse {
|
func listObjectsVersionsErr(hc *handlerContext, bktName, encoding string, err apierr.Error) {
|
||||||
|
w := listObjectsVersionsBase(hc, bktName, "", "", "", "", encoding, -1)
|
||||||
|
assertS3Error(hc.t, w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listObjectsVersionsBase(hc *handlerContext, bktName, prefix, delimiter, keyMarker, versionIDMarker, encoding string, maxKeys int) *httptest.ResponseRecorder {
|
||||||
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
query := prepareCommonListObjectsQuery(prefix, delimiter, maxKeys)
|
||||||
if len(keyMarker) != 0 {
|
if len(keyMarker) != 0 {
|
||||||
query.Add("key-marker", keyMarker)
|
query.Add("key-marker", keyMarker)
|
||||||
|
@ -872,14 +916,11 @@ func listObjectsVersionsBase(hc *handlerContext, bktName, prefix, delimiter, key
|
||||||
if len(versionIDMarker) != 0 {
|
if len(versionIDMarker) != 0 {
|
||||||
query.Add("version-id-marker", versionIDMarker)
|
query.Add("version-id-marker", versionIDMarker)
|
||||||
}
|
}
|
||||||
if encode {
|
if len(encoding) != 0 {
|
||||||
query.Add("encoding-type", "url")
|
query.Add("encoding-type", encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
w, r := prepareTestFullRequest(hc, bktName, "", query, nil)
|
||||||
hc.Handler().ListBucketObjectVersionsHandler(w, r)
|
hc.Handler().ListBucketObjectVersionsHandler(w, r)
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
return w
|
||||||
res := &ListObjectsVersionsResponse{}
|
|
||||||
parseTestResponse(hc.t, w, res)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,30 +22,30 @@ func (h *handler) PatchObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(ctx)
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
if _, ok := r.Header[api.ContentRange]; !ok {
|
if _, ok := r.Header[api.ContentRange]; !ok {
|
||||||
h.logAndSendError(w, "missing Content-Range", reqInfo, errors.GetAPIError(errors.ErrMissingContentRange))
|
h.logAndSendError(ctx, w, "missing Content-Range", reqInfo, errors.GetAPIError(errors.ErrMissingContentRange))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := r.Header[api.ContentLength]; !ok {
|
if _, ok := r.Header[api.ContentLength]; !ok {
|
||||||
h.logAndSendError(w, "missing Content-Length", reqInfo, errors.GetAPIError(errors.ErrMissingContentLength))
|
h.logAndSendError(ctx, w, "missing Content-Length", reqInfo, errors.GetAPIError(errors.ErrMissingContentLength))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conditional, err := parsePatchConditionalHeaders(r.Header)
|
conditional, err := parsePatchConditionalHeaders(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse conditional headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse conditional headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,40 +57,40 @@ func (h *handler) PatchObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
extendedSrcObjInfo, err := h.obj.GetExtendedObjectInfo(ctx, srcObjPrm)
|
extendedSrcObjInfo, err := h.obj.GetExtendedObjectInfo(ctx, srcObjPrm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not find object", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not find object", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srcObjInfo := extendedSrcObjInfo.ObjectInfo
|
srcObjInfo := extendedSrcObjInfo.ObjectInfo
|
||||||
|
|
||||||
if err = checkPreconditions(srcObjInfo, conditional, h.cfg.MD5Enabled()); err != nil {
|
if err = checkPreconditions(srcObjInfo, conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
h.logAndSendError(w, "precondition failed", reqInfo, err)
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
srcSize, err := layer.GetObjectSize(srcObjInfo)
|
srcSize, err := layer.GetObjectSize(srcObjInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed to get source object size", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to get source object size", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
byteRange, err := parsePatchByteRange(r.Header.Get(api.ContentRange), srcSize)
|
byteRange, err := parsePatchByteRange(r.Header.Get(api.ContentRange), srcSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse byte range", reqInfo, errors.GetAPIError(errors.ErrInvalidRange), zap.Error(err))
|
h.logAndSendError(ctx, w, "could not parse byte range", reqInfo, errors.GetAPIError(errors.ErrInvalidRange), zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if maxPatchSize < byteRange.End-byteRange.Start+1 {
|
if maxPatchSize < byteRange.End-byteRange.Start+1 {
|
||||||
h.logAndSendError(w, "byte range length is longer than allowed", reqInfo, errors.GetAPIError(errors.ErrInvalidRange), zap.Error(err))
|
h.logAndSendError(ctx, w, "byte range length is longer than allowed", reqInfo, errors.GetAPIError(errors.ErrInvalidRange), zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if uint64(r.ContentLength) != (byteRange.End - byteRange.Start + 1) {
|
if uint64(r.ContentLength) != (byteRange.End - byteRange.Start + 1) {
|
||||||
h.logAndSendError(w, "content-length must be equal to byte range length", reqInfo, errors.GetAPIError(errors.ErrInvalidRangeLength))
|
h.logAndSendError(ctx, w, "content-length must be equal to byte range length", reqInfo, errors.GetAPIError(errors.ErrInvalidRangeLength))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if byteRange.Start > srcSize {
|
if byteRange.Start > srcSize {
|
||||||
h.logAndSendError(w, "start byte is greater than object size", reqInfo, errors.GetAPIError(errors.ErrRangeOutOfBounds))
|
h.logAndSendError(ctx, w, "start byte is greater than object size", reqInfo, errors.GetAPIError(errors.ErrRangeOutOfBounds))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,16 +104,16 @@ func (h *handler) PatchObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
params.CopiesNumbers, err = h.pickCopiesNumbers(nil, reqInfo.Namespace, bktInfo.LocationConstraint)
|
params.CopiesNumbers, err = h.pickCopiesNumbers(nil, reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedObjInfo, err := h.obj.PatchObject(ctx, params)
|
extendedObjInfo, err := h.obj.PatchObject(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isErrObjectLocked(err) {
|
if isErrObjectLocked(err) {
|
||||||
h.logAndSendError(w, "object is locked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
h.logAndSendError(ctx, w, "object is locked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||||
} else {
|
} else {
|
||||||
h.logAndSendError(w, "could not patch object", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not patch object", reqInfo, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func (h *handler) PatchObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, resp); err != nil {
|
if err = middleware.EncodeToResponse(w, resp); err != nil {
|
||||||
h.logAndSendError(w, "could not encode PatchObjectResult to response", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not encode PatchObjectResult to response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,7 @@ func TestPatch(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
rng string
|
rng string
|
||||||
headers map[string]string
|
headers map[string]string
|
||||||
code s3errors.ErrorCode
|
code apierr.ErrorCode
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success",
|
||||||
|
@ -63,22 +63,22 @@ func TestPatch(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "invalid range syntax",
|
name: "invalid range syntax",
|
||||||
rng: "bytes 0-2",
|
rng: "bytes 0-2",
|
||||||
code: s3errors.ErrInvalidRange,
|
code: apierr.ErrInvalidRange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid range length",
|
name: "invalid range length",
|
||||||
rng: "bytes 0-5/*",
|
rng: "bytes 0-5/*",
|
||||||
code: s3errors.ErrInvalidRangeLength,
|
code: apierr.ErrInvalidRangeLength,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid range start",
|
name: "invalid range start",
|
||||||
rng: "bytes 20-22/*",
|
rng: "bytes 20-22/*",
|
||||||
code: s3errors.ErrRangeOutOfBounds,
|
code: apierr.ErrRangeOutOfBounds,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "range is too long",
|
name: "range is too long",
|
||||||
rng: "bytes 0-5368709120/*",
|
rng: "bytes 0-5368709120/*",
|
||||||
code: s3errors.ErrInvalidRange,
|
code: apierr.ErrInvalidRange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "If-Unmodified-Since precondition are not satisfied",
|
name: "If-Unmodified-Since precondition are not satisfied",
|
||||||
|
@ -86,7 +86,7 @@ func TestPatch(t *testing.T) {
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
api.IfUnmodifiedSince: created.Add(-24 * time.Hour).Format(http.TimeFormat),
|
api.IfUnmodifiedSince: created.Add(-24 * time.Hour).Format(http.TimeFormat),
|
||||||
},
|
},
|
||||||
code: s3errors.ErrPreconditionFailed,
|
code: apierr.ErrPreconditionFailed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "If-Match precondition are not satisfied",
|
name: "If-Match precondition are not satisfied",
|
||||||
|
@ -94,7 +94,7 @@ func TestPatch(t *testing.T) {
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
api.IfMatch: "etag",
|
api.IfMatch: "etag",
|
||||||
},
|
},
|
||||||
code: s3errors.ErrPreconditionFailed,
|
code: apierr.ErrPreconditionFailed,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -377,7 +377,7 @@ func TestPatchEncryptedObject(t *testing.T) {
|
||||||
tc.Handler().PutObjectHandler(w, r)
|
tc.Handler().PutObjectHandler(w, r)
|
||||||
assertStatus(t, w, http.StatusOK)
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
|
||||||
patchObjectErr(t, tc, bktName, objName, "bytes 2-4/*", []byte("new"), nil, s3errors.ErrInternalError)
|
patchObjectErr(t, tc, bktName, objName, "bytes 2-4/*", []byte("new"), nil, apierr.ErrInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPatchMissingHeaders(t *testing.T) {
|
func TestPatchMissingHeaders(t *testing.T) {
|
||||||
|
@ -393,13 +393,13 @@ func TestPatchMissingHeaders(t *testing.T) {
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
r = httptest.NewRequest(http.MethodPatch, defaultURL, strings.NewReader("new"))
|
r = httptest.NewRequest(http.MethodPatch, defaultURL, strings.NewReader("new"))
|
||||||
tc.Handler().PatchObjectHandler(w, r)
|
tc.Handler().PatchObjectHandler(w, r)
|
||||||
assertS3Error(t, w, s3errors.GetAPIError(s3errors.ErrMissingContentRange))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrMissingContentRange))
|
||||||
|
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
r = httptest.NewRequest(http.MethodPatch, defaultURL, strings.NewReader("new"))
|
r = httptest.NewRequest(http.MethodPatch, defaultURL, strings.NewReader("new"))
|
||||||
r.Header.Set(api.ContentRange, "bytes 0-2/*")
|
r.Header.Set(api.ContentRange, "bytes 0-2/*")
|
||||||
tc.Handler().PatchObjectHandler(w, r)
|
tc.Handler().PatchObjectHandler(w, r)
|
||||||
assertS3Error(t, w, s3errors.GetAPIError(s3errors.ErrMissingContentLength))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrMissingContentLength))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePatchByteRange(t *testing.T) {
|
func TestParsePatchByteRange(t *testing.T) {
|
||||||
|
@ -501,9 +501,9 @@ func patchObjectVersion(t *testing.T, tc *handlerContext, bktName, objName, vers
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchObjectErr(t *testing.T, tc *handlerContext, bktName, objName, rng string, payload []byte, headers map[string]string, code s3errors.ErrorCode) {
|
func patchObjectErr(t *testing.T, tc *handlerContext, bktName, objName, rng string, payload []byte, headers map[string]string, code apierr.ErrorCode) {
|
||||||
w := patchObjectBase(tc, bktName, objName, "", rng, payload, headers)
|
w := patchObjectBase(tc, bktName, objName, "", rng, payload, headers)
|
||||||
assertS3Error(t, w, s3errors.GetAPIError(code))
|
assertS3Error(t, w, apierr.GetAPIError(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchObjectBase(tc *handlerContext, bktName, objName, version, rng string, payload []byte, headers map[string]string) *httptest.ResponseRecorder {
|
func patchObjectBase(tc *handlerContext, bktName, objName, version, rng string, payload []byte, headers map[string]string) *httptest.ResponseRecorder {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
stderrors "errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
@ -21,7 +21,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
|
@ -92,11 +92,11 @@ func (p *postPolicy) CheckField(key string, value string) error {
|
||||||
}
|
}
|
||||||
cond := p.condition(key)
|
cond := p.condition(key)
|
||||||
if cond == nil {
|
if cond == nil {
|
||||||
return errors.GetAPIError(errors.ErrPostPolicyConditionInvalidFormat)
|
return apierr.GetAPIError(apierr.ErrPostPolicyConditionInvalidFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cond.match(value) {
|
if !cond.match(value) {
|
||||||
return errors.GetAPIError(errors.ErrPostPolicyConditionInvalidFormat)
|
return apierr.GetAPIError(apierr.ErrPostPolicyConditionInvalidFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -193,24 +193,24 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket objInfo", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket objInfo", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cannedACLStatus == aclStatusYes {
|
if cannedACLStatus == aclStatusYes {
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(ctx, w, "acl not supported for this bucket", reqInfo, apierr.GetAPIError(apierr.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tagSet, err := parseTaggingHeader(r.Header)
|
tagSet, err := parseTaggingHeader(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse tagging header", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse tagging header", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,13 +230,13 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := h.getBodyReader(r)
|
body, err := h.getBodyReader(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed to get body reader", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed to get body reader", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if encodings := r.Header.Get(api.ContentEncoding); len(encodings) > 0 {
|
if encodings := r.Header.Get(api.ContentEncoding); len(encodings) > 0 {
|
||||||
|
@ -261,13 +261,13 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, bktInfo.LocationConstraint)
|
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
params.Lock, err = formObjectLock(ctx, bktInfo, settings.LockConfiguration, r.Header)
|
params.Lock, err = formObjectLock(ctx, bktInfo, settings.LockConfiguration, r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not form object lock", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not form object lock", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, err2 := io.Copy(io.Discard, body)
|
_, err2 := io.Copy(io.Discard, body)
|
||||||
err3 := body.Close()
|
err3 := body.Close()
|
||||||
h.logAndSendError(w, "could not upload object", reqInfo, err, zap.Errors("body close errors", []error{err2, err3}))
|
h.logAndSendError(ctx, w, "could not upload object", reqInfo, err, zap.Errors("body close errors", []error{err2, err3}))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objInfo := extendedObjInfo.ObjectInfo
|
objInfo := extendedObjInfo.ObjectInfo
|
||||||
|
@ -290,8 +290,8 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
TagSet: tagSet,
|
TagSet: tagSet,
|
||||||
NodeVersion: extendedObjInfo.NodeVersion,
|
NodeVersion: extendedObjInfo.NodeVersion,
|
||||||
}
|
}
|
||||||
if err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil {
|
if err = h.obj.PutObjectTagging(ctx, tagPrm); err != nil {
|
||||||
h.logAndSendError(w, "could not upload object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not upload object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,7 +306,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set(api.ETag, data.Quote(objInfo.ETag(h.cfg.MD5Enabled())))
|
w.Header().Set(api.ETag, data.Quote(objInfo.ETag(h.cfg.MD5Enabled())))
|
||||||
|
|
||||||
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
||||||
h.logAndSendError(w, "write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,16 +333,16 @@ func (h *handler) getBodyReader(r *http.Request) (io.ReadCloser, error) {
|
||||||
|
|
||||||
if !chunkedEncoding && !h.cfg.BypassContentEncodingInChunks() {
|
if !chunkedEncoding && !h.cfg.BypassContentEncodingInChunks() {
|
||||||
return nil, fmt.Errorf("%w: request is not chunk encoded, encodings '%s'",
|
return nil, fmt.Errorf("%w: request is not chunk encoded, encodings '%s'",
|
||||||
errors.GetAPIError(errors.ErrInvalidEncodingMethod), strings.Join(encodings, ","))
|
apierr.GetAPIError(apierr.ErrInvalidEncodingMethod), strings.Join(encodings, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeContentSize := r.Header.Get(api.AmzDecodedContentLength)
|
decodeContentSize := r.Header.Get(api.AmzDecodedContentLength)
|
||||||
if len(decodeContentSize) == 0 {
|
if len(decodeContentSize) == 0 {
|
||||||
return nil, errors.GetAPIError(errors.ErrMissingContentLength)
|
return nil, apierr.GetAPIError(apierr.ErrMissingContentLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := strconv.Atoi(decodeContentSize); err != nil {
|
if _, err := strconv.Atoi(decodeContentSize); err != nil {
|
||||||
return nil, fmt.Errorf("%w: parse decoded content length: %s", errors.GetAPIError(errors.ErrMissingContentLength), err.Error())
|
return nil, fmt.Errorf("%w: parse decoded content length: %s", apierr.GetAPIError(apierr.ErrMissingContentLength), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkReader, err := newSignV4ChunkedReader(r)
|
chunkReader, err := newSignV4ChunkedReader(r)
|
||||||
|
@ -378,43 +378,43 @@ func formEncryptionParamsBase(r *http.Request, isCopySource bool) (enc encryptio
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.TLS == nil {
|
if r.TLS == nil {
|
||||||
return enc, errors.GetAPIError(errors.ErrInsecureSSECustomerRequest)
|
return enc, apierr.GetAPIError(apierr.ErrInsecureSSECustomerRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sseCustomerKey) > 0 && len(sseCustomerAlgorithm) == 0 {
|
if len(sseCustomerKey) > 0 && len(sseCustomerAlgorithm) == 0 {
|
||||||
return enc, errors.GetAPIError(errors.ErrMissingSSECustomerAlgorithm)
|
return enc, apierr.GetAPIError(apierr.ErrMissingSSECustomerAlgorithm)
|
||||||
}
|
}
|
||||||
if len(sseCustomerAlgorithm) > 0 && len(sseCustomerKey) == 0 {
|
if len(sseCustomerAlgorithm) > 0 && len(sseCustomerKey) == 0 {
|
||||||
return enc, errors.GetAPIError(errors.ErrMissingSSECustomerKey)
|
return enc, apierr.GetAPIError(apierr.ErrMissingSSECustomerKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sseCustomerAlgorithm != layer.AESEncryptionAlgorithm {
|
if sseCustomerAlgorithm != layer.AESEncryptionAlgorithm {
|
||||||
return enc, errors.GetAPIError(errors.ErrInvalidEncryptionAlgorithm)
|
return enc, apierr.GetAPIError(apierr.ErrInvalidEncryptionAlgorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := base64.StdEncoding.DecodeString(sseCustomerKey)
|
key, err := base64.StdEncoding.DecodeString(sseCustomerKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isCopySource {
|
if isCopySource {
|
||||||
return enc, errors.GetAPIError(errors.ErrInvalidSSECustomerParameters)
|
return enc, apierr.GetAPIError(apierr.ErrInvalidSSECustomerParameters)
|
||||||
}
|
}
|
||||||
return enc, errors.GetAPIError(errors.ErrInvalidSSECustomerKey)
|
return enc, apierr.GetAPIError(apierr.ErrInvalidSSECustomerKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(key) != layer.AESKeySize {
|
if len(key) != layer.AESKeySize {
|
||||||
if isCopySource {
|
if isCopySource {
|
||||||
return enc, errors.GetAPIError(errors.ErrInvalidSSECustomerParameters)
|
return enc, apierr.GetAPIError(apierr.ErrInvalidSSECustomerParameters)
|
||||||
}
|
}
|
||||||
return enc, errors.GetAPIError(errors.ErrInvalidSSECustomerKey)
|
return enc, apierr.GetAPIError(apierr.ErrInvalidSSECustomerKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyMD5, err := base64.StdEncoding.DecodeString(sseCustomerKeyMD5)
|
keyMD5, err := base64.StdEncoding.DecodeString(sseCustomerKeyMD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return enc, errors.GetAPIError(errors.ErrSSECustomerKeyMD5Mismatch)
|
return enc, apierr.GetAPIError(apierr.ErrSSECustomerKeyMD5Mismatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
md5Sum := md5.Sum(key)
|
md5Sum := md5.Sum(key)
|
||||||
if !bytes.Equal(md5Sum[:], keyMD5) {
|
if !bytes.Equal(md5Sum[:], keyMD5) {
|
||||||
return enc, errors.GetAPIError(errors.ErrSSECustomerKeyMD5Mismatch)
|
return enc, apierr.GetAPIError(apierr.ErrSSECustomerKeyMD5Mismatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
params, err := encryption.NewParams(key)
|
params, err := encryption.NewParams(key)
|
||||||
|
@ -435,7 +435,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
policy, err := checkPostPolicy(r, reqInfo, metadata)
|
policy, err := checkPostPolicy(r, reqInfo, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "failed check policy", reqInfo, err)
|
h.logAndSendError(ctx, w, "failed check policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,31 +443,31 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
buffer := bytes.NewBufferString(tagging)
|
buffer := bytes.NewBufferString(tagging)
|
||||||
tags := new(data.Tagging)
|
tags := new(data.Tagging)
|
||||||
if err = h.cfg.NewXMLDecoder(buffer).Decode(tags); err != nil {
|
if err = h.cfg.NewXMLDecoder(buffer).Decode(tags); err != nil {
|
||||||
h.logAndSendError(w, "could not decode tag set", reqInfo,
|
h.logAndSendError(ctx, w, "could not decode tag set", reqInfo,
|
||||||
fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error()))
|
fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tagSet, err = h.readTagSet(tags)
|
tagSet, err = h.readTagSet(tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not read tag set", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not read tag set", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket objInfo", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket objInfo", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if acl := auth.MultipartFormValue(r, "acl"); acl != "" && acl != basicACLPrivate {
|
if acl := auth.MultipartFormValue(r, "acl"); acl != "" && acl != basicACLPrivate {
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(ctx, w, "acl not supported for this bucket", reqInfo, apierr.GetAPIError(apierr.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,7 +485,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
if reqInfo.ObjectName == "" || strings.Contains(reqInfo.ObjectName, "${filename}") {
|
if reqInfo.ObjectName == "" || strings.Contains(reqInfo.ObjectName, "${filename}") {
|
||||||
_, head, err := r.FormFile("file")
|
_, head, err := r.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse file field", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse file field", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
filename = head.Filename
|
filename = head.Filename
|
||||||
|
@ -494,7 +494,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
var head *multipart.FileHeader
|
var head *multipart.FileHeader
|
||||||
contentReader, head, err = r.FormFile("file")
|
contentReader, head, err = r.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse file field", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse file field", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
size = uint64(head.Size)
|
size = uint64(head.Size)
|
||||||
|
@ -508,12 +508,12 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if reqInfo.ObjectName == "" {
|
if reqInfo.ObjectName == "" {
|
||||||
h.logAndSendError(w, "missing object name", reqInfo, errors.GetAPIError(errors.ErrInvalidArgument))
|
h.logAndSendError(ctx, w, "missing object name", reqInfo, apierr.GetAPIError(apierr.ErrInvalidArgument))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !policy.CheckContentLength(size) {
|
if !policy.CheckContentLength(size) {
|
||||||
h.logAndSendError(w, "invalid content-length", reqInfo, errors.GetAPIError(errors.ErrInvalidArgument))
|
h.logAndSendError(ctx, w, "invalid content-length", reqInfo, apierr.GetAPIError(apierr.ErrInvalidArgument))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,7 +527,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
extendedObjInfo, err := h.obj.PutObject(ctx, params)
|
extendedObjInfo, err := h.obj.PutObject(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not upload object", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not upload object", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objInfo := extendedObjInfo.ObjectInfo
|
objInfo := extendedObjInfo.ObjectInfo
|
||||||
|
@ -543,7 +543,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutObjectTagging(ctx, tagPrm); err != nil {
|
if err = h.obj.PutObjectTagging(ctx, tagPrm); err != nil {
|
||||||
h.logAndSendError(w, "could not upload object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not upload object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,10 +571,10 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
respData, err := middleware.EncodeResponse(resp)
|
respData, err := middleware.EncodeResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "encode response", reqInfo, err)
|
h.logAndSendError(ctx, w, "encode response", reqInfo, err)
|
||||||
}
|
}
|
||||||
if _, err = w.Write(respData); err != nil {
|
if _, err = w.Write(respData); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -595,13 +595,13 @@ func checkPostPolicy(r *http.Request, reqInfo *middleware.ReqInfo, metadata map[
|
||||||
return nil, fmt.Errorf("could not unmarshal policy: %w", err)
|
return nil, fmt.Errorf("could not unmarshal policy: %w", err)
|
||||||
}
|
}
|
||||||
if policy.Expiration.Before(time.Now()) {
|
if policy.Expiration.Before(time.Now()) {
|
||||||
return nil, fmt.Errorf("policy is expired: %w", errors.GetAPIError(errors.ErrInvalidArgument))
|
return nil, fmt.Errorf("policy is expired: %w", apierr.GetAPIError(apierr.ErrInvalidArgument))
|
||||||
}
|
}
|
||||||
policy.empty = false
|
policy.empty = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.MultipartForm == nil {
|
if r.MultipartForm == nil {
|
||||||
return nil, stderrors.New("empty multipart form")
|
return nil, errors.New("empty multipart form")
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, v := range r.MultipartForm.Value {
|
for key, v := range r.MultipartForm.Value {
|
||||||
|
@ -632,7 +632,7 @@ func checkPostPolicy(r *http.Request, reqInfo *middleware.ReqInfo, metadata map[
|
||||||
for _, cond := range policy.Conditions {
|
for _, cond := range policy.Conditions {
|
||||||
if cond.Key == "bucket" {
|
if cond.Key == "bucket" {
|
||||||
if !cond.match(reqInfo.BucketName) {
|
if !cond.match(reqInfo.BucketName) {
|
||||||
return nil, errors.GetAPIError(errors.ErrPostPolicyConditionInvalidFormat)
|
return nil, apierr.GetAPIError(apierr.ErrPostPolicyConditionInvalidFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -674,10 +674,10 @@ func parseTaggingHeader(header http.Header) (map[string]string, error) {
|
||||||
if tagging := header.Get(api.AmzTagging); len(tagging) > 0 {
|
if tagging := header.Get(api.AmzTagging); len(tagging) > 0 {
|
||||||
queries, err := url.ParseQuery(tagging)
|
queries, err := url.ParseQuery(tagging)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.GetAPIError(errors.ErrInvalidArgument)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidArgument)
|
||||||
}
|
}
|
||||||
if len(queries) > maxTags {
|
if len(queries) > maxTags {
|
||||||
return nil, errors.GetAPIError(errors.ErrInvalidTagsSizeExceed)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidTagsSizeExceed)
|
||||||
}
|
}
|
||||||
tagSet = make(map[string]string, len(queries))
|
tagSet = make(map[string]string, len(queries))
|
||||||
for k, v := range queries {
|
for k, v := range queries {
|
||||||
|
@ -727,7 +727,7 @@ func (h *handler) parseCommonCreateBucketParams(reqInfo *middleware.ReqInfo, box
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.SessionContainerCreation == nil {
|
if p.SessionContainerCreation == nil {
|
||||||
return nil, nil, fmt.Errorf("%w: couldn't find session token for put", errors.GetAPIError(errors.ErrAccessDenied))
|
return nil, nil, fmt.Errorf("%w: couldn't find session token for put", apierr.GetAPIError(apierr.ErrAccessDenied))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkBucketName(reqInfo.BucketName); err != nil {
|
if err := checkBucketName(reqInfo.BucketName); err != nil {
|
||||||
|
@ -759,25 +759,25 @@ func (h *handler) createBucketHandlerPolicy(w http.ResponseWriter, r *http.Reque
|
||||||
|
|
||||||
boxData, err := middleware.GetBoxData(ctx)
|
boxData, err := middleware.GetBoxData(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "get access box from request", reqInfo, err)
|
h.logAndSendError(ctx, w, "get access box from request", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key, p, err := h.parseCommonCreateBucketParams(reqInfo, boxData, r)
|
key, p, err := h.parseCommonCreateBucketParams(reqInfo, boxData, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "parse create bucket params", reqInfo, err)
|
h.logAndSendError(ctx, w, "parse create bucket params", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cannedACL, err := parseCannedACL(r.Header)
|
cannedACL, err := parseCannedACL(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse canned ACL", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not parse canned ACL", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.obj.CreateBucket(ctx, p)
|
bktInfo, err := h.obj.CreateBucket(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not create bucket", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not create bucket", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.reqLogger(ctx).Info(logs.BucketIsCreated, zap.Stringer("container_id", bktInfo.CID))
|
h.reqLogger(ctx).Info(logs.BucketIsCreated, zap.Stringer("container_id", bktInfo.CID))
|
||||||
|
@ -785,7 +785,7 @@ func (h *handler) createBucketHandlerPolicy(w http.ResponseWriter, r *http.Reque
|
||||||
chains := bucketCannedACLToAPERules(cannedACL, reqInfo, bktInfo.CID)
|
chains := bucketCannedACLToAPERules(cannedACL, reqInfo, bktInfo.CID)
|
||||||
if err = h.ape.SaveACLChains(bktInfo.CID.EncodeToString(), chains); err != nil {
|
if err = h.ape.SaveACLChains(bktInfo.CID.EncodeToString(), chains); err != nil {
|
||||||
cleanErr := h.cleanupBucketCreation(ctx, reqInfo, bktInfo, boxData, chains)
|
cleanErr := h.cleanupBucketCreation(ctx, reqInfo, bktInfo, boxData, chains)
|
||||||
h.logAndSendError(w, "failed to add morph rule chain", reqInfo, err, zap.NamedError("cleanup_error", cleanErr))
|
h.logAndSendError(ctx, w, "failed to add morph rule chain", reqInfo, err, zap.NamedError("cleanup_error", cleanErr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -807,13 +807,13 @@ func (h *handler) createBucketHandlerPolicy(w http.ResponseWriter, r *http.Reque
|
||||||
}, h.putBucketSettingsRetryer())
|
}, h.putBucketSettingsRetryer())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanErr := h.cleanupBucketCreation(ctx, reqInfo, bktInfo, boxData, chains)
|
cleanErr := h.cleanupBucketCreation(ctx, reqInfo, bktInfo, boxData, chains)
|
||||||
h.logAndSendError(w, "couldn't save bucket settings", reqInfo, err,
|
h.logAndSendError(ctx, w, "couldn't save bucket settings", reqInfo, err,
|
||||||
zap.String("container_id", bktInfo.CID.EncodeToString()), zap.NamedError("cleanup_error", cleanErr))
|
zap.String("container_id", bktInfo.CID.EncodeToString()), zap.NamedError("cleanup_error", cleanErr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
||||||
h.logAndSendError(w, "write response", reqInfo, err)
|
h.logAndSendError(ctx, w, "write response", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -853,7 +853,7 @@ func (h *handler) putBucketSettingsRetryer() aws.RetryerV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
options.Retryables = []retry.IsErrorRetryable{retry.IsErrorRetryableFunc(func(err error) aws.Ternary {
|
options.Retryables = []retry.IsErrorRetryable{retry.IsErrorRetryableFunc(func(err error) aws.Ternary {
|
||||||
if stderrors.Is(err, tree.ErrNodeAccessDenied) {
|
if errors.Is(err, tree.ErrNodeAccessDenied) {
|
||||||
return aws.TrueTernary
|
return aws.TrueTernary
|
||||||
}
|
}
|
||||||
return aws.FalseTernary
|
return aws.FalseTernary
|
||||||
|
@ -982,7 +982,7 @@ func (h handler) setPlacementPolicy(prm *layer.CreateBucketParams, namespace, lo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.GetAPIError(errors.ErrInvalidLocationConstraint)
|
return apierr.GetAPIError(apierr.ErrInvalidLocationConstraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLockEnabled(log *zap.Logger, header http.Header) bool {
|
func isLockEnabled(log *zap.Logger, header http.Header) bool {
|
||||||
|
@ -1001,28 +1001,22 @@ func isLockEnabled(log *zap.Logger, header http.Header) bool {
|
||||||
|
|
||||||
func checkBucketName(bucketName string) error {
|
func checkBucketName(bucketName string) error {
|
||||||
if len(bucketName) < 3 || len(bucketName) > 63 {
|
if len(bucketName) < 3 || len(bucketName) > 63 {
|
||||||
return errors.GetAPIError(errors.ErrInvalidBucketName)
|
return apierr.GetAPIError(apierr.ErrInvalidBucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(bucketName, "xn--") || strings.HasSuffix(bucketName, "-s3alias") {
|
if strings.HasPrefix(bucketName, "xn--") || strings.HasSuffix(bucketName, "-s3alias") {
|
||||||
return errors.GetAPIError(errors.ErrInvalidBucketName)
|
return apierr.GetAPIError(apierr.ErrInvalidBucketName)
|
||||||
}
|
}
|
||||||
if net.ParseIP(bucketName) != nil {
|
if net.ParseIP(bucketName) != nil {
|
||||||
return errors.GetAPIError(errors.ErrInvalidBucketName)
|
return apierr.GetAPIError(apierr.ErrInvalidBucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := strings.Split(bucketName, ".")
|
for i, r := range bucketName {
|
||||||
for _, label := range labels {
|
if r == '.' || (!isAlphaNum(r) && r != '-') {
|
||||||
if len(label) == 0 {
|
return apierr.GetAPIError(apierr.ErrInvalidBucketName)
|
||||||
return errors.GetAPIError(errors.ErrInvalidBucketName)
|
|
||||||
}
|
}
|
||||||
for i, r := range label {
|
if (i == 0 || i == len(bucketName)-1) && r == '-' {
|
||||||
if !isAlphaNum(r) && r != '-' {
|
return apierr.GetAPIError(apierr.ErrInvalidBucketName)
|
||||||
return errors.GetAPIError(errors.ErrInvalidBucketName)
|
|
||||||
}
|
|
||||||
if (i == 0 || i == len(label)-1) && r == '-' {
|
|
||||||
return errors.GetAPIError(errors.ErrInvalidBucketName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1040,7 +1034,7 @@ func (h *handler) parseLocationConstraint(r *http.Request) (*createBucketParams,
|
||||||
|
|
||||||
params := new(createBucketParams)
|
params := new(createBucketParams)
|
||||||
if err := h.cfg.NewXMLDecoder(r.Body).Decode(params); err != nil {
|
if err := h.cfg.NewXMLDecoder(r.Body).Decode(params); err != nil {
|
||||||
return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error())
|
||||||
}
|
}
|
||||||
return params, nil
|
return params, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
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/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
|
@ -42,10 +42,10 @@ func TestCheckBucketName(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{name: "bucket"},
|
{name: "bucket"},
|
||||||
{name: "2bucket"},
|
{name: "2bucket"},
|
||||||
{name: "buc.ket"},
|
|
||||||
{name: "buc-ket"},
|
{name: "buc-ket"},
|
||||||
{name: "abc"},
|
{name: "abc"},
|
||||||
{name: "63aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
{name: "63aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||||
|
{name: "buc.ket", err: true},
|
||||||
{name: "buc.-ket", err: true},
|
{name: "buc.-ket", err: true},
|
||||||
{name: "bucket.", err: true},
|
{name: "bucket.", err: true},
|
||||||
{name: ".bucket", err: true},
|
{name: ".bucket", err: true},
|
||||||
|
@ -205,7 +205,7 @@ func TestPostObject(t *testing.T) {
|
||||||
t.Run(tc.key+";"+tc.filename, func(t *testing.T) {
|
t.Run(tc.key+";"+tc.filename, func(t *testing.T) {
|
||||||
w := postObjectBase(hc, ns, bktName, tc.key, tc.filename, tc.content)
|
w := postObjectBase(hc, ns, bktName, tc.key, tc.filename, tc.content)
|
||||||
if tc.err {
|
if tc.err {
|
||||||
assertS3Error(hc.t, w, s3errors.GetAPIError(s3errors.ErrInternalError))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrInternalError))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assertStatus(hc.t, w, http.StatusNoContent)
|
assertStatus(hc.t, w, http.StatusNoContent)
|
||||||
|
@ -268,7 +268,7 @@ func TestPutObjectWithStreamBodyError(t *testing.T) {
|
||||||
r.Header.Set(api.AmzContentSha256, api.StreamingContentSHA256)
|
r.Header.Set(api.AmzContentSha256, api.StreamingContentSHA256)
|
||||||
r.Header.Set(api.ContentEncoding, api.AwsChunked)
|
r.Header.Set(api.ContentEncoding, api.AwsChunked)
|
||||||
tc.Handler().PutObjectHandler(w, r)
|
tc.Handler().PutObjectHandler(w, r)
|
||||||
assertS3Error(t, w, s3errors.GetAPIError(s3errors.ErrMissingContentLength))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrMissingContentLength))
|
||||||
|
|
||||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
}
|
}
|
||||||
|
@ -284,7 +284,7 @@ func TestPutObjectWithInvalidContentMD5(t *testing.T) {
|
||||||
w, r := prepareTestPayloadRequest(tc, bktName, objName, bytes.NewReader(content))
|
w, r := prepareTestPayloadRequest(tc, bktName, objName, bytes.NewReader(content))
|
||||||
r.Header.Set(api.ContentMD5, base64.StdEncoding.EncodeToString([]byte("invalid")))
|
r.Header.Set(api.ContentMD5, base64.StdEncoding.EncodeToString([]byte("invalid")))
|
||||||
tc.Handler().PutObjectHandler(w, r)
|
tc.Handler().PutObjectHandler(w, r)
|
||||||
assertS3Error(t, w, s3errors.GetAPIError(s3errors.ErrInvalidDigest))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||||
|
|
||||||
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
checkNotFound(t, tc, bktName, objName, emptyVersion)
|
||||||
}
|
}
|
||||||
|
@ -347,7 +347,7 @@ func TestPutObjectCheckContentSHA256(t *testing.T) {
|
||||||
hc.Handler().PutObjectHandler(w, r)
|
hc.Handler().PutObjectHandler(w, r)
|
||||||
|
|
||||||
if tc.error {
|
if tc.error {
|
||||||
assertS3Error(t, w, s3errors.GetAPIError(s3errors.ErrContentSHA256Mismatch))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch))
|
||||||
|
|
||||||
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
||||||
hc.Handler().GetObjectHandler(w, r)
|
hc.Handler().GetObjectHandler(w, r)
|
||||||
|
@ -382,6 +382,26 @@ func TestPutObjectWithStreamBodyAWSExample(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPutObjectWithStreamEmptyBodyAWSExample(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName, objName := "dkirillov", "tmp"
|
||||||
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
|
w, req := getEmptyChunkedRequest(hc.context, t, bktName, objName)
|
||||||
|
hc.Handler().PutObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
|
||||||
|
w, req = prepareTestRequest(hc, bktName, objName, nil)
|
||||||
|
hc.Handler().HeadObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
require.Equal(t, "0", w.Header().Get(api.ContentLength))
|
||||||
|
|
||||||
|
res := listObjectsV1(hc, bktName, "", "", "", -1)
|
||||||
|
require.Len(t, res.Contents, 1)
|
||||||
|
require.Empty(t, res.Contents[0].Size)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPutChunkedTestContentEncoding(t *testing.T) {
|
func TestPutChunkedTestContentEncoding(t *testing.T) {
|
||||||
hc := prepareHandlerContext(t)
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
@ -400,7 +420,7 @@ func TestPutChunkedTestContentEncoding(t *testing.T) {
|
||||||
w, req, _ = getChunkedRequest(hc.context, t, bktName, objName)
|
w, req, _ = getChunkedRequest(hc.context, t, bktName, objName)
|
||||||
req.Header.Set(api.ContentEncoding, "gzip")
|
req.Header.Set(api.ContentEncoding, "gzip")
|
||||||
hc.Handler().PutObjectHandler(w, req)
|
hc.Handler().PutObjectHandler(w, req)
|
||||||
assertS3Error(t, w, s3errors.GetAPIError(s3errors.ErrInvalidEncodingMethod))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrInvalidEncodingMethod))
|
||||||
|
|
||||||
hc.config.bypassContentEncodingInChunks = true
|
hc.config.bypassContentEncodingInChunks = true
|
||||||
w, req, _ = getChunkedRequest(hc.context, t, bktName, objName)
|
w, req, _ = getChunkedRequest(hc.context, t, bktName, objName)
|
||||||
|
@ -474,15 +494,59 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
|
||||||
return w, req, chunk
|
return w, req, chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEmptyChunkedRequest(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
|
||||||
|
AWSAccessKeyID := "48c1K4PLVb7SvmV3PjDKEuXaMh8yZMXZ8Wx9msrkKcYw06dZeaxeiPe8vyFm2WsoeVaNt7UWEjNsVkagDs8oX4XXh"
|
||||||
|
AWSSecretAccessKey := "09260955b4eb0279dc017ba20a1ddac909cbd226c86cbb2d868e55534c8e64b0"
|
||||||
|
|
||||||
|
//awsCreds := credentials.NewStaticCredentials(AWSAccessKeyID, AWSSecretAccessKey, "")
|
||||||
|
//signer := v4.NewSigner(awsCreds)
|
||||||
|
|
||||||
|
reqBody := bytes.NewBufferString("0;chunk-signature=311a7142c8f3a07972c3aca65c36484b513a8fee48ab7178c7225388f2ae9894\r\n\r\n")
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", "http://localhost:8084/"+bktName+"/"+objName, reqBody)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("Amz-Sdk-Invocation-Id", "8a8cd4be-aef8-8034-f08d-a6144ade41f9")
|
||||||
|
req.Header.Set("Amz-Sdk-Request", "attempt=1; max=2")
|
||||||
|
req.Header.Set(api.Authorization, "AWS4-HMAC-SHA256 Credential=48c1K4PLVb7SvmV3PjDKEuXaMh8yZMXZ8Wx9msrkKcYw06dZeaxeiPe8vyFm2WsoeVaNt7UWEjNsVkagDs8oX4XXh/20241003/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-encoding;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length, Signature=4b530ab4af2381f214941af591266b209968264a2c94337fa1efc048c7dff352")
|
||||||
|
req.Header.Set(api.ContentEncoding, "aws-chunked")
|
||||||
|
req.Header.Set(api.ContentLength, "86")
|
||||||
|
req.Header.Set(api.ContentType, "text/plain; charset=UTF-8")
|
||||||
|
req.Header.Set(api.AmzDate, "20241003T100055Z")
|
||||||
|
req.Header.Set(api.AmzContentSha256, "STREAMING-AWS4-HMAC-SHA256-PAYLOAD")
|
||||||
|
req.Header.Set(api.AmzDecodedContentLength, "0")
|
||||||
|
|
||||||
|
signTime, err := time.Parse("20060102T150405Z", "20241003T100055Z")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
|
||||||
|
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
|
||||||
|
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
|
||||||
|
ClientTime: signTime,
|
||||||
|
AuthHeaders: &middleware.AuthHeader{
|
||||||
|
AccessKeyID: AWSAccessKeyID,
|
||||||
|
SignatureV4: "4b530ab4af2381f214941af591266b209968264a2c94337fa1efc048c7dff352",
|
||||||
|
Region: "us-east-1",
|
||||||
|
},
|
||||||
|
AccessBox: &accessbox.Box{
|
||||||
|
Gate: &accessbox.GateData{
|
||||||
|
SecretKey: AWSSecretAccessKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
return w, req
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateBucket(t *testing.T) {
|
func TestCreateBucket(t *testing.T) {
|
||||||
hc := prepareHandlerContext(t)
|
hc := prepareHandlerContext(t)
|
||||||
bktName := "bkt-name"
|
bktName := "bkt-name"
|
||||||
|
|
||||||
info := createBucket(hc, bktName)
|
info := createBucket(hc, bktName)
|
||||||
createBucketAssertS3Error(hc, bktName, info.Box, s3errors.ErrBucketAlreadyOwnedByYou)
|
createBucketAssertS3Error(hc, bktName, info.Box, apierr.ErrBucketAlreadyOwnedByYou)
|
||||||
|
|
||||||
box2, _ := createAccessBox(t)
|
box2, _ := createAccessBox(t)
|
||||||
createBucketAssertS3Error(hc, bktName, box2, s3errors.ErrBucketAlreadyExists)
|
createBucketAssertS3Error(hc, bktName, box2, apierr.ErrBucketAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateBucketWithoutPermissions(t *testing.T) {
|
func TestCreateBucketWithoutPermissions(t *testing.T) {
|
||||||
|
@ -492,7 +556,7 @@ func TestCreateBucketWithoutPermissions(t *testing.T) {
|
||||||
hc.h.ape.(*apeMock).err = errors.New("no permissions")
|
hc.h.ape.(*apeMock).err = errors.New("no permissions")
|
||||||
|
|
||||||
box, _ := createAccessBox(t)
|
box, _ := createAccessBox(t)
|
||||||
createBucketAssertS3Error(hc, bktName, box, s3errors.ErrInternalError)
|
createBucketAssertS3Error(hc, bktName, box, apierr.ErrInternalError)
|
||||||
|
|
||||||
_, err := hc.tp.ContainerID(bktName)
|
_, err := hc.tp.ContainerID(bktName)
|
||||||
require.Errorf(t, err, "container exists after failed creation, but shouldn't")
|
require.Errorf(t, err, "container exists after failed creation, but shouldn't")
|
||||||
|
|
|
@ -26,13 +26,13 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request
|
||||||
|
|
||||||
tagSet, err := h.readTagSet(reqInfo.Tagging)
|
tagSet, err := h.readTagSet(reqInfo.Tagging)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not read tag set", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not read tag set", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutObjectTagging(ctx, tagPrm); err != nil {
|
if err = h.obj.PutObjectTagging(ctx, tagPrm); err != nil {
|
||||||
h.logAndSendError(w, "could not put object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not put object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,17 +54,18 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,9 +77,9 @@ func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
versionID, tagSet, err := h.obj.GetObjectTagging(r.Context(), tagPrm)
|
versionID, tagSet, err := h.obj.GetObjectTagging(ctx, tagPrm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request
|
||||||
w.Header().Set(api.AmzVersionID, versionID)
|
w.Header().Set(api.AmzVersionID, versionID)
|
||||||
}
|
}
|
||||||
if err = middleware.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
if err = middleware.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +108,7 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.DeleteObjectTagging(ctx, p); err != nil {
|
if err = h.obj.DeleteObjectTagging(ctx, p); err != nil {
|
||||||
h.logAndSendError(w, "could not delete object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not delete object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,58 +116,61 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
tagSet, err := h.readTagSet(reqInfo.Tagging)
|
tagSet, err := h.readTagSet(reqInfo.Tagging)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not read tag set", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not read tag set", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutBucketTagging(r.Context(), bktInfo, tagSet); err != nil {
|
if err = h.obj.PutBucketTagging(ctx, bktInfo, tagSet); err != nil {
|
||||||
h.logAndSendError(w, "could not put object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not put object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tagSet, err := h.obj.GetBucketTagging(r.Context(), bktInfo)
|
tagSet, err := h.obj.GetBucketTagging(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get object tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get object tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
if err = middleware.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.DeleteBucketTagging(r.Context(), bktInfo); err != nil {
|
if err = h.obj.DeleteBucketTagging(ctx, bktInfo); err != nil {
|
||||||
h.logAndSendError(w, "could not delete bucket tagging", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not delete bucket tagging", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -98,7 +98,7 @@ func TestPutObjectTaggingCheckUniqueness(t *testing.T) {
|
||||||
middleware.GetReqInfo(r.Context()).Tagging = tc.body
|
middleware.GetReqInfo(r.Context()).Tagging = tc.body
|
||||||
hc.Handler().PutObjectTaggingHandler(w, r)
|
hc.Handler().PutObjectTaggingHandler(w, r)
|
||||||
if tc.error {
|
if tc.error {
|
||||||
assertS3Error(t, w, apiErrors.GetAPIError(apiErrors.ErrInvalidTagKeyUniqueness))
|
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrInvalidTagKeyUniqueness))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assertStatus(t, w, http.StatusOK)
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
|
|
@ -8,53 +8,53 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketAccelerateHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketAccelerateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketLoggingHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketLoggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketReplicationHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketReplicationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(r.Context(), w, "not implemented", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,11 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
frosterrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"go.opentelemetry.io/otel/trace"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,15 +26,14 @@ func (h *handler) reqLogger(ctx context.Context) *zap.Logger {
|
||||||
return h.log
|
return h.log
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) logAndSendError(w http.ResponseWriter, logText string, reqInfo *middleware.ReqInfo, err error, additional ...zap.Field) {
|
func (h *handler) logAndSendError(ctx context.Context, w http.ResponseWriter, logText string, reqInfo *middleware.ReqInfo, err error, additional ...zap.Field) {
|
||||||
err = handleDeleteMarker(w, err)
|
err = handleDeleteMarker(w, err)
|
||||||
if code, wrErr := middleware.WriteErrorResponse(w, reqInfo, transformToS3Error(err)); wrErr != nil {
|
if code, wrErr := middleware.WriteErrorResponse(w, reqInfo, apierr.TransformToS3Error(err)); wrErr != nil {
|
||||||
additional = append(additional, zap.NamedError("write_response_error", wrErr))
|
additional = append(additional, zap.NamedError("write_response_error", wrErr))
|
||||||
} else {
|
} else {
|
||||||
additional = append(additional, zap.Int("status", code))
|
additional = append(additional, zap.Int("status", code))
|
||||||
}
|
}
|
||||||
fields := []zap.Field{
|
fields := []zap.Field{
|
||||||
zap.String("request_id", reqInfo.RequestID),
|
|
||||||
zap.String("method", reqInfo.API),
|
zap.String("method", reqInfo.API),
|
||||||
zap.String("bucket", reqInfo.BucketName),
|
zap.String("bucket", reqInfo.BucketName),
|
||||||
zap.String("object", reqInfo.ObjectName),
|
zap.String("object", reqInfo.ObjectName),
|
||||||
|
@ -44,10 +41,7 @@ func (h *handler) logAndSendError(w http.ResponseWriter, logText string, reqInfo
|
||||||
zap.String("user", reqInfo.User),
|
zap.String("user", reqInfo.User),
|
||||||
zap.Error(err)}
|
zap.Error(err)}
|
||||||
fields = append(fields, additional...)
|
fields = append(fields, additional...)
|
||||||
if traceID, err := trace.TraceIDFromHex(reqInfo.TraceID); err == nil && traceID.IsValid() {
|
h.reqLogger(ctx).Error(logs.RequestFailed, fields...)
|
||||||
fields = append(fields, zap.String("trace_id", reqInfo.TraceID))
|
|
||||||
}
|
|
||||||
h.log.Error(logs.RequestFailed, fields...) // consider using h.reqLogger (it requires accept context.Context or http.Request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeleteMarker(w http.ResponseWriter, err error) error {
|
func handleDeleteMarker(w http.ResponseWriter, err error) error {
|
||||||
|
@ -57,25 +51,7 @@ func handleDeleteMarker(w http.ResponseWriter, err error) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(api.AmzDeleteMarker, "true")
|
w.Header().Set(api.AmzDeleteMarker, "true")
|
||||||
return fmt.Errorf("%w: %s", s3errors.GetAPIError(target.ErrorCode), err)
|
return fmt.Errorf("%w: %s", apierr.GetAPIError(target.ErrorCode), err)
|
||||||
}
|
|
||||||
|
|
||||||
func transformToS3Error(err error) error {
|
|
||||||
err = frosterrors.UnwrapErr(err) // this wouldn't work with errors.Join
|
|
||||||
if _, ok := err.(s3errors.Error); ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, layer.ErrAccessDenied) ||
|
|
||||||
errors.Is(err, layer.ErrNodeAccessDenied) {
|
|
||||||
return s3errors.GetAPIError(s3errors.ErrAccessDenied)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, layer.ErrGatewayTimeout) {
|
|
||||||
return s3errors.GetAPIError(s3errors.ErrGatewayTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s3errors.GetAPIError(s3errors.ErrInternalError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ResolveBucket(ctx context.Context, bucket string) (*data.BucketInfo, error) {
|
func (h *handler) ResolveBucket(ctx context.Context, bucket string) (*data.BucketInfo, error) {
|
||||||
|
@ -110,17 +86,13 @@ func (h *handler) getPutPayloadSize(r *http.Request) uint64 {
|
||||||
decodeContentSize := r.Header.Get(api.AmzDecodedContentLength)
|
decodeContentSize := r.Header.Get(api.AmzDecodedContentLength)
|
||||||
decodedSize, err := strconv.Atoi(decodeContentSize)
|
decodedSize, err := strconv.Atoi(decodeContentSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
decodedSize = 0
|
if r.ContentLength >= 0 {
|
||||||
|
return uint64(r.ContentLength)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var size uint64
|
return uint64(decodedSize)
|
||||||
if decodedSize > 0 {
|
|
||||||
size = uint64(decodedSize)
|
|
||||||
} else if r.ContentLength > 0 {
|
|
||||||
size = uint64(r.ContentLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
return size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRange(s string) (*layer.RangeParams, error) {
|
func parseRange(s string) (*layer.RangeParams, error) {
|
||||||
|
@ -131,26 +103,26 @@ func parseRange(s string) (*layer.RangeParams, error) {
|
||||||
prefix := "bytes="
|
prefix := "bytes="
|
||||||
|
|
||||||
if !strings.HasPrefix(s, prefix) {
|
if !strings.HasPrefix(s, prefix) {
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
s = strings.TrimPrefix(s, prefix)
|
s = strings.TrimPrefix(s, prefix)
|
||||||
|
|
||||||
valuesStr := strings.Split(s, "-")
|
valuesStr := strings.Split(s, "-")
|
||||||
if len(valuesStr) != 2 {
|
if len(valuesStr) != 2 {
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
values := make([]uint64, 0, len(valuesStr))
|
values := make([]uint64, 0, len(valuesStr))
|
||||||
for _, v := range valuesStr {
|
for _, v := range valuesStr {
|
||||||
num, err := strconv.ParseUint(v, 10, 64)
|
num, err := strconv.ParseUint(v, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidRange)
|
||||||
}
|
}
|
||||||
values = append(values, num)
|
values = append(values, num)
|
||||||
}
|
}
|
||||||
if values[0] > values[1] {
|
if values[0] > values[1] {
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &layer.RangeParams{
|
return &layer.RangeParams{
|
||||||
|
|
|
@ -1,64 +1 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTransformS3Errors(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
err error
|
|
||||||
expected s3errors.ErrorCode
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "simple std error to internal error",
|
|
||||||
err: errors.New("some error"),
|
|
||||||
expected: s3errors.ErrInternalError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "layer access denied error to s3 access denied error",
|
|
||||||
err: layer.ErrAccessDenied,
|
|
||||||
expected: s3errors.ErrAccessDenied,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wrapped layer access denied error to s3 access denied error",
|
|
||||||
err: fmt.Errorf("wrap: %w", layer.ErrAccessDenied),
|
|
||||||
expected: s3errors.ErrAccessDenied,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "layer node access denied error to s3 access denied error",
|
|
||||||
err: layer.ErrNodeAccessDenied,
|
|
||||||
expected: s3errors.ErrAccessDenied,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "layer gateway timeout error to s3 gateway timeout error",
|
|
||||||
err: layer.ErrGatewayTimeout,
|
|
||||||
expected: s3errors.ErrGatewayTimeout,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "s3 error to s3 error",
|
|
||||||
err: s3errors.GetAPIError(s3errors.ErrInvalidPart),
|
|
||||||
expected: s3errors.ErrInvalidPart,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wrapped s3 error to s3 error",
|
|
||||||
err: fmt.Errorf("wrap: %w", s3errors.GetAPIError(s3errors.ErrInvalidPart)),
|
|
||||||
expected: s3errors.ErrInvalidPart,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
err := transformToS3Error(tc.err)
|
|
||||||
s3err, ok := err.(s3errors.Error)
|
|
||||||
require.True(t, ok, "error must be s3 error")
|
|
||||||
require.Equalf(t, tc.expected, s3err.ErrCode,
|
|
||||||
"expected: '%s', got: '%s'",
|
|
||||||
s3errors.GetAPIError(tc.expected).Code, s3errors.GetAPIError(s3err.ErrCode).Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,28 +10,29 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
configuration := new(VersioningConfiguration)
|
configuration := new(VersioningConfiguration)
|
||||||
if err := h.cfg.NewXMLDecoder(r.Body).Decode(configuration); err != nil {
|
if err := h.cfg.NewXMLDecoder(r.Body).Decode(configuration); err != nil {
|
||||||
h.logAndSendError(w, "couldn't decode versioning configuration", reqInfo, errors.GetAPIError(errors.ErrIllegalVersioningConfigurationException))
|
h.logAndSendError(ctx, w, "couldn't decode versioning configuration", reqInfo, errors.GetAPIError(errors.ErrIllegalVersioningConfigurationException))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Status != data.VersioningEnabled && configuration.Status != data.VersioningSuspended {
|
if configuration.Status != data.VersioningEnabled && configuration.Status != data.VersioningSuspended {
|
||||||
h.logAndSendError(w, "invalid versioning configuration", reqInfo, errors.GetAPIError(errors.ErrMalformedXML))
|
h.logAndSendError(ctx, w, "invalid versioning configuration", reqInfo, errors.GetAPIError(errors.ErrMalformedXML))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,33 +46,34 @@ func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Settings.VersioningSuspended() && bktInfo.ObjectLockEnabled {
|
if p.Settings.VersioningSuspended() && bktInfo.ObjectLockEnabled {
|
||||||
h.logAndSendError(w, "couldn't suspend bucket versioning", reqInfo, errors.GetAPIError(errors.ErrObjectLockConfigurationVersioningCannotBeChanged))
|
h.logAndSendError(ctx, w, "couldn't suspend bucket versioning", reqInfo, errors.GetAPIError(errors.ErrObjectLockConfigurationVersioningCannotBeChanged))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.obj.PutBucketSettings(r.Context(), p); err != nil {
|
if err = h.obj.PutBucketSettings(ctx, p); err != nil {
|
||||||
h.logAndSendError(w, "couldn't put update versioning settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't put update versioning settings", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketVersioningHandler implements bucket versioning getter handler.
|
// GetBucketVersioningHandler implements bucket versioning getter handler.
|
||||||
func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
ctx := r.Context()
|
||||||
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get version settings", reqInfo, err)
|
h.logAndSendError(ctx, w, "couldn't get version settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = middleware.EncodeToResponse(w, formVersioningConfiguration(settings)); err != nil {
|
if err = middleware.EncodeToResponse(w, formVersioningConfiguration(settings)); err != nil {
|
||||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *Layer) GetObjectTaggingAndLock(ctx context.Context, objVersion *data.ObjectVersion, nodeVersion *data.NodeVersion) (map[string]string, data.LockInfo, error) {
|
func (n *Layer) GetObjectTaggingAndLock(ctx context.Context, objVersion *data.ObjectVersion, nodeVersion *data.NodeVersion) (map[string]string, data.LockInfo, error) {
|
||||||
|
@ -29,8 +30,8 @@ func (n *Layer) GetObjectTaggingAndLock(ctx context.Context, objVersion *data.Ob
|
||||||
|
|
||||||
tags, lockInfo, err = n.treeService.GetObjectTaggingAndLock(ctx, objVersion.BktInfo, nodeVersion)
|
tags, lockInfo, err = n.treeService.GetObjectTaggingAndLock(ctx, objVersion.BktInfo, nodeVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, data.LockInfo{}, fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchKey), err.Error())
|
return nil, data.LockInfo{}, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
return nil, data.LockInfo{}, err
|
return nil, data.LockInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
|
@ -20,7 +21,7 @@ const (
|
||||||
AttributeLockEnabled = "LockEnabled"
|
AttributeLockEnabled = "LockEnabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *Layer) containerInfo(ctx context.Context, prm PrmContainer) (*data.BucketInfo, error) {
|
func (n *Layer) containerInfo(ctx context.Context, prm frostfs.PrmContainer) (*data.BucketInfo, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
res *container.Container
|
res *container.Container
|
||||||
|
@ -37,7 +38,7 @@ func (n *Layer) containerInfo(ctx context.Context, prm PrmContainer) (*data.Buck
|
||||||
res, err = n.frostFS.Container(ctx, prm)
|
res, err = n.frostFS.Container(ctx, prm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if client.IsErrContainerNotFound(err) {
|
if client.IsErrContainerNotFound(err) {
|
||||||
return nil, fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchBucket), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchBucket), err.Error())
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("get frostfs container: %w", err)
|
return nil, fmt.Errorf("get frostfs container: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +78,7 @@ func (n *Layer) containerInfo(ctx context.Context, prm PrmContainer) (*data.Buck
|
||||||
func (n *Layer) containerList(ctx context.Context) ([]*data.BucketInfo, error) {
|
func (n *Layer) containerList(ctx context.Context) ([]*data.BucketInfo, error) {
|
||||||
stoken := n.SessionTokenForRead(ctx)
|
stoken := n.SessionTokenForRead(ctx)
|
||||||
|
|
||||||
prm := PrmUserContainers{
|
prm := frostfs.PrmUserContainers{
|
||||||
UserID: n.BearerOwner(ctx),
|
UserID: n.BearerOwner(ctx),
|
||||||
SessionToken: stoken,
|
SessionToken: stoken,
|
||||||
}
|
}
|
||||||
|
@ -90,7 +91,7 @@ func (n *Layer) containerList(ctx context.Context) ([]*data.BucketInfo, error) {
|
||||||
|
|
||||||
list := make([]*data.BucketInfo, 0, len(res))
|
list := make([]*data.BucketInfo, 0, len(res))
|
||||||
for i := range res {
|
for i := range res {
|
||||||
getPrm := PrmContainer{
|
getPrm := frostfs.PrmContainer{
|
||||||
ContainerID: res[i],
|
ContainerID: res[i],
|
||||||
SessionToken: stoken,
|
SessionToken: stoken,
|
||||||
}
|
}
|
||||||
|
@ -132,7 +133,7 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := n.frostFS.CreateContainer(ctx, PrmContainerCreate{
|
res, err := n.frostFS.CreateContainer(ctx, frostfs.PrmContainerCreate{
|
||||||
Creator: bktInfo.Owner,
|
Creator: bktInfo.Owner,
|
||||||
Policy: p.Policy,
|
Policy: p.Policy,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
|
|
|
@ -3,12 +3,14 @@ package layer
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
errorsStd "errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -31,14 +33,14 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cors.CORSRules == nil {
|
if cors.CORSRules == nil {
|
||||||
return errors.GetAPIError(errors.ErrMalformedXML)
|
return apierr.GetAPIError(apierr.ErrMalformedXML)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkCORS(cors); err != nil {
|
if err := checkCORS(cors); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
prm := PrmObjectCreate{
|
prm := frostfs.PrmObjectCreate{
|
||||||
Payload: &buf,
|
Payload: &buf,
|
||||||
Filepath: p.BktInfo.CORSObjectName(),
|
Filepath: p.BktInfo.CORSObjectName(),
|
||||||
CreationTime: TimeNow(ctx),
|
CreationTime: TimeNow(ctx),
|
||||||
|
@ -61,7 +63,7 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
objsToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, newAddress(corsBkt.CID, createdObj.ID))
|
objsToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, newAddress(corsBkt.CID, createdObj.ID))
|
||||||
objToDeleteNotFound := errorsStd.Is(err, ErrNoNodeToRemove)
|
objToDeleteNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
|
||||||
if err != nil && !objToDeleteNotFound {
|
if err != nil && !objToDeleteNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -79,7 +81,7 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
|
||||||
|
|
||||||
// deleteCORSObject removes object and logs in case of error.
|
// deleteCORSObject removes object and logs in case of error.
|
||||||
func (n *Layer) deleteCORSObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) {
|
func (n *Layer) deleteCORSObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) {
|
||||||
var prmAuth PrmAuth
|
var prmAuth frostfs.PrmAuth
|
||||||
corsBkt := bktInfo
|
corsBkt := bktInfo
|
||||||
if !addr.Container().Equals(bktInfo.CID) && !addr.Container().Equals(cid.ID{}) {
|
if !addr.Container().Equals(bktInfo.CID) && !addr.Container().Equals(cid.ID{}) {
|
||||||
corsBkt = &data.BucketInfo{CID: addr.Container()}
|
corsBkt = &data.BucketInfo{CID: addr.Container()}
|
||||||
|
@ -104,7 +106,7 @@ func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*d
|
||||||
|
|
||||||
func (n *Layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error {
|
func (n *Layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error {
|
||||||
objs, err := n.treeService.DeleteBucketCORS(ctx, bktInfo)
|
objs, err := n.treeService.DeleteBucketCORS(ctx, bktInfo)
|
||||||
objNotFound := errorsStd.Is(err, ErrNoNodeToRemove)
|
objNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
|
||||||
if err != nil && !objNotFound {
|
if err != nil && !objNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -124,12 +126,12 @@ func checkCORS(cors *data.CORSConfiguration) error {
|
||||||
for _, r := range cors.CORSRules {
|
for _, r := range cors.CORSRules {
|
||||||
for _, m := range r.AllowedMethods {
|
for _, m := range r.AllowedMethods {
|
||||||
if _, ok := supportedMethods[m]; !ok {
|
if _, ok := supportedMethods[m]; !ok {
|
||||||
return errors.GetAPIErrorWithError(errors.ErrCORSUnsupportedMethod, fmt.Errorf("unsupported method is %s", m))
|
return apierr.GetAPIErrorWithError(apierr.ErrCORSUnsupportedMethod, fmt.Errorf("unsupported method is %s", m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, h := range r.ExposeHeaders {
|
for _, h := range r.ExposeHeaders {
|
||||||
if h == wildcard {
|
if h == wildcard {
|
||||||
return errors.GetAPIError(errors.ErrCORSWildcardExposeHeaders)
|
return apierr.GetAPIError(apierr.ErrCORSWildcardExposeHeaders)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package layer
|
package frostfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||||
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||||
|
@ -61,7 +62,7 @@ func (k *FeatureSettingsMock) FormContainerZone(ns string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestFrostFS struct {
|
type TestFrostFS struct {
|
||||||
FrostFS
|
frostfs.FrostFS
|
||||||
|
|
||||||
objects map[string]*object.Object
|
objects map[string]*object.Object
|
||||||
objectErrors map[string]error
|
objectErrors map[string]error
|
||||||
|
@ -139,7 +140,7 @@ func (t *TestFrostFS) SetContainer(cnrID cid.ID, cnr *container.Container) {
|
||||||
t.containers[cnrID.EncodeToString()] = cnr
|
t.containers[cnrID.EncodeToString()] = cnr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) CreateContainer(_ context.Context, prm PrmContainerCreate) (*ContainerCreateResult, error) {
|
func (t *TestFrostFS) CreateContainer(_ context.Context, prm frostfs.PrmContainerCreate) (*frostfs.ContainerCreateResult, error) {
|
||||||
var cnr container.Container
|
var cnr container.Container
|
||||||
cnr.Init()
|
cnr.Init()
|
||||||
cnr.SetOwner(prm.Creator)
|
cnr.SetOwner(prm.Creator)
|
||||||
|
@ -174,7 +175,7 @@ func (t *TestFrostFS) CreateContainer(_ context.Context, prm PrmContainerCreate)
|
||||||
id.SetSHA256(sha256.Sum256(b))
|
id.SetSHA256(sha256.Sum256(b))
|
||||||
t.containers[id.EncodeToString()] = &cnr
|
t.containers[id.EncodeToString()] = &cnr
|
||||||
|
|
||||||
return &ContainerCreateResult{ContainerID: id}, nil
|
return &frostfs.ContainerCreateResult{ContainerID: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) DeleteContainer(_ context.Context, cnrID cid.ID, _ *session.Container) error {
|
func (t *TestFrostFS) DeleteContainer(_ context.Context, cnrID cid.ID, _ *session.Container) error {
|
||||||
|
@ -183,7 +184,7 @@ func (t *TestFrostFS) DeleteContainer(_ context.Context, cnrID cid.ID, _ *sessio
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) Container(_ context.Context, prm PrmContainer) (*container.Container, error) {
|
func (t *TestFrostFS) Container(_ context.Context, prm frostfs.PrmContainer) (*container.Container, error) {
|
||||||
for k, v := range t.containers {
|
for k, v := range t.containers {
|
||||||
if k == prm.ContainerID.EncodeToString() {
|
if k == prm.ContainerID.EncodeToString() {
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -193,7 +194,7 @@ func (t *TestFrostFS) Container(_ context.Context, prm PrmContainer) (*container
|
||||||
return nil, fmt.Errorf("container not found %s", prm.ContainerID)
|
return nil, fmt.Errorf("container not found %s", prm.ContainerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) UserContainers(context.Context, PrmUserContainers) ([]cid.ID, error) {
|
func (t *TestFrostFS) UserContainers(context.Context, frostfs.PrmUserContainers) ([]cid.ID, error) {
|
||||||
var res []cid.ID
|
var res []cid.ID
|
||||||
for k := range t.containers {
|
for k := range t.containers {
|
||||||
var idCnr cid.ID
|
var idCnr cid.ID
|
||||||
|
@ -220,7 +221,7 @@ func (t *TestFrostFS) retrieveObject(ctx context.Context, cnrID cid.ID, objID oi
|
||||||
if obj, ok := t.objects[sAddr]; ok {
|
if obj, ok := t.objects[sAddr]; ok {
|
||||||
owner := getBearerOwner(ctx)
|
owner := getBearerOwner(ctx)
|
||||||
if !t.checkAccess(cnrID, owner) {
|
if !t.checkAccess(cnrID, owner) {
|
||||||
return nil, ErrAccessDenied
|
return nil, frostfs.ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj, nil
|
return obj, nil
|
||||||
|
@ -229,23 +230,23 @@ func (t *TestFrostFS) retrieveObject(ctx context.Context, cnrID cid.ID, objID oi
|
||||||
return nil, fmt.Errorf("%w: %s", &apistatus.ObjectNotFound{}, addr)
|
return nil, fmt.Errorf("%w: %s", &apistatus.ObjectNotFound{}, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) HeadObject(ctx context.Context, prm PrmObjectHead) (*object.Object, error) {
|
func (t *TestFrostFS) HeadObject(ctx context.Context, prm frostfs.PrmObjectHead) (*object.Object, error) {
|
||||||
return t.retrieveObject(ctx, prm.Container, prm.Object)
|
return t.retrieveObject(ctx, prm.Container, prm.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) GetObject(ctx context.Context, prm PrmObjectGet) (*Object, error) {
|
func (t *TestFrostFS) GetObject(ctx context.Context, prm frostfs.PrmObjectGet) (*frostfs.Object, error) {
|
||||||
obj, err := t.retrieveObject(ctx, prm.Container, prm.Object)
|
obj, err := t.retrieveObject(ctx, prm.Container, prm.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Object{
|
return &frostfs.Object{
|
||||||
Header: *obj,
|
Header: *obj,
|
||||||
Payload: io.NopCloser(bytes.NewReader(obj.Payload())),
|
Payload: io.NopCloser(bytes.NewReader(obj.Payload())),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) RangeObject(ctx context.Context, prm PrmObjectRange) (io.ReadCloser, error) {
|
func (t *TestFrostFS) RangeObject(ctx context.Context, prm frostfs.PrmObjectRange) (io.ReadCloser, error) {
|
||||||
obj, err := t.retrieveObject(ctx, prm.Container, prm.Object)
|
obj, err := t.retrieveObject(ctx, prm.Container, prm.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -257,7 +258,7 @@ func (t *TestFrostFS) RangeObject(ctx context.Context, prm PrmObjectRange) (io.R
|
||||||
return io.NopCloser(bytes.NewReader(payload)), nil
|
return io.NopCloser(bytes.NewReader(payload)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) CreateObject(_ context.Context, prm PrmObjectCreate) (*CreateObjectResult, error) {
|
func (t *TestFrostFS) CreateObject(_ context.Context, prm frostfs.PrmObjectCreate) (*frostfs.CreateObjectResult, error) {
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -327,13 +328,13 @@ func (t *TestFrostFS) CreateObject(_ context.Context, prm PrmObjectCreate) (*Cre
|
||||||
|
|
||||||
addr := newAddress(cnrID, objID)
|
addr := newAddress(cnrID, objID)
|
||||||
t.objects[addr.EncodeToString()] = obj
|
t.objects[addr.EncodeToString()] = obj
|
||||||
return &CreateObjectResult{
|
return &frostfs.CreateObjectResult{
|
||||||
ObjectID: objID,
|
ObjectID: objID,
|
||||||
CreationEpoch: t.currentEpoch - 1,
|
CreationEpoch: t.currentEpoch - 1,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) DeleteObject(ctx context.Context, prm PrmObjectDelete) error {
|
func (t *TestFrostFS) DeleteObject(ctx context.Context, prm frostfs.PrmObjectDelete) error {
|
||||||
var addr oid.Address
|
var addr oid.Address
|
||||||
addr.SetContainer(prm.Container)
|
addr.SetContainer(prm.Container)
|
||||||
addr.SetObject(prm.Object)
|
addr.SetObject(prm.Object)
|
||||||
|
@ -345,7 +346,7 @@ func (t *TestFrostFS) DeleteObject(ctx context.Context, prm PrmObjectDelete) err
|
||||||
if _, ok := t.objects[addr.EncodeToString()]; ok {
|
if _, ok := t.objects[addr.EncodeToString()]; ok {
|
||||||
owner := getBearerOwner(ctx)
|
owner := getBearerOwner(ctx)
|
||||||
if !t.checkAccess(prm.Container, owner) {
|
if !t.checkAccess(prm.Container, owner) {
|
||||||
return ErrAccessDenied
|
return frostfs.ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(t.objects, addr.EncodeToString())
|
delete(t.objects, addr.EncodeToString())
|
||||||
|
@ -372,7 +373,7 @@ func (t *TestFrostFS) AllObjects(cnrID cid.ID) []oid.ID {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]oid.ID, error) {
|
func (t *TestFrostFS) SearchObjects(_ context.Context, prm frostfs.PrmObjectSearch) ([]oid.ID, error) {
|
||||||
filters := object.NewSearchFilters()
|
filters := object.NewSearchFilters()
|
||||||
filters.AddRootFilter()
|
filters.AddRootFilter()
|
||||||
|
|
||||||
|
@ -412,11 +413,13 @@ func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]o
|
||||||
func (t *TestFrostFS) NetworkInfo(context.Context) (netmap.NetworkInfo, error) {
|
func (t *TestFrostFS) NetworkInfo(context.Context) (netmap.NetworkInfo, error) {
|
||||||
ni := netmap.NetworkInfo{}
|
ni := netmap.NetworkInfo{}
|
||||||
ni.SetCurrentEpoch(t.currentEpoch)
|
ni.SetCurrentEpoch(t.currentEpoch)
|
||||||
|
ni.SetEpochDuration(60)
|
||||||
|
ni.SetMsPerBlock(1000)
|
||||||
|
|
||||||
return ni, nil
|
return ni, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) PatchObject(ctx context.Context, prm PrmObjectPatch) (oid.ID, error) {
|
func (t *TestFrostFS) PatchObject(ctx context.Context, prm frostfs.PrmObjectPatch) (oid.ID, error) {
|
||||||
obj, err := t.retrieveObject(ctx, prm.Container, prm.Object)
|
obj, err := t.retrieveObject(ctx, prm.Container, prm.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oid.ID{}, err
|
return oid.ID{}, err
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
stderrors "errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -17,9 +17,10 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
|
@ -46,13 +47,13 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
Layer struct {
|
Layer struct {
|
||||||
frostFS FrostFS
|
frostFS frostfs.FrostFS
|
||||||
gateOwner user.ID
|
gateOwner user.ID
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
anonKey AnonymousKey
|
anonKey AnonymousKey
|
||||||
resolver BucketResolver
|
resolver BucketResolver
|
||||||
cache *Cache
|
cache *Cache
|
||||||
treeService TreeService
|
treeService tree.Service
|
||||||
features FeatureSettings
|
features FeatureSettings
|
||||||
gateKey *keys.PrivateKey
|
gateKey *keys.PrivateKey
|
||||||
corsCnrInfo *data.BucketInfo
|
corsCnrInfo *data.BucketInfo
|
||||||
|
@ -65,7 +66,7 @@ type (
|
||||||
Cache *Cache
|
Cache *Cache
|
||||||
AnonKey AnonymousKey
|
AnonKey AnonymousKey
|
||||||
Resolver BucketResolver
|
Resolver BucketResolver
|
||||||
TreeService TreeService
|
TreeService tree.Service
|
||||||
Features FeatureSettings
|
Features FeatureSettings
|
||||||
GateKey *keys.PrivateKey
|
GateKey *keys.PrivateKey
|
||||||
CORSCnrInfo *data.BucketInfo
|
CORSCnrInfo *data.BucketInfo
|
||||||
|
@ -235,7 +236,7 @@ func (p HeadObjectParams) Versioned() bool {
|
||||||
|
|
||||||
// NewLayer creates an instance of a Layer. It checks credentials
|
// NewLayer creates an instance of a Layer. It checks credentials
|
||||||
// and establishes gRPC connection with the node.
|
// and establishes gRPC connection with the node.
|
||||||
func NewLayer(log *zap.Logger, frostFS FrostFS, config *Config) *Layer {
|
func NewLayer(log *zap.Logger, frostFS frostfs.FrostFS, config *Config) *Layer {
|
||||||
return &Layer{
|
return &Layer{
|
||||||
frostFS: frostFS,
|
frostFS: frostFS,
|
||||||
log: log,
|
log: log,
|
||||||
|
@ -299,7 +300,7 @@ func (n *Layer) reqLogger(ctx context.Context) *zap.Logger {
|
||||||
return n.log
|
return n.log
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) prepareAuthParameters(ctx context.Context, prm *PrmAuth, bktOwner user.ID) {
|
func (n *Layer) prepareAuthParameters(ctx context.Context, prm *frostfs.PrmAuth, bktOwner user.ID) {
|
||||||
if prm.BearerToken != nil || prm.PrivateKey != nil {
|
if prm.BearerToken != nil || prm.PrivateKey != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -331,12 +332,12 @@ func (n *Layer) GetBucketInfo(ctx context.Context, name string) (*data.BucketInf
|
||||||
containerID, err := n.ResolveBucket(ctx, zone, name)
|
containerID, err := n.ResolveBucket(ctx, zone, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "not found") {
|
if strings.Contains(err.Error(), "not found") {
|
||||||
return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchBucket), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchBucket), err.Error())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
prm := PrmContainer{
|
prm := frostfs.PrmContainer{
|
||||||
ContainerID: containerID,
|
ContainerID: containerID,
|
||||||
SessionToken: n.SessionTokenForRead(ctx),
|
SessionToken: n.SessionTokenForRead(ctx),
|
||||||
}
|
}
|
||||||
|
@ -397,9 +398,9 @@ func (n *Layer) GetObject(ctx context.Context, p *GetObjectParams) (*ObjectPaylo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if client.IsErrObjectNotFound(err) {
|
if client.IsErrObjectNotFound(err) {
|
||||||
if p.Versioned {
|
if p.Versioned {
|
||||||
err = fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchVersion), err.Error())
|
err = fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchVersion), err.Error())
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchKey), err.Error())
|
err = fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,22 +656,22 @@ func (n *Layer) handleNotFoundError(bkt *data.BucketInfo, obj *VersionedObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNotFoundError(err error) bool {
|
func isNotFoundError(err error) bool {
|
||||||
return errors.IsS3Error(err, errors.ErrNoSuchKey) ||
|
return apierr.IsS3Error(err, apierr.ErrNoSuchKey) ||
|
||||||
errors.IsS3Error(err, errors.ErrNoSuchVersion)
|
apierr.IsS3Error(err, apierr.ErrNoSuchVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) getNodeVersionsToDelete(ctx context.Context, bkt *data.BucketInfo, obj *VersionedObject) ([]*data.NodeVersion, error) {
|
func (n *Layer) getNodeVersionsToDelete(ctx context.Context, bkt *data.BucketInfo, obj *VersionedObject) ([]*data.NodeVersion, error) {
|
||||||
var versionsToDelete []*data.NodeVersion
|
var versionsToDelete []*data.NodeVersion
|
||||||
versions, err := n.treeService.GetVersions(ctx, bkt, obj.Name)
|
versions, err := n.treeService.GetVersions(ctx, bkt, obj.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if stderrors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchKey), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(versions) == 0 {
|
if len(versions) == 0 {
|
||||||
return nil, fmt.Errorf("%w: there isn't tree node with requested version id", s3errors.GetAPIError(s3errors.ErrNoSuchVersion))
|
return nil, fmt.Errorf("%w: there isn't tree node with requested version id", apierr.GetAPIError(apierr.ErrNoSuchVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(versions, func(i, j int) bool {
|
sort.Slice(versions, func(i, j int) bool {
|
||||||
|
@ -712,7 +713,7 @@ func (n *Layer) getNodeVersionsToDelete(ctx context.Context, bkt *data.BucketInf
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(versionsToDelete) == 0 {
|
if len(versionsToDelete) == 0 {
|
||||||
return nil, fmt.Errorf("%w: there isn't tree node with requested version id", s3errors.GetAPIError(s3errors.ErrNoSuchVersion))
|
return nil, fmt.Errorf("%w: there isn't tree node with requested version id", apierr.GetAPIError(apierr.ErrNoSuchVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
n.reqLogger(ctx).Debug(logs.GetTreeNodeToDelete, zap.Stringer("cid", bkt.CID), zap.Strings("oids", oids))
|
n.reqLogger(ctx).Debug(logs.GetTreeNodeToDelete, zap.Stringer("cid", bkt.CID), zap.Strings("oids", oids))
|
||||||
|
@ -785,17 +786,17 @@ func (n *Layer) DeleteObjects(ctx context.Context, p *DeleteObjectParams) []*Ver
|
||||||
func (n *Layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*data.BucketInfo, error) {
|
func (n *Layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*data.BucketInfo, error) {
|
||||||
bktInfo, err := n.GetBucketInfo(ctx, p.Name)
|
bktInfo, err := n.GetBucketInfo(ctx, p.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsS3Error(err, errors.ErrNoSuchBucket) {
|
if apierr.IsS3Error(err, apierr.ErrNoSuchBucket) {
|
||||||
return n.createContainer(ctx, p)
|
return n.createContainer(ctx, p)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.SessionContainerCreation != nil && session.IssuedBy(*p.SessionContainerCreation, bktInfo.Owner) {
|
if p.SessionContainerCreation != nil && session.IssuedBy(*p.SessionContainerCreation, bktInfo.Owner) {
|
||||||
return nil, errors.GetAPIError(errors.ErrBucketAlreadyOwnedByYou)
|
return nil, apierr.GetAPIError(apierr.ErrBucketAlreadyOwnedByYou)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.GetAPIError(errors.ErrBucketAlreadyExists)
|
return nil, apierr.GetAPIError(apierr.ErrBucketAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) ResolveBucket(ctx context.Context, zone, name string) (cid.ID, error) {
|
func (n *Layer) ResolveBucket(ctx context.Context, zone, name string) (cid.ID, error) {
|
||||||
|
@ -822,7 +823,7 @@ func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(res) != 0 {
|
if len(res) != 0 {
|
||||||
return errors.GetAPIError(errors.ErrBucketNotEmpty)
|
return apierr.GetAPIError(apierr.ErrBucketNotEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,30 +3,33 @@ package layer
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PutBucketLifecycleParams struct {
|
type PutBucketLifecycleParams struct {
|
||||||
BktInfo *data.BucketInfo
|
BktInfo *data.BucketInfo
|
||||||
LifecycleCfg *data.LifecycleConfiguration
|
LifecycleCfg *data.LifecycleConfiguration
|
||||||
LifecycleReader io.Reader
|
CopiesNumbers []uint32
|
||||||
CopiesNumbers []uint32
|
|
||||||
MD5Hash string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucketLifecycleParams) error {
|
func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucketLifecycleParams) error {
|
||||||
prm := PrmObjectCreate{
|
cfgBytes, err := xml.Marshal(p.LifecycleCfg)
|
||||||
Payload: p.LifecycleReader,
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal lifecycle configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prm := frostfs.PrmObjectCreate{
|
||||||
|
Payload: bytes.NewReader(cfgBytes),
|
||||||
Filepath: p.BktInfo.LifecycleConfigurationObjectName(),
|
Filepath: p.BktInfo.LifecycleConfigurationObjectName(),
|
||||||
CreationTime: TimeNow(ctx),
|
CreationTime: TimeNow(ctx),
|
||||||
}
|
}
|
||||||
|
@ -47,19 +50,8 @@ func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucke
|
||||||
return fmt.Errorf("put lifecycle object: %w", err)
|
return fmt.Errorf("put lifecycle object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hashBytes, err := base64.StdEncoding.DecodeString(p.MD5Hash)
|
|
||||||
if err != nil {
|
|
||||||
return apiErr.GetAPIError(apiErr.ErrInvalidDigest)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(hashBytes, createdObj.MD5Sum) {
|
|
||||||
n.deleteLifecycleObject(ctx, p.BktInfo, newAddress(lifecycleBkt.CID, createdObj.ID))
|
|
||||||
|
|
||||||
return apiErr.GetAPIError(apiErr.ErrInvalidDigest)
|
|
||||||
}
|
|
||||||
|
|
||||||
objsToDelete, err := n.treeService.PutBucketLifecycleConfiguration(ctx, p.BktInfo, newAddress(lifecycleBkt.CID, createdObj.ID))
|
objsToDelete, err := n.treeService.PutBucketLifecycleConfiguration(ctx, p.BktInfo, newAddress(lifecycleBkt.CID, createdObj.ID))
|
||||||
objsToDeleteNotFound := errors.Is(err, ErrNoNodeToRemove)
|
objsToDeleteNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
|
||||||
if err != nil && !objsToDeleteNotFound {
|
if err != nil && !objsToDeleteNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -77,7 +69,7 @@ func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucke
|
||||||
|
|
||||||
// deleteLifecycleObject removes object and logs in case of error.
|
// deleteLifecycleObject removes object and logs in case of error.
|
||||||
func (n *Layer) deleteLifecycleObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) {
|
func (n *Layer) deleteLifecycleObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) {
|
||||||
var prmAuth PrmAuth
|
var prmAuth frostfs.PrmAuth
|
||||||
lifecycleBkt := bktInfo
|
lifecycleBkt := bktInfo
|
||||||
if !addr.Container().Equals(bktInfo.CID) {
|
if !addr.Container().Equals(bktInfo.CID) {
|
||||||
lifecycleBkt = &data.BucketInfo{CID: addr.Container()}
|
lifecycleBkt = &data.BucketInfo{CID: addr.Container()}
|
||||||
|
@ -98,16 +90,16 @@ func (n *Layer) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *da
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := n.treeService.GetBucketLifecycleConfiguration(ctx, bktInfo)
|
addr, err := n.treeService.GetBucketLifecycleConfiguration(ctx, bktInfo)
|
||||||
objNotFound := errors.Is(err, ErrNodeNotFound)
|
objNotFound := errors.Is(err, tree.ErrNodeNotFound)
|
||||||
if err != nil && !objNotFound {
|
if err != nil && !objNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if objNotFound {
|
if objNotFound {
|
||||||
return nil, fmt.Errorf("%w: %s", apiErr.GetAPIError(apiErr.ErrNoSuchLifecycleConfiguration), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchLifecycleConfiguration), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var prmAuth PrmAuth
|
var prmAuth frostfs.PrmAuth
|
||||||
lifecycleBkt := bktInfo
|
lifecycleBkt := bktInfo
|
||||||
if !addr.Container().Equals(bktInfo.CID) {
|
if !addr.Container().Equals(bktInfo.CID) {
|
||||||
lifecycleBkt = &data.BucketInfo{CID: addr.Container()}
|
lifecycleBkt = &data.BucketInfo{CID: addr.Container()}
|
||||||
|
@ -127,12 +119,18 @@ func (n *Layer) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *da
|
||||||
|
|
||||||
n.cache.PutLifecycleConfiguration(owner, bktInfo, lifecycleCfg)
|
n.cache.PutLifecycleConfiguration(owner, bktInfo, lifecycleCfg)
|
||||||
|
|
||||||
|
for i := range lifecycleCfg.Rules {
|
||||||
|
if lifecycleCfg.Rules[i].Expiration != nil {
|
||||||
|
lifecycleCfg.Rules[i].Expiration.Epoch = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return lifecycleCfg, nil
|
return lifecycleCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) error {
|
func (n *Layer) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) error {
|
||||||
objs, err := n.treeService.DeleteBucketLifecycleConfiguration(ctx, bktInfo)
|
objs, err := n.treeService.DeleteBucketLifecycleConfiguration(ctx, bktInfo)
|
||||||
objsNotFound := errors.Is(err, ErrNoNodeToRemove)
|
objsNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
|
||||||
if err != nil && !objsNotFound {
|
if err != nil && !objsNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
package layer
|
package layer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
frostfsErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
frosterr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,16 +34,14 @@ func TestBucketLifecycle(t *testing.T) {
|
||||||
hash.Write(lifecycleBytes)
|
hash.Write(lifecycleBytes)
|
||||||
|
|
||||||
_, err = tc.layer.GetBucketLifecycleConfiguration(tc.ctx, tc.bktInfo)
|
_, err = tc.layer.GetBucketLifecycleConfiguration(tc.ctx, tc.bktInfo)
|
||||||
require.Equal(t, apiErr.GetAPIError(apiErr.ErrNoSuchLifecycleConfiguration), frostfsErrors.UnwrapErr(err))
|
require.Equal(t, apierr.GetAPIError(apierr.ErrNoSuchLifecycleConfiguration), frosterr.UnwrapErr(err))
|
||||||
|
|
||||||
err = tc.layer.DeleteBucketLifecycleConfiguration(tc.ctx, tc.bktInfo)
|
err = tc.layer.DeleteBucketLifecycleConfiguration(tc.ctx, tc.bktInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = tc.layer.PutBucketLifecycleConfiguration(tc.ctx, &PutBucketLifecycleParams{
|
err = tc.layer.PutBucketLifecycleConfiguration(tc.ctx, &PutBucketLifecycleParams{
|
||||||
BktInfo: tc.bktInfo,
|
BktInfo: tc.bktInfo,
|
||||||
LifecycleCfg: lifecycle,
|
LifecycleCfg: lifecycle,
|
||||||
LifecycleReader: bytes.NewReader(lifecycleBytes),
|
|
||||||
MD5Hash: base64.StdEncoding.EncodeToString(hash.Sum(nil)),
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -57,7 +53,7 @@ func TestBucketLifecycle(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = tc.layer.GetBucketLifecycleConfiguration(tc.ctx, tc.bktInfo)
|
_, err = tc.layer.GetBucketLifecycleConfiguration(tc.ctx, tc.bktInfo)
|
||||||
require.Equal(t, apiErr.GetAPIError(apiErr.ErrNoSuchLifecycleConfiguration), frostfsErrors.UnwrapErr(err))
|
require.Equal(t, apierr.GetAPIError(apierr.ErrNoSuchLifecycleConfiguration), frosterr.UnwrapErr(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ptr[T any](t T) *T {
|
func ptr[T any](t T) *T {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"github.com/panjf2000/ants/v2"
|
"github.com/panjf2000/ants/v2"
|
||||||
|
@ -691,10 +691,10 @@ func filterVersionsByMarker(objects []*data.ExtendedNodeVersion, p *ListObjectVe
|
||||||
return objects[j+1:], nil
|
return objects[j+1:], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidVersion)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidVersion)
|
||||||
} else if obj.NodeVersion.FilePath > p.KeyMarker {
|
} else if obj.NodeVersion.FilePath > p.KeyMarker {
|
||||||
if p.VersionIDMarker != "" {
|
if p.VersionIDMarker != "" {
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidVersion)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidVersion)
|
||||||
}
|
}
|
||||||
return objects[i:], nil
|
return objects[i:], nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,10 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
@ -187,14 +189,14 @@ func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartPar
|
||||||
func (n *Layer) UploadPart(ctx context.Context, p *UploadPartParams) (string, error) {
|
func (n *Layer) UploadPart(ctx context.Context, p *UploadPartParams) (string, error) {
|
||||||
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Info.Bkt, p.Info.Key, p.Info.UploadID)
|
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Info.Bkt, p.Info.Key, p.Info.UploadID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return "", fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchUpload), err.Error())
|
return "", fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchUpload), err.Error())
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Size > UploadMaxSize {
|
if p.Size > UploadMaxSize {
|
||||||
return "", fmt.Errorf("%w: %d/%d", s3errors.GetAPIError(s3errors.ErrEntityTooLarge), p.Size, UploadMaxSize)
|
return "", fmt.Errorf("%w: %d/%d", apierr.GetAPIError(apierr.ErrEntityTooLarge), p.Size, UploadMaxSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo, err := n.uploadPart(ctx, multipartInfo, p)
|
objInfo, err := n.uploadPart(ctx, multipartInfo, p)
|
||||||
|
@ -209,11 +211,11 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
|
||||||
encInfo := FormEncryptionInfo(multipartInfo.Meta)
|
encInfo := FormEncryptionInfo(multipartInfo.Meta)
|
||||||
if err := p.Info.Encryption.MatchObjectEncryption(encInfo); err != nil {
|
if err := p.Info.Encryption.MatchObjectEncryption(encInfo); err != nil {
|
||||||
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err))
|
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err))
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidEncryptionParameters)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidEncryptionParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo := p.Info.Bkt
|
bktInfo := p.Info.Bkt
|
||||||
prm := PrmObjectCreate{
|
prm := frostfs.PrmObjectCreate{
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
Attributes: make([][2]string, 2),
|
Attributes: make([][2]string, 2),
|
||||||
Payload: p.Reader,
|
Payload: p.Reader,
|
||||||
|
@ -242,10 +244,10 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
|
||||||
if len(p.ContentMD5) > 0 {
|
if len(p.ContentMD5) > 0 {
|
||||||
hashBytes, err := base64.StdEncoding.DecodeString(p.ContentMD5)
|
hashBytes, err := base64.StdEncoding.DecodeString(p.ContentMD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidDigest)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidDigest)
|
||||||
}
|
}
|
||||||
if hex.EncodeToString(hashBytes) != hex.EncodeToString(createdObj.MD5Sum) {
|
if hex.EncodeToString(hashBytes) != hex.EncodeToString(createdObj.MD5Sum) {
|
||||||
prm := PrmObjectDelete{
|
prm := frostfs.PrmObjectDelete{
|
||||||
Object: createdObj.ID,
|
Object: createdObj.ID,
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
}
|
}
|
||||||
|
@ -254,7 +256,7 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
||||||
}
|
}
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidDigest)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidDigest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.Info.Encryption.Enabled() {
|
if p.Info.Encryption.Enabled() {
|
||||||
|
@ -264,14 +266,14 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
|
||||||
if !p.Info.Encryption.Enabled() && len(p.ContentSHA256Hash) > 0 && !auth.IsStandardContentSHA256(p.ContentSHA256Hash) {
|
if !p.Info.Encryption.Enabled() && len(p.ContentSHA256Hash) > 0 && !auth.IsStandardContentSHA256(p.ContentSHA256Hash) {
|
||||||
contentHashBytes, err := hex.DecodeString(p.ContentSHA256Hash)
|
contentHashBytes, err := hex.DecodeString(p.ContentSHA256Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrContentSHA256Mismatch)
|
return nil, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(contentHashBytes, createdObj.HashSum) {
|
if !bytes.Equal(contentHashBytes, createdObj.HashSum) {
|
||||||
err = n.objectDelete(ctx, bktInfo, createdObj.ID)
|
err = n.objectDelete(ctx, bktInfo, createdObj.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
||||||
}
|
}
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrContentSHA256Mismatch)
|
return nil, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +293,7 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
|
||||||
}
|
}
|
||||||
|
|
||||||
oldPartIDs, err := n.treeService.AddPart(ctx, bktInfo, multipartInfo.ID, partInfo)
|
oldPartIDs, err := n.treeService.AddPart(ctx, bktInfo, multipartInfo.ID, partInfo)
|
||||||
oldPartIDNotFound := errors.Is(err, ErrNoNodeToRemove)
|
oldPartIDNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
|
||||||
if err != nil && !oldPartIDNotFound {
|
if err != nil && !oldPartIDNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -323,8 +325,8 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
|
||||||
func (n *Layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.ObjectInfo, error) {
|
func (n *Layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.ObjectInfo, error) {
|
||||||
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Info.Bkt, p.Info.Key, p.Info.UploadID)
|
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Info.Bkt, p.Info.Key, p.Info.UploadID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchUpload), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchUpload), err.Error())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -340,11 +342,11 @@ func (n *Layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.
|
||||||
if p.Range != nil {
|
if p.Range != nil {
|
||||||
size = p.Range.End - p.Range.Start + 1
|
size = p.Range.End - p.Range.Start + 1
|
||||||
if p.Range.End > srcObjectSize {
|
if p.Range.End > srcObjectSize {
|
||||||
return nil, fmt.Errorf("%w: %d-%d/%d", s3errors.GetAPIError(s3errors.ErrInvalidCopyPartRangeSource), p.Range.Start, p.Range.End, srcObjectSize)
|
return nil, fmt.Errorf("%w: %d-%d/%d", apierr.GetAPIError(apierr.ErrInvalidCopyPartRangeSource), p.Range.Start, p.Range.End, srcObjectSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if size > UploadMaxSize {
|
if size > UploadMaxSize {
|
||||||
return nil, fmt.Errorf("%w: %d/%d", s3errors.GetAPIError(s3errors.ErrEntityTooLarge), size, UploadMaxSize)
|
return nil, fmt.Errorf("%w: %d/%d", apierr.GetAPIError(apierr.ErrEntityTooLarge), size, UploadMaxSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
objPayload, err := n.GetObject(ctx, &GetObjectParams{
|
objPayload, err := n.GetObject(ctx, &GetObjectParams{
|
||||||
|
@ -371,7 +373,7 @@ func (n *Layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.
|
||||||
func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*UploadData, *data.ExtendedObjectInfo, error) {
|
func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*UploadData, *data.ExtendedObjectInfo, error) {
|
||||||
for i := 1; i < len(p.Parts); i++ {
|
for i := 1; i < len(p.Parts); i++ {
|
||||||
if p.Parts[i].PartNumber <= p.Parts[i-1].PartNumber {
|
if p.Parts[i].PartNumber <= p.Parts[i-1].PartNumber {
|
||||||
return nil, nil, s3errors.GetAPIError(s3errors.ErrInvalidPartOrder)
|
return nil, nil, apierr.GetAPIError(apierr.ErrInvalidPartOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +384,7 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
||||||
encInfo := FormEncryptionInfo(multipartInfo.Meta)
|
encInfo := FormEncryptionInfo(multipartInfo.Meta)
|
||||||
|
|
||||||
if len(partsInfo) < len(p.Parts) {
|
if len(partsInfo) < len(p.Parts) {
|
||||||
return nil, nil, fmt.Errorf("%w: found %d parts, need %d", s3errors.GetAPIError(s3errors.ErrInvalidPart), len(partsInfo), len(p.Parts))
|
return nil, nil, fmt.Errorf("%w: found %d parts, need %d", apierr.GetAPIError(apierr.ErrInvalidPart), len(partsInfo), len(p.Parts))
|
||||||
}
|
}
|
||||||
|
|
||||||
var multipartObjetSize uint64
|
var multipartObjetSize uint64
|
||||||
|
@ -394,12 +396,12 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
||||||
for i, part := range p.Parts {
|
for i, part := range p.Parts {
|
||||||
partInfo := partsInfo.Extract(part.PartNumber, data.UnQuote(part.ETag), n.features.MD5Enabled())
|
partInfo := partsInfo.Extract(part.PartNumber, data.UnQuote(part.ETag), n.features.MD5Enabled())
|
||||||
if partInfo == nil {
|
if partInfo == nil {
|
||||||
return nil, nil, fmt.Errorf("%w: unknown part %d or etag mismatched", s3errors.GetAPIError(s3errors.ErrInvalidPart), part.PartNumber)
|
return nil, nil, fmt.Errorf("%w: unknown part %d or etag mismatched", apierr.GetAPIError(apierr.ErrInvalidPart), part.PartNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
// for the last part we have no minimum size limit
|
// for the last part we have no minimum size limit
|
||||||
if i != len(p.Parts)-1 && partInfo.Size < UploadMinSize {
|
if i != len(p.Parts)-1 && partInfo.Size < UploadMinSize {
|
||||||
return nil, nil, fmt.Errorf("%w: %d/%d", s3errors.GetAPIError(s3errors.ErrEntityTooSmall), partInfo.Size, UploadMinSize)
|
return nil, nil, fmt.Errorf("%w: %d/%d", apierr.GetAPIError(apierr.ErrEntityTooSmall), partInfo.Size, UploadMinSize)
|
||||||
}
|
}
|
||||||
parts = append(parts, partInfo)
|
parts = append(parts, partInfo)
|
||||||
multipartObjetSize += partInfo.Size // even if encryption is enabled size is actual (decrypted)
|
multipartObjetSize += partInfo.Size // even if encryption is enabled size is actual (decrypted)
|
||||||
|
@ -471,7 +473,7 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
||||||
zap.String("uploadKey", p.Info.Key),
|
zap.String("uploadKey", p.Info.Key),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
|
|
||||||
return nil, nil, s3errors.GetAPIError(s3errors.ErrInternalError)
|
return nil, nil, apierr.GetAPIError(apierr.ErrInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var addr oid.Address
|
var addr oid.Address
|
||||||
|
@ -579,7 +581,7 @@ func (n *Layer) ListParts(ctx context.Context, p *ListPartsParams) (*ListPartsIn
|
||||||
encInfo := FormEncryptionInfo(multipartInfo.Meta)
|
encInfo := FormEncryptionInfo(multipartInfo.Meta)
|
||||||
if err = p.Info.Encryption.MatchObjectEncryption(encInfo); err != nil {
|
if err = p.Info.Encryption.MatchObjectEncryption(encInfo); err != nil {
|
||||||
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err))
|
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err))
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrInvalidEncryptionParameters)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidEncryptionParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Owner = multipartInfo.Owner
|
res.Owner = multipartInfo.Owner
|
||||||
|
@ -646,8 +648,8 @@ func (p PartsInfo) Extract(part int, etag string, md5Enabled bool) *data.PartInf
|
||||||
func (n *Layer) getUploadParts(ctx context.Context, p *UploadInfoParams) (*data.MultipartInfo, PartsInfo, error) {
|
func (n *Layer) getUploadParts(ctx context.Context, p *UploadInfoParams) (*data.MultipartInfo, PartsInfo, error) {
|
||||||
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Bkt, p.Key, p.UploadID)
|
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Bkt, p.Key, p.UploadID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, nil, fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchUpload), err.Error())
|
return nil, nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchUpload), err.Error())
|
||||||
}
|
}
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,9 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/detector"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/detector"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
|
@ -49,7 +51,7 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteMarkerError struct {
|
DeleteMarkerError struct {
|
||||||
ErrorCode apiErrors.ErrorCode
|
ErrorCode apierr.ErrorCode
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ func newAddress(cnr cid.ID, obj oid.ID) oid.Address {
|
||||||
|
|
||||||
// objectHead returns all object's headers.
|
// objectHead returns all object's headers.
|
||||||
func (n *Layer) objectHead(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID) (*object.Object, error) {
|
func (n *Layer) objectHead(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID) (*object.Object, error) {
|
||||||
prm := PrmObjectHead{
|
prm := frostfs.PrmObjectHead{
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
Object: idObj,
|
Object: idObj,
|
||||||
}
|
}
|
||||||
|
@ -128,11 +130,11 @@ func (n *Layer) initObjectPayloadReader(ctx context.Context, p getParams) (io.Re
|
||||||
// initializes payload reader of the FrostFS object.
|
// initializes payload reader of the FrostFS object.
|
||||||
// Zero range corresponds to full payload (panics if only offset is set).
|
// Zero range corresponds to full payload (panics if only offset is set).
|
||||||
func (n *Layer) initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFSParams) (io.Reader, error) {
|
func (n *Layer) initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFSParams) (io.Reader, error) {
|
||||||
var prmAuth PrmAuth
|
var prmAuth frostfs.PrmAuth
|
||||||
n.prepareAuthParameters(ctx, &prmAuth, p.bktInfo.Owner)
|
n.prepareAuthParameters(ctx, &prmAuth, p.bktInfo.Owner)
|
||||||
|
|
||||||
if p.off+p.ln != 0 {
|
if p.off+p.ln != 0 {
|
||||||
prm := PrmObjectRange{
|
prm := frostfs.PrmObjectRange{
|
||||||
PrmAuth: prmAuth,
|
PrmAuth: prmAuth,
|
||||||
Container: p.bktInfo.CID,
|
Container: p.bktInfo.CID,
|
||||||
Object: p.oid,
|
Object: p.oid,
|
||||||
|
@ -142,7 +144,7 @@ func (n *Layer) initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFS
|
||||||
return n.frostFS.RangeObject(ctx, prm)
|
return n.frostFS.RangeObject(ctx, prm)
|
||||||
}
|
}
|
||||||
|
|
||||||
prm := PrmObjectGet{
|
prm := frostfs.PrmObjectGet{
|
||||||
PrmAuth: prmAuth,
|
PrmAuth: prmAuth,
|
||||||
Container: p.bktInfo.CID,
|
Container: p.bktInfo.CID,
|
||||||
Object: p.oid,
|
Object: p.oid,
|
||||||
|
@ -157,17 +159,17 @@ func (n *Layer) initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFS
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectGet returns an object with payload in the object.
|
// objectGet returns an object with payload in the object.
|
||||||
func (n *Layer) objectGet(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (*Object, error) {
|
func (n *Layer) objectGet(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (*frostfs.Object, error) {
|
||||||
return n.objectGetBase(ctx, bktInfo, objID, PrmAuth{})
|
return n.objectGetBase(ctx, bktInfo, objID, frostfs.PrmAuth{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectGetWithAuth returns an object with payload in the object. Uses provided PrmAuth.
|
// objectGetWithAuth returns an object with payload in the object. Uses provided PrmAuth.
|
||||||
func (n *Layer) objectGetWithAuth(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth PrmAuth) (*Object, error) {
|
func (n *Layer) objectGetWithAuth(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth frostfs.PrmAuth) (*frostfs.Object, error) {
|
||||||
return n.objectGetBase(ctx, bktInfo, objID, auth)
|
return n.objectGetBase(ctx, bktInfo, objID, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) objectGetBase(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth PrmAuth) (*Object, error) {
|
func (n *Layer) objectGetBase(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth frostfs.PrmAuth) (*frostfs.Object, error) {
|
||||||
prm := PrmObjectGet{
|
prm := frostfs.PrmObjectGet{
|
||||||
PrmAuth: auth,
|
PrmAuth: auth,
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
Object: objID,
|
Object: objID,
|
||||||
|
@ -262,7 +264,7 @@ func (n *Layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prm := PrmObjectCreate{
|
prm := frostfs.PrmObjectCreate{
|
||||||
Container: p.BktInfo.CID,
|
Container: p.BktInfo.CID,
|
||||||
Filepath: p.Object,
|
Filepath: p.Object,
|
||||||
Payload: r,
|
Payload: r,
|
||||||
|
@ -287,28 +289,28 @@ func (n *Layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend
|
||||||
if !p.Encryption.Enabled() && len(p.ContentMD5) > 0 {
|
if !p.Encryption.Enabled() && len(p.ContentMD5) > 0 {
|
||||||
headerMd5Hash, err := base64.StdEncoding.DecodeString(p.ContentMD5)
|
headerMd5Hash, err := base64.StdEncoding.DecodeString(p.ContentMD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidDigest)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidDigest)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(headerMd5Hash, createdObj.MD5Sum) {
|
if !bytes.Equal(headerMd5Hash, createdObj.MD5Sum) {
|
||||||
err = n.objectDelete(ctx, p.BktInfo, createdObj.ID)
|
err = n.objectDelete(ctx, p.BktInfo, createdObj.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", p.BktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", p.BktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
||||||
}
|
}
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidDigest)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidDigest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.Encryption.Enabled() && len(p.ContentSHA256Hash) > 0 && !auth.IsStandardContentSHA256(p.ContentSHA256Hash) {
|
if !p.Encryption.Enabled() && len(p.ContentSHA256Hash) > 0 && !auth.IsStandardContentSHA256(p.ContentSHA256Hash) {
|
||||||
contentHashBytes, err := hex.DecodeString(p.ContentSHA256Hash)
|
contentHashBytes, err := hex.DecodeString(p.ContentSHA256Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch)
|
return nil, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(contentHashBytes, createdObj.HashSum) {
|
if !bytes.Equal(contentHashBytes, createdObj.HashSum) {
|
||||||
err = n.objectDelete(ctx, p.BktInfo, createdObj.ID)
|
err = n.objectDelete(ctx, p.BktInfo, createdObj.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", p.BktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", p.BktInfo.CID), zap.Stringer("oid", createdObj.ID))
|
||||||
}
|
}
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch)
|
return nil, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,20 +397,20 @@ func (n *Layer) headLastVersionIfNotDeleted(ctx context.Context, bkt *data.Bucke
|
||||||
|
|
||||||
node, err := n.treeService.GetLatestVersion(ctx, bkt, objectName)
|
node, err := n.treeService.GetLatestVersion(ctx, bkt, objectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrNoSuchKey), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.IsDeleteMarker {
|
if node.IsDeleteMarker {
|
||||||
return nil, DeleteMarkerError{ErrorCode: apiErrors.ErrNoSuchKey}
|
return nil, DeleteMarkerError{ErrorCode: apierr.ErrNoSuchKey}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := n.objectHead(ctx, bkt, node.OID)
|
meta, err := n.objectHead(ctx, bkt, node.OID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if client.IsErrObjectNotFound(err) {
|
if client.IsErrObjectNotFound(err) {
|
||||||
return nil, fmt.Errorf("%w: %s; %s", apiErrors.GetAPIError(apiErrors.ErrNoSuchKey), err.Error(), node.OID.EncodeToString())
|
return nil, fmt.Errorf("%w: %s; %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error(), node.OID.EncodeToString())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -431,8 +433,8 @@ func (n *Layer) headVersion(ctx context.Context, bkt *data.BucketInfo, p *HeadOb
|
||||||
if p.VersionID == data.UnversionedObjectVersionID {
|
if p.VersionID == data.UnversionedObjectVersionID {
|
||||||
foundVersion, err = n.treeService.GetUnversioned(ctx, bkt, p.Object)
|
foundVersion, err = n.treeService.GetUnversioned(ctx, bkt, p.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrNoSuchVersion), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchVersion), err.Error())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -449,7 +451,7 @@ func (n *Layer) headVersion(ctx context.Context, bkt *data.BucketInfo, p *HeadOb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if foundVersion == nil {
|
if foundVersion == nil {
|
||||||
return nil, fmt.Errorf("%w: there isn't tree node with requested version id", apiErrors.GetAPIError(apiErrors.ErrNoSuchVersion))
|
return nil, fmt.Errorf("%w: there isn't tree node with requested version id", apierr.GetAPIError(apierr.ErrNoSuchVersion))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,13 +461,13 @@ func (n *Layer) headVersion(ctx context.Context, bkt *data.BucketInfo, p *HeadOb
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundVersion.IsDeleteMarker {
|
if foundVersion.IsDeleteMarker {
|
||||||
return nil, DeleteMarkerError{ErrorCode: apiErrors.ErrMethodNotAllowed}
|
return nil, DeleteMarkerError{ErrorCode: apierr.ErrMethodNotAllowed}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := n.objectHead(ctx, bkt, foundVersion.OID)
|
meta, err := n.objectHead(ctx, bkt, foundVersion.OID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if client.IsErrObjectNotFound(err) {
|
if client.IsErrObjectNotFound(err) {
|
||||||
return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrNoSuchVersion), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchVersion), err.Error())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -484,16 +486,16 @@ func (n *Layer) headVersion(ctx context.Context, bkt *data.BucketInfo, p *HeadOb
|
||||||
|
|
||||||
// objectDelete puts tombstone object into frostfs.
|
// objectDelete puts tombstone object into frostfs.
|
||||||
func (n *Layer) objectDelete(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID) error {
|
func (n *Layer) objectDelete(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID) error {
|
||||||
return n.objectDeleteBase(ctx, bktInfo, idObj, PrmAuth{})
|
return n.objectDeleteBase(ctx, bktInfo, idObj, frostfs.PrmAuth{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectDeleteWithAuth puts tombstone object into frostfs. Uses provided PrmAuth.
|
// objectDeleteWithAuth puts tombstone object into frostfs. Uses provided PrmAuth.
|
||||||
func (n *Layer) objectDeleteWithAuth(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth PrmAuth) error {
|
func (n *Layer) objectDeleteWithAuth(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth frostfs.PrmAuth) error {
|
||||||
return n.objectDeleteBase(ctx, bktInfo, idObj, auth)
|
return n.objectDeleteBase(ctx, bktInfo, idObj, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) objectDeleteBase(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth PrmAuth) error {
|
func (n *Layer) objectDeleteBase(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth frostfs.PrmAuth) error {
|
||||||
prm := PrmObjectDelete{
|
prm := frostfs.PrmObjectDelete{
|
||||||
PrmAuth: auth,
|
PrmAuth: auth,
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
Object: idObj,
|
Object: idObj,
|
||||||
|
@ -507,7 +509,7 @@ func (n *Layer) objectDeleteBase(ctx context.Context, bktInfo *data.BucketInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectPutAndHash prepare auth parameters and invoke frostfs.CreateObject.
|
// objectPutAndHash prepare auth parameters and invoke frostfs.CreateObject.
|
||||||
func (n *Layer) objectPutAndHash(ctx context.Context, prm PrmObjectCreate, bktInfo *data.BucketInfo) (*data.CreatedObjectInfo, error) {
|
func (n *Layer) objectPutAndHash(ctx context.Context, prm frostfs.PrmObjectCreate, bktInfo *data.BucketInfo) (*data.CreatedObjectInfo, error) {
|
||||||
n.prepareAuthParameters(ctx, &prm.PrmAuth, bktInfo.Owner)
|
n.prepareAuthParameters(ctx, &prm.PrmAuth, bktInfo.Owner)
|
||||||
prm.ClientCut = n.features.ClientCut()
|
prm.ClientCut = n.features.ClientCut()
|
||||||
prm.BufferMaxSize = n.features.BufferMaxSizeForPut()
|
prm.BufferMaxSize = n.features.BufferMaxSizeForPut()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ func TestGoroutinesDontLeakInPutAndHash(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
payload := bytes.NewReader(content)
|
payload := bytes.NewReader(content)
|
||||||
|
|
||||||
prm := PrmObjectCreate{
|
prm := frostfs.PrmObjectCreate{
|
||||||
Filepath: tc.obj,
|
Filepath: tc.obj,
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PatchObjectParams struct {
|
type PatchObjectParams struct {
|
||||||
|
@ -32,7 +33,7 @@ func (n *Layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
|
||||||
return n.patchMultipartObject(ctx, p)
|
return n.patchMultipartObject(ctx, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
prmPatch := PrmObjectPatch{
|
prmPatch := frostfs.PrmObjectPatch{
|
||||||
Container: p.BktInfo.CID,
|
Container: p.BktInfo.CID,
|
||||||
Object: p.Object.ObjectInfo.ID,
|
Object: p.Object.ObjectInfo.ID,
|
||||||
Payload: p.NewBytes,
|
Payload: p.NewBytes,
|
||||||
|
@ -74,13 +75,13 @@ func (n *Layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
|
||||||
return p.Object, nil
|
return p.Object, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) patchObject(ctx context.Context, p PrmObjectPatch) (*data.CreatedObjectInfo, error) {
|
func (n *Layer) patchObject(ctx context.Context, p frostfs.PrmObjectPatch) (*data.CreatedObjectInfo, error) {
|
||||||
objID, err := n.frostFS.PatchObject(ctx, p)
|
objID, err := n.frostFS.PatchObject(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("patch object: %w", err)
|
return nil, fmt.Errorf("patch object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
prmHead := PrmObjectHead{
|
prmHead := frostfs.PrmObjectHead{
|
||||||
PrmAuth: p.PrmAuth,
|
PrmAuth: p.PrmAuth,
|
||||||
Container: p.Container,
|
Container: p.Container,
|
||||||
Object: objID,
|
Object: objID,
|
||||||
|
@ -110,7 +111,7 @@ func (n *Layer) patchMultipartObject(ctx context.Context, p *PatchObjectParams)
|
||||||
return nil, fmt.Errorf("unmarshal combined object parts: %w", err)
|
return nil, fmt.Errorf("unmarshal combined object parts: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
prmPatch := PrmObjectPatch{
|
prmPatch := frostfs.PrmObjectPatch{
|
||||||
Container: p.BktInfo.CID,
|
Container: p.BktInfo.CID,
|
||||||
}
|
}
|
||||||
n.prepareAuthParameters(ctx, &prmPatch.PrmAuth, p.BktInfo.Owner)
|
n.prepareAuthParameters(ctx, &prmPatch.PrmAuth, p.BktInfo.Owner)
|
||||||
|
@ -144,13 +145,13 @@ func (n *Layer) patchMultipartObject(ctx context.Context, p *PatchObjectParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns patched part info, updated offset and length.
|
// Returns patched part info, updated offset and length.
|
||||||
func (n *Layer) patchPart(ctx context.Context, part *data.PartInfo, p *PatchObjectParams, prmPatch *PrmObjectPatch, off, ln uint64, lastPart bool) (*data.CreatedObjectInfo, uint64, uint64, error) {
|
func (n *Layer) patchPart(ctx context.Context, part *data.PartInfo, p *PatchObjectParams, prmPatch *frostfs.PrmObjectPatch, off, ln uint64, lastPart bool) (*data.CreatedObjectInfo, uint64, uint64, error) {
|
||||||
if off == 0 && ln >= part.Size {
|
if off == 0 && ln >= part.Size {
|
||||||
curLen := part.Size
|
curLen := part.Size
|
||||||
if lastPart {
|
if lastPart {
|
||||||
curLen = ln
|
curLen = ln
|
||||||
}
|
}
|
||||||
prm := PrmObjectCreate{
|
prm := frostfs.PrmObjectCreate{
|
||||||
Container: p.BktInfo.CID,
|
Container: p.BktInfo.CID,
|
||||||
Payload: io.LimitReader(p.NewBytes, int64(curLen)),
|
Payload: io.LimitReader(p.NewBytes, int64(curLen)),
|
||||||
CreationTime: part.Created,
|
CreationTime: part.Created,
|
||||||
|
@ -204,7 +205,7 @@ func (n *Layer) updateCombinedObject(ctx context.Context, parts []*data.PartInfo
|
||||||
headerParts.WriteString(headerPart)
|
headerParts.WriteString(headerPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
prm := PrmObjectCreate{
|
prm := frostfs.PrmObjectCreate{
|
||||||
Container: p.BktInfo.CID,
|
Container: p.BktInfo.CID,
|
||||||
PayloadSize: fullObjSize,
|
PayloadSize: fullObjSize,
|
||||||
Filepath: p.Object.ObjectInfo.Name,
|
Filepath: p.Object.ObjectInfo.Name,
|
||||||
|
|
|
@ -3,7 +3,7 @@ package layer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
errorsStd "errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -11,7 +11,9 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
)
|
)
|
||||||
|
@ -40,7 +42,7 @@ func (n *Layer) PutLockInfo(ctx context.Context, p *PutLockInfoParams) (err erro
|
||||||
}
|
}
|
||||||
|
|
||||||
lockInfo, err := n.treeService.GetLock(ctx, p.ObjVersion.BktInfo, versionNode.ID)
|
lockInfo, err := n.treeService.GetLock(ctx, p.ObjVersion.BktInfo, versionNode.ID)
|
||||||
if err != nil && !errorsStd.Is(err, ErrNodeNotFound) {
|
if err != nil && !errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +115,7 @@ func (n *Layer) getNodeVersionFromCacheOrFrostfs(ctx context.Context, objVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) putLockObject(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, lock *data.ObjectLock, copiesNumber []uint32) (oid.ID, error) {
|
func (n *Layer) putLockObject(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, lock *data.ObjectLock, copiesNumber []uint32) (oid.ID, error) {
|
||||||
prm := PrmObjectCreate{
|
prm := frostfs.PrmObjectCreate{
|
||||||
Container: bktInfo.CID,
|
Container: bktInfo.CID,
|
||||||
Locks: []oid.ID{objID},
|
Locks: []oid.ID{objID},
|
||||||
CreationTime: TimeNow(ctx),
|
CreationTime: TimeNow(ctx),
|
||||||
|
@ -146,7 +148,7 @@ func (n *Layer) GetLockInfo(ctx context.Context, objVersion *data.ObjectVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockInfo, err := n.treeService.GetLock(ctx, objVersion.BktInfo, versionNode.ID)
|
lockInfo, err := n.treeService.GetLock(ctx, objVersion.BktInfo, versionNode.ID)
|
||||||
if err != nil && !errorsStd.Is(err, ErrNodeNotFound) {
|
if err != nil && !errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if lockInfo == nil {
|
if lockInfo == nil {
|
||||||
|
@ -165,16 +167,16 @@ func (n *Layer) getCORS(ctx context.Context, bkt *data.BucketInfo) (*data.CORSCo
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := n.treeService.GetBucketCORS(ctx, bkt)
|
addr, err := n.treeService.GetBucketCORS(ctx, bkt)
|
||||||
objNotFound := errorsStd.Is(err, ErrNodeNotFound)
|
objNotFound := errors.Is(err, tree.ErrNodeNotFound)
|
||||||
if err != nil && !objNotFound {
|
if err != nil && !objNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if objNotFound {
|
if objNotFound {
|
||||||
return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchCORSConfiguration), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchCORSConfiguration), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var prmAuth PrmAuth
|
var prmAuth frostfs.PrmAuth
|
||||||
corsBkt := bkt
|
corsBkt := bkt
|
||||||
if !addr.Container().Equals(bkt.CID) && !addr.Container().Equals(cid.ID{}) {
|
if !addr.Container().Equals(bkt.CID) && !addr.Container().Equals(cid.ID{}) {
|
||||||
corsBkt = &data.BucketInfo{CID: addr.Container()}
|
corsBkt = &data.BucketInfo{CID: addr.Container()}
|
||||||
|
@ -209,7 +211,7 @@ func (n *Layer) GetBucketSettings(ctx context.Context, bktInfo *data.BucketInfo)
|
||||||
|
|
||||||
settings, err := n.treeService.GetSettingsNode(ctx, bktInfo)
|
settings, err := n.treeService.GetSettingsNode(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errorsStd.Is(err, ErrNodeNotFound) {
|
if !errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
settings = &data.BucketSettings{Versioning: data.VersioningUnversioned}
|
settings = &data.BucketSettings{Versioning: data.VersioningUnversioned}
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -39,8 +40,8 @@ func (n *Layer) GetObjectTagging(ctx context.Context, p *data.GetObjectTaggingPa
|
||||||
|
|
||||||
tags, err := n.treeService.GetObjectTagging(ctx, p.ObjectVersion.BktInfo, nodeVersion)
|
tags, err := n.treeService.GetObjectTagging(ctx, p.ObjectVersion.BktInfo, nodeVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return "", nil, fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchKey), err.Error())
|
return "", nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
@ -62,8 +63,8 @@ func (n *Layer) PutObjectTagging(ctx context.Context, p *data.PutObjectTaggingPa
|
||||||
|
|
||||||
err = n.treeService.PutObjectTagging(ctx, p.ObjectVersion.BktInfo, nodeVersion, p.TagSet)
|
err = n.treeService.PutObjectTagging(ctx, p.ObjectVersion.BktInfo, nodeVersion, p.TagSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchKey), err.Error())
|
return fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -81,8 +82,8 @@ func (n *Layer) DeleteObjectTagging(ctx context.Context, p *data.ObjectVersion)
|
||||||
|
|
||||||
err = n.treeService.DeleteObjectTagging(ctx, p.BktInfo, version)
|
err = n.treeService.DeleteObjectTagging(ctx, p.BktInfo, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrNodeNotFound) {
|
if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchKey), err.Error())
|
return fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -102,7 +103,7 @@ func (n *Layer) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := n.treeService.GetBucketTagging(ctx, bktInfo)
|
tags, err := n.treeService.GetBucketTagging(ctx, bktInfo)
|
||||||
if err != nil && !errors.Is(err, ErrNodeNotFound) {
|
if err != nil && !errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,14 +156,14 @@ func (n *Layer) getNodeVersion(ctx context.Context, objVersion *data.ObjectVersi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if version == nil {
|
if version == nil {
|
||||||
err = fmt.Errorf("%w: there isn't tree node with requested version id", s3errors.GetAPIError(s3errors.ErrNoSuchVersion))
|
err = fmt.Errorf("%w: there isn't tree node with requested version id", apierr.GetAPIError(apierr.ErrNoSuchVersion))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && version.IsDeleteMarker && !objVersion.NoErrorOnDeleteMarker {
|
if err == nil && version.IsDeleteMarker && !objVersion.NoErrorOnDeleteMarker {
|
||||||
return nil, fmt.Errorf("%w: found version is delete marker", s3errors.GetAPIError(s3errors.ErrNoSuchKey))
|
return nil, fmt.Errorf("%w: found version is delete marker", apierr.GetAPIError(apierr.ErrNoSuchKey))
|
||||||
} else if errors.Is(err, ErrNodeNotFound) {
|
} else if errors.Is(err, tree.ErrNodeNotFound) {
|
||||||
return nil, fmt.Errorf("%w: %s", s3errors.GetAPIError(s3errors.ErrNoSuchKey), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchKey), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && version != nil && !version.IsDeleteMarker {
|
if err == nil && version != nil && !version.IsDeleteMarker {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package layer
|
package tree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -8,8 +8,8 @@ import (
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TreeService provide interface to interact with tree service using s3 data models.
|
// Service provide interface to interact with tree service using s3 data models.
|
||||||
type TreeService interface {
|
type Service interface {
|
||||||
// PutSettingsNode update or create new settings node in tree service.
|
// PutSettingsNode update or create new settings node in tree service.
|
||||||
PutSettingsNode(ctx context.Context, bktInfo *data.BucketInfo, settings *data.BucketSettings) error
|
PutSettingsNode(ctx context.Context, bktInfo *data.BucketInfo, settings *data.BucketSettings) error
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ func (t *TreeServiceMock) PutSettingsNode(_ context.Context, bktInfo *data.Bucke
|
||||||
func (t *TreeServiceMock) GetSettingsNode(_ context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
|
func (t *TreeServiceMock) GetSettingsNode(_ context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
|
||||||
settings, ok := t.settings[bktInfo.CID.EncodeToString()]
|
settings, ok := t.settings[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
|
@ -140,7 +141,7 @@ func (t *TreeServiceMock) PutBucketCORS(_ context.Context, bktInfo *data.BucketI
|
||||||
|
|
||||||
t.system[bktInfo.CID.EncodeToString()] = systemMap
|
t.system[bktInfo.CID.EncodeToString()] = systemMap
|
||||||
|
|
||||||
return nil, ErrNoNodeToRemove
|
return nil, tree.ErrNoNodeToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) ([]oid.Address, error) {
|
func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) ([]oid.Address, error) {
|
||||||
|
@ -150,12 +151,12 @@ func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) ([
|
||||||
func (t *TreeServiceMock) GetVersions(_ context.Context, bktInfo *data.BucketInfo, objectName string) ([]*data.NodeVersion, error) {
|
func (t *TreeServiceMock) GetVersions(_ context.Context, bktInfo *data.BucketInfo, objectName string) ([]*data.NodeVersion, error) {
|
||||||
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
versions, ok := cnrVersionsMap[objectName]
|
versions, ok := cnrVersionsMap[objectName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return versions, nil
|
return versions, nil
|
||||||
|
@ -164,12 +165,12 @@ func (t *TreeServiceMock) GetVersions(_ context.Context, bktInfo *data.BucketInf
|
||||||
func (t *TreeServiceMock) GetLatestVersion(_ context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) {
|
func (t *TreeServiceMock) GetLatestVersion(_ context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) {
|
||||||
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
versions, ok := cnrVersionsMap[objectName]
|
versions, ok := cnrVersionsMap[objectName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(versions, func(i, j int) bool {
|
sort.Slice(versions, func(i, j int) bool {
|
||||||
|
@ -180,13 +181,13 @@ func (t *TreeServiceMock) GetLatestVersion(_ context.Context, bktInfo *data.Buck
|
||||||
return versions[len(versions)-1], nil
|
return versions[len(versions)-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) InitVersionsByPrefixStream(_ context.Context, bktInfo *data.BucketInfo, prefix string, latestOnly bool) (data.VersionsStream, error) {
|
func (t *TreeServiceMock) InitVersionsByPrefixStream(_ context.Context, bktInfo *data.BucketInfo, prefix string, latestOnly bool) (data.VersionsStream, error) {
|
||||||
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []*data.NodeVersion
|
var result []*data.NodeVersion
|
||||||
|
@ -218,12 +219,12 @@ func (t *TreeServiceMock) InitVersionsByPrefixStream(_ context.Context, bktInfo
|
||||||
func (t *TreeServiceMock) GetUnversioned(_ context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) {
|
func (t *TreeServiceMock) GetUnversioned(_ context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) {
|
||||||
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
versions, ok := cnrVersionsMap[objectName]
|
versions, ok := cnrVersionsMap[objectName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
|
@ -232,7 +233,7 @@ func (t *TreeServiceMock) GetUnversioned(_ context.Context, bktInfo *data.Bucket
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) AddVersion(_ context.Context, bktInfo *data.BucketInfo, newVersion *data.NodeVersion) (uint64, error) {
|
func (t *TreeServiceMock) AddVersion(_ context.Context, bktInfo *data.BucketInfo, newVersion *data.NodeVersion) (uint64, error) {
|
||||||
|
@ -278,7 +279,7 @@ func (t *TreeServiceMock) AddVersion(_ context.Context, bktInfo *data.BucketInfo
|
||||||
func (t *TreeServiceMock) RemoveVersion(_ context.Context, bktInfo *data.BucketInfo, nodeID uint64) error {
|
func (t *TreeServiceMock) RemoveVersion(_ context.Context, bktInfo *data.BucketInfo, nodeID uint64) error {
|
||||||
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrNodeNotFound
|
return tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, versions := range cnrVersionsMap {
|
for key, versions := range cnrVersionsMap {
|
||||||
|
@ -290,7 +291,7 @@ func (t *TreeServiceMock) RemoveVersion(_ context.Context, bktInfo *data.BucketI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrNodeNotFound
|
return tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) GetAllVersionsByPrefix(_ context.Context, bktInfo *data.BucketInfo, prefix string) ([]*data.NodeVersion, error) {
|
func (t *TreeServiceMock) GetAllVersionsByPrefix(_ context.Context, bktInfo *data.BucketInfo, prefix string) ([]*data.NodeVersion, error) {
|
||||||
|
@ -334,7 +335,7 @@ func (t *TreeServiceMock) GetMultipartUploadsByPrefix(context.Context, *data.Buc
|
||||||
func (t *TreeServiceMock) GetMultipartUpload(_ context.Context, bktInfo *data.BucketInfo, objectName, uploadID string) (*data.MultipartInfo, error) {
|
func (t *TreeServiceMock) GetMultipartUpload(_ context.Context, bktInfo *data.BucketInfo, objectName, uploadID string) (*data.MultipartInfo, error) {
|
||||||
cnrMultipartsMap, ok := t.multiparts[bktInfo.CID.EncodeToString()]
|
cnrMultipartsMap, ok := t.multiparts[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
multiparts := cnrMultipartsMap[objectName]
|
multiparts := cnrMultipartsMap[objectName]
|
||||||
|
@ -344,7 +345,7 @@ func (t *TreeServiceMock) GetMultipartUpload(_ context.Context, bktInfo *data.Bu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64, info *data.PartInfo) (oldObjIDsToDelete []oid.ID, err error) {
|
func (t *TreeServiceMock) AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64, info *data.PartInfo) (oldObjIDsToDelete []oid.ID, err error) {
|
||||||
|
@ -387,7 +388,7 @@ LOOP:
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundMultipart == nil {
|
if foundMultipart == nil {
|
||||||
return nil, ErrNodeNotFound
|
return nil, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
partsMap := t.parts[foundMultipart.UploadID]
|
partsMap := t.parts[foundMultipart.UploadID]
|
||||||
|
@ -411,18 +412,18 @@ func (t *TreeServiceMock) PutBucketLifecycleConfiguration(_ context.Context, bkt
|
||||||
|
|
||||||
t.system[bktInfo.CID.EncodeToString()] = systemMap
|
t.system[bktInfo.CID.EncodeToString()] = systemMap
|
||||||
|
|
||||||
return nil, ErrNoNodeToRemove
|
return nil, tree.ErrNoNodeToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeServiceMock) GetBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
|
func (t *TreeServiceMock) GetBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
|
||||||
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return oid.Address{}, ErrNodeNotFound
|
return oid.Address{}, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
node, ok := systemMap["lifecycle"]
|
node, ok := systemMap["lifecycle"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return oid.Address{}, ErrNodeNotFound
|
return oid.Address{}, tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return newAddress(bktInfo.CID, node.OID), nil
|
return newAddress(bktInfo.CID, node.OID), nil
|
||||||
|
@ -431,12 +432,12 @@ func (t *TreeServiceMock) GetBucketLifecycleConfiguration(_ context.Context, bkt
|
||||||
func (t *TreeServiceMock) DeleteBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error) {
|
func (t *TreeServiceMock) DeleteBucketLifecycleConfiguration(_ context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error) {
|
||||||
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNoNodeToRemove
|
return nil, tree.ErrNoNodeToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
node, ok := systemMap["lifecycle"]
|
node, ok := systemMap["lifecycle"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNoNodeToRemove
|
return nil, tree.ErrNoNodeToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(systemMap, "lifecycle")
|
delete(systemMap, "lifecycle")
|
||||||
|
@ -461,7 +462,7 @@ LOOP:
|
||||||
}
|
}
|
||||||
|
|
||||||
if uploadID == "" {
|
if uploadID == "" {
|
||||||
return ErrNodeNotFound
|
return tree.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(t.parts, uploadID)
|
delete(t.parts, uploadID)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
|
bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
|
||||||
|
@ -154,7 +155,7 @@ func prepareContext(t *testing.T, cachesConfig ...*CachesConfig) *testContext {
|
||||||
tp := NewTestFrostFS(key)
|
tp := NewTestFrostFS(key)
|
||||||
|
|
||||||
bktName := "testbucket1"
|
bktName := "testbucket1"
|
||||||
res, err := tp.CreateContainer(ctx, PrmContainerCreate{
|
res, err := tp.CreateContainer(ctx, frostfs.PrmContainerCreate{
|
||||||
Name: bktName,
|
Name: bktName,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -8,9 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "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"
|
||||||
frostfsErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
@ -57,9 +56,9 @@ func Auth(center Center, log *zap.Logger) Func {
|
||||||
reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err))
|
reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err))
|
reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err))
|
||||||
err = frostfsErrors.UnwrapErr(err)
|
err = apierr.TransformToS3Error(err)
|
||||||
if _, ok := err.(apiErrors.Error); !ok {
|
if err.(apierr.Error).ErrCode == apierr.ErrInternalError {
|
||||||
err = apiErrors.GetAPIError(apiErrors.ErrAccessDenied)
|
err = apierr.GetAPIError(apierr.ErrAccessDenied)
|
||||||
}
|
}
|
||||||
if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil {
|
if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil {
|
||||||
reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
|
reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
|
||||||
|
|
|
@ -11,8 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
frostfsErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||||
|
@ -25,11 +24,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
QueryVersionID = "versionId"
|
QueryVersionID = "versionId"
|
||||||
QueryPrefix = "prefix"
|
QueryPrefix = "prefix"
|
||||||
QueryDelimiter = "delimiter"
|
QueryDelimiter = "delimiter"
|
||||||
QueryMaxKeys = "max-keys"
|
QueryMaxKeys = "max-keys"
|
||||||
amzTagging = "x-amz-tagging"
|
QueryMarker = "marker"
|
||||||
|
QueryEncodingType = "encoding-type"
|
||||||
|
amzTagging = "x-amz-tagging"
|
||||||
|
|
||||||
|
unmatchedBucketOperation = "UnmatchedBucketOperation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// In these operations we don't check resource tags because
|
// In these operations we don't check resource tags because
|
||||||
|
@ -85,7 +88,7 @@ func PolicyCheck(cfg PolicyConfig) Func {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
if err := policyCheck(r, cfg); err != nil {
|
if err := policyCheck(r, cfg); err != nil {
|
||||||
reqLogOrDefault(ctx, cfg.Log).Error(logs.PolicyValidationFailed, zap.Error(err))
|
reqLogOrDefault(ctx, cfg.Log).Error(logs.PolicyValidationFailed, zap.Error(err))
|
||||||
err = frostfsErrors.UnwrapErr(err)
|
err = apierr.TransformToS3Error(err)
|
||||||
if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil {
|
if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil {
|
||||||
reqLogOrDefault(ctx, cfg.Log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
|
reqLogOrDefault(ctx, cfg.Log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
|
||||||
}
|
}
|
||||||
|
@ -145,11 +148,11 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error {
|
||||||
case st == chain.Allow:
|
case st == chain.Allow:
|
||||||
return nil
|
return nil
|
||||||
case st != chain.NoRuleFound:
|
case st != chain.NoRuleFound:
|
||||||
return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
return apierr.GetAPIErrorWithError(apierr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Settings.PolicyDenyByDefault() {
|
if cfg.Settings.PolicyDenyByDefault() {
|
||||||
return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
return apierr.GetAPIErrorWithError(apierr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -269,8 +272,17 @@ func determineBucketOperation(r *http.Request) string {
|
||||||
return ListObjectsV2MOperation
|
return ListObjectsV2MOperation
|
||||||
case query.Get(ListTypeQuery) == "2":
|
case query.Get(ListTypeQuery) == "2":
|
||||||
return ListObjectsV2Operation
|
return ListObjectsV2Operation
|
||||||
default:
|
case len(query) == 0 || func() bool {
|
||||||
|
for key := range query {
|
||||||
|
if key != QueryDelimiter && key != QueryMaxKeys && key != QueryPrefix && key != QueryMarker && key != QueryEncodingType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}():
|
||||||
return ListObjectsV1Operation
|
return ListObjectsV1Operation
|
||||||
|
default:
|
||||||
|
return unmatchedBucketOperation
|
||||||
}
|
}
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
switch {
|
switch {
|
||||||
|
@ -292,8 +304,10 @@ func determineBucketOperation(r *http.Request) string {
|
||||||
return PutBucketVersioningOperation
|
return PutBucketVersioningOperation
|
||||||
case query.Has(NotificationQuery):
|
case query.Has(NotificationQuery):
|
||||||
return PutBucketNotificationOperation
|
return PutBucketNotificationOperation
|
||||||
default:
|
case len(query) == 0:
|
||||||
return CreateBucketOperation
|
return CreateBucketOperation
|
||||||
|
default:
|
||||||
|
return unmatchedBucketOperation
|
||||||
}
|
}
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
switch {
|
switch {
|
||||||
|
@ -316,12 +330,14 @@ func determineBucketOperation(r *http.Request) string {
|
||||||
return DeleteBucketLifecycleOperation
|
return DeleteBucketLifecycleOperation
|
||||||
case query.Has(EncryptionQuery):
|
case query.Has(EncryptionQuery):
|
||||||
return DeleteBucketEncryptionOperation
|
return DeleteBucketEncryptionOperation
|
||||||
default:
|
case len(query) == 0:
|
||||||
return DeleteBucketOperation
|
return DeleteBucketOperation
|
||||||
|
default:
|
||||||
|
return unmatchedBucketOperation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UnmatchedBucketOperation"
|
return unmatchedBucketOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
func determineObjectOperation(r *http.Request) string {
|
func determineObjectOperation(r *http.Request) string {
|
||||||
|
@ -461,7 +477,7 @@ func determineRequestTags(r *http.Request, decoder XMLDecoder, op string) (map[s
|
||||||
if strings.HasSuffix(op, PutObjectTaggingOperation) || strings.HasSuffix(op, PutBucketTaggingOperation) {
|
if strings.HasSuffix(op, PutObjectTaggingOperation) || strings.HasSuffix(op, PutBucketTaggingOperation) {
|
||||||
tagging := new(data.Tagging)
|
tagging := new(data.Tagging)
|
||||||
if err := decoder.NewXMLDecoder(r.Body).Decode(tagging); err != nil {
|
if err := decoder.NewXMLDecoder(r.Body).Decode(tagging); err != nil {
|
||||||
return nil, fmt.Errorf("%w: %s", apiErr.GetAPIError(apiErr.ErrMalformedXML), err.Error())
|
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error())
|
||||||
}
|
}
|
||||||
GetReqInfo(r.Context()).Tagging = tagging
|
GetReqInfo(r.Context()).Tagging = tagging
|
||||||
|
|
||||||
|
@ -473,7 +489,7 @@ func determineRequestTags(r *http.Request, decoder XMLDecoder, op string) (map[s
|
||||||
if tagging := r.Header.Get(amzTagging); len(tagging) > 0 {
|
if tagging := r.Header.Get(amzTagging); len(tagging) > 0 {
|
||||||
queries, err := url.ParseQuery(tagging)
|
queries, err := url.ParseQuery(tagging)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, apiErr.GetAPIError(apiErr.ErrInvalidArgument)
|
return nil, apierr.GetAPIError(apierr.ErrInvalidArgument)
|
||||||
}
|
}
|
||||||
for key := range queries {
|
for key := range queries {
|
||||||
tags[fmt.Sprintf(s3.PropertyKeyFormatRequestTag, key)] = queries.Get(key)
|
tags[fmt.Sprintf(s3.PropertyKeyFormatRequestTag, key)] = queries.Get(key)
|
||||||
|
|
|
@ -152,6 +152,12 @@ func TestDetermineBucketOperation(t *testing.T) {
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
expected: ListObjectsV1Operation,
|
expected: ListObjectsV1Operation,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "UnmatchedBucketOperation GET",
|
||||||
|
method: http.MethodGet,
|
||||||
|
queryParam: map[string]string{"query": ""},
|
||||||
|
expected: unmatchedBucketOperation,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "PutBucketCorsOperation",
|
name: "PutBucketCorsOperation",
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
|
@ -211,6 +217,12 @@ func TestDetermineBucketOperation(t *testing.T) {
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
expected: CreateBucketOperation,
|
expected: CreateBucketOperation,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "UnmatchedBucketOperation PUT",
|
||||||
|
method: http.MethodPut,
|
||||||
|
queryParam: map[string]string{"query": ""},
|
||||||
|
expected: unmatchedBucketOperation,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "DeleteMultipleObjectsOperation",
|
name: "DeleteMultipleObjectsOperation",
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
|
@ -263,10 +275,16 @@ func TestDetermineBucketOperation(t *testing.T) {
|
||||||
method: http.MethodDelete,
|
method: http.MethodDelete,
|
||||||
expected: DeleteBucketOperation,
|
expected: DeleteBucketOperation,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "UnmatchedBucketOperation DELETE",
|
||||||
|
method: http.MethodDelete,
|
||||||
|
queryParam: map[string]string{"query": ""},
|
||||||
|
expected: unmatchedBucketOperation,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "UnmatchedBucketOperation",
|
name: "UnmatchedBucketOperation",
|
||||||
method: "invalid-method",
|
method: "invalid-method",
|
||||||
expected: "UnmatchedBucketOperation",
|
expected: unmatchedBucketOperation,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
|
@ -187,14 +187,21 @@ func Request(log *zap.Logger, settings RequestSettings) Func {
|
||||||
|
|
||||||
r = r.WithContext(treepool.SetRequestID(r.Context(), reqInfo.RequestID))
|
r = r.WithContext(treepool.SetRequestID(r.Context(), reqInfo.RequestID))
|
||||||
|
|
||||||
reqLogger := log.With(zap.String("request_id", reqInfo.RequestID))
|
fields := []zap.Field{zap.String("request_id", reqInfo.RequestID)}
|
||||||
r = r.WithContext(SetReqLogger(r.Context(), reqLogger))
|
ctx, span := StartHTTPServerSpan(r, "REQUEST S3")
|
||||||
|
if traceID := span.SpanContext().TraceID(); traceID.IsValid() {
|
||||||
|
fields = append(fields, zap.String("trace_id", traceID.String()))
|
||||||
|
}
|
||||||
|
lw := &traceResponseWriter{ResponseWriter: w, ctx: ctx, span: span}
|
||||||
|
|
||||||
|
reqLogger := log.With(fields...)
|
||||||
|
r = r.WithContext(SetReqLogger(ctx, reqLogger))
|
||||||
|
|
||||||
reqLogger.Info(logs.RequestStart, zap.String("host", r.Host),
|
reqLogger.Info(logs.RequestStart, zap.String("host", r.Host),
|
||||||
zap.String("remote_host", reqInfo.RemoteHost), zap.String("namespace", reqInfo.Namespace))
|
zap.String("remote_host", reqInfo.RemoteHost), zap.String("namespace", reqInfo.Namespace))
|
||||||
|
|
||||||
// continue execution
|
// continue execution
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(lw, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"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/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
||||||
"go.opentelemetry.io/otel/trace"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,7 +61,7 @@ const (
|
||||||
hdrSSE = "X-Amz-Server-Side-Encryption"
|
hdrSSE = "X-Amz-Server-Side-Encryption"
|
||||||
|
|
||||||
// hdrSSECustomerKey is the HTTP header key referencing the
|
// hdrSSECustomerKey is the HTTP header key referencing the
|
||||||
// SSE-C client-provided key..
|
// SSE-C client-provided key.
|
||||||
hdrSSECustomerKey = hdrSSE + "-Customer-Key"
|
hdrSSECustomerKey = hdrSSE + "-Customer-Key"
|
||||||
|
|
||||||
// hdrSSECopyKey is the HTTP header key referencing the SSE-C
|
// hdrSSECopyKey is the HTTP header key referencing the SSE-C
|
||||||
|
@ -74,7 +73,7 @@ var (
|
||||||
xmlHeader = []byte(xml.Header)
|
xmlHeader = []byte(xml.Header)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Non exhaustive list of AWS S3 standard error responses -
|
// Non-exhaustive list of AWS S3 standard error responses -
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||||
var s3ErrorResponseMap = map[string]string{
|
var s3ErrorResponseMap = map[string]string{
|
||||||
"AccessDenied": "Access Denied.",
|
"AccessDenied": "Access Denied.",
|
||||||
|
@ -332,10 +331,6 @@ func LogSuccessResponse(l *zap.Logger) Func {
|
||||||
fields = append(fields, zap.String("user", reqInfo.User))
|
fields = append(fields, zap.String("user", reqInfo.User))
|
||||||
}
|
}
|
||||||
|
|
||||||
if traceID, err := trace.TraceIDFromHex(reqInfo.TraceID); err == nil && traceID.IsValid() {
|
|
||||||
fields = append(fields, zap.String("trace_id", reqInfo.TraceID))
|
|
||||||
}
|
|
||||||
|
|
||||||
reqLogger.Info(logs.RequestEnd, fields...)
|
reqLogger.Info(logs.RequestEnd, fields...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,20 +11,6 @@ import (
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tracing adds tracing support for requests.
|
|
||||||
// Must be placed after prepareRequest middleware.
|
|
||||||
func Tracing() Func {
|
|
||||||
return func(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
appCtx, span := StartHTTPServerSpan(r, "REQUEST S3")
|
|
||||||
reqInfo := GetReqInfo(r.Context())
|
|
||||||
reqInfo.TraceID = span.SpanContext().TraceID().String()
|
|
||||||
lw := &traceResponseWriter{ResponseWriter: w, ctx: appCtx, span: span}
|
|
||||||
h.ServeHTTP(lw, r.WithContext(appCtx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type traceResponseWriter struct {
|
type traceResponseWriter struct {
|
||||||
sync.Once
|
sync.Once
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
|
@ -134,7 +134,6 @@ func NewRouter(cfg Config) *chi.Mux {
|
||||||
s3middleware.Request(cfg.Log, cfg.MiddlewareSettings),
|
s3middleware.Request(cfg.Log, cfg.MiddlewareSettings),
|
||||||
middleware.ThrottleWithOpts(cfg.Throttle),
|
middleware.ThrottleWithOpts(cfg.Throttle),
|
||||||
middleware.Recoverer,
|
middleware.Recoverer,
|
||||||
s3middleware.Tracing(),
|
|
||||||
s3middleware.Metrics(cfg.Log, cfg.Handler.ResolveCID, cfg.Metrics, cfg.MiddlewareSettings),
|
s3middleware.Metrics(cfg.Log, cfg.Handler.ResolveCID, cfg.Metrics, cfg.MiddlewareSettings),
|
||||||
s3middleware.LogSuccessResponse(cfg.Log),
|
s3middleware.LogSuccessResponse(cfg.Log),
|
||||||
s3middleware.Auth(cfg.Center, cfg.Log),
|
s3middleware.Auth(cfg.Center, cfg.Log),
|
||||||
|
@ -226,6 +225,28 @@ func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func notSupportedHandler() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
reqInfo := s3middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
|
_, wrErr := s3middleware.WriteErrorResponse(w, reqInfo, errors.GetAPIError(errors.ErrNotSupported))
|
||||||
|
|
||||||
|
if log := s3middleware.GetReqLog(ctx); log != nil {
|
||||||
|
fields := []zap.Field{
|
||||||
|
zap.String("http method", r.Method),
|
||||||
|
zap.String("url", r.RequestURI),
|
||||||
|
}
|
||||||
|
|
||||||
|
if wrErr != nil {
|
||||||
|
fields = append(fields, zap.NamedError("write_response_error", wrErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Error(logs.NotSupported, fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// attachErrorHandler set NotFoundHandler and MethodNotAllowedHandler for chi.Router.
|
// attachErrorHandler set NotFoundHandler and MethodNotAllowedHandler for chi.Router.
|
||||||
func attachErrorHandler(api *chi.Mux) {
|
func attachErrorHandler(api *chi.Mux) {
|
||||||
errorHandler := http.HandlerFunc(errorResponseHandler)
|
errorHandler := http.HandlerFunc(errorResponseHandler)
|
||||||
|
@ -313,7 +334,14 @@ func bucketRouter(h Handler) chi.Router {
|
||||||
Add(NewFilter().
|
Add(NewFilter().
|
||||||
Queries(s3middleware.VersionsQuery).
|
Queries(s3middleware.VersionsQuery).
|
||||||
Handler(named(s3middleware.ListBucketObjectVersionsOperation, h.ListBucketObjectVersionsHandler))).
|
Handler(named(s3middleware.ListBucketObjectVersionsOperation, h.ListBucketObjectVersionsHandler))).
|
||||||
DefaultHandler(listWrapper(h)))
|
Add(NewFilter().
|
||||||
|
AllowedQueries(s3middleware.QueryDelimiter, s3middleware.QueryMaxKeys, s3middleware.QueryPrefix,
|
||||||
|
s3middleware.QueryMarker, s3middleware.QueryEncodingType).
|
||||||
|
Handler(named(s3middleware.ListObjectsV1Operation, h.ListObjectsV1Handler))).
|
||||||
|
Add(NewFilter().
|
||||||
|
NoQueries().
|
||||||
|
Handler(listWrapper(h))).
|
||||||
|
DefaultHandler(notSupportedHandler()))
|
||||||
})
|
})
|
||||||
|
|
||||||
// PUT method handlers
|
// PUT method handlers
|
||||||
|
@ -346,7 +374,10 @@ func bucketRouter(h Handler) chi.Router {
|
||||||
Add(NewFilter().
|
Add(NewFilter().
|
||||||
Queries(s3middleware.NotificationQuery).
|
Queries(s3middleware.NotificationQuery).
|
||||||
Handler(named(s3middleware.PutBucketNotificationOperation, h.PutBucketNotificationHandler))).
|
Handler(named(s3middleware.PutBucketNotificationOperation, h.PutBucketNotificationHandler))).
|
||||||
DefaultHandler(named(s3middleware.CreateBucketOperation, h.CreateBucketHandler)))
|
Add(NewFilter().
|
||||||
|
NoQueries().
|
||||||
|
Handler(named(s3middleware.CreateBucketOperation, h.CreateBucketHandler))).
|
||||||
|
DefaultHandler(notSupportedHandler()))
|
||||||
})
|
})
|
||||||
|
|
||||||
// POST method handlers
|
// POST method handlers
|
||||||
|
@ -380,7 +411,10 @@ func bucketRouter(h Handler) chi.Router {
|
||||||
Add(NewFilter().
|
Add(NewFilter().
|
||||||
Queries(s3middleware.EncryptionQuery).
|
Queries(s3middleware.EncryptionQuery).
|
||||||
Handler(named(s3middleware.DeleteBucketEncryptionOperation, h.DeleteBucketEncryptionHandler))).
|
Handler(named(s3middleware.DeleteBucketEncryptionOperation, h.DeleteBucketEncryptionHandler))).
|
||||||
DefaultHandler(named(s3middleware.DeleteBucketOperation, h.DeleteBucketHandler)))
|
Add(NewFilter().
|
||||||
|
NoQueries().
|
||||||
|
Handler(named(s3middleware.DeleteBucketOperation, h.DeleteBucketHandler))).
|
||||||
|
DefaultHandler(notSupportedHandler()))
|
||||||
})
|
})
|
||||||
|
|
||||||
attachErrorHandler(bktRouter)
|
attachErrorHandler(bktRouter)
|
||||||
|
|
|
@ -11,9 +11,11 @@ type HandlerFilters struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
queries []Pair
|
queries []Pair
|
||||||
headers []Pair
|
headers []Pair
|
||||||
h http.Handler
|
allowedQueries map[string]struct{}
|
||||||
|
noQueries bool
|
||||||
|
h http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pair struct {
|
type Pair struct {
|
||||||
|
@ -105,6 +107,22 @@ func (f *Filter) Queries(queries ...string) *Filter {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoQueries sets flag indicating that request shouldn't have query parameters.
|
||||||
|
func (f *Filter) NoQueries() *Filter {
|
||||||
|
f.noQueries = true
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedQueries adds query parameter keys that may be present in request.
|
||||||
|
func (f *Filter) AllowedQueries(queries ...string) *Filter {
|
||||||
|
f.allowedQueries = make(map[string]struct{}, len(queries))
|
||||||
|
for _, query := range queries {
|
||||||
|
f.allowedQueries[query] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
func (hf *HandlerFilters) DefaultHandler(handler http.HandlerFunc) *HandlerFilters {
|
func (hf *HandlerFilters) DefaultHandler(handler http.HandlerFunc) *HandlerFilters {
|
||||||
hf.defaultHandler = handler
|
hf.defaultHandler = handler
|
||||||
return hf
|
return hf
|
||||||
|
@ -122,6 +140,17 @@ func (hf *HandlerFilters) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
func (hf *HandlerFilters) match(r *http.Request) http.Handler {
|
func (hf *HandlerFilters) match(r *http.Request) http.Handler {
|
||||||
LOOP:
|
LOOP:
|
||||||
for _, filter := range hf.filters {
|
for _, filter := range hf.filters {
|
||||||
|
if filter.noQueries && len(r.URL.Query()) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(filter.allowedQueries) > 0 {
|
||||||
|
queries := r.URL.Query()
|
||||||
|
for key := range queries {
|
||||||
|
if _, ok := filter.allowedQueries[key]; !ok {
|
||||||
|
continue LOOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, header := range filter.headers {
|
for _, header := range filter.headers {
|
||||||
hdrVals := r.Header.Values(header.Key)
|
hdrVals := r.Header.Values(header.Key)
|
||||||
if len(hdrVals) == 0 || header.Value != "" && header.Value != hdrVals[0] {
|
if len(hdrVals) == 0 || header.Value != "" && header.Value != hdrVals[0] {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
|
@ -40,8 +40,9 @@ type centerMock struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
anon bool
|
anon bool
|
||||||
noAuthHeader bool
|
noAuthHeader bool
|
||||||
isError bool
|
err error
|
||||||
attrs []object.Attribute
|
attrs []object.Attribute
|
||||||
|
key *keys.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
|
func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
|
||||||
|
@ -49,8 +50,8 @@ func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
|
||||||
return nil, middleware.ErrNoAuthorizationHeader
|
return nil, middleware.ErrNoAuthorizationHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.isError {
|
if c.err != nil {
|
||||||
return nil, fmt.Errorf("some error")
|
return nil, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
var token *bearer.Token
|
var token *bearer.Token
|
||||||
|
@ -58,8 +59,12 @@ func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
|
||||||
if !c.anon {
|
if !c.anon {
|
||||||
bt := bearertest.Token()
|
bt := bearertest.Token()
|
||||||
token = &bt
|
token = &bt
|
||||||
key, err := keys.NewPrivateKey()
|
key := c.key
|
||||||
require.NoError(c.t, err)
|
if key == nil {
|
||||||
|
var err error
|
||||||
|
key, err = keys.NewPrivateKey()
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
}
|
||||||
require.NoError(c.t, token.Sign(key.PrivateKey))
|
require.NoError(c.t, token.Sign(key.PrivateKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,22 +156,21 @@ func (m *xmlMock) NewXMLDecoder(r io.Reader) *xml.Decoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
type resourceTaggingMock struct {
|
type resourceTaggingMock struct {
|
||||||
bucketTags map[string]string
|
bucketTags map[string]string
|
||||||
objectTags map[string]string
|
objectTags map[string]string
|
||||||
noSuchObjectKey bool
|
err error
|
||||||
noSuchBucketKey bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resourceTaggingMock) GetBucketTagging(context.Context, *data.BucketInfo) (map[string]string, error) {
|
func (m *resourceTaggingMock) GetBucketTagging(context.Context, *data.BucketInfo) (map[string]string, error) {
|
||||||
if m.noSuchBucketKey {
|
if m.err != nil {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)
|
return nil, m.err
|
||||||
}
|
}
|
||||||
return m.bucketTags, nil
|
return m.bucketTags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) {
|
func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) {
|
||||||
if m.noSuchObjectKey {
|
if m.err != nil {
|
||||||
return "", nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)
|
return "", nil, m.err
|
||||||
}
|
}
|
||||||
return "", m.objectTags, nil
|
return "", m.objectTags, nil
|
||||||
}
|
}
|
||||||
|
@ -573,7 +577,7 @@ func (h *handlerMock) ResolveBucket(ctx context.Context, name string) (*data.Buc
|
||||||
reqInfo := middleware.GetReqInfo(ctx)
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
bktInfo, ok := h.buckets[reqInfo.Namespace+name]
|
bktInfo, ok := h.buckets[reqInfo.Namespace+name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchBucket)
|
return nil, apierr.GetAPIError(apierr.ErrNoSuchBucket)
|
||||||
}
|
}
|
||||||
return bktInfo, nil
|
return bktInfo, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
@ -26,6 +27,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
|
@ -214,18 +216,18 @@ func TestPolicyChecker(t *testing.T) {
|
||||||
deleteObject(chiRouter, ns2, bktName2, objName2, nil)
|
deleteObject(chiRouter, ns2, bktName2, objName2, nil)
|
||||||
|
|
||||||
// check we cannot access 'bucket' in custom namespace
|
// check we cannot access 'bucket' in custom namespace
|
||||||
putObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied)
|
putObjectErr(chiRouter, ns2, bktName1, objName2, nil, apierr.ErrAccessDenied)
|
||||||
deleteObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied)
|
deleteObjectErr(chiRouter, ns2, bktName1, objName2, nil, apierr.ErrAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyCheckerError(t *testing.T) {
|
func TestPolicyCheckerError(t *testing.T) {
|
||||||
chiRouter := prepareRouter(t)
|
chiRouter := prepareRouter(t)
|
||||||
ns1, bktName1, objName1 := "", "bucket", "object"
|
ns1, bktName1, objName1 := "", "bucket", "object"
|
||||||
putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apiErrors.ErrNoSuchBucket)
|
putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apierr.ErrNoSuchBucket)
|
||||||
|
|
||||||
chiRouter = prepareRouter(t)
|
chiRouter = prepareRouter(t)
|
||||||
chiRouter.cfg.FrostfsID.(*frostFSIDMock).userGroupsError = true
|
chiRouter.cfg.FrostfsID.(*frostFSIDMock).userGroupsError = true
|
||||||
putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apiErrors.ErrInternalError)
|
putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apierr.ErrInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyCheckerReqTypeDetermination(t *testing.T) {
|
func TestPolicyCheckerReqTypeDetermination(t *testing.T) {
|
||||||
|
@ -274,6 +276,36 @@ func TestPolicyCheckerReqTypeDetermination(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPolicyCheckFrostfsErrors(t *testing.T) {
|
||||||
|
chiRouter := prepareRouter(t)
|
||||||
|
ns1, bktName1, objName1 := "", "bucket", "object"
|
||||||
|
|
||||||
|
createBucket(chiRouter, ns1, bktName1)
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
chiRouter.cfg.Center.(*centerMock).key = key
|
||||||
|
chiRouter.cfg.MiddlewareSettings.(*middlewareSettingsMock).denyByDefault = true
|
||||||
|
|
||||||
|
ruleChain := &chain.Chain{
|
||||||
|
ID: chain.ID("id"),
|
||||||
|
Rules: []chain.Rule{{
|
||||||
|
Status: chain.Allow,
|
||||||
|
Actions: chain.Actions{Names: []string{"*"}},
|
||||||
|
Resources: chain.Resources{Names: []string{fmt.Sprintf(s3.ResourceFormatS3BucketObjects, bktName1)}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = chiRouter.policyChecker.MorphRuleChainStorage().AddMorphRuleChain(chain.S3, engine.UserTarget(ns1+":"+key.Address()), ruleChain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// check we can access 'bucket' in default namespace
|
||||||
|
putObject(chiRouter, ns1, bktName1, objName1, nil)
|
||||||
|
|
||||||
|
chiRouter.cfg.Center.(*centerMock).anon = true
|
||||||
|
chiRouter.cfg.Tagging.(*resourceTaggingMock).err = frostfs.ErrAccessDenied
|
||||||
|
getObjectErr(chiRouter, ns1, bktName1, objName1, apierr.ErrAccessDenied)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultBehaviorPolicyChecker(t *testing.T) {
|
func TestDefaultBehaviorPolicyChecker(t *testing.T) {
|
||||||
chiRouter := prepareRouter(t)
|
chiRouter := prepareRouter(t)
|
||||||
ns, bktName := "", "bucket"
|
ns, bktName := "", "bucket"
|
||||||
|
@ -283,7 +315,7 @@ func TestDefaultBehaviorPolicyChecker(t *testing.T) {
|
||||||
|
|
||||||
// check we cannot access if rules not found when settings is enabled
|
// check we cannot access if rules not found when settings is enabled
|
||||||
chiRouter.middlewareSettings.denyByDefault = true
|
chiRouter.middlewareSettings.denyByDefault = true
|
||||||
createBucketErr(chiRouter, ns, bktName, nil, apiErrors.ErrAccessDenied)
|
createBucketErr(chiRouter, ns, bktName, nil, apierr.ErrAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultPolicyCheckerWithUserTags(t *testing.T) {
|
func TestDefaultPolicyCheckerWithUserTags(t *testing.T) {
|
||||||
|
@ -294,7 +326,7 @@ func TestDefaultPolicyCheckerWithUserTags(t *testing.T) {
|
||||||
allowOperations(router, ns, []string{"s3:CreateBucket"}, engineiam.Conditions{
|
allowOperations(router, ns, []string{"s3:CreateBucket"}, engineiam.Conditions{
|
||||||
engineiam.CondStringEquals: engineiam.Condition{fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "tag-test"): []string{"test"}},
|
engineiam.CondStringEquals: engineiam.Condition{fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "tag-test"): []string{"test"}},
|
||||||
})
|
})
|
||||||
createBucketErr(router, ns, bktName, nil, apiErrors.ErrAccessDenied)
|
createBucketErr(router, ns, bktName, nil, apierr.ErrAccessDenied)
|
||||||
|
|
||||||
tags := make(map[string]string)
|
tags := make(map[string]string)
|
||||||
tags["tag-test"] = "test"
|
tags["tag-test"] = "test"
|
||||||
|
@ -321,8 +353,8 @@ func TestRequestParametersCheck(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
listObjectsV1(router, ns, bktName, prefix, "", "")
|
listObjectsV1(router, ns, bktName, prefix, "", "")
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", "", apierr.ErrAccessDenied)
|
||||||
listObjectsV1Err(router, ns, bktName, "invalid", "", "", apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "invalid", "", "", apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("delimiter parameter, prohibit specific value", func(t *testing.T) {
|
t.Run("delimiter parameter, prohibit specific value", func(t *testing.T) {
|
||||||
|
@ -344,7 +376,7 @@ func TestRequestParametersCheck(t *testing.T) {
|
||||||
|
|
||||||
listObjectsV1(router, ns, bktName, "", "", "")
|
listObjectsV1(router, ns, bktName, "", "", "")
|
||||||
listObjectsV1(router, ns, bktName, "", "some-delimiter", "")
|
listObjectsV1(router, ns, bktName, "", "some-delimiter", "")
|
||||||
listObjectsV1Err(router, ns, bktName, "", delimiter, "", apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", delimiter, "", apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("max-keys parameter, allow specific value", func(t *testing.T) {
|
t.Run("max-keys parameter, allow specific value", func(t *testing.T) {
|
||||||
|
@ -365,9 +397,9 @@ func TestRequestParametersCheck(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys))
|
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys))
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", "", apierr.ErrAccessDenied)
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", strconv.Itoa(maxKeys-1), apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", strconv.Itoa(maxKeys-1), apierr.ErrAccessDenied)
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", "invalid", apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", "invalid", apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("max-keys parameter, allow range of values", func(t *testing.T) {
|
t.Run("max-keys parameter, allow range of values", func(t *testing.T) {
|
||||||
|
@ -389,7 +421,7 @@ func TestRequestParametersCheck(t *testing.T) {
|
||||||
|
|
||||||
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys))
|
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys))
|
||||||
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys-1))
|
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys-1))
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", strconv.Itoa(maxKeys+1), apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", strconv.Itoa(maxKeys+1), apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("max-keys parameter, prohibit specific value", func(t *testing.T) {
|
t.Run("max-keys parameter, prohibit specific value", func(t *testing.T) {
|
||||||
|
@ -411,7 +443,7 @@ func TestRequestParametersCheck(t *testing.T) {
|
||||||
|
|
||||||
listObjectsV1(router, ns, bktName, "", "", "")
|
listObjectsV1(router, ns, bktName, "", "", "")
|
||||||
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys-1))
|
listObjectsV1(router, ns, bktName, "", "", strconv.Itoa(maxKeys-1))
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", strconv.Itoa(maxKeys), apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", strconv.Itoa(maxKeys), apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,10 +471,10 @@ func TestRequestTagsCheck(t *testing.T) {
|
||||||
|
|
||||||
tagging, err = xml.Marshal(data.Tagging{TagSet: []data.Tag{{Key: "key", Value: tagValue}}})
|
tagging, err = xml.Marshal(data.Tagging{TagSet: []data.Tag{{Key: "key", Value: tagValue}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrAccessDenied)
|
putBucketTaggingErr(router, ns, bktName, tagging, apierr.ErrAccessDenied)
|
||||||
|
|
||||||
tagging = nil
|
tagging = nil
|
||||||
putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrMalformedXML)
|
putBucketTaggingErr(router, ns, bktName, tagging, apierr.ErrMalformedXML)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("put object with tag", func(t *testing.T) {
|
t.Run("put object with tag", func(t *testing.T) {
|
||||||
|
@ -464,7 +496,7 @@ func TestRequestTagsCheck(t *testing.T) {
|
||||||
|
|
||||||
putObject(router, ns, bktName, objName, &data.Tag{Key: tagKey, Value: tagValue})
|
putObject(router, ns, bktName, objName, &data.Tag{Key: tagKey, Value: tagValue})
|
||||||
|
|
||||||
putObjectErr(router, ns, bktName, objName, &data.Tag{Key: "key", Value: tagValue}, apiErrors.ErrAccessDenied)
|
putObjectErr(router, ns, bktName, objName, &data.Tag{Key: "key", Value: tagValue}, apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,7 +522,7 @@ func TestResourceTagsCheck(t *testing.T) {
|
||||||
listObjectsV1(router, ns, bktName, "", "", "")
|
listObjectsV1(router, ns, bktName, "", "", "")
|
||||||
|
|
||||||
router.cfg.Tagging.(*resourceTaggingMock).bucketTags = map[string]string{}
|
router.cfg.Tagging.(*resourceTaggingMock).bucketTags = map[string]string{}
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", "", apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("object tagging", func(t *testing.T) {
|
t.Run("object tagging", func(t *testing.T) {
|
||||||
|
@ -515,22 +547,21 @@ func TestResourceTagsCheck(t *testing.T) {
|
||||||
getObject(router, ns, bktName, objName)
|
getObject(router, ns, bktName, objName)
|
||||||
|
|
||||||
router.cfg.Tagging.(*resourceTaggingMock).objectTags = map[string]string{}
|
router.cfg.Tagging.(*resourceTaggingMock).objectTags = map[string]string{}
|
||||||
getObjectErr(router, ns, bktName, objName, apiErrors.ErrAccessDenied)
|
getObjectErr(router, ns, bktName, objName, apierr.ErrAccessDenied)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("non-existent resources", func(t *testing.T) {
|
t.Run("non-existent resources", func(t *testing.T) {
|
||||||
router := prepareRouter(t)
|
router := prepareRouter(t)
|
||||||
ns, bktName, objName := "", "bucket", "object"
|
ns, bktName, objName := "", "bucket", "object"
|
||||||
|
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrNoSuchBucket)
|
listObjectsV1Err(router, ns, bktName, "", "", "", apierr.ErrNoSuchBucket)
|
||||||
|
|
||||||
router.cfg.Tagging.(*resourceTaggingMock).noSuchBucketKey = true
|
router.cfg.Tagging.(*resourceTaggingMock).err = apierr.GetAPIError(apierr.ErrNoSuchKey)
|
||||||
createBucket(router, ns, bktName)
|
createBucket(router, ns, bktName)
|
||||||
getBucketErr(router, ns, bktName, apiErrors.ErrNoSuchKey)
|
getBucketErr(router, ns, bktName, apierr.ErrNoSuchKey)
|
||||||
|
|
||||||
router.cfg.Tagging.(*resourceTaggingMock).noSuchObjectKey = true
|
|
||||||
createBucket(router, ns, bktName)
|
createBucket(router, ns, bktName)
|
||||||
getObjectErr(router, ns, bktName, objName, apiErrors.ErrNoSuchKey)
|
getObjectErr(router, ns, bktName, objName, apierr.ErrNoSuchKey)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,7 +579,7 @@ func TestAccessBoxAttributesCheck(t *testing.T) {
|
||||||
engineiam.CondBool: engineiam.Condition{fmt.Sprintf(s3.PropertyKeyFormatAccessBoxAttr, attrKey): []string{attrValue}},
|
engineiam.CondBool: engineiam.Condition{fmt.Sprintf(s3.PropertyKeyFormatAccessBoxAttr, attrKey): []string{attrValue}},
|
||||||
})
|
})
|
||||||
|
|
||||||
listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrAccessDenied)
|
listObjectsV1Err(router, ns, bktName, "", "", "", apierr.ErrAccessDenied)
|
||||||
|
|
||||||
var attr object.Attribute
|
var attr object.Attribute
|
||||||
attr.SetKey(attrKey)
|
attr.SetKey(attrKey)
|
||||||
|
@ -570,7 +601,7 @@ func TestSourceIPCheck(t *testing.T) {
|
||||||
|
|
||||||
router.middlewareSettings.sourceIPHeader = hdr
|
router.middlewareSettings.sourceIPHeader = hdr
|
||||||
header := map[string][]string{hdr: {"192.0.3.0"}}
|
header := map[string][]string{hdr: {"192.0.3.0"}}
|
||||||
createBucketErr(router, ns, bktName, header, apiErrors.ErrAccessDenied)
|
createBucketErr(router, ns, bktName, header, apierr.ErrAccessDenied)
|
||||||
|
|
||||||
router.middlewareSettings.sourceIPHeader = ""
|
router.middlewareSettings.sourceIPHeader = ""
|
||||||
createBucket(router, ns, bktName)
|
createBucket(router, ns, bktName)
|
||||||
|
@ -586,7 +617,7 @@ func TestMFAPolicy(t *testing.T) {
|
||||||
denyOperations(router, ns, []string{"s3:CreateBucket"}, engineiam.Conditions{
|
denyOperations(router, ns, []string{"s3:CreateBucket"}, engineiam.Conditions{
|
||||||
engineiam.CondBool: engineiam.Condition{s3.PropertyKeyAccessBoxAttrMFA: []string{"false"}},
|
engineiam.CondBool: engineiam.Condition{s3.PropertyKeyAccessBoxAttrMFA: []string{"false"}},
|
||||||
})
|
})
|
||||||
createBucketErr(router, ns, bktName, nil, apiErrors.ErrAccessDenied)
|
createBucketErr(router, ns, bktName, nil, apierr.ErrAccessDenied)
|
||||||
|
|
||||||
var attr object.Attribute
|
var attr object.Attribute
|
||||||
attr.SetKey("IAM-MFA")
|
attr.SetKey("IAM-MFA")
|
||||||
|
@ -630,7 +661,7 @@ func createBucket(router *routerMock, namespace, bktName string) {
|
||||||
require.Equal(router.t, s3middleware.CreateBucketOperation, resp.Method)
|
require.Equal(router.t, s3middleware.CreateBucketOperation, resp.Method)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBucketErr(router *routerMock, namespace, bktName string, header http.Header, errCode apiErrors.ErrorCode) {
|
func createBucketErr(router *routerMock, namespace, bktName string, header http.Header, errCode apierr.ErrorCode) {
|
||||||
w := createBucketBase(router, namespace, bktName, header)
|
w := createBucketBase(router, namespace, bktName, header)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
}
|
}
|
||||||
|
@ -646,7 +677,7 @@ func createBucketBase(router *routerMock, namespace, bktName string, header http
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBucketErr(router *routerMock, namespace, bktName string, errCode apiErrors.ErrorCode) {
|
func getBucketErr(router *routerMock, namespace, bktName string, errCode apierr.ErrorCode) {
|
||||||
w := getBucketBase(router, namespace, bktName)
|
w := getBucketBase(router, namespace, bktName)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
}
|
}
|
||||||
|
@ -665,7 +696,7 @@ func putObject(router *routerMock, namespace, bktName, objName string, tag *data
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func putObjectErr(router *routerMock, namespace, bktName, objName string, tag *data.Tag, errCode apiErrors.ErrorCode) {
|
func putObjectErr(router *routerMock, namespace, bktName, objName string, tag *data.Tag, errCode apierr.ErrorCode) {
|
||||||
w := putObjectBase(router, namespace, bktName, objName, tag)
|
w := putObjectBase(router, namespace, bktName, objName, tag)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
}
|
}
|
||||||
|
@ -690,7 +721,7 @@ func deleteObject(router *routerMock, namespace, bktName, objName string, tag *d
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteObjectErr(router *routerMock, namespace, bktName, objName string, tag *data.Tag, errCode apiErrors.ErrorCode) {
|
func deleteObjectErr(router *routerMock, namespace, bktName, objName string, tag *data.Tag, errCode apierr.ErrorCode) {
|
||||||
w := deleteObjectBase(router, namespace, bktName, objName, tag)
|
w := deleteObjectBase(router, namespace, bktName, objName, tag)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
}
|
}
|
||||||
|
@ -715,7 +746,7 @@ func putBucketTagging(router *routerMock, namespace, bktName string, tagging []b
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketTaggingErr(router *routerMock, namespace, bktName string, tagging []byte, errCode apiErrors.ErrorCode) {
|
func putBucketTaggingErr(router *routerMock, namespace, bktName string, tagging []byte, errCode apierr.ErrorCode) {
|
||||||
w := putBucketTaggingBase(router, namespace, bktName, tagging)
|
w := putBucketTaggingBase(router, namespace, bktName, tagging)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
}
|
}
|
||||||
|
@ -738,7 +769,7 @@ func getObject(router *routerMock, namespace, bktName, objName string) handlerRe
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObjectErr(router *routerMock, namespace, bktName, objName string, errCode apiErrors.ErrorCode) {
|
func getObjectErr(router *routerMock, namespace, bktName, objName string, errCode apierr.ErrorCode) {
|
||||||
w := getObjectBase(router, namespace, bktName, objName)
|
w := getObjectBase(router, namespace, bktName, objName)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
}
|
}
|
||||||
|
@ -757,7 +788,7 @@ func listObjectsV1(router *routerMock, namespace, bktName, prefix, delimiter, ma
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func listObjectsV1Err(router *routerMock, namespace, bktName, prefix, delimiter, maxKeys string, errCode apiErrors.ErrorCode) {
|
func listObjectsV1Err(router *routerMock, namespace, bktName, prefix, delimiter, maxKeys string, errCode apierr.ErrorCode) {
|
||||||
w := listObjectsV1Base(router, namespace, bktName, prefix, delimiter, maxKeys)
|
w := listObjectsV1Base(router, namespace, bktName, prefix, delimiter, maxKeys)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
}
|
}
|
||||||
|
@ -826,8 +857,17 @@ func TestAuthenticate(t *testing.T) {
|
||||||
createBucket(chiRouter, "", "bkt-2")
|
createBucket(chiRouter, "", "bkt-2")
|
||||||
|
|
||||||
chiRouter = prepareRouter(t)
|
chiRouter = prepareRouter(t)
|
||||||
chiRouter.cfg.Center.(*centerMock).isError = true
|
chiRouter.cfg.Center.(*centerMock).err = apierr.GetAPIError(apierr.ErrAccessDenied)
|
||||||
createBucketErr(chiRouter, "", "bkt-3", nil, apiErrors.ErrAccessDenied)
|
createBucketErr(chiRouter, "", "bkt-3", nil, apierr.ErrAccessDenied)
|
||||||
|
|
||||||
|
chiRouter.cfg.Center.(*centerMock).err = frostfs.ErrGatewayTimeout
|
||||||
|
createBucketErr(chiRouter, "", "bkt-3", nil, apierr.ErrGatewayTimeout)
|
||||||
|
|
||||||
|
chiRouter.cfg.Center.(*centerMock).err = apierr.GetAPIError(apierr.ErrInternalError)
|
||||||
|
createBucketErr(chiRouter, "", "bkt-3", nil, apierr.ErrAccessDenied)
|
||||||
|
|
||||||
|
chiRouter.cfg.Center.(*centerMock).err = apierr.GetAPIError(apierr.ErrBadRequest)
|
||||||
|
createBucketErr(chiRouter, "", "bkt-3", nil, apierr.ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFrostFSIDValidation(t *testing.T) {
|
func TestFrostFSIDValidation(t *testing.T) {
|
||||||
|
@ -843,7 +883,7 @@ func TestFrostFSIDValidation(t *testing.T) {
|
||||||
// frostFSID validation failed
|
// frostFSID validation failed
|
||||||
chiRouter = prepareRouter(t, frostFSIDValidation(true))
|
chiRouter = prepareRouter(t, frostFSIDValidation(true))
|
||||||
chiRouter.cfg.FrostfsID.(*frostFSIDMock).validateError = true
|
chiRouter.cfg.FrostfsID.(*frostFSIDMock).validateError = true
|
||||||
createBucketErr(chiRouter, "", "bkt-3", nil, apiErrors.ErrInternalError)
|
createBucketErr(chiRouter, "", "bkt-3", nil, apierr.ErrInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterListObjectsV2Domains(t *testing.T) {
|
func TestRouterListObjectsV2Domains(t *testing.T) {
|
||||||
|
@ -882,17 +922,17 @@ func readResponse(t *testing.T, w *httptest.ResponseRecorder) handlerResult {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertAPIError(t *testing.T, w *httptest.ResponseRecorder, expectedErrorCode apiErrors.ErrorCode) {
|
func assertAPIError(t *testing.T, w *httptest.ResponseRecorder, expectedErrorCode apierr.ErrorCode) {
|
||||||
actualErrorResponse := &s3middleware.ErrorResponse{}
|
actualErrorResponse := &s3middleware.ErrorResponse{}
|
||||||
err := xml.NewDecoder(w.Result().Body).Decode(actualErrorResponse)
|
err := xml.NewDecoder(w.Result().Body).Decode(actualErrorResponse)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expectedError := apiErrors.GetAPIError(expectedErrorCode)
|
expectedError := apierr.GetAPIError(expectedErrorCode)
|
||||||
|
|
||||||
require.Equal(t, expectedError.HTTPStatusCode, w.Code)
|
require.Equal(t, expectedError.HTTPStatusCode, w.Code)
|
||||||
require.Equal(t, expectedError.Code, actualErrorResponse.Code)
|
require.Equal(t, expectedError.Code, actualErrorResponse.Code)
|
||||||
|
|
||||||
if expectedError.ErrCode != apiErrors.ErrInternalError {
|
if expectedError.ErrCode != apierr.ErrInternalError {
|
||||||
require.Contains(t, actualErrorResponse.Message, expectedError.Description)
|
require.Contains(t, actualErrorResponse.Message, expectedError.Description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,9 @@ type (
|
||||||
|
|
||||||
// IssueSecretOptions contains options for passing to Agent.IssueSecret method.
|
// IssueSecretOptions contains options for passing to Agent.IssueSecret method.
|
||||||
IssueSecretOptions struct {
|
IssueSecretOptions struct {
|
||||||
Container ContainerOptions
|
Container cid.ID
|
||||||
|
AccessKeyID string
|
||||||
|
SecretAccessKey string
|
||||||
FrostFSKey *keys.PrivateKey
|
FrostFSKey *keys.PrivateKey
|
||||||
GatesPublicKeys []*keys.PublicKey
|
GatesPublicKeys []*keys.PublicKey
|
||||||
Impersonate bool
|
Impersonate bool
|
||||||
|
@ -114,7 +116,9 @@ type (
|
||||||
UpdateSecretOptions struct {
|
UpdateSecretOptions struct {
|
||||||
FrostFSKey *keys.PrivateKey
|
FrostFSKey *keys.PrivateKey
|
||||||
GatesPublicKeys []*keys.PublicKey
|
GatesPublicKeys []*keys.PublicKey
|
||||||
Address oid.Address
|
IsCustom bool
|
||||||
|
AccessKeyID string
|
||||||
|
ContainerID cid.ID
|
||||||
GatePrivateKey *keys.PrivateKey
|
GatePrivateKey *keys.PrivateKey
|
||||||
CustomAttributes []object.Attribute
|
CustomAttributes []object.Attribute
|
||||||
}
|
}
|
||||||
|
@ -141,7 +145,8 @@ type (
|
||||||
|
|
||||||
// ObtainSecretOptions contains options for passing to Agent.ObtainSecret method.
|
// ObtainSecretOptions contains options for passing to Agent.ObtainSecret method.
|
||||||
ObtainSecretOptions struct {
|
ObtainSecretOptions struct {
|
||||||
SecretAddress string
|
Container cid.ID
|
||||||
|
AccessKeyID string
|
||||||
GatePrivateKey *keys.PrivateKey
|
GatePrivateKey *keys.PrivateKey
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -168,32 +173,9 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner user.ID) (cid.ID, error) {
|
func (a *Agent) checkContainer(ctx context.Context, cnrID cid.ID) error {
|
||||||
if !opts.ID.Equals(cid.ID{}) {
|
a.log.Info(logs.CheckContainer, zap.Stringer("cid", cnrID))
|
||||||
a.log.Info(logs.CheckContainer, zap.Stringer("cid", opts.ID))
|
return a.frostFS.ContainerExists(ctx, cnrID)
|
||||||
return opts.ID, a.frostFS.ContainerExists(ctx, opts.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.log.Info(logs.CreateContainer,
|
|
||||||
zap.String("friendly_name", opts.FriendlyName),
|
|
||||||
zap.String("placement_policy", opts.PlacementPolicy))
|
|
||||||
|
|
||||||
var prm PrmContainerCreate
|
|
||||||
|
|
||||||
err := prm.Policy.DecodeString(opts.PlacementPolicy)
|
|
||||||
if err != nil {
|
|
||||||
return cid.ID{}, fmt.Errorf("failed to build placement policy: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prm.Owner = idOwner
|
|
||||||
prm.FriendlyName = opts.FriendlyName
|
|
||||||
|
|
||||||
cnrID, err := a.frostFS.CreateContainer(ctx, prm)
|
|
||||||
if err != nil {
|
|
||||||
return cid.ID{}, fmt.Errorf("create container in FrostFS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cnrID, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPolicy(policyString string) (*netmap.PlacementPolicy, error) {
|
func checkPolicy(policyString string) (*netmap.PlacementPolicy, error) {
|
||||||
|
@ -255,20 +237,24 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
||||||
return fmt.Errorf("create tokens: %w", err)
|
return fmt.Errorf("create tokens: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
box, secrets, err := accessbox.PackTokens(gatesData, nil)
|
var secret []byte
|
||||||
|
isCustom := options.AccessKeyID != ""
|
||||||
|
if isCustom {
|
||||||
|
secret = []byte(options.SecretAccessKey)
|
||||||
|
}
|
||||||
|
box, secrets, err := accessbox.PackTokens(gatesData, secret, isCustom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pack tokens: %w", err)
|
return fmt.Errorf("pack tokens: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
box.ContainerPolicy = policies
|
box.ContainerPolicy = policies
|
||||||
|
|
||||||
var idOwner user.ID
|
if err = a.checkContainer(ctx, options.Container); err != nil {
|
||||||
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
|
||||||
id, err := a.checkContainer(ctx, options.Container, idOwner)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("check container: %w", err)
|
return fmt.Errorf("check container: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var idOwner user.ID
|
||||||
|
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
||||||
a.log.Info(logs.StoreBearerTokenIntoFrostFS,
|
a.log.Info(logs.StoreBearerTokenIntoFrostFS,
|
||||||
zap.Stringer("owner_tkn", idOwner))
|
zap.Stringer("owner_tkn", idOwner))
|
||||||
|
|
||||||
|
@ -281,26 +267,31 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
||||||
creds := tokens.New(cfg)
|
creds := tokens.New(cfg)
|
||||||
|
|
||||||
prm := tokens.CredentialsParam{
|
prm := tokens.CredentialsParam{
|
||||||
OwnerID: idOwner,
|
Container: options.Container,
|
||||||
|
AccessKeyID: options.AccessKeyID,
|
||||||
AccessBox: box,
|
AccessBox: box,
|
||||||
Expiration: lifetime.Exp,
|
Expiration: lifetime.Exp,
|
||||||
Keys: options.GatesPublicKeys,
|
Keys: options.GatesPublicKeys,
|
||||||
CustomAttributes: options.CustomAttributes,
|
CustomAttributes: options.CustomAttributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := creds.Put(ctx, id, prm)
|
addr, err := creds.Put(ctx, prm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to put creds: %w", err)
|
return fmt.Errorf("failed to put creds: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
accessKeyID := accessKeyIDFromAddr(addr)
|
accessKeyID := options.AccessKeyID
|
||||||
|
if accessKeyID == "" {
|
||||||
|
accessKeyID = accessKeyIDFromAddr(addr)
|
||||||
|
}
|
||||||
|
|
||||||
ir := &issuingResult{
|
ir := &issuingResult{
|
||||||
InitialAccessKeyID: accessKeyID,
|
InitialAccessKeyID: accessKeyID,
|
||||||
AccessKeyID: accessKeyID,
|
AccessKeyID: accessKeyID,
|
||||||
SecretAccessKey: secrets.SecretKey,
|
SecretAccessKey: secrets.SecretKey,
|
||||||
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
||||||
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
||||||
ContainerID: id.EncodeToString(),
|
ContainerID: options.Container.EncodeToString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
|
@ -337,13 +328,15 @@ func (a *Agent) UpdateSecret(ctx context.Context, w io.Writer, options *UpdateSe
|
||||||
|
|
||||||
creds := tokens.New(cfg)
|
creds := tokens.New(cfg)
|
||||||
|
|
||||||
box, _, err := creds.GetBox(ctx, options.Address)
|
box, _, err := creds.GetBox(ctx, options.ContainerID, options.AccessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get accessbox: %w", err)
|
return fmt.Errorf("get accessbox: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := hex.DecodeString(box.Gate.SecretKey)
|
var secret []byte
|
||||||
if err != nil {
|
if options.IsCustom {
|
||||||
|
secret = []byte(box.Gate.SecretKey)
|
||||||
|
} else if secret, err = hex.DecodeString(box.Gate.SecretKey); err != nil {
|
||||||
return fmt.Errorf("failed to decode secret key access box: %w", err)
|
return fmt.Errorf("failed to decode secret key access box: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +353,7 @@ func (a *Agent) UpdateSecret(ctx context.Context, w io.Writer, options *UpdateSe
|
||||||
return fmt.Errorf("create tokens: %w", err)
|
return fmt.Errorf("create tokens: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedBox, secrets, err := accessbox.PackTokens(gatesData, secret)
|
updatedBox, secrets, err := accessbox.PackTokens(gatesData, secret, options.IsCustom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("pack tokens: %w", err)
|
return fmt.Errorf("pack tokens: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -371,22 +364,26 @@ func (a *Agent) UpdateSecret(ctx context.Context, w io.Writer, options *UpdateSe
|
||||||
zap.Stringer("owner_tkn", idOwner))
|
zap.Stringer("owner_tkn", idOwner))
|
||||||
|
|
||||||
prm := tokens.CredentialsParam{
|
prm := tokens.CredentialsParam{
|
||||||
OwnerID: idOwner,
|
Container: options.ContainerID,
|
||||||
AccessBox: updatedBox,
|
AccessBox: updatedBox,
|
||||||
Expiration: lifetime.Exp,
|
Expiration: lifetime.Exp,
|
||||||
Keys: options.GatesPublicKeys,
|
Keys: options.GatesPublicKeys,
|
||||||
CustomAttributes: options.CustomAttributes,
|
CustomAttributes: options.CustomAttributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
oldAddr := options.Address
|
addr, err := creds.Update(ctx, prm)
|
||||||
addr, err := creds.Update(ctx, oldAddr, prm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update creds: %w", err)
|
return fmt.Errorf("failed to update creds: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessKeyID := options.AccessKeyID
|
||||||
|
if !options.IsCustom {
|
||||||
|
accessKeyID = accessKeyIDFromAddr(addr)
|
||||||
|
}
|
||||||
|
|
||||||
ir := &issuingResult{
|
ir := &issuingResult{
|
||||||
AccessKeyID: accessKeyIDFromAddr(addr),
|
AccessKeyID: accessKeyID,
|
||||||
InitialAccessKeyID: accessKeyIDFromAddr(oldAddr),
|
InitialAccessKeyID: options.AccessKeyID,
|
||||||
SecretAccessKey: secrets.SecretKey,
|
SecretAccessKey: secrets.SecretKey,
|
||||||
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
||||||
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
||||||
|
@ -419,12 +416,7 @@ func (a *Agent) ObtainSecret(ctx context.Context, w io.Writer, options *ObtainSe
|
||||||
|
|
||||||
bearerCreds := tokens.New(cfg)
|
bearerCreds := tokens.New(cfg)
|
||||||
|
|
||||||
var addr oid.Address
|
box, _, err := bearerCreds.GetBox(ctx, options.Container, options.AccessKeyID)
|
||||||
if err := addr.DecodeString(options.SecretAddress); err != nil {
|
|
||||||
return fmt.Errorf("failed to parse secret address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
box, _, err := bearerCreds.GetBox(ctx, addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get tokens: %w", err)
|
return fmt.Errorf("failed to get tokens: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
|
@ -21,7 +22,7 @@ var generatePresignedURLCmd = &cobra.Command{
|
||||||
You provide profile to load using --profile flag or explicitly provide credentials and region using
|
You provide profile to load using --profile flag or explicitly provide credentials and region using
|
||||||
--aws-access-key-id, --aws-secret-access-key, --region.
|
--aws-access-key-id, --aws-secret-access-key, --region.
|
||||||
Note to override credentials you must provide both access key and secret key.`,
|
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`,
|
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,
|
RunE: runGeneratePresignedURLCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ const (
|
||||||
regionFlag = "region"
|
regionFlag = "region"
|
||||||
awsAccessKeyIDFlag = "aws-access-key-id"
|
awsAccessKeyIDFlag = "aws-access-key-id"
|
||||||
awsSecretAccessKeyFlag = "aws-secret-access-key"
|
awsSecretAccessKeyFlag = "aws-secret-access-key"
|
||||||
|
headerFlag = "header"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initGeneratePresignedURLCmd() {
|
func initGeneratePresignedURLCmd() {
|
||||||
|
@ -48,6 +50,7 @@ func initGeneratePresignedURLCmd() {
|
||||||
generatePresignedURLCmd.Flags().String(regionFlag, "", "AWS region to use in signature (default is taken from ~/.aws/config)")
|
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(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.MarkFlagRequired(endpointFlag)
|
_ = generatePresignedURLCmd.MarkFlagRequired(endpointFlag)
|
||||||
_ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag)
|
_ = generatePresignedURLCmd.MarkFlagRequired(bucketFlag)
|
||||||
|
@ -92,6 +95,12 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error {
|
||||||
SignTime: time.Now().UTC(),
|
SignTime: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers, err := parseHeaders()
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(fmt.Errorf("failed to parse headers: %s", err))
|
||||||
|
}
|
||||||
|
presignData.Headers = headers
|
||||||
|
|
||||||
req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData)
|
req, err := auth.PresignRequest(sess.Config.Credentials, reqData, presignData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapBusinessLogicError(err)
|
return wrapBusinessLogicError(err)
|
||||||
|
@ -110,3 +119,21 @@ func runGeneratePresignedURLCmd(*cobra.Command, []string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,22 +4,34 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
"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/frostfs/util"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
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/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var issueSecretCmd = &cobra.Command{
|
var issueSecretCmd = &cobra.Command{
|
||||||
Use: "issue-secret",
|
Use: "issue-secret",
|
||||||
Short: "Issue a secret in FrostFS network",
|
Short: "Issue a secret in FrostFS network",
|
||||||
Long: "Creates new s3 credentials to use with frostfs-s3-gw",
|
Long: "Creates new s3 credentials to use with frostfs-s3-gw",
|
||||||
Example: `frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a
|
Example: `To create new s3 credentials use:
|
||||||
frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a --attributes LOGIN=NUUb82KR2JrVByHs2YSKgtK29gKnF5q6Vt`,
|
frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a
|
||||||
|
frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a --attributes LOGIN=NUUb82KR2JrVByHs2YSKgtK29gKnF5q6Vt
|
||||||
|
|
||||||
|
To create new s3 credentials using specific access key id and secret access key use:
|
||||||
|
frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a --access-key-id my-access-key-id --secret-access-key my-secret-key --container-id BpExV76416Vo7GrkJsGwXGoLM35xsBwup8voedDZR3c6
|
||||||
|
`,
|
||||||
RunE: runIssueSecretCmd,
|
RunE: runIssueSecretCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +66,9 @@ const (
|
||||||
poolHealthcheckTimeoutFlag = "pool-healthcheck-timeout"
|
poolHealthcheckTimeoutFlag = "pool-healthcheck-timeout"
|
||||||
poolRebalanceIntervalFlag = "pool-rebalance-interval"
|
poolRebalanceIntervalFlag = "pool-rebalance-interval"
|
||||||
poolStreamTimeoutFlag = "pool-stream-timeout"
|
poolStreamTimeoutFlag = "pool-stream-timeout"
|
||||||
|
|
||||||
|
accessKeyIDFlag = "access-key-id"
|
||||||
|
secretAccessKeyFlag = "secret-access-key"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initIssueSecretCmd() {
|
func initIssueSecretCmd() {
|
||||||
|
@ -73,6 +88,9 @@ func initIssueSecretCmd() {
|
||||||
issueSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status")
|
issueSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status")
|
||||||
issueSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC")
|
issueSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC")
|
||||||
issueSecretCmd.Flags().String(attributesFlag, "", "User attributes in form of Key1=Value1,Key2=Value2 (note: you cannot override system attributes)")
|
issueSecretCmd.Flags().String(attributesFlag, "", "User attributes in form of Key1=Value1,Key2=Value2 (note: you cannot override system attributes)")
|
||||||
|
issueSecretCmd.Flags().String(accessKeyIDFlag, "", "Access key id of s3 credential that must be created")
|
||||||
|
issueSecretCmd.Flags().String(secretAccessKeyFlag, "", "Secret access key of s3 credential that must be used")
|
||||||
|
issueSecretCmd.Flags().String(rpcEndpointFlag, "", "NEO node RPC address (must be provided if container-id is nns name)")
|
||||||
|
|
||||||
_ = issueSecretCmd.MarkFlagRequired(walletFlag)
|
_ = issueSecretCmd.MarkFlagRequired(walletFlag)
|
||||||
_ = issueSecretCmd.MarkFlagRequired(peerFlag)
|
_ = issueSecretCmd.MarkFlagRequired(peerFlag)
|
||||||
|
@ -91,14 +109,6 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
return wrapPreparationError(fmt.Errorf("failed to load frostfs private key: %s", err))
|
return wrapPreparationError(fmt.Errorf("failed to load frostfs private key: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
var cnrID cid.ID
|
|
||||||
containerID := viper.GetString(containerIDFlag)
|
|
||||||
if len(containerID) > 0 {
|
|
||||||
if err = cnrID.DecodeString(containerID); err != nil {
|
|
||||||
return wrapPreparationError(fmt.Errorf("failed to parse auth container id: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var gatesPublicKeys []*keys.PublicKey
|
var gatesPublicKeys []*keys.PublicKey
|
||||||
for _, keyStr := range viper.GetStringSlice(gatePublicKeyFlag) {
|
for _, keyStr := range viper.GetStringSlice(gatePublicKeyFlag) {
|
||||||
gpk, err := keys.NewPublicKeyFromString(keyStr)
|
gpk, err := keys.NewPublicKeyFromString(keyStr)
|
||||||
|
@ -137,17 +147,29 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
return wrapFrostFSInitError(fmt.Errorf("failed to create FrostFS component: %s", err))
|
return wrapFrostFSInitError(fmt.Errorf("failed to create FrostFS component: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accessBox cid.ID
|
||||||
|
if viper.IsSet(containerIDFlag) {
|
||||||
|
if accessBox, err = util.ResolveContainerID(viper.GetString(containerIDFlag), viper.GetString(rpcEndpointFlag)); err != nil {
|
||||||
|
return wrapPreparationError(fmt.Errorf("resolve accessbox container id (make sure you provided %s): %w", rpcEndpointFlag, err))
|
||||||
|
}
|
||||||
|
} else if accessBox, err = createAccessBox(ctx, frostFS, key, log); err != nil {
|
||||||
|
return wrapPreparationError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessKeyID, secretAccessKey, err := parseAccessKeys()
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(err)
|
||||||
|
}
|
||||||
|
|
||||||
customAttrs, err := parseObjectAttrs(viper.GetString(attributesFlag))
|
customAttrs, err := parseObjectAttrs(viper.GetString(attributesFlag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapPreparationError(fmt.Errorf("failed to parse attributes: %s", err))
|
return wrapPreparationError(fmt.Errorf("failed to parse attributes: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
issueSecretOptions := &authmate.IssueSecretOptions{
|
issueSecretOptions := &authmate.IssueSecretOptions{
|
||||||
Container: authmate.ContainerOptions{
|
Container: accessBox,
|
||||||
ID: cnrID,
|
AccessKeyID: accessKeyID,
|
||||||
FriendlyName: viper.GetString(containerFriendlyNameFlag),
|
SecretAccessKey: secretAccessKey,
|
||||||
PlacementPolicy: viper.GetString(containerPlacementPolicyFlag),
|
|
||||||
},
|
|
||||||
FrostFSKey: key,
|
FrostFSKey: key,
|
||||||
GatesPublicKeys: gatesPublicKeys,
|
GatesPublicKeys: gatesPublicKeys,
|
||||||
Impersonate: true,
|
Impersonate: true,
|
||||||
|
@ -164,3 +186,59 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseAccessKeys() (accessKeyID, secretAccessKey string, err error) {
|
||||||
|
accessKeyID = viper.GetString(accessKeyIDFlag)
|
||||||
|
secretAccessKey = viper.GetString(secretAccessKeyFlag)
|
||||||
|
|
||||||
|
if accessKeyID == "" && secretAccessKey != "" || accessKeyID != "" && secretAccessKey == "" {
|
||||||
|
return "", "", fmt.Errorf("flags %s and %s must be both provided or not", accessKeyIDFlag, secretAccessKeyFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if accessKeyID != "" {
|
||||||
|
if !isCustomCreds(accessKeyID) {
|
||||||
|
return "", "", fmt.Errorf("invalid custom AccessKeyID format: %s", accessKeyID)
|
||||||
|
}
|
||||||
|
if !checkAccessKeyLength(accessKeyID) {
|
||||||
|
return "", "", fmt.Errorf("invalid custom AccessKeyID length: %s", accessKeyID)
|
||||||
|
}
|
||||||
|
if !checkAccessKeyLength(secretAccessKey) {
|
||||||
|
return "", "", fmt.Errorf("invalid custom SecretAccessKey length: %s", secretAccessKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessKeyID, secretAccessKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCustomCreds(accessKeyID string) bool {
|
||||||
|
var addr oid.Address
|
||||||
|
return addr.DecodeString(strings.ReplaceAll(accessKeyID, "0", "/")) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAccessKeyLength(key string) bool {
|
||||||
|
return 4 <= len(key) && len(key) <= 128
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAccessBox(ctx context.Context, frostFS *frostfs.AuthmateFrostFS, key *keys.PrivateKey, log *zap.Logger) (cid.ID, error) {
|
||||||
|
friendlyName := viper.GetString(containerFriendlyNameFlag)
|
||||||
|
placementPolicy := viper.GetString(containerPlacementPolicyFlag)
|
||||||
|
|
||||||
|
log.Info(logs.CreateContainer, zap.String("friendly_name", friendlyName), zap.String("placement_policy", placementPolicy))
|
||||||
|
|
||||||
|
prm := authmate.PrmContainerCreate{
|
||||||
|
FriendlyName: friendlyName,
|
||||||
|
}
|
||||||
|
|
||||||
|
user.IDFromKey(&prm.Owner, key.PrivateKey.PublicKey)
|
||||||
|
|
||||||
|
if err := prm.Policy.DecodeString(placementPolicy); err != nil {
|
||||||
|
return cid.ID{}, fmt.Errorf("failed to build placement policy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessBox, err := frostFS.CreateContainer(ctx, prm)
|
||||||
|
if err != nil {
|
||||||
|
return cid.ID{}, fmt.Errorf("create container in FrostFS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessBox, nil
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||||
|
@ -24,7 +23,6 @@ var obtainSecretCmd = &cobra.Command{
|
||||||
const (
|
const (
|
||||||
gateWalletFlag = "gate-wallet"
|
gateWalletFlag = "gate-wallet"
|
||||||
gateAddressFlag = "gate-address"
|
gateAddressFlag = "gate-address"
|
||||||
accessKeyIDFlag = "access-key-id"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -38,10 +36,12 @@ func initObtainSecretCmd() {
|
||||||
obtainSecretCmd.Flags().String(gateWalletFlag, "", "Path to the s3 gateway wallet to decrypt accessbox")
|
obtainSecretCmd.Flags().String(gateWalletFlag, "", "Path to the s3 gateway wallet to decrypt accessbox")
|
||||||
obtainSecretCmd.Flags().String(gateAddressFlag, "", "Address of the s3 gateway wallet account")
|
obtainSecretCmd.Flags().String(gateAddressFlag, "", "Address of the s3 gateway wallet account")
|
||||||
obtainSecretCmd.Flags().String(accessKeyIDFlag, "", "Access key id of s3 credential for which secret must be obtained")
|
obtainSecretCmd.Flags().String(accessKeyIDFlag, "", "Access key id of s3 credential for which secret must be obtained")
|
||||||
|
obtainSecretCmd.Flags().String(containerIDFlag, "", "CID or NNS name of auth container that contains provided credential (must be provided if custom access key id is used)")
|
||||||
obtainSecretCmd.Flags().Duration(poolDialTimeoutFlag, defaultPoolDialTimeout, "Timeout for connection to the node in pool to be established")
|
obtainSecretCmd.Flags().Duration(poolDialTimeoutFlag, defaultPoolDialTimeout, "Timeout for connection to the node in pool to be established")
|
||||||
obtainSecretCmd.Flags().Duration(poolHealthcheckTimeoutFlag, defaultPoolHealthcheckTimeout, "Timeout for request to node to decide if it is alive")
|
obtainSecretCmd.Flags().Duration(poolHealthcheckTimeoutFlag, defaultPoolHealthcheckTimeout, "Timeout for request to node to decide if it is alive")
|
||||||
obtainSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status")
|
obtainSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status")
|
||||||
obtainSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC")
|
obtainSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC")
|
||||||
|
obtainSecretCmd.Flags().String(rpcEndpointFlag, "", "NEO node RPC address (must be provided if container-id is nns name)")
|
||||||
|
|
||||||
_ = obtainSecretCmd.MarkFlagRequired(walletFlag)
|
_ = obtainSecretCmd.MarkFlagRequired(walletFlag)
|
||||||
_ = obtainSecretCmd.MarkFlagRequired(peerFlag)
|
_ = obtainSecretCmd.MarkFlagRequired(peerFlag)
|
||||||
|
@ -81,8 +81,14 @@ func runObtainSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
return wrapFrostFSInitError(cli.Exit(fmt.Sprintf("failed to create FrostFS component: %s", err), 2))
|
return wrapFrostFSInitError(cli.Exit(fmt.Sprintf("failed to create FrostFS component: %s", err), 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessBox, accessKeyID, _, err := getAccessBoxID()
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(err)
|
||||||
|
}
|
||||||
|
|
||||||
obtainSecretOptions := &authmate.ObtainSecretOptions{
|
obtainSecretOptions := &authmate.ObtainSecretOptions{
|
||||||
SecretAddress: strings.Replace(viper.GetString(accessKeyIDFlag), "0", "/", 1),
|
Container: accessBox,
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
GatePrivateKey: gateKey,
|
GatePrivateKey: gateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
||||||
ffsidContract "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid/contract"
|
ffsidContract "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid/contract"
|
||||||
policyContact "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy/contract"
|
policyContact "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy/contract"
|
||||||
|
@ -116,6 +117,10 @@ func initFrostFSIDContract(ctx context.Context, log *zap.Logger, key *keys.Priva
|
||||||
Contract: viper.GetString(frostfsIDContractFlag),
|
Contract: viper.GetString(frostfsIDContractFlag),
|
||||||
ProxyContract: viper.GetString(proxyContractFlag),
|
ProxyContract: viper.GetString(proxyContractFlag),
|
||||||
Key: key,
|
Key: key,
|
||||||
|
Waiter: commonclient.WaiterOptions{
|
||||||
|
IgnoreAlreadyExistsError: false,
|
||||||
|
VerifyExecResults: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cli, err := ffsidContract.New(ctx, cfg)
|
cli, err := ffsidContract.New(ctx, cfg)
|
||||||
|
|
|
@ -68,4 +68,7 @@ GoVersion: {{ runtimeVersion }}
|
||||||
|
|
||||||
rootCmd.AddCommand(registerUserCmd)
|
rootCmd.AddCommand(registerUserCmd)
|
||||||
initRegisterUserCmd()
|
initRegisterUserCmd()
|
||||||
|
|
||||||
|
rootCmd.AddCommand(signCmd)
|
||||||
|
initSignCmd()
|
||||||
}
|
}
|
||||||
|
|
115
cmd/s3-authmate/modules/sign.go
Normal file
115
cmd/s3-authmate/modules/sign.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var signCmd = &cobra.Command{
|
||||||
|
Use: "sign",
|
||||||
|
Short: "Sign arbitrary data using AWS Signature Version 4",
|
||||||
|
Long: `Generate signature for provided data using AWS credentials. Credentials must be placed in ~/.aws/credentials.
|
||||||
|
You can 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 sign --data some-data
|
||||||
|
frostfs-s3-authmate sign --data file://data.txt
|
||||||
|
frostfs-s3-authmate sign --data file://data.txt --profile my-profile --time 2024-09-27
|
||||||
|
frostfs-s3-authmate sign --data some-data --region ru --service s3 --time 2024-09-27 --aws-access-key-id ETaA2CadPcA7bAkLsML2PbTudXY8uRt2PDjCCwkvRv9s0FDCxWDXYc1SA1vKv8KbyCNsLY2AmAjJ92Vz5rgvsFCy --aws-secret-access-key c2d65ef2980f03f4f495bdebedeeae760496697880d61d106bb9a4e5cd2e0607`,
|
||||||
|
RunE: runSignCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
serviceFlag = "s3"
|
||||||
|
timeFlag = "time"
|
||||||
|
dataFlag = "data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initSignCmd() {
|
||||||
|
signCmd.Flags().StringP(dataFlag, "d", "", "Data to sign. Can be provided as string or as a file ('file://path-to-file')")
|
||||||
|
signCmd.Flags().String(profileFlag, "", "AWS profile to load")
|
||||||
|
signCmd.Flags().String(serviceFlag, "s3", "AWS service name to form signature")
|
||||||
|
signCmd.Flags().String(timeFlag, "", "Signing time in '2006-01-02' format (default is current UTC time)")
|
||||||
|
signCmd.Flags().String(regionFlag, "", "AWS region to use in signature (default is taken from ~/.aws/config)")
|
||||||
|
signCmd.Flags().String(awsAccessKeyIDFlag, "", "AWS access key id to sign data (default is taken from ~/.aws/credentials)")
|
||||||
|
signCmd.Flags().String(awsSecretAccessKeyFlag, "", "AWS secret access key to sign data (default is taken from ~/.aws/credentials)")
|
||||||
|
|
||||||
|
_ = signCmd.MarkFlagRequired(dataFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSignCmd(cmd *cobra.Command, _ []string) error {
|
||||||
|
var cfg aws.Config
|
||||||
|
|
||||||
|
if region := viper.GetString(regionFlag); region != "" {
|
||||||
|
cfg.Region = ®ion
|
||||||
|
}
|
||||||
|
accessKeyID := viper.GetString(awsAccessKeyIDFlag)
|
||||||
|
secretAccessKey := viper.GetString(awsSecretAccessKeyFlag)
|
||||||
|
|
||||||
|
if accessKeyID != "" && secretAccessKey != "" {
|
||||||
|
cfg.Credentials = credentials.NewStaticCredentialsFromCreds(credentials.Value{
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
SecretAccessKey: secretAccessKey,
|
||||||
|
})
|
||||||
|
} else if accessKeyID != "" || secretAccessKey != "" {
|
||||||
|
return wrapPreparationError(fmt.Errorf("both flags '%s' and '%s' must be provided", accessKeyIDFlag, awsSecretAccessKeyFlag))
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, err := session.NewSessionWithOptions(session.Options{
|
||||||
|
Config: cfg,
|
||||||
|
Profile: viper.GetString(profileFlag),
|
||||||
|
SharedConfigState: session.SharedConfigEnable,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(fmt.Errorf("couldn't get aws credentials: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
data := viper.GetString(dataFlag)
|
||||||
|
if strings.HasPrefix(data, "file://") {
|
||||||
|
dataToSign, err := os.ReadFile(data[7:])
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(fmt.Errorf("read data file: %w", err))
|
||||||
|
}
|
||||||
|
data = string(dataToSign)
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := sess.Config.Credentials.Get()
|
||||||
|
if err != nil {
|
||||||
|
return wrapPreparationError(fmt.Errorf("get creds: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if sess.Config.Region == nil || *sess.Config.Region == "" {
|
||||||
|
return wrapPreparationError(errors.New("missing region"))
|
||||||
|
}
|
||||||
|
|
||||||
|
service := viper.GetString(serviceFlag)
|
||||||
|
if service == "" {
|
||||||
|
return wrapPreparationError(errors.New("missing service"))
|
||||||
|
}
|
||||||
|
|
||||||
|
signTime := viper.GetTime(timeFlag)
|
||||||
|
if signTime.IsZero() {
|
||||||
|
signTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := auth.SignStr(creds.SecretAccessKey, service, *sess.Config.Region, signTime, data)
|
||||||
|
|
||||||
|
cmd.Println("service:", service)
|
||||||
|
cmd.Println("region:", *sess.Config.Region)
|
||||||
|
cmd.Println("time:", signTime.UTC().Format("20060102"))
|
||||||
|
cmd.Println("accessKeyId:", creds.AccessKeyID)
|
||||||
|
cmd.Printf("secretAccessKey: [****************%s]\n", creds.SecretAccessKey[max(0, len(creds.SecretAccessKey)-4):])
|
||||||
|
cmd.Println("signature:", signature)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -4,11 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -33,13 +31,15 @@ func initUpdateSecretCmd() {
|
||||||
updateSecretCmd.Flags().String(peerFlag, "", "Address of a frostfs peer to connect to")
|
updateSecretCmd.Flags().String(peerFlag, "", "Address of a frostfs peer to connect to")
|
||||||
updateSecretCmd.Flags().String(gateWalletFlag, "", "Path to the s3 gateway wallet to decrypt accessbox")
|
updateSecretCmd.Flags().String(gateWalletFlag, "", "Path to the s3 gateway wallet to decrypt accessbox")
|
||||||
updateSecretCmd.Flags().String(gateAddressFlag, "", "Address of the s3 gateway wallet account")
|
updateSecretCmd.Flags().String(gateAddressFlag, "", "Address of the s3 gateway wallet account")
|
||||||
updateSecretCmd.Flags().String(accessKeyIDFlag, "", "Access key id of s3 credential for which secret must be obtained")
|
updateSecretCmd.Flags().String(accessKeyIDFlag, "", "Access key id of s3 credential for which secret must be updatedd")
|
||||||
|
updateSecretCmd.Flags().String(containerIDFlag, "", "CID or NNS name of auth container that contains provided credential (must be provided if custom access key id is used)")
|
||||||
updateSecretCmd.Flags().StringSlice(gatePublicKeyFlag, nil, "Public 256r1 key of a gate (use flags repeatedly for multiple gates or separate them by comma)")
|
updateSecretCmd.Flags().StringSlice(gatePublicKeyFlag, nil, "Public 256r1 key of a gate (use flags repeatedly for multiple gates or separate them by comma)")
|
||||||
updateSecretCmd.Flags().Duration(poolDialTimeoutFlag, defaultPoolDialTimeout, "Timeout for connection to the node in pool to be established")
|
updateSecretCmd.Flags().Duration(poolDialTimeoutFlag, defaultPoolDialTimeout, "Timeout for connection to the node in pool to be established")
|
||||||
updateSecretCmd.Flags().Duration(poolHealthcheckTimeoutFlag, defaultPoolHealthcheckTimeout, "Timeout for request to node to decide if it is alive")
|
updateSecretCmd.Flags().Duration(poolHealthcheckTimeoutFlag, defaultPoolHealthcheckTimeout, "Timeout for request to node to decide if it is alive")
|
||||||
updateSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status")
|
updateSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status")
|
||||||
updateSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC")
|
updateSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC")
|
||||||
updateSecretCmd.Flags().String(attributesFlag, "", "User attributes in form of Key1=Value1,Key2=Value2 (note: you cannot override system attributes)")
|
updateSecretCmd.Flags().String(attributesFlag, "", "User attributes in form of Key1=Value1,Key2=Value2 (note: you cannot override system attributes)")
|
||||||
|
updateSecretCmd.Flags().String(rpcEndpointFlag, "", "NEO node RPC address (must be provided if container-id is nns name)")
|
||||||
|
|
||||||
_ = updateSecretCmd.MarkFlagRequired(walletFlag)
|
_ = updateSecretCmd.MarkFlagRequired(walletFlag)
|
||||||
_ = updateSecretCmd.MarkFlagRequired(peerFlag)
|
_ = updateSecretCmd.MarkFlagRequired(peerFlag)
|
||||||
|
@ -66,10 +66,9 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
return wrapPreparationError(fmt.Errorf("failed to load s3 gate private key: %s", err))
|
return wrapPreparationError(fmt.Errorf("failed to load s3 gate private key: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
var accessBoxAddress oid.Address
|
accessBox, accessKeyID, isCustom, err := getAccessBoxID()
|
||||||
credAddr := strings.Replace(viper.GetString(accessKeyIDFlag), "0", "/", 1)
|
if err != nil {
|
||||||
if err = accessBoxAddress.DecodeString(credAddr); err != nil {
|
return wrapPreparationError(err)
|
||||||
return wrapPreparationError(fmt.Errorf("failed to parse creds address: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var gatesPublicKeys []*keys.PublicKey
|
var gatesPublicKeys []*keys.PublicKey
|
||||||
|
@ -101,7 +100,9 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSecretOptions := &authmate.UpdateSecretOptions{
|
updateSecretOptions := &authmate.UpdateSecretOptions{
|
||||||
Address: accessBoxAddress,
|
ContainerID: accessBox,
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
IsCustom: isCustom,
|
||||||
FrostFSKey: key,
|
FrostFSKey: key,
|
||||||
GatesPublicKeys: gatesPublicKeys,
|
GatesPublicKeys: gatesPublicKeys,
|
||||||
GatePrivateKey: gateKey,
|
GatePrivateKey: gateKey,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package modules
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -11,8 +12,11 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
"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/frostfs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"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"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -163,3 +167,23 @@ func parseObjectAttrs(attributes string) ([]object.Attribute, error) {
|
||||||
|
|
||||||
return attrs, nil
|
return attrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAccessBoxID() (cid.ID, string, bool, error) {
|
||||||
|
accessKeyID := viper.GetString(accessKeyIDFlag)
|
||||||
|
|
||||||
|
var accessBoxAddress oid.Address
|
||||||
|
if err := accessBoxAddress.DecodeString(strings.Replace(accessKeyID, "0", "/", 1)); err == nil {
|
||||||
|
return accessBoxAddress.Container(), accessKeyID, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !viper.IsSet(containerIDFlag) {
|
||||||
|
return cid.ID{}, "", false, errors.New("accessbox parameter must be set when custom access key id is used")
|
||||||
|
}
|
||||||
|
|
||||||
|
accessBox, err := util.ResolveContainerID(viper.GetString(containerIDFlag), viper.GetString(rpcEndpointFlag))
|
||||||
|
if err != nil {
|
||||||
|
return cid.ID{}, "", false, fmt.Errorf("resolve accessbox container id (make sure you provided %s): %w", rpcEndpointFlag, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessBox, accessKeyID, true, nil
|
||||||
|
}
|
||||||
|
|
107
cmd/s3-gw/app.go
107
cmd/s3-gw/app.go
|
@ -18,6 +18,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||||
grpctracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
grpctracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
|
@ -36,6 +37,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy/contract"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy/contract"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/services"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/services"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
internalnet "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/net"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
|
||||||
|
@ -95,6 +97,8 @@ type (
|
||||||
resolveZoneList []string
|
resolveZoneList []string
|
||||||
isResolveListAllow bool // True if ResolveZoneList contains allowed zones
|
isResolveListAllow bool // True if ResolveZoneList contains allowed zones
|
||||||
frostfsidValidation bool
|
frostfsidValidation bool
|
||||||
|
accessbox *cid.ID
|
||||||
|
dialerSource *internalnet.DialerSource
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
namespaces Namespaces
|
namespaces Namespaces
|
||||||
|
@ -129,20 +133,11 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
|
func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
|
||||||
objPool, treePool, key := getPools(ctx, log.logger, v)
|
settings := newAppSettings(log, v)
|
||||||
|
|
||||||
cfg := tokens.Config{
|
objPool, treePool, key := getPools(ctx, log.logger, v, settings.dialerSource)
|
||||||
FrostFS: frostfs.NewAuthmateFrostFS(frostfs.NewFrostFS(objPool, key), log.logger),
|
|
||||||
Key: key,
|
|
||||||
CacheConfig: getAccessBoxCacheConfig(v, log.logger),
|
|
||||||
RemovingCheckAfterDurations: fetchRemovingCheckInterval(v, log.logger),
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare auth center
|
|
||||||
ctr := auth.New(tokens.New(cfg), v.GetStringSlice(cfgAllowedAccessKeyIDPrefixes))
|
|
||||||
|
|
||||||
app := &App{
|
app := &App{
|
||||||
ctr: ctr,
|
|
||||||
log: log.logger,
|
log: log.logger,
|
||||||
cfg: v,
|
cfg: v,
|
||||||
pool: objPool,
|
pool: objPool,
|
||||||
|
@ -152,7 +147,7 @@ func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
|
||||||
webDone: make(chan struct{}, 1),
|
webDone: make(chan struct{}, 1),
|
||||||
wrkDone: make(chan struct{}, 1),
|
wrkDone: make(chan struct{}, 1),
|
||||||
|
|
||||||
settings: newAppSettings(log, v),
|
settings: settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
app.init(ctx)
|
app.init(ctx)
|
||||||
|
@ -161,6 +156,8 @@ func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) init(ctx context.Context) {
|
func (a *App) init(ctx context.Context) {
|
||||||
|
a.initResolver()
|
||||||
|
a.initAuthCenter(ctx)
|
||||||
a.setRuntimeParameters()
|
a.setRuntimeParameters()
|
||||||
a.initFrostfsID(ctx)
|
a.initFrostfsID(ctx)
|
||||||
a.initPolicyStorage(ctx)
|
a.initPolicyStorage(ctx)
|
||||||
|
@ -170,9 +167,26 @@ func (a *App) init(ctx context.Context) {
|
||||||
a.initTracing(ctx)
|
a.initTracing(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initLayer(ctx context.Context) {
|
func (a *App) initAuthCenter(ctx context.Context) {
|
||||||
a.initResolver()
|
if a.cfg.IsSet(cfgContainersAccessBox) {
|
||||||
|
cnrID, err := a.resolveContainerID(ctx, cfgContainersAccessBox)
|
||||||
|
if err != nil {
|
||||||
|
a.log.Fatal(logs.CouldNotFetchAccessBoxContainerInfo, zap.Error(err))
|
||||||
|
}
|
||||||
|
a.settings.accessbox = &cnrID
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := tokens.Config{
|
||||||
|
FrostFS: frostfs.NewAuthmateFrostFS(frostfs.NewFrostFS(a.pool, a.key), a.log),
|
||||||
|
Key: a.key,
|
||||||
|
CacheConfig: getAccessBoxCacheConfig(a.cfg, a.log),
|
||||||
|
RemovingCheckAfterDurations: fetchRemovingCheckInterval(a.cfg, a.log),
|
||||||
|
}
|
||||||
|
|
||||||
|
a.ctr = auth.New(tokens.New(cfg), a.cfg.GetStringSlice(cfgAllowedAccessKeyIDPrefixes), a.settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) initLayer(ctx context.Context) {
|
||||||
// prepare random key for anonymous requests
|
// prepare random key for anonymous requests
|
||||||
randomKey, err := keys.NewPrivateKey()
|
randomKey, err := keys.NewPrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -224,6 +238,7 @@ func newAppSettings(log *Logger, v *viper.Viper) *appSettings {
|
||||||
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
|
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
|
||||||
reconnectInterval: fetchReconnectInterval(v),
|
reconnectInterval: fetchReconnectInterval(v),
|
||||||
frostfsidValidation: v.GetBool(cfgFrostfsIDValidationEnabled),
|
frostfsidValidation: v.GetBool(cfgFrostfsIDValidationEnabled),
|
||||||
|
dialerSource: getDialerSource(log.logger, v),
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.resolveZoneList = v.GetStringSlice(cfgResolveBucketAllow)
|
settings.resolveZoneList = v.GetStringSlice(cfgResolveBucketAllow)
|
||||||
|
@ -483,6 +498,14 @@ func (s *appSettings) RetryStrategy() handler.RetryStrategy {
|
||||||
return s.retryStrategy
|
return s.retryStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *appSettings) AccessBoxContainer() (cid.ID, bool) {
|
||||||
|
if s.accessbox != nil {
|
||||||
|
return *s.accessbox, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return cid.ID{}, false
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) initAPI(ctx context.Context) {
|
func (a *App) initAPI(ctx context.Context) {
|
||||||
a.initLayer(ctx)
|
a.initLayer(ctx)
|
||||||
a.initHandler()
|
a.initHandler()
|
||||||
|
@ -506,6 +529,10 @@ func (a *App) initFrostfsID(ctx context.Context) {
|
||||||
Contract: a.cfg.GetString(cfgFrostfsIDContract),
|
Contract: a.cfg.GetString(cfgFrostfsIDContract),
|
||||||
ProxyContract: a.cfg.GetString(cfgProxyContract),
|
ProxyContract: a.cfg.GetString(cfgProxyContract),
|
||||||
Key: a.key,
|
Key: a.key,
|
||||||
|
Waiter: commonclient.WaiterOptions{
|
||||||
|
IgnoreAlreadyExistsError: false,
|
||||||
|
VerifyExecResults: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.Fatal(logs.InitFrostfsIDContractFailed, zap.Error(err))
|
a.log.Fatal(logs.InitFrostfsIDContractFailed, zap.Error(err))
|
||||||
|
@ -527,6 +554,10 @@ func (a *App) initPolicyStorage(ctx context.Context) {
|
||||||
Contract: a.cfg.GetString(cfgPolicyContract),
|
Contract: a.cfg.GetString(cfgPolicyContract),
|
||||||
ProxyContract: a.cfg.GetString(cfgProxyContract),
|
ProxyContract: a.cfg.GetString(cfgProxyContract),
|
||||||
Key: a.key,
|
Key: a.key,
|
||||||
|
Waiter: commonclient.WaiterOptions{
|
||||||
|
IgnoreAlreadyExistsError: false,
|
||||||
|
VerifyExecResults: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.Fatal(logs.InitPolicyContractFailed, zap.Error(err))
|
a.log.Fatal(logs.InitPolicyContractFailed, zap.Error(err))
|
||||||
|
@ -626,7 +657,15 @@ func newMaxClients(cfg *viper.Viper) maxClientsConfig {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPools(ctx context.Context, logger *zap.Logger, cfg *viper.Viper) (*pool.Pool, *treepool.Pool, *keys.PrivateKey) {
|
func getDialerSource(logger *zap.Logger, cfg *viper.Viper) *internalnet.DialerSource {
|
||||||
|
source, err := internalnet.NewDialerSource(fetchMultinetConfig(cfg, logger))
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(logs.FailedToLoadMultinetConfig, zap.Error(err))
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPools(ctx context.Context, logger *zap.Logger, cfg *viper.Viper, dialSource *internalnet.DialerSource) (*pool.Pool, *treepool.Pool, *keys.PrivateKey) {
|
||||||
var prm pool.InitParameters
|
var prm pool.InitParameters
|
||||||
var prmTree treepool.InitParameters
|
var prmTree treepool.InitParameters
|
||||||
|
|
||||||
|
@ -671,18 +710,13 @@ func getPools(ctx context.Context, logger *zap.Logger, cfg *viper.Viper) (*pool.
|
||||||
|
|
||||||
prmTree.SetMaxRequestAttempts(cfg.GetInt(cfgTreePoolMaxAttempts))
|
prmTree.SetMaxRequestAttempts(cfg.GetInt(cfgTreePoolMaxAttempts))
|
||||||
|
|
||||||
var apiGRPCDialOpts []grpc.DialOption
|
interceptors := []grpc.DialOption{
|
||||||
var treeGRPCDialOpts []grpc.DialOption
|
grpc.WithUnaryInterceptor(grpctracing.NewUnaryClientInteceptor()),
|
||||||
if cfg.GetBool(cfgTracingEnabled) {
|
grpc.WithStreamInterceptor(grpctracing.NewStreamClientInterceptor()),
|
||||||
interceptors := []grpc.DialOption{
|
grpc.WithContextDialer(dialSource.GrpcContextDialer()),
|
||||||
grpc.WithUnaryInterceptor(grpctracing.NewUnaryClientInteceptor()),
|
|
||||||
grpc.WithStreamInterceptor(grpctracing.NewStreamClientInterceptor()),
|
|
||||||
}
|
|
||||||
treeGRPCDialOpts = append(treeGRPCDialOpts, interceptors...)
|
|
||||||
apiGRPCDialOpts = append(apiGRPCDialOpts, interceptors...)
|
|
||||||
}
|
}
|
||||||
prm.SetGRPCDialOptions(apiGRPCDialOpts...)
|
prm.SetGRPCDialOptions(interceptors...)
|
||||||
prmTree.SetGRPCDialOptions(treeGRPCDialOpts...)
|
prmTree.SetGRPCDialOptions(interceptors...)
|
||||||
|
|
||||||
p, err := pool.NewPool(prm)
|
p, err := pool.NewPool(prm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -858,6 +892,10 @@ func (a *App) updateSettings() {
|
||||||
a.settings.logLevel.SetLevel(lvl)
|
a.settings.logLevel.SetLevel(lvl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := a.settings.dialerSource.Update(fetchMultinetConfig(a.cfg, a.log)); err != nil {
|
||||||
|
a.log.Warn(logs.MultinetConfigWontBeUpdated, zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
a.settings.update(a.cfg, a.log)
|
a.settings.update(a.cfg, a.log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1101,21 +1139,30 @@ func (a *App) tryReconnect(ctx context.Context, sr *http.Server) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) fetchContainerInfo(ctx context.Context, cfgKey string) (info *data.BucketInfo, err error) {
|
func (a *App) fetchContainerInfo(ctx context.Context, cfgKey string) (info *data.BucketInfo, err error) {
|
||||||
|
cnrID, err := a.resolveContainerID(ctx, cfgKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getContainerInfo(ctx, cnrID, a.pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) resolveContainerID(ctx context.Context, cfgKey string) (cid.ID, error) {
|
||||||
containerString := a.cfg.GetString(cfgKey)
|
containerString := a.cfg.GetString(cfgKey)
|
||||||
|
|
||||||
var id cid.ID
|
var id cid.ID
|
||||||
if err = id.DecodeString(containerString); err != nil {
|
if err := id.DecodeString(containerString); err != nil {
|
||||||
i := strings.Index(containerString, ".")
|
i := strings.Index(containerString, ".")
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return nil, fmt.Errorf("invalid container address: %s", containerString)
|
return cid.ID{}, fmt.Errorf("invalid container address: %s", containerString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id, err = a.bucketResolver.Resolve(ctx, containerString[i+1:], containerString[:i]); err != nil {
|
if id, err = a.bucketResolver.Resolve(ctx, containerString[i+1:], containerString[:i]); err != nil {
|
||||||
return nil, fmt.Errorf("resolve container address %s: %w", containerString, err)
|
return cid.ID{}, fmt.Errorf("resolve container address %s: %w", containerString, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return getContainerInfo(ctx, id, a.pool)
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainerInfo(ctx context.Context, id cid.ID, frostFSPool *pool.Pool) (*data.BucketInfo, error) {
|
func getContainerInfo(ctx context.Context, id cid.ID, frostFSPool *pool.Pool) (*data.BucketInfo, error) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
internalnet "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/net"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||||
|
@ -41,6 +42,8 @@ const (
|
||||||
defaultStreamTimeout = 10 * time.Second
|
defaultStreamTimeout = 10 * time.Second
|
||||||
defaultShutdownTimeout = 15 * time.Second
|
defaultShutdownTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
defaultLoggerSamplerInterval = 1 * time.Second
|
||||||
|
|
||||||
defaultGracefulCloseOnSwitchTimeout = 10 * time.Second
|
defaultGracefulCloseOnSwitchTimeout = 10 * time.Second
|
||||||
|
|
||||||
defaultPoolErrorThreshold uint32 = 100
|
defaultPoolErrorThreshold uint32 = 100
|
||||||
|
@ -60,6 +63,8 @@ const (
|
||||||
defaultVHSHeader = "X-Frostfs-S3-VHS"
|
defaultVHSHeader = "X-Frostfs-S3-VHS"
|
||||||
defaultServernameHeader = "X-Frostfs-Servername"
|
defaultServernameHeader = "X-Frostfs-Servername"
|
||||||
|
|
||||||
|
defaultMultinetFallbackDelay = 300 * time.Millisecond
|
||||||
|
|
||||||
defaultConstraintName = "default"
|
defaultConstraintName = "default"
|
||||||
|
|
||||||
defaultNamespace = ""
|
defaultNamespace = ""
|
||||||
|
@ -81,6 +86,11 @@ const ( // Settings.
|
||||||
cfgLoggerLevel = "logger.level"
|
cfgLoggerLevel = "logger.level"
|
||||||
cfgLoggerDestination = "logger.destination"
|
cfgLoggerDestination = "logger.destination"
|
||||||
|
|
||||||
|
cfgLoggerSamplingEnabled = "logger.sampling.enabled"
|
||||||
|
cfgLoggerSamplingInitial = "logger.sampling.initial"
|
||||||
|
cfgLoggerSamplingThereafter = "logger.sampling.thereafter"
|
||||||
|
cfgLoggerSamplingInterval = "logger.sampling.interval"
|
||||||
|
|
||||||
// HttpLogging.
|
// HttpLogging.
|
||||||
cfgHTTPLoggingEnabled = "http_logging.enabled"
|
cfgHTTPLoggingEnabled = "http_logging.enabled"
|
||||||
cfgHTTPLoggingMaxBody = "http_logging.max_body"
|
cfgHTTPLoggingMaxBody = "http_logging.max_body"
|
||||||
|
@ -201,6 +211,14 @@ const ( // Settings.
|
||||||
// Containers.
|
// Containers.
|
||||||
cfgContainersCORS = "containers.cors"
|
cfgContainersCORS = "containers.cors"
|
||||||
cfgContainersLifecycle = "containers.lifecycle"
|
cfgContainersLifecycle = "containers.lifecycle"
|
||||||
|
cfgContainersAccessBox = "containers.accessbox"
|
||||||
|
|
||||||
|
// Multinet.
|
||||||
|
cfgMultinetEnabled = "multinet.enabled"
|
||||||
|
cfgMultinetBalancer = "multinet.balancer"
|
||||||
|
cfgMultinetRestrict = "multinet.restrict"
|
||||||
|
cfgMultinetFallbackDelay = "multinet.fallback_delay"
|
||||||
|
cfgMultinetSubnets = "multinet.subnets"
|
||||||
|
|
||||||
// Command line args.
|
// Command line args.
|
||||||
cmdHelp = "help"
|
cmdHelp = "help"
|
||||||
|
@ -738,6 +756,29 @@ func fetchVHSNamespaces(v *viper.Viper, log *zap.Logger) map[string]bool {
|
||||||
return vhsNamespacesEnabled
|
return vhsNamespacesEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchMultinetConfig(v *viper.Viper, logger *zap.Logger) (cfg internalnet.Config) {
|
||||||
|
cfg.Enabled = v.GetBool(cfgMultinetEnabled)
|
||||||
|
cfg.Balancer = v.GetString(cfgMultinetBalancer)
|
||||||
|
cfg.Restrict = v.GetBool(cfgMultinetRestrict)
|
||||||
|
cfg.FallbackDelay = v.GetDuration(cfgMultinetFallbackDelay)
|
||||||
|
cfg.Subnets = make([]internalnet.Subnet, 0, 5)
|
||||||
|
cfg.EventHandler = internalnet.NewLogEventHandler(logger)
|
||||||
|
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
key := cfgMultinetSubnets + "." + strconv.Itoa(i) + "."
|
||||||
|
subnet := internalnet.Subnet{}
|
||||||
|
|
||||||
|
subnet.Prefix = v.GetString(key + "mask")
|
||||||
|
if subnet.Prefix == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
subnet.SourceIPs = v.GetStringSlice(key + "source_ips")
|
||||||
|
cfg.Subnets = append(cfg.Subnets, subnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func newSettings() *viper.Viper {
|
func newSettings() *viper.Viper {
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
|
|
||||||
|
@ -788,6 +829,10 @@ func newSettings() *viper.Viper {
|
||||||
// logger:
|
// logger:
|
||||||
v.SetDefault(cfgLoggerLevel, "debug")
|
v.SetDefault(cfgLoggerLevel, "debug")
|
||||||
v.SetDefault(cfgLoggerDestination, "stdout")
|
v.SetDefault(cfgLoggerDestination, "stdout")
|
||||||
|
v.SetDefault(cfgLoggerSamplingEnabled, false)
|
||||||
|
v.SetDefault(cfgLoggerSamplingThereafter, 100)
|
||||||
|
v.SetDefault(cfgLoggerSamplingInitial, 100)
|
||||||
|
v.SetDefault(cfgLoggerSamplingInterval, defaultLoggerSamplerInterval)
|
||||||
|
|
||||||
// http logger
|
// http logger
|
||||||
v.SetDefault(cfgHTTPLoggingEnabled, false)
|
v.SetDefault(cfgHTTPLoggingEnabled, false)
|
||||||
|
@ -836,6 +881,9 @@ func newSettings() *viper.Viper {
|
||||||
v.SetDefault(cfgVHSHeader, defaultVHSHeader)
|
v.SetDefault(cfgVHSHeader, defaultVHSHeader)
|
||||||
v.SetDefault(cfgServernameHeader, defaultServernameHeader)
|
v.SetDefault(cfgServernameHeader, defaultServernameHeader)
|
||||||
|
|
||||||
|
// multinet
|
||||||
|
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
|
||||||
|
|
||||||
// Bind flags
|
// Bind flags
|
||||||
if err := bindFlags(v, flags); err != nil {
|
if err := bindFlags(v, flags); err != nil {
|
||||||
panic(fmt.Errorf("bind flags: %w", err))
|
panic(fmt.Errorf("bind flags: %w", err))
|
||||||
|
@ -1031,9 +1079,9 @@ func pickLogger(v *viper.Viper) *Logger {
|
||||||
|
|
||||||
switch dest {
|
switch dest {
|
||||||
case destinationStdout:
|
case destinationStdout:
|
||||||
return newStdoutLogger(lvl)
|
return newStdoutLogger(v, lvl)
|
||||||
case destinationJournald:
|
case destinationJournald:
|
||||||
return newJournaldLogger(lvl)
|
return newJournaldLogger(v, lvl)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("wrong destination for logger: %s", dest))
|
panic(fmt.Sprintf("wrong destination for logger: %s", dest))
|
||||||
}
|
}
|
||||||
|
@ -1046,53 +1094,74 @@ func pickLogger(v *viper.Viper) *Logger {
|
||||||
// - parameterized level (debug by default)
|
// - parameterized level (debug by default)
|
||||||
// - console encoding
|
// - console encoding
|
||||||
// - ISO8601 time encoding
|
// - ISO8601 time encoding
|
||||||
|
// - sampling intervals
|
||||||
//
|
//
|
||||||
// and atomic log level to dynamically change it.
|
// and atomic log level to dynamically change it.
|
||||||
//
|
//
|
||||||
// Logger records a stack trace for all messages at or above fatal level.
|
// Logger records a stack trace for all messages at or above fatal level.
|
||||||
//
|
//
|
||||||
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
|
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
|
||||||
func newStdoutLogger(lvl zapcore.Level) *Logger {
|
func newStdoutLogger(v *viper.Viper, lvl zapcore.Level) *Logger {
|
||||||
c := zap.NewProductionConfig()
|
stdout := zapcore.AddSync(os.Stderr)
|
||||||
c.Level = zap.NewAtomicLevelAt(lvl)
|
level := zap.NewAtomicLevelAt(lvl)
|
||||||
c.Encoding = "console"
|
|
||||||
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
||||||
|
|
||||||
l, err := c.Build(
|
consoleOutCore := zapcore.NewCore(newLogEncoder(), stdout, level)
|
||||||
zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)),
|
|
||||||
)
|
consoleOutCore = samplingEnabling(v, consoleOutCore)
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("build zap logger instance: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Logger{
|
return &Logger{
|
||||||
logger: l,
|
logger: zap.New(consoleOutCore, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel))),
|
||||||
lvl: c.Level,
|
lvl: level,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newJournaldLogger(lvl zapcore.Level) *Logger {
|
func newJournaldLogger(v *viper.Viper, lvl zapcore.Level) *Logger {
|
||||||
c := zap.NewProductionConfig()
|
level := zap.NewAtomicLevelAt(lvl)
|
||||||
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
||||||
c.Level = zap.NewAtomicLevelAt(lvl)
|
|
||||||
|
|
||||||
encoder := zapjournald.NewPartialEncoder(zapcore.NewConsoleEncoder(c.EncoderConfig), zapjournald.SyslogFields)
|
encoder := zapjournald.NewPartialEncoder(newLogEncoder(), zapjournald.SyslogFields)
|
||||||
|
|
||||||
core := zapjournald.NewCore(c.Level, encoder, &journald.Journal{}, zapjournald.SyslogFields)
|
core := zapjournald.NewCore(level, encoder, &journald.Journal{}, zapjournald.SyslogFields)
|
||||||
coreWithContext := core.With([]zapcore.Field{
|
coreWithContext := core.With([]zapcore.Field{
|
||||||
zapjournald.SyslogFacility(zapjournald.LogDaemon),
|
zapjournald.SyslogFacility(zapjournald.LogDaemon),
|
||||||
zapjournald.SyslogIdentifier(),
|
zapjournald.SyslogIdentifier(),
|
||||||
zapjournald.SyslogPid(),
|
zapjournald.SyslogPid(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
coreWithContext = samplingEnabling(v, coreWithContext)
|
||||||
|
|
||||||
l := zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)))
|
l := zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)))
|
||||||
|
|
||||||
return &Logger{
|
return &Logger{
|
||||||
logger: l,
|
logger: l,
|
||||||
lvl: c.Level,
|
lvl: level,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newLogEncoder() zapcore.Encoder {
|
||||||
|
c := zap.NewProductionEncoderConfig()
|
||||||
|
c.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||||
|
|
||||||
|
return zapcore.NewConsoleEncoder(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func samplingEnabling(v *viper.Viper, core zapcore.Core) zapcore.Core {
|
||||||
|
// Zap samples by logging the first cgfLoggerSamplingInitial entries with a given level
|
||||||
|
// and message within the specified time interval.
|
||||||
|
// In the above config, only the first cgfLoggerSamplingInitial log entries with the same level and message
|
||||||
|
// are recorded in cfgLoggerSamplingInterval interval. Every other log entry will be dropped within the interval since
|
||||||
|
// cfgLoggerSamplingThereafter is specified here.
|
||||||
|
if v.GetBool(cfgLoggerSamplingEnabled) {
|
||||||
|
core = zapcore.NewSamplerWithOptions(
|
||||||
|
core,
|
||||||
|
v.GetDuration(cfgLoggerSamplingInterval),
|
||||||
|
v.GetInt(cfgLoggerSamplingInitial),
|
||||||
|
v.GetInt(cfgLoggerSamplingThereafter),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return core
|
||||||
|
}
|
||||||
|
|
||||||
func getLogLevel(v *viper.Viper) (zapcore.Level, error) {
|
func getLogLevel(v *viper.Viper) (zapcore.Level, error) {
|
||||||
var lvl zapcore.Level
|
var lvl zapcore.Level
|
||||||
lvlStr := v.GetString(cfgLoggerLevel)
|
lvlStr := v.GetString(cfgLoggerLevel)
|
||||||
|
|
|
@ -51,6 +51,10 @@ S3_GW_CONFIG=/path/to/config/yaml
|
||||||
|
|
||||||
# Logger
|
# Logger
|
||||||
S3_GW_LOGGER_LEVEL=debug
|
S3_GW_LOGGER_LEVEL=debug
|
||||||
|
S3_GW_LOGGER_SAMPLING_ENABLED=false
|
||||||
|
S3_GW_LOGGER_SAMPLING_INITIAL=100
|
||||||
|
S3_GW_LOGGER_SAMPLING_THEREAFTER=100
|
||||||
|
S3_GW_LOGGER_SAMPLING_INTERVAL=1s
|
||||||
|
|
||||||
# HTTP logger
|
# HTTP logger
|
||||||
S3_GW_HTTP_LOGGING_ENABLED=false
|
S3_GW_HTTP_LOGGING_ENABLED=false
|
||||||
|
@ -242,3 +246,16 @@ S3_GW_RETRY_STRATEGY=exponential
|
||||||
# Containers properties
|
# Containers properties
|
||||||
S3_GW_CONTAINERS_CORS=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
S3_GW_CONTAINERS_CORS=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
S3_GW_CONTAINERS_LIFECYCLE=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
S3_GW_CONTAINERS_LIFECYCLE=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
|
|
||||||
|
# Multinet properties
|
||||||
|
# Enable multinet support
|
||||||
|
S3_GW_MULTINET_ENABLED=false
|
||||||
|
# Strategy to pick source IP address
|
||||||
|
S3_GW_MULTINET_BALANCER=roundrobin
|
||||||
|
# Restrict requests with unknown destination subnet
|
||||||
|
S3_GW_MULTINET_RESTRICT=false
|
||||||
|
# Delay between ipv6 to ipv4 fallback switch
|
||||||
|
S3_GW_MULTINET_FALLBACK_DELAY=300ms
|
||||||
|
# List of subnets and IP addresses to use as source for those subnets
|
||||||
|
S3_GW_MULTINET_SUBNETS_1_MASK=1.2.3.4/24
|
||||||
|
S3_GW_MULTINET_SUBNETS_1_SOURCE_IPS=1.2.3.4 1.2.3.5
|
||||||
|
|
|
@ -55,6 +55,11 @@ vhs:
|
||||||
logger:
|
logger:
|
||||||
level: debug
|
level: debug
|
||||||
destination: stdout
|
destination: stdout
|
||||||
|
sampling:
|
||||||
|
enabled: false
|
||||||
|
initial: 100
|
||||||
|
thereafter: 100
|
||||||
|
interval: 1s
|
||||||
|
|
||||||
# log http request data (URI, headers, query, etc)
|
# log http request data (URI, headers, query, etc)
|
||||||
http_logging:
|
http_logging:
|
||||||
|
@ -284,3 +289,20 @@ retry:
|
||||||
containers:
|
containers:
|
||||||
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
lifecycle: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
lifecycle: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
|
|
||||||
|
# Multinet properties
|
||||||
|
multinet:
|
||||||
|
# Enable multinet support
|
||||||
|
enabled: false
|
||||||
|
# Strategy to pick source IP address
|
||||||
|
balancer: roundrobin
|
||||||
|
# Restrict requests with unknown destination subnet
|
||||||
|
restrict: false
|
||||||
|
# Delay between ipv6 to ipv4 fallback switch
|
||||||
|
fallback_delay: 300ms
|
||||||
|
# List of subnets and IP addresses to use as source for those subnets
|
||||||
|
subnets:
|
||||||
|
- mask: 1.2.3.4/24
|
||||||
|
source_ips:
|
||||||
|
- 1.2.3.4
|
||||||
|
- 1.2.3.5
|
||||||
|
|
|
@ -99,13 +99,14 @@ func (x *AccessBox) Unmarshal(data []byte) error {
|
||||||
// PackTokens adds bearer and session tokens to BearerTokens and SessionToken lists respectively.
|
// PackTokens adds bearer and session tokens to BearerTokens and SessionToken lists respectively.
|
||||||
// Session token can be nil.
|
// Session token can be nil.
|
||||||
// Secret can be nil. In such case secret will be generated.
|
// Secret can be nil. In such case secret will be generated.
|
||||||
func PackTokens(gatesData []*GateData, secret []byte) (*AccessBox, *Secrets, error) {
|
func PackTokens(gatesData []*GateData, secret []byte, isCustomSecret bool) (*AccessBox, *Secrets, error) {
|
||||||
box := &AccessBox{}
|
box := &AccessBox{}
|
||||||
ephemeralKey, err := keys.NewPrivateKey()
|
ephemeralKey, err := keys.NewPrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("create ephemeral key: %w", err)
|
return nil, nil, fmt.Errorf("create ephemeral key: %w", err)
|
||||||
}
|
}
|
||||||
box.SeedKey = ephemeralKey.PublicKey().Bytes()
|
box.SeedKey = ephemeralKey.PublicKey().Bytes()
|
||||||
|
box.IsCustom = isCustomSecret
|
||||||
|
|
||||||
if secret == nil {
|
if secret == nil {
|
||||||
secret, err = generateSecret()
|
secret, err = generateSecret()
|
||||||
|
@ -118,7 +119,12 @@ func PackTokens(gatesData []*GateData, secret []byte) (*AccessBox, *Secrets, err
|
||||||
return nil, nil, fmt.Errorf("failed to add tokens to accessbox: %w", err)
|
return nil, nil, fmt.Errorf("failed to add tokens to accessbox: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return box, &Secrets{hex.EncodeToString(secret), ephemeralKey}, err
|
secretKey := string(secret)
|
||||||
|
if !isCustomSecret {
|
||||||
|
secretKey = hex.EncodeToString(secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
return box, &Secrets{SecretKey: secretKey, EphemeralKey: ephemeralKey}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTokens returns gate tokens from AccessBox.
|
// GetTokens returns gate tokens from AccessBox.
|
||||||
|
@ -133,7 +139,7 @@ func (x *AccessBox) GetTokens(owner *keys.PrivateKey) (*GateData, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
gateData, err := decodeGate(gate, owner, seedKey)
|
gateData, err := x.decodeGate(gate, owner, seedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode gate: %w", err)
|
return nil, fmt.Errorf("failed to decode gate: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -217,7 +223,7 @@ func encodeGate(ephemeralKey *keys.PrivateKey, seedKey *keys.PublicKey, tokens *
|
||||||
return gate, nil
|
return gate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeGate(gate *AccessBox_Gate, owner *keys.PrivateKey, seedKey *keys.PublicKey) (*GateData, error) {
|
func (x *AccessBox) decodeGate(gate *AccessBox_Gate, owner *keys.PrivateKey, seedKey *keys.PublicKey) (*GateData, error) {
|
||||||
data, err := decrypt(owner, seedKey, gate.Tokens)
|
data, err := decrypt(owner, seedKey, gate.Tokens)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("decrypt tokens: %w", err)
|
return nil, fmt.Errorf("decrypt tokens: %w", err)
|
||||||
|
@ -243,7 +249,11 @@ func decodeGate(gate *AccessBox_Gate, owner *keys.PrivateKey, seedKey *keys.Publ
|
||||||
|
|
||||||
gateData := NewGateData(owner.PublicKey(), &bearerTkn)
|
gateData := NewGateData(owner.PublicKey(), &bearerTkn)
|
||||||
gateData.SessionTokens = sessionTkns
|
gateData.SessionTokens = sessionTkns
|
||||||
gateData.SecretKey = hex.EncodeToString(tokens.SecretKey)
|
if x.IsCustom {
|
||||||
|
gateData.SecretKey = string(tokens.SecretKey)
|
||||||
|
} else {
|
||||||
|
gateData.SecretKey = hex.EncodeToString(tokens.SecretKey)
|
||||||
|
}
|
||||||
return gateData, nil
|
return gateData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.30.0
|
// protoc-gen-go v1.34.2
|
||||||
// protoc v3.12.4
|
// protoc v3.21.9
|
||||||
// source: creds/accessbox/accessbox.proto
|
// source: creds/accessbox/accessbox.proto
|
||||||
|
|
||||||
package accessbox
|
package accessbox
|
||||||
|
@ -28,6 +28,7 @@ type AccessBox struct {
|
||||||
SeedKey []byte `protobuf:"bytes,1,opt,name=seedKey,proto3" json:"seedKey,omitempty"`
|
SeedKey []byte `protobuf:"bytes,1,opt,name=seedKey,proto3" json:"seedKey,omitempty"`
|
||||||
Gates []*AccessBox_Gate `protobuf:"bytes,2,rep,name=gates,proto3" json:"gates,omitempty"`
|
Gates []*AccessBox_Gate `protobuf:"bytes,2,rep,name=gates,proto3" json:"gates,omitempty"`
|
||||||
ContainerPolicy []*AccessBox_ContainerPolicy `protobuf:"bytes,3,rep,name=containerPolicy,proto3" json:"containerPolicy,omitempty"`
|
ContainerPolicy []*AccessBox_ContainerPolicy `protobuf:"bytes,3,rep,name=containerPolicy,proto3" json:"containerPolicy,omitempty"`
|
||||||
|
IsCustom bool `protobuf:"varint,4,opt,name=isCustom,proto3" json:"isCustom,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AccessBox) Reset() {
|
func (x *AccessBox) Reset() {
|
||||||
|
@ -83,6 +84,13 @@ func (x *AccessBox) GetContainerPolicy() []*AccessBox_ContainerPolicy {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *AccessBox) GetIsCustom() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsCustom
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type Tokens struct {
|
type Tokens struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@ -261,7 +269,7 @@ var File_creds_accessbox_accessbox_proto protoreflect.FileDescriptor
|
||||||
var file_creds_accessbox_accessbox_proto_rawDesc = []byte{
|
var file_creds_accessbox_accessbox_proto_rawDesc = []byte{
|
||||||
0x0a, 0x1f, 0x63, 0x72, 0x65, 0x64, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f,
|
0x0a, 0x1f, 0x63, 0x72, 0x65, 0x64, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f,
|
||||||
0x78, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x78, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x6f, 0x12, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x22, 0xc7, 0x02, 0x0a,
|
0x6f, 0x12, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x22, 0xe3, 0x02, 0x0a,
|
||||||
0x09, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x6f, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65,
|
0x09, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x6f, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65,
|
||||||
0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x65, 0x65,
|
0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x65, 0x65,
|
||||||
0x64, 0x4b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x67, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20,
|
0x64, 0x4b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x67, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20,
|
||||||
|
@ -272,29 +280,31 @@ var file_creds_accessbox_accessbox_proto_rawDesc = []byte{
|
||||||
0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73,
|
0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73,
|
||||||
0x73, 0x42, 0x6f, 0x78, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f,
|
0x73, 0x42, 0x6f, 0x78, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f,
|
||||||
0x6c, 0x69, 0x63, 0x79, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50,
|
0x6c, 0x69, 0x63, 0x79, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50,
|
||||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x44, 0x0a, 0x04, 0x47, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a,
|
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x43, 0x75, 0x73, 0x74, 0x6f,
|
||||||
0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74,
|
0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x43, 0x75, 0x73, 0x74, 0x6f,
|
||||||
0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62,
|
0x6d, 0x1a, 0x44, 0x0a, 0x04, 0x47, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x6b,
|
||||||
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x67, 0x61,
|
0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||||
0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x1a, 0x59, 0x0a, 0x0f, 0x43,
|
0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
|
||||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2e,
|
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75,
|
||||||
0x0a, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72,
|
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x1a, 0x59, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||||
0x61, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61,
|
0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x6c, 0x6f,
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x16,
|
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74,
|
||||||
0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x6e, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
|
0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f,
|
||||||
0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69,
|
||||||
0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x20,
|
0x63, 0x79, 0x22, 0x6e, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09,
|
||||||
0x0a, 0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20,
|
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||||
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x65,
|
||||||
0x12, 0x24, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0d,
|
||||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72,
|
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x03, 0x20,
|
||||||
0x6f, 0x73, 0x74, 0x66, 0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43,
|
0x03, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65,
|
||||||
0x6c, 0x6f, 0x75, 0x64, 0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d,
|
0x6e, 0x73, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66,
|
||||||
0x73, 0x33, 0x2d, 0x67, 0x77, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x73, 0x2f, 0x74, 0x6f, 0x6b, 0x65,
|
0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64,
|
||||||
0x6e, 0x62, 0x6f, 0x78, 0x3b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x62, 0x06,
|
0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d, 0x73, 0x33, 0x2d, 0x67,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x77, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x73, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x62, 0x6f, 0x78,
|
||||||
|
0x3b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x62, 0x6f, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -310,7 +320,7 @@ func file_creds_accessbox_accessbox_proto_rawDescGZIP() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_creds_accessbox_accessbox_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
var file_creds_accessbox_accessbox_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||||
var file_creds_accessbox_accessbox_proto_goTypes = []interface{}{
|
var file_creds_accessbox_accessbox_proto_goTypes = []any{
|
||||||
(*AccessBox)(nil), // 0: accessbox.AccessBox
|
(*AccessBox)(nil), // 0: accessbox.AccessBox
|
||||||
(*Tokens)(nil), // 1: accessbox.Tokens
|
(*Tokens)(nil), // 1: accessbox.Tokens
|
||||||
(*AccessBox_Gate)(nil), // 2: accessbox.AccessBox.Gate
|
(*AccessBox_Gate)(nil), // 2: accessbox.AccessBox.Gate
|
||||||
|
@ -332,7 +342,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
if !protoimpl.UnsafeEnabled {
|
||||||
file_creds_accessbox_accessbox_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
file_creds_accessbox_accessbox_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*AccessBox); i {
|
switch v := v.(*AccessBox); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
|
@ -344,7 +354,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_creds_accessbox_accessbox_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
file_creds_accessbox_accessbox_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*Tokens); i {
|
switch v := v.(*Tokens); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
|
@ -356,7 +366,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_creds_accessbox_accessbox_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
file_creds_accessbox_accessbox_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*AccessBox_Gate); i {
|
switch v := v.(*AccessBox_Gate); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
|
@ -368,7 +378,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_creds_accessbox_accessbox_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
file_creds_accessbox_accessbox_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*AccessBox_ContainerPolicy); i {
|
switch v := v.(*AccessBox_ContainerPolicy); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
|
|
|
@ -20,6 +20,7 @@ message AccessBox {
|
||||||
bytes seedKey = 1 [json_name = "seedKey"];
|
bytes seedKey = 1 [json_name = "seedKey"];
|
||||||
repeated Gate gates = 2 [json_name = "gates"];
|
repeated Gate gates = 2 [json_name = "gates"];
|
||||||
repeated ContainerPolicy containerPolicy = 3 [json_name = "containerPolicy"];
|
repeated ContainerPolicy containerPolicy = 3 [json_name = "containerPolicy"];
|
||||||
|
bool isCustom = 4 [json_name = "isCustom"];
|
||||||
}
|
}
|
||||||
|
|
||||||
message Tokens {
|
message Tokens {
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestBearerTokenInAccessBox(t *testing.T) {
|
||||||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||||
|
|
||||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
box, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data, err := box.Marshal()
|
data, err := box.Marshal()
|
||||||
|
@ -96,7 +96,7 @@ func TestSessionTokenInAccessBox(t *testing.T) {
|
||||||
var newTkn bearer.Token
|
var newTkn bearer.Token
|
||||||
gate := NewGateData(cred.PublicKey(), &newTkn)
|
gate := NewGateData(cred.PublicKey(), &newTkn)
|
||||||
gate.SessionTokens = []*session.Container{tkn}
|
gate.SessionTokens = []*session.Container{tkn}
|
||||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
box, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data, err := box.Marshal()
|
data, err := box.Marshal()
|
||||||
|
@ -136,7 +136,7 @@ func TestAccessboxMultipleKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
box, _, err = PackTokens(gates, nil)
|
box, _, err = PackTokens(gates, nil, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for i, k := range privateKeys {
|
for i, k := range privateKeys {
|
||||||
|
@ -165,7 +165,7 @@ func TestUnknownKey(t *testing.T) {
|
||||||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||||
|
|
||||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
box, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = box.GetTokens(wrongCred)
|
_, err = box.GetTokens(wrongCred)
|
||||||
|
@ -224,14 +224,27 @@ func TestGetBox(t *testing.T) {
|
||||||
|
|
||||||
var tkn bearer.Token
|
var tkn bearer.Token
|
||||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||||
|
|
||||||
secret := []byte("secret")
|
secret := []byte("secret")
|
||||||
accessBox, _, err := PackTokens([]*GateData{gate}, secret)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
box, err := accessBox.GetBox(cred)
|
t.Run("regular secret", func(t *testing.T) {
|
||||||
require.NoError(t, err)
|
accessBox, secrets, err := PackTokens([]*GateData{gate}, secret, false)
|
||||||
require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, hex.EncodeToString(secret), secrets.SecretKey)
|
||||||
|
|
||||||
|
box, err := accessBox.GetBox(cred)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("custom secret", func(t *testing.T) {
|
||||||
|
accessBox, secrets, err := PackTokens([]*GateData{gate}, secret, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(secret), secrets.SecretKey)
|
||||||
|
|
||||||
|
box, err := accessBox.GetBox(cred)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(secret), box.Gate.SecretKey)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessBox(t *testing.T) {
|
func TestAccessBox(t *testing.T) {
|
||||||
|
@ -241,7 +254,7 @@ func TestAccessBox(t *testing.T) {
|
||||||
var tkn bearer.Token
|
var tkn bearer.Token
|
||||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||||
|
|
||||||
accessBox, _, err := PackTokens([]*GateData{gate}, nil)
|
accessBox, _, err := PackTokens([]*GateData{gate}, nil, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("invalid owner", func(t *testing.T) {
|
t.Run("invalid owner", func(t *testing.T) {
|
||||||
|
@ -300,7 +313,7 @@ func TestAccessBox(t *testing.T) {
|
||||||
BearerToken: &tkn,
|
BearerToken: &tkn,
|
||||||
GateKey: &keys.PublicKey{},
|
GateKey: &keys.PublicKey{},
|
||||||
}
|
}
|
||||||
_, _, err = PackTokens([]*GateData{gate}, nil)
|
_, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@ import (
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"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"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -22,13 +21,14 @@ import (
|
||||||
type (
|
type (
|
||||||
// Credentials is a bearer token get/put interface.
|
// Credentials is a bearer token get/put interface.
|
||||||
Credentials interface {
|
Credentials interface {
|
||||||
GetBox(context.Context, oid.Address) (*accessbox.Box, []object.Attribute, error)
|
GetBox(context.Context, cid.ID, string) (*accessbox.Box, []object.Attribute, error)
|
||||||
Put(context.Context, cid.ID, CredentialsParam) (oid.Address, error)
|
Put(context.Context, CredentialsParam) (oid.Address, error)
|
||||||
Update(context.Context, oid.Address, CredentialsParam) (oid.Address, error)
|
Update(context.Context, CredentialsParam) (oid.Address, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
CredentialsParam struct {
|
CredentialsParam struct {
|
||||||
OwnerID user.ID
|
Container cid.ID
|
||||||
|
AccessKeyID string
|
||||||
AccessBox *accessbox.AccessBox
|
AccessBox *accessbox.AccessBox
|
||||||
Expiration uint64
|
Expiration uint64
|
||||||
Keys keys.PublicKeys
|
Keys keys.PublicKeys
|
||||||
|
@ -49,13 +49,16 @@ type (
|
||||||
CacheConfig *cache.Config
|
CacheConfig *cache.Config
|
||||||
RemovingCheckAfterDurations time.Duration
|
RemovingCheckAfterDurations time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box struct {
|
||||||
|
AccessBox *accessbox.AccessBox
|
||||||
|
Attributes []object.Attribute
|
||||||
|
Address *oid.Address
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrmObjectCreate groups parameters of objects created by credential tool.
|
// PrmObjectCreate groups parameters of objects created by credential tool.
|
||||||
type PrmObjectCreate struct {
|
type PrmObjectCreate struct {
|
||||||
// FrostFS identifier of the object creator.
|
|
||||||
Creator user.ID
|
|
||||||
|
|
||||||
// FrostFS container to store the object.
|
// FrostFS container to store the object.
|
||||||
Container cid.ID
|
Container cid.ID
|
||||||
|
|
||||||
|
@ -64,7 +67,12 @@ type PrmObjectCreate struct {
|
||||||
|
|
||||||
// Optional.
|
// Optional.
|
||||||
// If provided cred object will be created using crdt approach.
|
// If provided cred object will be created using crdt approach.
|
||||||
NewVersionFor *oid.ID
|
NewVersionForAccessKeyID string
|
||||||
|
|
||||||
|
// Optional.
|
||||||
|
// If provided cred object will contain specific crdt name attribute for first accessbox object version.
|
||||||
|
// If NewVersionForAccessKeyID is provided this field isn't used.
|
||||||
|
CustomAccessKey string
|
||||||
|
|
||||||
// Last FrostFS epoch of the object lifetime.
|
// Last FrostFS epoch of the object lifetime.
|
||||||
ExpirationEpoch uint64
|
ExpirationEpoch uint64
|
||||||
|
@ -76,6 +84,21 @@ type PrmObjectCreate struct {
|
||||||
CustomAttributes []object.Attribute
|
CustomAttributes []object.Attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrmGetCredsObject groups parameters of getting credential object.
|
||||||
|
type PrmGetCredsObject struct {
|
||||||
|
// FrostFS container to get the object.
|
||||||
|
Container cid.ID
|
||||||
|
|
||||||
|
// S3 access key id.
|
||||||
|
AccessKeyID string
|
||||||
|
|
||||||
|
// FallbackAddress is an address that should be used to get creds if we couldn't find it by AccessKeyID.
|
||||||
|
// Optional.
|
||||||
|
FallbackAddress *oid.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrCustomAccessKeyIDNotFound = errors.New("custom AccessKeyId not found")
|
||||||
|
|
||||||
// FrostFS represents virtual connection to FrostFS network.
|
// FrostFS represents virtual connection to FrostFS network.
|
||||||
type FrostFS interface {
|
type FrostFS interface {
|
||||||
// CreateObject creates and saves a parameterized object in the specified
|
// CreateObject creates and saves a parameterized object in the specified
|
||||||
|
@ -92,8 +115,9 @@ type FrostFS interface {
|
||||||
//
|
//
|
||||||
// It returns exactly one non-nil value. It returns any error encountered which
|
// It returns exactly one non-nil value. It returns any error encountered which
|
||||||
// prevented the object payload from being read.
|
// prevented the object payload from being read.
|
||||||
|
// Returns ErrCustomAccessKeyIDNotFound if provided AccessKey is custom, and it was not found.
|
||||||
// Object must contain full payload.
|
// Object must contain full payload.
|
||||||
GetCredsObject(context.Context, oid.Address) (*object.Object, error)
|
GetCredsObject(context.Context, PrmGetCredsObject) (*object.Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -116,84 +140,128 @@ func New(cfg Config) Credentials {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cred) GetBox(ctx context.Context, addr oid.Address) (*accessbox.Box, []object.Attribute, error) {
|
func (c *cred) GetBox(ctx context.Context, cnrID cid.ID, accessKeyID string) (*accessbox.Box, []object.Attribute, error) {
|
||||||
cachedBoxValue := c.cache.Get(addr)
|
cachedBoxValue := c.cache.Get(accessKeyID)
|
||||||
if cachedBoxValue != nil {
|
if cachedBoxValue != nil {
|
||||||
return c.checkIfCredentialsAreRemoved(ctx, addr, cachedBoxValue)
|
return c.checkIfCredentialsAreRemoved(ctx, cnrID, accessKeyID, cachedBoxValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
box, attrs, err := c.getAccessBox(ctx, addr)
|
box, err := c.getAccessBox(ctx, cnrID, accessKeyID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("get access box: %w", err)
|
return nil, nil, fmt.Errorf("get access box: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedBox, err := box.GetBox(c.key)
|
cachedBox, err := box.AccessBox.GetBox(c.key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("get gate box: %w", err)
|
return nil, nil, fmt.Errorf("get gate box: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.putBoxToCache(addr, cachedBox, attrs)
|
val := &cache.AccessBoxCacheValue{
|
||||||
|
Box: cachedBox,
|
||||||
|
Attributes: box.Attributes,
|
||||||
|
PutTime: time.Now(),
|
||||||
|
Address: box.Address,
|
||||||
|
}
|
||||||
|
|
||||||
return cachedBox, attrs, nil
|
c.putBoxToCache(accessKeyID, val)
|
||||||
|
|
||||||
|
return cachedBox, box.Attributes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cred) checkIfCredentialsAreRemoved(ctx context.Context, addr oid.Address, cachedBoxValue *cache.AccessBoxCacheValue) (*accessbox.Box, []object.Attribute, error) {
|
func (c *cred) checkIfCredentialsAreRemoved(ctx context.Context, cnrID cid.ID, accessKeyID string, cachedBoxValue *cache.AccessBoxCacheValue) (*accessbox.Box, []object.Attribute, error) {
|
||||||
if time.Since(cachedBoxValue.PutTime) < c.removingCheckDuration {
|
if time.Since(cachedBoxValue.PutTime) < c.removingCheckDuration {
|
||||||
return cachedBoxValue.Box, cachedBoxValue.Attributes, nil
|
return cachedBoxValue.Box, cachedBoxValue.Attributes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
box, attrs, err := c.getAccessBox(ctx, addr)
|
box, err := c.getAccessBox(ctx, cnrID, accessKeyID, cachedBoxValue.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if client.IsErrObjectAlreadyRemoved(err) {
|
if client.IsErrObjectAlreadyRemoved(err) {
|
||||||
c.cache.Delete(addr)
|
c.cache.Delete(accessKeyID)
|
||||||
return nil, nil, fmt.Errorf("get access box: %w", err)
|
return nil, nil, fmt.Errorf("get access box: %w", err)
|
||||||
}
|
}
|
||||||
return cachedBoxValue.Box, cachedBoxValue.Attributes, nil
|
return cachedBoxValue.Box, cachedBoxValue.Attributes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedBox, err := box.GetBox(c.key)
|
cachedBox, err := box.AccessBox.GetBox(c.key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.cache.Delete(addr)
|
c.cache.Delete(accessKeyID)
|
||||||
return nil, nil, fmt.Errorf("get gate box: %w", err)
|
return nil, nil, fmt.Errorf("get gate box: %w", err)
|
||||||
}
|
}
|
||||||
// we need this to reset PutTime
|
// we need this to reset PutTime
|
||||||
// to don't check for removing each time after removingCheckDuration interval
|
// to don't check for removing each time after removingCheckDuration interval
|
||||||
c.putBoxToCache(addr, cachedBox, attrs)
|
val := &cache.AccessBoxCacheValue{
|
||||||
|
Box: cachedBox,
|
||||||
|
Attributes: box.Attributes,
|
||||||
|
PutTime: time.Now(),
|
||||||
|
Address: box.Address,
|
||||||
|
}
|
||||||
|
c.putBoxToCache(accessKeyID, val)
|
||||||
|
|
||||||
return cachedBoxValue.Box, attrs, nil
|
return cachedBoxValue.Box, box.Attributes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cred) putBoxToCache(addr oid.Address, box *accessbox.Box, attrs []object.Attribute) {
|
func (c *cred) putBoxToCache(accessKeyID string, val *cache.AccessBoxCacheValue) {
|
||||||
if err := c.cache.Put(addr, box, attrs); err != nil {
|
if err := c.cache.Put(accessKeyID, val); err != nil {
|
||||||
c.log.Warn(logs.CouldntPutAccessBoxIntoCache, zap.String("address", addr.EncodeToString()))
|
c.log.Warn(logs.CouldntPutAccessBoxIntoCache, zap.String("accessKeyID", accessKeyID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.AccessBox, []object.Attribute, error) {
|
func (c *cred) getAccessBox(ctx context.Context, cnrID cid.ID, accessKeyID string, fallbackAddr *oid.Address) (*Box, error) {
|
||||||
obj, err := c.frostFS.GetCredsObject(ctx, addr)
|
prm := PrmGetCredsObject{
|
||||||
|
Container: cnrID,
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
FallbackAddress: fallbackAddr,
|
||||||
|
}
|
||||||
|
obj, err := c.frostFS.GetCredsObject(ctx, prm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("read payload and attributes: %w", err)
|
return nil, fmt.Errorf("read payload and attributes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode access box
|
// decode access box
|
||||||
var box accessbox.AccessBox
|
var box accessbox.AccessBox
|
||||||
if err = box.Unmarshal(obj.Payload()); err != nil {
|
if err = box.Unmarshal(obj.Payload()); err != nil {
|
||||||
return nil, nil, fmt.Errorf("unmarhal access box: %w", err)
|
return nil, fmt.Errorf("unmarhal access box: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &box, obj.Attributes(), nil
|
addr := &oid.Address{}
|
||||||
|
boxCnrID, cnrIDOk := obj.ContainerID()
|
||||||
|
boxObjID, objIDOk := obj.ID()
|
||||||
|
addr.SetContainer(boxCnrID)
|
||||||
|
addr.SetObject(boxObjID)
|
||||||
|
if !cnrIDOk || !objIDOk {
|
||||||
|
addr = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Box{
|
||||||
|
AccessBox: &box,
|
||||||
|
Attributes: obj.Attributes(),
|
||||||
|
Address: addr,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cred) Put(ctx context.Context, idCnr cid.ID, prm CredentialsParam) (oid.Address, error) {
|
func (c *cred) Put(ctx context.Context, prm CredentialsParam) (oid.Address, error) {
|
||||||
return c.createObject(ctx, idCnr, nil, prm)
|
if prm.AccessKeyID != "" {
|
||||||
|
c.log.Info(logs.CheckCustomAccessKeyIDUniqueness, zap.String("access_key_id", prm.AccessKeyID))
|
||||||
|
credsPrm := PrmGetCredsObject{
|
||||||
|
Container: prm.Container,
|
||||||
|
AccessKeyID: prm.AccessKeyID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.frostFS.GetCredsObject(ctx, credsPrm); err == nil {
|
||||||
|
return oid.Address{}, fmt.Errorf("access key id '%s' already exists", prm.AccessKeyID)
|
||||||
|
} else if !errors.Is(err, ErrCustomAccessKeyIDNotFound) {
|
||||||
|
return oid.Address{}, fmt.Errorf("check AccessKeyID uniqueness: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.createObject(ctx, prm, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cred) Update(ctx context.Context, addr oid.Address, prm CredentialsParam) (oid.Address, error) {
|
func (c *cred) Update(ctx context.Context, prm CredentialsParam) (oid.Address, error) {
|
||||||
objID := addr.Object()
|
return c.createObject(ctx, prm, true)
|
||||||
return c.createObject(ctx, addr.Container(), &objID, prm)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cred) createObject(ctx context.Context, cnrID cid.ID, newVersionFor *oid.ID, prm CredentialsParam) (oid.Address, error) {
|
func (c *cred) createObject(ctx context.Context, prm CredentialsParam, update bool) (oid.Address, error) {
|
||||||
if len(prm.Keys) == 0 {
|
if len(prm.Keys) == 0 {
|
||||||
return oid.Address{}, ErrEmptyPublicKeys
|
return oid.Address{}, ErrEmptyPublicKeys
|
||||||
} else if prm.AccessBox == nil {
|
} else if prm.AccessBox == nil {
|
||||||
|
@ -204,14 +272,19 @@ func (c *cred) createObject(ctx context.Context, cnrID cid.ID, newVersionFor *oi
|
||||||
return oid.Address{}, fmt.Errorf("marshall box: %w", err)
|
return oid.Address{}, fmt.Errorf("marshall box: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newVersionFor string
|
||||||
|
if update {
|
||||||
|
newVersionFor = prm.AccessKeyID
|
||||||
|
}
|
||||||
|
|
||||||
idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
|
idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
|
||||||
Creator: prm.OwnerID,
|
Container: prm.Container,
|
||||||
Container: cnrID,
|
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
||||||
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
ExpirationEpoch: prm.Expiration,
|
||||||
ExpirationEpoch: prm.Expiration,
|
CustomAccessKey: prm.AccessKeyID,
|
||||||
NewVersionFor: newVersionFor,
|
NewVersionForAccessKeyID: newVersionFor,
|
||||||
Payload: data,
|
Payload: data,
|
||||||
CustomAttributes: prm.CustomAttributes,
|
CustomAttributes: prm.CustomAttributes,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oid.Address{}, fmt.Errorf("create object: %w", err)
|
return oid.Address{}, fmt.Errorf("create object: %w", err)
|
||||||
|
@ -219,7 +292,7 @@ func (c *cred) createObject(ctx context.Context, cnrID cid.ID, newVersionFor *oi
|
||||||
|
|
||||||
var addr oid.Address
|
var addr oid.Address
|
||||||
addr.SetObject(idObj)
|
addr.SetObject(idObj)
|
||||||
addr.SetContainer(cnrID)
|
addr.SetContainer(prm.Container)
|
||||||
|
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,27 +16,40 @@ 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"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type frostfsMock struct {
|
type frostfsMock struct {
|
||||||
objects map[oid.Address][]*object.Object
|
key *keys.PrivateKey
|
||||||
errors map[oid.Address]error
|
objects map[string][]*object.Object
|
||||||
|
errors map[string]error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFrostfsMock() *frostfsMock {
|
func newFrostfsMock(key *keys.PrivateKey) *frostfsMock {
|
||||||
return &frostfsMock{
|
return &frostfsMock{
|
||||||
objects: map[oid.Address][]*object.Object{},
|
objects: map[string][]*object.Object{},
|
||||||
errors: map[oid.Address]error{},
|
errors: map[string]error{},
|
||||||
|
key: key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *frostfsMock) ownerID() user.ID {
|
||||||
|
if f.key == nil {
|
||||||
|
return user.ID{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ownerID user.ID
|
||||||
|
user.IDFromKey(&ownerID, f.key.PrivateKey.PublicKey)
|
||||||
|
return ownerID
|
||||||
|
}
|
||||||
|
|
||||||
func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.ID, error) {
|
func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.ID, error) {
|
||||||
var obj object.Object
|
var obj object.Object
|
||||||
obj.SetPayload(prm.Payload)
|
obj.SetPayload(prm.Payload)
|
||||||
obj.SetOwnerID(prm.Creator)
|
obj.SetOwnerID(f.ownerID())
|
||||||
obj.SetContainerID(prm.Container)
|
obj.SetContainerID(prm.Container)
|
||||||
|
|
||||||
a := object.NewAttribute()
|
a := object.NewAttribute()
|
||||||
|
@ -44,19 +58,15 @@ func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.
|
||||||
prm.CustomAttributes = append(prm.CustomAttributes, *a)
|
prm.CustomAttributes = append(prm.CustomAttributes, *a)
|
||||||
obj.SetAttributes(prm.CustomAttributes...)
|
obj.SetAttributes(prm.CustomAttributes...)
|
||||||
|
|
||||||
if prm.NewVersionFor != nil {
|
if prm.NewVersionForAccessKeyID != "" {
|
||||||
var addr oid.Address
|
_, ok := f.objects[prm.NewVersionForAccessKeyID]
|
||||||
addr.SetObject(*prm.NewVersionFor)
|
|
||||||
addr.SetContainer(prm.Container)
|
|
||||||
|
|
||||||
_, ok := f.objects[addr]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return oid.ID{}, errors.New("not found")
|
return oid.ID{}, errors.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
objID := oidtest.ID()
|
objID := oidtest.ID()
|
||||||
obj.SetID(objID)
|
obj.SetID(objID)
|
||||||
f.objects[addr] = append(f.objects[addr], &obj)
|
f.objects[prm.NewVersionForAccessKeyID] = append(f.objects[prm.NewVersionForAccessKeyID], &obj)
|
||||||
|
|
||||||
return objID, nil
|
return objID, nil
|
||||||
}
|
}
|
||||||
|
@ -64,22 +74,27 @@ func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.
|
||||||
objID := oidtest.ID()
|
objID := oidtest.ID()
|
||||||
obj.SetID(objID)
|
obj.SetID(objID)
|
||||||
|
|
||||||
|
accessKeyID := prm.CustomAccessKey
|
||||||
|
if accessKeyID == "" {
|
||||||
|
accessKeyID = prm.Container.EncodeToString() + "0" + objID.EncodeToString()
|
||||||
|
}
|
||||||
|
|
||||||
var addr oid.Address
|
var addr oid.Address
|
||||||
addr.SetObject(objID)
|
addr.SetObject(objID)
|
||||||
addr.SetContainer(prm.Container)
|
addr.SetContainer(prm.Container)
|
||||||
f.objects[addr] = []*object.Object{&obj}
|
f.objects[accessKeyID] = []*object.Object{&obj}
|
||||||
|
|
||||||
return objID, nil
|
return objID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frostfsMock) GetCredsObject(_ context.Context, address oid.Address) (*object.Object, error) {
|
func (f *frostfsMock) GetCredsObject(_ context.Context, prm PrmGetCredsObject) (*object.Object, error) {
|
||||||
if err := f.errors[address]; err != nil {
|
if err := f.errors[prm.AccessKeyID]; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
objects, ok := f.objects[address]
|
objects, ok := f.objects[prm.AccessKeyID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("not found")
|
return nil, ErrCustomAccessKeyIDNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return objects[len(objects)-1], nil
|
return objects[len(objects)-1], nil
|
||||||
|
@ -100,7 +115,7 @@ func TestRemovingAccessBox(t *testing.T) {
|
||||||
sk, err := hex.DecodeString(secretKey)
|
sk, err := hex.DecodeString(secretKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
accessBox, _, err := accessbox.PackTokens(gateData, sk)
|
accessBox, _, err := accessbox.PackTokens(gateData, sk, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
data, err := accessBox.Marshal()
|
data, err := accessBox.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -111,9 +126,24 @@ func TestRemovingAccessBox(t *testing.T) {
|
||||||
obj.SetID(addr.Object())
|
obj.SetID(addr.Object())
|
||||||
obj.SetContainerID(addr.Container())
|
obj.SetContainerID(addr.Container())
|
||||||
|
|
||||||
|
accessKeyID := getAccessKeyID(addr)
|
||||||
|
|
||||||
|
accessBoxCustom, _, err := accessbox.PackTokens(gateData, []byte("secret"), true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
dataCustom, err := accessBoxCustom.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var objCustom object.Object
|
||||||
|
objCustom.SetPayload(dataCustom)
|
||||||
|
addrCustom := oidtest.Address()
|
||||||
|
objCustom.SetID(addrCustom.Object())
|
||||||
|
objCustom.SetContainerID(addrCustom.Container())
|
||||||
|
|
||||||
|
accessKeyIDCustom := "accessKeyID"
|
||||||
|
|
||||||
frostfs := &frostfsMock{
|
frostfs := &frostfsMock{
|
||||||
objects: map[oid.Address][]*object.Object{addr: {&obj}},
|
objects: map[string][]*object.Object{accessKeyID: {&obj}, accessKeyIDCustom: {&objCustom}},
|
||||||
errors: map[oid.Address]error{},
|
errors: map[string]error{},
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
|
@ -129,15 +159,30 @@ func TestRemovingAccessBox(t *testing.T) {
|
||||||
|
|
||||||
creds := New(cfg)
|
creds := New(cfg)
|
||||||
|
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
frostfs.errors[addr] = errors.New("network error")
|
frostfs.errors[accessKeyID] = errors.New("network error")
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
frostfs.errors[accessKeyIDCustom] = errors.New("network error")
|
||||||
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
frostfs.errors[addr] = &apistatus.ObjectAlreadyRemoved{}
|
frostfs.errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{}
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
|
require.Error(t, err)
|
||||||
|
frostfs.errors[accessKeyIDCustom] = &apistatus.ObjectAlreadyRemoved{}
|
||||||
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
frostfs.errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{}
|
||||||
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
|
require.Error(t, err)
|
||||||
|
frostfs.errors[accessKeyIDCustom] = &apistatus.ObjectAlreadyRemoved{}
|
||||||
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +198,9 @@ func TestGetBox(t *testing.T) {
|
||||||
}}
|
}}
|
||||||
|
|
||||||
secret := []byte("secret")
|
secret := []byte("secret")
|
||||||
accessBox, _, err := accessbox.PackTokens(gateData, secret)
|
accessBox, secrets, err := accessbox.PackTokens(gateData, secret, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, hex.EncodeToString(secret), secrets.SecretKey)
|
||||||
data, err := accessBox.Marshal()
|
data, err := accessBox.Marshal()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -172,108 +218,107 @@ func TestGetBox(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("no removing check, accessbox from cache", func(t *testing.T) {
|
t.Run("no removing check, accessbox from cache", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
creds := newCreds(key, cfg, time.Hour)
|
||||||
cfg.FrostFS = frostfs
|
|
||||||
cfg.RemovingCheckAfterDurations = time.Hour
|
|
||||||
cfg.Key = key
|
|
||||||
creds := New(cfg)
|
|
||||||
|
|
||||||
cnrID := cidtest.ID()
|
cnrID := cidtest.ID()
|
||||||
addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
accessKeyID := getAccessKeyID(addr)
|
||||||
|
|
||||||
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
frostfs.errors[addr] = &apistatus.ObjectAlreadyRemoved{}
|
creds.(*cred).frostFS.(*frostfsMock).errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{}
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error while getting box from frostfs", func(t *testing.T) {
|
t.Run("error while getting box from frostfs", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
creds := newCreds(key, cfg, 0)
|
||||||
cfg.FrostFS = frostfs
|
|
||||||
cfg.RemovingCheckAfterDurations = 0
|
|
||||||
cfg.Key = key
|
|
||||||
creds := New(cfg)
|
|
||||||
|
|
||||||
cnrID := cidtest.ID()
|
cnrID := cidtest.ID()
|
||||||
addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
frostfs.errors[addr] = errors.New("network error")
|
accessKeyID := getAccessKeyID(addr)
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
creds.(*cred).frostFS.(*frostfsMock).errors[accessKeyID] = errors.New("network error")
|
||||||
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid key", func(t *testing.T) {
|
t.Run("invalid key", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
frostfs := newFrostfsMock(key)
|
||||||
|
|
||||||
var obj object.Object
|
var obj object.Object
|
||||||
obj.SetPayload(data)
|
obj.SetPayload(data)
|
||||||
addr := oidtest.Address()
|
addr := oidtest.Address()
|
||||||
frostfs.objects[addr] = []*object.Object{&obj}
|
accessKeyID := getAccessKeyID(addr)
|
||||||
|
frostfs.objects[accessKeyID] = []*object.Object{&obj}
|
||||||
|
|
||||||
cfg.FrostFS = frostfs
|
cfg.FrostFS = frostfs
|
||||||
cfg.RemovingCheckAfterDurations = 0
|
cfg.RemovingCheckAfterDurations = 0
|
||||||
cfg.Key = &keys.PrivateKey{}
|
cfg.Key = &keys.PrivateKey{}
|
||||||
creds := New(cfg)
|
creds := New(cfg)
|
||||||
|
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid payload", func(t *testing.T) {
|
t.Run("invalid payload", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
frostfs := newFrostfsMock(key)
|
||||||
|
|
||||||
var obj object.Object
|
var obj object.Object
|
||||||
obj.SetPayload([]byte("invalid"))
|
obj.SetPayload([]byte("invalid"))
|
||||||
addr := oidtest.Address()
|
addr := oidtest.Address()
|
||||||
frostfs.objects[addr] = []*object.Object{&obj}
|
accessKeyID := getAccessKeyID(addr)
|
||||||
|
frostfs.objects[accessKeyID] = []*object.Object{&obj}
|
||||||
|
|
||||||
cfg.FrostFS = frostfs
|
cfg.FrostFS = frostfs
|
||||||
cfg.RemovingCheckAfterDurations = 0
|
cfg.RemovingCheckAfterDurations = 0
|
||||||
cfg.Key = key
|
cfg.Key = key
|
||||||
creds := New(cfg)
|
creds := New(cfg)
|
||||||
|
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check attributes update", func(t *testing.T) {
|
t.Run("check attributes update", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
creds := newCreds(key, cfg, 0)
|
||||||
cfg.FrostFS = frostfs
|
|
||||||
cfg.RemovingCheckAfterDurations = 0
|
|
||||||
cfg.Key = key
|
|
||||||
creds := New(cfg)
|
|
||||||
|
|
||||||
cnrID := cidtest.ID()
|
cnrID := cidtest.ID()
|
||||||
addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, boxAttrs, err := creds.GetBox(ctx, addr)
|
accessKeyID := getAccessKeyID(addr)
|
||||||
|
_, boxAttrs, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = creds.Update(ctx, addr, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox, CustomAttributes: attrs})
|
prm := CredentialsParam{
|
||||||
|
Container: addr.Container(),
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
Keys: keys.PublicKeys{key.PublicKey()},
|
||||||
|
AccessBox: accessBox,
|
||||||
|
CustomAttributes: attrs,
|
||||||
|
}
|
||||||
|
_, err = creds.Update(ctx, prm)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, newBoxAttrs, err := creds.GetBox(ctx, addr)
|
_, newBoxAttrs, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(boxAttrs)+1, len(newBoxAttrs))
|
require.Equal(t, len(boxAttrs)+1, len(newBoxAttrs))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check accessbox update", func(t *testing.T) {
|
t.Run("check accessbox update", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
creds := newCreds(key, cfg, 0)
|
||||||
cfg.FrostFS = frostfs
|
|
||||||
cfg.RemovingCheckAfterDurations = 0
|
|
||||||
cfg.Key = key
|
|
||||||
creds := New(cfg)
|
|
||||||
|
|
||||||
cnrID := cidtest.ID()
|
cnrID := cidtest.ID()
|
||||||
addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
box, _, err := creds.GetBox(ctx, addr)
|
accessKeyID := getAccessKeyID(addr)
|
||||||
|
|
||||||
|
box, _, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey)
|
require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey)
|
||||||
|
|
||||||
|
@ -286,44 +331,134 @@ func TestGetBox(t *testing.T) {
|
||||||
}}
|
}}
|
||||||
|
|
||||||
newSecret := []byte("new-secret")
|
newSecret := []byte("new-secret")
|
||||||
newAccessBox, _, err := accessbox.PackTokens(newGateData, newSecret)
|
newAccessBox, _, err := accessbox.PackTokens(newGateData, newSecret, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = creds.Update(ctx, addr, CredentialsParam{Keys: keys.PublicKeys{newKey.PublicKey()}, AccessBox: newAccessBox})
|
prm := CredentialsParam{
|
||||||
|
Container: addr.Container(),
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
Keys: keys.PublicKeys{newKey.PublicKey()},
|
||||||
|
AccessBox: newAccessBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = creds.Update(ctx, prm)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, _, err = creds.GetBox(ctx, addr)
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
cfg.Key = newKey
|
newCfg := Config{
|
||||||
newCreds := New(cfg)
|
FrostFS: creds.(*cred).frostFS,
|
||||||
|
Key: newKey,
|
||||||
|
CacheConfig: cfg.CacheConfig,
|
||||||
|
}
|
||||||
|
newCreds := New(newCfg)
|
||||||
|
|
||||||
box, _, err = newCreds.GetBox(ctx, addr)
|
box, _, err = newCreds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, hex.EncodeToString(newSecret), box.Gate.SecretKey)
|
require.Equal(t, hex.EncodeToString(newSecret), box.Gate.SecretKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("check access key id uniqueness", func(t *testing.T) {
|
||||||
|
creds := newCreds(key, cfg, 0)
|
||||||
|
|
||||||
|
prm := CredentialsParam{
|
||||||
|
Container: cidtest.ID(),
|
||||||
|
AccessBox: accessBox,
|
||||||
|
Keys: keys.PublicKeys{key.PublicKey()},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = creds.Put(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = creds.Put(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("empty keys", func(t *testing.T) {
|
t.Run("empty keys", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
creds := newCreds(key, cfg, 0)
|
||||||
cfg.FrostFS = frostfs
|
|
||||||
cfg.RemovingCheckAfterDurations = 0
|
|
||||||
cfg.Key = key
|
|
||||||
creds := New(cfg)
|
|
||||||
|
|
||||||
cnrID := cidtest.ID()
|
cnrID := cidtest.ID()
|
||||||
_, err = creds.Put(ctx, cnrID, CredentialsParam{AccessBox: accessBox})
|
_, err = creds.Put(ctx, CredentialsParam{Container: cnrID, AccessBox: accessBox})
|
||||||
require.ErrorIs(t, err, ErrEmptyPublicKeys)
|
require.ErrorIs(t, err, ErrEmptyPublicKeys)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("empty accessbox", func(t *testing.T) {
|
t.Run("empty accessbox", func(t *testing.T) {
|
||||||
frostfs := newFrostfsMock()
|
creds := newCreds(key, cfg, 0)
|
||||||
cfg.FrostFS = frostfs
|
|
||||||
cfg.RemovingCheckAfterDurations = 0
|
|
||||||
cfg.Key = key
|
|
||||||
creds := New(cfg)
|
|
||||||
|
|
||||||
cnrID := cidtest.ID()
|
cnrID := cidtest.ID()
|
||||||
_, err = creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}})
|
_, err = creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}})
|
||||||
require.ErrorIs(t, err, ErrEmptyBearerToken)
|
require.ErrorIs(t, err, ErrEmptyBearerToken)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBoxWithCustomAccessKeyID(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gateData := []*accessbox.GateData{{
|
||||||
|
BearerToken: &bearer.Token{},
|
||||||
|
GateKey: key.PublicKey(),
|
||||||
|
}}
|
||||||
|
|
||||||
|
secret := []byte("secret")
|
||||||
|
accessBox, secrets, err := accessbox.PackTokens(gateData, secret, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(secret), secrets.SecretKey)
|
||||||
|
|
||||||
|
cfg := Config{
|
||||||
|
CacheConfig: &cache.Config{
|
||||||
|
Size: 10,
|
||||||
|
Lifetime: 24 * time.Hour,
|
||||||
|
Logger: zaptest.NewLogger(t),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("check secret format", func(t *testing.T) {
|
||||||
|
creds := newCreds(key, cfg, 0)
|
||||||
|
|
||||||
|
prm := CredentialsParam{
|
||||||
|
Container: cidtest.ID(),
|
||||||
|
AccessKeyID: "custom-access-key-id",
|
||||||
|
AccessBox: accessBox,
|
||||||
|
Keys: keys.PublicKeys{key.PublicKey()},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = creds.Put(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
box, _, err := creds.GetBox(ctx, prm.Container, prm.AccessKeyID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(secret), box.Gate.SecretKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check custom access key id uniqueness", func(t *testing.T) {
|
||||||
|
creds := newCreds(key, cfg, 0)
|
||||||
|
|
||||||
|
prm := CredentialsParam{
|
||||||
|
Container: cidtest.ID(),
|
||||||
|
AccessKeyID: "custom-access-key-id",
|
||||||
|
AccessBox: accessBox,
|
||||||
|
Keys: keys.PublicKeys{key.PublicKey()},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = creds.Put(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = creds.Put(ctx, prm)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreds(key *keys.PrivateKey, cfg Config, removingCheckDuration time.Duration) Credentials {
|
||||||
|
frostfs := newFrostfsMock(key)
|
||||||
|
cfg.FrostFS = frostfs
|
||||||
|
cfg.RemovingCheckAfterDurations = removingCheckDuration
|
||||||
|
cfg.Key = key
|
||||||
|
return New(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccessKeyID(addr oid.Address) string {
|
||||||
|
return strings.ReplaceAll(addr.EncodeToString(), "/", "0")
|
||||||
|
}
|
||||||
|
|
|
@ -159,8 +159,10 @@ storage node.
|
||||||
Object s3 credentials are formed based on:
|
Object s3 credentials are formed based on:
|
||||||
|
|
||||||
* `AccessKeyId` - is concatenated container id and object id (`<cid>0<oid>`) of `AccessBox` (
|
* `AccessKeyId` - is concatenated container id and object id (`<cid>0<oid>`) of `AccessBox` (
|
||||||
e.g. `2XGRML5EW3LMHdf64W2DkBy1Nkuu4y4wGhUj44QjbXBi05ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf`)
|
e.g. `2XGRML5EW3LMHdf64W2DkBy1Nkuu4y4wGhUj44QjbXBi05ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf`).
|
||||||
* `SecretAccessKey` - hex-encoded random generated 32 bytes (that is encrypted and stored in object payload)
|
Or it can be arbitrary user-provided unique string with min length 4 and max length 128.
|
||||||
|
* `SecretAccessKey` - hex-encoded random generated 32 bytes (that is encrypted and stored in object payload).
|
||||||
|
Or it can be arbitrary user-provided unique string with min length 4 and max length 128.
|
||||||
|
|
||||||
> **Note**: sensitive info in `AccessBox` is [encrypted](#encryption), so only someone who posses specific private key
|
> **Note**: sensitive info in `AccessBox` is [encrypted](#encryption), so only someone who posses specific private key
|
||||||
> can decrypt such info.
|
> can decrypt such info.
|
||||||
|
@ -192,7 +194,7 @@ It contains:
|
||||||
* List of gate data:
|
* List of gate data:
|
||||||
* Gate public key (so that gate (when it will decrypt data later) know which item from the list it should process)
|
* Gate public key (so that gate (when it will decrypt data later) know which item from the list it should process)
|
||||||
* Encrypted tokens:
|
* Encrypted tokens:
|
||||||
* `SecretAccessKey` - hex-encoded random generated 32 bytes
|
* `SecretAccessKey` - hex-encoded random generated 32 bytes (or arbitrary user-provided string)
|
||||||
* Marshaled bearer token - more detail
|
* Marshaled bearer token - more detail
|
||||||
in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/acl/types.proto#L189)
|
in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/acl/types.proto#L189)
|
||||||
* Marshaled session token - more detail
|
* Marshaled session token - more detail
|
||||||
|
@ -229,10 +231,12 @@ relevant data) the following sequence is used:
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
* Search all object whose attribute `S3-Access-Box-CRDT-Name` is equal to `AccessKeyId` (extract container id
|
* Search all object whose attribute `S3-Access-Box-CRDT-Name` is equal to `AccessKeyId` (extract container id
|
||||||
from `AccessKeyId` that has format: `<cid>0<oid>`).
|
from `AccessKeyId` that has format: `<cid>0<oid>` if `AccessBox` was created with default parameters, or it can also
|
||||||
|
be arbitrary user-defined string).
|
||||||
* Get metadata for these object using `HEAD` requests (not `Get` to reduce network traffic)
|
* Get metadata for these object using `HEAD` requests (not `Get` to reduce network traffic)
|
||||||
* Sort all these objects by creation epoch and object id
|
* Sort all these objects by creation epoch and object id
|
||||||
* Pick last object id (If no object is found then extract object id from `AccessKeyId` that has format: `<cid>0<oid>`.
|
* Pick last object id (If no object is found then extract object id from `AccessKeyId` that has format: `<cid>0<oid>`
|
||||||
|
(if `AccessBox` was created with default parameters, or it can also be arbitrary user-defined string).
|
||||||
We need to do this because versions of `AccessBox` can miss the `S3-Access-Box-CRDT-Name` attribute.)
|
We need to do this because versions of `AccessBox` can miss the `S3-Access-Box-CRDT-Name` attribute.)
|
||||||
* Get appropriate object from FrostFS storage
|
* Get appropriate object from FrostFS storage
|
||||||
* Decrypt `AccessBox` (see [encryption](#encryption))
|
* Decrypt `AccessBox` (see [encryption](#encryption))
|
||||||
|
@ -253,7 +257,7 @@ secp256r1 or prime256v1) is used (unless otherwise stated).
|
||||||
|
|
||||||
* Create ephemeral key (`SeedKey`), it's need to generate shared secret
|
* Create ephemeral key (`SeedKey`), it's need to generate shared secret
|
||||||
* Generate random 32-byte (that after hex-encoded be `SecretAccessKey`) or use existing secret access key
|
* Generate random 32-byte (that after hex-encoded be `SecretAccessKey`) or use existing secret access key
|
||||||
(if `AccessBox` is being updated rather than creating brand new)
|
(if `AccessBox` is being updated rather than creating brand new) or use arbitrary user-provided string
|
||||||
* Generate shared secret as [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman)
|
* Generate shared secret as [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman)
|
||||||
* Derive 32-byte key using shared secret from previous step with key derivation function based on
|
* Derive 32-byte key using shared secret from previous step with key derivation function based on
|
||||||
HMAC with SHA256 [HKDF](https://en.wikipedia.org/wiki/HKDF)
|
HMAC with SHA256 [HKDF](https://en.wikipedia.org/wiki/HKDF)
|
||||||
|
|
|
@ -146,6 +146,32 @@ the secret. Format of `access_key_id`: `%cid0%oid`, where 0(zero) is a delimiter
|
||||||
24h). Default value is `720h` (30 days). It will be ceil rounded to the nearest amount of epoch
|
24h). Default value is `720h` (30 days). It will be ceil rounded to the nearest amount of epoch
|
||||||
* `--aws-cli-credentials` - path to the aws cli credentials file, where authmate will write `access_key_id` and
|
* `--aws-cli-credentials` - path to the aws cli credentials file, where authmate will write `access_key_id` and
|
||||||
`secret_access_key` to
|
`secret_access_key` to
|
||||||
|
* `--rpc-endpoint` - NEO node RPC address (must be provided if `--container-id` is NNS name)
|
||||||
|
* `--access-key-id` - access key id of s3 credential that must be created (must be unique)
|
||||||
|
* `--secret-access-key` - secret access key of s3 credential that must be used
|
||||||
|
|
||||||
|
You also can specify `AccessKeyID`/`SecretAccessKey` pair that should be created:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ frostfs-s3-authmate issue-secret --wallet wallet.json \
|
||||||
|
--peer 192.168.130.71:8080 \
|
||||||
|
--gate-public-key 0313b1ac3a8076e155a7e797b24f0b650cccad5941ea59d7cfd51a024a8b2a06bf \
|
||||||
|
--gate-public-key 0317585fa8274f7afdf1fc5f2a2e7bece549d5175c4e5182e37924f30229aef967 \
|
||||||
|
--access-key-id my-access-key \
|
||||||
|
--secret-access-key my-secret-key \
|
||||||
|
--container-id BpExV76416Vo7GrkJsGwXGoLM35xsBwup8voedDZR3c6
|
||||||
|
|
||||||
|
Enter password for wallet.json >
|
||||||
|
|
||||||
|
{
|
||||||
|
"initial_access_key_id": "my-access-key-3",
|
||||||
|
"access_key_id": "my-access-key",
|
||||||
|
"secret_access_key": "my-secret-key",
|
||||||
|
"owner_private_key": "d9972cc4f21b07a90f4b347c72c33c1d1611c2b9a2cfd0cc28cee8cb221e8e55",
|
||||||
|
"wallet_public_key": "031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a",
|
||||||
|
"container_id": "BpExV76416Vo7GrkJsGwXGoLM35xsBwup8voedDZR3c6"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Bearer tokens
|
### Bearer tokens
|
||||||
|
|
||||||
|
@ -323,6 +349,25 @@ $ frostfs-s3-authmate generate-presigned-url --endpoint http://localhost:8084 \
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Upload file with presigned URL
|
||||||
|
|
||||||
|
1. Generate presigned URL to upload object `obj` to bucket `presigned`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ frostfs-s3-authmate generate-presigned-url --endpoint http://localhost:8084 \
|
||||||
|
--method put --bucket presigned --object obj --lifetime 30s
|
||||||
|
|
||||||
|
{
|
||||||
|
"URL": "http://localhost:8084/presigned/obj?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CYfbvKwSC9VNvttj5snyEZ5Ttr2VaBabpw7mRuEzNXyw09ewUERj6MGDKfyckfg5VZ39GfXbwLwz62UPVeRxhJDet%2F20241029%2Fdefault%2Fs3%2Faws4_request&X-Amz-Date=20241029T145726Z&X-Amz-Expires=30&X-Amz-SignedHeaders=host&X-Amz-Signature=2bb13b3e6448968219ad95147debe49e37bce5ce3ed1344c4015f43cb444a956"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Upload file using `curl`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl --upload-file /path/to/file 'http://localhost:8084/presigned/obj?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CYfbvKwSC9VNvttj5snyEZ5Ttr2VaBabpw7mRuEzNXyw09ewUERj6MGDKfyckfg5VZ39GfXbwLwz62UPVeRxhJDet%2F20241029%2Fdefault%2Fs3%2Faws4_request&X-Amz-Date=20241029T145726Z&X-Amz-Expires=30&X-Amz-SignedHeaders=host&X-Amz-Signature=2bb13b3e6448968219ad95147debe49e37bce5ce3ed1344c4015f43cb444a956'
|
||||||
|
```
|
||||||
|
|
||||||
### AWS CLI
|
### AWS CLI
|
||||||
|
|
||||||
You can also can get the presigned URL (only for GET) using aws cli v2:
|
You can also can get the presigned URL (only for GET) using aws cli v2:
|
||||||
|
|
|
@ -195,6 +195,7 @@ There are some custom types used for brevity:
|
||||||
| `retry` | [Retry configuration](#retry-section) |
|
| `retry` | [Retry configuration](#retry-section) |
|
||||||
| `containers` | [Containers configuration](#containers-section) |
|
| `containers` | [Containers configuration](#containers-section) |
|
||||||
| `vhs` | [VHS configuration](#vhs-section) |
|
| `vhs` | [VHS configuration](#vhs-section) |
|
||||||
|
| `multinet` | [Multinet configuration](#multinet-section) |
|
||||||
|
|
||||||
### General section
|
### General section
|
||||||
|
|
||||||
|
@ -370,12 +371,21 @@ server:
|
||||||
logger:
|
logger:
|
||||||
level: debug
|
level: debug
|
||||||
destination: stdout
|
destination: stdout
|
||||||
|
sampling:
|
||||||
|
enabled: false
|
||||||
|
initial: 100
|
||||||
|
thereafter: 100
|
||||||
|
interval: 1s
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|---------------|----------|---------------|---------------|----------------------------------------------------------------------------------------------------|
|
|-----------------------|------------|---------------|---------------|----------------------------------------------------------------------------------------------------|
|
||||||
| `level` | `string` | yes | `debug` | Logging level.<br/>Possible values: `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal`. |
|
| `level` | `string` | yes | `debug` | Logging level.<br/>Possible values: `debug`, `info`, `warn`, `error`, `dpanic`, `panic`, `fatal`. |
|
||||||
| `destination` | `string` | no | `stdout` | Destination for logger: `stdout` or `journald` |
|
| `destination` | `string` | no | `stdout` | Destination for logger: `stdout` or `journald` |
|
||||||
|
| `sampling.enabled` | `bool` | no | false | Sampling enabling flag. |
|
||||||
|
| `sampling.initial` | `int` | no | '100' | Sampling count of first log entries. |
|
||||||
|
| `sampling.thereafter` | `int` | no | '100' | Sampling count of entries after an `interval`. |
|
||||||
|
| `sampling.interval` | `duration` | no | '1s' | Sampling interval of messaging similar entries. |
|
||||||
|
|
||||||
|
|
||||||
### `http_logging` section
|
### `http_logging` section
|
||||||
|
@ -538,7 +548,7 @@ tracing:
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|--------------|----------|---------------|---------------|---------------------------------------------------------------------------------------------------------------------------------|
|
|--------------|----------|---------------|---------------|---------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `enabled` | `bool` | no | `false` | Flag to enable the service. |
|
| `enabled` | `bool` | yes | `false` | Flag to enable the service. |
|
||||||
| `exporter` | `string` | yes | `` | Type of tracing exporter. |
|
| `exporter` | `string` | yes | `` | Type of tracing exporter. |
|
||||||
| `endpoint` | `string` | yes | `` | Address that service listener binds to. |
|
| `endpoint` | `string` | yes | `` | Address that service listener binds to. |
|
||||||
| `trusted_ca` | `string` | yes | | Path to certificate of a certification authority in pem format, that issued the TLS certificate of the telemetry remote server. |
|
| `trusted_ca` | `string` | yes | | Path to certificate of a certification authority in pem format, that issued the TLS certificate of the telemetry remote server. |
|
||||||
|
@ -752,12 +762,14 @@ Section for well-known containers to store s3-related data and settings.
|
||||||
containers:
|
containers:
|
||||||
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
lifecycle: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
lifecycle: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||||
|
accessbox: ExnA1gSY3kzgomi2wJxNyWo1ytWv9VAKXRE55fNXEPL2
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|-------------|----------|---------------|---------------|-------------------------------------------------------------------------------------------|
|
|-------------|----------|---------------|---------------|-------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `cors` | `string` | no | | Container name for CORS configurations. If not set, container of the bucket is used. |
|
| `cors` | `string` | no | | Container name for CORS configurations. If not set, container of the bucket is used. |
|
||||||
| `lifecycle` | `string` | no | | Container name for lifecycle configurations. If not set, container of the bucket is used. |
|
| `lifecycle` | `string` | no | | Container name for lifecycle configurations. If not set, container of the bucket is used. |
|
||||||
|
| `accessbox` | `string` | no | | Container name to lookup accessbox if custom aws credentials is used. If not set, custom credentials are not supported. |
|
||||||
|
|
||||||
# `vhs` section
|
# `vhs` section
|
||||||
|
|
||||||
|
@ -779,3 +791,42 @@ vhs:
|
||||||
| `vhs_header` | `string` | yes | `X-Frostfs-S3-VHS` | Header for determining whether VHS is enabled for the request. |
|
| `vhs_header` | `string` | yes | `X-Frostfs-S3-VHS` | Header for determining whether VHS is enabled for the request. |
|
||||||
| `servername_header` | `string` | yes | `X-Frostfs-Servername` | Header for determining servername. |
|
| `servername_header` | `string` | yes | `X-Frostfs-Servername` | Header for determining servername. |
|
||||||
| `namespaces` | `map[string]bool` | yes | | A map in which the keys are the name of the namespace, and the values are the flag responsible for enabling VHS for the specified namespace. Overrides global 'enabled' setting even when it is disabled. |
|
| `namespaces` | `map[string]bool` | yes | | A map in which the keys are the name of the namespace, and the values are the flag responsible for enabling VHS for the specified namespace. Overrides global 'enabled' setting even when it is disabled. |
|
||||||
|
|
||||||
|
# `multinet` section
|
||||||
|
|
||||||
|
Configuration of multinet support.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
multinet:
|
||||||
|
enabled: false
|
||||||
|
balancer: roundrobin
|
||||||
|
restrict: false
|
||||||
|
fallback_delay: 300ms
|
||||||
|
subnets:
|
||||||
|
- mask: 1.2.3.4/24
|
||||||
|
source_ips:
|
||||||
|
- 1.2.3.4
|
||||||
|
- 1.2.3.5
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|
|------------------|--------------------------------|---------------|---------------|--------------------------------------------------------------------------------------------|
|
||||||
|
| `enabled` | `bool` | yes | `false` | Enables multinet setting to manage source ip of outcoming requests. |
|
||||||
|
| `balancer` | `string` | yes | `""` | Strategy to pick source IP. By default picks first address. Supports `roundrobin` setting. |
|
||||||
|
| `restrict` | `bool` | yes | `false` | Restricts requests to an undefined subnets. |
|
||||||
|
| `fallback_delay` | `duration` | yes | `300ms` | Delay between IPv6 and IPv4 fallback stack switch. |
|
||||||
|
| `subnets` | [[]Subnet](#subnet-subsection) | yes | | Set of subnets to apply multinet dial settings. |
|
||||||
|
|
||||||
|
#### `subnet` subsection
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- mask: 1.2.3.4/24
|
||||||
|
source_ips:
|
||||||
|
- 1.2.3.4
|
||||||
|
- 1.2.3.5
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|
|--------------|------------|---------------|---------------|----------------------------------------------------------------------|
|
||||||
|
| `mask` | `string` | yes | | Destination subnet. |
|
||||||
|
| `source_ips` | `[]string` | yes | | Array of source IP addresses to use when dialing destination subnet. |
|
||||||
|
|
|
@ -21,6 +21,7 @@ package AccessBox {
|
||||||
SeedKey => Encoded public seed key
|
SeedKey => Encoded public seed key
|
||||||
List of Gates *--> Gate
|
List of Gates *--> Gate
|
||||||
List of container policies *--> ContainerPolicy
|
List of container policies *--> ContainerPolicy
|
||||||
|
IsCustom => True if SecretKey was imported and must be treated as it is
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
23
go.mod
23
go.mod
|
@ -3,10 +3,11 @@ module git.frostfs.info/TrueCloudLab/frostfs-s3-gw
|
||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240916093537-13fa0da3741e
|
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20241011114054-f0fc40e116d1
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e
|
git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751d48b
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20240909114314-666d326cc573
|
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20240909114314-666d326cc573
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240918095938-e580ee991d98
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241022124111-5361f0ecebd3
|
||||||
|
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972
|
||||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b
|
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b
|
||||||
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
|
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
|
||||||
github.com/aws/aws-sdk-go v1.44.6
|
github.com/aws/aws-sdk-go v1.44.6
|
||||||
|
@ -17,7 +18,7 @@ require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/minio/sio v0.3.0
|
github.com/minio/sio v0.3.0
|
||||||
github.com/mr-tron/base58 v1.2.0
|
github.com/mr-tron/base58 v1.2.0
|
||||||
github.com/nspcc-dev/neo-go v0.106.2
|
github.com/nspcc-dev/neo-go v0.106.3
|
||||||
github.com/panjf2000/ants/v2 v2.5.0
|
github.com/panjf2000/ants/v2 v2.5.0
|
||||||
github.com/prometheus/client_golang v1.19.0
|
github.com/prometheus/client_golang v1.19.0
|
||||||
github.com/prometheus/client_model v0.5.0
|
github.com/prometheus/client_model v0.5.0
|
||||||
|
@ -27,13 +28,14 @@ require (
|
||||||
github.com/ssgreg/journald v1.0.0
|
github.com/ssgreg/journald v1.0.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4
|
github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.27.2
|
||||||
go.opentelemetry.io/otel v1.28.0
|
go.opentelemetry.io/otel v1.28.0
|
||||||
go.opentelemetry.io/otel/trace v1.28.0
|
go.opentelemetry.io/otel/trace v1.28.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.24.0
|
golang.org/x/crypto v0.24.0
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/net v0.26.0
|
golang.org/x/net v0.26.0
|
||||||
|
golang.org/x/sys v0.22.0
|
||||||
golang.org/x/text v0.16.0
|
golang.org/x/text v0.16.0
|
||||||
google.golang.org/grpc v1.66.2
|
google.golang.org/grpc v1.66.2
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2
|
||||||
|
@ -46,7 +48,7 @@ require (
|
||||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
||||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||||
github.com/aws/smithy-go v1.20.3 // indirect
|
github.com/aws/smithy-go v1.20.3 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
@ -62,6 +64,7 @@ require (
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/holiman/uint256 v1.2.4 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
@ -69,9 +72,10 @@ require (
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 // indirect
|
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 // indirect
|
||||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d // indirect
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec // indirect
|
||||||
github.com/nspcc-dev/rfc6979 v0.2.1 // indirect
|
github.com/nspcc-dev/rfc6979 v0.2.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||||
|
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
@ -82,7 +86,7 @@ require (
|
||||||
github.com/subosito/gotenv v1.4.2 // indirect
|
github.com/subosito/gotenv v1.4.2 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
|
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
|
||||||
github.com/twmb/murmur3 v1.1.8 // indirect
|
github.com/twmb/murmur3 v1.1.8 // indirect
|
||||||
github.com/urfave/cli v1.22.5 // indirect
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||||
go.etcd.io/bbolt v1.3.9 // indirect
|
go.etcd.io/bbolt v1.3.9 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
|
||||||
|
@ -92,7 +96,6 @@ require (
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
|
||||||
golang.org/x/term v0.21.0 // indirect
|
golang.org/x/term v0.21.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
|
48
go.sum
48
go.sum
|
@ -36,18 +36,20 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240916093537-13fa0da3741e h1:740ABnOBYx4o6jxULHdSSnVW2fYIO35ohg+Uz59sxd0=
|
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20241011114054-f0fc40e116d1 h1:ivcdxQeQDnx4srF2ezoaeVlF0FAycSAztwfIUJnUI4s=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240916093537-13fa0da3741e/go.mod h1:F5GS7hRb62PUy5sTYDC4ajVdeffoAfjHSSHTKUJEaYU=
|
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20241011114054-f0fc40e116d1/go.mod h1:F5GS7hRb62PUy5sTYDC4ajVdeffoAfjHSSHTKUJEaYU=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e h1:kcBqZBiFIUBATUqEuvVigtkJJWQ2Gug/eYXn967o3M4=
|
git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751d48b h1:WZkY4KUr90WOxxqf+PtXYGINr9xXkh19Mz+wnmgg/Do=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
|
git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751d48b/go.mod h1:5fSm/l5xSjGWqsPUffSdboiGFUHa7y/1S0fvxzQowN8=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
|
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
|
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20240909114314-666d326cc573 h1:6qCcm1oqFbmf9C5AauXzrL5OPGnTbI9HoB/jAtD9274=
|
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20240909114314-666d326cc573 h1:6qCcm1oqFbmf9C5AauXzrL5OPGnTbI9HoB/jAtD9274=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20240909114314-666d326cc573/go.mod h1:kbwB4v2o6RyOfCo9kEFeUDZIX3LKhmS0yXPrtvzkQ1g=
|
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20240909114314-666d326cc573/go.mod h1:kbwB4v2o6RyOfCo9kEFeUDZIX3LKhmS0yXPrtvzkQ1g=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240918095938-e580ee991d98 h1:ijUci3thz0EwWkuRJDocW5D1RkVAJlt9xNG4CYepC90=
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241022124111-5361f0ecebd3 h1:f7jan6eBDN88DKnKj8GKyWpfjBbSzjDALcDejYKRgCs=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240918095938-e580ee991d98/go.mod h1:GeNpo12HcEW4J412sH5yf8xFYapxlrt5fcYzRwg0Ino=
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241022124111-5361f0ecebd3/go.mod h1:3txOjFJ8M/JFs01h7xOrnQHVn6hZgDNA16ivyUlu1iU=
|
||||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
||||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
|
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
|
||||||
|
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 h1:/960fWeyn2AFHwQUwDsWB3sbP6lTEnFnMzLMM6tx6N8=
|
||||||
|
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972/go.mod h1:2hM42MBrlhvN6XToaW6OWNk5ZLcu1FhaukGgxtfpDDI=
|
||||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b h1:M50kdfrf/h8c3cz0bJ2AEUcbXvAlPFVC1Wp1WkfZ/8E=
|
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b h1:M50kdfrf/h8c3cz0bJ2AEUcbXvAlPFVC1Wp1WkfZ/8E=
|
||||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b/go.mod h1:GZTk55RI4dKzsK6BCn5h2xxE28UHNfgoq/NJxW/LQ6A=
|
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b/go.mod h1:GZTk55RI4dKzsK6BCn5h2xxE28UHNfgoq/NJxW/LQ6A=
|
||||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA=
|
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA=
|
||||||
|
@ -60,8 +62,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V2KhTBPf+Sc=
|
github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V2KhTBPf+Sc=
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
|
github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||||
github.com/aws/aws-sdk-go v1.44.6 h1:Y+uHxmZfhRTLX2X3khkdxCoTZAyGEX21aOUHe1U6geg=
|
github.com/aws/aws-sdk-go v1.44.6 h1:Y+uHxmZfhRTLX2X3khkdxCoTZAyGEX21aOUHe1U6geg=
|
||||||
github.com/aws/aws-sdk-go v1.44.6/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
github.com/aws/aws-sdk-go v1.44.6/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
|
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
|
||||||
|
@ -92,7 +94,6 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj
|
||||||
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
||||||
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
|
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
|
||||||
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
|
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -228,12 +229,14 @@ github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iP
|
||||||
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
||||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||||
|
github.com/nspcc-dev/dbft v0.2.0 h1:sDwsQES600OSIMncV176t2SX5OvB14lzeOAyKFOkbMI=
|
||||||
|
github.com/nspcc-dev/dbft v0.2.0/go.mod h1:oFE6paSC/yfFh9mcNU6MheMGOYXK9+sPiRk3YMoz49o=
|
||||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 h1:mD9hU3v+zJcnHAVmHnZKt3I++tvn30gBj2rP2PocZMk=
|
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 h1:mD9hU3v+zJcnHAVmHnZKt3I++tvn30gBj2rP2PocZMk=
|
||||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2/go.mod h1:U5VfmPNM88P4RORFb6KSUVBdJBDhlqggJZYGXGPxOcc=
|
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2/go.mod h1:U5VfmPNM88P4RORFb6KSUVBdJBDhlqggJZYGXGPxOcc=
|
||||||
github.com/nspcc-dev/neo-go v0.106.2 h1:KXSJ2J5Oacc7LrX3r4jvnC8ihKqHs5NB21q4f2S3r9o=
|
github.com/nspcc-dev/neo-go v0.106.3 h1:HEyhgkjQY+HfBzotMJ12xx2VuOUphkngZ4kEkjvXDtE=
|
||||||
github.com/nspcc-dev/neo-go v0.106.2/go.mod h1:Ojwfx3/lv0VTeEHMpQ17g0wTnXcCSoFQVq5GEeCZmGo=
|
github.com/nspcc-dev/neo-go v0.106.3/go.mod h1:3vEwJ2ld12N7HRGCaH/l/7EwopplC/+8XdIdPDNmD/M=
|
||||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d h1:Vcb7YkZuUSSIC+WF/xV3UDfHbAxZgyT2zGleJP3Ig5k=
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec h1:vDrbVXF2+2uP0RlkZmem3QYATcXCu9BzzGGCNsNcK7Q=
|
||||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
|
||||||
github.com/nspcc-dev/rfc6979 v0.2.1 h1:8wWxkamHWFmO790GsewSoKUSJjVnL1fmdRpokU/RgRM=
|
github.com/nspcc-dev/rfc6979 v0.2.1 h1:8wWxkamHWFmO790GsewSoKUSJjVnL1fmdRpokU/RgRM=
|
||||||
github.com/nspcc-dev/rfc6979 v0.2.1/go.mod h1:Tk7h5kyUWkhjyO3zUgFFhy1v2vQv3BvQEntakdtqrWc=
|
github.com/nspcc-dev/rfc6979 v0.2.1/go.mod h1:Tk7h5kyUWkhjyO3zUgFFhy1v2vQv3BvQEntakdtqrWc=
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||||
|
@ -249,6 +252,8 @@ github.com/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q
|
||||||
github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
|
github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||||
|
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||||
|
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -265,10 +270,8 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
|
@ -286,6 +289,8 @@ github.com/ssgreg/journald v1.0.0/go.mod h1:RUckwmTM8ghGWPslq2+ZBZzbb9/2KgjzYZ4J
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
@ -303,10 +308,10 @@ github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4 h1:GpfJ7
|
||||||
github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4/go.mod h1:f3jBhpWvuZmue0HZK52GzRHJOYHYSILs/c8+K2S/J+o=
|
github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4/go.mod h1:f3jBhpWvuZmue0HZK52GzRHJOYHYSILs/c8+K2S/J+o=
|
||||||
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
|
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
|
||||||
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||||
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
|
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
@ -361,8 +366,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
@ -671,7 +676,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
||||||
|
@ -28,18 +29,18 @@ const (
|
||||||
|
|
||||||
// AuthmateFrostFS is a mediator which implements authmate.FrostFS through pool.Pool.
|
// AuthmateFrostFS is a mediator which implements authmate.FrostFS through pool.Pool.
|
||||||
type AuthmateFrostFS struct {
|
type AuthmateFrostFS struct {
|
||||||
frostFS layer.FrostFS
|
frostFS frostfs.FrostFS
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthmateFrostFS creates new AuthmateFrostFS using provided pool.Pool.
|
// NewAuthmateFrostFS creates new AuthmateFrostFS using provided pool.Pool.
|
||||||
func NewAuthmateFrostFS(frostFS layer.FrostFS, log *zap.Logger) *AuthmateFrostFS {
|
func NewAuthmateFrostFS(frostFS frostfs.FrostFS, log *zap.Logger) *AuthmateFrostFS {
|
||||||
return &AuthmateFrostFS{frostFS: frostFS, log: log}
|
return &AuthmateFrostFS{frostFS: frostFS, log: log}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerExists implements authmate.FrostFS interface method.
|
// ContainerExists implements authmate.FrostFS interface method.
|
||||||
func (x *AuthmateFrostFS) ContainerExists(ctx context.Context, idCnr cid.ID) error {
|
func (x *AuthmateFrostFS) ContainerExists(ctx context.Context, idCnr cid.ID) error {
|
||||||
_, err := x.frostFS.Container(ctx, layer.PrmContainer{ContainerID: idCnr})
|
_, err := x.frostFS.Container(ctx, frostfs.PrmContainer{ContainerID: idCnr})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get container via connection pool: %w", err)
|
return fmt.Errorf("get container via connection pool: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +61,7 @@ func (x *AuthmateFrostFS) CreateContainer(ctx context.Context, prm authmate.PrmC
|
||||||
basicACL.AllowOp(acl.OpObjectHead, acl.RoleOthers)
|
basicACL.AllowOp(acl.OpObjectHead, acl.RoleOthers)
|
||||||
basicACL.AllowOp(acl.OpObjectSearch, acl.RoleOthers)
|
basicACL.AllowOp(acl.OpObjectSearch, acl.RoleOthers)
|
||||||
|
|
||||||
res, err := x.frostFS.CreateContainer(ctx, layer.PrmContainerCreate{
|
res, err := x.frostFS.CreateContainer(ctx, frostfs.PrmContainerCreate{
|
||||||
Creator: prm.Owner,
|
Creator: prm.Owner,
|
||||||
Policy: prm.Policy,
|
Policy: prm.Policy,
|
||||||
Name: prm.FriendlyName,
|
Name: prm.FriendlyName,
|
||||||
|
@ -73,20 +74,45 @@ func (x *AuthmateFrostFS) CreateContainer(ctx context.Context, prm authmate.PrmC
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCredsObject implements authmate.FrostFS interface method.
|
// GetCredsObject implements authmate.FrostFS interface method.
|
||||||
func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, addr oid.Address) (*object.Object, error) {
|
func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, prm tokens.PrmGetCredsObject) (obj *object.Object, err error) {
|
||||||
versions, err := x.getCredVersions(ctx, addr)
|
var readObjAddr *oid.Address
|
||||||
|
defer func() {
|
||||||
|
if prm.FallbackAddress == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil && (readObjAddr == nil || !readObjAddr.Equals(*prm.FallbackAddress)) {
|
||||||
|
obj, err = x.readObject(ctx, *prm.FallbackAddress)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
versions, err := x.getCredVersions(ctx, prm.Container, prm.AccessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
credObjID := addr.Object()
|
var addr oid.Address
|
||||||
|
isCustom := addr.DecodeString(strings.ReplaceAll(prm.AccessKeyID, "0", "/")) != nil
|
||||||
|
|
||||||
|
var credObjID oid.ID
|
||||||
if last := versions.GetLast(); last != nil {
|
if last := versions.GetLast(); last != nil {
|
||||||
credObjID = last.ObjID
|
credObjID = last.ObjID
|
||||||
|
} else if !isCustom {
|
||||||
|
credObjID = addr.Object()
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("%w: '%s'", tokens.ErrCustomAccessKeyIDNotFound, prm.AccessKeyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := x.frostFS.GetObject(ctx, layer.PrmObjectGet{
|
readObjAddr = &oid.Address{}
|
||||||
|
readObjAddr.SetContainer(prm.Container)
|
||||||
|
readObjAddr.SetObject(credObjID)
|
||||||
|
|
||||||
|
return x.readObject(ctx, *readObjAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AuthmateFrostFS) readObject(ctx context.Context, addr oid.Address) (*object.Object, error) {
|
||||||
|
res, err := x.frostFS.GetObject(ctx, frostfs.PrmObjectGet{
|
||||||
Container: addr.Container(),
|
Container: addr.Container(),
|
||||||
Object: credObjID,
|
Object: addr.Object(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -111,17 +137,20 @@ func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, addr oid.Address)
|
||||||
func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObjectCreate) (oid.ID, error) {
|
func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObjectCreate) (oid.ID, error) {
|
||||||
attributes := [][2]string{{objectv2.SysAttributeExpEpoch, strconv.FormatUint(prm.ExpirationEpoch, 10)}}
|
attributes := [][2]string{{objectv2.SysAttributeExpEpoch, strconv.FormatUint(prm.ExpirationEpoch, 10)}}
|
||||||
|
|
||||||
if prm.NewVersionFor != nil {
|
if prm.NewVersionForAccessKeyID != "" {
|
||||||
var addr oid.Address
|
versions, err := x.getCredVersions(ctx, prm.Container, prm.NewVersionForAccessKeyID)
|
||||||
addr.SetContainer(prm.Container)
|
|
||||||
addr.SetObject(*prm.NewVersionFor)
|
|
||||||
|
|
||||||
versions, err := x.getCredVersions(ctx, addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oid.ID{}, err
|
return oid.ID{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if versions.GetLast() == nil {
|
if versions.GetLast() == nil {
|
||||||
|
var addr oid.Address
|
||||||
|
isCustom := addr.DecodeString(strings.ReplaceAll(prm.NewVersionForAccessKeyID, "0", "/")) != nil
|
||||||
|
|
||||||
|
if isCustom {
|
||||||
|
return oid.ID{}, fmt.Errorf("creds object for accessKeyId '%s' not found", prm.NewVersionForAccessKeyID)
|
||||||
|
}
|
||||||
|
|
||||||
versions.AppendVersion(&crdt.ObjectVersion{ObjID: addr.Object()})
|
versions.AppendVersion(&crdt.ObjectVersion{ObjID: addr.Object()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +159,8 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, versions.Name()})
|
attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, versions.Name()})
|
||||||
|
} else if prm.CustomAccessKey != "" {
|
||||||
|
attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, prm.CustomAccessKey})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, attr := range prm.CustomAttributes {
|
for _, attr := range prm.CustomAttributes {
|
||||||
|
@ -137,7 +168,7 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
|
||||||
attributes = append(attributes, [2]string{attr.Key(), attr.Value()})
|
attributes = append(attributes, [2]string{attr.Key(), attr.Value()})
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := x.frostFS.CreateObject(ctx, layer.PrmObjectCreate{
|
res, err := x.frostFS.CreateObject(ctx, frostfs.PrmObjectCreate{
|
||||||
Container: prm.Container,
|
Container: prm.Container,
|
||||||
Filepath: prm.Filepath,
|
Filepath: prm.Filepath,
|
||||||
Attributes: attributes,
|
Attributes: attributes,
|
||||||
|
@ -150,21 +181,20 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
|
||||||
return res.ObjectID, nil
|
return res.ObjectID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, addr oid.Address) (*crdt.ObjectVersions, error) {
|
func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, cnrID cid.ID, accessKeyID string) (*crdt.ObjectVersions, error) {
|
||||||
objCredSystemName := credVersionSysName(addr.Container(), addr.Object())
|
credVersions, err := x.frostFS.SearchObjects(ctx, frostfs.PrmObjectSearch{
|
||||||
credVersions, err := x.frostFS.SearchObjects(ctx, layer.PrmObjectSearch{
|
Container: cnrID,
|
||||||
Container: addr.Container(),
|
ExactAttribute: [2]string{accessBoxCRDTNameAttr, accessKeyID},
|
||||||
ExactAttribute: [2]string{accessBoxCRDTNameAttr, objCredSystemName},
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("search s3 access boxes: %w", err)
|
return nil, fmt.Errorf("search s3 access boxes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
versions := crdt.NewObjectVersions(objCredSystemName)
|
versions := crdt.NewObjectVersions(accessKeyID)
|
||||||
|
|
||||||
for _, id := range credVersions {
|
for _, id := range credVersions {
|
||||||
objVersion, err := x.frostFS.HeadObject(ctx, layer.PrmObjectHead{
|
objVersion, err := x.frostFS.HeadObject(ctx, frostfs.PrmObjectHead{
|
||||||
Container: addr.Container(),
|
Container: cnrID,
|
||||||
Object: id,
|
Object: id,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -184,7 +214,3 @@ func (x *AuthmateFrostFS) reqLogger(ctx context.Context) *zap.Logger {
|
||||||
}
|
}
|
||||||
return x.log
|
return x.log
|
||||||
}
|
}
|
||||||
|
|
||||||
func credVersionSysName(cnrID cid.ID, objID oid.ID) string {
|
|
||||||
return cnrID.EncodeToString() + "0" + objID.EncodeToString()
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,22 +2,29 @@ package frostfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
||||||
"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"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
"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"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetCredsObject(t *testing.T) {
|
func TestCredsObject(t *testing.T) {
|
||||||
ctx, bktName, payload, newPayload := context.Background(), "bucket", []byte("payload"), []byte("new-payload")
|
ctx, bktName, payload, newPayload := context.Background(), "bucket", []byte("payload"), []byte("new-payload")
|
||||||
|
|
||||||
key, err := keys.NewPrivateKey()
|
key, err := keys.NewPrivateKey()
|
||||||
|
@ -38,34 +45,242 @@ func TestGetCredsObject(t *testing.T) {
|
||||||
|
|
||||||
frostfs := NewAuthmateFrostFS(layer.NewTestFrostFS(key), zaptest.NewLogger(t))
|
frostfs := NewAuthmateFrostFS(layer.NewTestFrostFS(key), zaptest.NewLogger(t))
|
||||||
|
|
||||||
cid, err := frostfs.CreateContainer(ctx, authmate.PrmContainerCreate{
|
cnrID, err := frostfs.CreateContainer(ctx, authmate.PrmContainerCreate{
|
||||||
FriendlyName: bktName,
|
FriendlyName: bktName,
|
||||||
Owner: userID,
|
Owner: userID,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
objID, err := frostfs.CreateObject(ctx, tokens.PrmObjectCreate{
|
t.Run("regular access key", func(t *testing.T) {
|
||||||
Container: cid,
|
attr1 := object.NewAttribute()
|
||||||
Payload: payload,
|
attr1.SetKey("attr1")
|
||||||
|
attr1.SetValue("val1")
|
||||||
|
|
||||||
|
prm := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Filepath: "regular-obj",
|
||||||
|
ExpirationEpoch: 10,
|
||||||
|
Payload: payload,
|
||||||
|
CustomAttributes: []object.Attribute{*attr1},
|
||||||
|
}
|
||||||
|
|
||||||
|
objID, err := frostfs.CreateObject(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
accessKeyID := cnrID.EncodeToString() + "0" + objID.EncodeToString()
|
||||||
|
|
||||||
|
obj, err := frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertParamsSet(t, prm, obj, userID)
|
||||||
|
|
||||||
|
t.Run("update existing", func(t *testing.T) {
|
||||||
|
attr2 := object.NewAttribute()
|
||||||
|
attr2.SetKey("attr2")
|
||||||
|
attr2.SetValue("val2")
|
||||||
|
|
||||||
|
prmNew := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Filepath: "regular-obj-new",
|
||||||
|
ExpirationEpoch: 11,
|
||||||
|
Payload: newPayload,
|
||||||
|
CustomAttributes: []object.Attribute{*attr2},
|
||||||
|
NewVersionForAccessKeyID: accessKeyID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = frostfs.CreateObject(ctx, prmNew)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
obj, err = frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertParamsSet(t, prmNew, obj, userID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("update not existing", func(t *testing.T) {
|
||||||
|
attr2 := object.NewAttribute()
|
||||||
|
attr2.SetKey("attr2")
|
||||||
|
attr2.SetValue("val2")
|
||||||
|
|
||||||
|
addr := oidtest.Address()
|
||||||
|
accessKeyIDNotExisting := getAccessKeyID(addr)
|
||||||
|
|
||||||
|
prmNew := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Filepath: "regular-obj-new",
|
||||||
|
ExpirationEpoch: 11,
|
||||||
|
Payload: newPayload,
|
||||||
|
CustomAttributes: []object.Attribute{*attr2},
|
||||||
|
NewVersionForAccessKeyID: accessKeyIDNotExisting,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = frostfs.CreateObject(ctx, prmNew)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
obj, err = frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyIDNotExisting})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertParamsSet(t, prmNew, obj, userID)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var addr oid.Address
|
t.Run("custom access key", func(t *testing.T) {
|
||||||
addr.SetContainer(cid)
|
attr1 := object.NewAttribute()
|
||||||
addr.SetObject(objID)
|
attr1.SetKey("attr1")
|
||||||
|
attr1.SetValue("val1")
|
||||||
|
|
||||||
obj, err := frostfs.GetCredsObject(ctx, addr)
|
accessKeyID := "custom-access-key-id"
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, payload, obj.Payload())
|
|
||||||
|
|
||||||
_, err = frostfs.CreateObject(ctx, tokens.PrmObjectCreate{
|
prm := tokens.PrmObjectCreate{
|
||||||
Container: cid,
|
Container: cnrID,
|
||||||
Payload: newPayload,
|
Filepath: "custom-obj",
|
||||||
NewVersionFor: &objID,
|
ExpirationEpoch: 10,
|
||||||
|
Payload: payload,
|
||||||
|
CustomAccessKey: accessKeyID,
|
||||||
|
CustomAttributes: []object.Attribute{*attr1},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = frostfs.CreateObject(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
obj, err := frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertParamsSet(t, prm, obj, userID)
|
||||||
|
|
||||||
|
t.Run("update", func(t *testing.T) {
|
||||||
|
attr2 := object.NewAttribute()
|
||||||
|
attr2.SetKey("attr2")
|
||||||
|
attr2.SetValue("val2")
|
||||||
|
|
||||||
|
prmNew := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Filepath: "custom-obj-new",
|
||||||
|
ExpirationEpoch: 11,
|
||||||
|
Payload: newPayload,
|
||||||
|
CustomAttributes: []object.Attribute{*attr2},
|
||||||
|
NewVersionForAccessKeyID: accessKeyID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = frostfs.CreateObject(ctx, prmNew)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
obj, err = frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertParamsSet(t, prmNew, obj, userID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("update not existing", func(t *testing.T) {
|
||||||
|
accessKeyIDNotExisting := "unknown"
|
||||||
|
|
||||||
|
prmNew := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Payload: newPayload,
|
||||||
|
NewVersionForAccessKeyID: accessKeyIDNotExisting,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = frostfs.CreateObject(ctx, prmNew)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
obj, err = frostfs.GetCredsObject(ctx, addr)
|
t.Run("fallback", func(t *testing.T) {
|
||||||
require.NoError(t, err)
|
t.Run("regular", func(t *testing.T) {
|
||||||
require.Equal(t, newPayload, obj.Payload())
|
prm := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Filepath: "regular-obj",
|
||||||
|
Payload: payload,
|
||||||
|
}
|
||||||
|
|
||||||
|
objID, err := frostfs.CreateObject(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
accessKeyID := cnrID.EncodeToString() + "0" + objID.EncodeToString()
|
||||||
|
|
||||||
|
prmNew := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Filepath: "regular-obj-new",
|
||||||
|
Payload: newPayload,
|
||||||
|
NewVersionForAccessKeyID: accessKeyID,
|
||||||
|
}
|
||||||
|
|
||||||
|
objIDNew, err := frostfs.CreateObject(ctx, prmNew)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addr := newAddress(cnrID, objID)
|
||||||
|
prmFallback := tokens.PrmGetCredsObject{
|
||||||
|
Container: cnrID,
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
FallbackAddress: &addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
frostfs.frostFS.(*layer.TestFrostFS).SetObjectError(newAddress(cnrID, objIDNew), errors.New("error"))
|
||||||
|
|
||||||
|
obj, err := frostfs.GetCredsObject(ctx, prmFallback)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertParamsSet(t, prm, obj, userID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("custom", func(t *testing.T) {
|
||||||
|
prm := tokens.PrmObjectCreate{
|
||||||
|
Container: cnrID,
|
||||||
|
Filepath: "custom-obj",
|
||||||
|
ExpirationEpoch: 10,
|
||||||
|
Payload: payload,
|
||||||
|
CustomAccessKey: "custom-access-key-id",
|
||||||
|
}
|
||||||
|
|
||||||
|
objID, err := frostfs.CreateObject(ctx, prm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addr := newAddress(cnrID, objID)
|
||||||
|
prmFallback := tokens.PrmGetCredsObject{
|
||||||
|
Container: cnrID,
|
||||||
|
AccessKeyID: "unknown",
|
||||||
|
FallbackAddress: &addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := frostfs.GetCredsObject(ctx, prmFallback)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertParamsSet(t, prm, obj, userID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAddress(cnr cid.ID, obj oid.ID) oid.Address {
|
||||||
|
var addr oid.Address
|
||||||
|
addr.SetContainer(cnr)
|
||||||
|
addr.SetObject(obj)
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertParamsSet(t *testing.T, prm tokens.PrmObjectCreate, obj *object.Object, userID user.ID) {
|
||||||
|
require.Equal(t, prm.Payload, obj.Payload())
|
||||||
|
require.True(t, userID.Equals(obj.OwnerID()), "owners not matched")
|
||||||
|
|
||||||
|
require.True(t, containerAttribute(obj.Attributes(), object.AttributeFilePath, prm.Filepath), "missing FilePath")
|
||||||
|
require.True(t, containerAttribute(obj.Attributes(), objectv2.SysAttributeExpEpoch, strconv.FormatUint(prm.ExpirationEpoch, 10)), "missing expiration epoch")
|
||||||
|
|
||||||
|
var crdtName string
|
||||||
|
if prm.CustomAccessKey != "" {
|
||||||
|
crdtName = prm.CustomAccessKey
|
||||||
|
} else if prm.NewVersionForAccessKeyID != "" {
|
||||||
|
crdtName = prm.NewVersionForAccessKeyID
|
||||||
|
}
|
||||||
|
if crdtName != "" {
|
||||||
|
require.Truef(t, containerAttribute(obj.Attributes(), accessBoxCRDTNameAttr, crdtName), "wrong crdt name '%s'", crdtName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, attr := range prm.CustomAttributes {
|
||||||
|
require.True(t, containerAttribute(obj.Attributes(), attr.Key(), attr.Value()), "missing custom attribute")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerAttribute(attrs []object.Attribute, key, val string) bool {
|
||||||
|
for _, attr := range attrs {
|
||||||
|
if attr.Key() == key && attr.Value() == val {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccessKeyID(addr oid.Address) string {
|
||||||
|
return strings.ReplaceAll(addr.EncodeToString(), "/", "0")
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue