From 77f8bdac58a1ed60d2cb41324240f96102758f1e Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 28 May 2024 11:47:26 +0300 Subject: [PATCH 1/3] [#372] Drop kludge.acl_enabled flag Now only APE container can be created using s3-gw Signed-off-by: Denis Kirillov --- api/handler/acl_test.go | 53 ------------------------- api/handler/api.go | 1 - api/handler/handlers_test.go | 5 --- api/handler/put.go | 77 ------------------------------------ api/handler/put_test.go | 15 ------- api/middleware/policy.go | 3 +- api/router_mock_test.go | 7 +--- api/router_test.go | 67 +++++++++---------------------- cmd/s3-gw/app.go | 14 ------- cmd/s3-gw/app_settings.go | 3 -- config/config.env | 2 - config/config.yaml | 2 - docs/configuration.md | 2 - 13 files changed, 20 insertions(+), 231 deletions(-) diff --git a/api/handler/acl_test.go b/api/handler/acl_test.go index 490d321..1081e8f 100644 --- a/api/handler/acl_test.go +++ b/api/handler/acl_test.go @@ -1301,21 +1301,6 @@ func TestBucketAclToAst(t *testing.T) { require.Equal(t, expectedAst, actualAst) } -func TestPutBucketACL(t *testing.T) { - tc := prepareHandlerContext(t) - tc.config.aclEnabled = true - bktName := "bucket-for-acl" - - info := createBucket(tc, bktName) - - header := map[string]string{api.AmzACL: "public-read"} - putBucketACL(tc, bktName, info.Box, header) - - header = map[string]string{api.AmzACL: "private"} - putBucketACL(tc, bktName, info.Box, header) - checkLastRecords(t, tc, info.BktInfo, eacl.ActionDeny) -} - func TestPutBucketAPE(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-for-acl-ape" @@ -1361,27 +1346,6 @@ func TestCreateObjectACLErrorAPE(t *testing.T) { createMultipartUpload(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate}) } -func TestPutObjectACLBackwardCompatibility(t *testing.T) { - hc := prepareHandlerContext(t) - hc.config.aclEnabled = true - bktName, objName := "bucket-for-acl-ape", "object" - - info := createBucket(hc, bktName) - - putObjectWithHeadersBase(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate}, info.Box, nil) - putObjectWithHeadersBase(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPublic}, info.Box, nil) - - aclRes := getObjectACL(hc, bktName, objName) - require.Len(t, aclRes.AccessControlList, 2) - require.Equal(t, hex.EncodeToString(info.Key.PublicKey().Bytes()), aclRes.AccessControlList[0].Grantee.ID) - require.Equal(t, aclFullControl, aclRes.AccessControlList[0].Permission) - require.Equal(t, allUsersGroup, aclRes.AccessControlList[1].Grantee.URI) - require.Equal(t, aclFullControl, aclRes.AccessControlList[1].Permission) - - aclBody := &AccessControlPolicy{} - putObjectACLBase(hc, bktName, objName, info.Box, nil, aclBody) -} - func TestBucketACLAPE(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-for-acl-ape" @@ -1648,23 +1612,6 @@ func putBucketPolicy(hc *handlerContext, bktName string, bktPolicy engineiam.Pol } } -func checkLastRecords(t *testing.T, tc *handlerContext, bktInfo *data.BucketInfo, action eacl.Action) { - bktACL, err := tc.Layer().GetBucketACL(tc.Context(), bktInfo) - require.NoError(t, err) - - length := len(bktACL.EACL.Records()) - - if length < 7 { - t.Fatalf("length of records is less than 7: '%d'", length) - } - - for _, rec := range bktACL.EACL.Records()[length-7:] { - if rec.Action() != action || rec.Targets()[0].Role() != eacl.RoleOthers { - t.Fatalf("inavid last record: '%s', '%s', '%s',", rec.Action(), rec.Operation(), rec.Targets()[0].Role()) - } - } -} - func createAccessBox(t *testing.T) (*accessbox.Box, *keys.PrivateKey) { key, err := keys.NewPrivateKey() require.NoError(t, err) diff --git a/api/handler/api.go b/api/handler/api.go index 2b35527..559977a 100644 --- a/api/handler/api.go +++ b/api/handler/api.go @@ -38,7 +38,6 @@ type ( IsResolveListAllow() bool BypassContentEncodingInChunks() bool MD5Enabled() bool - ACLEnabled() bool RetryMaxAttempts() int RetryMaxBackoff() time.Duration RetryStrategy() RetryStrategy diff --git a/api/handler/handlers_test.go b/api/handler/handlers_test.go index 5280c0f..254087a 100644 --- a/api/handler/handlers_test.go +++ b/api/handler/handlers_test.go @@ -72,7 +72,6 @@ type configMock struct { defaultCopiesNumbers []uint32 bypassContentEncodingInChunks bool md5Enabled bool - aclEnabled bool } func (c *configMock) DefaultPlacementPolicy(_ string) netmap.PlacementPolicy { @@ -120,10 +119,6 @@ func (c *configMock) MD5Enabled() bool { return c.md5Enabled } -func (c *configMock) ACLEnabled() bool { - return c.aclEnabled -} - func (c *configMock) ResolveNamespaceAlias(ns string) string { return ns } diff --git a/api/handler/put.go b/api/handler/put.go index 2c180f9..050596b 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -814,11 +814,6 @@ func parseCannedACL(header http.Header) (string, error) { } func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { - if h.cfg.ACLEnabled() { - h.createBucketHandlerACL(w, r) - return - } - h.createBucketHandlerPolicy(w, r) } @@ -941,78 +936,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:" var ( diff --git a/api/handler/put_test.go b/api/handler/put_test.go index 4bf7078..152a969 100644 --- a/api/handler/put_test.go +++ b/api/handler/put_test.go @@ -381,21 +381,6 @@ func TestCreateBucket(t *testing.T) { 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) { hc := prepareHandlerContext(t) bktName := "bkt-name" diff --git a/api/middleware/policy.go b/api/middleware/policy.go index e03e36c..2786535 100644 --- a/api/middleware/policy.go +++ b/api/middleware/policy.go @@ -51,7 +51,6 @@ var withoutResourceOps = []string{ type PolicySettings interface { PolicyDenyByDefault() bool - ACLEnabled() bool } type FrostFSIDInformer interface { @@ -150,7 +149,7 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error { return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String())) } - isAPE := !cfg.Settings.ACLEnabled() + isAPE := true if bktInfo != nil { isAPE = bktInfo.APEEnabled } diff --git a/api/router_mock_test.go b/api/router_mock_test.go index 0b72813..9ecd947 100644 --- a/api/router_mock_test.go +++ b/api/router_mock_test.go @@ -72,7 +72,6 @@ func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) { type middlewareSettingsMock struct { denyByDefault bool - aclEnabled bool sourceIPHeader string } @@ -92,10 +91,6 @@ func (r *middlewareSettingsMock) PolicyDenyByDefault() bool { return r.denyByDefault } -func (r *middlewareSettingsMock) ACLEnabled() bool { - return r.aclEnabled -} - type frostFSIDMock struct { tags map[string]string validateError bool @@ -430,7 +425,7 @@ func (h *handlerMock) CreateBucketHandler(w http.ResponseWriter, r *http.Request h.buckets[reqInfo.Namespace+reqInfo.BucketName] = &data.BucketInfo{ Name: reqInfo.BucketName, - APEEnabled: !h.cfg.ACLEnabled(), + APEEnabled: true, } res := &handlerResult{ diff --git a/api/router_test.go b/api/router_test.go index bbe5146..b341012 100644 --- a/api/router_test.go +++ b/api/router_test.go @@ -17,7 +17,9 @@ import ( apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics" + cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" + usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" @@ -37,6 +39,7 @@ type routerMock struct { cfg Config middlewareSettings *middlewareSettingsMock policyChecker engine.LocalOverrideEngine + handler *handlerMock } func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -64,12 +67,14 @@ func prepareRouter(t *testing.T, opts ...option) *routerMock { Enabled: true, } + handlerTestMock := &handlerMock{t: t, cfg: middlewareSettings, buckets: map[string]*data.BucketInfo{}} + cfg := Config{ Throttle: middleware.ThrottleOpts{ Limit: 10, BacklogTimeout: 30 * time.Second, }, - Handler: &handlerMock{t: t, cfg: middlewareSettings, buckets: map[string]*data.BucketInfo{}}, + Handler: handlerTestMock, Center: ¢erMock{t: t}, Log: logger, Metrics: metrics.NewAppMetrics(metricsConfig), @@ -91,6 +96,7 @@ func prepareRouter(t *testing.T, opts ...option) *routerMock { cfg: cfg, middlewareSettings: middlewareSettings, policyChecker: policyChecker, + handler: handlerTestMock, } } @@ -308,7 +314,6 @@ func TestACLAPE(t *testing.T) { createOldBucket(router, bktNameOld) createNewBucket(router, bktNameNew) - router.middlewareSettings.aclEnabled = false router.middlewareSettings.denyByDefault = true // Allow because of using old bucket @@ -334,7 +339,6 @@ func TestACLAPE(t *testing.T) { createOldBucket(router, bktNameOld) createNewBucket(router, bktNameNew) - router.middlewareSettings.aclEnabled = false router.middlewareSettings.denyByDefault = false // Allow because of using old bucket @@ -351,48 +355,6 @@ func TestACLAPE(t *testing.T) { 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) { @@ -726,10 +688,17 @@ func createNewBucket(router *routerMock, bktName string) { } func createSpecificBucket(router *routerMock, bktName string, old bool) { - aclEnabled := router.middlewareSettings.ACLEnabled() - router.middlewareSettings.aclEnabled = old - createBucket(router, "", bktName) - router.middlewareSettings.aclEnabled = aclEnabled + router.handler.buckets[bktName] = &data.BucketInfo{ + Name: bktName, + Zone: "container", + CID: cidtest.ID(), + Owner: usertest.ID(), + Created: time.Now(), + LocationConstraint: "default", + ObjectLockEnabled: false, + HomomorphicHashDisabled: false, + APEEnabled: !old, + } } func createBucket(router *routerMock, namespace, bktName string) { diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index 3ccc425..f46c39d 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -97,7 +97,6 @@ type ( clientCut bool maxBufferSizeForPut uint64 md5Enabled bool - aclEnabled bool namespaceHeader string defaultNamespaces []string policyDenyByDefault bool @@ -210,7 +209,6 @@ func newAppSettings(log *Logger, v *viper.Viper) *appSettings { func (s *appSettings) update(v *viper.Viper, log *zap.Logger) { s.updateNamespacesSettings(v, log) s.useDefaultXMLNamespace(v.GetBool(cfgKludgeUseDefaultXMLNS)) - s.setACLEnabled(v.GetBool(cfgKludgeACLEnabled)) s.setBypassContentEncodingInChunks(v.GetBool(cfgKludgeBypassContentEncodingCheckInChunks)) s.setClientCut(v.GetBool(cfgClientCut)) s.setBufferMaxSizeForPut(v.GetUint64(cfgBufferMaxSizeForPut)) @@ -347,18 +345,6 @@ func (s *appSettings) setMD5Enabled(md5Enabled bool) { 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 { s.mu.RLock() defer s.mu.RUnlock() diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index ca20cc7..18ad39e 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -160,8 +160,6 @@ const ( // Settings. cfgKludgeUseDefaultXMLNS = "kludge.use_default_xmlns" cfgKludgeBypassContentEncodingCheckInChunks = "kludge.bypass_content_encoding_check_in_chunks" cfgKludgeDefaultNamespaces = "kludge.default_namespaces" - cfgKludgeACLEnabled = "kludge.acl_enabled" - // Web. cfgWebReadTimeout = "web.read_timeout" cfgWebReadHeaderTimeout = "web.read_header_timeout" @@ -731,7 +729,6 @@ func newSettings() *viper.Viper { v.SetDefault(cfgKludgeUseDefaultXMLNS, false) v.SetDefault(cfgKludgeBypassContentEncodingCheckInChunks, false) v.SetDefault(cfgKludgeDefaultNamespaces, defaultDefaultNamespaces) - v.SetDefault(cfgKludgeACLEnabled, false) // web v.SetDefault(cfgWebReadHeaderTimeout, defaultReadHeaderTimeout) diff --git a/config/config.env b/config/config.env index a16ecff..69e12f5 100644 --- a/config/config.env +++ b/config/config.env @@ -154,8 +154,6 @@ S3_GW_KLUDGE_USE_DEFAULT_XMLNS=false S3_GW_KLUDGE_BYPASS_CONTENT_ENCODING_CHECK_IN_CHUNKS=false # Namespaces that should be handled as default 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_ENDPOINT="localhost:4318" diff --git a/config/config.yaml b/config/config.yaml index d9cc827..4ed6be3 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -182,8 +182,6 @@ kludge: bypass_content_encoding_check_in_chunks: false # Namespaces that should be handled as default default_namespaces: [ "", "root" ] - # Enable bucket/object ACL support for newly created buckets. - acl_enabled: false runtime: soft_memory_limit: 1gb diff --git a/docs/configuration.md b/docs/configuration.md index d25837a..524f91b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -558,7 +558,6 @@ kludge: use_default_xmlns: false bypass_content_encoding_check_in_chunks: false default_namespaces: [ "", "root" ] - acl_enabled: false ``` | 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. | | `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. | -| `acl_enabled` | `bool` | yes | `false` | Enable bucket/object ACL support for newly created buckets. | # `runtime` section Contains runtime parameters. -- 2.40.1 From 9241954496ba972efb5cf11cd747dd8e2a558638 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 28 May 2024 12:10:28 +0300 Subject: [PATCH 2/3] [#372] authmate: Don't create creds with eacl table Allow only impersonate flag. Don't allow SetEACL container session token. Signed-off-by: Denis Kirillov --- authmate/authmate.go | 60 ++------ authmate/session_tokens.go | 17 +-- authmate/session_tokens_test.go | 7 +- cmd/s3-authmate/modules/issue-secret.go | 19 +-- docs/authmate.md | 195 +++++++++--------------- 5 files changed, 89 insertions(+), 209 deletions(-) diff --git a/authmate/authmate.go b/authmate/authmate.go index f35d549..401b26f 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -20,7 +20,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" 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/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" @@ -102,7 +101,6 @@ type ( Container ContainerOptions FrostFSKey *keys.PrivateKey GatesPublicKeys []*keys.PublicKey - EACLRules []byte Impersonate bool SessionTokenRules []byte SkipSessionRules bool @@ -441,47 +439,11 @@ func (a *Agent) ObtainSecret(ctx context.Context, w io.Writer, options *ObtainSe return enc.Encode(or) } -func buildEACLTable(eaclTable []byte) (*eacl.Table, 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) { +func buildBearerToken(key *keys.PrivateKey, impersonate bool, lifetime lifetimeOptions, gateKey *keys.PublicKey) (*bearer.Token, error) { var ownerID user.ID user.IDFromKey(&ownerID, (ecdsa.PublicKey)(*gateKey)) var bearerToken bearer.Token - - if !impersonate { - bearerToken.SetEACLTable(*table) - } - bearerToken.ForUser(ownerID) bearerToken.SetExp(lifetime.Exp) bearerToken.SetIat(lifetime.Iat) @@ -496,10 +458,10 @@ func buildBearerToken(key *keys.PrivateKey, impersonate bool, table *eacl.Table, 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)) for _, gateKey := range gatesKeys { - tkn, err := buildBearerToken(key, impersonate, table, lifetime, gateKey) + tkn, err := buildBearerToken(key, impersonate, lifetime, gateKey) if err != nil { 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) { gates := make([]*accessbox.GateData, len(options.GatesPublicKeys)) - table, err := buildEACLTable(options.EACLRules) - 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) + bearerTokens, err := buildBearerTokens(options.FrostFSKey, options.Impersonate, lifetime, options.GatesPublicKeys) if err != nil { 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) { 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 { return nil, fmt.Errorf("failed to build bearer tokens: %w", err) } diff --git a/authmate/session_tokens.go b/authmate/session_tokens.go index d0066b8..b30722c 100644 --- a/authmate/session_tokens.go +++ b/authmate/session_tokens.go @@ -2,6 +2,7 @@ package authmate import ( "encoding/json" + "errors" "fmt" 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) } - var ( - containsPut = false - containsSetEACL = false - ) for _, d := range sessionCtxs { - if d.verb == session.VerbContainerPut { - containsPut = true - } else if d.verb == session.VerbContainerSetEACL { - containsSetEACL = true + if d.verb == session.VerbContainerSetEACL { + return nil, errors.New("verb container SetEACL isn't supported") } } - if containsPut && !containsSetEACL { - sessionCtxs = append(sessionCtxs, sessionTokenContext{ - verb: session.VerbContainerSetEACL, - }) - } return sessionCtxs, nil } @@ -78,6 +68,5 @@ func buildContext(rules []byte) ([]sessionTokenContext, error) { return []sessionTokenContext{ {verb: session.VerbContainerPut}, {verb: session.VerbContainerDelete}, - {verb: session.VerbContainerSetEACL}, }, nil } diff --git a/authmate/session_tokens_test.go b/authmate/session_tokens_test.go index 7adcc01..222c08c 100644 --- a/authmate/session_tokens_test.go +++ b/authmate/session_tokens_test.go @@ -17,20 +17,15 @@ func TestContainerSessionRules(t *testing.T) { { "verb": "DELETE", "containerID": "6CcWg8LkcbfMUC8pt7wiy5zM1fyS3psNoxgfppcCgig1" - }, - { - "verb": "SETEACL" } ]`) sessionContext, err := buildContext(jsonRules) require.NoError(t, err) - require.Len(t, sessionContext, 3) + require.Len(t, sessionContext, 2) require.Equal(t, sessionContext[0].verb, session.VerbContainerPut) require.Zero(t, sessionContext[0].containerID) require.Equal(t, sessionContext[1].verb, session.VerbContainerDelete) require.NotNil(t, sessionContext[1].containerID) - require.Equal(t, sessionContext[2].verb, session.VerbContainerSetEACL) - require.Zero(t, sessionContext[2].containerID) } diff --git a/cmd/s3-authmate/modules/issue-secret.go b/cmd/s3-authmate/modules/issue-secret.go index b447585..6b26596 100644 --- a/cmd/s3-authmate/modules/issue-secret.go +++ b/cmd/s3-authmate/modules/issue-secret.go @@ -2,7 +2,6 @@ package modules import ( "context" - "errors" "fmt" "os" "time" @@ -29,8 +28,6 @@ const ( walletFlag = "wallet" addressFlag = "address" peerFlag = "peer" - bearerRulesFlag = "bearer-rules" - disableImpersonateFlag = "disable-impersonate" gatePublicKeyFlag = "gate-public-key" containerIDFlag = "container-id" 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(addressFlag, "", "Address of the wallet account") 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().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)") @@ -134,17 +129,6 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) 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)) if err != nil { 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, GatesPublicKeys: gatesPublicKeys, - EACLRules: bearerRules, - Impersonate: !disableImpersonate, + Impersonate: true, SessionTokenRules: sessionRules, SkipSessionRules: skipSessionRules, ContainerPolicies: policies, diff --git a/docs/authmate.md b/docs/authmate.md index b9fbed5..d6f5281 100644 --- a/docs/authmate.md +++ b/docs/authmate.md @@ -20,10 +20,10 @@ potentially). 1. [Generation of wallet](#generation-of-wallet) 2. [Issuance of a secret](#issuance-of-a-secret) - 1. [CLI parameters](#cli-parameters) - 2. [Bearer tokens](#bearer-tokens) - 3. [Session tokens](#session-tokens) - 4. [Containers policy](#containers-policy) + 1. [CLI parameters](#cli-parameters) + 2. [Bearer tokens](#bearer-tokens) + 3. [Session tokens](#session-tokens) + 4. [Containers policy](#containers-policy) 3. [Obtainment of a secret](#obtaining-credential-secrets) 4. [Generate presigned url](#generate-presigned-url) 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: + ```shell $ ./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 **Required parameters:** + * `--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 -interactively. You can also specify an account address to use from a wallet using the `--address` parameter. + 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. * `--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 -by a set of gateway keys, so you need to pass them as well. +* `--gate-public-key` is a public `secp256r1` 33-byte short key of a gate (use flags repeatedly for multiple gates). The + 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 -1. create a new container - 1. without a friendly name - 2. with ACL `0x3c8c8cce` -- all operations are forbidden for `OTHERS` and `BEARER` user groups, except for `GET` - 3. with policy `REP 2 IN X CBF 3 SELECT 2 FROM * AS X` -2. put bearer and session tokens with default rules (details in [Bearer tokens](#Bearer tokens) and -[Session tokens](#Session tokens)) + +1. create a new container + 1. without a friendly name + 2. with ACL `0x3c8c8cce` -- all operations are forbidden for `OTHERS` and `BEARER` user groups, except for `GET` + 3. with policy `REP 2 IN X CBF 3 SELECT 2 FROM * AS X` +2. put bearer and session tokens with default rules (details in [Bearer tokens](#bearer-tokens) and + [Session tokens](#session-tokens)) E.g.: + ```shell $ frostfs-s3-authmate issue-secret --wallet wallet.json \ --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 (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. **Optional parameters:** + * `--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-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` -* `--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 +* `--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` +* `--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 * `--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) -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. + 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. * `--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. ### Bearer tokens 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 -to interact with node can have access to your private containers or to containers in which eACL grants access to you -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": []}]} - ] -} -``` +Bearer token will be created with `impersonate` flag. It means that gate (which will use such token to interact with +node) can have access to your private containers or to containers in which eACL grants access to you by public key. ### Session tokens 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.: -```shell -$ frostfs-s3-authmate issue-secret --wallet wallet.json \ ---peer 192.168.130.71:8080 \ ---gate-public-key 0313b1ac3a8076e155a7e797b24f0b650cccad5941ea59d7cfd51a024a8b2a06bf \ ---session-tokens session.json -``` -where content of `session.json`: -```json -[ - { - "verb": "PUT", - "containerID": null - }, - { - "verb": "DELETE", - "containerID": null - }, - { - "verb": "SETEACL", - "containerID": null - } -] -``` + ```shell + $ frostfs-s3-authmate issue-secret --wallet wallet.json \ + --peer 192.168.130.71:8080 \ + --gate-public-key 0313b1ac3a8076e155a7e797b24f0b650cccad5941ea59d7cfd51a024a8b2a06bf \ + --session-tokens session.json + ``` + where content of `session.json`: + ```json + [ + { + "verb": "PUT", + "containerID": null + }, + { + "verb": "DELETE", + "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 -to all containers. Otherwise, specify `containerID` value in human-redabale -format (base58 encoded string). + If `containerID` is `null` or omitted, then session token rule will be applied + to all containers. Otherwise, specify `containerID` value in human-readable + 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 -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`. + > **_NB!_** To create buckets in FrostFS it's necessary to have session tokens with `PUT` permissions. 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` -in example above) + in example above) ### 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` can be set via parameter `--container-policy` (json-string and file path allowed): + ```json { "rep-3": "REP 3", @@ -302,7 +250,6 @@ Enter password for gate-wallet.json > } ``` - ## Generate presigned URL 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 + You can also can get the presigned URL (only for GET) using aws cli v2: ```shell @@ -341,19 +289,22 @@ http://localhost:8084/pregigned/obj?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Crede ``` ## Update secret + You can extend list of s3 gates that can accept already issued credentials. To do this use `frostfs-s3-authmate update-secret` command: **Required parameters:** + * `--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 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 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 -* `--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. +* `--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. ```shell $ 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. -| Code | Description | -|-------|--------------------------------------------------------------------------------------------| -| 1 | Any unknown errors, or errors generated by the parser of command line parameters. | -| 2 | Preparation errors: malformed configuration, issues with input data parsing. | -| 3 | FrostFS errors: connectivity problems, misconfiguration. | -| 4 | Business logic errors: `authmate` could not execute its task because of some restrictions. | +| Code | Description | +|------|--------------------------------------------------------------------------------------------| +| 1 | Any unknown errors, or errors generated by the parser of command line parameters. | +| 2 | Preparation errors: malformed configuration, issues with input data parsing. | +| 3 | FrostFS errors: connectivity problems, misconfiguration. | +| 4 | Business logic errors: `authmate` could not execute its task because of some restrictions. | -- 2.40.1 From 465eaa816ad966b3425a7625c2040e94c6fa9e59 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 28 May 2024 15:50:34 +0300 Subject: [PATCH 3/3] [#372] Drop [e]ACL related code Always consider buckets as APE compatible Signed-off-by: Denis Kirillov --- api/data/info.go | 1 - api/handler/acl.go | 1392 +------------------------- api/handler/acl_test.go | 1354 +------------------------ api/handler/copy.go | 40 +- api/handler/head_test.go | 30 +- api/handler/multipart_upload.go | 69 +- api/handler/put.go | 153 +-- api/handler/util.go | 14 - api/layer/container.go | 35 +- api/layer/frostfs.go | 22 - api/layer/frostfs_mock.go | 88 +- api/layer/layer.go | 26 - api/layer/multipart_upload.go | 14 +- api/middleware/policy.go | 7 +- api/router_mock_test.go | 3 +- api/router_test.go | 94 -- creds/accessbox/accessbox.go | 7 +- creds/accessbox/bearer_token_test.go | 12 - internal/frostfs/frostfs.go | 24 - 19 files changed, 43 insertions(+), 3342 deletions(-) diff --git a/api/data/info.go b/api/data/info.go index 3d85788..78a124a 100644 --- a/api/data/info.go +++ b/api/data/info.go @@ -31,7 +31,6 @@ type ( LocationConstraint string ObjectLockEnabled bool HomomorphicHashDisabled bool - APEEnabled bool } // ObjectInfo holds S3 object data. diff --git a/api/handler/acl.go b/api/handler/acl.go index 7933340..e463874 100644 --- a/api/handler/acl.go +++ b/api/handler/acl.go @@ -2,7 +2,6 @@ package handler import ( "context" - "crypto/ecdsa" "crypto/elliptic" "encoding/hex" "encoding/json" @@ -10,11 +9,8 @@ import ( "fmt" "io" "net/http" - "sort" - "strconv" "strings" - v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" @@ -22,43 +18,15 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" - oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "go.uber.org/zap" ) -var ( - writeOps = []eacl.Operation{eacl.OperationPut, eacl.OperationDelete} - readOps = []eacl.Operation{eacl.OperationGet, eacl.OperationHead, - eacl.OperationSearch, eacl.OperationRange, eacl.OperationRangeHash} - fullOps = []eacl.Operation{eacl.OperationGet, eacl.OperationHead, eacl.OperationPut, - eacl.OperationDelete, eacl.OperationSearch, eacl.OperationRange, eacl.OperationRangeHash} -) - -var actionToOpMap = map[string][]eacl.Operation{ - s3DeleteObject: {eacl.OperationDelete}, - s3GetObject: readOps, - s3PutObject: {eacl.OperationPut}, - s3ListBucket: readOps, -} - const ( - arnAwsPrefix = "arn:aws:s3:::" - allUsersWildcard = "*" - allUsersGroup = "http://acs.amazonaws.com/groups/global/AllUsers" - - s3DeleteObject = "s3:DeleteObject" - s3GetObject = "s3:GetObject" - s3PutObject = "s3:PutObject" - s3ListBucket = "s3:ListBucket" - s3ListBucketVersions = "s3:ListBucketVersions" - s3ListBucketMultipartUploads = "s3:ListBucketMultipartUploads" - s3GetObjectVersion = "s3:GetObjectVersion" + arnAwsPrefix = "arn:aws:s3:::" + allUsersGroup = "http://acs.amazonaws.com/groups/global/AllUsers" ) // AWSACL is aws permission constants. @@ -74,179 +42,10 @@ const ( type GranteeType string const ( - acpCanonicalUser GranteeType = "CanonicalUser" - acpAmazonCustomerByEmail GranteeType = "AmazonCustomerByEmail" - acpGroup GranteeType = "Group" + acpCanonicalUser GranteeType = "CanonicalUser" + acpGroup GranteeType = "Group" ) -type bucketPolicy struct { - Version string `json:"Version"` - ID string `json:"Id"` - Statement []statement `json:"Statement"` - Bucket string `json:"-"` -} - -type statement struct { - Sid string `json:"Sid"` - Effect string `json:"Effect"` - Principal principal `json:"Principal"` - Action []string `json:"Action"` - Resource []string `json:"Resource"` -} - -type principal struct { - AWS string `json:"AWS,omitempty"` - CanonicalUser string `json:"CanonicalUser,omitempty"` -} - -type orderedAstResource struct { - Index int - Resource *astResource -} - -type ast struct { - Resources []*astResource -} - -type astResource struct { - resourceInfo - Operations []*astOperation -} - -type resourceInfo struct { - Bucket string - Object string - Version string -} - -func (r *resourceInfo) Name() string { - if len(r.Object) == 0 { - return r.Bucket - } - if len(r.Version) == 0 { - return r.Bucket + "/" + r.Object - } - return r.Bucket + "/" + r.Object + ":" + r.Version -} - -func (r *resourceInfo) IsBucket() bool { - return len(r.Object) == 0 -} - -type astOperation struct { - Users []string - Op eacl.Operation - Action eacl.Action -} - -func (a astOperation) IsGroupGrantee() bool { - return len(a.Users) == 0 -} - -const ( - serviceRecordResourceKey = "Resource" - serviceRecordGroupLengthKey = "GroupLength" -) - -type ServiceRecord struct { - Resource string - GroupRecordsLength int -} - -func (s ServiceRecord) ToEACLRecord() *eacl.Record { - serviceRecord := eacl.NewRecord() - serviceRecord.SetAction(eacl.ActionAllow) - serviceRecord.SetOperation(eacl.OperationGet) - serviceRecord.AddFilter(eacl.HeaderFromService, eacl.MatchUnknown, serviceRecordResourceKey, s.Resource) - serviceRecord.AddFilter(eacl.HeaderFromService, eacl.MatchUnknown, serviceRecordGroupLengthKey, strconv.Itoa(s.GroupRecordsLength)) - eacl.AddFormedTarget(serviceRecord, eacl.RoleSystem) - return serviceRecord -} - -var ( - errInvalidStatement = stderrors.New("invalid statement") - errInvalidPrincipal = stderrors.New("invalid principal") -) - -func (s *statement) UnmarshalJSON(data []byte) error { - var statementMap map[string]interface{} - if err := json.Unmarshal(data, &statementMap); err != nil { - return err - } - - sidField, ok := statementMap["Sid"] - if ok { - if s.Sid, ok = sidField.(string); !ok { - return errInvalidStatement - } - } - - effectField, ok := statementMap["Effect"] - if ok { - if s.Effect, ok = effectField.(string); !ok { - return errInvalidStatement - } - } - - principalField, ok := statementMap["Principal"] - if ok { - principalMap, ok := principalField.(map[string]interface{}) - if !ok { - return errInvalidPrincipal - } - - awsField, ok := principalMap["AWS"] - if ok { - if s.Principal.AWS, ok = awsField.(string); !ok { - return fmt.Errorf("%w: 'AWS' field must be string", errInvalidPrincipal) - } - } - - canonicalUserField, ok := principalMap["CanonicalUser"] - if ok { - if s.Principal.CanonicalUser, ok = canonicalUserField.(string); !ok { - return errInvalidPrincipal - } - } - } - - actionField, ok := statementMap["Action"] - if ok { - switch actionField := actionField.(type) { - case []interface{}: - s.Action = make([]string, len(actionField)) - for i, action := range actionField { - if s.Action[i], ok = action.(string); !ok { - return errInvalidStatement - } - } - case string: - s.Action = []string{actionField} - default: - return errInvalidStatement - } - } - - resourceField, ok := statementMap["Resource"] - if ok { - switch resourceField := resourceField.(type) { - case []interface{}: - s.Resource = make([]string, len(resourceField)) - for i, action := range resourceField { - if s.Resource[i], ok = action.(string); !ok { - return errInvalidStatement - } - } - case string: - s.Resource = []string{resourceField} - default: - return errInvalidStatement - } - } - - return nil -} - func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reqInfo := middleware.GetReqInfo(ctx) @@ -263,21 +62,7 @@ func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { return } - if bktInfo.APEEnabled || len(settings.CannedACL) != 0 { - if err = middleware.EncodeToResponse(w, h.encodeBucketCannedACL(ctx, bktInfo, settings)); err != nil { - h.logAndSendError(w, "something went wrong", reqInfo, err) - return - } - return - } - - bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo) - if err != nil { - h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err) - return - } - - if err = middleware.EncodeToResponse(w, h.encodeBucketACL(ctx, bktInfo.Name, bucketACL)); err != nil { + if err = middleware.EncodeToResponse(w, h.encodeBucketCannedACL(ctx, bktInfo, settings)); err != nil { h.logAndSendError(w, "something went wrong", reqInfo, err) return } @@ -328,15 +113,6 @@ func (h *handler) encodePrivateCannedACL(ctx context.Context, bktInfo *data.Buck return res } -func (h *handler) bearerTokenIssuerKey(ctx context.Context) (*keys.PublicKey, error) { - box, err := middleware.GetBoxData(ctx) - if err != nil { - return nil, err - } - - return getTokenIssuerKey(box) -} - func getTokenIssuerKey(box *accessbox.Box) (*keys.PublicKey, error) { if box.Gate.BearerToken == nil { return nil, stderrors.New("bearer token is missing") @@ -365,47 +141,7 @@ func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) { return } - if bktInfo.APEEnabled || len(settings.CannedACL) != 0 { - h.putBucketACLAPEHandler(w, r, reqInfo, bktInfo, settings) - return - } - - key, err := h.bearerTokenIssuerKey(r.Context()) - if err != nil { - h.logAndSendError(w, "couldn't get bearer token issuer key", reqInfo, err) - return - } - - token, err := getSessionTokenSetEACL(r.Context()) - if err != nil { - h.logAndSendError(w, "couldn't get eacl token", reqInfo, err) - return - } - - list := &AccessControlPolicy{} - if r.ContentLength == 0 { - list, err = parseACLHeaders(r.Header, key) - if err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, err) - return - } - } else if err = h.cfg.NewXMLDecoder(r.Body).Decode(list); err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error())) - return - } - - resInfo := &resourceInfo{Bucket: reqInfo.BucketName} - astBucket, err := aclToAst(list, resInfo) - if err != nil { - h.logAndSendError(w, "could not translate acl to policy", reqInfo, err) - return - } - - if _, err = h.updateBucketACL(r, astBucket, bktInfo, token); err != nil { - h.logAndSendError(w, "could not update bucket acl", reqInfo, err) - return - } - w.WriteHeader(http.StatusOK) + h.putBucketACLAPEHandler(w, r, reqInfo, bktInfo, settings) } func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request, reqInfo *middleware.ReqInfo, bktInfo *data.BucketInfo, settings *data.BucketSettings) { @@ -456,44 +192,6 @@ func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request, w.WriteHeader(http.StatusOK) } -func (h *handler) updateBucketACL(r *http.Request, astChild *ast, bktInfo *data.BucketInfo, sessionToken *session.Container) (bool, error) { - bucketACL, err := h.obj.GetBucketACL(r.Context(), bktInfo) - if err != nil { - return false, fmt.Errorf("could not get bucket eacl: %w", err) - } - - parentAst := tableToAst(bucketACL.EACL, bktInfo.Name) - strCID := bucketACL.Info.CID.EncodeToString() - - for _, resource := range parentAst.Resources { - if resource.Bucket == strCID { - resource.Bucket = bktInfo.Name - } - } - - resAst, updated := mergeAst(parentAst, astChild) - if !updated { - return false, nil - } - - table, err := astToTable(resAst) - if err != nil { - return false, fmt.Errorf("could not translate ast to table: %w", err) - } - - p := &layer.PutBucketACLParams{ - BktInfo: bktInfo, - EACL: table, - SessionToken: sessionToken, - } - - if err = h.obj.PutBucketACL(r.Context(), p); err != nil { - return false, fmt.Errorf("could not put bucket acl: %w", err) - } - - return true, nil -} - func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reqInfo := middleware.GetReqInfo(ctx) @@ -510,118 +208,22 @@ func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { return } - if bktInfo.APEEnabled || len(settings.CannedACL) != 0 { - if err = middleware.EncodeToResponse(w, h.encodePrivateCannedACL(ctx, bktInfo, settings)); err != nil { - h.logAndSendError(w, "something went wrong", reqInfo, err) - return - } + if err = middleware.EncodeToResponse(w, h.encodePrivateCannedACL(ctx, bktInfo, settings)); err != nil { + h.logAndSendError(w, "something went wrong", reqInfo, err) return } - - bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo) - if err != nil { - h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err) - return - } - - prm := &layer.HeadObjectParams{ - BktInfo: bktInfo, - Object: reqInfo.ObjectName, - VersionID: reqInfo.URL.Query().Get(api.QueryVersionID), - } - - objInfo, err := h.obj.GetObjectInfo(ctx, prm) - if err != nil { - h.logAndSendError(w, "could not get object info", reqInfo, err) - return - } - - if err = middleware.EncodeToResponse(w, h.encodeObjectACL(ctx, bucketACL, reqInfo.BucketName, objInfo.VersionID())); err != nil { - h.logAndSendError(w, "failed to encode response", reqInfo, err) - } } func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reqInfo := middleware.GetReqInfo(ctx) - bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) - if err != nil { + if _, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil { h.logAndSendError(w, "could not get bucket info", reqInfo, err) return } - apeEnabled := bktInfo.APEEnabled - - if !apeEnabled { - settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) - if err != nil { - h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err) - return - } - apeEnabled = len(settings.CannedACL) != 0 - } - - if apeEnabled { - h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) - return - } - - versionID := reqInfo.URL.Query().Get(api.QueryVersionID) - key, err := h.bearerTokenIssuerKey(ctx) - if err != nil { - h.logAndSendError(w, "couldn't get gate key", reqInfo, err) - return - } - - token, err := getSessionTokenSetEACL(ctx) - if err != nil { - h.logAndSendError(w, "couldn't get eacl token", reqInfo, err) - return - } - - p := &layer.HeadObjectParams{ - BktInfo: bktInfo, - Object: reqInfo.ObjectName, - VersionID: versionID, - } - - objInfo, err := h.obj.GetObjectInfo(ctx, p) - if err != nil { - h.logAndSendError(w, "could not get object info", reqInfo, err) - return - } - - list := &AccessControlPolicy{} - if r.ContentLength == 0 { - list, err = parseACLHeaders(r.Header, key) - if err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, err) - return - } - } else if err = h.cfg.NewXMLDecoder(r.Body).Decode(list); err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error())) - return - } - - resInfo := &resourceInfo{ - Bucket: reqInfo.BucketName, - Object: reqInfo.ObjectName, - Version: objInfo.VersionID(), - } - - astObject, err := aclToAst(list, resInfo) - if err != nil { - h.logAndSendError(w, "could not translate acl to ast", reqInfo, err) - return - } - - if _, err = h.updateBucketACL(r, astObject, bktInfo, token); err != nil { - h.logAndSendError(w, "could not update bucket acl", reqInfo, err) - return - } - - w.WriteHeader(http.StatusOK) + h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) } func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Request) { @@ -814,977 +416,3 @@ func (h *handler) nativeResolver(ns string, bktInfo *data.BucketInfo) engineiam. func getBucketChainID(prefix chain.Name, bktInfo *data.BucketInfo) chain.ID { return chain.ID(string(prefix) + ":bkt" + string(bktInfo.CID[:])) } - -func parseACLHeaders(header http.Header, key *keys.PublicKey) (*AccessControlPolicy, error) { - var err error - acp := &AccessControlPolicy{Owner: Owner{ - ID: hex.EncodeToString(key.Bytes()), - DisplayName: key.Address(), - }} - acp.AccessControlList = []*Grant{{ - Grantee: &Grantee{ - ID: hex.EncodeToString(key.Bytes()), - DisplayName: key.Address(), - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }} - - cannedACL := header.Get(api.AmzACL) - if cannedACL != "" { - return addPredefinedACP(acp, cannedACL) - } - - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantFullControl); err != nil { - return nil, fmt.Errorf("add grantees full control: %w", err) - } - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantRead); err != nil { - return nil, fmt.Errorf("add grantees read: %w", err) - } - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantWrite); err != nil { - return nil, fmt.Errorf("add grantees write: %w", err) - } - - return acp, nil -} - -func addGrantees(list []*Grant, headers http.Header, hdr string) ([]*Grant, error) { - grant := headers.Get(hdr) - if grant == "" { - return list, nil - } - - permission, err := grantHdrToPermission(hdr) - if err != nil { - return nil, fmt.Errorf("parse header: %w", err) - } - - grantees, err := parseGrantee(grant) - if err != nil { - return nil, fmt.Errorf("parse grantee: %w", err) - } - - for _, grantee := range grantees { - if grantee.Type == acpAmazonCustomerByEmail || (grantee.Type == acpGroup && grantee.URI != allUsersGroup) { - return nil, stderrors.New("unsupported grantee type") - } - - list = append(list, &Grant{ - Grantee: grantee, - Permission: permission, - }) - } - return list, nil -} - -func grantHdrToPermission(grant string) (AWSACL, error) { - switch grant { - case api.AmzGrantFullControl: - return aclFullControl, nil - case api.AmzGrantRead: - return aclRead, nil - case api.AmzGrantWrite: - return aclWrite, nil - } - return "", fmt.Errorf("unsuppoted header: %s", grant) -} - -func parseGrantee(grantees string) ([]*Grantee, error) { - var result []*Grantee - - split := strings.Split(grantees, ", ") - for _, pair := range split { - split2 := strings.Split(pair, "=") - if len(split2) != 2 { - return nil, errors.GetAPIError(errors.ErrInvalidArgument) - } - - grantee, err := formGrantee(split2[0], split2[1]) - if err != nil { - return nil, fmt.Errorf("form grantee: %w", err) - } - result = append(result, grantee) - } - - return result, nil -} - -func formGrantee(granteeType, value string) (*Grantee, error) { - value = data.UnQuote(value) - switch granteeType { - case "id": - return &Grantee{ - ID: value, - Type: acpCanonicalUser, - }, nil - case "uri": - return &Grantee{ - URI: value, - Type: acpGroup, - }, nil - case "emailAddress": - return &Grantee{ - EmailAddress: value, - Type: acpAmazonCustomerByEmail, - }, nil - } - // do not return grantee type to avoid sensitive data logging (#489) - return nil, fmt.Errorf("unknown grantee type") -} - -func addPredefinedACP(acp *AccessControlPolicy, cannedACL string) (*AccessControlPolicy, error) { - switch cannedACL { - case basicACLPrivate: - case basicACLPublic: - acp.AccessControlList = append(acp.AccessControlList, &Grant{ - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclFullControl, - }) - case cannedACLAuthRead: - fallthrough - case basicACLReadOnly: - acp.AccessControlList = append(acp.AccessControlList, &Grant{ - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }) - default: - return nil, errors.GetAPIError(errors.ErrInvalidArgument) - } - - return acp, nil -} - -func tableToAst(table *eacl.Table, bktName string) *ast { - resourceMap := make(map[string]orderedAstResource) - - var groupRecordsLeft int - var currentResource orderedAstResource - for i, record := range table.Records() { - if serviceRec := tryServiceRecord(record); serviceRec != nil { - resInfo := resourceInfoFromName(serviceRec.Resource, bktName) - groupRecordsLeft = serviceRec.GroupRecordsLength - - currentResource = getResourceOrCreate(resourceMap, i, resInfo) - resourceMap[resInfo.Name()] = currentResource - } else if groupRecordsLeft != 0 { - groupRecordsLeft-- - addOperationsAndUpdateMap(currentResource, record, resourceMap) - } else { - resInfo := resInfoFromFilters(bktName, record.Filters()) - resource := getResourceOrCreate(resourceMap, i, resInfo) - addOperationsAndUpdateMap(resource, record, resourceMap) - } - } - - return &ast{ - Resources: formReverseOrderResources(resourceMap), - } -} - -func formReverseOrderResources(resourceMap map[string]orderedAstResource) []*astResource { - orderedResources := make([]orderedAstResource, 0, len(resourceMap)) - for _, resource := range resourceMap { - orderedResources = append(orderedResources, resource) - } - sort.Slice(orderedResources, func(i, j int) bool { - return orderedResources[i].Index >= orderedResources[j].Index // reverse order - }) - - result := make([]*astResource, len(orderedResources)) - for i, ordered := range orderedResources { - res := ordered.Resource - for j, k := 0, len(res.Operations)-1; j < k; j, k = j+1, k-1 { - res.Operations[j], res.Operations[k] = res.Operations[k], res.Operations[j] - } - - result[i] = res - } - - return result -} - -func addOperationsAndUpdateMap(orderedRes orderedAstResource, record eacl.Record, resMap map[string]orderedAstResource) { - for _, target := range record.Targets() { - orderedRes.Resource.Operations = addToList(orderedRes.Resource.Operations, record, target) - } - resMap[orderedRes.Resource.Name()] = orderedRes -} - -func getResourceOrCreate(resMap map[string]orderedAstResource, index int, resInfo resourceInfo) orderedAstResource { - resource, ok := resMap[resInfo.Name()] - if !ok { - resource = orderedAstResource{ - Index: index, - Resource: &astResource{resourceInfo: resInfo}, - } - } - return resource -} - -func resInfoFromFilters(bucketName string, filters []eacl.Filter) resourceInfo { - resInfo := resourceInfo{Bucket: bucketName} - for _, filter := range filters { - if filter.Matcher() == eacl.MatchStringEqual { - if filter.Key() == object.AttributeFilePath { - resInfo.Object = filter.Value() - } else if filter.Key() == v2acl.FilterObjectID { - resInfo.Version = filter.Value() - } - } - } - - return resInfo -} - -func mergeAst(parent, child *ast) (*ast, bool) { - updated := false - for _, resource := range child.Resources { - parentResource := getParentResource(parent, resource) - if parentResource == nil { - parent.Resources = append(parent.Resources, resource) - updated = true - continue - } - - var newOps []*astOperation - for _, astOp := range resource.Operations { - // get parent matched operations - ops := getAstOps(parentResource, astOp) - switch len(ops) { - case 2: // parent contains different actions for the same child operation - // potential inconsistency - if groupGrantee := astOp.IsGroupGrantee(); groupGrantee { - // it is not likely (such state must be detected early) - // inconsistency - action := eacl.ActionAllow - if astOp.Action == eacl.ActionAllow { - action = eacl.ActionDeny - } - removeAstOp(parentResource, groupGrantee, astOp.Op, action) - updated = true - continue - } - - opToAdd, opToDelete := ops[0], ops[1] - if ops[1].Action == astOp.Action { - opToAdd, opToDelete = ops[1], ops[0] - } - - if handleAddOperations(parentResource, astOp, opToAdd) { - updated = true - } - if handleRemoveOperations(parentResource, astOp, opToDelete) { - updated = true - } - case 1: // parent contains some action for the same child operation - if astOp.Action != ops[0].Action { - // potential inconsistency - if groupGrantee := astOp.IsGroupGrantee(); groupGrantee { - // inconsistency - ops[0].Action = astOp.Action - updated = true - continue - } - - if handleRemoveOperations(parentResource, astOp, ops[0]) { - updated = true - } - parentResource.Operations = append(parentResource.Operations, astOp) - continue - } - - if handleAddOperations(parentResource, astOp, ops[0]) { - updated = true - } - case 0: // parent doesn't contain actions for the same child operation - newOps = append(newOps, astOp) - updated = true - } - } - - if newOps != nil { - parentResource.Operations = append(newOps, parentResource.Operations...) - } - } - - return parent, updated -} - -func handleAddOperations(parentResource *astResource, astOp, existedOp *astOperation) bool { - var needToAdd []string - for _, user := range astOp.Users { - if !containsStr(existedOp.Users, user) { - needToAdd = append(needToAdd, user) - } - } - if len(needToAdd) != 0 { - addUsers(parentResource, existedOp, needToAdd) - return true - } - return false -} - -func handleRemoveOperations(parentResource *astResource, astOp, existedOp *astOperation) bool { - var needToRemove []string - for _, user := range astOp.Users { - if containsStr(existedOp.Users, user) { - needToRemove = append(needToRemove, user) - } - } - if len(needToRemove) != 0 { - removeUsers(parentResource, existedOp, needToRemove) - return true - } - - return false -} - -func containsStr(list []string, element string) bool { - for _, str := range list { - if str == element { - return true - } - } - return false -} - -func getAstOps(resource *astResource, childOp *astOperation) []*astOperation { - var res []*astOperation - for _, astOp := range resource.Operations { - if astOp.IsGroupGrantee() == childOp.IsGroupGrantee() && astOp.Op == childOp.Op { - res = append(res, astOp) - } - } - return res -} - -func removeAstOp(resource *astResource, group bool, op eacl.Operation, action eacl.Action) { - for i, astOp := range resource.Operations { - if astOp.IsGroupGrantee() == group && astOp.Op == op && astOp.Action == action { - resource.Operations = append(resource.Operations[:i], resource.Operations[i+1:]...) - return - } - } -} - -func addUsers(resource *astResource, astO *astOperation, users []string) { - for _, astOp := range resource.Operations { - if astOp.IsGroupGrantee() == astO.IsGroupGrantee() && astOp.Op == astO.Op && astOp.Action == astO.Action { - astOp.Users = append(astO.Users, users...) - return - } - } -} - -func removeUsers(resource *astResource, astOperation *astOperation, users []string) { - for ind, astOp := range resource.Operations { - if !astOp.IsGroupGrantee() && astOp.Op == astOperation.Op && astOp.Action == astOperation.Action { - filteredUsers := astOp.Users[:0] // new slice without allocation - for _, user := range astOp.Users { - if !containsStr(users, user) { - filteredUsers = append(filteredUsers, user) - } - } - if len(filteredUsers) == 0 { // remove ast resource - resource.Operations = append(resource.Operations[:ind], resource.Operations[ind+1:]...) - } else { - astOp.Users = filteredUsers - } - return - } - } -} - -func getParentResource(parent *ast, resource *astResource) *astResource { - for _, parentResource := range parent.Resources { - if resource.Bucket == parentResource.Bucket && resource.Object == parentResource.Object && - resource.Version == parentResource.Version { - return parentResource - } - } - return nil -} - -func astToTable(ast *ast) (*eacl.Table, error) { - table := eacl.NewTable() - - for i := len(ast.Resources) - 1; i >= 0; i-- { - records, err := formRecords(ast.Resources[i]) - if err != nil { - return nil, fmt.Errorf("form records: %w", err) - } - - serviceRecord := ServiceRecord{ - Resource: ast.Resources[i].Name(), - GroupRecordsLength: len(records), - } - table.AddRecord(serviceRecord.ToEACLRecord()) - - for _, rec := range records { - table.AddRecord(rec) - } - } - - return table, nil -} - -func tryServiceRecord(record eacl.Record) *ServiceRecord { - if record.Action() != eacl.ActionAllow || record.Operation() != eacl.OperationGet || - len(record.Targets()) != 1 || len(record.Filters()) != 2 { - return nil - } - - target := record.Targets()[0] - if target.Role() != eacl.RoleSystem { - return nil - } - - resourceFilter := record.Filters()[0] - recordsFilter := record.Filters()[1] - if resourceFilter.From() != eacl.HeaderFromService || recordsFilter.From() != eacl.HeaderFromService || - resourceFilter.Matcher() != eacl.MatchUnknown || recordsFilter.Matcher() != eacl.MatchUnknown || - resourceFilter.Key() != serviceRecordResourceKey || recordsFilter.Key() != serviceRecordGroupLengthKey { - return nil - } - - groupLength, err := strconv.Atoi(recordsFilter.Value()) - if err != nil { - return nil - } - - return &ServiceRecord{ - Resource: resourceFilter.Value(), - GroupRecordsLength: groupLength, - } -} - -func formRecords(resource *astResource) ([]*eacl.Record, error) { - var res []*eacl.Record - - for i := len(resource.Operations) - 1; i >= 0; i-- { - astOp := resource.Operations[i] - record := eacl.NewRecord() - record.SetOperation(astOp.Op) - record.SetAction(astOp.Action) - if astOp.IsGroupGrantee() { - eacl.AddFormedTarget(record, eacl.RoleOthers) - } else { - targetKeys := make([]ecdsa.PublicKey, 0, len(astOp.Users)) - for _, user := range astOp.Users { - pk, err := keys.NewPublicKeyFromString(user) - if err != nil { - return nil, fmt.Errorf("public key from string: %w", err) - } - targetKeys = append(targetKeys, (ecdsa.PublicKey)(*pk)) - } - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record, eacl.RoleUnknown, targetKeys...) - } - if len(resource.Object) != 0 { - if len(resource.Version) != 0 { - var id oid.ID - if err := id.DecodeString(resource.Version); err != nil { - return nil, fmt.Errorf("parse object version (oid): %w", err) - } - record.AddObjectIDFilter(eacl.MatchStringEqual, id) - } else { - record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, resource.Object) - } - } - res = append(res, record) - } - - return res, nil -} - -func addToList(operations []*astOperation, rec eacl.Record, target eacl.Target) []*astOperation { - var ( - found *astOperation - groupTarget = target.Role() == eacl.RoleOthers - ) - - for _, astOp := range operations { - if astOp.Op == rec.Operation() && astOp.IsGroupGrantee() == groupTarget { - found = astOp - } - } - - if found != nil { - if !groupTarget { - for _, key := range target.BinaryKeys() { - found.Users = append(found.Users, hex.EncodeToString(key)) - } - } - } else { - astOperation := &astOperation{ - Op: rec.Operation(), - Action: rec.Action(), - } - if !groupTarget { - for _, key := range target.BinaryKeys() { - astOperation.Users = append(astOperation.Users, hex.EncodeToString(key)) - } - } - - operations = append(operations, astOperation) - } - - return operations -} - -func policyToAst(bktPolicy *bucketPolicy) (*ast, error) { - res := &ast{} - - rr := make(map[string]*astResource) - - for _, state := range bktPolicy.Statement { - if state.Principal.AWS != "" && state.Principal.AWS != allUsersWildcard || - state.Principal.AWS == "" && state.Principal.CanonicalUser == "" { - return nil, fmt.Errorf("unsupported principal: %v", state.Principal) - } - var groupGrantee bool - if state.Principal.AWS == allUsersWildcard { - groupGrantee = true - } - - for _, resource := range state.Resource { - trimmedResource := strings.TrimPrefix(resource, arnAwsPrefix) - r, ok := rr[trimmedResource] - if !ok { - if !strings.HasPrefix(trimmedResource, bktPolicy.Bucket) { - return nil, fmt.Errorf("resource '%s' must be in the same bucket '%s'", trimmedResource, bktPolicy.Bucket) - } - - r = &astResource{ - resourceInfo: resourceInfoFromName(trimmedResource, bktPolicy.Bucket), - } - } - for _, action := range state.Action { - for _, op := range actionToOpMap[action] { - toAction := effectToAction(state.Effect) - r.Operations = addTo(r.Operations, state.Principal.CanonicalUser, op, groupGrantee, toAction) - } - } - - rr[trimmedResource] = r - } - } - - for _, val := range rr { - res.Resources = append(res.Resources, val) - } - - return res, nil -} - -func resourceInfoFromName(name, bucketName string) resourceInfo { - resInfo := resourceInfo{Bucket: bucketName} - if name != bucketName { - versionedObject := strings.TrimPrefix(name, bucketName+"/") - objVersion := strings.Split(versionedObject, ":") - if len(objVersion) <= 2 { - resInfo.Object = objVersion[0] - if len(objVersion) == 2 { - resInfo.Version = objVersion[1] - } - } else { - resInfo.Object = strings.Join(objVersion[:len(objVersion)-1], ":") - resInfo.Version = objVersion[len(objVersion)-1] - } - } - - return resInfo -} - -func addTo(list []*astOperation, userID string, op eacl.Operation, groupGrantee bool, action eacl.Action) []*astOperation { - var found *astOperation - for _, astop := range list { - if astop.Op == op && astop.IsGroupGrantee() == groupGrantee { - found = astop - } - } - - if found != nil { - if !groupGrantee { - found.Users = append(found.Users, userID) - } - } else { - astoperation := &astOperation{ - Op: op, - Action: action, - } - if !groupGrantee { - astoperation.Users = append(astoperation.Users, userID) - } - - list = append(list, astoperation) - } - - return list -} - -func aclToAst(acl *AccessControlPolicy, resInfo *resourceInfo) (*ast, error) { - res := &ast{} - - resource := &astResource{resourceInfo: *resInfo} - - ops := readOps - if resInfo.IsBucket() { - ops = append(ops, writeOps...) - } - - // Expect to have at least 1 full control grant for owner which is set in - // parseACLHeaders(). If there is no other grants, then user sets private - // canned ACL, which is processed in this branch. - if len(acl.AccessControlList) < 2 { - for _, op := range ops { - operation := &astOperation{ - Op: op, - Action: eacl.ActionDeny, - } - resource.Operations = append(resource.Operations, operation) - } - } - - for _, op := range ops { - operation := &astOperation{ - Users: []string{acl.Owner.ID}, - Op: op, - Action: eacl.ActionAllow, - } - resource.Operations = append(resource.Operations, operation) - } - - for _, grant := range acl.AccessControlList { - if grant.Grantee.Type == acpAmazonCustomerByEmail || (grant.Grantee.Type == acpGroup && grant.Grantee.URI != allUsersGroup) { - return nil, stderrors.New("unsupported grantee type") - } - - var groupGrantee bool - if grant.Grantee.Type == acpGroup { - groupGrantee = true - } else if grant.Grantee.ID == acl.Owner.ID { - continue - } - - for _, action := range getActions(grant.Permission, resInfo.IsBucket()) { - for _, op := range actionToOpMap[action] { - resource.Operations = addTo(resource.Operations, grant.Grantee.ID, op, groupGrantee, eacl.ActionAllow) - } - } - } - - res.Resources = []*astResource{resource} - return res, nil -} - -func aclToPolicy(acl *AccessControlPolicy, resInfo *resourceInfo) (*bucketPolicy, error) { - if resInfo.Bucket == "" { - return nil, fmt.Errorf("resource bucket must not be empty") - } - - results := []statement{ - getAllowStatement(resInfo, acl.Owner.ID, aclFullControl), - } - - // Expect to have at least 1 full control grant for owner which is set in - // parseACLHeaders(). If there is no other grants, then user sets private - // canned ACL, which is processed in this branch. - if len(acl.AccessControlList) < 2 { - results = append([]statement{getDenyStatement(resInfo, allUsersWildcard, aclFullControl)}, results...) - } - - for _, grant := range acl.AccessControlList { - if grant.Grantee.Type == acpAmazonCustomerByEmail || (grant.Grantee.Type == acpGroup && grant.Grantee.URI != allUsersGroup) { - return nil, stderrors.New("unsupported grantee type") - } - - user := grant.Grantee.ID - if grant.Grantee.Type == acpGroup { - user = allUsersWildcard - } else if user == acl.Owner.ID { - continue - } - results = append(results, getAllowStatement(resInfo, user, grant.Permission)) - } - - return &bucketPolicy{ - Statement: results, - Bucket: resInfo.Bucket, - }, nil -} - -func getAllowStatement(resInfo *resourceInfo, id string, permission AWSACL) statement { - state := statement{ - Effect: "Allow", - Principal: principal{ - CanonicalUser: id, - }, - Action: getActions(permission, resInfo.IsBucket()), - Resource: []string{arnAwsPrefix + resInfo.Name()}, - } - - if id == allUsersWildcard { - state.Principal = principal{AWS: allUsersWildcard} - } - - return state -} - -func getDenyStatement(resInfo *resourceInfo, id string, permission AWSACL) statement { - state := statement{ - Effect: "Deny", - Principal: principal{ - CanonicalUser: id, - }, - Action: getActions(permission, resInfo.IsBucket()), - Resource: []string{arnAwsPrefix + resInfo.Name()}, - } - - if id == allUsersWildcard { - state.Principal = principal{AWS: allUsersWildcard} - } - - return state -} - -func getActions(permission AWSACL, isBucket bool) []string { - var res []string - switch permission { - case aclRead: - if isBucket { - res = []string{s3ListBucket, s3ListBucketVersions, s3ListBucketMultipartUploads} - } else { - res = []string{s3GetObject, s3GetObjectVersion} - } - case aclWrite: - if isBucket { - res = []string{s3PutObject, s3DeleteObject} - } - case aclFullControl: - if isBucket { - res = []string{s3ListBucket, s3ListBucketVersions, s3ListBucketMultipartUploads, s3PutObject, s3DeleteObject} - } else { - res = []string{s3GetObject, s3GetObjectVersion} - } - } - - return res -} - -func effectToAction(effect string) eacl.Action { - switch effect { - case "Allow": - return eacl.ActionAllow - case "Deny": - return eacl.ActionDeny - } - return eacl.ActionUnknown -} - -func permissionToOperations(permission AWSACL) []eacl.Operation { - switch permission { - case aclFullControl: - return fullOps - case aclRead: - return readOps - case aclWrite: - return writeOps - } - return nil -} - -func isWriteOperation(op eacl.Operation) bool { - return op == eacl.OperationDelete || op == eacl.OperationPut -} - -type access struct { - recipient string - operations []eacl.Operation -} - -type accessList struct { - list []access -} - -func (c *accessList) addAccess(recipient string, operation eacl.Operation) { - for i, v := range c.list { - if v.recipient == recipient { - c.list[i].operations = append(c.list[i].operations, operation) - return - } - } - - c.list = append(c.list, access{recipient, []eacl.Operation{operation}}) -} - -func (h *handler) encodeObjectACL(ctx context.Context, bucketACL *layer.BucketACL, bucketName, objectVersion string) *AccessControlPolicy { - res := &AccessControlPolicy{ - Owner: Owner{ - ID: bucketACL.Info.Owner.String(), - DisplayName: bucketACL.Info.Owner.String(), - }, - } - - m := &accessList{} - - astList := tableToAst(bucketACL.EACL, bucketName) - - for _, resource := range astList.Resources { - if resource.Version != objectVersion { - continue - } - - for _, op := range resource.Operations { - if op.Action != eacl.ActionAllow { - continue - } - - if len(op.Users) == 0 { - m.addAccess(allUsersGroup, op.Op) - } else { - for _, user := range op.Users { - m.addAccess(user, op.Op) - } - } - } - } - - for _, val := range m.list { - permission := aclFullControl - read := true - for op := eacl.OperationGet; op <= eacl.OperationRangeHash; op++ { - if !contains(val.operations, op) && !isWriteOperation(op) { - read = false - } - } - - if read { - permission = aclFullControl - } else { - h.reqLogger(ctx).Warn(logs.SomeACLNotFullyMapped) - } - - var grantee *Grantee - if val.recipient == allUsersGroup { - grantee = NewGrantee(acpGroup) - grantee.URI = allUsersGroup - } else { - grantee = NewGrantee(acpCanonicalUser) - grantee.ID = val.recipient - } - - grant := &Grant{ - Grantee: grantee, - Permission: permission, - } - res.AccessControlList = append(res.AccessControlList, grant) - } - - return res -} - -func (h *handler) encodeBucketACL(ctx context.Context, bucketName string, bucketACL *layer.BucketACL) *AccessControlPolicy { - return h.encodeObjectACL(ctx, bucketACL, bucketName, "") -} - -func contains(list []eacl.Operation, op eacl.Operation) bool { - for _, operation := range list { - if operation == op { - return true - } - } - return false -} - -type getRecordFunc func(op eacl.Operation) *eacl.Record - -func bucketACLToTable(acp *AccessControlPolicy, resInfo *resourceInfo) (*eacl.Table, error) { - if !resInfo.IsBucket() { - return nil, fmt.Errorf("allowed only bucket acl") - } - - var found bool - table := eacl.NewTable() - - ownerKey, err := keys.NewPublicKeyFromString(acp.Owner.ID) - if err != nil { - return nil, fmt.Errorf("public key from string: %w", err) - } - - for _, grant := range acp.AccessControlList { - if !isValidGrant(grant) { - return nil, stderrors.New("unsupported grantee") - } - if grant.Grantee.ID == acp.Owner.ID { - found = true - } - - getRecord, err := getRecordFunction(grant.Grantee) - if err != nil { - return nil, fmt.Errorf("record func from grantee: %w", err) - } - for _, op := range permissionToOperations(grant.Permission) { - table.AddRecord(getRecord(op)) - } - } - - if !found { - for _, op := range fullOps { - table.AddRecord(getAllowRecord(op, ownerKey)) - } - } - - for _, op := range fullOps { - table.AddRecord(getOthersRecord(op, eacl.ActionDeny)) - } - - return table, nil -} - -func getRecordFunction(grantee *Grantee) (getRecordFunc, error) { - switch grantee.Type { - case acpAmazonCustomerByEmail: - case acpCanonicalUser: - pk, err := keys.NewPublicKeyFromString(grantee.ID) - if err != nil { - return nil, fmt.Errorf("couldn't parse canonical ID %s: %w", grantee.ID, err) - } - return func(op eacl.Operation) *eacl.Record { - return getAllowRecord(op, pk) - }, nil - case acpGroup: - return func(op eacl.Operation) *eacl.Record { - return getOthersRecord(op, eacl.ActionAllow) - }, nil - } - return nil, fmt.Errorf("unknown type: %s", grantee.Type) -} - -func isValidGrant(grant *Grant) bool { - return (grant.Permission == aclFullControl || grant.Permission == aclRead || grant.Permission == aclWrite) && - (grant.Grantee.Type == acpCanonicalUser || (grant.Grantee.Type == acpGroup && grant.Grantee.URI == allUsersGroup)) -} - -func getAllowRecord(op eacl.Operation, pk *keys.PublicKey) *eacl.Record { - record := eacl.NewRecord() - record.SetOperation(op) - record.SetAction(eacl.ActionAllow) - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record, eacl.RoleUnknown, (ecdsa.PublicKey)(*pk)) - return record -} - -func getOthersRecord(op eacl.Operation, action eacl.Action) *eacl.Record { - record := eacl.NewRecord() - record.SetOperation(op) - record.SetAction(action) - eacl.AddFormedTarget(record, eacl.RoleOthers) - return record -} diff --git a/api/handler/acl_test.go b/api/handler/acl_test.go index 1081e8f..fb1c46b 100644 --- a/api/handler/acl_test.go +++ b/api/handler/acl_test.go @@ -2,14 +2,9 @@ package handler import ( "bytes" - "crypto/ecdsa" - "crypto/rand" - "crypto/sha256" "encoding/hex" "encoding/json" "encoding/xml" - "fmt" - "io" "net/http" "net/http/httptest" "testing" @@ -17,13 +12,9 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" - "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" - oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" @@ -31,1290 +22,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestTableToAst(t *testing.T) { - b := make([]byte, 32) - _, err := io.ReadFull(rand.Reader, b) - require.NoError(t, err) - var id oid.ID - id.SetSHA256(sha256.Sum256(b)) - - key, err := keys.NewPrivateKey() - require.NoError(t, err) - key2, err := keys.NewPrivateKey() - require.NoError(t, err) - - table := new(eacl.Table) - record := eacl.NewRecord() - record.SetAction(eacl.ActionAllow) - record.SetOperation(eacl.OperationGet) - eacl.AddFormedTarget(record, eacl.RoleOthers) - table.AddRecord(record) - record2 := eacl.NewRecord() - record2.SetAction(eacl.ActionDeny) - record2.SetOperation(eacl.OperationPut) - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record2, eacl.RoleUnknown, *(*ecdsa.PublicKey)(key.PublicKey()), *((*ecdsa.PublicKey)(key2.PublicKey()))) - record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, "objectName") - record2.AddObjectIDFilter(eacl.MatchStringEqual, id) - table.AddRecord(record2) - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{Bucket: "bucketName"}, - Operations: []*astOperation{{ - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }}}, - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - Object: "objectName", - Version: id.EncodeToString(), - }, - Operations: []*astOperation{{ - Users: []string{ - hex.EncodeToString(key.PublicKey().Bytes()), - hex.EncodeToString(key2.PublicKey().Bytes()), - }, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }}}, - }, - } - - actualAst := tableToAst(table, expectedAst.Resources[0].Bucket) - - if actualAst.Resources[0].Name() == expectedAst.Resources[0].Name() { - require.Equal(t, expectedAst, actualAst) - } else { - require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources)) - require.Equal(t, expectedAst.Resources[0], actualAst.Resources[1]) - require.Equal(t, expectedAst.Resources[1], actualAst.Resources[0]) - } -} - -func TestPolicyToAst(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - policy := &bucketPolicy{ - Statement: []statement{ - { - Effect: "Allow", - Principal: principal{AWS: allUsersWildcard}, - Action: []string{"s3:PutObject"}, - Resource: []string{"arn:aws:s3:::bucketName"}, - }, - { - Effect: "Deny", - Principal: principal{ - CanonicalUser: hex.EncodeToString(key.PublicKey().Bytes()), - }, - Action: []string{"s3:GetObject"}, - Resource: []string{"arn:aws:s3:::bucketName/object"}, - }}, - } - policy.Bucket = "bucketName" - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - }, - Operations: []*astOperation{{ - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }}, - }, - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - Object: "object", - }, - Operations: getReadOps(key, false, eacl.ActionDeny), - }, - }, - } - - actualAst, err := policyToAst(policy) - require.NoError(t, err) - - if actualAst.Resources[0].Name() == expectedAst.Resources[0].Name() { - require.Equal(t, expectedAst, actualAst) - } else { - require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources)) - require.Equal(t, expectedAst.Resources[0], actualAst.Resources[1]) - require.Equal(t, expectedAst.Resources[1], actualAst.Resources[0]) - } -} - -func getReadOps(key *keys.PrivateKey, groupGrantee bool, action eacl.Action) []*astOperation { - var ( - result []*astOperation - users []string - ) - if !groupGrantee { - users = append(users, hex.EncodeToString(key.PublicKey().Bytes())) - } - - for _, op := range readOps { - result = append(result, &astOperation{ - Users: users, - Op: op, - Action: action, - }) - } - - return result -} - -func TestMergeAstUnModified(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{hex.EncodeToString(key.PublicKey().Bytes())}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{{ - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }}, - }, - child.Resources[0], - }, - } - - result, updated := mergeAst(parent, child) - require.False(t, updated) - require.Equal(t, parent, result) -} - -func TestMergeAstModified(t *testing.T) { - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user2"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{"user1"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - expected := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - child.Resources[0].Operations[0], - { - Users: []string{"user1", "user2"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - }, - }, - }, - } - - actual, updated := mergeAst(parent, child) - require.True(t, updated) - require.Equal(t, expected, actual) -} - -func TestMergeAppended(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - users := []string{hex.EncodeToString(key.PublicKey().Bytes())} - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionDeny, - }, - }, - }, - }, - } - - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - }, - }, - }, - } - - expected := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionDeny, - }, - }, - }, - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - }, - }, - }, - } - actual, updated := mergeAst(parent, child) - require.True(t, updated) - require.Equal(t, expected, actual) -} - -func TestOrder(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - users := []string{hex.EncodeToString(key.PublicKey().Bytes())} - targetUser := eacl.NewTarget() - targetUser.SetBinaryKeys([][]byte{key.PublicKey().Bytes()}) - targetOther := eacl.NewTarget() - targetOther.SetRole(eacl.RoleOthers) - bucketName := "bucket" - objectName := "objectName" - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: bucketName, - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - }, - }, - { - resourceInfo: resourceInfo{ - Bucket: bucketName, - Object: objectName, - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, - }, - }, - }, - } - bucketServiceRec := &ServiceRecord{Resource: expectedAst.Resources[0].Name(), GroupRecordsLength: 2} - bucketUsersGetRec := eacl.NewRecord() - bucketUsersGetRec.SetOperation(eacl.OperationGet) - bucketUsersGetRec.SetAction(eacl.ActionAllow) - bucketUsersGetRec.SetTargets(*targetUser) - bucketOtherGetRec := eacl.NewRecord() - bucketOtherGetRec.SetOperation(eacl.OperationGet) - bucketOtherGetRec.SetAction(eacl.ActionDeny) - bucketOtherGetRec.SetTargets(*targetOther) - objectServiceRec := &ServiceRecord{Resource: expectedAst.Resources[1].Name(), GroupRecordsLength: 2} - objectUsersPutRec := eacl.NewRecord() - objectUsersPutRec.SetOperation(eacl.OperationPut) - objectUsersPutRec.SetAction(eacl.ActionAllow) - objectUsersPutRec.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, objectName) - objectUsersPutRec.SetTargets(*targetUser) - objectOtherPutRec := eacl.NewRecord() - objectOtherPutRec.SetOperation(eacl.OperationPut) - objectOtherPutRec.SetAction(eacl.ActionDeny) - objectOtherPutRec.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, objectName) - objectOtherPutRec.SetTargets(*targetOther) - - expectedEacl := eacl.NewTable() - expectedEacl.AddRecord(objectServiceRec.ToEACLRecord()) - expectedEacl.AddRecord(objectOtherPutRec) - expectedEacl.AddRecord(objectUsersPutRec) - expectedEacl.AddRecord(bucketServiceRec.ToEACLRecord()) - expectedEacl.AddRecord(bucketOtherGetRec) - expectedEacl.AddRecord(bucketUsersGetRec) - - t.Run("astToTable order and vice versa", func(t *testing.T) { - actualEacl, err := astToTable(expectedAst) - require.NoError(t, err) - require.Equal(t, expectedEacl, actualEacl) - - actualAst := tableToAst(actualEacl, bucketName) - require.Equal(t, expectedAst, actualAst) - }) - - t.Run("tableToAst order and vice versa", func(t *testing.T) { - actualAst := tableToAst(expectedEacl, bucketName) - require.Equal(t, expectedAst, actualAst) - - actualEacl, err := astToTable(actualAst) - require.NoError(t, err) - require.Equal(t, expectedEacl, actualEacl) - }) - - t.Run("append a resource", func(t *testing.T) { - childName := "child" - child := &ast{Resources: []*astResource{{ - resourceInfo: resourceInfo{ - Bucket: bucketName, - Object: childName, - }, - Operations: []*astOperation{{Op: eacl.OperationDelete, Action: eacl.ActionDeny}}}}, - } - - childRecord := eacl.NewRecord() - childRecord.SetOperation(eacl.OperationDelete) - childRecord.SetAction(eacl.ActionDeny) - childRecord.SetTargets(*targetOther) - childRecord.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, childName) - - mergedAst, updated := mergeAst(expectedAst, child) - require.True(t, updated) - - mergedEacl, err := astToTable(mergedAst) - require.NoError(t, err) - - require.Equal(t, *childRecord, mergedEacl.Records()[1]) - }) -} - -func TestMergeAstModifiedConflict(t *testing.T) { - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{"user1"}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user3"}, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }}, - }, - }, - } - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{"user1"}, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, { - Users: []string{"user2"}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user3"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - expected := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - { - Users: []string{"user2", "user1"}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user3"}, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - }, - }, - }, - } - - actual, updated := mergeAst(parent, child) - require.True(t, updated) - require.Equal(t, expected, actual) -} - -func TestAstToTable(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - ast := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - }, - Operations: []*astOperation{{ - Users: []string{hex.EncodeToString(key.PublicKey().Bytes())}, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }}, - }, - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - Object: "objectName", - }, - Operations: []*astOperation{{ - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - expectedTable := eacl.NewTable() - serviceRec1 := &ServiceRecord{Resource: ast.Resources[0].Name(), GroupRecordsLength: 1} - record1 := eacl.NewRecord() - record1.SetAction(eacl.ActionAllow) - record1.SetOperation(eacl.OperationPut) - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record1, eacl.RoleUnknown, *(*ecdsa.PublicKey)(key.PublicKey())) - - serviceRec2 := &ServiceRecord{Resource: ast.Resources[1].Name(), GroupRecordsLength: 1} - record2 := eacl.NewRecord() - record2.SetAction(eacl.ActionDeny) - record2.SetOperation(eacl.OperationGet) - eacl.AddFormedTarget(record2, eacl.RoleOthers) - record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, "objectName") - - expectedTable.AddRecord(serviceRec2.ToEACLRecord()) - expectedTable.AddRecord(record2) - expectedTable.AddRecord(serviceRec1.ToEACLRecord()) - expectedTable.AddRecord(record1) - - actualTable, err := astToTable(ast) - require.NoError(t, err) - require.Equal(t, expectedTable, actualTable) -} - -func TestRemoveUsers(t *testing.T) { - resource := &astResource{ - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{{ - Users: []string{"user1", "user3", "user4"}, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: []string{"user5"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - }, - } - - op1 := &astOperation{ - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - } - op2 := &astOperation{ - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - } - - removeUsers(resource, op1, []string{"user1", "user2", "user4"}) // modify astOperation - removeUsers(resource, op2, []string{"user5"}) // remove astOperation - - require.Equal(t, len(resource.Operations), 1) - require.Equal(t, []string{"user3"}, resource.Operations[0].Users) -} - -func TestBucketAclToPolicy(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - key2, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - id2 := hex.EncodeToString(key2.PublicKey().Bytes()) - - acl := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: "user1", - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: id2, - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }}, - } - - resInfo := &resourceInfo{ - Bucket: "bucketName", - } - - expectedPolicy := &bucketPolicy{ - Bucket: resInfo.Bucket, - Statement: []statement{ - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id, - }, - Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads", "s3:PutObject", "s3:DeleteObject"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{AWS: allUsersWildcard}, - Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id2, - }, - Action: []string{"s3:PutObject", "s3:DeleteObject"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - }, - } - - actualPolicy, err := aclToPolicy(acl, resInfo) - require.NoError(t, err) - require.Equal(t, expectedPolicy, actualPolicy) -} - -func TestObjectAclToPolicy(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - key2, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - id2 := hex.EncodeToString(key2.PublicKey().Bytes()) - - acl := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: "user1", - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - ID: id2, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }}, - } - - resInfo := &resourceInfo{ - Bucket: "bucketName", - Object: "object", - } - - expectedPolicy := &bucketPolicy{ - Bucket: resInfo.Bucket, - Statement: []statement{ - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id, - }, - Action: []string{"s3:GetObject", "s3:GetObjectVersion"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id2, - }, - Action: []string{"s3:GetObject", "s3:GetObjectVersion"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{AWS: allUsersWildcard}, - Action: []string{"s3:GetObject", "s3:GetObjectVersion"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - }, - } - - actualPolicy, err := aclToPolicy(acl, resInfo) - require.NoError(t, err) - require.Equal(t, expectedPolicy, actualPolicy) -} - -func TestObjectWithVersionAclToTable(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - id := hex.EncodeToString(key.PublicKey().Bytes()) - - acl := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: "user1", - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }}, - } - - resInfoObject := &resourceInfo{ - Bucket: "bucketName", - Object: "object", - } - expectedTable := allowedTableForPrivateObject(t, key, resInfoObject) - actualTable := tableFromACL(t, acl, resInfoObject) - checkTables(t, expectedTable, actualTable) - - resInfoObjectVersion := &resourceInfo{ - Bucket: "bucketName", - Object: "objectVersion", - Version: "Gfrct4Afhio8pCGCCKVNTf1kyexQjMBeaUfvDtQCkAvg", - } - expectedTable = allowedTableForPrivateObject(t, key, resInfoObjectVersion) - actualTable = tableFromACL(t, acl, resInfoObjectVersion) - checkTables(t, expectedTable, actualTable) -} - -func allowedTableForPrivateObject(t *testing.T, key *keys.PrivateKey, resInfo *resourceInfo) *eacl.Table { - var isVersion bool - var objID oid.ID - if resInfo.Version != "" { - isVersion = true - err := objID.DecodeString(resInfo.Version) - require.NoError(t, err) - } - - expectedTable := eacl.NewTable() - serviceRec := &ServiceRecord{Resource: resInfo.Name(), GroupRecordsLength: len(readOps) * 2} - expectedTable.AddRecord(serviceRec.ToEACLRecord()) - - for i := len(readOps) - 1; i >= 0; i-- { - op := readOps[i] - record := getAllowRecord(op, key.PublicKey()) - if isVersion { - record.AddObjectIDFilter(eacl.MatchStringEqual, objID) - } else { - record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, resInfo.Object) - } - expectedTable.AddRecord(record) - } - for i := len(readOps) - 1; i >= 0; i-- { - op := readOps[i] - record := getOthersRecord(op, eacl.ActionDeny) - if isVersion { - record.AddObjectIDFilter(eacl.MatchStringEqual, objID) - } else { - record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, resInfo.Object) - } - expectedTable.AddRecord(record) - } - - return expectedTable -} - -func tableFromACL(t *testing.T, acl *AccessControlPolicy, resInfo *resourceInfo) *eacl.Table { - actualPolicy, err := aclToPolicy(acl, resInfo) - require.NoError(t, err) - actualAst, err := policyToAst(actualPolicy) - require.NoError(t, err) - actualTable, err := astToTable(actualAst) - require.NoError(t, err) - return actualTable -} - -func checkTables(t *testing.T, expectedTable, actualTable *eacl.Table) { - require.Equal(t, len(expectedTable.Records()), len(actualTable.Records()), "different number of records") - for i, record := range expectedTable.Records() { - actRecord := actualTable.Records()[i] - - require.Equal(t, len(record.Targets()), len(actRecord.Targets()), "different number of targets") - for j, target := range record.Targets() { - actTarget := actRecord.Targets()[j] - - expected := fmt.Sprintf("%s %v", target.Role().String(), target.BinaryKeys()) - actual := fmt.Sprintf("%s %v", actTarget.Role().String(), actTarget.BinaryKeys()) - require.Equalf(t, target, actTarget, "want: '%s'\ngot: '%s'", expected, actual) - } - - require.Equal(t, len(record.Filters()), len(actRecord.Filters()), "different number of filters") - for j, filter := range record.Filters() { - actFilter := actRecord.Filters()[j] - - expected := fmt.Sprintf("%s:%s %s %s", filter.From().String(), filter.Key(), filter.Matcher().String(), filter.Value()) - actual := fmt.Sprintf("%s:%s %s %s", actFilter.From().String(), actFilter.Key(), actFilter.Matcher().String(), actFilter.Value()) - require.Equalf(t, filter, actFilter, "want: '%s'\ngot: '%s'", expected, actual) - } - - require.Equal(t, record.Action().String(), actRecord.Action().String()) - require.Equal(t, record.Operation().String(), actRecord.Operation().String()) - } -} - -func TestParseCannedACLHeaders(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - address := key.PublicKey().Address() - - req := &http.Request{ - Header: map[string][]string{ - api.AmzACL: {basicACLReadOnly}, - }, - } - - expectedACL := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: address, - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - DisplayName: address, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }}, - } - - actualACL, err := parseACLHeaders(req.Header, key.PublicKey()) - require.NoError(t, err) - require.Equal(t, expectedACL, actualACL) -} - -func TestParseACLHeaders(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - address := key.PublicKey().Address() - - req := &http.Request{ - Header: map[string][]string{ - api.AmzGrantFullControl: {"id=\"user1\""}, - api.AmzGrantRead: {"uri=\"" + allUsersGroup + "\", id=\"user2\""}, - api.AmzGrantWrite: {"id=\"user2\", id=\"user3\""}, - }, - } - - expectedACL := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: address, - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - DisplayName: address, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - ID: "user1", - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: "user2", - Type: acpCanonicalUser, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: "user2", - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }, { - Grantee: &Grantee{ - ID: "user3", - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }}, - } - - actualACL, err := parseACLHeaders(req.Header, key.PublicKey()) - require.NoError(t, err) - require.Equal(t, expectedACL, actualACL) -} - -func TestAddGranteeError(t *testing.T) { - headers := map[string][]string{ - api.AmzGrantFullControl: {"i=\"user1\""}, - api.AmzGrantRead: {"uri, id=\"user2\""}, - api.AmzGrantWrite: {"emailAddress=\"user2\""}, - "unknown header": {"something"}, - } - - expectedList := []*Grant{{ - Permission: "predefined", - }} - - actualList, err := addGrantees(expectedList, headers, "unknown header1") - require.NoError(t, err) - require.Equal(t, expectedList, actualList) - - actualList, err = addGrantees(expectedList, headers, "unknown header") - require.Error(t, err) - require.Nil(t, actualList) - - actualList, err = addGrantees(expectedList, headers, api.AmzGrantFullControl) - require.Error(t, err) - require.Nil(t, actualList) - - actualList, err = addGrantees(expectedList, headers, api.AmzGrantRead) - require.Error(t, err) - require.Nil(t, actualList) - - actualList, err = addGrantees(expectedList, headers, api.AmzGrantWrite) - require.Error(t, err) - require.Nil(t, actualList) -} - -func TestBucketAclToTable(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - key2, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - id2 := hex.EncodeToString(key2.PublicKey().Bytes()) - - acl := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: "user1", - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: id2, - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }}, - } - - expectedTable := new(eacl.Table) - for _, op := range readOps { - expectedTable.AddRecord(getOthersRecord(op, eacl.ActionAllow)) - } - for _, op := range writeOps { - expectedTable.AddRecord(getAllowRecord(op, key2.PublicKey())) - } - for _, op := range fullOps { - expectedTable.AddRecord(getAllowRecord(op, key.PublicKey())) - } - for _, op := range fullOps { - expectedTable.AddRecord(getOthersRecord(op, eacl.ActionDeny)) - } - resInfo := &resourceInfo{ - Bucket: "bucketName", - } - - actualTable, err := bucketACLToTable(acl, resInfo) - require.NoError(t, err) - require.Equal(t, expectedTable.Records(), actualTable.Records()) -} - -func TestObjectAclToAst(t *testing.T) { - b := make([]byte, 32) - _, err := io.ReadFull(rand.Reader, b) - require.NoError(t, err) - var objID oid.ID - objID.SetSHA256(sha256.Sum256(b)) - - key, err := keys.NewPrivateKey() - require.NoError(t, err) - key2, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - id2 := hex.EncodeToString(key2.PublicKey().Bytes()) - - acl := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: "user1", - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - ID: id2, - Type: acpCanonicalUser, - }, - Permission: aclRead, - }, - }, - } - - resInfo := &resourceInfo{ - Bucket: "bucketName", - Object: "object", - Version: objID.EncodeToString(), - } - - var operations []*astOperation - for _, op := range readOps { - astOp := &astOperation{Users: []string{ - hex.EncodeToString(key.PublicKey().Bytes()), - hex.EncodeToString(key2.PublicKey().Bytes()), - }, - Op: op, - Action: eacl.ActionAllow, - } - operations = append(operations, astOp) - } - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: *resInfo, - Operations: operations, - }, - }, - } - - actualAst, err := aclToAst(acl, resInfo) - require.NoError(t, err) - require.Equal(t, expectedAst, actualAst) -} - -func TestBucketAclToAst(t *testing.T) { - b := make([]byte, 32) - _, err := io.ReadFull(rand.Reader, b) - require.NoError(t, err) - var objID oid.ID - objID.SetSHA256(sha256.Sum256(b)) - - key, err := keys.NewPrivateKey() - require.NoError(t, err) - key2, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - id2 := hex.EncodeToString(key2.PublicKey().Bytes()) - - acl := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: "user1", - }, - AccessControlList: []*Grant{ - { - Grantee: &Grantee{ - ID: id2, - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }, { - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }, - }, - } - - var operations []*astOperation - for _, op := range readOps { - astOp := &astOperation{Users: []string{ - hex.EncodeToString(key.PublicKey().Bytes()), - }, - Op: op, - Action: eacl.ActionAllow, - } - operations = append(operations, astOp) - } - for _, op := range writeOps { - astOp := &astOperation{Users: []string{ - hex.EncodeToString(key.PublicKey().Bytes()), - hex.EncodeToString(key2.PublicKey().Bytes()), - }, - Op: op, - Action: eacl.ActionAllow, - } - operations = append(operations, astOp) - } - for _, op := range readOps { - astOp := &astOperation{ - Op: op, - Action: eacl.ActionAllow, - } - operations = append(operations, astOp) - } - - resInfo := &resourceInfo{Bucket: "bucketName"} - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: *resInfo, - Operations: operations, - }, - }, - } - - actualAst, err := aclToAst(acl, resInfo) - require.NoError(t, err) - require.Equal(t, expectedAst, actualAst) -} - -func TestPutBucketAPE(t *testing.T) { - hc := prepareHandlerContext(t) - bktName := "bucket-for-acl-ape" - - info := createBucket(hc, bktName) - - _, err := hc.tp.ContainerEACL(hc.Context(), layer.PrmContainerEACL{ContainerID: info.BktInfo.CID}) - require.ErrorContains(t, err, "not found") - - chains, err := hc.h.ape.(*apeMock).ListChains(engine.ContainerTarget(info.BktInfo.CID.EncodeToString())) - require.NoError(t, err) - require.Len(t, chains, 2) -} - func TestPutObjectACLErrorAPE(t *testing.T) { hc := prepareHandlerContext(t) bktName, objName := "bucket-for-acl-ape", "object" @@ -1492,58 +199,6 @@ func TestDeleteBucketWithPolicy(t *testing.T) { require.Empty(t, chains) } -func TestBucketPolicyUnmarshal(t *testing.T) { - for _, tc := range []struct { - name string - policy string - }{ - { - name: "action/resource array", - policy: ` -{ - "Version": "2012-10-17", - "Statement": [{ - "Principal": { - "AWS": "arn:aws:iam::111122223333:role/JohnDoe" - }, - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:GetObjectVersion" - ], - "Resource": [ - "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*", - "arn:aws:s3:::DOC-EXAMPLE-BUCKET2/*" - ] - }] -} -`, - }, - { - name: "action/resource string", - policy: ` -{ - "Version": "2012-10-17", - "Statement": [{ - "Principal": { - "AWS": "arn:aws:iam::111122223333:role/JohnDoe" - }, - "Effect": "Deny", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*" - }] -} -`, - }, - } { - t.Run(tc.name, func(t *testing.T) { - bktPolicy := &bucketPolicy{} - err := json.Unmarshal([]byte(tc.policy), bktPolicy) - require.NoError(t, err) - }) - } -} - func TestPutBucketPolicy(t *testing.T) { bktPolicy := ` { @@ -1621,18 +276,13 @@ func createAccessBox(t *testing.T) (*accessbox.Box, *keys.PrivateKey) { require.NoError(t, err) tok := new(session.Container) - tok.ForVerb(session.VerbContainerSetEACL) + tok.ForVerb(session.VerbContainerPut) err = tok.Sign(key.PrivateKey) require.NoError(t, err) - tok2 := new(session.Container) - tok2.ForVerb(session.VerbContainerPut) - err = tok2.Sign(key.PrivateKey) - require.NoError(t, err) - box := &accessbox.Box{ Gate: &accessbox.GateData{ - SessionTokens: []*session.Container{tok, tok2}, + SessionTokens: []*session.Container{tok}, BearerToken: &bearerToken, }, } diff --git a/api/handler/copy.go b/api/handler/copy.go index fbd931c..0f2e5b3 100644 --- a/api/handler/copy.go +++ b/api/handler/copy.go @@ -13,7 +13,6 @@ import ( "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/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "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) { var ( - err error - versionID string - metadata map[string]string - tagSet map[string]string - sessionTokenEACL *session.Container + err error + versionID string + metadata map[string]string + tagSet map[string]string ctx = r.Context() reqInfo = middleware.GetReqInfo(ctx) @@ -93,20 +91,11 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { return } - apeEnabled := dstBktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if cannedACLStatus == aclStatusYes { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) 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) if err != nil { h.logAndSendError(w, "could not find object", reqInfo, err) @@ -239,25 +228,6 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { 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 { tagPrm := &data.PutObjectTaggingParams{ ObjectVersion: &data.ObjectVersion{ diff --git a/api/handler/head_test.go b/api/handler/head_test.go index fc68a17..2225c96 100644 --- a/api/handler/head_test.go +++ b/api/handler/head_test.go @@ -7,12 +7,9 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" 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-sdk-go/bearer" 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/stretchr/testify/require" ) @@ -84,31 +81,6 @@ func headObject(t *testing.T, tc *handlerContext, bktName, objName string, heade 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) { hc := prepareHandlerContextWithMinCache(t) bktName, objName := "bucket", "obj" @@ -155,7 +127,7 @@ func newTestAccessBox(t *testing.T, key *keys.PrivateKey) *accessbox.Box { } var btoken bearer.Token - btoken.SetEACLTable(*eacl.NewTable()) + btoken.SetImpersonate(true) err = btoken.Sign(key.PrivateKey) require.NoError(t, err) diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index 169f776..d0a42fb 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -112,14 +112,7 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re return } - settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) - if err != nil { - h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err) - return - } - - apeEnabled := bktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if cannedACLStatus == aclStatusYes { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) return } @@ -133,20 +126,6 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re 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 { p.Data.TagSet, err = parseTaggingHeader(r.Header) 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) { 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 } diff --git a/api/handler/put.go b/api/handler/put.go index 050596b..0f71e7e 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -28,8 +28,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/retryer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree" 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/schema/native" "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. const ( - basicACLPrivate = "private" - basicACLReadOnly = "public-read" - basicACLPublic = "public-read-write" - cannedACLAuthRead = "authenticated-read" + basicACLPrivate = "private" + basicACLReadOnly = "public-read" + basicACLPublic = "public-read-write" ) type createBucketParams struct { @@ -186,12 +183,10 @@ type createBucketParams struct { func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { var ( - err error - newEaclTable *eacl.Table - sessionTokenEACL *session.Container - cannedACLStatus = aclHeadersStatus(r) - ctx = r.Context() - reqInfo = middleware.GetReqInfo(ctx) + err error + cannedACLStatus = aclHeadersStatus(r) + ctx = r.Context() + reqInfo = middleware.GetReqInfo(ctx) ) bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) @@ -206,20 +201,11 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { return } - apeEnabled := bktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if cannedACLStatus == aclStatusYes { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) 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) if err != nil { 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 - 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 { tagPrm := &data.PutObjectTaggingParams{ 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() { 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) { var ( - newEaclTable *eacl.Table - tagSet map[string]string - sessionTokenEACL *session.Container - ctx = r.Context() - reqInfo = middleware.GetReqInfo(ctx) - metadata = make(map[string]string) - cannedACLStatus = aclHeadersStatus(r) + tagSet map[string]string + ctx = r.Context() + reqInfo = middleware.GetReqInfo(ctx) + metadata = make(map[string]string) ) policy, err := checkPostPolicy(r, reqInfo, metadata) @@ -501,20 +464,11 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { return } - apeEnabled := bktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if acl := auth.MultipartFormValue(r, "acl"); acl != "" && acl != basicACLPrivate { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) 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 size uint64 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 - 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 { tagPrm := &data.PutObjectTaggingParams{ 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() { w.Header().Set(api.AmzVersionID, objInfo.VersionID()) } @@ -716,56 +645,6 @@ func aclHeadersStatus(r *http.Request) aclStatus { 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) { var tagSet map[string]string if tagging := header.Get(api.AmzTagging); len(tagging) > 0 { @@ -805,8 +684,7 @@ func parseCannedACL(header http.Header) (string, error) { return basicACLPrivate, nil } - if acl == basicACLPrivate || acl == basicACLPublic || - acl == cannedACLAuthRead || acl == basicACLReadOnly { + if acl == basicACLPrivate || acl == basicACLPublic || acl == basicACLReadOnly { return acl, nil } @@ -873,7 +751,6 @@ func (h *handler) createBucketHandlerPolicy(w http.ResponseWriter, r *http.Reque return } - p.APEEnabled = true bktInfo, err := h.obj.CreateBucket(ctx, p) if err != nil { h.logAndSendError(w, "could not create bucket", reqInfo, err) @@ -990,8 +867,6 @@ func bucketCannedACLToAPERules(cannedACL string, reqInfo *middleware.ReqInfo, cn switch cannedACL { case basicACLPrivate: - case cannedACLAuthRead: - fallthrough case basicACLReadOnly: chains[0].Rules = append(chains[0].Rules, chain.Rule{ Status: chain.Allow, diff --git a/api/handler/util.go b/api/handler/util.go index 41fe57a..dde5274 100644 --- a/api/handler/util.go +++ b/api/handler/util.go @@ -16,7 +16,6 @@ import ( frosterrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) @@ -142,16 +141,3 @@ func parseRange(s string) (*layer.RangeParams, error) { End: values[1], }, 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 -} diff --git a/api/layer/container.go b/api/layer/container.go index 9077053..6c45da3 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -12,21 +12,9 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "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" ) -type ( - // BucketACL extends BucketInfo by eacl.Table. - BucketACL struct { - Info *data.BucketInfo - EACL *eacl.Table - } -) - const ( attributeLocationConstraint = ".s3-location-constraint" AttributeLockEnabled = "LockEnabled" @@ -64,7 +52,6 @@ func (n *Layer) containerInfo(ctx context.Context, prm PrmContainer) (*data.Buck info.Created = container.CreatedAt(cnr) info.LocationConstraint = cnr.Attribute(attributeLocationConstraint) info.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(cnr) - info.APEEnabled = cnr.BasicACL().Bits() == 0 attrLockEnabled := cnr.Attribute(AttributeLockEnabled) if len(attrLockEnabled) > 0 { @@ -133,7 +120,6 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da Created: TimeNow(ctx), LocationConstraint: p.LocationConstraint, ObjectLockEnabled: p.ObjectLockEnabled, - APEEnabled: p.APEEnabled, } 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{ Creator: bktInfo.Owner, Policy: p.Policy, @@ -159,7 +140,7 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da SessionToken: p.SessionContainerCreation, CreationTime: bktInfo.Created, AdditionalAttributes: attributes, - BasicACL: basicACL, + BasicACL: 0, // means APE }) if err != nil { 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 } - -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) -} diff --git a/api/layer/frostfs.go b/api/layer/frostfs.go index ce9fa45..b3c2c87 100644 --- a/api/layer/frostfs.go +++ b/api/layer/frostfs.go @@ -11,7 +11,6 @@ import ( "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/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" @@ -64,15 +63,6 @@ type PrmUserContainers struct { 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. type ContainerCreateResult struct { ContainerID cid.ID @@ -216,18 +206,6 @@ type FrostFS interface { // prevented the containers from being listed. 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. // Request is sent within session if the session token is specified. // Successful return does not guarantee actual removal. diff --git a/api/layer/frostfs_mock.go b/api/layer/frostfs_mock.go index ff7f3f9..d360020 100644 --- a/api/layer/frostfs_mock.go +++ b/api/layer/frostfs_mock.go @@ -5,13 +5,11 @@ import ( "context" "crypto/rand" "crypto/sha256" - "errors" "fmt" "io" "strings" "time" - "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" @@ -20,7 +18,6 @@ import ( apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" @@ -68,7 +65,6 @@ type TestFrostFS struct { objectErrors map[string]error objectPutErrors map[string]error containers map[string]*container.Container - eaclTables map[string]*eacl.Table currentEpoch uint64 key *keys.PrivateKey } @@ -79,7 +75,6 @@ func NewTestFrostFS(key *keys.PrivateKey) *TestFrostFS { objectErrors: make(map[string]error), objectPutErrors: make(map[string]error), containers: make(map[string]*container.Container), - eaclTables: make(map[string]*eacl.Table), key: key, } } @@ -222,7 +217,7 @@ func (t *TestFrostFS) ReadObject(ctx context.Context, prm PrmObjectRead) (*Objec if obj, ok := t.objects[sAddr]; ok { owner := getBearerOwner(ctx) - if !t.checkAccess(prm.Container, owner, eacl.OperationGet, obj) { + if !t.checkAccess(prm.Container, owner) { return nil, ErrAccessDenied } @@ -324,9 +319,9 @@ func (t *TestFrostFS) DeleteObject(ctx context.Context, prm PrmObjectDelete) err return err } - if obj, ok := t.objects[addr.EncodeToString()]; ok { + if _, ok := t.objects[addr.EncodeToString()]; ok { owner := getBearerOwner(ctx) - if !t.checkAccess(prm.Container, owner, eacl.OperationDelete, obj) { + if !t.checkAccess(prm.Container, owner) { return ErrAccessDenied } @@ -354,30 +349,6 @@ func (t *TestFrostFS) AllObjects(cnrID cid.ID) []oid.ID { 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) { filters := object.NewSearchFilters() filters.AddRootFilter() @@ -415,7 +386,7 @@ func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]o 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()] if !ok { return false @@ -425,57 +396,6 @@ func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID, op eacl.Operation 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 } diff --git a/api/layer/layer.go b/api/layer/layer.go index ece8884..c57d3df 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -22,7 +22,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" 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" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" @@ -159,13 +158,6 @@ type ( SessionContainerCreation *session.Container LocationConstraint string 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 struct { @@ -350,24 +342,6 @@ func (n *Layer) ResolveCID(ctx context.Context, name string) (cid.ID, error) { 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 // id. Timestamp is omitted since it is not saved in frostfs container. func (n *Layer) ListBuckets(ctx context.Context) ([]*data.BucketInfo, error) { diff --git a/api/layer/multipart_upload.go b/api/layer/multipart_upload.go index 6c89d90..aeb6759 100644 --- a/api/layer/multipart_upload.go +++ b/api/layer/multipart_upload.go @@ -36,7 +36,6 @@ const ( MultipartObjectSize = "S3-Multipart-Object-Size" metaPrefix = "meta-" - aclPrefix = "acl-" MaxSizeUploadsList = 1000 MaxSizePartsList = 1000 @@ -62,8 +61,7 @@ type ( } UploadData struct { - TagSet map[string]string - ACLHeaders map[string]string + TagSet map[string]string } UploadPartParams struct { @@ -149,7 +147,6 @@ type ( func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartParams) error { metaSize := len(p.Header) if p.Data != nil { - metaSize += len(p.Data.ACLHeaders) metaSize += len(p.Data.TagSet) } @@ -167,10 +164,6 @@ func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartPar } if p.Data != nil { - for key, val := range p.Data.ACLHeaders { - info.Meta[aclPrefix+key] = val - } - for key, val := range p.Data.TagSet { info.Meta[tagPrefix+key] = val } @@ -432,16 +425,13 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar initMetadata[MultipartObjectSize] = strconv.FormatUint(multipartObjetSize, 10) uploadData := &UploadData{ - TagSet: make(map[string]string), - ACLHeaders: make(map[string]string), + TagSet: make(map[string]string), } for key, val := range multipartInfo.Meta { if strings.HasPrefix(key, metaPrefix) { initMetadata[strings.TrimPrefix(key, metaPrefix)] = val } else if strings.HasPrefix(key, tagPrefix) { uploadData.TagSet[strings.TrimPrefix(key, tagPrefix)] = val - } else if strings.HasPrefix(key, aclPrefix) { - uploadData.ACLHeaders[strings.TrimPrefix(key, aclPrefix)] = val } } diff --git a/api/middleware/policy.go b/api/middleware/policy.go index 2786535..eaf9ffe 100644 --- a/api/middleware/policy.go +++ b/api/middleware/policy.go @@ -149,12 +149,7 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error { return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String())) } - isAPE := true - if bktInfo != nil { - isAPE = bktInfo.APEEnabled - } - - if isAPE && cfg.Settings.PolicyDenyByDefault() { + if cfg.Settings.PolicyDenyByDefault() { return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String())) } diff --git a/api/router_mock_test.go b/api/router_mock_test.go index 9ecd947..c262023 100644 --- a/api/router_mock_test.go +++ b/api/router_mock_test.go @@ -424,8 +424,7 @@ func (h *handlerMock) CreateBucketHandler(w http.ResponseWriter, r *http.Request reqInfo := middleware.GetReqInfo(r.Context()) h.buckets[reqInfo.Namespace+reqInfo.BucketName] = &data.BucketInfo{ - Name: reqInfo.BucketName, - APEEnabled: true, + Name: reqInfo.BucketName, } res := &handlerResult{ diff --git a/api/router_test.go b/api/router_test.go index b341012..48cf459 100644 --- a/api/router_test.go +++ b/api/router_test.go @@ -17,9 +17,7 @@ import ( apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics" - cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" - usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" @@ -305,58 +303,6 @@ func TestDefaultPolicyCheckerWithUserTags(t *testing.T) { 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.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.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) - }) -} - func TestRequestParametersCheck(t *testing.T) { t.Run("prefix parameter, allow specific value", func(t *testing.T) { router := prepareRouter(t) @@ -679,28 +625,6 @@ func addPolicy(router *routerMock, ns string, id string, effect engineiam.Effect 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) { - router.handler.buckets[bktName] = &data.BucketInfo{ - Name: bktName, - Zone: "container", - CID: cidtest.ID(), - Owner: usertest.ID(), - Created: time.Now(), - LocationConstraint: "default", - ObjectLockEnabled: false, - HomomorphicHashDisabled: false, - APEEnabled: !old, - } -} - func createBucket(router *routerMock, namespace, bktName string) { w := createBucketBase(router, namespace, bktName, nil) resp := readResponse(router.t, w) @@ -723,24 +647,6 @@ func createBucketBase(router *routerMock, namespace, bktName string, header http 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) { w := getBucketBase(router, namespace, bktName) assertAPIError(router.t, w, errCode) diff --git a/creds/accessbox/accessbox.go b/creds/accessbox/accessbox.go index 3c02723..690c171 100644 --- a/creds/accessbox/accessbox.go +++ b/creds/accessbox/accessbox.go @@ -54,11 +54,6 @@ func (g *GateData) SessionTokenForDelete() *session.Container { 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. func (g *GateData) SessionToken() *session.Container { 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 { switch verb { - case session.VerbContainerSetEACL, session.VerbContainerDelete, session.VerbContainerPut: + case session.VerbContainerDelete, session.VerbContainerPut: return tok.AssertVerb(verb) default: return false diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/bearer_token_test.go index 593332f..b6ee0e0 100644 --- a/creds/accessbox/bearer_token_test.go +++ b/creds/accessbox/bearer_token_test.go @@ -205,18 +205,6 @@ func TestGateDataSessionToken(t *testing.T) { 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) { gate.SessionTokens = []*session.Container{} sessionTkn := gate.SessionToken() diff --git a/internal/frostfs/frostfs.go b/internal/frostfs/frostfs.go index 3948cbd..a6b3214 100644 --- a/internal/frostfs/frostfs.go +++ b/internal/frostfs/frostfs.go @@ -15,7 +15,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" @@ -162,29 +161,6 @@ func (x *FrostFS) UserContainers(ctx context.Context, layerPrm layer.PrmUserCont 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. func (x *FrostFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.Container) error { prm := pool.PrmContainerDelete{ContainerID: id, Session: token, WaitParams: &x.await} -- 2.40.1