feature/372-drop_eacl_related_code #397
32 changed files with 137 additions and 3767 deletions
|
@ -31,7 +31,6 @@ type (
|
||||||
LocationConstraint string
|
LocationConstraint string
|
||||||
ObjectLockEnabled bool
|
ObjectLockEnabled bool
|
||||||
HomomorphicHashDisabled bool
|
HomomorphicHashDisabled bool
|
||||||
APEEnabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectInfo holds S3 object data.
|
// ObjectInfo holds S3 object data.
|
||||||
|
|
1392
api/handler/acl.go
1392
api/handler/acl.go
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -38,7 +38,6 @@ type (
|
||||||
IsResolveListAllow() bool
|
IsResolveListAllow() bool
|
||||||
BypassContentEncodingInChunks() bool
|
BypassContentEncodingInChunks() bool
|
||||||
MD5Enabled() bool
|
MD5Enabled() bool
|
||||||
ACLEnabled() bool
|
|
||||||
RetryMaxAttempts() int
|
RetryMaxAttempts() int
|
||||||
RetryMaxBackoff() time.Duration
|
RetryMaxBackoff() time.Duration
|
||||||
RetryStrategy() RetryStrategy
|
RetryStrategy() RetryStrategy
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,11 +41,10 @@ func path2BucketObject(path string) (string, string, error) {
|
||||||
|
|
||||||
func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
versionID string
|
versionID string
|
||||||
metadata map[string]string
|
metadata map[string]string
|
||||||
tagSet map[string]string
|
tagSet map[string]string
|
||||||
sessionTokenEACL *session.Container
|
|
||||||
|
|
||||||
ctx = r.Context()
|
ctx = r.Context()
|
||||||
reqInfo = middleware.GetReqInfo(ctx)
|
reqInfo = middleware.GetReqInfo(ctx)
|
||||||
|
@ -93,20 +91,11 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apeEnabled := dstBktInfo.APEEnabled || settings.CannedACL != ""
|
if cannedACLStatus == aclStatusYes {
|
||||||
if apeEnabled && cannedACLStatus == aclStatusYes {
|
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo)
|
|
||||||
if needUpdateEACLTable {
|
|
||||||
if sessionTokenEACL, err = getSessionTokenSetEACL(ctx); err != nil {
|
|
||||||
h.logAndSendError(w, "could not get eacl session token from a box", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extendedSrcObjInfo, err := h.obj.GetExtendedObjectInfo(ctx, srcObjPrm)
|
extendedSrcObjInfo, err := h.obj.GetExtendedObjectInfo(ctx, srcObjPrm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not find object", reqInfo, err)
|
h.logAndSendError(w, "could not find object", reqInfo, err)
|
||||||
|
@ -239,25 +228,6 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if needUpdateEACLTable {
|
|
||||||
newEaclTable, err := h.getNewEAclTable(r, dstBktInfo, dstObjInfo)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not get new eacl table", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &layer.PutBucketACLParams{
|
|
||||||
BktInfo: dstBktInfo,
|
|
||||||
EACL: newEaclTable,
|
|
||||||
SessionToken: sessionTokenEACL,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = h.obj.PutBucketACL(ctx, p); err != nil {
|
|
||||||
h.logAndSendError(w, "could not put bucket acl", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagSet != nil {
|
if tagSet != nil {
|
||||||
tagPrm := &data.PutObjectTaggingParams{
|
tagPrm := &data.PutObjectTaggingParams{
|
||||||
ObjectVersion: &data.ObjectVersion{
|
ObjectVersion: &data.ObjectVersion{
|
||||||
|
|
|
@ -72,7 +72,6 @@ type configMock struct {
|
||||||
defaultCopiesNumbers []uint32
|
defaultCopiesNumbers []uint32
|
||||||
bypassContentEncodingInChunks bool
|
bypassContentEncodingInChunks bool
|
||||||
md5Enabled bool
|
md5Enabled bool
|
||||||
aclEnabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configMock) DefaultPlacementPolicy(_ string) netmap.PlacementPolicy {
|
func (c *configMock) DefaultPlacementPolicy(_ string) netmap.PlacementPolicy {
|
||||||
|
@ -120,10 +119,6 @@ func (c *configMock) MD5Enabled() bool {
|
||||||
return c.md5Enabled
|
return c.md5Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configMock) ACLEnabled() bool {
|
|
||||||
return c.aclEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *configMock) ResolveNamespaceAlias(ns string) string {
|
func (c *configMock) ResolveNamespaceAlias(ns string) string {
|
||||||
return ns
|
return ns
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,9 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -84,31 +81,6 @@ func headObject(t *testing.T, tc *handlerContext, bktName, objName string, heade
|
||||||
assertStatus(t, w, status)
|
assertStatus(t, w, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidAccessThroughCache(t *testing.T) {
|
|
||||||
hc := prepareHandlerContext(t)
|
|
||||||
|
|
||||||
bktName, objName := "bucket-for-cache", "obj-for-cache"
|
|
||||||
bktInfo, _ := createBucketAndObject(hc, bktName, objName)
|
|
||||||
setContainerEACL(hc, bktInfo.CID)
|
|
||||||
|
|
||||||
headObject(t, hc, bktName, objName, nil, http.StatusOK)
|
|
||||||
|
|
||||||
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
|
||||||
hc.Handler().HeadObjectHandler(w, r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: newTestAccessBox(t, nil)})))
|
|
||||||
assertStatus(t, w, http.StatusForbidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setContainerEACL(hc *handlerContext, cnrID cid.ID) {
|
|
||||||
table := eacl.NewTable()
|
|
||||||
table.SetCID(cnrID)
|
|
||||||
for _, op := range fullOps {
|
|
||||||
table.AddRecord(getOthersRecord(op, eacl.ActionDeny))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := hc.MockedPool().SetContainerEACL(hc.Context(), *table, nil)
|
|
||||||
require.NoError(hc.t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeadObject(t *testing.T) {
|
func TestHeadObject(t *testing.T) {
|
||||||
hc := prepareHandlerContextWithMinCache(t)
|
hc := prepareHandlerContextWithMinCache(t)
|
||||||
bktName, objName := "bucket", "obj"
|
bktName, objName := "bucket", "obj"
|
||||||
|
@ -155,7 +127,7 @@ func newTestAccessBox(t *testing.T, key *keys.PrivateKey) *accessbox.Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
var btoken bearer.Token
|
var btoken bearer.Token
|
||||||
btoken.SetEACLTable(*eacl.NewTable())
|
btoken.SetImpersonate(true)
|
||||||
err = btoken.Sign(key.PrivateKey)
|
err = btoken.Sign(key.PrivateKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -112,14 +112,7 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
if cannedACLStatus == aclStatusYes {
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
apeEnabled := bktInfo.APEEnabled || settings.CannedACL != ""
|
|
||||||
if apeEnabled && cannedACLStatus == aclStatusYes {
|
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -133,20 +126,6 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
Data: &layer.UploadData{},
|
Data: &layer.UploadData{},
|
||||||
}
|
}
|
||||||
|
|
||||||
needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo)
|
|
||||||
if needUpdateEACLTable {
|
|
||||||
key, err := h.bearerTokenIssuerKey(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "couldn't get gate key", reqInfo, err, additional...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = parseACLHeaders(r.Header, key); err != nil {
|
|
||||||
h.logAndSendError(w, "could not parse acl", reqInfo, err, additional...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Data.ACLHeaders = formACLHeadersForMultipart(r.Header)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.Header.Get(api.AmzTagging)) > 0 {
|
if len(r.Header.Get(api.AmzTagging)) > 0 {
|
||||||
p.Data.TagSet, err = parseTaggingHeader(r.Header)
|
p.Data.TagSet, err = parseTaggingHeader(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -196,25 +175,6 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func formACLHeadersForMultipart(header http.Header) map[string]string {
|
|
||||||
result := make(map[string]string)
|
|
||||||
|
|
||||||
if value := header.Get(api.AmzACL); value != "" {
|
|
||||||
result[api.AmzACL] = value
|
|
||||||
}
|
|
||||||
if value := header.Get(api.AmzGrantRead); value != "" {
|
|
||||||
result[api.AmzGrantRead] = value
|
|
||||||
}
|
|
||||||
if value := header.Get(api.AmzGrantFullControl); value != "" {
|
|
||||||
result[api.AmzGrantFullControl] = value
|
|
||||||
}
|
|
||||||
if value := header.Get(api.AmzGrantWrite); value != "" {
|
|
||||||
result[api.AmzGrantWrite] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
reqInfo := middleware.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
@ -500,33 +460,6 @@ func (h *handler) completeMultipartUpload(r *http.Request, c *layer.CompleteMult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uploadData.ACLHeaders) != 0 {
|
|
||||||
sessionTokenSetEACL, err := getSessionTokenSetEACL(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get eacl token: %w", err)
|
|
||||||
}
|
|
||||||
key, err := h.bearerTokenIssuerKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get gate key: %w", err)
|
|
||||||
}
|
|
||||||
acl, err := parseACLHeaders(r.Header, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse acl: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resInfo := &resourceInfo{
|
|
||||||
Bucket: objInfo.Bucket,
|
|
||||||
Object: objInfo.Name,
|
|
||||||
}
|
|
||||||
astObject, err := aclToAst(acl, resInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not translate acl of completed multipart upload to ast: %w", err)
|
|
||||||
}
|
|
||||||
if _, err = h.updateBucketACL(r, astObject, bktInfo, sessionTokenSetEACL); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not update bucket acl while completing multipart upload: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return objInfo, nil
|
return objInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/retryer"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/retryer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
|
||||||
|
@ -173,10 +171,9 @@ func (p *policyCondition) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// keywords of predefined basic ACL values.
|
// keywords of predefined basic ACL values.
|
||||||
const (
|
const (
|
||||||
basicACLPrivate = "private"
|
basicACLPrivate = "private"
|
||||||
basicACLReadOnly = "public-read"
|
basicACLReadOnly = "public-read"
|
||||||
basicACLPublic = "public-read-write"
|
basicACLPublic = "public-read-write"
|
||||||
cannedACLAuthRead = "authenticated-read"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type createBucketParams struct {
|
type createBucketParams struct {
|
||||||
|
@ -186,12 +183,10 @@ type createBucketParams struct {
|
||||||
|
|
||||||
func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
newEaclTable *eacl.Table
|
cannedACLStatus = aclHeadersStatus(r)
|
||||||
sessionTokenEACL *session.Container
|
ctx = r.Context()
|
||||||
cannedACLStatus = aclHeadersStatus(r)
|
reqInfo = middleware.GetReqInfo(ctx)
|
||||||
ctx = r.Context()
|
|
||||||
reqInfo = middleware.GetReqInfo(ctx)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
|
@ -206,20 +201,11 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apeEnabled := bktInfo.APEEnabled || settings.CannedACL != ""
|
if cannedACLStatus == aclStatusYes {
|
||||||
if apeEnabled && cannedACLStatus == aclStatusYes {
|
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo)
|
|
||||||
if needUpdateEACLTable {
|
|
||||||
if sessionTokenEACL, err = getSessionTokenSetEACL(r.Context()); err != nil {
|
|
||||||
h.logAndSendError(w, "could not get eacl session token from a box", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tagSet, err := parseTaggingHeader(r.Header)
|
tagSet, err := parseTaggingHeader(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not parse tagging header", reqInfo, err)
|
h.logAndSendError(w, "could not parse tagging header", reqInfo, err)
|
||||||
|
@ -292,13 +278,6 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
objInfo := extendedObjInfo.ObjectInfo
|
objInfo := extendedObjInfo.ObjectInfo
|
||||||
|
|
||||||
if needUpdateEACLTable {
|
|
||||||
if newEaclTable, err = h.getNewEAclTable(r, bktInfo, objInfo); err != nil {
|
|
||||||
h.logAndSendError(w, "could not get new eacl table", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagSet != nil {
|
if tagSet != nil {
|
||||||
tagPrm := &data.PutObjectTaggingParams{
|
tagPrm := &data.PutObjectTaggingParams{
|
||||||
ObjectVersion: &data.ObjectVersion{
|
ObjectVersion: &data.ObjectVersion{
|
||||||
|
@ -315,19 +294,6 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newEaclTable != nil {
|
|
||||||
p := &layer.PutBucketACLParams{
|
|
||||||
BktInfo: bktInfo,
|
|
||||||
EACL: newEaclTable,
|
|
||||||
SessionToken: sessionTokenEACL,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = h.obj.PutBucketACL(r.Context(), p); err != nil {
|
|
||||||
h.logAndSendError(w, "could not put bucket acl", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.VersioningEnabled() {
|
if settings.VersioningEnabled() {
|
||||||
w.Header().Set(api.AmzVersionID, objInfo.VersionID())
|
w.Header().Set(api.AmzVersionID, objInfo.VersionID())
|
||||||
}
|
}
|
||||||
|
@ -459,13 +425,10 @@ func formEncryptionParamsBase(r *http.Request, isCopySource bool) (enc encryptio
|
||||||
|
|
||||||
func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
newEaclTable *eacl.Table
|
tagSet map[string]string
|
||||||
tagSet map[string]string
|
ctx = r.Context()
|
||||||
sessionTokenEACL *session.Container
|
reqInfo = middleware.GetReqInfo(ctx)
|
||||||
ctx = r.Context()
|
metadata = make(map[string]string)
|
||||||
reqInfo = middleware.GetReqInfo(ctx)
|
|
||||||
metadata = make(map[string]string)
|
|
||||||
cannedACLStatus = aclHeadersStatus(r)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
policy, err := checkPostPolicy(r, reqInfo, metadata)
|
policy, err := checkPostPolicy(r, reqInfo, metadata)
|
||||||
|
@ -501,20 +464,11 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apeEnabled := bktInfo.APEEnabled || settings.CannedACL != ""
|
if acl := auth.MultipartFormValue(r, "acl"); acl != "" && acl != basicACLPrivate {
|
||||||
if apeEnabled && cannedACLStatus == aclStatusYes {
|
|
||||||
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo)
|
|
||||||
if needUpdateEACLTable {
|
|
||||||
if sessionTokenEACL, err = getSessionTokenSetEACL(ctx); err != nil {
|
|
||||||
h.logAndSendError(w, "could not get eacl session token from a box", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var contentReader io.Reader
|
var contentReader io.Reader
|
||||||
var size uint64
|
var size uint64
|
||||||
if content, ok := r.MultipartForm.Value["file"]; ok {
|
if content, ok := r.MultipartForm.Value["file"]; ok {
|
||||||
|
@ -550,18 +504,6 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
objInfo := extendedObjInfo.ObjectInfo
|
objInfo := extendedObjInfo.ObjectInfo
|
||||||
|
|
||||||
if acl := auth.MultipartFormValue(r, "acl"); acl != "" {
|
|
||||||
r.Header.Set(api.AmzACL, acl)
|
|
||||||
r.Header.Set(api.AmzGrantFullControl, "")
|
|
||||||
r.Header.Set(api.AmzGrantWrite, "")
|
|
||||||
r.Header.Set(api.AmzGrantRead, "")
|
|
||||||
|
|
||||||
if newEaclTable, err = h.getNewEAclTable(r, bktInfo, objInfo); err != nil {
|
|
||||||
h.logAndSendError(w, "could not get new eacl table", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagSet != nil {
|
if tagSet != nil {
|
||||||
tagPrm := &data.PutObjectTaggingParams{
|
tagPrm := &data.PutObjectTaggingParams{
|
||||||
ObjectVersion: &data.ObjectVersion{
|
ObjectVersion: &data.ObjectVersion{
|
||||||
|
@ -578,19 +520,6 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newEaclTable != nil {
|
|
||||||
p := &layer.PutBucketACLParams{
|
|
||||||
BktInfo: bktInfo,
|
|
||||||
EACL: newEaclTable,
|
|
||||||
SessionToken: sessionTokenEACL,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = h.obj.PutBucketACL(ctx, p); err != nil {
|
|
||||||
h.logAndSendError(w, "could not put bucket acl", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.VersioningEnabled() {
|
if settings.VersioningEnabled() {
|
||||||
w.Header().Set(api.AmzVersionID, objInfo.VersionID())
|
w.Header().Set(api.AmzVersionID, objInfo.VersionID())
|
||||||
}
|
}
|
||||||
|
@ -716,56 +645,6 @@ func aclHeadersStatus(r *http.Request) aclStatus {
|
||||||
return aclStatusNo
|
return aclStatusNo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) getNewEAclTable(r *http.Request, bktInfo *data.BucketInfo, objInfo *data.ObjectInfo) (*eacl.Table, error) {
|
|
||||||
var newEaclTable *eacl.Table
|
|
||||||
key, err := h.bearerTokenIssuerKey(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get bearer token issuer: %w", err)
|
|
||||||
}
|
|
||||||
objectACL, err := parseACLHeaders(r.Header, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse object acl: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resInfo := &resourceInfo{
|
|
||||||
Bucket: objInfo.Bucket,
|
|
||||||
Object: objInfo.Name,
|
|
||||||
Version: objInfo.VersionID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
bktPolicy, err := aclToPolicy(objectACL, resInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not translate object acl to bucket policy: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
astChild, err := policyToAst(bktPolicy)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not translate policy to ast: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bacl, err := h.obj.GetBucketACL(r.Context(), bktInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not get bucket eacl: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parentAst := tableToAst(bacl.EACL, objInfo.Bucket)
|
|
||||||
strCID := bacl.Info.CID.EncodeToString()
|
|
||||||
|
|
||||||
for _, resource := range parentAst.Resources {
|
|
||||||
if resource.Bucket == strCID {
|
|
||||||
resource.Bucket = objInfo.Bucket
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resAst, updated := mergeAst(parentAst, astChild); updated {
|
|
||||||
if newEaclTable, err = astToTable(resAst); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not translate ast to table: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newEaclTable, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTaggingHeader(header http.Header) (map[string]string, error) {
|
func parseTaggingHeader(header http.Header) (map[string]string, error) {
|
||||||
var tagSet map[string]string
|
var tagSet map[string]string
|
||||||
if tagging := header.Get(api.AmzTagging); len(tagging) > 0 {
|
if tagging := header.Get(api.AmzTagging); len(tagging) > 0 {
|
||||||
|
@ -805,8 +684,7 @@ func parseCannedACL(header http.Header) (string, error) {
|
||||||
return basicACLPrivate, nil
|
return basicACLPrivate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if acl == basicACLPrivate || acl == basicACLPublic ||
|
if acl == basicACLPrivate || acl == basicACLPublic || acl == basicACLReadOnly {
|
||||||
acl == cannedACLAuthRead || acl == basicACLReadOnly {
|
|
||||||
return acl, nil
|
return acl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -814,11 +692,6 @@ func parseCannedACL(header http.Header) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if h.cfg.ACLEnabled() {
|
|
||||||
h.createBucketHandlerACL(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.createBucketHandlerPolicy(w, r)
|
h.createBucketHandlerPolicy(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -878,7 +751,6 @@ func (h *handler) createBucketHandlerPolicy(w http.ResponseWriter, r *http.Reque
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.APEEnabled = true
|
|
||||||
bktInfo, err := h.obj.CreateBucket(ctx, p)
|
bktInfo, err := h.obj.CreateBucket(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not create bucket", reqInfo, err)
|
h.logAndSendError(w, "could not create bucket", reqInfo, err)
|
||||||
|
@ -941,78 +813,6 @@ func (h *handler) putBucketSettingsRetryer() aws.RetryerV2 {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) createBucketHandlerACL(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
reqInfo := middleware.GetReqInfo(ctx)
|
|
||||||
|
|
||||||
boxData, err := middleware.GetBoxData(ctx)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "get access box from request", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key, p, err := h.parseCommonCreateBucketParams(reqInfo, boxData, r)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "parse create bucket params", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
aclPrm := &layer.PutBucketACLParams{SessionToken: boxData.Gate.SessionTokenForSetEACL()}
|
|
||||||
if aclPrm.SessionToken == nil {
|
|
||||||
h.logAndSendError(w, "couldn't find session token for setEACL", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bktACL, err := parseACLHeaders(r.Header, key)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not parse bucket acl", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resInfo := &resourceInfo{Bucket: reqInfo.BucketName}
|
|
||||||
|
|
||||||
aclPrm.EACL, err = bucketACLToTable(bktACL, resInfo)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could translate bucket acl to eacl", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bktInfo, err := h.obj.CreateBucket(ctx, p)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not create bucket", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.reqLogger(ctx).Info(logs.BucketIsCreated, zap.Stringer("container_id", bktInfo.CID))
|
|
||||||
|
|
||||||
aclPrm.BktInfo = bktInfo
|
|
||||||
if err = h.obj.PutBucketACL(r.Context(), aclPrm); err != nil {
|
|
||||||
h.logAndSendError(w, "could not put bucket e/ACL", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sp := &layer.PutSettingsParams{
|
|
||||||
BktInfo: bktInfo,
|
|
||||||
Settings: &data.BucketSettings{
|
|
||||||
OwnerKey: key,
|
|
||||||
Versioning: data.VersioningUnversioned,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.ObjectLockEnabled {
|
|
||||||
sp.Settings.Versioning = data.VersioningEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
|
||||||
h.logAndSendError(w, "couldn't save bucket settings", reqInfo, err,
|
|
||||||
zap.String("container_id", bktInfo.CID.EncodeToString()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = middleware.WriteSuccessResponseHeadersOnly(w); err != nil {
|
|
||||||
h.logAndSendError(w, "write response", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const s3ActionPrefix = "s3:"
|
const s3ActionPrefix = "s3:"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -1067,8 +867,6 @@ func bucketCannedACLToAPERules(cannedACL string, reqInfo *middleware.ReqInfo, cn
|
||||||
|
|
||||||
switch cannedACL {
|
switch cannedACL {
|
||||||
case basicACLPrivate:
|
case basicACLPrivate:
|
||||||
case cannedACLAuthRead:
|
|
||||||
fallthrough
|
|
||||||
case basicACLReadOnly:
|
case basicACLReadOnly:
|
||||||
chains[0].Rules = append(chains[0].Rules, chain.Rule{
|
chains[0].Rules = append(chains[0].Rules, chain.Rule{
|
||||||
Status: chain.Allow,
|
Status: chain.Allow,
|
||||||
|
|
|
@ -381,21 +381,6 @@ func TestCreateBucket(t *testing.T) {
|
||||||
createBucketAssertS3Error(hc, bktName, box2, s3errors.ErrBucketAlreadyExists)
|
createBucketAssertS3Error(hc, bktName, box2, s3errors.ErrBucketAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateOldBucketPutVersioning(t *testing.T) {
|
|
||||||
hc := prepareHandlerContext(t)
|
|
||||||
hc.config.aclEnabled = true
|
|
||||||
bktName := "bkt-name"
|
|
||||||
|
|
||||||
info := createBucket(hc, bktName)
|
|
||||||
settings, err := hc.tree.GetSettingsNode(hc.Context(), info.BktInfo)
|
|
||||||
require.NoError(t, err)
|
|
||||||
settings.OwnerKey = nil
|
|
||||||
err = hc.tree.PutSettingsNode(hc.Context(), info.BktInfo, settings)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
putBucketVersioning(t, hc, bktName, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateNamespacedBucket(t *testing.T) {
|
func TestCreateNamespacedBucket(t *testing.T) {
|
||||||
hc := prepareHandlerContext(t)
|
hc := prepareHandlerContext(t)
|
||||||
bktName := "bkt-name"
|
bktName := "bkt-name"
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
frosterrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
frosterrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -142,16 +141,3 @@ func parseRange(s string) (*layer.RangeParams, error) {
|
||||||
End: values[1],
|
End: values[1],
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSessionTokenSetEACL(ctx context.Context) (*session.Container, error) {
|
|
||||||
boxData, err := middleware.GetBoxData(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sessionToken := boxData.Gate.SessionTokenForSetEACL()
|
|
||||||
if sessionToken == nil {
|
|
||||||
return nil, s3errors.GetAPIError(s3errors.ErrAccessDenied)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessionToken, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,21 +12,9 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
|
||||||
// BucketACL extends BucketInfo by eacl.Table.
|
|
||||||
BucketACL struct {
|
|
||||||
Info *data.BucketInfo
|
|
||||||
EACL *eacl.Table
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
attributeLocationConstraint = ".s3-location-constraint"
|
attributeLocationConstraint = ".s3-location-constraint"
|
||||||
AttributeLockEnabled = "LockEnabled"
|
AttributeLockEnabled = "LockEnabled"
|
||||||
|
@ -64,7 +52,6 @@ func (n *Layer) containerInfo(ctx context.Context, prm PrmContainer) (*data.Buck
|
||||||
info.Created = container.CreatedAt(cnr)
|
info.Created = container.CreatedAt(cnr)
|
||||||
info.LocationConstraint = cnr.Attribute(attributeLocationConstraint)
|
info.LocationConstraint = cnr.Attribute(attributeLocationConstraint)
|
||||||
info.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(cnr)
|
info.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(cnr)
|
||||||
info.APEEnabled = cnr.BasicACL().Bits() == 0
|
|
||||||
|
|
||||||
attrLockEnabled := cnr.Attribute(AttributeLockEnabled)
|
attrLockEnabled := cnr.Attribute(AttributeLockEnabled)
|
||||||
if len(attrLockEnabled) > 0 {
|
if len(attrLockEnabled) > 0 {
|
||||||
|
@ -133,7 +120,6 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da
|
||||||
Created: TimeNow(ctx),
|
Created: TimeNow(ctx),
|
||||||
LocationConstraint: p.LocationConstraint,
|
LocationConstraint: p.LocationConstraint,
|
||||||
ObjectLockEnabled: p.ObjectLockEnabled,
|
ObjectLockEnabled: p.ObjectLockEnabled,
|
||||||
APEEnabled: p.APEEnabled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes := [][2]string{
|
attributes := [][2]string{
|
||||||
|
@ -146,11 +132,6 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
basicACL := acl.PublicRWExtended
|
|
||||||
if p.APEEnabled {
|
|
||||||
basicACL = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := n.frostFS.CreateContainer(ctx, PrmContainerCreate{
|
res, err := n.frostFS.CreateContainer(ctx, PrmContainerCreate{
|
||||||
Creator: bktInfo.Owner,
|
Creator: bktInfo.Owner,
|
||||||
Policy: p.Policy,
|
Policy: p.Policy,
|
||||||
|
@ -159,7 +140,7 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da
|
||||||
SessionToken: p.SessionContainerCreation,
|
SessionToken: p.SessionContainerCreation,
|
||||||
CreationTime: bktInfo.Created,
|
CreationTime: bktInfo.Created,
|
||||||
AdditionalAttributes: attributes,
|
AdditionalAttributes: attributes,
|
||||||
BasicACL: basicACL,
|
BasicACL: 0, // means APE
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create container: %w", err)
|
return nil, fmt.Errorf("create container: %w", err)
|
||||||
|
@ -172,17 +153,3 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da
|
||||||
|
|
||||||
return bktInfo, nil
|
return bktInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Layer) setContainerEACLTable(ctx context.Context, idCnr cid.ID, table *eacl.Table, sessionToken *session.Container) error {
|
|
||||||
table.SetCID(idCnr)
|
|
||||||
|
|
||||||
return n.frostFS.SetContainerEACL(ctx, *table, sessionToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Layer) GetContainerEACL(ctx context.Context, cnrID cid.ID) (*eacl.Table, error) {
|
|
||||||
prm := PrmContainerEACL{
|
|
||||||
ContainerID: cnrID,
|
|
||||||
SessionToken: n.SessionTokenForRead(ctx),
|
|
||||||
}
|
|
||||||
return n.frostFS.ContainerEACL(ctx, prm)
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -64,15 +63,6 @@ type PrmUserContainers struct {
|
||||||
SessionToken *session.Container
|
SessionToken *session.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrmContainerEACL groups parameters of FrostFS.ContainerEACL operation.
|
|
||||||
type PrmContainerEACL struct {
|
|
||||||
// Container identifier.
|
|
||||||
ContainerID cid.ID
|
|
||||||
|
|
||||||
// Token of the container's creation session. Nil means session absence.
|
|
||||||
SessionToken *session.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerCreateResult is a result parameter of FrostFS.CreateContainer operation.
|
// ContainerCreateResult is a result parameter of FrostFS.CreateContainer operation.
|
||||||
type ContainerCreateResult struct {
|
type ContainerCreateResult struct {
|
||||||
ContainerID cid.ID
|
ContainerID cid.ID
|
||||||
|
@ -216,18 +206,6 @@ type FrostFS interface {
|
||||||
// prevented the containers from being listed.
|
// prevented the containers from being listed.
|
||||||
UserContainers(context.Context, PrmUserContainers) ([]cid.ID, error)
|
UserContainers(context.Context, PrmUserContainers) ([]cid.ID, error)
|
||||||
|
|
||||||
// SetContainerEACL saves the eACL table of the container in FrostFS. The
|
|
||||||
// extended ACL is modified within session if session token is not nil.
|
|
||||||
//
|
|
||||||
// It returns any error encountered which prevented the eACL from being saved.
|
|
||||||
SetContainerEACL(context.Context, eacl.Table, *session.Container) error
|
|
||||||
|
|
||||||
// ContainerEACL reads the container eACL from FrostFS by the container ID.
|
|
||||||
//
|
|
||||||
// It returns exactly one non-nil value. It returns any error encountered which
|
|
||||||
// prevented the eACL from being read.
|
|
||||||
ContainerEACL(context.Context, PrmContainerEACL) (*eacl.Table, error)
|
|
||||||
|
|
||||||
// DeleteContainer marks the container to be removed from FrostFS by ID.
|
// DeleteContainer marks the container to be removed from FrostFS by ID.
|
||||||
// Request is sent within session if the session token is specified.
|
// Request is sent within session if the session token is specified.
|
||||||
// Successful return does not guarantee actual removal.
|
// Successful return does not guarantee actual removal.
|
||||||
|
|
|
@ -5,13 +5,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
|
||||||
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||||
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
|
@ -20,7 +18,6 @@ import (
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||||
|
@ -68,7 +65,6 @@ type TestFrostFS struct {
|
||||||
objectErrors map[string]error
|
objectErrors map[string]error
|
||||||
objectPutErrors map[string]error
|
objectPutErrors map[string]error
|
||||||
containers map[string]*container.Container
|
containers map[string]*container.Container
|
||||||
eaclTables map[string]*eacl.Table
|
|
||||||
currentEpoch uint64
|
currentEpoch uint64
|
||||||
key *keys.PrivateKey
|
key *keys.PrivateKey
|
||||||
}
|
}
|
||||||
|
@ -79,7 +75,6 @@ func NewTestFrostFS(key *keys.PrivateKey) *TestFrostFS {
|
||||||
objectErrors: make(map[string]error),
|
objectErrors: make(map[string]error),
|
||||||
objectPutErrors: make(map[string]error),
|
objectPutErrors: make(map[string]error),
|
||||||
containers: make(map[string]*container.Container),
|
containers: make(map[string]*container.Container),
|
||||||
eaclTables: make(map[string]*eacl.Table),
|
|
||||||
key: key,
|
key: key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,7 +217,7 @@ func (t *TestFrostFS) ReadObject(ctx context.Context, prm PrmObjectRead) (*Objec
|
||||||
|
|
||||||
if obj, ok := t.objects[sAddr]; ok {
|
if obj, ok := t.objects[sAddr]; ok {
|
||||||
owner := getBearerOwner(ctx)
|
owner := getBearerOwner(ctx)
|
||||||
if !t.checkAccess(prm.Container, owner, eacl.OperationGet, obj) {
|
if !t.checkAccess(prm.Container, owner) {
|
||||||
return nil, ErrAccessDenied
|
return nil, ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,9 +319,9 @@ func (t *TestFrostFS) DeleteObject(ctx context.Context, prm PrmObjectDelete) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj, ok := t.objects[addr.EncodeToString()]; ok {
|
if _, ok := t.objects[addr.EncodeToString()]; ok {
|
||||||
owner := getBearerOwner(ctx)
|
owner := getBearerOwner(ctx)
|
||||||
if !t.checkAccess(prm.Container, owner, eacl.OperationDelete, obj) {
|
if !t.checkAccess(prm.Container, owner) {
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,30 +349,6 @@ func (t *TestFrostFS) AllObjects(cnrID cid.ID) []oid.ID {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) SetContainerEACL(_ context.Context, table eacl.Table, _ *session.Container) error {
|
|
||||||
cnrID, ok := table.CID()
|
|
||||||
if !ok {
|
|
||||||
return errors.New("invalid cid")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok = t.containers[cnrID.EncodeToString()]; !ok {
|
|
||||||
return errors.New("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.eaclTables[cnrID.EncodeToString()] = &table
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestFrostFS) ContainerEACL(_ context.Context, prm PrmContainerEACL) (*eacl.Table, error) {
|
|
||||||
table, ok := t.eaclTables[prm.ContainerID.EncodeToString()]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return table, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]oid.ID, error) {
|
func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]oid.ID, error) {
|
||||||
filters := object.NewSearchFilters()
|
filters := object.NewSearchFilters()
|
||||||
filters.AddRootFilter()
|
filters.AddRootFilter()
|
||||||
|
@ -415,7 +386,7 @@ func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]o
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID, op eacl.Operation, obj *object.Object) bool {
|
func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID) bool {
|
||||||
cnr, ok := t.containers[cnrID.EncodeToString()]
|
cnr, ok := t.containers[cnrID.EncodeToString()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
|
@ -425,57 +396,6 @@ func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID, op eacl.Operation
|
||||||
return cnr.Owner().Equals(owner)
|
return cnr.Owner().Equals(owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
table, ok := t.eaclTables[cnrID.EncodeToString()]
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rec := range table.Records() {
|
|
||||||
if rec.Operation() != op {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matchTarget(rec, owner) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if matchFilter(rec.Filters(), obj) {
|
|
||||||
return rec.Action() == eacl.ActionAllow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchTarget(rec eacl.Record, owner user.ID) bool {
|
|
||||||
for _, trgt := range rec.Targets() {
|
|
||||||
if trgt.Role() == eacl.RoleOthers {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
var targetOwner user.ID
|
|
||||||
for _, pk := range eacl.TargetECDSAKeys(&trgt) {
|
|
||||||
user.IDFromKey(&targetOwner, *pk)
|
|
||||||
if targetOwner.Equals(owner) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchFilter(filters []eacl.Filter, obj *object.Object) bool {
|
|
||||||
objID, _ := obj.ID()
|
|
||||||
for _, f := range filters {
|
|
||||||
fv2 := f.ToV2()
|
|
||||||
if fv2.GetMatchType() != acl.MatchTypeStringEqual ||
|
|
||||||
fv2.GetHeaderType() != acl.HeaderTypeObject ||
|
|
||||||
fv2.GetKey() != acl.FilterObjectID ||
|
|
||||||
fv2.GetValue() != objID.EncodeToString() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||||
|
@ -159,13 +158,6 @@ type (
|
||||||
SessionContainerCreation *session.Container
|
SessionContainerCreation *session.Container
|
||||||
LocationConstraint string
|
LocationConstraint string
|
||||||
ObjectLockEnabled bool
|
ObjectLockEnabled bool
|
||||||
APEEnabled bool
|
|
||||||
}
|
|
||||||
// PutBucketACLParams stores put bucket acl request parameters.
|
|
||||||
PutBucketACLParams struct {
|
|
||||||
BktInfo *data.BucketInfo
|
|
||||||
EACL *eacl.Table
|
|
||||||
SessionToken *session.Container
|
|
||||||
}
|
}
|
||||||
// DeleteBucketParams stores delete bucket request parameters.
|
// DeleteBucketParams stores delete bucket request parameters.
|
||||||
DeleteBucketParams struct {
|
DeleteBucketParams struct {
|
||||||
|
@ -350,24 +342,6 @@ func (n *Layer) ResolveCID(ctx context.Context, name string) (cid.ID, error) {
|
||||||
return n.ResolveBucket(ctx, name)
|
return n.ResolveBucket(ctx, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketACL returns bucket acl info by name.
|
|
||||||
func (n *Layer) GetBucketACL(ctx context.Context, bktInfo *data.BucketInfo) (*BucketACL, error) {
|
|
||||||
eACL, err := n.GetContainerEACL(ctx, bktInfo.CID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("get container eacl: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &BucketACL{
|
|
||||||
Info: bktInfo,
|
|
||||||
EACL: eACL,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutBucketACL puts bucket acl by name.
|
|
||||||
func (n *Layer) PutBucketACL(ctx context.Context, param *PutBucketACLParams) error {
|
|
||||||
return n.setContainerEACLTable(ctx, param.BktInfo.CID, param.EACL, param.SessionToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListBuckets returns all user containers. The name of the bucket is a container
|
// ListBuckets returns all user containers. The name of the bucket is a container
|
||||||
// id. Timestamp is omitted since it is not saved in frostfs container.
|
// id. Timestamp is omitted since it is not saved in frostfs container.
|
||||||
func (n *Layer) ListBuckets(ctx context.Context) ([]*data.BucketInfo, error) {
|
func (n *Layer) ListBuckets(ctx context.Context) ([]*data.BucketInfo, error) {
|
||||||
|
|
|
@ -36,7 +36,6 @@ const (
|
||||||
MultipartObjectSize = "S3-Multipart-Object-Size"
|
MultipartObjectSize = "S3-Multipart-Object-Size"
|
||||||
|
|
||||||
metaPrefix = "meta-"
|
metaPrefix = "meta-"
|
||||||
aclPrefix = "acl-"
|
|
||||||
|
|
||||||
MaxSizeUploadsList = 1000
|
MaxSizeUploadsList = 1000
|
||||||
MaxSizePartsList = 1000
|
MaxSizePartsList = 1000
|
||||||
|
@ -62,8 +61,7 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadData struct {
|
UploadData struct {
|
||||||
TagSet map[string]string
|
TagSet map[string]string
|
||||||
ACLHeaders map[string]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadPartParams struct {
|
UploadPartParams struct {
|
||||||
|
@ -149,7 +147,6 @@ type (
|
||||||
func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartParams) error {
|
func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartParams) error {
|
||||||
metaSize := len(p.Header)
|
metaSize := len(p.Header)
|
||||||
if p.Data != nil {
|
if p.Data != nil {
|
||||||
metaSize += len(p.Data.ACLHeaders)
|
|
||||||
metaSize += len(p.Data.TagSet)
|
metaSize += len(p.Data.TagSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,10 +164,6 @@ func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartPar
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Data != nil {
|
if p.Data != nil {
|
||||||
for key, val := range p.Data.ACLHeaders {
|
|
||||||
info.Meta[aclPrefix+key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, val := range p.Data.TagSet {
|
for key, val := range p.Data.TagSet {
|
||||||
info.Meta[tagPrefix+key] = val
|
info.Meta[tagPrefix+key] = val
|
||||||
}
|
}
|
||||||
|
@ -432,16 +425,13 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
||||||
initMetadata[MultipartObjectSize] = strconv.FormatUint(multipartObjetSize, 10)
|
initMetadata[MultipartObjectSize] = strconv.FormatUint(multipartObjetSize, 10)
|
||||||
|
|
||||||
uploadData := &UploadData{
|
uploadData := &UploadData{
|
||||||
TagSet: make(map[string]string),
|
TagSet: make(map[string]string),
|
||||||
ACLHeaders: make(map[string]string),
|
|
||||||
}
|
}
|
||||||
for key, val := range multipartInfo.Meta {
|
for key, val := range multipartInfo.Meta {
|
||||||
if strings.HasPrefix(key, metaPrefix) {
|
if strings.HasPrefix(key, metaPrefix) {
|
||||||
initMetadata[strings.TrimPrefix(key, metaPrefix)] = val
|
initMetadata[strings.TrimPrefix(key, metaPrefix)] = val
|
||||||
} else if strings.HasPrefix(key, tagPrefix) {
|
} else if strings.HasPrefix(key, tagPrefix) {
|
||||||
uploadData.TagSet[strings.TrimPrefix(key, tagPrefix)] = val
|
uploadData.TagSet[strings.TrimPrefix(key, tagPrefix)] = val
|
||||||
} else if strings.HasPrefix(key, aclPrefix) {
|
|
||||||
uploadData.ACLHeaders[strings.TrimPrefix(key, aclPrefix)] = val
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,6 @@ var withoutResourceOps = []string{
|
||||||
|
|
||||||
type PolicySettings interface {
|
type PolicySettings interface {
|
||||||
PolicyDenyByDefault() bool
|
PolicyDenyByDefault() bool
|
||||||
ACLEnabled() bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FrostFSIDInformer interface {
|
type FrostFSIDInformer interface {
|
||||||
|
@ -150,12 +149,7 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error {
|
||||||
return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
isAPE := !cfg.Settings.ACLEnabled()
|
if cfg.Settings.PolicyDenyByDefault() {
|
||||||
if bktInfo != nil {
|
|
||||||
isAPE = bktInfo.APEEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAPE && cfg.Settings.PolicyDenyByDefault() {
|
|
||||||
return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,6 @@ func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
|
||||||
|
|
||||||
type middlewareSettingsMock struct {
|
type middlewareSettingsMock struct {
|
||||||
denyByDefault bool
|
denyByDefault bool
|
||||||
aclEnabled bool
|
|
||||||
sourceIPHeader string
|
sourceIPHeader string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,10 +91,6 @@ func (r *middlewareSettingsMock) PolicyDenyByDefault() bool {
|
||||||
return r.denyByDefault
|
return r.denyByDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *middlewareSettingsMock) ACLEnabled() bool {
|
|
||||||
return r.aclEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
type frostFSIDMock struct {
|
type frostFSIDMock struct {
|
||||||
tags map[string]string
|
tags map[string]string
|
||||||
validateError bool
|
validateError bool
|
||||||
|
@ -429,8 +424,7 @@ func (h *handlerMock) CreateBucketHandler(w http.ResponseWriter, r *http.Request
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
reqInfo := middleware.GetReqInfo(r.Context())
|
||||||
|
|
||||||
h.buckets[reqInfo.Namespace+reqInfo.BucketName] = &data.BucketInfo{
|
h.buckets[reqInfo.Namespace+reqInfo.BucketName] = &data.BucketInfo{
|
||||||
Name: reqInfo.BucketName,
|
Name: reqInfo.BucketName,
|
||||||
APEEnabled: !h.cfg.ACLEnabled(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &handlerResult{
|
res := &handlerResult{
|
||||||
|
|
|
@ -37,6 +37,7 @@ type routerMock struct {
|
||||||
cfg Config
|
cfg Config
|
||||||
middlewareSettings *middlewareSettingsMock
|
middlewareSettings *middlewareSettingsMock
|
||||||
policyChecker engine.LocalOverrideEngine
|
policyChecker engine.LocalOverrideEngine
|
||||||
|
handler *handlerMock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -64,12 +65,14 @@ func prepareRouter(t *testing.T, opts ...option) *routerMock {
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlerTestMock := &handlerMock{t: t, cfg: middlewareSettings, buckets: map[string]*data.BucketInfo{}}
|
||||||
|
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
Throttle: middleware.ThrottleOpts{
|
Throttle: middleware.ThrottleOpts{
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
BacklogTimeout: 30 * time.Second,
|
BacklogTimeout: 30 * time.Second,
|
||||||
},
|
},
|
||||||
Handler: &handlerMock{t: t, cfg: middlewareSettings, buckets: map[string]*data.BucketInfo{}},
|
Handler: handlerTestMock,
|
||||||
Center: ¢erMock{t: t},
|
Center: ¢erMock{t: t},
|
||||||
Log: logger,
|
Log: logger,
|
||||||
Metrics: metrics.NewAppMetrics(metricsConfig),
|
Metrics: metrics.NewAppMetrics(metricsConfig),
|
||||||
|
@ -91,6 +94,7 @@ func prepareRouter(t *testing.T, opts ...option) *routerMock {
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
middlewareSettings: middlewareSettings,
|
middlewareSettings: middlewareSettings,
|
||||||
policyChecker: policyChecker,
|
policyChecker: policyChecker,
|
||||||
|
handler: handlerTestMock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,102 +303,6 @@ func TestDefaultPolicyCheckerWithUserTags(t *testing.T) {
|
||||||
createBucket(router, ns, bktName)
|
createBucket(router, ns, bktName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACLAPE(t *testing.T) {
|
|
||||||
t.Run("acl disabled, ape deny by default", func(t *testing.T) {
|
|
||||||
router := prepareRouter(t)
|
|
||||||
|
|
||||||
ns, bktName, objName := "", "bucket", "object"
|
|
||||||
bktNameOld, bktNameNew := "old-bucket", "new-bucket"
|
|
||||||
createOldBucket(router, bktNameOld)
|
|
||||||
createNewBucket(router, bktNameNew)
|
|
||||||
|
|
||||||
router.middlewareSettings.aclEnabled = false
|
|
||||||
router.middlewareSettings.denyByDefault = true
|
|
||||||
|
|
||||||
// Allow because of using old bucket
|
|
||||||
putObject(router, ns, bktNameOld, objName, nil)
|
|
||||||
// Deny because of deny by default
|
|
||||||
putObjectErr(router, ns, bktNameNew, objName, nil, apiErrors.ErrAccessDenied)
|
|
||||||
|
|
||||||
// Deny because of deny by default
|
|
||||||
createBucketErr(router, ns, bktName, nil, apiErrors.ErrAccessDenied)
|
|
||||||
listBucketsErr(router, ns, apiErrors.ErrAccessDenied)
|
|
||||||
|
|
||||||
// Allow operations and check
|
|
||||||
allowOperations(router, ns, []string{"s3:CreateBucket", "s3:ListAllMyBuckets"}, nil)
|
|
||||||
createBucket(router, ns, bktName)
|
|
||||||
listBuckets(router, ns)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("acl disabled, ape allow by default", func(t *testing.T) {
|
|
||||||
router := prepareRouter(t)
|
|
||||||
|
|
||||||
ns, bktName, objName := "", "bucket", "object"
|
|
||||||
bktNameOld, bktNameNew := "old-bucket", "new-bucket"
|
|
||||||
createOldBucket(router, bktNameOld)
|
|
||||||
createNewBucket(router, bktNameNew)
|
|
||||||
|
|
||||||
router.middlewareSettings.aclEnabled = false
|
|
||||||
router.middlewareSettings.denyByDefault = false
|
|
||||||
|
|
||||||
// Allow because of using old bucket
|
|
||||||
putObject(router, ns, bktNameOld, objName, nil)
|
|
||||||
// Allow because of allow by default
|
|
||||||
putObject(router, ns, bktNameNew, objName, nil)
|
|
||||||
|
|
||||||
// Allow because of deny by default
|
|
||||||
createBucket(router, ns, bktName)
|
|
||||||
listBuckets(router, ns)
|
|
||||||
|
|
||||||
// Deny operations and check
|
|
||||||
denyOperations(router, ns, []string{"s3:CreateBucket", "s3:ListAllMyBuckets"}, nil)
|
|
||||||
createBucketErr(router, ns, bktName, nil, apiErrors.ErrAccessDenied)
|
|
||||||
listBucketsErr(router, ns, apiErrors.ErrAccessDenied)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("acl enabled, ape deny by default", func(t *testing.T) {
|
|
||||||
router := prepareRouter(t)
|
|
||||||
|
|
||||||
ns, bktName, objName := "", "bucket", "object"
|
|
||||||
bktNameOld, bktNameNew := "old-bucket", "new-bucket"
|
|
||||||
createOldBucket(router, bktNameOld)
|
|
||||||
createNewBucket(router, bktNameNew)
|
|
||||||
|
|
||||||
router.middlewareSettings.aclEnabled = true
|
|
||||||
router.middlewareSettings.denyByDefault = true
|
|
||||||
|
|
||||||
// Allow because of using old bucket
|
|
||||||
putObject(router, ns, bktNameOld, objName, nil)
|
|
||||||
// Deny because of deny by default
|
|
||||||
putObjectErr(router, ns, bktNameNew, objName, nil, apiErrors.ErrAccessDenied)
|
|
||||||
|
|
||||||
// Allow because of old behavior
|
|
||||||
createBucket(router, ns, bktName)
|
|
||||||
listBuckets(router, ns)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("acl enabled, ape allow by default", func(t *testing.T) {
|
|
||||||
router := prepareRouter(t)
|
|
||||||
|
|
||||||
ns, bktName, objName := "", "bucket", "object"
|
|
||||||
bktNameOld, bktNameNew := "old-bucket", "new-bucket"
|
|
||||||
createOldBucket(router, bktNameOld)
|
|
||||||
createNewBucket(router, bktNameNew)
|
|
||||||
|
|
||||||
router.middlewareSettings.aclEnabled = true
|
|
||||||
router.middlewareSettings.denyByDefault = false
|
|
||||||
|
|
||||||
// Allow because of using old bucket
|
|
||||||
putObject(router, ns, bktNameOld, objName, nil)
|
|
||||||
// Allow because of allow by default
|
|
||||||
putObject(router, ns, bktNameNew, objName, nil)
|
|
||||||
|
|
||||||
// Allow because of old behavior
|
|
||||||
createBucket(router, ns, bktName)
|
|
||||||
listBuckets(router, ns)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestParametersCheck(t *testing.T) {
|
func TestRequestParametersCheck(t *testing.T) {
|
||||||
t.Run("prefix parameter, allow specific value", func(t *testing.T) {
|
t.Run("prefix parameter, allow specific value", func(t *testing.T) {
|
||||||
router := prepareRouter(t)
|
router := prepareRouter(t)
|
||||||
|
@ -717,21 +625,6 @@ func addPolicy(router *routerMock, ns string, id string, effect engineiam.Effect
|
||||||
require.NoError(router.t, err)
|
require.NoError(router.t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createOldBucket(router *routerMock, bktName string) {
|
|
||||||
createSpecificBucket(router, bktName, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNewBucket(router *routerMock, bktName string) {
|
|
||||||
createSpecificBucket(router, bktName, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSpecificBucket(router *routerMock, bktName string, old bool) {
|
|
||||||
aclEnabled := router.middlewareSettings.ACLEnabled()
|
|
||||||
router.middlewareSettings.aclEnabled = old
|
|
||||||
createBucket(router, "", bktName)
|
|
||||||
router.middlewareSettings.aclEnabled = aclEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func createBucket(router *routerMock, namespace, bktName string) {
|
func createBucket(router *routerMock, namespace, bktName string) {
|
||||||
w := createBucketBase(router, namespace, bktName, nil)
|
w := createBucketBase(router, namespace, bktName, nil)
|
||||||
resp := readResponse(router.t, w)
|
resp := readResponse(router.t, w)
|
||||||
|
@ -754,24 +647,6 @@ func createBucketBase(router *routerMock, namespace, bktName string, header http
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func listBuckets(router *routerMock, namespace string) {
|
|
||||||
w := listBucketsBase(router, namespace)
|
|
||||||
resp := readResponse(router.t, w)
|
|
||||||
require.Equal(router.t, s3middleware.ListBucketsOperation, resp.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listBucketsErr(router *routerMock, namespace string, errCode apiErrors.ErrorCode) {
|
|
||||||
w := listBucketsBase(router, namespace)
|
|
||||||
assertAPIError(router.t, w, errCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listBucketsBase(router *routerMock, namespace string) *httptest.ResponseRecorder {
|
|
||||||
w, r := httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
r.Header.Set(FrostfsNamespaceHeader, namespace)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBucketErr(router *routerMock, namespace, bktName string, errCode apiErrors.ErrorCode) {
|
func getBucketErr(router *routerMock, namespace, bktName string, errCode apiErrors.ErrorCode) {
|
||||||
w := getBucketBase(router, namespace, bktName)
|
w := getBucketBase(router, namespace, bktName)
|
||||||
assertAPIError(router.t, w, errCode)
|
assertAPIError(router.t, w, errCode)
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -102,7 +101,6 @@ type (
|
||||||
Container ContainerOptions
|
Container ContainerOptions
|
||||||
FrostFSKey *keys.PrivateKey
|
FrostFSKey *keys.PrivateKey
|
||||||
GatesPublicKeys []*keys.PublicKey
|
GatesPublicKeys []*keys.PublicKey
|
||||||
EACLRules []byte
|
|
||||||
Impersonate bool
|
Impersonate bool
|
||||||
SessionTokenRules []byte
|
SessionTokenRules []byte
|
||||||
SkipSessionRules bool
|
SkipSessionRules bool
|
||||||
|
@ -441,47 +439,11 @@ func (a *Agent) ObtainSecret(ctx context.Context, w io.Writer, options *ObtainSe
|
||||||
return enc.Encode(or)
|
return enc.Encode(or)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildEACLTable(eaclTable []byte) (*eacl.Table, error) {
|
func buildBearerToken(key *keys.PrivateKey, impersonate bool, lifetime lifetimeOptions, gateKey *keys.PublicKey) (*bearer.Token, error) {
|
||||||
table := eacl.NewTable()
|
|
||||||
if len(eaclTable) != 0 {
|
|
||||||
return table, table.UnmarshalJSON(eaclTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
record := eacl.NewRecord()
|
|
||||||
record.SetOperation(eacl.OperationGet)
|
|
||||||
record.SetAction(eacl.ActionAllow)
|
|
||||||
eacl.AddFormedTarget(record, eacl.RoleOthers)
|
|
||||||
table.AddRecord(record)
|
|
||||||
|
|
||||||
for _, rec := range restrictedRecords() {
|
|
||||||
table.AddRecord(rec)
|
|
||||||
}
|
|
||||||
|
|
||||||
return table, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func restrictedRecords() (records []*eacl.Record) {
|
|
||||||
for op := eacl.OperationGet; op <= eacl.OperationRangeHash; op++ {
|
|
||||||
record := eacl.NewRecord()
|
|
||||||
record.SetOperation(op)
|
|
||||||
record.SetAction(eacl.ActionDeny)
|
|
||||||
eacl.AddFormedTarget(record, eacl.RoleOthers)
|
|
||||||
records = append(records, record)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBearerToken(key *keys.PrivateKey, impersonate bool, table *eacl.Table, lifetime lifetimeOptions, gateKey *keys.PublicKey) (*bearer.Token, error) {
|
|
||||||
var ownerID user.ID
|
var ownerID user.ID
|
||||||
user.IDFromKey(&ownerID, (ecdsa.PublicKey)(*gateKey))
|
user.IDFromKey(&ownerID, (ecdsa.PublicKey)(*gateKey))
|
||||||
|
|
||||||
var bearerToken bearer.Token
|
var bearerToken bearer.Token
|
||||||
|
|
||||||
if !impersonate {
|
|
||||||
bearerToken.SetEACLTable(*table)
|
|
||||||
}
|
|
||||||
|
|
||||||
bearerToken.ForUser(ownerID)
|
bearerToken.ForUser(ownerID)
|
||||||
bearerToken.SetExp(lifetime.Exp)
|
bearerToken.SetExp(lifetime.Exp)
|
||||||
bearerToken.SetIat(lifetime.Iat)
|
bearerToken.SetIat(lifetime.Iat)
|
||||||
|
@ -496,10 +458,10 @@ func buildBearerToken(key *keys.PrivateKey, impersonate bool, table *eacl.Table,
|
||||||
return &bearerToken, nil
|
return &bearerToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildBearerTokens(key *keys.PrivateKey, impersonate bool, table *eacl.Table, lifetime lifetimeOptions, gatesKeys []*keys.PublicKey) ([]*bearer.Token, error) {
|
func buildBearerTokens(key *keys.PrivateKey, impersonate bool, lifetime lifetimeOptions, gatesKeys []*keys.PublicKey) ([]*bearer.Token, error) {
|
||||||
bearerTokens := make([]*bearer.Token, 0, len(gatesKeys))
|
bearerTokens := make([]*bearer.Token, 0, len(gatesKeys))
|
||||||
for _, gateKey := range gatesKeys {
|
for _, gateKey := range gatesKeys {
|
||||||
tkn, err := buildBearerToken(key, impersonate, table, lifetime, gateKey)
|
tkn, err := buildBearerToken(key, impersonate, lifetime, gateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("build bearer token: %w", err)
|
return nil, fmt.Errorf("build bearer token: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -544,12 +506,7 @@ func buildSessionTokens(key *keys.PrivateKey, lifetime lifetimeOptions, ctxs []s
|
||||||
func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions) ([]*accessbox.GateData, error) {
|
func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions) ([]*accessbox.GateData, error) {
|
||||||
gates := make([]*accessbox.GateData, len(options.GatesPublicKeys))
|
gates := make([]*accessbox.GateData, len(options.GatesPublicKeys))
|
||||||
|
|
||||||
table, err := buildEACLTable(options.EACLRules)
|
bearerTokens, err := buildBearerTokens(options.FrostFSKey, options.Impersonate, lifetime, options.GatesPublicKeys)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to build eacl table: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bearerTokens, err := buildBearerTokens(options.FrostFSKey, options.Impersonate, table, lifetime, options.GatesPublicKeys)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to build bearer tokens: %w", err)
|
return nil, fmt.Errorf("failed to build bearer tokens: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -577,9 +534,14 @@ func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions) ([]*acc
|
||||||
|
|
||||||
func formTokensToUpdate(options tokenUpdateOptions) ([]*accessbox.GateData, error) {
|
func formTokensToUpdate(options tokenUpdateOptions) ([]*accessbox.GateData, error) {
|
||||||
btoken := options.box.Gate.BearerToken
|
btoken := options.box.Gate.BearerToken
|
||||||
table := btoken.EACLTable()
|
|
||||||
|
|
||||||
bearerTokens, err := buildBearerTokens(options.frostFSKey, btoken.Impersonate(), &table, options.lifetime, options.gatesPublicKeys)
|
btokenv2 := new(acl.BearerToken)
|
||||||
|
btoken.WriteToV2(btokenv2)
|
||||||
|
if btokenv2.GetBody().GetEACL() != nil {
|
||||||
|
return nil, errors.New("EACL table in bearer token isn't supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
bearerTokens, err := buildBearerTokens(options.frostFSKey, btoken.Impersonate(), options.lifetime, options.gatesPublicKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to build bearer tokens: %w", err)
|
return nil, fmt.Errorf("failed to build bearer tokens: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package authmate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
apisession "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
apisession "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||||
|
@ -55,22 +56,11 @@ func buildContext(rules []byte) ([]sessionTokenContext, error) {
|
||||||
return nil, fmt.Errorf("failed to unmarshal rules for session token: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal rules for session token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
containsPut = false
|
|
||||||
containsSetEACL = false
|
|
||||||
)
|
|
||||||
for _, d := range sessionCtxs {
|
for _, d := range sessionCtxs {
|
||||||
if d.verb == session.VerbContainerPut {
|
if d.verb == session.VerbContainerSetEACL {
|
||||||
containsPut = true
|
return nil, errors.New("verb container SetEACL isn't supported")
|
||||||
} else if d.verb == session.VerbContainerSetEACL {
|
|
||||||
containsSetEACL = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if containsPut && !containsSetEACL {
|
|
||||||
sessionCtxs = append(sessionCtxs, sessionTokenContext{
|
|
||||||
verb: session.VerbContainerSetEACL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessionCtxs, nil
|
return sessionCtxs, nil
|
||||||
}
|
}
|
||||||
|
@ -78,6 +68,5 @@ func buildContext(rules []byte) ([]sessionTokenContext, error) {
|
||||||
return []sessionTokenContext{
|
return []sessionTokenContext{
|
||||||
{verb: session.VerbContainerPut},
|
{verb: session.VerbContainerPut},
|
||||||
{verb: session.VerbContainerDelete},
|
{verb: session.VerbContainerDelete},
|
||||||
{verb: session.VerbContainerSetEACL},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,20 +17,15 @@ func TestContainerSessionRules(t *testing.T) {
|
||||||
{
|
{
|
||||||
"verb": "DELETE",
|
"verb": "DELETE",
|
||||||
"containerID": "6CcWg8LkcbfMUC8pt7wiy5zM1fyS3psNoxgfppcCgig1"
|
"containerID": "6CcWg8LkcbfMUC8pt7wiy5zM1fyS3psNoxgfppcCgig1"
|
||||||
},
|
|
||||||
{
|
|
||||||
"verb": "SETEACL"
|
|
||||||
}
|
}
|
||||||
]`)
|
]`)
|
||||||
|
|
||||||
sessionContext, err := buildContext(jsonRules)
|
sessionContext, err := buildContext(jsonRules)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, sessionContext, 3)
|
require.Len(t, sessionContext, 2)
|
||||||
require.Equal(t, sessionContext[0].verb, session.VerbContainerPut)
|
require.Equal(t, sessionContext[0].verb, session.VerbContainerPut)
|
||||||
require.Zero(t, sessionContext[0].containerID)
|
require.Zero(t, sessionContext[0].containerID)
|
||||||
require.Equal(t, sessionContext[1].verb, session.VerbContainerDelete)
|
require.Equal(t, sessionContext[1].verb, session.VerbContainerDelete)
|
||||||
require.NotNil(t, sessionContext[1].containerID)
|
require.NotNil(t, sessionContext[1].containerID)
|
||||||
require.Equal(t, sessionContext[2].verb, session.VerbContainerSetEACL)
|
|
||||||
require.Zero(t, sessionContext[2].containerID)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package modules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -29,8 +28,6 @@ const (
|
||||||
walletFlag = "wallet"
|
walletFlag = "wallet"
|
||||||
addressFlag = "address"
|
addressFlag = "address"
|
||||||
peerFlag = "peer"
|
peerFlag = "peer"
|
||||||
bearerRulesFlag = "bearer-rules"
|
|
||||||
disableImpersonateFlag = "disable-impersonate"
|
|
||||||
gatePublicKeyFlag = "gate-public-key"
|
gatePublicKeyFlag = "gate-public-key"
|
||||||
containerIDFlag = "container-id"
|
containerIDFlag = "container-id"
|
||||||
containerFriendlyNameFlag = "container-friendly-name"
|
containerFriendlyNameFlag = "container-friendly-name"
|
||||||
|
@ -70,8 +67,6 @@ func initIssueSecretCmd() {
|
||||||
issueSecretCmd.Flags().String(walletFlag, "", "Path to the wallet that will be owner of the credentials")
|
issueSecretCmd.Flags().String(walletFlag, "", "Path to the wallet that will be owner of the credentials")
|
||||||
issueSecretCmd.Flags().String(addressFlag, "", "Address of the wallet account")
|
issueSecretCmd.Flags().String(addressFlag, "", "Address of the wallet account")
|
||||||
issueSecretCmd.Flags().String(peerFlag, "", "Address of a frostfs peer to connect to")
|
issueSecretCmd.Flags().String(peerFlag, "", "Address of a frostfs peer to connect to")
|
||||||
issueSecretCmd.Flags().String(bearerRulesFlag, "", "Rules for bearer token (filepath or a plain json string are allowed, can be used only with --disable-impersonate)")
|
|
||||||
issueSecretCmd.Flags().Bool(disableImpersonateFlag, false, "Mark token as not impersonate to don't consider token signer as request owner (must be provided to use --bearer-rules flag)")
|
|
||||||
issueSecretCmd.Flags().StringSlice(gatePublicKeyFlag, nil, "Public 256r1 key of a gate (use flags repeatedly for multiple gates or separate them by comma)")
|
issueSecretCmd.Flags().StringSlice(gatePublicKeyFlag, nil, "Public 256r1 key of a gate (use flags repeatedly for multiple gates or separate them by comma)")
|
||||||
issueSecretCmd.Flags().String(containerIDFlag, "", "Auth container id to put the secret into (if not provided new container will be created)")
|
issueSecretCmd.Flags().String(containerIDFlag, "", "Auth container id to put the secret into (if not provided new container will be created)")
|
||||||
issueSecretCmd.Flags().String(containerFriendlyNameFlag, "", "Friendly name of auth container to put the secret into (flag value will be used only if --container-id is missed)")
|
issueSecretCmd.Flags().String(containerFriendlyNameFlag, "", "Friendly name of auth container to put the secret into (flag value will be used only if --container-id is missed)")
|
||||||
|
@ -134,17 +129,6 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
return wrapPreparationError(fmt.Errorf("couldn't parse container policy: %s", err.Error()))
|
return wrapPreparationError(fmt.Errorf("couldn't parse container policy: %s", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
disableImpersonate := viper.GetBool(disableImpersonateFlag)
|
|
||||||
eaclRules := viper.GetString(bearerRulesFlag)
|
|
||||||
if !disableImpersonate && eaclRules != "" {
|
|
||||||
return wrapPreparationError(errors.New("--bearer-rules flag can be used only with --disable-impersonate"))
|
|
||||||
}
|
|
||||||
|
|
||||||
bearerRules, err := getJSONRules(eaclRules)
|
|
||||||
if err != nil {
|
|
||||||
return wrapPreparationError(fmt.Errorf("couldn't parse 'bearer-rules' flag: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionRules, skipSessionRules, err := getSessionRules(viper.GetString(sessionTokensFlag))
|
sessionRules, skipSessionRules, err := getSessionRules(viper.GetString(sessionTokensFlag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapPreparationError(fmt.Errorf("couldn't parse 'session-tokens' flag: %s", err.Error()))
|
return wrapPreparationError(fmt.Errorf("couldn't parse 'session-tokens' flag: %s", err.Error()))
|
||||||
|
@ -200,8 +184,7 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error {
|
||||||
},
|
},
|
||||||
FrostFSKey: key,
|
FrostFSKey: key,
|
||||||
GatesPublicKeys: gatesPublicKeys,
|
GatesPublicKeys: gatesPublicKeys,
|
||||||
EACLRules: bearerRules,
|
Impersonate: true,
|
||||||
Impersonate: !disableImpersonate,
|
|
||||||
SessionTokenRules: sessionRules,
|
SessionTokenRules: sessionRules,
|
||||||
SkipSessionRules: skipSessionRules,
|
SkipSessionRules: skipSessionRules,
|
||||||
ContainerPolicies: policies,
|
ContainerPolicies: policies,
|
||||||
|
|
|
@ -97,7 +97,6 @@ type (
|
||||||
clientCut bool
|
clientCut bool
|
||||||
maxBufferSizeForPut uint64
|
maxBufferSizeForPut uint64
|
||||||
md5Enabled bool
|
md5Enabled bool
|
||||||
aclEnabled bool
|
|
||||||
namespaceHeader string
|
namespaceHeader string
|
||||||
defaultNamespaces []string
|
defaultNamespaces []string
|
||||||
policyDenyByDefault bool
|
policyDenyByDefault bool
|
||||||
|
@ -210,7 +209,6 @@ func newAppSettings(log *Logger, v *viper.Viper) *appSettings {
|
||||||
func (s *appSettings) update(v *viper.Viper, log *zap.Logger) {
|
func (s *appSettings) update(v *viper.Viper, log *zap.Logger) {
|
||||||
s.updateNamespacesSettings(v, log)
|
s.updateNamespacesSettings(v, log)
|
||||||
s.useDefaultXMLNamespace(v.GetBool(cfgKludgeUseDefaultXMLNS))
|
s.useDefaultXMLNamespace(v.GetBool(cfgKludgeUseDefaultXMLNS))
|
||||||
s.setACLEnabled(v.GetBool(cfgKludgeACLEnabled))
|
|
||||||
s.setBypassContentEncodingInChunks(v.GetBool(cfgKludgeBypassContentEncodingCheckInChunks))
|
s.setBypassContentEncodingInChunks(v.GetBool(cfgKludgeBypassContentEncodingCheckInChunks))
|
||||||
s.setClientCut(v.GetBool(cfgClientCut))
|
s.setClientCut(v.GetBool(cfgClientCut))
|
||||||
s.setBufferMaxSizeForPut(v.GetUint64(cfgBufferMaxSizeForPut))
|
s.setBufferMaxSizeForPut(v.GetUint64(cfgBufferMaxSizeForPut))
|
||||||
|
@ -347,18 +345,6 @@ func (s *appSettings) setMD5Enabled(md5Enabled bool) {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) setACLEnabled(enableACL bool) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.aclEnabled = enableACL
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) ACLEnabled() bool {
|
|
||||||
s.mu.RLock()
|
|
||||||
defer s.mu.RUnlock()
|
|
||||||
return s.aclEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) NamespaceHeader() string {
|
func (s *appSettings) NamespaceHeader() string {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
|
@ -160,8 +160,6 @@ const ( // Settings.
|
||||||
cfgKludgeUseDefaultXMLNS = "kludge.use_default_xmlns"
|
cfgKludgeUseDefaultXMLNS = "kludge.use_default_xmlns"
|
||||||
cfgKludgeBypassContentEncodingCheckInChunks = "kludge.bypass_content_encoding_check_in_chunks"
|
cfgKludgeBypassContentEncodingCheckInChunks = "kludge.bypass_content_encoding_check_in_chunks"
|
||||||
cfgKludgeDefaultNamespaces = "kludge.default_namespaces"
|
cfgKludgeDefaultNamespaces = "kludge.default_namespaces"
|
||||||
cfgKludgeACLEnabled = "kludge.acl_enabled"
|
|
||||||
|
|
||||||
// Web.
|
// Web.
|
||||||
cfgWebReadTimeout = "web.read_timeout"
|
cfgWebReadTimeout = "web.read_timeout"
|
||||||
cfgWebReadHeaderTimeout = "web.read_header_timeout"
|
cfgWebReadHeaderTimeout = "web.read_header_timeout"
|
||||||
|
@ -731,7 +729,6 @@ func newSettings() *viper.Viper {
|
||||||
v.SetDefault(cfgKludgeUseDefaultXMLNS, false)
|
v.SetDefault(cfgKludgeUseDefaultXMLNS, false)
|
||||||
v.SetDefault(cfgKludgeBypassContentEncodingCheckInChunks, false)
|
v.SetDefault(cfgKludgeBypassContentEncodingCheckInChunks, false)
|
||||||
v.SetDefault(cfgKludgeDefaultNamespaces, defaultDefaultNamespaces)
|
v.SetDefault(cfgKludgeDefaultNamespaces, defaultDefaultNamespaces)
|
||||||
v.SetDefault(cfgKludgeACLEnabled, false)
|
|
||||||
|
|
||||||
// web
|
// web
|
||||||
v.SetDefault(cfgWebReadHeaderTimeout, defaultReadHeaderTimeout)
|
v.SetDefault(cfgWebReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||||
|
|
|
@ -154,8 +154,6 @@ S3_GW_KLUDGE_USE_DEFAULT_XMLNS=false
|
||||||
S3_GW_KLUDGE_BYPASS_CONTENT_ENCODING_CHECK_IN_CHUNKS=false
|
S3_GW_KLUDGE_BYPASS_CONTENT_ENCODING_CHECK_IN_CHUNKS=false
|
||||||
# Namespaces that should be handled as default
|
# Namespaces that should be handled as default
|
||||||
S3_GW_KLUDGE_DEFAULT_NAMESPACES="" "root"
|
S3_GW_KLUDGE_DEFAULT_NAMESPACES="" "root"
|
||||||
# Enable bucket/object ACL support for newly created buckets.
|
|
||||||
S3_GW_KLUDGE_ACL_ENABLED=false
|
|
||||||
|
|
||||||
S3_GW_TRACING_ENABLED=false
|
S3_GW_TRACING_ENABLED=false
|
||||||
S3_GW_TRACING_ENDPOINT="localhost:4318"
|
S3_GW_TRACING_ENDPOINT="localhost:4318"
|
||||||
|
|
|
@ -182,8 +182,6 @@ kludge:
|
||||||
bypass_content_encoding_check_in_chunks: false
|
bypass_content_encoding_check_in_chunks: false
|
||||||
# Namespaces that should be handled as default
|
# Namespaces that should be handled as default
|
||||||
default_namespaces: [ "", "root" ]
|
default_namespaces: [ "", "root" ]
|
||||||
# Enable bucket/object ACL support for newly created buckets.
|
|
||||||
acl_enabled: false
|
|
||||||
|
|
||||||
runtime:
|
runtime:
|
||||||
soft_memory_limit: 1gb
|
soft_memory_limit: 1gb
|
||||||
|
|
|
@ -54,11 +54,6 @@ func (g *GateData) SessionTokenForDelete() *session.Container {
|
||||||
return g.containerSessionToken(session.VerbContainerDelete)
|
return g.containerSessionToken(session.VerbContainerDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionTokenForSetEACL returns the first suitable container session context for SetEACL operation.
|
|
||||||
func (g *GateData) SessionTokenForSetEACL() *session.Container {
|
|
||||||
return g.containerSessionToken(session.VerbContainerSetEACL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionToken returns the first container session context.
|
// SessionToken returns the first container session context.
|
||||||
func (g *GateData) SessionToken() *session.Container {
|
func (g *GateData) SessionToken() *session.Container {
|
||||||
if len(g.SessionTokens) != 0 {
|
if len(g.SessionTokens) != 0 {
|
||||||
|
@ -78,7 +73,7 @@ func (g *GateData) containerSessionToken(verb session.ContainerVerb) *session.Co
|
||||||
|
|
||||||
func isAppropriateContainerContext(tok *session.Container, verb session.ContainerVerb) bool {
|
func isAppropriateContainerContext(tok *session.Container, verb session.ContainerVerb) bool {
|
||||||
switch verb {
|
switch verb {
|
||||||
case session.VerbContainerSetEACL, session.VerbContainerDelete, session.VerbContainerPut:
|
case session.VerbContainerDelete, session.VerbContainerPut:
|
||||||
return tok.AssertVerb(verb)
|
return tok.AssertVerb(verb)
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -205,18 +205,6 @@ func TestGateDataSessionToken(t *testing.T) {
|
||||||
require.Equal(t, sessionTknDelete, sessionTkn)
|
require.Equal(t, sessionTknDelete, sessionTkn)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("session token for set eACL", func(t *testing.T) {
|
|
||||||
gate.SessionTokens = []*session.Container{}
|
|
||||||
sessionTkn := gate.SessionTokenForSetEACL()
|
|
||||||
require.Nil(t, sessionTkn)
|
|
||||||
|
|
||||||
sessionTknSetEACL := new(session.Container)
|
|
||||||
sessionTknSetEACL.ForVerb(session.VerbContainerSetEACL)
|
|
||||||
gate.SessionTokens = []*session.Container{sessionTknSetEACL}
|
|
||||||
sessionTkn = gate.SessionTokenForSetEACL()
|
|
||||||
require.Equal(t, sessionTknSetEACL, sessionTkn)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("session token", func(t *testing.T) {
|
t.Run("session token", func(t *testing.T) {
|
||||||
gate.SessionTokens = []*session.Container{}
|
gate.SessionTokens = []*session.Container{}
|
||||||
sessionTkn := gate.SessionToken()
|
sessionTkn := gate.SessionToken()
|
||||||
|
|
195
docs/authmate.md
195
docs/authmate.md
|
@ -20,10 +20,10 @@ potentially).
|
||||||
|
|
||||||
1. [Generation of wallet](#generation-of-wallet)
|
1. [Generation of wallet](#generation-of-wallet)
|
||||||
2. [Issuance of a secret](#issuance-of-a-secret)
|
2. [Issuance of a secret](#issuance-of-a-secret)
|
||||||
1. [CLI parameters](#cli-parameters)
|
1. [CLI parameters](#cli-parameters)
|
||||||
2. [Bearer tokens](#bearer-tokens)
|
2. [Bearer tokens](#bearer-tokens)
|
||||||
3. [Session tokens](#session-tokens)
|
3. [Session tokens](#session-tokens)
|
||||||
4. [Containers policy](#containers-policy)
|
4. [Containers policy](#containers-policy)
|
||||||
3. [Obtainment of a secret](#obtaining-credential-secrets)
|
3. [Obtainment of a secret](#obtaining-credential-secrets)
|
||||||
4. [Generate presigned url](#generate-presigned-url)
|
4. [Generate presigned url](#generate-presigned-url)
|
||||||
5. [Update secrets](#update-secret)
|
5. [Update secrets](#update-secret)
|
||||||
|
@ -75,6 +75,7 @@ wallet is successfully created, the file location is wallet.json
|
||||||
```
|
```
|
||||||
|
|
||||||
To get the public key from the wallet:
|
To get the public key from the wallet:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ ./bin/neo-go wallet dump-keys -w wallet.json
|
$ ./bin/neo-go wallet dump-keys -w wallet.json
|
||||||
|
|
||||||
|
@ -90,22 +91,25 @@ put them as an object into a container on the FrostFS network.
|
||||||
### CLI parameters
|
### CLI parameters
|
||||||
|
|
||||||
**Required parameters:**
|
**Required parameters:**
|
||||||
|
|
||||||
* `--wallet` is a path to a wallet `.json` file. You can provide a passphrase to decrypt
|
* `--wallet` is a path to a wallet `.json` file. You can provide a passphrase to decrypt
|
||||||
a wallet via environment variable `AUTHMATE_WALLET_PASSPHRASE`, or you will be asked to enter a passphrase
|
a wallet via environment variable `AUTHMATE_WALLET_PASSPHRASE`, or you will be asked to enter a passphrase
|
||||||
interactively. You can also specify an account address to use from a wallet using the `--address` parameter.
|
interactively. You can also specify an account address to use from a wallet using the `--address` parameter.
|
||||||
* `--peer` is an address of a FrostFS peer to connect to
|
* `--peer` is an address of a FrostFS peer to connect to
|
||||||
* `--gate-public-key` is a public `secp256r1` 33-byte short key of a gate (use flags repeatedly for multiple gates). The tokens are encrypted
|
* `--gate-public-key` is a public `secp256r1` 33-byte short key of a gate (use flags repeatedly for multiple gates). The
|
||||||
by a set of gateway keys, so you need to pass them as well.
|
tokens are encrypted by a set of gateway keys, so you need to pass them as well.
|
||||||
|
|
||||||
You can issue a secret using the parameters above only. The tool will
|
You can issue a secret using the parameters above only. The tool will
|
||||||
1. create a new container
|
|
||||||
1. without a friendly name
|
1. create a new container
|
||||||
2. with ACL `0x3c8c8cce` -- all operations are forbidden for `OTHERS` and `BEARER` user groups, except for `GET`
|
1. without a friendly name
|
||||||
3. with policy `REP 2 IN X CBF 3 SELECT 2 FROM * AS X`
|
2. with ACL `0x3c8c8cce` -- all operations are forbidden for `OTHERS` and `BEARER` user groups, except for `GET`
|
||||||
2. put bearer and session tokens with default rules (details in [Bearer tokens](#Bearer tokens) and
|
3. with policy `REP 2 IN X CBF 3 SELECT 2 FROM * AS X`
|
||||||
[Session tokens](#Session tokens))
|
2. put bearer and session tokens with default rules (details in [Bearer tokens](#bearer-tokens) and
|
||||||
|
[Session tokens](#session-tokens))
|
||||||
|
|
||||||
E.g.:
|
E.g.:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ frostfs-s3-authmate issue-secret --wallet wallet.json \
|
$ frostfs-s3-authmate issue-secret --wallet wallet.json \
|
||||||
--peer 192.168.130.71:8080 \
|
--peer 192.168.130.71:8080 \
|
||||||
|
@ -128,134 +132,78 @@ $ frostfs-s3-authmate issue-secret --wallet wallet.json \
|
||||||
`initial_access_key_id` contains the first credentials in the chain of credentials versions
|
`initial_access_key_id` contains the first credentials in the chain of credentials versions
|
||||||
(can be useful when you update your credentials).
|
(can be useful when you update your credentials).
|
||||||
|
|
||||||
`access_key_id` consists of Base58 encoded containerID(cid) and objectID(oid) stored on the FrostFS network and containing
|
`access_key_id` consists of Base58 encoded containerID(cid) and objectID(oid) stored on the FrostFS network and
|
||||||
|
containing
|
||||||
the secret. Format of `access_key_id`: `%cid0%oid`, where 0(zero) is a delimiter.
|
the secret. Format of `access_key_id`: `%cid0%oid`, where 0(zero) is a delimiter.
|
||||||
|
|
||||||
**Optional parameters:**
|
**Optional parameters:**
|
||||||
|
|
||||||
* `--container-id` - you can put the tokens into an existing container, but this way is ***not recommended***.
|
* `--container-id` - you can put the tokens into an existing container, but this way is ***not recommended***.
|
||||||
* `--container-friendly-name` -- name of a container with tokens, by default container will not have a friendly name
|
* `--container-friendly-name` -- name of a container with tokens, by default container will not have a friendly name
|
||||||
* `--container-placement-policy` - placement policy of auth container to put the secret into. Default value is
|
* `--container-placement-policy` - placement policy of auth container to put the secret into. Default value is
|
||||||
`REP 2 IN X CBF 3 SELECT 2 FROM * AS X`
|
`REP 2 IN X CBF 3 SELECT 2 FROM * AS X`
|
||||||
* `--lifetime`-- lifetime of tokens. For example 50h30m (note: max time unit is an hour so to set a day you should use
|
* `--lifetime`-- lifetime of tokens. For example 50h30m (note: max time unit is an hour so to set a day you should use
|
||||||
24h). Default value is `720h` (30 days). It will be ceil rounded to the nearest amount of epoch
|
24h). Default value is `720h` (30 days). It will be ceil rounded to the nearest amount of epoch
|
||||||
* `--aws-cli-credentials` - path to the aws cli credentials file, where authmate will write `access_key_id` and
|
* `--aws-cli-credentials` - path to the aws cli credentials file, where authmate will write `access_key_id` and
|
||||||
`secret_access_key` to
|
`secret_access_key` to
|
||||||
* `--access-key-id` -- credentials that you want to update (e.g. to add more gates that can use your creds)
|
* `--access-key-id` -- credentials that you want to update (e.g. to add more gates that can use your creds)
|
||||||
without changing values of `aws_access_key_id` and `aws_secret_access_key`. If you want to update credential you MUST
|
without changing values of `aws_access_key_id` and `aws_secret_access_key`. If you want to update credential you MUST
|
||||||
provide also secret key using `AUTHMATE_SECRET_ACCESS_KEY` env variable.
|
provide also secret key using `AUTHMATE_SECRET_ACCESS_KEY` env variable.
|
||||||
* `--frostfsid` -- FrostfsID contract hash (LE) or name in NNS to register public key in contract
|
* `--frostfsid` -- FrostfsID contract hash (LE) or name in NNS to register public key in contract
|
||||||
(`--rpc-endpoint` flag also must be provided).
|
(`--rpc-endpoint` flag also must be provided).
|
||||||
* `--rpc-endpoint` -- NEO node RPC address.
|
* `--rpc-endpoint` -- NEO node RPC address.
|
||||||
|
|
||||||
### Bearer tokens
|
### Bearer tokens
|
||||||
|
|
||||||
Creation of bearer tokens is mandatory.
|
Creation of bearer tokens is mandatory.
|
||||||
|
|
||||||
By default, bearer token will be created with `impersonate` flag and won't have eACL table. It means that gate which will use such token
|
Bearer token will be created with `impersonate` flag. It means that gate (which will use such token to interact with
|
||||||
to interact with node can have access to your private containers or to containers in which eACL grants access to you
|
node) can have access to your private containers or to containers in which eACL grants access to you by public key.
|
||||||
by public key.
|
|
||||||
|
|
||||||
Rules for a bearer token can be set via parameter `--bearer-rules` (json-string and file path allowed).
|
|
||||||
But you must provide `--disable-impersonate` flag:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ frostfs-s3-authmate issue-secret --wallet wallet.json \
|
|
||||||
--peer 192.168.130.71:8080 \
|
|
||||||
--gate-public-key 0313b1ac3a8076e155a7e797b24f0b650cccad5941ea59d7cfd51a024a8b2a06bf \
|
|
||||||
--bearer-rules bearer-rules.json \
|
|
||||||
--disable-impersonate
|
|
||||||
```
|
|
||||||
where content of `bearer-rules.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{"operation": "PUT", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "GET", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "HEAD", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "DELETE", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "SEARCH", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "GETRANGE", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "GETRANGEHASH", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** such rules allow all operations for all users (the same behavior when records are empty).
|
|
||||||
To restrict access you MUST provide records with `DENY` action. That's why we recommend always place such records
|
|
||||||
at the end of records (see default rules below) to prevent undesirable access violation.
|
|
||||||
Since the rules are applied from top to bottom, they do not override what was previously allowed.
|
|
||||||
|
|
||||||
If bearer rules are not set, a token will be auto-generated with a value:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": {
|
|
||||||
"major": 2,
|
|
||||||
"minor": 11
|
|
||||||
},
|
|
||||||
"containerID": {
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
"records": [
|
|
||||||
{"operation": "GET", "action": "ALLOW", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
|
|
||||||
{"operation": "GET", "action": "DENY", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "HEAD", "action": "DENY", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "PUT", "action": "DENY", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "DELETE", "action": "DENY", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "SEARCH", "action": "DENY", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "GETRANGE", "action": "DENY", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]},
|
|
||||||
{"operation": "GETRANGEHASH", "action": "DENY", "filters": [], "targets": [{"role": "OTHERS", "keys": []}]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Session tokens
|
### Session tokens
|
||||||
|
|
||||||
With a session token, there are 3 options:
|
With a session token, there are 3 options:
|
||||||
|
|
||||||
1. append `--session-tokens` parameter with your custom rules in json format (as a string or file path). E.g.:
|
1. append `--session-tokens` parameter with your custom rules in json format (as a string or file path). E.g.:
|
||||||
```shell
|
```shell
|
||||||
$ frostfs-s3-authmate issue-secret --wallet wallet.json \
|
$ frostfs-s3-authmate issue-secret --wallet wallet.json \
|
||||||
--peer 192.168.130.71:8080 \
|
--peer 192.168.130.71:8080 \
|
||||||
--gate-public-key 0313b1ac3a8076e155a7e797b24f0b650cccad5941ea59d7cfd51a024a8b2a06bf \
|
--gate-public-key 0313b1ac3a8076e155a7e797b24f0b650cccad5941ea59d7cfd51a024a8b2a06bf \
|
||||||
--session-tokens session.json
|
--session-tokens session.json
|
||||||
```
|
```
|
||||||
where content of `session.json`:
|
where content of `session.json`:
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"verb": "PUT",
|
"verb": "PUT",
|
||||||
"containerID": null
|
"containerID": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"verb": "DELETE",
|
"verb": "DELETE",
|
||||||
"containerID": null
|
"containerID": null
|
||||||
},
|
}
|
||||||
{
|
]
|
||||||
"verb": "SETEACL",
|
```
|
||||||
"containerID": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Available `verb` values: `PUT`, `DELETE`, `SETEACL`.
|
Available `verb` values: `PUT`, `DELETE`.
|
||||||
|
|
||||||
If `containerID` is `null` or omitted, then session token rule will be applied
|
If `containerID` is `null` or omitted, then session token rule will be applied
|
||||||
to all containers. Otherwise, specify `containerID` value in human-redabale
|
to all containers. Otherwise, specify `containerID` value in human-readable
|
||||||
format (base58 encoded string).
|
format (base58 encoded string).
|
||||||
|
|
||||||
> **_NB!_** To create buckets in FrostFS it's necessary to have session tokens with `PUT` and `SETEACL` permissions, that's why
|
> **_NB!_** To create buckets in FrostFS it's necessary to have session tokens with `PUT` permissions.
|
||||||
the authmate creates a `SETEACL` session token automatically in case when a user specified the token rule with `PUT` and
|
|
||||||
forgot about the rule with `SETEACL`.
|
|
||||||
|
|
||||||
2. append `--session-tokens` parameter with the value `none` -- no session token will be created
|
2. append `--session-tokens` parameter with the value `none` -- no session token will be created
|
||||||
3. skip the parameter, and `authmate` will create session tokens with default rules (the same as in `session.json`
|
3. skip the parameter, and `authmate` will create session tokens with default rules (the same as in `session.json`
|
||||||
in example above)
|
in example above)
|
||||||
|
|
||||||
### Containers policy
|
### Containers policy
|
||||||
|
|
||||||
Rules for mapping of `LocationConstraint` ([aws spec](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html#API_CreateBucket_RequestBody))
|
Rules for mapping
|
||||||
|
of `LocationConstraint` ([aws spec](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html#API_CreateBucket_RequestBody))
|
||||||
to `PlacementPolicy`
|
to `PlacementPolicy`
|
||||||
can be set via parameter `--container-policy` (json-string and file path allowed):
|
can be set via parameter `--container-policy` (json-string and file path allowed):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"rep-3": "REP 3",
|
"rep-3": "REP 3",
|
||||||
|
@ -302,7 +250,6 @@ Enter password for gate-wallet.json >
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Generate presigned URL
|
## Generate presigned URL
|
||||||
|
|
||||||
You can generate [presigned url](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html)
|
You can generate [presigned url](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html)
|
||||||
|
@ -332,6 +279,7 @@ $ frostfs-s3-authmate generate-presigned-url --endpoint http://localhost:8084 \
|
||||||
```
|
```
|
||||||
|
|
||||||
### AWS CLI
|
### AWS CLI
|
||||||
|
|
||||||
You can also can get the presigned URL (only for GET) using aws cli v2:
|
You can also can get the presigned URL (only for GET) using aws cli v2:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -341,19 +289,22 @@ http://localhost:8084/pregigned/obj?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Crede
|
||||||
```
|
```
|
||||||
|
|
||||||
## Update secret
|
## Update secret
|
||||||
|
|
||||||
You can extend list of s3 gates that can accept already issued credentials.
|
You can extend list of s3 gates that can accept already issued credentials.
|
||||||
To do this use `frostfs-s3-authmate update-secret` command:
|
To do this use `frostfs-s3-authmate update-secret` command:
|
||||||
|
|
||||||
**Required parameters:**
|
**Required parameters:**
|
||||||
|
|
||||||
* `--wallet` is a path to a user wallet `.json` file. You can provide a passphrase to decrypt
|
* `--wallet` is a path to a user wallet `.json` file. You can provide a passphrase to decrypt
|
||||||
a wallet via environment variable `AUTHMATE_WALLET_PASSPHRASE`, or you will be asked to enter a passphrase
|
a wallet via environment variable `AUTHMATE_WALLET_PASSPHRASE`, or you will be asked to enter a passphrase
|
||||||
interactively. You can also specify an account address to use from a wallet using the `--address` parameter.
|
interactively. You can also specify an account address to use from a wallet using the `--address` parameter.
|
||||||
* `--gate-wallet` is a path to a gate wallet `.json` file (need to decrypt current access box version). You can provide a passphrase to decrypt
|
* `--gate-wallet` is a path to a gate wallet `.json` file (need to decrypt current access box version). You can provide
|
||||||
|
a passphrase to decrypt
|
||||||
a wallet via environment variable `AUTHMATE_WALLET_GATE_PASSPHRASE`, or you will be asked to enter a passphrase
|
a wallet via environment variable `AUTHMATE_WALLET_GATE_PASSPHRASE`, or you will be asked to enter a passphrase
|
||||||
interactively. You can also specify an account address to use from a wallet using the `--gate-address` parameter.
|
interactively. You can also specify an account address to use from a wallet using the `--gate-address` parameter.
|
||||||
* `--peer` is an address of a FrostFS peer to connect to
|
* `--peer` is an address of a FrostFS peer to connect to
|
||||||
* `--gate-public-key` is a public `secp256r1` 33-byte short key of a gate (use flags repeatedly for multiple gates).
|
* `--gate-public-key` is a public `secp256r1` 33-byte short key of a gate (use flags repeatedly for multiple gates).
|
||||||
* `--access-key-id` is a credential id to update.
|
* `--access-key-id` is a credential id to update.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ frostfs-s3-authmate update-secret --wallet wallet.json --gate-wallet s3-wallet.json \
|
$ frostfs-s3-authmate update-secret --wallet wallet.json --gate-wallet s3-wallet.json \
|
||||||
|
@ -380,9 +331,9 @@ Enter password for s3-wallet.json >
|
||||||
|
|
||||||
There are several non-zero exit codes added at the moment.
|
There are several non-zero exit codes added at the moment.
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
|-------|--------------------------------------------------------------------------------------------|
|
|------|--------------------------------------------------------------------------------------------|
|
||||||
| 1 | Any unknown errors, or errors generated by the parser of command line parameters. |
|
| 1 | Any unknown errors, or errors generated by the parser of command line parameters. |
|
||||||
| 2 | Preparation errors: malformed configuration, issues with input data parsing. |
|
| 2 | Preparation errors: malformed configuration, issues with input data parsing. |
|
||||||
| 3 | FrostFS errors: connectivity problems, misconfiguration. |
|
| 3 | FrostFS errors: connectivity problems, misconfiguration. |
|
||||||
| 4 | Business logic errors: `authmate` could not execute its task because of some restrictions. |
|
| 4 | Business logic errors: `authmate` could not execute its task because of some restrictions. |
|
||||||
|
|
|
@ -558,7 +558,6 @@ kludge:
|
||||||
use_default_xmlns: false
|
use_default_xmlns: false
|
||||||
bypass_content_encoding_check_in_chunks: false
|
bypass_content_encoding_check_in_chunks: false
|
||||||
default_namespaces: [ "", "root" ]
|
default_namespaces: [ "", "root" ]
|
||||||
acl_enabled: false
|
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|
@ -566,7 +565,6 @@ kludge:
|
||||||
| `use_default_xmlns` | `bool` | yes | `false` | Enable using default xml namespace `http://s3.amazonaws.com/doc/2006-03-01/` when parse xml bodies. |
|
| `use_default_xmlns` | `bool` | yes | `false` | Enable using default xml namespace `http://s3.amazonaws.com/doc/2006-03-01/` when parse xml bodies. |
|
||||||
| `bypass_content_encoding_check_in_chunks` | `bool` | yes | `false` | Use this flag to be able to use [chunked upload approach](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html) without having `aws-chunked` value in `Content-Encoding` header. |
|
| `bypass_content_encoding_check_in_chunks` | `bool` | yes | `false` | Use this flag to be able to use [chunked upload approach](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html) without having `aws-chunked` value in `Content-Encoding` header. |
|
||||||
| `default_namespaces` | `[]string` | yes | `["","root"]` | Namespaces that should be handled as default. |
|
| `default_namespaces` | `[]string` | yes | `["","root"]` | Namespaces that should be handled as default. |
|
||||||
| `acl_enabled` | `bool` | yes | `false` | Enable bucket/object ACL support for newly created buckets. |
|
|
||||||
|
|
||||||
# `runtime` section
|
# `runtime` section
|
||||||
Contains runtime parameters.
|
Contains runtime parameters.
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||||
|
@ -162,29 +161,6 @@ func (x *FrostFS) UserContainers(ctx context.Context, layerPrm layer.PrmUserCont
|
||||||
return r, handleObjectError("list user containers via connection pool", err)
|
return r, handleObjectError("list user containers via connection pool", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContainerEACL implements frostfs.FrostFS interface method.
|
|
||||||
func (x *FrostFS) SetContainerEACL(ctx context.Context, table eacl.Table, sessionToken *session.Container) error {
|
|
||||||
prm := pool.PrmContainerSetEACL{Table: table, Session: sessionToken, WaitParams: &x.await}
|
|
||||||
|
|
||||||
err := x.pool.SetEACL(ctx, prm)
|
|
||||||
return handleObjectError("save eACL via connection pool", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerEACL implements frostfs.FrostFS interface method.
|
|
||||||
func (x *FrostFS) ContainerEACL(ctx context.Context, layerPrm layer.PrmContainerEACL) (*eacl.Table, error) {
|
|
||||||
prm := pool.PrmContainerEACL{
|
|
||||||
ContainerID: layerPrm.ContainerID,
|
|
||||||
Session: layerPrm.SessionToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := x.pool.GetEACL(ctx, prm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, handleObjectError("read eACL via connection pool", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteContainer implements frostfs.FrostFS interface method.
|
// DeleteContainer implements frostfs.FrostFS interface method.
|
||||||
func (x *FrostFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.Container) error {
|
func (x *FrostFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.Container) error {
|
||||||
prm := pool.PrmContainerDelete{ContainerID: id, Session: token, WaitParams: &x.await}
|
prm := pool.PrmContainerDelete{ContainerID: id, Session: token, WaitParams: &x.await}
|
||||||
|
|
Loading…
Add table
Reference in a new issue