forked from TrueCloudLab/frostfs-node
Compare commits
No commits in common. "431ed829b59f70a90437ad99dcd0efe31b177a6d" and "764450d04a38bedc3d1b38a098a7546983fcdf93" have entirely different histories.
431ed829b5
...
764450d04a
39 changed files with 658 additions and 126 deletions
0
.forgejo/logo.svg → .github/logo.svg
vendored
0
.forgejo/logo.svg → .github/logo.svg
vendored
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./.forgejo/logo.svg" width="500px" alt="FrostFS">
|
<img src="./.github/logo.svg" width="500px" alt="FrostFS">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|
|
@ -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, constants.ConsensusAccountName)
|
ac, err := helper.NewLocalActor(cmd, c)
|
||||||
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
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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"
|
||||||
|
@ -30,11 +31,7 @@ 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
|
||||||
|
@ -56,8 +53,8 @@ func NewLocalActor(cmd *cobra.Command, c actor.RPCActor, accName string) (*Local
|
||||||
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, accName)
|
acc, err := GetWalletAccount(w, constants.CommitteeAccountName)
|
||||||
commonCmd.ExitOnErr(cmd, fmt.Sprintf("can't find %s account: %%w", accName), err)
|
commonCmd.ExitOnErr(cmd, "can't find committee account: %w", err)
|
||||||
accounts = append(accounts, acc)
|
accounts = append(accounts, acc)
|
||||||
}
|
}
|
||||||
act, err = actor.New(c, []actor.SignerAccount{{
|
act, err = actor.New(c, []actor.SignerAccount{{
|
||||||
|
|
|
@ -2,7 +2,6 @@ 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"
|
||||||
|
@ -16,7 +15,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, constants.CommitteeAccountName)
|
ac, err := helper.NewLocalActor(cmd, c)
|
||||||
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)
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -13,7 +14,6 @@ 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,16 +163,6 @@ 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)
|
||||||
|
@ -199,38 +189,22 @@ func (repl *policyPlaygroundREPL) run() error {
|
||||||
"rm": repl.handleRemove,
|
"rm": repl.handleRemove,
|
||||||
"eval": repl.handleEval,
|
"eval": repl.handleEval,
|
||||||
}
|
}
|
||||||
|
for reader := bufio.NewReader(os.Stdin); ; {
|
||||||
rl, err := readline.NewEx(&readline.Config{
|
fmt.Print("> ")
|
||||||
Prompt: "> ",
|
line, err := reader.ReadString('\n')
|
||||||
InterruptPrompt: "^C",
|
|
||||||
AutoComplete: policyPlaygroundCompleter,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error initializing readline: %w", err)
|
|
||||||
}
|
|
||||||
defer rl.Close()
|
|
||||||
|
|
||||||
var exit bool
|
|
||||||
for {
|
|
||||||
line, err := rl.Readline()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, readline.ErrInterrupt) {
|
if err == io.EOF {
|
||||||
if exit {
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exit = true
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("reading line: %w", err)
|
return fmt.Errorf("reading line: %v", 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]
|
||||||
if handler, exists := cmdHandlers[cmd]; exists {
|
handler, exists := cmdHandlers[cmd]
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ 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"
|
||||||
|
@ -31,27 +30,5 @@ 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,8 +26,5 @@ 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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,9 +64,6 @@ 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)
|
||||||
|
|
|
@ -166,9 +166,6 @@ 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)
|
||||||
|
|
|
@ -19,6 +19,7 @@ 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"
|
||||||
|
@ -38,6 +39,7 @@ 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"
|
||||||
|
@ -218,9 +220,6 @@ 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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,10 +458,17 @@ 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),
|
||||||
)
|
)
|
||||||
|
|
|
@ -61,8 +61,5 @@ 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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 relevant issues in `#123` format (if possible)
|
* make sure all changes have references to GitHub 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 release (if not automated)
|
### Make a proper GitHub release (if not automated)
|
||||||
|
|
||||||
Edit an automatically-created release on git.frostfs.info, copy things from `CHANGELOG.md`.
|
Edit an automatically-created release on GitHub, 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 milestone
|
### Close GitHub 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.
|
||||||
|
|
||||||
|
|
|
@ -412,12 +412,12 @@ object:
|
||||||
- $attribute:ClusterName
|
- $attribute:ClusterName
|
||||||
```
|
```
|
||||||
|
|
||||||
| 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 requests. |
|
| `get.priority` | `[]string` | | List of metrics of nodes for prioritization. Used for computing response on GET and SEARCH requests. |
|
||||||
|
|
||||||
# `runtime` section
|
# `runtime` section
|
||||||
Contains runtime parameters.
|
Contains runtime parameters.
|
||||||
|
|
|
@ -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 `.forgejo/workflows/*.yml` files:
|
There is `go` section in `.github/workflows/go.yaml` file:
|
||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|
|
@ -45,8 +45,6 @@ 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
|
||||||
|
@ -70,6 +68,8 @@ 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,8 +93,6 @@ 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
|
||||||
|
@ -116,6 +114,8 @@ 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) {
|
||||||
|
|
|
@ -58,7 +58,6 @@ 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)
|
||||||
|
@ -69,6 +68,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,6 @@ 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)
|
||||||
|
@ -67,6 +66,8 @@ 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
|
||||||
|
|
|
@ -70,7 +70,6 @@ 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)
|
||||||
|
@ -81,6 +80,8 @@ 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()
|
||||||
|
|
|
@ -7,7 +7,6 @@ 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
|
||||||
|
@ -99,10 +98,6 @@ 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
|
||||||
|
|
|
@ -32,7 +32,6 @@ 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)
|
||||||
|
|
|
@ -56,7 +56,6 @@ 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)
|
||||||
|
@ -67,6 +66,8 @@ 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
|
||||||
|
|
|
@ -65,15 +65,6 @@ 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
|
||||||
|
@ -83,6 +74,16 @@ 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)
|
||||||
|
|
|
@ -51,7 +51,6 @@ 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)
|
||||||
|
@ -62,6 +61,8 @@ 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{})
|
||||||
|
|
||||||
|
@ -98,7 +99,6 @@ 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,6 +108,8 @@ 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)
|
||||||
|
|
|
@ -44,6 +44,9 @@ 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.
|
||||||
|
@ -101,7 +104,7 @@ func (c *checkerCoreImpl) CheckAPE(prm CheckPrm) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if found && status == apechain.Allow {
|
if !found && prm.SoftAPECheck || 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())
|
||||||
|
|
262
pkg/services/object/acl/acl.go
Normal file
262
pkg/services/object/acl/acl.go
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
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
|
||||||
|
}
|
89
pkg/services/object/acl/acl_test.go
Normal file
89
pkg/services/object/acl/acl_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
const invalidRequestMessage = "malformed request"
|
const invalidRequestMessage = "malformed request"
|
||||||
|
@ -18,3 +20,22 @@ 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
|
||||||
|
}
|
||||||
|
|
30
pkg/services/object/acl/v2/errors_test.go
Normal file
30
pkg/services/object/acl/v2/errors_test.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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")
|
||||||
|
}
|
|
@ -104,6 +104,13 @@ 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 {
|
||||||
|
|
|
@ -41,6 +41,30 @@ 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)
|
||||||
|
|
||||||
|
@ -49,6 +73,8 @@ type cfg struct {
|
||||||
|
|
||||||
containers container.Source
|
containers container.Source
|
||||||
|
|
||||||
|
checker ACLChecker
|
||||||
|
|
||||||
irFetcher InnerRingFetcher
|
irFetcher InnerRingFetcher
|
||||||
|
|
||||||
nm netmap.Source
|
nm netmap.Source
|
||||||
|
@ -60,6 +86,7 @@ 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 {
|
||||||
|
@ -68,6 +95,7 @@ func New(next object.ServiceServer,
|
||||||
next: next,
|
next: next,
|
||||||
nm: nm,
|
nm: nm,
|
||||||
irFetcher: irf,
|
irFetcher: irf,
|
||||||
|
checker: acl,
|
||||||
containers: cs,
|
containers: cs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +123,7 @@ 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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -120,6 +149,7 @@ 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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -145,6 +175,7 @@ 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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -200,7 +231,19 @@ func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream
|
||||||
|
|
||||||
reqInfo.obj = obj
|
reqInfo.obj = obj
|
||||||
|
|
||||||
return b.next.Get(request, newWrappedGetObjectStreamStream(stream, reqInfo))
|
if reqInfo.IsSoftAPECheck() {
|
||||||
|
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) {
|
||||||
|
@ -266,7 +309,22 @@ func (b Service) Head(
|
||||||
|
|
||||||
reqInfo.obj = obj
|
reqInfo.obj = obj
|
||||||
|
|
||||||
return b.next.Head(requestContext(ctx, reqInfo), request)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -304,7 +362,19 @@ func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStr
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.next.Search(request, newWrappedSearchStream(stream, reqInfo))
|
if reqInfo.IsSoftAPECheck() {
|
||||||
|
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(
|
||||||
|
@ -352,6 +422,14 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,7 +475,19 @@ func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetOb
|
||||||
|
|
||||||
reqInfo.obj = obj
|
reqInfo.obj = obj
|
||||||
|
|
||||||
return b.next.GetRange(request, newWrappedRangeStream(stream, reqInfo))
|
if reqInfo.IsSoftAPECheck() {
|
||||||
|
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 {
|
||||||
|
@ -406,6 +496,7 @@ 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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -455,6 +546,14 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,6 +605,15 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,6 +679,12 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -609,6 +723,32 @@ 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 {
|
||||||
|
|
|
@ -1,5 +1,24 @@
|
||||||
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 {
|
||||||
|
|
|
@ -64,6 +64,9 @@ 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
|
||||||
|
|
||||||
|
@ -106,5 +109,6 @@ 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,8 @@ type getStreamBasicChecker struct {
|
||||||
|
|
||||||
role string
|
role string
|
||||||
|
|
||||||
|
softAPECheck bool
|
||||||
|
|
||||||
bearerToken *bearer.Token
|
bearerToken *bearer.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +105,7 @@ 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(),
|
||||||
}
|
}
|
||||||
|
@ -139,6 +142,7 @@ 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -170,6 +174,7 @@ 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(),
|
||||||
}
|
}
|
||||||
|
@ -225,6 +230,7 @@ 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(),
|
||||||
}
|
}
|
||||||
|
@ -294,6 +300,7 @@ 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(),
|
||||||
})
|
})
|
||||||
|
@ -323,6 +330,7 @@ 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(),
|
||||||
})
|
})
|
||||||
|
@ -352,6 +360,7 @@ 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(),
|
||||||
})
|
})
|
||||||
|
@ -386,6 +395,7 @@ 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(),
|
||||||
})
|
})
|
||||||
|
@ -415,6 +425,7 @@ 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(),
|
||||||
}
|
}
|
||||||
|
@ -450,6 +461,7 @@ 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(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,5 +20,7 @@ type RequestContext struct {
|
||||||
|
|
||||||
Role acl.Role
|
Role acl.Role
|
||||||
|
|
||||||
|
SoftAPECheck bool
|
||||||
|
|
||||||
BearerToken *bearer.Token
|
BearerToken *bearer.Token
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue