Airat Arifullin
0b367007fc
All checks were successful
DCO action / DCO (pull_request) Successful in 5m23s
Build / Build Components (1.22) (pull_request) Successful in 7m33s
Build / Build Components (1.21) (pull_request) Successful in 7m43s
Tests and linters / Lint (pull_request) Successful in 8m51s
Tests and linters / Tests (1.22) (pull_request) Successful in 14m33s
Pre-commit hooks / Pre-commit (pull_request) Successful in 15m48s
Vulncheck / Vulncheck (pull_request) Successful in 15m12s
Tests and linters / Tests (1.21) (pull_request) Successful in 16m38s
Tests and linters / gopls check (pull_request) Successful in 19m24s
Tests and linters / Staticcheck (pull_request) Successful in 21m29s
Tests and linters / Tests with -race (pull_request) Successful in 23m16s
* Resolve conflicts for apemanager since api-go contains ape and apemanager packages and SDK only ape package. Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
244 lines
6.6 KiB
Go
244 lines
6.6 KiB
Go
package apemanager
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
|
|
apeV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/ape"
|
|
apemanagerV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
|
session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
|
ape_contract "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/contract_storage"
|
|
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
|
apemanager_errors "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/apemanager/errors"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
|
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
policy_engine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
|
"github.com/mr-tron/base58/base58"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var errEmptyBodySignature = errors.New("malformed request: empty body signature")
|
|
|
|
type cfg struct {
|
|
log *logger.Logger
|
|
}
|
|
|
|
type Service struct {
|
|
cfg
|
|
|
|
cnrSrc containercore.Source
|
|
|
|
contractStorage ape_contract.ProxyAdaptedContractStorage
|
|
}
|
|
|
|
type Option func(*cfg)
|
|
|
|
func New(cnrSrc containercore.Source, contractStorage ape_contract.ProxyAdaptedContractStorage, opts ...Option) *Service {
|
|
s := &Service{
|
|
cnrSrc: cnrSrc,
|
|
|
|
contractStorage: contractStorage,
|
|
}
|
|
|
|
for i := range opts {
|
|
opts[i](&s.cfg)
|
|
}
|
|
|
|
if s.log == nil {
|
|
s.log = &logger.Logger{Logger: zap.NewNop()}
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func WithLogger(log *logger.Logger) Option {
|
|
return func(c *cfg) {
|
|
c.log = log
|
|
}
|
|
}
|
|
|
|
var _ Server = (*Service)(nil)
|
|
|
|
// validateContainerTargetRequest validates request for the container target.
|
|
// It checks if request actor is the owner of the container, otherwise it denies the request.
|
|
func (s *Service) validateContainerTargetRequest(cid string, pubKey *keys.PublicKey) error {
|
|
var cidSDK cidSDK.ID
|
|
if err := cidSDK.DecodeString(cid); err != nil {
|
|
return fmt.Errorf("invalid CID format: %w", err)
|
|
}
|
|
isOwner, err := s.isActorContainerOwner(cidSDK, pubKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check owner: %w", err)
|
|
}
|
|
if !isOwner {
|
|
return apemanager_errors.ErrAPEManagerAccessDenied("actor must be container owner")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) AddChain(_ context.Context, req *apemanagerV2.AddChainRequest) (*apemanagerV2.AddChainResponse, error) {
|
|
pub, err := getSignaturePublicKey(req.GetVerificationHeader())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
chain, err := decodeAndValidateChain(req.GetBody().GetChain().GetKind().(*apeV2.ChainRaw).GetRaw())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(chain.ID) == 0 {
|
|
const randomIDLength = 10
|
|
randID, err := base58Str(randomIDLength)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("randomize chain ID error: %w", err)
|
|
}
|
|
chain.ID = apechain.ID(randID)
|
|
}
|
|
|
|
var target policy_engine.Target
|
|
|
|
switch targetType := req.GetBody().GetTarget().GetTargetType(); targetType {
|
|
case apeV2.TargetTypeContainer:
|
|
reqCID := req.GetBody().GetTarget().GetName()
|
|
if err = s.validateContainerTargetRequest(reqCID, pub); err != nil {
|
|
return nil, err
|
|
}
|
|
target = policy_engine.ContainerTarget(reqCID)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported target type: %s", targetType)
|
|
}
|
|
|
|
if _, _, err = s.contractStorage.AddMorphRuleChain(apechain.Ingress, target, &chain); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body := new(apemanagerV2.AddChainResponseBody)
|
|
body.SetChainID(chain.ID)
|
|
|
|
resp := new(apemanagerV2.AddChainResponse)
|
|
resp.SetBody(body)
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *Service) RemoveChain(_ context.Context, req *apemanagerV2.RemoveChainRequest) (*apemanagerV2.RemoveChainResponse, error) {
|
|
pub, err := getSignaturePublicKey(req.GetVerificationHeader())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var target policy_engine.Target
|
|
|
|
switch targetType := req.GetBody().GetTarget().GetTargetType(); targetType {
|
|
case apeV2.TargetTypeContainer:
|
|
reqCID := req.GetBody().GetTarget().GetName()
|
|
if err = s.validateContainerTargetRequest(reqCID, pub); err != nil {
|
|
return nil, err
|
|
}
|
|
target = policy_engine.ContainerTarget(reqCID)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported target type: %s", targetType)
|
|
}
|
|
|
|
if _, _, err = s.contractStorage.RemoveMorphRuleChain(apechain.Ingress, target, req.GetBody().GetChainID()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body := new(apemanagerV2.RemoveChainResponseBody)
|
|
|
|
resp := new(apemanagerV2.RemoveChainResponse)
|
|
resp.SetBody(body)
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *Service) ListChains(_ context.Context, req *apemanagerV2.ListChainsRequest) (*apemanagerV2.ListChainsResponse, error) {
|
|
pub, err := getSignaturePublicKey(req.GetVerificationHeader())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var target policy_engine.Target
|
|
|
|
switch targetType := req.GetBody().GetTarget().GetTargetType(); targetType {
|
|
case apeV2.TargetTypeContainer:
|
|
reqCID := req.GetBody().GetTarget().GetName()
|
|
if err = s.validateContainerTargetRequest(reqCID, pub); err != nil {
|
|
return nil, err
|
|
}
|
|
target = policy_engine.ContainerTarget(reqCID)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported target type: %s", targetType)
|
|
}
|
|
|
|
chs, err := s.contractStorage.ListMorphRuleChains(apechain.Ingress, target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res := make([]*apeV2.Chain, 0, len(chs))
|
|
for _, ch := range chs {
|
|
v2chraw := new(apeV2.ChainRaw)
|
|
v2chraw.SetRaw(ch.Bytes())
|
|
|
|
v2ch := new(apeV2.Chain)
|
|
v2ch.SetKind(v2chraw)
|
|
|
|
res = append(res, v2ch)
|
|
}
|
|
|
|
body := new(apemanagerV2.ListChainsResponseBody)
|
|
body.SetChains(res)
|
|
|
|
resp := new(apemanagerV2.ListChainsResponse)
|
|
resp.SetBody(body)
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func getSignaturePublicKey(vh *session.RequestVerificationHeader) (*keys.PublicKey, error) {
|
|
for vh.GetOrigin() != nil {
|
|
vh = vh.GetOrigin()
|
|
}
|
|
sig := vh.GetBodySignature()
|
|
if sig == nil {
|
|
return nil, errEmptyBodySignature
|
|
}
|
|
key, err := keys.NewPublicKeyFromBytes(sig.GetKey(), elliptic.P256())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid signature key: %w", err)
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
func (s *Service) isActorContainerOwner(cid cidSDK.ID, pk *keys.PublicKey) (bool, error) {
|
|
var actor user.ID
|
|
user.IDFromKey(&actor, (ecdsa.PublicKey)(*pk))
|
|
actorOwnerID := new(refs.OwnerID)
|
|
actor.WriteToV2(actorOwnerID)
|
|
|
|
cnr, err := s.cnrSrc.Get(cid)
|
|
if err != nil {
|
|
return false, fmt.Errorf("get container error: %w", err)
|
|
}
|
|
return cnr.Value.Owner().Equals(actor), nil
|
|
}
|
|
|
|
// base58Str generates base58 string.
|
|
func base58Str(n int) (string, error) {
|
|
b := make([]byte, n)
|
|
_, err := rand.Read(b)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base58.FastBase58Encoding(b), nil
|
|
}
|