frostfs-node/internal/ape/converter.go

249 lines
7.7 KiB
Go

package ape
import (
"encoding/hex"
"fmt"
v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
)
type ConvertEACLError struct {
nested error
}
func (e *ConvertEACLError) Error() string {
if e == nil {
return ""
}
return "failed to convert eACL table to policy engine chain: " + e.nested.Error()
}
func (e *ConvertEACLError) Unwrap() error {
if e == nil {
return nil
}
return e.nested
}
// ConvertEACLToAPE converts eacl.Table to apechain.Chain.
func ConvertEACLToAPE(eaclTable *eacl.Table) (*apechain.Chain, error) {
if eaclTable == nil {
return nil, nil
}
res := &apechain.Chain{
MatchType: apechain.MatchTypeFirstMatch,
}
resource := getResource(eaclTable)
for _, eaclRecord := range eaclTable.Records() {
if len(eaclRecord.Targets()) == 0 {
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
// and https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L36
// such record doesn't have any effect
continue
}
st, err := actionToStatus(eaclRecord.Action())
if err != nil {
return nil, err
}
act, err := operationToAction(eaclRecord.Operation())
if err != nil {
return nil, err
}
if len(eaclRecord.Filters()) == 0 {
res.Rules = appendTargetsOnly(res.Rules, st, act, resource, eaclRecord.Targets())
} else {
res.Rules, err = appendTargetsAndFilters(res.Rules, st, act, resource, eaclRecord.Targets(), eaclRecord.Filters())
if err != nil {
return nil, err
}
}
}
return res, nil
}
func appendTargetsOnly(source []apechain.Rule, st apechain.Status, act apechain.Actions, res apechain.Resources, targets []eacl.Target) []apechain.Rule {
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
// role OR public key must be equal
rule := apechain.Rule{
Status: st,
Actions: act,
Resources: res,
Any: true,
}
for _, target := range targets {
var roleCondition apechain.Condition
roleCondition.Object = apechain.ObjectRequest
roleCondition.Key = nativeschema.PropertyKeyActorRole
roleCondition.Value = target.Role().String()
roleCondition.Op = apechain.CondStringEquals
rule.Condition = append(rule.Condition, roleCondition)
for _, binKey := range target.BinaryKeys() {
var pubKeyCondition apechain.Condition
pubKeyCondition.Object = apechain.ObjectRequest
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
pubKeyCondition.Value = hex.EncodeToString(binKey)
pubKeyCondition.Op = apechain.CondStringEquals
rule.Condition = append(rule.Condition, pubKeyCondition)
}
}
return append(source, rule)
}
func appendTargetsAndFilters(source []apechain.Rule, st apechain.Status, act apechain.Actions, res apechain.Resources,
targets []eacl.Target, filters []eacl.Filter,
) ([]apechain.Rule, error) {
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
// role OR public key must be equal
// so filters are repeated for each role and public key
var err error
for _, target := range targets {
rule := apechain.Rule{
Status: st,
Actions: act,
Resources: res,
}
var roleCondition apechain.Condition
roleCondition.Object = apechain.ObjectRequest
roleCondition.Key = nativeschema.PropertyKeyActorRole
roleCondition.Value = target.Role().String()
roleCondition.Op = apechain.CondStringEquals
rule.Condition = append(rule.Condition, roleCondition)
rule.Condition, err = appendFilters(rule.Condition, filters)
if err != nil {
return nil, err
}
source = append(source, rule)
for _, binKey := range target.BinaryKeys() {
rule := apechain.Rule{
Status: st,
Actions: act,
Resources: res,
}
var pubKeyCondition apechain.Condition
pubKeyCondition.Object = apechain.ObjectRequest
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
pubKeyCondition.Value = hex.EncodeToString(binKey)
pubKeyCondition.Op = apechain.CondStringEquals
rule.Condition = append(rule.Condition, pubKeyCondition)
rule.Condition, err = appendFilters(rule.Condition, filters)
if err != nil {
return nil, err
}
source = append(source, rule)
}
}
return source, nil
}
func appendFilters(source []apechain.Condition, filters []eacl.Filter) ([]apechain.Condition, error) {
for _, filter := range filters {
var cond apechain.Condition
var isObject bool
if filter.From() == eacl.HeaderFromObject {
cond.Object = apechain.ObjectResource
isObject = true
} else if filter.From() == eacl.HeaderFromRequest {
cond.Object = apechain.ObjectRequest
} else {
return nil, &ConvertEACLError{nested: fmt.Errorf("unknown filter from: %d", filter.From())}
}
if filter.Matcher() == eacl.MatchStringEqual {
cond.Op = apechain.CondStringEquals
} else if filter.Matcher() == eacl.MatchStringNotEqual {
cond.Op = apechain.CondStringNotEquals
} else {
return nil, &ConvertEACLError{nested: fmt.Errorf("unknown filter matcher: %d", filter.Matcher())}
}
cond.Key = eaclKeyToAPEKey(filter.Key(), isObject)
cond.Value = filter.Value()
source = append(source, cond)
}
return source, nil
}
func eaclKeyToAPEKey(key string, isObject bool) string {
if !isObject {
return key
}
switch key {
default:
return key
case v2acl.FilterObjectVersion:
return nativeschema.PropertyKeyObjectVersion
case v2acl.FilterObjectID:
return nativeschema.PropertyKeyObjectID
case v2acl.FilterObjectContainerID:
return nativeschema.PropertyKeyObjectContainerID
case v2acl.FilterObjectOwnerID:
return nativeschema.PropertyKeyObjectOwnerID
case v2acl.FilterObjectCreationEpoch:
return nativeschema.PropertyKeyObjectCreationEpoch
case v2acl.FilterObjectPayloadLength:
return nativeschema.PropertyKeyObjectPayloadLength
case v2acl.FilterObjectPayloadHash:
return nativeschema.PropertyKeyObjectPayloadHash
case v2acl.FilterObjectType:
return nativeschema.PropertyKeyObjectType
case v2acl.FilterObjectHomomorphicHash:
return nativeschema.PropertyKeyObjectHomomorphicHash
}
}
func getResource(eaclTable *eacl.Table) apechain.Resources {
cnrID, isSet := eaclTable.CID()
if isSet {
return apechain.Resources{
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
}
}
return apechain.Resources{
Names: []string{nativeschema.ResourceFormatRootObjects},
}
}
func actionToStatus(a eacl.Action) (apechain.Status, error) {
switch a {
case eacl.ActionAllow:
return apechain.Allow, nil
case eacl.ActionDeny:
return apechain.AccessDenied, nil
default:
return apechain.NoRuleFound, &ConvertEACLError{nested: fmt.Errorf("unknown action: %d", a)}
}
}
var eaclOperationToEngineAction = map[eacl.Operation]apechain.Actions{
eacl.OperationGet: {Names: []string{nativeschema.MethodGetObject}},
eacl.OperationHead: {Names: []string{nativeschema.MethodHeadObject}},
eacl.OperationPut: {Names: []string{nativeschema.MethodPutObject}},
eacl.OperationDelete: {Names: []string{nativeschema.MethodDeleteObject}},
eacl.OperationSearch: {Names: []string{nativeschema.MethodSearchObject}},
eacl.OperationRange: {Names: []string{nativeschema.MethodRangeObject}},
eacl.OperationRangeHash: {Names: []string{nativeschema.MethodHashObject}},
}
func operationToAction(op eacl.Operation) (apechain.Actions, error) {
if v, ok := eaclOperationToEngineAction[op]; ok {
return v, nil
}
return apechain.Actions{}, &ConvertEACLError{nested: fmt.Errorf("unknown operation: %d", op)}
}