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)} }