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