forked from TrueCloudLab/frostfs-node
247 lines
7.9 KiB
Go
247 lines
7.9 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"
|
||
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||
|
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||
|
)
|
||
|
|
||
|
type ConvertEACLError struct {
|
||
|
nested error
|
||
|
}
|
||
|
|
||
|
func (e *ConvertEACLError) Error() string {
|
||
|
if e == nil {
|
||
|
return ""
|
||
|
}
|
||
|
return fmt.Sprintf("failed to convert eACL table to policy engine chain: %s", e.nested.Error())
|
||
|
}
|
||
|
|
||
|
func (e *ConvertEACLError) Unwrap() error {
|
||
|
if e == nil {
|
||
|
return nil
|
||
|
}
|
||
|
return e.nested
|
||
|
}
|
||
|
|
||
|
// ConvertEACLToAPE converts eacl.Table to policyengine.Chain.
|
||
|
func ConvertEACLToAPE(eaclTable *eacl.Table) (*policyengine.Chain, error) {
|
||
|
if eaclTable == nil {
|
||
|
return nil, nil
|
||
|
}
|
||
|
res := &policyengine.Chain{}
|
||
|
|
||
|
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 []policyengine.Rule, st policyengine.Status, act policyengine.Actions, res policyengine.Resources, targets []eacl.Target) []policyengine.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 := policyengine.Rule{
|
||
|
Status: st,
|
||
|
Actions: act,
|
||
|
Resources: res,
|
||
|
Any: true,
|
||
|
}
|
||
|
for _, target := range targets {
|
||
|
var roleCondition policyengine.Condition
|
||
|
roleCondition.Object = policyengine.ObjectRequest
|
||
|
roleCondition.Key = nativeschema.PropertyKeyActorRole
|
||
|
roleCondition.Value = target.Role().String()
|
||
|
roleCondition.Op = policyengine.CondStringEquals
|
||
|
rule.Condition = append(rule.Condition, roleCondition)
|
||
|
|
||
|
for _, binKey := range target.BinaryKeys() {
|
||
|
var pubKeyCondition policyengine.Condition
|
||
|
pubKeyCondition.Object = policyengine.ObjectRequest
|
||
|
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
|
||
|
pubKeyCondition.Value = hex.EncodeToString(binKey)
|
||
|
pubKeyCondition.Op = policyengine.CondStringEquals
|
||
|
rule.Condition = append(rule.Condition, pubKeyCondition)
|
||
|
}
|
||
|
}
|
||
|
return append(source, rule)
|
||
|
}
|
||
|
|
||
|
func appendTargetsAndFilters(source []policyengine.Rule, st policyengine.Status, act policyengine.Actions, res policyengine.Resources,
|
||
|
targets []eacl.Target, filters []eacl.Filter,
|
||
|
) ([]policyengine.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 := policyengine.Rule{
|
||
|
Status: st,
|
||
|
Actions: act,
|
||
|
Resources: res,
|
||
|
}
|
||
|
var roleCondition policyengine.Condition
|
||
|
roleCondition.Object = policyengine.ObjectRequest
|
||
|
roleCondition.Key = nativeschema.PropertyKeyActorRole
|
||
|
roleCondition.Value = target.Role().String()
|
||
|
roleCondition.Op = policyengine.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 := policyengine.Rule{
|
||
|
Status: st,
|
||
|
Actions: act,
|
||
|
Resources: res,
|
||
|
}
|
||
|
var pubKeyCondition policyengine.Condition
|
||
|
pubKeyCondition.Object = policyengine.ObjectRequest
|
||
|
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
|
||
|
pubKeyCondition.Value = hex.EncodeToString(binKey)
|
||
|
pubKeyCondition.Op = policyengine.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 []policyengine.Condition, filters []eacl.Filter) ([]policyengine.Condition, error) {
|
||
|
for _, filter := range filters {
|
||
|
var cond policyengine.Condition
|
||
|
var isObject bool
|
||
|
if filter.From() == eacl.HeaderFromObject {
|
||
|
cond.Object = policyengine.ObjectResource
|
||
|
isObject = true
|
||
|
} else if filter.From() == eacl.HeaderFromRequest {
|
||
|
cond.Object = policyengine.ObjectRequest
|
||
|
} else {
|
||
|
return nil, &ConvertEACLError{nested: fmt.Errorf("unknown filter from: %d", filter.From())}
|
||
|
}
|
||
|
|
||
|
if filter.Matcher() == eacl.MatchStringEqual {
|
||
|
cond.Op = policyengine.CondStringEquals
|
||
|
} else if filter.Matcher() == eacl.MatchStringNotEqual {
|
||
|
cond.Op = policyengine.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) policyengine.Resources {
|
||
|
cnrID, isSet := eaclTable.CID()
|
||
|
if isSet {
|
||
|
return policyengine.Resources{
|
||
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
|
||
|
}
|
||
|
}
|
||
|
return policyengine.Resources{
|
||
|
Names: []string{nativeschema.ResourceFormatRootObjects},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func actionToStatus(a eacl.Action) (policyengine.Status, error) {
|
||
|
switch a {
|
||
|
case eacl.ActionAllow:
|
||
|
return policyengine.Allow, nil
|
||
|
case eacl.ActionDeny:
|
||
|
return policyengine.AccessDenied, nil
|
||
|
default:
|
||
|
return policyengine.NoRuleFound, &ConvertEACLError{nested: fmt.Errorf("unknown action: %d", a)}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var eaclOperationToEngineAction = map[eacl.Operation]policyengine.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) (policyengine.Actions, error) {
|
||
|
if v, ok := eaclOperationToEngineAction[op]; ok {
|
||
|
return v, nil
|
||
|
}
|
||
|
return policyengine.Actions{}, &ConvertEACLError{nested: fmt.Errorf("unknown operation: %d", op)}
|
||
|
}
|