Support custom s3 credentials #515
28 changed files with 1098 additions and 437 deletions
|
@ -9,6 +9,7 @@ This document outlines major changes between releases.
|
||||||
- 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)
|
- Add `sign` command to `frostfs-s3-authmate` (#467)
|
||||||
|
- Support custom aws credentials (#509)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Update go version to go1.19 (#470)
|
- Update go version to go1.19 (#470)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"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,12 +87,13 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,11 +103,6 @@ func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
||||||
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.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", apierr.GetAPIError(apierr.ErrInvalidAccessKeyID), accessKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
signedFields := strings.Split(submatches["signed_header_fields"], ";")
|
signedFields := strings.Split(submatches["signed_header_fields"], ";")
|
||||||
|
|
||||||
return &AuthHeader{
|
return &AuthHeader{
|
||||||
|
@ -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", apierr.GetAPIError(apierr.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,6 +208,20 @@ 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)
|
||||||
|
@ -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
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
||||||
frosterr "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,7 +384,7 @@ 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 {
|
||||||
|
@ -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,7 +562,7 @@ 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 {
|
||||||
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
@ -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,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ 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
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
namespaces Namespaces
|
namespaces Namespaces
|
||||||
|
@ -132,18 +133,7 @@ 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)
|
objPool, treePool, key := getPools(ctx, log.logger, v)
|
||||||
|
|
||||||
cfg := tokens.Config{
|
|
||||||
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,
|
||||||
|
@ -162,6 +152,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)
|
||||||
|
@ -171,9 +163,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 {
|
||||||
|
@ -484,6 +493,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()
|
||||||
|
@ -1104,21 +1121,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) {
|
||||||
|
|
|
@ -208,6 +208,7 @@ const ( // Settings.
|
||||||
// Containers.
|
// Containers.
|
||||||
cfgContainersCORS = "containers.cors"
|
cfgContainersCORS = "containers.cors"
|
||||||
cfgContainersLifecycle = "containers.lifecycle"
|
cfgContainersLifecycle = "containers.lifecycle"
|
||||||
|
cfgContainersAccessBox = "containers.accessbox"
|
||||||
|
|
||||||
// Command line args.
|
// Command line args.
|
||||||
cmdHelp = "help"
|
cmdHelp = "help"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -761,12 +761,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
|
||||||
|
|
||||||
|
|
|
@ -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 |
|
@ -6,6 +6,7 @@ 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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{
|
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 {
|
||||||
|
@ -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, frostfs.PrmObjectSearch{
|
||||||
Container: addr.Container(),
|
Container: cnrID,
|
||||||
ExactAttribute: [2]string{accessBoxCRDTNameAttr, objCredSystemName},
|
ExactAttribute: [2]string{accessBoxCRDTNameAttr, accessKeyID},
|
||||||
})
|
})
|
||||||
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, frostfs.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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -36,6 +37,31 @@ func ResolveContractHash(contractHash, rpcAddress string) (util.Uint160, error)
|
||||||
return nns.ResolveContractHash(domain)
|
return nns.ResolveContractHash(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveContainerID determine container id by resolving NNS name.
|
||||||
|
func ResolveContainerID(containerID, rpcAddress string) (cid.ID, error) {
|
||||||
|
var cnrID cid.ID
|
||||||
|
if err := cnrID.DecodeString(containerID); err == nil {
|
||||||
|
return cnrID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
splitName := strings.Split(containerID, ".")
|
||||||
|
if len(splitName) != 2 {
|
||||||
|
return cid.ID{}, fmt.Errorf("invalid container name: '%s'", containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var domain container.Domain
|
||||||
|
domain.SetName(splitName[0])
|
||||||
|
domain.SetZone(splitName[1])
|
||||||
|
|
||||||
|
var nns ns.NNS
|
||||||
|
if err := nns.Dial(rpcAddress); err != nil {
|
||||||
|
return cid.ID{}, fmt.Errorf("dial nns '%s': %w", rpcAddress, err)
|
||||||
|
}
|
||||||
|
defer nns.Close()
|
||||||
|
|
||||||
|
return nns.ResolveContainerDomain(domain)
|
||||||
|
}
|
||||||
|
|
||||||
func TimeToEpoch(ni *netmap.NetworkInfo, now, t time.Time) (uint64, error) {
|
func TimeToEpoch(ni *netmap.NetworkInfo, now, t time.Time) (uint64, error) {
|
||||||
duration := t.Sub(now)
|
duration := t.Sub(now)
|
||||||
durationAbs := duration.Abs()
|
durationAbs := duration.Abs()
|
||||||
|
|
|
@ -159,6 +159,7 @@ const (
|
||||||
FoundSeveralSystemNodes = "found several system nodes"
|
FoundSeveralSystemNodes = "found several system nodes"
|
||||||
FailedToParsePartInfo = "failed to parse part info"
|
FailedToParsePartInfo = "failed to parse part info"
|
||||||
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
|
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
|
||||||
|
CouldNotFetchAccessBoxContainerInfo = "couldn't fetch AccessBox container info"
|
||||||
CloseCredsObjectPayload = "close creds object payload"
|
CloseCredsObjectPayload = "close creds object payload"
|
||||||
CouldntDeleteLifecycleObject = "couldn't delete lifecycle configuration object"
|
CouldntDeleteLifecycleObject = "couldn't delete lifecycle configuration object"
|
||||||
CouldntCacheLifecycleConfiguration = "couldn't cache lifecycle configuration"
|
CouldntCacheLifecycleConfiguration = "couldn't cache lifecycle configuration"
|
||||||
|
@ -171,4 +172,5 @@ const (
|
||||||
FailedToRemoveOldPartNode = "failed to remove old part node"
|
FailedToRemoveOldPartNode = "failed to remove old part node"
|
||||||
CouldntCacheNetworkInfo = "couldn't cache network info"
|
CouldntCacheNetworkInfo = "couldn't cache network info"
|
||||||
NotSupported = "not supported"
|
NotSupported = "not supported"
|
||||||
|
CheckCustomAccessKeyIDUniqueness = "check custom access key id uniqueness"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue