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 patch object method (#479)
|
||||
- Add `sign` command to `frostfs-s3-authmate` (#467)
|
||||
- Support custom aws credentials (#509)
|
||||
|
||||
### Changed
|
||||
- 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/creds/accessbox"
|
||||
"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"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
)
|
||||
|
@ -34,6 +35,11 @@ type (
|
|||
postReg *RegexpSubmatcher
|
||||
cli tokens.Credentials
|
||||
allowedAccessKeyIDPrefixes []string // empty slice means all access key ids are allowed
|
||||
settings CenterSettings
|
||||
}
|
||||
|
||||
CenterSettings interface {
|
||||
AccessBoxContainer() (cid.ID, bool)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
|
@ -50,7 +56,6 @@ type (
|
|||
)
|
||||
|
||||
const (
|
||||
accessKeyPartsNum = 2
|
||||
authHeaderPartsNum = 6
|
||||
maxFormSizeMemory = 50 * 1048576 // 50 MB
|
||||
|
||||
|
@ -82,12 +87,13 @@ var ContentSHA256HeaderStandardValue = map[string]struct{}{
|
|||
}
|
||||
|
||||
// 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{
|
||||
cli: creds,
|
||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
|
||||
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)
|
||||
}
|
||||
|
||||
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"], ";")
|
||||
|
||||
return &AuthHeader{
|
||||
|
@ -114,15 +115,6 @@ func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
|||
}, 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 {
|
||||
_, ok := ContentSHA256HeaderStandardValue[key]
|
||||
return ok
|
||||
|
@ -181,14 +173,14 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := getAddress(authHdr.AccessKeyID)
|
||||
cnrID, err := c.getAccessBoxContainer(authHdr.AccessKeyID)
|
||||
if err != nil {
|
||||
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 {
|
||||
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 {
|
||||
|
@ -216,6 +208,20 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
|
|||
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 {
|
||||
if !IsStandardContentSHA256(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"]
|
||||
|
||||
addr, err := getAddress(accessKeyID)
|
||||
cnrID, err := c.getAccessBoxContainer(accessKeyID)
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
||||
frosterr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
||||
"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"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
|
@ -28,11 +29,23 @@ import (
|
|||
"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) {
|
||||
defaultHeader := "AWS4-HMAC-SHA256 Credential=oid0cid/20210809/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=2811ccb9e242f41426738fb1f"
|
||||
|
||||
center := &Center{
|
||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||
settings: ¢erSettingsMock{},
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
|
@ -57,11 +70,6 @@ func TestAuthHeaderParse(t *testing.T) {
|
|||
err: errors.GetAPIError(errors.ErrAuthorizationHeaderMalformed),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
header: strings.ReplaceAll(defaultHeader, "oid0cid", "oidcid"),
|
||||
err: errors.GetAPIError(errors.ErrInvalidAccessKeyID),
|
||||
expected: nil,
|
||||
},
|
||||
} {
|
||||
authHeader, err := center.parseAuthHeader(tc.header)
|
||||
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) {
|
||||
secret := "66be461c3cd429941c55daf42fad2b8153e5a2016ba89c9494d97677cc9d3872"
|
||||
strToSign := "eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLAogICJjb25kaXRpb25zIjogWwogICAgeyJidWNrZXQiOiAiYWNsIn0sCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwKICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL2xvY2FsaG9zdDo4MDg0L2FjbCJ9LAogICAgWyJzdGFydHMtd2l0aCIsICIkQ29udGVudC1UeXBlIiwgImltYWdlLyJdLAogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwKICAgIFsic3RhcnRzLXdpdGgiLCAiJHgtYW16LW1ldGEtdGFnIiwgIiJdLAoKICAgIHsiWC1BbXotQ3JlZGVudGlhbCI6ICI4Vmk0MVBIbjVGMXNzY2J4OUhqMXdmMUU2aERUYURpNndxOGhxTU05NllKdTA1QzVDeUVkVlFoV1E2aVZGekFpTkxXaTlFc3BiUTE5ZDRuR3pTYnZVZm10TS8yMDE1MTIyOS91cy1lYXN0LTEvczMvYXdzNF9yZXF1ZXN0In0sCiAgICB7IngtYW16LWFsZ29yaXRobSI6ICJBV1M0LUhNQUMtU0hBMjU2In0sCiAgICB7IlgtQW16LURhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfSwKICAgIHsieC1pZ25vcmUtdG1wIjogInNvbWV0aGluZyIgfQogIF0KfQ=="
|
||||
|
@ -171,17 +142,17 @@ func TestCheckFormatContentSHA256(t *testing.T) {
|
|||
}
|
||||
|
||||
type frostFSMock struct {
|
||||
objects map[oid.Address]*object.Object
|
||||
objects map[string]*object.Object
|
||||
}
|
||||
|
||||
func newFrostFSMock() *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) {
|
||||
obj, ok := f.objects[address]
|
||||
func (f *frostFSMock) GetCredsObject(_ context.Context, prm tokens.PrmGetCredsObject) (*object.Object, error) {
|
||||
obj, ok := f.objects[prm.AccessKeyID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
@ -208,7 +179,7 @@ func TestAuthenticate(t *testing.T) {
|
|||
GateKey: key.PublicKey(),
|
||||
}}
|
||||
|
||||
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"))
|
||||
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"), false)
|
||||
require.NoError(t, err)
|
||||
data, err := accessBox.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
@ -219,10 +190,10 @@ func TestAuthenticate(t *testing.T) {
|
|||
obj.SetContainerID(addr.Container())
|
||||
obj.SetID(addr.Object())
|
||||
|
||||
frostfs := newFrostFSMock()
|
||||
frostfs.objects[addr] = &obj
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
|
||||
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
||||
frostfs := newFrostFSMock()
|
||||
frostfs.objects[accessKeyID] = &obj
|
||||
|
||||
awsCreds := credentials.NewStaticCredentials(accessKeyID, secret.SecretKey, "")
|
||||
defaultSigner := v4.NewSigner(awsCreds)
|
||||
|
@ -413,7 +384,7 @@ func TestAuthenticate(t *testing.T) {
|
|||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
creds := tokens.New(bigConfig)
|
||||
cntr := New(creds, tc.prefixes)
|
||||
cntr := New(creds, tc.prefixes, ¢erSettingsMock{})
|
||||
box, err := cntr.Authenticate(tc.request)
|
||||
|
||||
if tc.err {
|
||||
|
@ -455,7 +426,7 @@ func TestHTTPPostAuthenticate(t *testing.T) {
|
|||
GateKey: key.PublicKey(),
|
||||
}}
|
||||
|
||||
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"))
|
||||
accessBox, secret, err := accessbox.PackTokens(gateData, []byte("secret"), false)
|
||||
require.NoError(t, err)
|
||||
data, err := accessBox.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
@ -466,10 +437,11 @@ func TestHTTPPostAuthenticate(t *testing.T) {
|
|||
obj.SetContainerID(addr.Container())
|
||||
obj.SetID(addr.Object())
|
||||
|
||||
frostfs := newFrostFSMock()
|
||||
frostfs.objects[addr] = &obj
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
|
||||
frostfs := newFrostFSMock()
|
||||
frostfs.objects[accessKeyID] = &obj
|
||||
|
||||
accessKeyID := addr.Container().String() + "0" + addr.Object().String()
|
||||
invalidAccessKeyID := oidtest.Address().String() + "0" + oidtest.Address().Object().String()
|
||||
|
||||
timeToSign := time.Now()
|
||||
|
@ -590,7 +562,7 @@ func TestHTTPPostAuthenticate(t *testing.T) {
|
|||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
creds := tokens.New(bigConfig)
|
||||
cntr := New(creds, tc.prefixes)
|
||||
cntr := New(creds, tc.prefixes, ¢erSettingsMock{})
|
||||
box, err := cntr.Authenticate(tc.request)
|
||||
|
||||
if tc.err {
|
||||
|
@ -633,3 +605,7 @@ func getRequestWithMultipartForm(t *testing.T, policy, creds, date, sign, fieldN
|
|||
|
||||
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) {
|
||||
m.boxes[addr.String()] = box
|
||||
m.boxes[getAccessKeyID(addr)] = box
|
||||
}
|
||||
|
||||
func (m credentialsMock) GetBox(_ context.Context, addr oid.Address) (*accessbox.Box, []object.Attribute, error) {
|
||||
box, ok := m.boxes[addr.String()]
|
||||
func (m credentialsMock) GetBox(_ context.Context, _ cid.ID, accessKeyID string) (*accessbox.Box, []object.Attribute, error) {
|
||||
box, ok := m.boxes[accessKeyID]
|
||||
if !ok {
|
||||
return nil, nil, &apistatus.ObjectNotFound{}
|
||||
}
|
||||
|
@ -41,11 +41,11 @@ func (m credentialsMock) GetBox(_ context.Context, addr oid.Address) (*accessbox
|
|||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -84,9 +84,10 @@ func TestCheckSign(t *testing.T) {
|
|||
mock.addBox(accessKeyAddr, expBox)
|
||||
|
||||
c := &Center{
|
||||
cli: mock,
|
||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
|
||||
cli: mock,
|
||||
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
|
||||
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
|
||||
settings: ¢erSettingsMock{},
|
||||
}
|
||||
box, err := c.Authenticate(req)
|
||||
require.NoError(t, err)
|
||||
|
|
18
api/cache/accessbox.go
vendored
18
api/cache/accessbox.go
vendored
|
@ -30,6 +30,7 @@ type (
|
|||
Box *accessbox.Box
|
||||
Attributes []object.Attribute
|
||||
PutTime time.Time
|
||||
Address *oid.Address
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -57,8 +58,8 @@ func NewAccessBoxCache(config *Config) *AccessBoxCache {
|
|||
}
|
||||
|
||||
// Get returns a cached accessbox.
|
||||
func (o *AccessBoxCache) Get(address oid.Address) *AccessBoxCacheValue {
|
||||
entry, err := o.cache.Get(address)
|
||||
func (o *AccessBoxCache) Get(accessKeyID string) *AccessBoxCacheValue {
|
||||
entry, err := o.cache.Get(accessKeyID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -74,16 +75,11 @@ func (o *AccessBoxCache) Get(address oid.Address) *AccessBoxCacheValue {
|
|||
}
|
||||
|
||||
// Put stores an accessbox to cache.
|
||||
func (o *AccessBoxCache) Put(address oid.Address, box *accessbox.Box, attrs []object.Attribute) error {
|
||||
val := &AccessBoxCacheValue{
|
||||
Box: box,
|
||||
Attributes: attrs,
|
||||
PutTime: time.Now(),
|
||||
}
|
||||
return o.cache.Set(address, val)
|
||||
func (o *AccessBoxCache) Put(accessKeyID string, val *AccessBoxCacheValue) error {
|
||||
return o.cache.Set(accessKeyID, val)
|
||||
}
|
||||
|
||||
// Delete removes an accessbox from cache.
|
||||
func (o *AccessBoxCache) Delete(address oid.Address) {
|
||||
o.cache.Remove(address)
|
||||
func (o *AccessBoxCache) Delete(accessKeyID string) {
|
||||
o.cache.Remove(accessKeyID)
|
||||
}
|
||||
|
|
24
api/cache/cache_test.go
vendored
24
api/cache/cache_test.go
vendored
|
@ -1,13 +1,14 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||
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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -22,18 +23,21 @@ func TestAccessBoxCacheType(t *testing.T) {
|
|||
|
||||
addr := oidtest.Address()
|
||||
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)
|
||||
val := cache.Get(addr)
|
||||
require.Equal(t, box, val.Box)
|
||||
require.Equal(t, attrs, val.Attributes)
|
||||
resVal := cache.Get(accessKeyID)
|
||||
require.Equal(t, box, resVal.Box)
|
||||
require.Equal(t, 0, observedLog.Len())
|
||||
|
||||
err = cache.cache.Set(addr, "tmp")
|
||||
err = cache.cache.Set(accessKeyID, "tmp")
|
||||
require.NoError(t, err)
|
||||
assertInvalidCacheEntry(t, cache.Get(addr), observedLog)
|
||||
assertInvalidCacheEntry(t, cache.Get(accessKeyID), observedLog)
|
||||
}
|
||||
|
||||
func TestBucketsCacheType(t *testing.T) {
|
||||
|
@ -230,3 +234,7 @@ func getObservedLogger() (*zap.Logger, *observer.ObservedLogs) {
|
|||
loggerCore, observedLog := observer.New(zap.WarnLevel)
|
||||
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 struct {
|
||||
Container ContainerOptions
|
||||
Container cid.ID
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
FrostFSKey *keys.PrivateKey
|
||||
GatesPublicKeys []*keys.PublicKey
|
||||
Impersonate bool
|
||||
|
@ -114,7 +116,9 @@ type (
|
|||
UpdateSecretOptions struct {
|
||||
FrostFSKey *keys.PrivateKey
|
||||
GatesPublicKeys []*keys.PublicKey
|
||||
Address oid.Address
|
||||
IsCustom bool
|
||||
AccessKeyID string
|
||||
ContainerID cid.ID
|
||||
GatePrivateKey *keys.PrivateKey
|
||||
CustomAttributes []object.Attribute
|
||||
}
|
||||
|
@ -141,7 +145,8 @@ type (
|
|||
|
||||
// ObtainSecretOptions contains options for passing to Agent.ObtainSecret method.
|
||||
ObtainSecretOptions struct {
|
||||
SecretAddress string
|
||||
Container cid.ID
|
||||
AccessKeyID string
|
||||
GatePrivateKey *keys.PrivateKey
|
||||
}
|
||||
)
|
||||
|
@ -168,32 +173,9 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner user.ID) (cid.ID, error) {
|
||||
if !opts.ID.Equals(cid.ID{}) {
|
||||
a.log.Info(logs.CheckContainer, zap.Stringer("cid", opts.ID))
|
||||
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 (a *Agent) checkContainer(ctx context.Context, cnrID cid.ID) error {
|
||||
a.log.Info(logs.CheckContainer, zap.Stringer("cid", cnrID))
|
||||
return a.frostFS.ContainerExists(ctx, cnrID)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("pack tokens: %w", err)
|
||||
}
|
||||
|
||||
box.ContainerPolicy = policies
|
||||
|
||||
var idOwner user.ID
|
||||
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
||||
id, err := a.checkContainer(ctx, options.Container, idOwner)
|
||||
if err != nil {
|
||||
if err = a.checkContainer(ctx, options.Container); err != nil {
|
||||
return fmt.Errorf("check container: %w", err)
|
||||
}
|
||||
|
||||
var idOwner user.ID
|
||||
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
||||
a.log.Info(logs.StoreBearerTokenIntoFrostFS,
|
||||
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)
|
||||
|
||||
prm := tokens.CredentialsParam{
|
||||
OwnerID: idOwner,
|
||||
Container: options.Container,
|
||||
AccessKeyID: options.AccessKeyID,
|
||||
AccessBox: box,
|
||||
Expiration: lifetime.Exp,
|
||||
Keys: options.GatesPublicKeys,
|
||||
CustomAttributes: options.CustomAttributes,
|
||||
}
|
||||
|
||||
addr, err := creds.Put(ctx, id, prm)
|
||||
addr, err := creds.Put(ctx, prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put creds: %w", err)
|
||||
}
|
||||
|
||||
accessKeyID := accessKeyIDFromAddr(addr)
|
||||
accessKeyID := options.AccessKeyID
|
||||
if accessKeyID == "" {
|
||||
accessKeyID = accessKeyIDFromAddr(addr)
|
||||
}
|
||||
|
||||
ir := &issuingResult{
|
||||
InitialAccessKeyID: accessKeyID,
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secrets.SecretKey,
|
||||
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
||||
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
||||
ContainerID: id.EncodeToString(),
|
||||
ContainerID: options.Container.EncodeToString(),
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
|
@ -337,13 +328,15 @@ func (a *Agent) UpdateSecret(ctx context.Context, w io.Writer, options *UpdateSe
|
|||
|
||||
creds := tokens.New(cfg)
|
||||
|
||||
box, _, err := creds.GetBox(ctx, options.Address)
|
||||
box, _, err := creds.GetBox(ctx, options.ContainerID, options.AccessKeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get accessbox: %w", err)
|
||||
}
|
||||
|
||||
secret, err := hex.DecodeString(box.Gate.SecretKey)
|
||||
if err != nil {
|
||||
var secret []byte
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -360,7 +353,7 @@ func (a *Agent) UpdateSecret(ctx context.Context, w io.Writer, options *UpdateSe
|
|||
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 {
|
||||
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))
|
||||
|
||||
prm := tokens.CredentialsParam{
|
||||
OwnerID: idOwner,
|
||||
Container: options.ContainerID,
|
||||
AccessBox: updatedBox,
|
||||
Expiration: lifetime.Exp,
|
||||
Keys: options.GatesPublicKeys,
|
||||
CustomAttributes: options.CustomAttributes,
|
||||
}
|
||||
|
||||
oldAddr := options.Address
|
||||
addr, err := creds.Update(ctx, oldAddr, prm)
|
||||
addr, err := creds.Update(ctx, prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update creds: %w", err)
|
||||
}
|
||||
|
||||
accessKeyID := options.AccessKeyID
|
||||
if !options.IsCustom {
|
||||
accessKeyID = accessKeyIDFromAddr(addr)
|
||||
}
|
||||
|
||||
ir := &issuingResult{
|
||||
AccessKeyID: accessKeyIDFromAddr(addr),
|
||||
InitialAccessKeyID: accessKeyIDFromAddr(oldAddr),
|
||||
AccessKeyID: accessKeyID,
|
||||
InitialAccessKeyID: options.AccessKeyID,
|
||||
SecretAccessKey: secrets.SecretKey,
|
||||
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.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)
|
||||
|
||||
var addr oid.Address
|
||||
if err := addr.DecodeString(options.SecretAddress); err != nil {
|
||||
return fmt.Errorf("failed to parse secret address: %w", err)
|
||||
}
|
||||
|
||||
box, _, err := bearerCreds.GetBox(ctx, addr)
|
||||
box, _, err := bearerCreds.GetBox(ctx, options.Container, options.AccessKeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tokens: %w", err)
|
||||
}
|
||||
|
|
|
@ -4,22 +4,34 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
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/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var issueSecretCmd = &cobra.Command{
|
||||
Use: "issue-secret",
|
||||
Short: "Issue a secret in FrostFS network",
|
||||
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
|
||||
frostfs-s3-authmate issue-secret --wallet wallet.json --peer s01.frostfs.devenv:8080 --gate-public-key 031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a --attributes LOGIN=NUUb82KR2JrVByHs2YSKgtK29gKnF5q6Vt`,
|
||||
Example: `To create new s3 credentials use:
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -54,6 +66,9 @@ const (
|
|||
poolHealthcheckTimeoutFlag = "pool-healthcheck-timeout"
|
||||
poolRebalanceIntervalFlag = "pool-rebalance-interval"
|
||||
poolStreamTimeoutFlag = "pool-stream-timeout"
|
||||
|
||||
accessKeyIDFlag = "access-key-id"
|
||||
secretAccessKeyFlag = "secret-access-key"
|
||||
)
|
||||
|
||||
func initIssueSecretCmd() {
|
||||
|
@ -73,6 +88,9 @@ func initIssueSecretCmd() {
|
|||
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().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(peerFlag)
|
||||
|
@ -91,14 +109,6 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
|||
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
|
||||
for _, keyStr := range viper.GetStringSlice(gatePublicKeyFlag) {
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
if err != nil {
|
||||
return wrapPreparationError(fmt.Errorf("failed to parse attributes: %s", err))
|
||||
}
|
||||
|
||||
issueSecretOptions := &authmate.IssueSecretOptions{
|
||||
Container: authmate.ContainerOptions{
|
||||
ID: cnrID,
|
||||
FriendlyName: viper.GetString(containerFriendlyNameFlag),
|
||||
PlacementPolicy: viper.GetString(containerPlacementPolicyFlag),
|
||||
},
|
||||
Container: accessBox,
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
FrostFSKey: key,
|
||||
GatesPublicKeys: gatesPublicKeys,
|
||||
Impersonate: true,
|
||||
|
@ -164,3 +186,59 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
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"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||
|
@ -24,7 +23,6 @@ var obtainSecretCmd = &cobra.Command{
|
|||
const (
|
||||
gateWalletFlag = "gate-wallet"
|
||||
gateAddressFlag = "gate-address"
|
||||
accessKeyIDFlag = "access-key-id"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -38,10 +36,12 @@ func initObtainSecretCmd() {
|
|||
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(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(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(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(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))
|
||||
}
|
||||
|
||||
accessBox, accessKeyID, _, err := getAccessBoxID()
|
||||
if err != nil {
|
||||
return wrapPreparationError(err)
|
||||
}
|
||||
|
||||
obtainSecretOptions := &authmate.ObtainSecretOptions{
|
||||
SecretAddress: strings.Replace(viper.GetString(accessKeyIDFlag), "0", "/", 1),
|
||||
Container: accessBox,
|
||||
AccessKeyID: accessKeyID,
|
||||
GatePrivateKey: gateKey,
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
||||
"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/spf13/cobra"
|
||||
"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(gateWalletFlag, "", "Path to the s3 gateway wallet to decrypt accessbox")
|
||||
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().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(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status")
|
||||
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(rpcEndpointFlag, "", "NEO node RPC address (must be provided if container-id is nns name)")
|
||||
|
||||
_ = updateSecretCmd.MarkFlagRequired(walletFlag)
|
||||
_ = 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))
|
||||
}
|
||||
|
||||
var accessBoxAddress oid.Address
|
||||
credAddr := strings.Replace(viper.GetString(accessKeyIDFlag), "0", "/", 1)
|
||||
if err = accessBoxAddress.DecodeString(credAddr); err != nil {
|
||||
return wrapPreparationError(fmt.Errorf("failed to parse creds address: %w", err))
|
||||
accessBox, accessKeyID, isCustom, err := getAccessBoxID()
|
||||
if err != nil {
|
||||
return wrapPreparationError(err)
|
||||
}
|
||||
|
||||
var gatesPublicKeys []*keys.PublicKey
|
||||
|
@ -101,7 +100,9 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
updateSecretOptions := &authmate.UpdateSecretOptions{
|
||||
Address: accessBoxAddress,
|
||||
ContainerID: accessBox,
|
||||
AccessKeyID: accessKeyID,
|
||||
IsCustom: isCustom,
|
||||
FrostFSKey: key,
|
||||
GatesPublicKeys: gatesPublicKeys,
|
||||
GatePrivateKey: gateKey,
|
||||
|
|
|
@ -3,6 +3,7 @@ package modules
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -11,8 +12,11 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||
"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"
|
||||
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"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -163,3 +167,23 @@ func parseObjectAttrs(attributes string) ([]object.Attribute, error) {
|
|||
|
||||
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
|
||||
isResolveListAllow bool // True if ResolveZoneList contains allowed zones
|
||||
frostfsidValidation bool
|
||||
accessbox *cid.ID
|
||||
|
||||
mu sync.RWMutex
|
||||
namespaces Namespaces
|
||||
|
@ -132,18 +133,7 @@ type (
|
|||
func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
|
||||
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{
|
||||
ctr: ctr,
|
||||
log: log.logger,
|
||||
cfg: v,
|
||||
pool: objPool,
|
||||
|
@ -162,6 +152,8 @@ func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
|
|||
}
|
||||
|
||||
func (a *App) init(ctx context.Context) {
|
||||
a.initResolver()
|
||||
a.initAuthCenter(ctx)
|
||||
a.setRuntimeParameters()
|
||||
a.initFrostfsID(ctx)
|
||||
a.initPolicyStorage(ctx)
|
||||
|
@ -171,9 +163,26 @@ func (a *App) init(ctx context.Context) {
|
|||
a.initTracing(ctx)
|
||||
}
|
||||
|
||||
func (a *App) initLayer(ctx context.Context) {
|
||||
a.initResolver()
|
||||
func (a *App) initAuthCenter(ctx context.Context) {
|
||||
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
|
||||
randomKey, err := keys.NewPrivateKey()
|
||||
if err != nil {
|
||||
|
@ -484,6 +493,14 @@ func (s *appSettings) RetryStrategy() handler.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) {
|
||||
a.initLayer(ctx)
|
||||
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) {
|
||||
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)
|
||||
|
||||
var id cid.ID
|
||||
if err = id.DecodeString(containerString); err != nil {
|
||||
if err := id.DecodeString(containerString); err != nil {
|
||||
i := strings.Index(containerString, ".")
|
||||
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 {
|
||||
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) {
|
||||
|
|
|
@ -208,6 +208,7 @@ const ( // Settings.
|
|||
// Containers.
|
||||
cfgContainersCORS = "containers.cors"
|
||||
cfgContainersLifecycle = "containers.lifecycle"
|
||||
cfgContainersAccessBox = "containers.accessbox"
|
||||
|
||||
// Command line args.
|
||||
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.
|
||||
// Session token can be nil.
|
||||
// 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{}
|
||||
ephemeralKey, err := keys.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("create ephemeral key: %w", err)
|
||||
}
|
||||
box.SeedKey = ephemeralKey.PublicKey().Bytes()
|
||||
box.IsCustom = isCustomSecret
|
||||
|
||||
if secret == nil {
|
||||
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 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.
|
||||
|
@ -133,7 +139,7 @@ func (x *AccessBox) GetTokens(owner *keys.PrivateKey) (*GateData, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
gateData, err := decodeGate(gate, owner, seedKey)
|
||||
gateData, err := x.decodeGate(gate, owner, seedKey)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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.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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.30.0
|
||||
// protoc v3.12.4
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v3.21.9
|
||||
// source: creds/accessbox/accessbox.proto
|
||||
|
||||
package accessbox
|
||||
|
@ -28,6 +28,7 @@ type AccessBox struct {
|
|||
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"`
|
||||
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() {
|
||||
|
@ -83,6 +84,13 @@ func (x *AccessBox) GetContainerPolicy() []*AccessBox_ContainerPolicy {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *AccessBox) GetIsCustom() bool {
|
||||
if x != nil {
|
||||
return x.IsCustom
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Tokens struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -261,7 +269,7 @@ var File_creds_accessbox_accessbox_proto protoreflect.FileDescriptor
|
|||
var file_creds_accessbox_accessbox_proto_rawDesc = []byte{
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
|
@ -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,
|
||||
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,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x44, 0x0a, 0x04, 0x47, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x67, 0x61,
|
||||
0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x1a, 0x59, 0x0a, 0x0f, 0x43,
|
||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2e,
|
||||
0x0a, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72,
|
||||
0x61, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x16,
|
||||
0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,
|
||||
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x6e, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
|
||||
0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x20,
|
||||
0x0a, 0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x12, 0x24, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72,
|
||||
0x6f, 0x73, 0x74, 0x66, 0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43,
|
||||
0x6c, 0x6f, 0x75, 0x64, 0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d,
|
||||
0x73, 0x33, 0x2d, 0x67, 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,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x43, 0x75, 0x73, 0x74, 0x6f,
|
||||
0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x43, 0x75, 0x73, 0x74, 0x6f,
|
||||
0x6d, 0x1a, 0x44, 0x0a, 0x04, 0x47, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x73, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
|
||||
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x50, 0x75,
|
||||
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x1a, 0x59, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f,
|
||||
0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69,
|
||||
0x63, 0x79, 0x22, 0x6e, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09,
|
||||
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x65,
|
||||
0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0d,
|
||||
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x03, 0x20,
|
||||
0x03, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x73, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66,
|
||||
0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64,
|
||||
0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d, 0x73, 0x33, 0x2d, 0x67,
|
||||
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 (
|
||||
|
@ -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_goTypes = []interface{}{
|
||||
var file_creds_accessbox_accessbox_proto_goTypes = []any{
|
||||
(*AccessBox)(nil), // 0: accessbox.AccessBox
|
||||
(*Tokens)(nil), // 1: accessbox.Tokens
|
||||
(*AccessBox_Gate)(nil), // 2: accessbox.AccessBox.Gate
|
||||
|
@ -332,7 +342,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
|||
return
|
||||
}
|
||||
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 {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -344,7 +354,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
|||
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 {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -356,7 +366,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
|||
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 {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -368,7 +378,7 @@ func file_creds_accessbox_accessbox_proto_init() {
|
|||
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 {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
|
|
@ -20,6 +20,7 @@ message AccessBox {
|
|||
bytes seedKey = 1 [json_name = "seedKey"];
|
||||
repeated Gate gates = 2 [json_name = "gates"];
|
||||
repeated ContainerPolicy containerPolicy = 3 [json_name = "containerPolicy"];
|
||||
bool isCustom = 4 [json_name = "isCustom"];
|
||||
}
|
||||
|
||||
message Tokens {
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestBearerTokenInAccessBox(t *testing.T) {
|
|||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||
|
||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := box.Marshal()
|
||||
|
@ -96,7 +96,7 @@ func TestSessionTokenInAccessBox(t *testing.T) {
|
|||
var newTkn bearer.Token
|
||||
gate := NewGateData(cred.PublicKey(), &newTkn)
|
||||
gate.SessionTokens = []*session.Container{tkn}
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
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)
|
||||
|
||||
for i, k := range privateKeys {
|
||||
|
@ -165,7 +165,7 @@ func TestUnknownKey(t *testing.T) {
|
|||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||
|
||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = box.GetTokens(wrongCred)
|
||||
|
@ -224,14 +224,27 @@ func TestGetBox(t *testing.T) {
|
|||
|
||||
var tkn bearer.Token
|
||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||
|
||||
secret := []byte("secret")
|
||||
accessBox, _, err := PackTokens([]*GateData{gate}, secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
box, err := accessBox.GetBox(cred)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey)
|
||||
t.Run("regular secret", func(t *testing.T) {
|
||||
accessBox, secrets, err := PackTokens([]*GateData{gate}, secret, false)
|
||||
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) {
|
||||
|
@ -241,7 +254,7 @@ func TestAccessBox(t *testing.T) {
|
|||
var tkn bearer.Token
|
||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||
|
||||
accessBox, _, err := PackTokens([]*GateData{gate}, nil)
|
||||
accessBox, _, err := PackTokens([]*GateData{gate}, nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("invalid owner", func(t *testing.T) {
|
||||
|
@ -300,7 +313,7 @@ func TestAccessBox(t *testing.T) {
|
|||
BearerToken: &tkn,
|
||||
GateKey: &keys.PublicKey{},
|
||||
}
|
||||
_, _, err = PackTokens([]*GateData{gate}, nil)
|
||||
_, _, err = PackTokens([]*GateData{gate}, nil, false)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
|
@ -14,7 +14,6 @@ import (
|
|||
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"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
@ -22,13 +21,14 @@ import (
|
|||
type (
|
||||
// Credentials is a bearer token get/put interface.
|
||||
Credentials interface {
|
||||
GetBox(context.Context, oid.Address) (*accessbox.Box, []object.Attribute, error)
|
||||
Put(context.Context, cid.ID, CredentialsParam) (oid.Address, error)
|
||||
Update(context.Context, oid.Address, CredentialsParam) (oid.Address, error)
|
||||
GetBox(context.Context, cid.ID, string) (*accessbox.Box, []object.Attribute, error)
|
||||
Put(context.Context, CredentialsParam) (oid.Address, error)
|
||||
Update(context.Context, CredentialsParam) (oid.Address, error)
|
||||
}
|
||||
|
||||
CredentialsParam struct {
|
||||
OwnerID user.ID
|
||||
Container cid.ID
|
||||
AccessKeyID string
|
||||
AccessBox *accessbox.AccessBox
|
||||
Expiration uint64
|
||||
Keys keys.PublicKeys
|
||||
|
@ -49,13 +49,16 @@ type (
|
|||
CacheConfig *cache.Config
|
||||
RemovingCheckAfterDurations time.Duration
|
||||
}
|
||||
|
||||
Box struct {
|
||||
AccessBox *accessbox.AccessBox
|
||||
Attributes []object.Attribute
|
||||
Address *oid.Address
|
||||
}
|
||||
)
|
||||
|
||||
// PrmObjectCreate groups parameters of objects created by credential tool.
|
||||
type PrmObjectCreate struct {
|
||||
// FrostFS identifier of the object creator.
|
||||
Creator user.ID
|
||||
|
||||
// FrostFS container to store the object.
|
||||
Container cid.ID
|
||||
|
||||
|
@ -64,7 +67,12 @@ type PrmObjectCreate struct {
|
|||
|
||||
// Optional.
|
||||
// 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.
|
||||
ExpirationEpoch uint64
|
||||
|
@ -76,6 +84,21 @@ type PrmObjectCreate struct {
|
|||
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.
|
||||
type FrostFS interface {
|
||||
// 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
|
||||
// prevented the object payload from being read.
|
||||
// Returns ErrCustomAccessKeyIDNotFound if provided AccessKey is custom, and it was not found.
|
||||
// Object must contain full payload.
|
||||
GetCredsObject(context.Context, oid.Address) (*object.Object, error)
|
||||
GetCredsObject(context.Context, PrmGetCredsObject) (*object.Object, error)
|
||||
}
|
||||
|
||||
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) {
|
||||
cachedBoxValue := c.cache.Get(addr)
|
||||
func (c *cred) GetBox(ctx context.Context, cnrID cid.ID, accessKeyID string) (*accessbox.Box, []object.Attribute, error) {
|
||||
cachedBoxValue := c.cache.Get(accessKeyID)
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 client.IsErrObjectAlreadyRemoved(err) {
|
||||
c.cache.Delete(addr)
|
||||
c.cache.Delete(accessKeyID)
|
||||
return nil, nil, fmt.Errorf("get access box: %w", err)
|
||||
}
|
||||
return cachedBoxValue.Box, cachedBoxValue.Attributes, nil
|
||||
}
|
||||
|
||||
cachedBox, err := box.GetBox(c.key)
|
||||
cachedBox, err := box.AccessBox.GetBox(c.key)
|
||||
if err != nil {
|
||||
c.cache.Delete(addr)
|
||||
c.cache.Delete(accessKeyID)
|
||||
return nil, nil, fmt.Errorf("get gate box: %w", err)
|
||||
}
|
||||
// we need this to reset PutTime
|
||||
// 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) {
|
||||
if err := c.cache.Put(addr, box, attrs); err != nil {
|
||||
c.log.Warn(logs.CouldntPutAccessBoxIntoCache, zap.String("address", addr.EncodeToString()))
|
||||
func (c *cred) putBoxToCache(accessKeyID string, val *cache.AccessBoxCacheValue) {
|
||||
if err := c.cache.Put(accessKeyID, val); err != nil {
|
||||
c.log.Warn(logs.CouldntPutAccessBoxIntoCache, zap.String("accessKeyID", accessKeyID))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.AccessBox, []object.Attribute, error) {
|
||||
obj, err := c.frostFS.GetCredsObject(ctx, addr)
|
||||
func (c *cred) getAccessBox(ctx context.Context, cnrID cid.ID, accessKeyID string, fallbackAddr *oid.Address) (*Box, error) {
|
||||
prm := PrmGetCredsObject{
|
||||
Container: cnrID,
|
||||
AccessKeyID: accessKeyID,
|
||||
FallbackAddress: fallbackAddr,
|
||||
}
|
||||
obj, err := c.frostFS.GetCredsObject(ctx, prm)
|
||||
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
|
||||
var box accessbox.AccessBox
|
||||
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) {
|
||||
return c.createObject(ctx, idCnr, nil, prm)
|
||||
func (c *cred) Put(ctx context.Context, prm CredentialsParam) (oid.Address, error) {
|
||||
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) {
|
||||
objID := addr.Object()
|
||||
return c.createObject(ctx, addr.Container(), &objID, prm)
|
||||
func (c *cred) Update(ctx context.Context, prm CredentialsParam) (oid.Address, error) {
|
||||
return c.createObject(ctx, prm, true)
|
||||
}
|
||||
|
||||
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 {
|
||||
return oid.Address{}, ErrEmptyPublicKeys
|
||||
} 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)
|
||||
}
|
||||
|
||||
var newVersionFor string
|
||||
if update {
|
||||
newVersionFor = prm.AccessKeyID
|
||||
}
|
||||
|
||||
idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
|
||||
Creator: prm.OwnerID,
|
||||
Container: cnrID,
|
||||
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
||||
ExpirationEpoch: prm.Expiration,
|
||||
NewVersionFor: newVersionFor,
|
||||
Payload: data,
|
||||
CustomAttributes: prm.CustomAttributes,
|
||||
Container: prm.Container,
|
||||
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
||||
ExpirationEpoch: prm.Expiration,
|
||||
CustomAccessKey: prm.AccessKeyID,
|
||||
NewVersionForAccessKeyID: newVersionFor,
|
||||
Payload: data,
|
||||
CustomAttributes: prm.CustomAttributes,
|
||||
})
|
||||
if err != nil {
|
||||
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
|
||||
addr.SetObject(idObj)
|
||||
addr.SetContainer(cnrID)
|
||||
addr.SetContainer(prm.Container)
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -15,27 +16,40 @@ import (
|
|||
"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"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
type frostfsMock struct {
|
||||
objects map[oid.Address][]*object.Object
|
||||
errors map[oid.Address]error
|
||||
key *keys.PrivateKey
|
||||
objects map[string][]*object.Object
|
||||
errors map[string]error
|
||||
}
|
||||
|
||||
func newFrostfsMock() *frostfsMock {
|
||||
func newFrostfsMock(key *keys.PrivateKey) *frostfsMock {
|
||||
return &frostfsMock{
|
||||
objects: map[oid.Address][]*object.Object{},
|
||||
errors: map[oid.Address]error{},
|
||||
objects: map[string][]*object.Object{},
|
||||
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) {
|
||||
var obj object.Object
|
||||
obj.SetPayload(prm.Payload)
|
||||
obj.SetOwnerID(prm.Creator)
|
||||
obj.SetOwnerID(f.ownerID())
|
||||
obj.SetContainerID(prm.Container)
|
||||
|
||||
a := object.NewAttribute()
|
||||
|
@ -44,19 +58,15 @@ func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.
|
|||
prm.CustomAttributes = append(prm.CustomAttributes, *a)
|
||||
obj.SetAttributes(prm.CustomAttributes...)
|
||||
|
||||
if prm.NewVersionFor != nil {
|
||||
var addr oid.Address
|
||||
addr.SetObject(*prm.NewVersionFor)
|
||||
addr.SetContainer(prm.Container)
|
||||
|
||||
_, ok := f.objects[addr]
|
||||
if prm.NewVersionForAccessKeyID != "" {
|
||||
_, ok := f.objects[prm.NewVersionForAccessKeyID]
|
||||
if !ok {
|
||||
return oid.ID{}, errors.New("not found")
|
||||
}
|
||||
|
||||
objID := oidtest.ID()
|
||||
obj.SetID(objID)
|
||||
f.objects[addr] = append(f.objects[addr], &obj)
|
||||
f.objects[prm.NewVersionForAccessKeyID] = append(f.objects[prm.NewVersionForAccessKeyID], &obj)
|
||||
|
||||
return objID, nil
|
||||
}
|
||||
|
@ -64,22 +74,27 @@ func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.
|
|||
objID := oidtest.ID()
|
||||
obj.SetID(objID)
|
||||
|
||||
accessKeyID := prm.CustomAccessKey
|
||||
if accessKeyID == "" {
|
||||
accessKeyID = prm.Container.EncodeToString() + "0" + objID.EncodeToString()
|
||||
}
|
||||
|
||||
var addr oid.Address
|
||||
addr.SetObject(objID)
|
||||
addr.SetContainer(prm.Container)
|
||||
f.objects[addr] = []*object.Object{&obj}
|
||||
f.objects[accessKeyID] = []*object.Object{&obj}
|
||||
|
||||
return objID, nil
|
||||
}
|
||||
|
||||
func (f *frostfsMock) GetCredsObject(_ context.Context, address oid.Address) (*object.Object, error) {
|
||||
if err := f.errors[address]; err != nil {
|
||||
func (f *frostfsMock) GetCredsObject(_ context.Context, prm PrmGetCredsObject) (*object.Object, error) {
|
||||
if err := f.errors[prm.AccessKeyID]; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects, ok := f.objects[address]
|
||||
objects, ok := f.objects[prm.AccessKeyID]
|
||||
if !ok {
|
||||
return nil, errors.New("not found")
|
||||
return nil, ErrCustomAccessKeyIDNotFound
|
||||
}
|
||||
|
||||
return objects[len(objects)-1], nil
|
||||
|
@ -100,7 +115,7 @@ func TestRemovingAccessBox(t *testing.T) {
|
|||
sk, err := hex.DecodeString(secretKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
accessBox, _, err := accessbox.PackTokens(gateData, sk)
|
||||
accessBox, _, err := accessbox.PackTokens(gateData, sk, false)
|
||||
require.NoError(t, err)
|
||||
data, err := accessBox.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
@ -111,9 +126,24 @@ func TestRemovingAccessBox(t *testing.T) {
|
|||
obj.SetID(addr.Object())
|
||||
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{
|
||||
objects: map[oid.Address][]*object.Object{addr: {&obj}},
|
||||
errors: map[oid.Address]error{},
|
||||
objects: map[string][]*object.Object{accessKeyID: {&obj}, accessKeyIDCustom: {&objCustom}},
|
||||
errors: map[string]error{},
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
|
@ -129,15 +159,30 @@ func TestRemovingAccessBox(t *testing.T) {
|
|||
|
||||
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)
|
||||
|
||||
frostfs.errors[addr] = errors.New("network error")
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
frostfs.errors[accessKeyID] = errors.New("network error")
|
||||
_, _, 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)
|
||||
|
||||
frostfs.errors[addr] = &apistatus.ObjectAlreadyRemoved{}
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -153,8 +198,9 @@ func TestGetBox(t *testing.T) {
|
|||
}}
|
||||
|
||||
secret := []byte("secret")
|
||||
accessBox, _, err := accessbox.PackTokens(gateData, secret)
|
||||
accessBox, secrets, err := accessbox.PackTokens(gateData, secret, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hex.EncodeToString(secret), secrets.SecretKey)
|
||||
data, err := accessBox.Marshal()
|
||||
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) {
|
||||
frostfs := newFrostfsMock()
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = time.Hour
|
||||
cfg.Key = key
|
||||
creds := New(cfg)
|
||||
creds := newCreds(key, cfg, time.Hour)
|
||||
|
||||
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)
|
||||
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
|
||||
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.NoError(t, err)
|
||||
|
||||
frostfs.errors[addr] = &apistatus.ObjectAlreadyRemoved{}
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
creds.(*cred).frostFS.(*frostfsMock).errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{}
|
||||
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("error while getting box from frostfs", func(t *testing.T) {
|
||||
frostfs := newFrostfsMock()
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = 0
|
||||
cfg.Key = key
|
||||
creds := New(cfg)
|
||||
creds := newCreds(key, cfg, 0)
|
||||
|
||||
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)
|
||||
|
||||
frostfs.errors[addr] = errors.New("network error")
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
creds.(*cred).frostFS.(*frostfsMock).errors[accessKeyID] = errors.New("network error")
|
||||
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid key", func(t *testing.T) {
|
||||
frostfs := newFrostfsMock()
|
||||
frostfs := newFrostfsMock(key)
|
||||
|
||||
var obj object.Object
|
||||
obj.SetPayload(data)
|
||||
addr := oidtest.Address()
|
||||
frostfs.objects[addr] = []*object.Object{&obj}
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
frostfs.objects[accessKeyID] = []*object.Object{&obj}
|
||||
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = 0
|
||||
cfg.Key = &keys.PrivateKey{}
|
||||
creds := New(cfg)
|
||||
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid payload", func(t *testing.T) {
|
||||
frostfs := newFrostfsMock()
|
||||
frostfs := newFrostfsMock(key)
|
||||
|
||||
var obj object.Object
|
||||
obj.SetPayload([]byte("invalid"))
|
||||
addr := oidtest.Address()
|
||||
frostfs.objects[addr] = []*object.Object{&obj}
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
frostfs.objects[accessKeyID] = []*object.Object{&obj}
|
||||
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = 0
|
||||
cfg.Key = key
|
||||
creds := New(cfg)
|
||||
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("check attributes update", func(t *testing.T) {
|
||||
frostfs := newFrostfsMock()
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = 0
|
||||
cfg.Key = key
|
||||
creds := New(cfg)
|
||||
creds := newCreds(key, cfg, 0)
|
||||
|
||||
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)
|
||||
|
||||
_, boxAttrs, err := creds.GetBox(ctx, addr)
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
_, boxAttrs, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
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)
|
||||
|
||||
_, newBoxAttrs, err := creds.GetBox(ctx, addr)
|
||||
_, newBoxAttrs, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(boxAttrs)+1, len(newBoxAttrs))
|
||||
})
|
||||
|
||||
t.Run("check accessbox update", func(t *testing.T) {
|
||||
frostfs := newFrostfsMock()
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = 0
|
||||
cfg.Key = key
|
||||
creds := New(cfg)
|
||||
creds := newCreds(key, cfg, 0)
|
||||
|
||||
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)
|
||||
|
||||
box, _, err := creds.GetBox(ctx, addr)
|
||||
accessKeyID := getAccessKeyID(addr)
|
||||
|
||||
box, _, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey)
|
||||
|
||||
|
@ -286,44 +331,134 @@ func TestGetBox(t *testing.T) {
|
|||
}}
|
||||
|
||||
newSecret := []byte("new-secret")
|
||||
newAccessBox, _, err := accessbox.PackTokens(newGateData, newSecret)
|
||||
newAccessBox, _, err := accessbox.PackTokens(newGateData, newSecret, false)
|
||||
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)
|
||||
|
||||
_, _, err = creds.GetBox(ctx, addr)
|
||||
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
||||
require.Error(t, err)
|
||||
|
||||
cfg.Key = newKey
|
||||
newCreds := New(cfg)
|
||||
newCfg := Config{
|
||||
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.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) {
|
||||
frostfs := newFrostfsMock()
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = 0
|
||||
cfg.Key = key
|
||||
creds := New(cfg)
|
||||
creds := newCreds(key, cfg, 0)
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
t.Run("empty accessbox", func(t *testing.T) {
|
||||
frostfs := newFrostfsMock()
|
||||
cfg.FrostFS = frostfs
|
||||
cfg.RemovingCheckAfterDurations = 0
|
||||
cfg.Key = key
|
||||
creds := New(cfg)
|
||||
creds := newCreds(key, cfg, 0)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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:
|
||||
|
||||
* `AccessKeyId` - is concatenated container id and object id (`<cid>0<oid>`) of `AccessBox` (
|
||||
e.g. `2XGRML5EW3LMHdf64W2DkBy1Nkuu4y4wGhUj44QjbXBi05ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf`)
|
||||
* `SecretAccessKey` - hex-encoded random generated 32 bytes (that is encrypted and stored in object payload)
|
||||
e.g. `2XGRML5EW3LMHdf64W2DkBy1Nkuu4y4wGhUj44QjbXBi05ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf`).
|
||||
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
|
||||
> can decrypt such info.
|
||||
|
@ -192,7 +194,7 @@ It contains:
|
|||
* 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)
|
||||
* 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
|
||||
in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/acl/types.proto#L189)
|
||||
* Marshaled session token - more detail
|
||||
|
@ -229,10 +231,12 @@ relevant data) the following sequence is used:
|
|||
</a>
|
||||
|
||||
* 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)
|
||||
* 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.)
|
||||
* Get appropriate object from FrostFS storage
|
||||
* 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
|
||||
* 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)
|
||||
* 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)
|
||||
|
|
|
@ -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
|
||||
* `--aws-cli-credentials` - path to the aws cli credentials file, where authmate will write `access_key_id` and
|
||||
`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
|
||||
|
||||
|
|
|
@ -761,12 +761,14 @@ Section for well-known containers to store s3-related data and settings.
|
|||
containers:
|
||||
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||
lifecycle: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
|
||||
accessbox: ExnA1gSY3kzgomi2wJxNyWo1ytWv9VAKXRE55fNXEPL2
|
||||
```
|
||||
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|-------------|----------|---------------|---------------|-------------------------------------------------------------------------------------------|
|
||||
| `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. |
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|-------------|----------|---------------|---------------|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| `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. |
|
||||
| `accessbox` | `string` | no | | Container name to lookup accessbox if custom aws credentials is used. If not set, custom credentials are not supported. |
|
||||
|
||||
# `vhs` section
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ package AccessBox {
|
|||
SeedKey => Encoded public seed key
|
||||
List of Gates *--> Gate
|
||||
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"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
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.
|
||||
func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, addr oid.Address) (*object.Object, error) {
|
||||
versions, err := x.getCredVersions(ctx, addr)
|
||||
func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, prm tokens.PrmGetCredsObject) (obj *object.Object, err error) {
|
||||
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 {
|
||||
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 {
|
||||
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{
|
||||
Container: addr.Container(),
|
||||
Object: credObjID,
|
||||
Object: addr.Object(),
|
||||
})
|
||||
if err != nil {
|
||||
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) {
|
||||
attributes := [][2]string{{objectv2.SysAttributeExpEpoch, strconv.FormatUint(prm.ExpirationEpoch, 10)}}
|
||||
|
||||
if prm.NewVersionFor != nil {
|
||||
var addr oid.Address
|
||||
addr.SetContainer(prm.Container)
|
||||
addr.SetObject(*prm.NewVersionFor)
|
||||
|
||||
versions, err := x.getCredVersions(ctx, addr)
|
||||
if prm.NewVersionForAccessKeyID != "" {
|
||||
versions, err := x.getCredVersions(ctx, prm.Container, prm.NewVersionForAccessKeyID)
|
||||
if err != nil {
|
||||
return oid.ID{}, err
|
||||
}
|
||||
|
||||
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()})
|
||||
}
|
||||
|
||||
|
@ -130,6 +159,8 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -150,21 +181,20 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
|
|||
return res.ObjectID, nil
|
||||
}
|
||||
|
||||
func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, addr oid.Address) (*crdt.ObjectVersions, error) {
|
||||
objCredSystemName := credVersionSysName(addr.Container(), addr.Object())
|
||||
func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, cnrID cid.ID, accessKeyID string) (*crdt.ObjectVersions, error) {
|
||||
credVersions, err := x.frostFS.SearchObjects(ctx, frostfs.PrmObjectSearch{
|
||||
Container: addr.Container(),
|
||||
ExactAttribute: [2]string{accessBoxCRDTNameAttr, objCredSystemName},
|
||||
Container: cnrID,
|
||||
ExactAttribute: [2]string{accessBoxCRDTNameAttr, accessKeyID},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search s3 access boxes: %w", err)
|
||||
}
|
||||
|
||||
versions := crdt.NewObjectVersions(objCredSystemName)
|
||||
versions := crdt.NewObjectVersions(accessKeyID)
|
||||
|
||||
for _, id := range credVersions {
|
||||
objVersion, err := x.frostFS.HeadObject(ctx, frostfs.PrmObjectHead{
|
||||
Container: addr.Container(),
|
||||
Container: cnrID,
|
||||
Object: id,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -184,7 +214,3 @@ func (x *AuthmateFrostFS) reqLogger(ctx context.Context) *zap.Logger {
|
|||
}
|
||||
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 (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/middleware"
|
||||
"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/tokens"
|
||||
"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"
|
||||
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/stretchr/testify/require"
|
||||
"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")
|
||||
|
||||
key, err := keys.NewPrivateKey()
|
||||
|
@ -38,34 +45,242 @@ func TestGetCredsObject(t *testing.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,
|
||||
Owner: userID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
objID, err := frostfs.CreateObject(ctx, tokens.PrmObjectCreate{
|
||||
Container: cid,
|
||||
Payload: payload,
|
||||
t.Run("regular access key", func(t *testing.T) {
|
||||
attr1 := object.NewAttribute()
|
||||
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
|
||||
addr.SetContainer(cid)
|
||||
addr.SetObject(objID)
|
||||
t.Run("custom access key", func(t *testing.T) {
|
||||
attr1 := object.NewAttribute()
|
||||
attr1.SetKey("attr1")
|
||||
attr1.SetValue("val1")
|
||||
|
||||
obj, err := frostfs.GetCredsObject(ctx, addr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, payload, obj.Payload())
|
||||
accessKeyID := "custom-access-key-id"
|
||||
|
||||
_, err = frostfs.CreateObject(ctx, tokens.PrmObjectCreate{
|
||||
Container: cid,
|
||||
Payload: newPayload,
|
||||
NewVersionFor: &objID,
|
||||
prm := tokens.PrmObjectCreate{
|
||||
Container: cnrID,
|
||||
Filepath: "custom-obj",
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newPayload, obj.Payload())
|
||||
t.Run("fallback", func(t *testing.T) {
|
||||
t.Run("regular", func(t *testing.T) {
|
||||
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"
|
||||
|
||||
"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/ns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -36,6 +37,31 @@ func ResolveContractHash(contractHash, rpcAddress string) (util.Uint160, error)
|
|||
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) {
|
||||
duration := t.Sub(now)
|
||||
durationAbs := duration.Abs()
|
||||
|
|
|
@ -159,6 +159,7 @@ const (
|
|||
FoundSeveralSystemNodes = "found several system nodes"
|
||||
FailedToParsePartInfo = "failed to parse part info"
|
||||
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
|
||||
CouldNotFetchAccessBoxContainerInfo = "couldn't fetch AccessBox container info"
|
||||
CloseCredsObjectPayload = "close creds object payload"
|
||||
CouldntDeleteLifecycleObject = "couldn't delete lifecycle configuration object"
|
||||
CouldntCacheLifecycleConfiguration = "couldn't cache lifecycle configuration"
|
||||
|
@ -171,4 +172,5 @@ const (
|
|||
FailedToRemoveOldPartNode = "failed to remove old part node"
|
||||
CouldntCacheNetworkInfo = "couldn't cache network info"
|
||||
NotSupported = "not supported"
|
||||
CheckCustomAccessKeyIDUniqueness = "check custom access key id uniqueness"
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue