Airat Arifullin
e2cb0640f1
* `ConvertEACLToAPE` is useful method which couldn't be imported out of frostfs-node so far as it has been in `internal` * Since `ConvertEACLToAPE` and related structures and unit-tests are placed in `pkg/util` Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
280 lines
8.4 KiB
Go
280 lines
8.4 KiB
Go
package ape
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
v2acl "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/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 apeRoleConds(role eacl.Role) (res []apechain.Condition) {
|
|
switch role {
|
|
case eacl.RoleSystem:
|
|
res = append(res,
|
|
apechain.Condition{
|
|
Op: apechain.CondStringEquals,
|
|
Kind: apechain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleContainer,
|
|
},
|
|
)
|
|
res = append(res,
|
|
apechain.Condition{
|
|
Op: apechain.CondStringEquals,
|
|
Kind: apechain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleIR,
|
|
},
|
|
)
|
|
case eacl.RoleOthers:
|
|
res = append(res,
|
|
apechain.Condition{
|
|
Op: apechain.CondStringEquals,
|
|
Kind: apechain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOthers,
|
|
},
|
|
)
|
|
case eacl.RoleUser:
|
|
res = append(res,
|
|
apechain.Condition{
|
|
Op: apechain.CondStringEquals,
|
|
Kind: apechain.KindRequest,
|
|
Key: nativeschema.PropertyKeyActorRole,
|
|
Value: nativeschema.PropertyValueContainerRoleOwner,
|
|
},
|
|
)
|
|
case eacl.RoleUnknown:
|
|
// such condition has no effect
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
|
|
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 {
|
|
rule.Condition = append(rule.Condition, apeRoleConds(target.Role())...)
|
|
for _, binKey := range target.BinaryKeys() {
|
|
var pubKeyCondition apechain.Condition
|
|
pubKeyCondition.Kind = apechain.KindRequest
|
|
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,
|
|
}
|
|
rule.Condition = append(rule.Condition, apeRoleConds(target.Role())...)
|
|
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.Kind = apechain.KindRequest
|
|
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.Kind = apechain.KindResource
|
|
isObject = true
|
|
} else if filter.From() == eacl.HeaderFromRequest {
|
|
cond.Kind = apechain.KindRequest
|
|
} 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)}
|
|
}
|