Compare commits

..

10 commits

Author SHA1 Message Date
431ed829b5 [#xx] docs: Update description for object.get.priority
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2024-11-12 13:31:13 +03:00
b543569c3f [#1486] node: Introduce dual service support
* Register GRPC services for both neo.fs.v2 and frost.fs namespaces
* Use this temporary solution until all nodes are updated

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-11-12 08:43:47 +00:00
80f8a8fd3a
[#1396] cli/playground: Refactor
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-12 11:09:27 +03:00
2f3bc6eb84
[#1396] cli/playground: Improve terminal control key handling
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-12 11:07:48 +03:00
8a57c78f5f
[#1484] engine: Fix engine metrics
1. Add forgotten metrics for client requests
2. Include execIfNotBlocked into metrics

Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2024-11-11 12:59:20 +03:00
ad01fb958a [#1474] Stop using obsolete .github directory
This commit is a part of multi-repo cleanup effort:
TrueCloudLab/frostfs-infra#136

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-11-11 06:44:13 +00:00
d336f2d487 [#1393] adm: Make NewLocalActor receive accout name
* Some RPC-clients for contracts require different wallet account types.
  Since, `Policy` contract gets `consensus` accounts while `NNS` gets
  `committee` accounts.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-11-08 13:57:51 +00:00
c82c753e9f [#1480] objsvc: Remove useless stream wrappers
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-11-08 12:01:14 +00:00
f666898e5d [#1480] objsvc: Remove EACL checks
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-11-08 12:01:14 +00:00
b1a31281e4 [#1480] ape: Remove SoftAPECheck flag
Previous release was EACL-compatible.
Starting from now all EACL should've been migrated to APE chains.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-11-08 12:01:14 +00:00
39 changed files with 126 additions and 658 deletions

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="./.github/logo.svg" width="500px" alt="FrostFS"> <img src="./.forgejo/logo.svg" width="500px" alt="FrostFS">
</p> </p>
<p align="center"> <p align="center">

View file

@ -139,7 +139,7 @@ func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *he
c, err := helper.GetN3Client(viper.GetViper()) c, err := helper.GetN3Client(viper.GetViper())
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err) commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
ac, err := helper.NewLocalActor(cmd, c) ac, err := helper.NewLocalActor(cmd, c, constants.ConsensusAccountName)
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err) commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
var ch util.Uint160 var ch util.Uint160

View file

@ -5,7 +5,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
@ -31,7 +30,11 @@ type LocalActor struct {
// NewLocalActor create LocalActor with accounts form provided wallets. // NewLocalActor create LocalActor with accounts form provided wallets.
// In case of empty wallets provided created actor with dummy account only for read operation. // In case of empty wallets provided created actor with dummy account only for read operation.
func NewLocalActor(cmd *cobra.Command, c actor.RPCActor) (*LocalActor, error) { //
// If wallets are provided, the contract client will use accounts with accName name from these wallets.
// To determine which account name should be used in a contract client, refer to how the contract
// verifies the transaction signature.
func NewLocalActor(cmd *cobra.Command, c actor.RPCActor, accName string) (*LocalActor, error) {
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag)) walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
var act *actor.Actor var act *actor.Actor
var accounts []*wallet.Account var accounts []*wallet.Account
@ -53,8 +56,8 @@ func NewLocalActor(cmd *cobra.Command, c actor.RPCActor) (*LocalActor, error) {
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err) commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
for _, w := range wallets { for _, w := range wallets {
acc, err := GetWalletAccount(w, constants.CommitteeAccountName) acc, err := GetWalletAccount(w, accName)
commonCmd.ExitOnErr(cmd, "can't find committee account: %w", err) commonCmd.ExitOnErr(cmd, fmt.Sprintf("can't find %s account: %%w", accName), err)
accounts = append(accounts, acc) accounts = append(accounts, acc)
} }
act, err = actor.New(c, []actor.SignerAccount{{ act, err = actor.New(c, []actor.SignerAccount{{

View file

@ -2,6 +2,7 @@ package nns
import ( import (
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns" client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
@ -15,7 +16,7 @@ func getRPCClient(cmd *cobra.Command) (*client.Contract, *helper.LocalActor, uti
c, err := helper.GetN3Client(v) c, err := helper.GetN3Client(v)
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err) commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
ac, err := helper.NewLocalActor(cmd, c) ac, err := helper.NewLocalActor(cmd, c, constants.CommitteeAccountName)
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err) commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
r := management.NewReader(ac.Invoker) r := management.NewReader(ac.Invoker)

View file

@ -1,11 +1,10 @@
package container package container
import ( import (
"bufio"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"os" "os"
"strings" "strings"
@ -14,6 +13,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"github.com/chzyer/readline"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -163,6 +163,16 @@ func (repl *policyPlaygroundREPL) netMap() netmap.NetMap {
return nm return nm
} }
var policyPlaygroundCompleter = readline.NewPrefixCompleter(
readline.PcItem("list"),
readline.PcItem("ls"),
readline.PcItem("add"),
readline.PcItem("load"),
readline.PcItem("remove"),
readline.PcItem("rm"),
readline.PcItem("eval"),
)
func (repl *policyPlaygroundREPL) run() error { func (repl *policyPlaygroundREPL) run() error {
if len(viper.GetString(commonflags.RPC)) > 0 { if len(viper.GetString(commonflags.RPC)) > 0 {
key := key.GetOrGenerate(repl.cmd) key := key.GetOrGenerate(repl.cmd)
@ -189,22 +199,38 @@ func (repl *policyPlaygroundREPL) run() error {
"rm": repl.handleRemove, "rm": repl.handleRemove,
"eval": repl.handleEval, "eval": repl.handleEval,
} }
for reader := bufio.NewReader(os.Stdin); ; {
fmt.Print("> ") rl, err := readline.NewEx(&readline.Config{
line, err := reader.ReadString('\n') Prompt: "> ",
InterruptPrompt: "^C",
AutoComplete: policyPlaygroundCompleter,
})
if err != nil { if err != nil {
if err == io.EOF { return fmt.Errorf("error initializing readline: %w", err)
}
defer rl.Close()
var exit bool
for {
line, err := rl.Readline()
if err != nil {
if errors.Is(err, readline.ErrInterrupt) {
if exit {
return nil return nil
} }
return fmt.Errorf("reading line: %v", err) exit = true
continue
} }
return fmt.Errorf("reading line: %w", err)
}
exit = false
parts := strings.Fields(line) parts := strings.Fields(line)
if len(parts) == 0 { if len(parts) == 0 {
continue continue
} }
cmd := parts[0] cmd := parts[0]
handler, exists := cmdHandlers[cmd] if handler, exists := cmdHandlers[cmd]; exists {
if exists {
if err := handler(parts[1:]); err != nil { if err := handler(parts[1:]); err != nil {
fmt.Printf("error: %v\n", err) fmt.Printf("error: %v\n", err)
} }

View file

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"net" "net"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance"
accountingTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/accounting/grpc" accountingTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/accounting/grpc"
@ -30,5 +31,27 @@ func initAccountingService(ctx context.Context, c *cfg) {
c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) { c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) {
accountingGRPC.RegisterAccountingServiceServer(s, server) accountingGRPC.RegisterAccountingServiceServer(s, server)
// TODO(@aarifullin): #1487 remove the dual service support.
s.RegisterService(frostFSServiceDesc(accountingGRPC.AccountingService_ServiceDesc), server)
}) })
} }
// frostFSServiceDesc creates a service descriptor with the new namespace for dual service support.
func frostFSServiceDesc(sd grpc.ServiceDesc) *grpc.ServiceDesc {
sdLegacy := new(grpc.ServiceDesc)
*sdLegacy = sd
const (
legacyNamespace = "neo.fs.v2"
apemanagerLegacyNamespace = "frostfs.v2"
newNamespace = "frost.fs"
)
if strings.HasPrefix(sd.ServiceName, legacyNamespace) {
sdLegacy.ServiceName = strings.ReplaceAll(sd.ServiceName, legacyNamespace, newNamespace)
} else if strings.HasPrefix(sd.ServiceName, apemanagerLegacyNamespace) {
sdLegacy.ServiceName = strings.ReplaceAll(sd.ServiceName, apemanagerLegacyNamespace, newNamespace)
}
return sdLegacy
}

View file

@ -26,5 +26,8 @@ func initAPEManagerService(c *cfg) {
c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) { c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) {
apemanager_grpc.RegisterAPEManagerServiceServer(s, server) apemanager_grpc.RegisterAPEManagerServiceServer(s, server)
// TODO(@aarifullin): #1487 remove the dual service support.
s.RegisterService(frostFSServiceDesc(apemanager_grpc.APEManagerService_ServiceDesc), server)
}) })
} }

View file

@ -64,6 +64,9 @@ func initContainerService(_ context.Context, c *cfg) {
c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) { c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) {
containerGRPC.RegisterContainerServiceServer(s, server) containerGRPC.RegisterContainerServiceServer(s, server)
// TODO(@aarifullin): #1487 remove the dual service support.
s.RegisterService(frostFSServiceDesc(containerGRPC.ContainerService_ServiceDesc), server)
}) })
c.cfgObject.cfgLocalStorage.localStorage.SetContainerSource(cnrRdr) c.cfgObject.cfgLocalStorage.localStorage.SetContainerSource(cnrRdr)

View file

@ -166,6 +166,9 @@ func initNetmapService(ctx context.Context, c *cfg) {
c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) { c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) {
netmapGRPC.RegisterNetmapServiceServer(s, server) netmapGRPC.RegisterNetmapServiceServer(s, server)
// TODO(@aarifullin): #1487 remove the dual service support.
s.RegisterService(frostFSServiceDesc(netmapGRPC.NetmapService_ServiceDesc), server)
}) })
addNewEpochNotificationHandlers(c) addNewEpochNotificationHandlers(c)

View file

@ -19,7 +19,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/cache" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/cache"
objectTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/object/grpc" objectTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/object/grpc"
objectService "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object" objectService "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl"
v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2" v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2"
objectAPE "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/ape" objectAPE "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/ape"
objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer" objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer"
@ -39,7 +38,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
objectGRPC "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object/grpc" objectGRPC "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object/grpc"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
@ -220,6 +218,9 @@ func initObjectService(c *cfg) {
c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) { c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) {
objectGRPC.RegisterObjectServiceServer(s, server) objectGRPC.RegisterObjectServiceServer(s, server)
// TODO(@aarifullin): #1487 remove the dual service support.
s.RegisterService(frostFSServiceDesc(objectGRPC.ObjectService_ServiceDesc), server)
}) })
} }
@ -458,17 +459,10 @@ func createSplitService(c *cfg, sPutV2 *putsvcV2.Service, sGetV2 *getsvcV2.Servi
} }
func createACLServiceV2(c *cfg, apeSvc *objectAPE.Service, irFetcher *cachedIRFetcher) v2.Service { func createACLServiceV2(c *cfg, apeSvc *objectAPE.Service, irFetcher *cachedIRFetcher) v2.Service {
ls := c.cfgObject.cfgLocalStorage.localStorage
return v2.New( return v2.New(
apeSvc, apeSvc,
c.netMapSource, c.netMapSource,
irFetcher, irFetcher,
acl.NewChecker(
c.cfgNetmap.state,
c.cfgObject.eaclSource,
eaclSDK.NewValidator(),
ls),
c.cfgObject.cnrSource, c.cfgObject.cnrSource,
v2.WithLogger(c.log), v2.WithLogger(c.log),
) )

View file

@ -61,5 +61,8 @@ func initSessionService(c *cfg) {
c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) { c.cfgGRPC.performAndSave(func(_ string, _ net.Listener, s *grpc.Server) {
sessionGRPC.RegisterSessionServiceServer(s, server) sessionGRPC.RegisterSessionServiceServer(s, server)
// TODO(@aarifullin): #1487 remove the dual service support.
s.RegisterService(frostFSServiceDesc(sessionGRPC.SessionService_ServiceDesc), server)
}) })
} }

View file

@ -55,7 +55,7 @@ Add an entry to the `CHANGELOG.md` following the style established there.
* update `Unreleased...new` and `new...old` diff-links at the bottom of the file * update `Unreleased...new` and `new...old` diff-links at the bottom of the file
* add optional codename and release date in the heading * add optional codename and release date in the heading
* remove all empty sections such as `Added`, `Removed`, etc. * remove all empty sections such as `Added`, `Removed`, etc.
* make sure all changes have references to GitHub issues in `#123` format (if possible) * make sure all changes have references to relevant issues in `#123` format (if possible)
* clean up all `Unreleased` sections and leave them empty * clean up all `Unreleased` sections and leave them empty
### Make release commit ### Make release commit
@ -110,9 +110,9 @@ $ docker push truecloudlab/frostfs-cli:${FROSTFS_REVISION}
$ docker push truecloudlab/frostfs-adm:${FROSTFS_REVISION} $ docker push truecloudlab/frostfs-adm:${FROSTFS_REVISION}
``` ```
### Make a proper GitHub release (if not automated) ### Make a proper release (if not automated)
Edit an automatically-created release on GitHub, copy things from `CHANGELOG.md`. Edit an automatically-created release on git.frostfs.info, copy things from `CHANGELOG.md`.
Build and tar release binaries with `make prepare-release`, attach them to Build and tar release binaries with `make prepare-release`, attach them to
the release. Publish the release. the release. Publish the release.
@ -121,7 +121,7 @@ the release. Publish the release.
Prepare pull-request in [frostfs-devenv](https://git.frostfs.info/TrueCloudLab/frostfs-dev-env) Prepare pull-request in [frostfs-devenv](https://git.frostfs.info/TrueCloudLab/frostfs-dev-env)
with new versions. with new versions.
### Close GitHub milestone ### Close milestone
Look up [milestones](https://git.frostfs.info/TrueCloudLab/frostfs-node/milestones) and close the release one if exists. Look up [milestones](https://git.frostfs.info/TrueCloudLab/frostfs-node/milestones) and close the release one if exists.

View file

@ -413,11 +413,11 @@ object:
``` ```
| Parameter | Type | Default value | Description | | Parameter | Type | Default value | Description |
|-----------------------------|------------|---------------|------------------------------------------------------------------------------------------------------| |-----------------------------|------------|---------------|------------------------------------------------------------------------------------------------|
| `delete.tombstone_lifetime` | `int` | `5` | Tombstone lifetime for removed objects in epochs. | | `delete.tombstone_lifetime` | `int` | `5` | Tombstone lifetime for removed objects in epochs. |
| `put.remote_pool_size` | `int` | `10` | Max pool size for performing remote `PUT` operations. Used by Policer and Replicator services. | | `put.remote_pool_size` | `int` | `10` | Max pool size for performing remote `PUT` operations. Used by Policer and Replicator services. |
| `put.local_pool_size` | `int` | `10` | Max pool size for performing local `PUT` operations. Used by Policer and Replicator services. | | `put.local_pool_size` | `int` | `10` | Max pool size for performing local `PUT` operations. Used by Policer and Replicator services. |
| `get.priority` | `[]string` | | List of metrics of nodes for prioritization. Used for computing response on GET and SEARCH requests. | | `get.priority` | `[]string` | | List of metrics of nodes for prioritization. Used for computing response on GET requests. |
# `runtime` section # `runtime` section
Contains runtime parameters. Contains runtime parameters.

View file

@ -7,7 +7,7 @@
## Update CI ## Update CI
Change Golang versions for unit test in CI. Change Golang versions for unit test in CI.
There is `go` section in `.github/workflows/go.yaml` file: There is `go` section in `.forgejo/workflows/*.yml` files:
```yaml ```yaml
jobs: jobs:
test: test:

View file

@ -45,6 +45,8 @@ func (r ListContainersRes) Containers() []cid.ID {
// //
// Returns an error if executions are blocked (see BlockExecution). // Returns an error if executions are blocked (see BlockExecution).
func (e *StorageEngine) ContainerSize(prm ContainerSizePrm) (res ContainerSizeRes, err error) { func (e *StorageEngine) ContainerSize(prm ContainerSizePrm) (res ContainerSizeRes, err error) {
defer elapsed("ContainerSize", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e.containerSize(prm) res, err = e.containerSize(prm)
return err return err
@ -68,8 +70,6 @@ func ContainerSize(e *StorageEngine, id cid.ID) (uint64, error) {
} }
func (e *StorageEngine) containerSize(prm ContainerSizePrm) (res ContainerSizeRes, err error) { func (e *StorageEngine) containerSize(prm ContainerSizePrm) (res ContainerSizeRes, err error) {
defer elapsed("EstimateContainerSize", e.metrics.AddMethodDuration)()
e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) { e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) {
var csPrm shard.ContainerSizePrm var csPrm shard.ContainerSizePrm
csPrm.SetContainerID(prm.cnr) csPrm.SetContainerID(prm.cnr)
@ -93,6 +93,8 @@ func (e *StorageEngine) containerSize(prm ContainerSizePrm) (res ContainerSizeRe
// //
// Returns an error if executions are blocked (see BlockExecution). // Returns an error if executions are blocked (see BlockExecution).
func (e *StorageEngine) ListContainers(ctx context.Context, _ ListContainersPrm) (res ListContainersRes, err error) { func (e *StorageEngine) ListContainers(ctx context.Context, _ ListContainersPrm) (res ListContainersRes, err error) {
defer elapsed("ListContainers", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e.listContainers(ctx) res, err = e.listContainers(ctx)
return err return err
@ -114,8 +116,6 @@ func ListContainers(ctx context.Context, e *StorageEngine) ([]cid.ID, error) {
} }
func (e *StorageEngine) listContainers(ctx context.Context) (ListContainersRes, error) { func (e *StorageEngine) listContainers(ctx context.Context) (ListContainersRes, error) {
defer elapsed("ListContainers", e.metrics.AddMethodDuration)()
uniqueIDs := make(map[string]cid.ID) uniqueIDs := make(map[string]cid.ID)
e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) { e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) {

View file

@ -58,6 +58,7 @@ func (e *StorageEngine) Delete(ctx context.Context, prm DeletePrm) (res DeleteRe
attribute.Bool("force_removal", prm.forceRemoval), attribute.Bool("force_removal", prm.forceRemoval),
)) ))
defer span.End() defer span.End()
defer elapsed("Delete", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e.delete(ctx, prm) res, err = e.delete(ctx, prm)
@ -68,8 +69,6 @@ func (e *StorageEngine) Delete(ctx context.Context, prm DeletePrm) (res DeleteRe
} }
func (e *StorageEngine) delete(ctx context.Context, prm DeletePrm) (DeleteRes, error) { func (e *StorageEngine) delete(ctx context.Context, prm DeletePrm) (DeleteRes, error) {
defer elapsed("Delete", e.metrics.AddMethodDuration)()
var locked struct { var locked struct {
is bool is bool
} }

View file

@ -56,6 +56,7 @@ func (e *StorageEngine) Get(ctx context.Context, prm GetPrm) (res GetRes, err er
attribute.String("address", prm.addr.EncodeToString()), attribute.String("address", prm.addr.EncodeToString()),
)) ))
defer span.End() defer span.End()
defer elapsed("Get", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e.get(ctx, prm) res, err = e.get(ctx, prm)
@ -66,8 +67,6 @@ func (e *StorageEngine) Get(ctx context.Context, prm GetPrm) (res GetRes, err er
} }
func (e *StorageEngine) get(ctx context.Context, prm GetPrm) (GetRes, error) { func (e *StorageEngine) get(ctx context.Context, prm GetPrm) (GetRes, error) {
defer elapsed("Get", e.metrics.AddMethodDuration)()
errNotFound := new(apistatus.ObjectNotFound) errNotFound := new(apistatus.ObjectNotFound)
var shPrm shard.GetPrm var shPrm shard.GetPrm

View file

@ -70,6 +70,7 @@ var errInhumeFailure = errors.New("inhume operation failed")
func (e *StorageEngine) Inhume(ctx context.Context, prm InhumePrm) (res InhumeRes, err error) { func (e *StorageEngine) Inhume(ctx context.Context, prm InhumePrm) (res InhumeRes, err error) {
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.Inhume") ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.Inhume")
defer span.End() defer span.End()
defer elapsed("Inhume", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e.inhume(ctx, prm) res, err = e.inhume(ctx, prm)
@ -80,8 +81,6 @@ func (e *StorageEngine) Inhume(ctx context.Context, prm InhumePrm) (res InhumeRe
} }
func (e *StorageEngine) inhume(ctx context.Context, prm InhumePrm) (InhumeRes, error) { func (e *StorageEngine) inhume(ctx context.Context, prm InhumePrm) (InhumeRes, error) {
defer elapsed("Inhume", e.metrics.AddMethodDuration)()
var shPrm shard.InhumePrm var shPrm shard.InhumePrm
if prm.forceRemoval { if prm.forceRemoval {
shPrm.ForceRemoval() shPrm.ForceRemoval()

View file

@ -7,6 +7,7 @@ import (
objectcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object" objectcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
) )
// ErrEndOfListing is returned from an object listing with cursor // ErrEndOfListing is returned from an object listing with cursor
@ -98,6 +99,10 @@ func (l ListWithCursorRes) Cursor() *Cursor {
// Returns ErrEndOfListing if there are no more objects to return or count // Returns ErrEndOfListing if there are no more objects to return or count
// parameter set to zero. // parameter set to zero.
func (e *StorageEngine) ListWithCursor(ctx context.Context, prm ListWithCursorPrm) (ListWithCursorRes, error) { func (e *StorageEngine) ListWithCursor(ctx context.Context, prm ListWithCursorPrm) (ListWithCursorRes, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.ListWithCursor")
defer span.End()
defer elapsed("ListWithCursor", e.metrics.AddMethodDuration)()
result := make([]objectcore.Info, 0, prm.count) result := make([]objectcore.Info, 0, prm.count)
// Set initial cursors // Set initial cursors

View file

@ -32,6 +32,7 @@ func (e *StorageEngine) Lock(ctx context.Context, idCnr cid.ID, locker oid.ID, l
attribute.Int("locked_count", len(locked)), attribute.Int("locked_count", len(locked)),
)) ))
defer span.End() defer span.End()
defer elapsed("Lock", e.metrics.AddMethodDuration)()
return e.execIfNotBlocked(func() error { return e.execIfNotBlocked(func() error {
return e.lock(ctx, idCnr, locker, locked) return e.lock(ctx, idCnr, locker, locked)

View file

@ -56,6 +56,7 @@ func (e *StorageEngine) Put(ctx context.Context, prm PutPrm) (err error) {
attribute.String("address", object.AddressOf(prm.Object).EncodeToString()), attribute.String("address", object.AddressOf(prm.Object).EncodeToString()),
)) ))
defer span.End() defer span.End()
defer elapsed("Put", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
err = e.put(ctx, prm) err = e.put(ctx, prm)
@ -66,8 +67,6 @@ func (e *StorageEngine) Put(ctx context.Context, prm PutPrm) (err error) {
} }
func (e *StorageEngine) put(ctx context.Context, prm PutPrm) error { func (e *StorageEngine) put(ctx context.Context, prm PutPrm) error {
defer elapsed("Put", e.metrics.AddMethodDuration)()
addr := object.AddressOf(prm.Object) addr := object.AddressOf(prm.Object)
// In #1146 this check was parallelized, however, it became // In #1146 this check was parallelized, however, it became

View file

@ -65,6 +65,15 @@ func (r RngRes) Object() *objectSDK.Object {
// //
// Returns an error if executions are blocked (see BlockExecution). // Returns an error if executions are blocked (see BlockExecution).
func (e *StorageEngine) GetRange(ctx context.Context, prm RngPrm) (res RngRes, err error) { func (e *StorageEngine) GetRange(ctx context.Context, prm RngPrm) (res RngRes, err error) {
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.getRange",
trace.WithAttributes(
attribute.String("address", prm.addr.EncodeToString()),
attribute.String("offset", strconv.FormatUint(prm.off, 10)),
attribute.String("length", strconv.FormatUint(prm.ln, 10)),
))
defer span.End()
defer elapsed("GetRange", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e.getRange(ctx, prm) res, err = e.getRange(ctx, prm)
return err return err
@ -74,16 +83,6 @@ func (e *StorageEngine) GetRange(ctx context.Context, prm RngPrm) (res RngRes, e
} }
func (e *StorageEngine) getRange(ctx context.Context, prm RngPrm) (RngRes, error) { func (e *StorageEngine) getRange(ctx context.Context, prm RngPrm) (RngRes, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.getRange",
trace.WithAttributes(
attribute.String("address", prm.addr.EncodeToString()),
attribute.String("offset", strconv.FormatUint(prm.off, 10)),
attribute.String("length", strconv.FormatUint(prm.ln, 10)),
))
defer span.End()
defer elapsed("GetRange", e.metrics.AddMethodDuration)()
var shPrm shard.RngPrm var shPrm shard.RngPrm
shPrm.SetAddress(prm.addr) shPrm.SetAddress(prm.addr)
shPrm.SetRange(prm.off, prm.ln) shPrm.SetRange(prm.off, prm.ln)

View file

@ -51,6 +51,7 @@ func (e *StorageEngine) Select(ctx context.Context, prm SelectPrm) (res SelectRe
attribute.String("container_id", prm.cnr.EncodeToString()), attribute.String("container_id", prm.cnr.EncodeToString()),
)) ))
defer span.End() defer span.End()
defer elapsed("Select", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e._select(ctx, prm) res, err = e._select(ctx, prm)
@ -61,8 +62,6 @@ func (e *StorageEngine) Select(ctx context.Context, prm SelectPrm) (res SelectRe
} }
func (e *StorageEngine) _select(ctx context.Context, prm SelectPrm) (SelectRes, error) { func (e *StorageEngine) _select(ctx context.Context, prm SelectPrm) (SelectRes, error) {
defer elapsed("Search", e.metrics.AddMethodDuration)()
addrList := make([]oid.Address, 0) addrList := make([]oid.Address, 0)
uniqueMap := make(map[string]struct{}) uniqueMap := make(map[string]struct{})
@ -99,6 +98,7 @@ func (e *StorageEngine) _select(ctx context.Context, prm SelectPrm) (SelectRes,
// //
// Returns an error if executions are blocked (see BlockExecution). // Returns an error if executions are blocked (see BlockExecution).
func (e *StorageEngine) List(ctx context.Context, limit uint64) (res SelectRes, err error) { func (e *StorageEngine) List(ctx context.Context, limit uint64) (res SelectRes, err error) {
defer elapsed("List", e.metrics.AddMethodDuration)()
err = e.execIfNotBlocked(func() error { err = e.execIfNotBlocked(func() error {
res, err = e.list(ctx, limit) res, err = e.list(ctx, limit)
return err return err
@ -108,8 +108,6 @@ func (e *StorageEngine) List(ctx context.Context, limit uint64) (res SelectRes,
} }
func (e *StorageEngine) list(ctx context.Context, limit uint64) (SelectRes, error) { func (e *StorageEngine) list(ctx context.Context, limit uint64) (SelectRes, error) {
defer elapsed("ListObjects", e.metrics.AddMethodDuration)()
addrList := make([]oid.Address, 0, limit) addrList := make([]oid.Address, 0, limit)
uniqueMap := make(map[string]struct{}) uniqueMap := make(map[string]struct{})
ln := uint64(0) ln := uint64(0)

View file

@ -44,9 +44,6 @@ type CheckPrm struct {
// The request's bearer token. It is used in order to check APE overrides with the token. // The request's bearer token. It is used in order to check APE overrides with the token.
BearerToken *bearer.Token BearerToken *bearer.Token
// If SoftAPECheck is set to true, then NoRuleFound is interpreted as allow.
SoftAPECheck bool
} }
// CheckCore provides methods to perform the common logic of APE check. // CheckCore provides methods to perform the common logic of APE check.
@ -104,7 +101,7 @@ func (c *checkerCoreImpl) CheckAPE(prm CheckPrm) error {
if err != nil { if err != nil {
return err return err
} }
if !found && prm.SoftAPECheck || status == apechain.Allow { if found && status == apechain.Allow {
return nil return nil
} }
err = fmt.Errorf("access to operation %s is denied by access policy engine: %s", prm.Request.Operation(), status.String()) err = fmt.Errorf("access to operation %s is denied by access policy engine: %s", prm.Request.Operation(), status.String())

View file

@ -1,262 +0,0 @@
package acl
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"io"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
eaclV2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/eacl/v2"
v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2"
bearerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)
// Checker implements v2.ACLChecker interfaces and provides
// ACL/eACL validation functionality.
type Checker struct {
eaclSrc container.EACLSource
validator *eaclSDK.Validator
localStorage *engine.StorageEngine
state netmap.State
}
type localStorage struct {
ls *engine.StorageEngine
}
func (s *localStorage) Head(ctx context.Context, addr oid.Address) (*objectSDK.Object, error) {
if s.ls == nil {
return nil, io.ErrUnexpectedEOF
}
return engine.Head(ctx, s.ls, addr)
}
// Various EACL check errors.
var (
errEACLDeniedByRule = errors.New("denied by rule")
errBearerExpired = errors.New("bearer token has expired")
errBearerInvalidSignature = errors.New("bearer token has invalid signature")
errBearerInvalidContainerID = errors.New("bearer token was created for another container")
errBearerNotSignedByOwner = errors.New("bearer token is not signed by the container owner")
errBearerInvalidOwner = errors.New("bearer token owner differs from the request sender")
)
// NewChecker creates Checker.
// Panics if at least one of the parameter is nil.
func NewChecker(
state netmap.State,
eaclSrc container.EACLSource,
validator *eaclSDK.Validator,
localStorage *engine.StorageEngine,
) *Checker {
return &Checker{
eaclSrc: eaclSrc,
validator: validator,
localStorage: localStorage,
state: state,
}
}
// CheckBasicACL is a main check function for basic ACL.
func (c *Checker) CheckBasicACL(info v2.RequestInfo) bool {
// check basic ACL permissions
return info.BasicACL().IsOpAllowed(info.Operation(), info.RequestRole())
}
// StickyBitCheck validates owner field in the request if sticky bit is enabled.
func (c *Checker) StickyBitCheck(info v2.RequestInfo, owner user.ID) bool {
// According to FrostFS specification sticky bit has no effect on system nodes
// for correct intra-container work with objects (in particular, replication).
if info.RequestRole() == acl.RoleContainer {
return true
}
if !info.BasicACL().Sticky() {
return true
}
if len(info.SenderKey()) == 0 {
return false
}
requestSenderKey := unmarshalPublicKey(info.SenderKey())
return isOwnerFromKey(owner, requestSenderKey)
}
// CheckEACL is a main check function for extended ACL.
func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error {
basicACL := reqInfo.BasicACL()
if !basicACL.Extendable() {
return nil
}
bearerTok := reqInfo.Bearer()
impersonate := bearerTok != nil && bearerTok.Impersonate()
// if bearer token is not allowed, then ignore it
if impersonate || !basicACL.AllowedBearerRules(reqInfo.Operation()) {
reqInfo.CleanBearer()
}
var table eaclSDK.Table
cnr := reqInfo.ContainerID()
if bearerTok == nil {
eaclInfo, err := c.eaclSrc.GetEACL(cnr)
if err != nil {
if client.IsErrEACLNotFound(err) {
return nil
}
return err
}
table = *eaclInfo.Value
} else {
table = bearerTok.EACLTable()
}
// if bearer token is not present, isValidBearer returns true
if err := isValidBearer(reqInfo, c.state); err != nil {
return err
}
hdrSrc, err := c.getHeaderSource(cnr, msg, reqInfo)
if err != nil {
return err
}
eaclRole := getRole(reqInfo)
action, _ := c.validator.CalculateAction(new(eaclSDK.ValidationUnit).
WithRole(eaclRole).
WithOperation(eaclSDK.Operation(reqInfo.Operation())).
WithContainerID(&cnr).
WithSenderKey(reqInfo.SenderKey()).
WithHeaderSource(hdrSrc).
WithEACLTable(&table),
)
if action != eaclSDK.ActionAllow {
return errEACLDeniedByRule
}
return nil
}
func getRole(reqInfo v2.RequestInfo) eaclSDK.Role {
var eaclRole eaclSDK.Role
switch op := reqInfo.RequestRole(); op {
default:
eaclRole = eaclSDK.Role(op)
case acl.RoleOwner:
eaclRole = eaclSDK.RoleUser
case acl.RoleInnerRing, acl.RoleContainer:
eaclRole = eaclSDK.RoleSystem
case acl.RoleOthers:
eaclRole = eaclSDK.RoleOthers
}
return eaclRole
}
func (c *Checker) getHeaderSource(cnr cid.ID, msg any, reqInfo v2.RequestInfo) (eaclSDK.TypedHeaderSource, error) {
var xHeaderSource eaclV2.XHeaderSource
if req, ok := msg.(eaclV2.Request); ok {
xHeaderSource = eaclV2.NewRequestXHeaderSource(req)
} else {
xHeaderSource = eaclV2.NewResponseXHeaderSource(msg.(eaclV2.Response), reqInfo.Request().(eaclV2.Request))
}
hdrSrc, err := eaclV2.NewMessageHeaderSource(&localStorage{ls: c.localStorage}, xHeaderSource, cnr, eaclV2.WithOID(reqInfo.ObjectID()))
if err != nil {
return nil, fmt.Errorf("can't parse headers: %w", err)
}
return hdrSrc, nil
}
// isValidBearer checks whether bearer token was correctly signed by authorized
// entity. This method might be defined on whole ACL service because it will
// require fetching current epoch to check lifetime.
func isValidBearer(reqInfo v2.RequestInfo, st netmap.State) error {
ownerCnr := reqInfo.ContainerOwner()
token := reqInfo.Bearer()
// 0. Check if bearer token is present in reqInfo.
if token == nil {
return nil
}
// 1. First check token lifetime. Simplest verification.
if token.InvalidAt(st.CurrentEpoch()) {
return errBearerExpired
}
// 2. Then check if bearer token is signed correctly.
if !token.VerifySignature() {
return errBearerInvalidSignature
}
// 3. Then check if container is either empty or equal to the container in the request.
cnr, isSet := token.EACLTable().CID()
if isSet && !cnr.Equals(reqInfo.ContainerID()) {
return errBearerInvalidContainerID
}
// 4. Then check if container owner signed this token.
if !bearerSDK.ResolveIssuer(*token).Equals(ownerCnr) {
// TODO: #767 in this case we can issue all owner keys from frostfs.id and check once again
return errBearerNotSignedByOwner
}
// 5. Then check if request sender has rights to use this token.
var keySender frostfsecdsa.PublicKey
err := keySender.Decode(reqInfo.SenderKey())
if err != nil {
return fmt.Errorf("decode sender public key: %w", err)
}
var usrSender user.ID
user.IDFromKey(&usrSender, ecdsa.PublicKey(keySender))
if !token.AssertUser(usrSender) {
// TODO: #767 in this case we can issue all owner keys from frostfs.id and check once again
return errBearerInvalidOwner
}
return nil
}
func isOwnerFromKey(id user.ID, key *keys.PublicKey) bool {
if key == nil {
return false
}
var id2 user.ID
user.IDFromKey(&id2, (ecdsa.PublicKey)(*key))
return id.Equals(id2)
}
func unmarshalPublicKey(bs []byte) *keys.PublicKey {
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
if err != nil {
return nil
}
return pub
}

View file

@ -1,89 +0,0 @@
package acl
import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
"github.com/stretchr/testify/require"
)
type emptyEACLSource struct{}
func (e emptyEACLSource) GetEACL(_ cid.ID) (*container.EACL, error) {
return nil, nil
}
type emptyNetmapState struct{}
func (e emptyNetmapState) CurrentEpoch() uint64 {
return 0
}
func TestStickyCheck(t *testing.T) {
checker := NewChecker(
emptyNetmapState{},
emptyEACLSource{},
eaclSDK.NewValidator(),
&engine.StorageEngine{})
t.Run("system role", func(t *testing.T) {
var info v2.RequestInfo
info.SetSenderKey(make([]byte, 33)) // any non-empty key
info.SetRequestRole(acl.RoleContainer)
require.True(t, checker.StickyBitCheck(info, usertest.ID()))
var basicACL acl.Basic
basicACL.MakeSticky()
info.SetBasicACL(basicACL)
require.True(t, checker.StickyBitCheck(info, usertest.ID()))
})
t.Run("owner ID and/or public key emptiness", func(t *testing.T) {
var info v2.RequestInfo
info.SetRequestRole(acl.RoleOthers) // should be non-system role
assertFn := func(isSticky, withKey, withOwner, expected bool) {
info := info
if isSticky {
var basicACL acl.Basic
basicACL.MakeSticky()
info.SetBasicACL(basicACL)
}
if withKey {
info.SetSenderKey(make([]byte, 33))
} else {
info.SetSenderKey(nil)
}
var ownerID user.ID
if withOwner {
ownerID = usertest.ID()
}
require.Equal(t, expected, checker.StickyBitCheck(info, ownerID))
}
assertFn(true, false, false, false)
assertFn(true, true, false, false)
assertFn(true, false, true, false)
assertFn(false, false, false, true)
assertFn(false, true, false, true)
assertFn(false, false, true, true)
assertFn(false, true, true, true)
})
}

View file

@ -2,8 +2,6 @@ package v2
import ( import (
"fmt" "fmt"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
) )
const invalidRequestMessage = "malformed request" const invalidRequestMessage = "malformed request"
@ -20,22 +18,3 @@ var (
errInvalidSessionOwner = malformedRequestError("invalid session token owner") errInvalidSessionOwner = malformedRequestError("invalid session token owner")
errInvalidVerb = malformedRequestError("session token verb is invalid") errInvalidVerb = malformedRequestError("session token verb is invalid")
) )
const (
accessDeniedACLReasonFmt = "access to operation %s is denied by basic ACL check"
accessDeniedEACLReasonFmt = "access to operation %s is denied by extended ACL check: %v"
)
func basicACLErr(info RequestInfo) error {
errAccessDenied := &apistatus.ObjectAccessDenied{}
errAccessDenied.WriteReason(fmt.Sprintf(accessDeniedACLReasonFmt, info.operation))
return errAccessDenied
}
func eACLErr(info RequestInfo, err error) error {
errAccessDenied := &apistatus.ObjectAccessDenied{}
errAccessDenied.WriteReason(fmt.Sprintf(accessDeniedEACLReasonFmt, info.operation, err))
return errAccessDenied
}

View file

@ -1,30 +0,0 @@
package v2
import (
"errors"
"testing"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"github.com/stretchr/testify/require"
)
func TestBasicACLErr(t *testing.T) {
var reqInfo RequestInfo
err := basicACLErr(reqInfo)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied,
"basicACLErr must be able to be casted to apistatus.ObjectAccessDenied")
}
func TestEACLErr(t *testing.T) {
var reqInfo RequestInfo
testErr := errors.New("test-eacl")
err := eACLErr(reqInfo, testErr)
var errAccessDenied *apistatus.ObjectAccessDenied
require.ErrorAs(t, err, &errAccessDenied,
"eACLErr must be able to be casted to apistatus.ObjectAccessDenied")
}

View file

@ -104,13 +104,6 @@ func (r RequestInfo) RequestRole() acl.Role {
return r.requestRole return r.requestRole
} }
// IsSoftAPECheck states if APE should perform soft checks.
// Soft APE check allows a request if CheckAPE returns NoRuleFound for it,
// otherwise it denies the request.
func (r RequestInfo) IsSoftAPECheck() bool {
return r.BasicACL().Bits() != 0
}
// MetaWithToken groups session and bearer tokens, // MetaWithToken groups session and bearer tokens,
// verification header and raw API request. // verification header and raw API request.
type MetaWithToken struct { type MetaWithToken struct {

View file

@ -41,30 +41,6 @@ type patchStreamBasicChecker struct {
nonFirstSend bool nonFirstSend bool
} }
type getStreamBasicChecker struct {
checker ACLChecker
object.GetObjectStream
info RequestInfo
}
type rangeStreamBasicChecker struct {
checker ACLChecker
object.GetObjectRangeStream
info RequestInfo
}
type searchStreamBasicChecker struct {
checker ACLChecker
object.SearchStream
info RequestInfo
}
// Option represents Service constructor option. // Option represents Service constructor option.
type Option func(*cfg) type Option func(*cfg)
@ -73,8 +49,6 @@ type cfg struct {
containers container.Source containers container.Source
checker ACLChecker
irFetcher InnerRingFetcher irFetcher InnerRingFetcher
nm netmap.Source nm netmap.Source
@ -86,7 +60,6 @@ type cfg struct {
func New(next object.ServiceServer, func New(next object.ServiceServer,
nm netmap.Source, nm netmap.Source,
irf InnerRingFetcher, irf InnerRingFetcher,
acl ACLChecker,
cs container.Source, cs container.Source,
opts ...Option, opts ...Option,
) Service { ) Service {
@ -95,7 +68,6 @@ func New(next object.ServiceServer,
next: next, next: next,
nm: nm, nm: nm,
irFetcher: irf, irFetcher: irf,
checker: acl,
containers: cs, containers: cs,
} }
@ -123,7 +95,6 @@ func (w *wrappedGetObjectStream) Context() context.Context {
ContainerOwner: w.requestInfo.ContainerOwner(), ContainerOwner: w.requestInfo.ContainerOwner(),
SenderKey: w.requestInfo.SenderKey(), SenderKey: w.requestInfo.SenderKey(),
Role: w.requestInfo.RequestRole(), Role: w.requestInfo.RequestRole(),
SoftAPECheck: w.requestInfo.IsSoftAPECheck(),
BearerToken: w.requestInfo.Bearer(), BearerToken: w.requestInfo.Bearer(),
}) })
} }
@ -149,7 +120,6 @@ func (w *wrappedRangeStream) Context() context.Context {
ContainerOwner: w.requestInfo.ContainerOwner(), ContainerOwner: w.requestInfo.ContainerOwner(),
SenderKey: w.requestInfo.SenderKey(), SenderKey: w.requestInfo.SenderKey(),
Role: w.requestInfo.RequestRole(), Role: w.requestInfo.RequestRole(),
SoftAPECheck: w.requestInfo.IsSoftAPECheck(),
BearerToken: w.requestInfo.Bearer(), BearerToken: w.requestInfo.Bearer(),
}) })
} }
@ -175,7 +145,6 @@ func (w *wrappedSearchStream) Context() context.Context {
ContainerOwner: w.requestInfo.ContainerOwner(), ContainerOwner: w.requestInfo.ContainerOwner(),
SenderKey: w.requestInfo.SenderKey(), SenderKey: w.requestInfo.SenderKey(),
Role: w.requestInfo.RequestRole(), Role: w.requestInfo.RequestRole(),
SoftAPECheck: w.requestInfo.IsSoftAPECheck(),
BearerToken: w.requestInfo.Bearer(), BearerToken: w.requestInfo.Bearer(),
}) })
} }
@ -231,19 +200,7 @@ func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream
reqInfo.obj = obj reqInfo.obj = obj
if reqInfo.IsSoftAPECheck() { return b.next.Get(request, newWrappedGetObjectStreamStream(stream, reqInfo))
if !b.checker.CheckBasicACL(reqInfo) {
return basicACLErr(reqInfo)
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo, err)
}
}
return b.next.Get(request, &getStreamBasicChecker{
GetObjectStream: newWrappedGetObjectStreamStream(stream, reqInfo),
info: reqInfo,
checker: b.checker,
})
} }
func (b Service) Put() (object.PutObjectStream, error) { func (b Service) Put() (object.PutObjectStream, error) {
@ -309,22 +266,7 @@ func (b Service) Head(
reqInfo.obj = obj reqInfo.obj = obj
if reqInfo.IsSoftAPECheck() { return b.next.Head(requestContext(ctx, reqInfo), request)
if !b.checker.CheckBasicACL(reqInfo) {
return nil, basicACLErr(reqInfo)
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err)
}
}
resp, err := b.next.Head(requestContext(ctx, reqInfo), request)
if err == nil {
if err = b.checker.CheckEACL(resp, reqInfo); err != nil {
err = eACLErr(reqInfo, err)
}
}
return resp, err
} }
func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStream) error { func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStream) error {
@ -362,19 +304,7 @@ func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStr
return err return err
} }
if reqInfo.IsSoftAPECheck() { return b.next.Search(request, newWrappedSearchStream(stream, reqInfo))
if !b.checker.CheckBasicACL(reqInfo) {
return basicACLErr(reqInfo)
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo, err)
}
}
return b.next.Search(request, &searchStreamBasicChecker{
checker: b.checker,
SearchStream: newWrappedSearchStream(stream, reqInfo),
info: reqInfo,
})
} }
func (b Service) Delete( func (b Service) Delete(
@ -422,14 +352,6 @@ func (b Service) Delete(
reqInfo.obj = obj reqInfo.obj = obj
if reqInfo.IsSoftAPECheck() {
if !b.checker.CheckBasicACL(reqInfo) {
return nil, basicACLErr(reqInfo)
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err)
}
}
return b.next.Delete(requestContext(ctx, reqInfo), request) return b.next.Delete(requestContext(ctx, reqInfo), request)
} }
@ -475,19 +397,7 @@ func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetOb
reqInfo.obj = obj reqInfo.obj = obj
if reqInfo.IsSoftAPECheck() { return b.next.GetRange(request, newWrappedRangeStream(stream, reqInfo))
if !b.checker.CheckBasicACL(reqInfo) {
return basicACLErr(reqInfo)
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return eACLErr(reqInfo, err)
}
}
return b.next.GetRange(request, &rangeStreamBasicChecker{
checker: b.checker,
GetObjectRangeStream: newWrappedRangeStream(stream, reqInfo),
info: reqInfo,
})
} }
func requestContext(ctx context.Context, reqInfo RequestInfo) context.Context { func requestContext(ctx context.Context, reqInfo RequestInfo) context.Context {
@ -496,7 +406,6 @@ func requestContext(ctx context.Context, reqInfo RequestInfo) context.Context {
ContainerOwner: reqInfo.ContainerOwner(), ContainerOwner: reqInfo.ContainerOwner(),
SenderKey: reqInfo.SenderKey(), SenderKey: reqInfo.SenderKey(),
Role: reqInfo.RequestRole(), Role: reqInfo.RequestRole(),
SoftAPECheck: reqInfo.IsSoftAPECheck(),
BearerToken: reqInfo.Bearer(), BearerToken: reqInfo.Bearer(),
}) })
} }
@ -546,14 +455,6 @@ func (b Service) GetRangeHash(
reqInfo.obj = obj reqInfo.obj = obj
if reqInfo.IsSoftAPECheck() {
if !b.checker.CheckBasicACL(reqInfo) {
return nil, basicACLErr(reqInfo)
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err)
}
}
return b.next.GetRangeHash(requestContext(ctx, reqInfo), request) return b.next.GetRangeHash(requestContext(ctx, reqInfo), request)
} }
@ -605,15 +506,6 @@ func (b Service) PutSingle(ctx context.Context, request *objectV2.PutSingleReque
reqInfo.obj = obj reqInfo.obj = obj
if reqInfo.IsSoftAPECheck() {
if !b.checker.CheckBasicACL(reqInfo) || !b.checker.StickyBitCheck(reqInfo, idOwner) {
return nil, basicACLErr(reqInfo)
}
if err := b.checker.CheckEACL(request, reqInfo); err != nil {
return nil, eACLErr(reqInfo, err)
}
}
return b.next.PutSingle(requestContext(ctx, reqInfo), request) return b.next.PutSingle(requestContext(ctx, reqInfo), request)
} }
@ -679,12 +571,6 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe
reqInfo.obj = obj reqInfo.obj = obj
if reqInfo.IsSoftAPECheck() {
if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, idOwner) {
return basicACLErr(reqInfo)
}
}
ctx = requestContext(ctx, reqInfo) ctx = requestContext(ctx, reqInfo)
} }
@ -723,32 +609,6 @@ func (p putStreamBasicChecker) CloseAndRecv(ctx context.Context) (*objectV2.PutR
return p.next.CloseAndRecv(ctx) return p.next.CloseAndRecv(ctx)
} }
func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
if _, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok {
if err := g.checker.CheckEACL(resp, g.info); err != nil {
return eACLErr(g.info, err)
}
}
return g.GetObjectStream.Send(resp)
}
func (g *rangeStreamBasicChecker) Send(resp *objectV2.GetRangeResponse) error {
if err := g.checker.CheckEACL(resp, g.info); err != nil {
return eACLErr(g.info, err)
}
return g.GetObjectRangeStream.Send(resp)
}
func (g *searchStreamBasicChecker) Send(resp *objectV2.SearchResponse) error {
if err := g.checker.CheckEACL(resp, g.info); err != nil {
return eACLErr(g.info, err)
}
return g.SearchStream.Send(resp)
}
func (p *patchStreamBasicChecker) Send(ctx context.Context, request *objectV2.PatchRequest) error { func (p *patchStreamBasicChecker) Send(ctx context.Context, request *objectV2.PatchRequest) error {
body := request.GetBody() body := request.GetBody()
if body == nil { if body == nil {

View file

@ -1,24 +1,5 @@
package v2 package v2
import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
)
// ACLChecker is an interface that must provide
// ACL related checks.
type ACLChecker interface {
// CheckBasicACL must return true only if request
// passes basic ACL validation.
CheckBasicACL(RequestInfo) bool
// CheckEACL must return non-nil error if request
// doesn't pass extended ACL validation.
CheckEACL(any, RequestInfo) error
// StickyBitCheck must return true only if sticky bit
// is disabled or enabled but request contains correct
// owner field.
StickyBitCheck(RequestInfo, user.ID) bool
}
// InnerRingFetcher is an interface that must provide // InnerRingFetcher is an interface that must provide
// Inner Ring information. // Inner Ring information.
type InnerRingFetcher interface { type InnerRingFetcher interface {

View file

@ -64,9 +64,6 @@ type Prm struct {
// An encoded container's owner user ID. // An encoded container's owner user ID.
ContainerOwner user.ID ContainerOwner user.ID
// If SoftAPECheck is set to true, then NoRuleFound is interpreted as allow.
SoftAPECheck bool
// The request's bearer token. It is used in order to check APE overrides with the token. // The request's bearer token. It is used in order to check APE overrides with the token.
BearerToken *bearer.Token BearerToken *bearer.Token
@ -109,6 +106,5 @@ func (c *checkerImpl) CheckAPE(ctx context.Context, prm Prm) error {
Container: prm.Container, Container: prm.Container,
ContainerOwner: prm.ContainerOwner, ContainerOwner: prm.ContainerOwner,
BearerToken: prm.BearerToken, BearerToken: prm.BearerToken,
SoftAPECheck: prm.SoftAPECheck,
}) })
} }

View file

@ -84,8 +84,6 @@ type getStreamBasicChecker struct {
role string role string
softAPECheck bool
bearerToken *bearer.Token bearerToken *bearer.Token
} }
@ -105,7 +103,6 @@ func (g *getStreamBasicChecker) Send(resp *objectV2.GetResponse) error {
SenderKey: hex.EncodeToString(g.senderKey), SenderKey: hex.EncodeToString(g.senderKey),
ContainerOwner: g.containerOwner, ContainerOwner: g.containerOwner,
Role: g.role, Role: g.role,
SoftAPECheck: g.softAPECheck,
BearerToken: g.bearerToken, BearerToken: g.bearerToken,
XHeaders: resp.GetMetaHeader().GetXHeaders(), XHeaders: resp.GetMetaHeader().GetXHeaders(),
} }
@ -142,7 +139,6 @@ func (c *Service) Get(request *objectV2.GetRequest, stream objectSvc.GetObjectSt
senderKey: reqCtx.SenderKey, senderKey: reqCtx.SenderKey,
containerOwner: reqCtx.ContainerOwner, containerOwner: reqCtx.ContainerOwner,
role: nativeSchemaRole(reqCtx.Role), role: nativeSchemaRole(reqCtx.Role),
softAPECheck: reqCtx.SoftAPECheck,
bearerToken: reqCtx.BearerToken, bearerToken: reqCtx.BearerToken,
}) })
} }
@ -174,7 +170,6 @@ func (p *putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutR
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
} }
@ -230,7 +225,6 @@ func (p *patchStreamBasicChecker) Send(ctx context.Context, request *objectV2.Pa
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
} }
@ -300,7 +294,6 @@ func (c *Service) Head(ctx context.Context, request *objectV2.HeadRequest) (*obj
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
}) })
@ -330,7 +323,6 @@ func (c *Service) Search(request *objectV2.SearchRequest, stream objectSvc.Searc
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
}) })
@ -360,7 +352,6 @@ func (c *Service) Delete(ctx context.Context, request *objectV2.DeleteRequest) (
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
}) })
@ -395,7 +386,6 @@ func (c *Service) GetRange(request *objectV2.GetRangeRequest, stream objectSvc.G
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
}) })
@ -425,7 +415,6 @@ func (c *Service) GetRangeHash(ctx context.Context, request *objectV2.GetRangeHa
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
} }
@ -461,7 +450,6 @@ func (c *Service) PutSingle(ctx context.Context, request *objectV2.PutSingleRequ
Role: nativeSchemaRole(reqCtx.Role), Role: nativeSchemaRole(reqCtx.Role),
SenderKey: hex.EncodeToString(reqCtx.SenderKey), SenderKey: hex.EncodeToString(reqCtx.SenderKey),
ContainerOwner: reqCtx.ContainerOwner, ContainerOwner: reqCtx.ContainerOwner,
SoftAPECheck: reqCtx.SoftAPECheck,
BearerToken: reqCtx.BearerToken, BearerToken: reqCtx.BearerToken,
XHeaders: request.GetMetaHeader().GetXHeaders(), XHeaders: request.GetMetaHeader().GetXHeaders(),
} }

View file

@ -20,7 +20,5 @@ type RequestContext struct {
Role acl.Role Role acl.Role
SoftAPECheck bool
BearerToken *bearer.Token BearerToken *bearer.Token
} }

View file

@ -81,7 +81,6 @@ func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
ContainerOwner: container.Value.Owner(), ContainerOwner: container.Value.Owner(),
PublicKey: publicKey, PublicKey: publicKey,
BearerToken: bt, BearerToken: bt,
SoftAPECheck: false,
}) })
} }