policy-engine/iam/converter_native.go
Denis Kirillov 5fa9d91903 [#17] iam: Add converter to native/s3 policy
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-11-21 11:45:41 +03:00

241 lines
6.5 KiB
Go

package iam
import (
"errors"
"fmt"
"strings"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
)
const PropertyKeyFilePath = "FilePath"
// ErrActionsNotApplicable occurs when failed to convert any actions.
var ErrActionsNotApplicable = errors.New("actions not applicable")
var actionToOpMap = map[string][]string{
supportedS3ActionDeleteObject: {native.MethodDeleteObject},
supportedS3ActionGetObject: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
supportedS3ActionHeadObject: {native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
supportedS3ActionPutObject: {native.MethodPutObject},
supportedS3ActionListBucket: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
}
const (
supportedS3ActionDeleteObject = "DeleteObject"
supportedS3ActionGetObject = "GetObject"
supportedS3ActionHeadObject = "HeadObject"
supportedS3ActionPutObject = "PutObject"
supportedS3ActionListBucket = "ListBucket"
)
type NativeResolver interface {
GetUserKey(account, name string) (string, error)
GetBucketCID(bucket string) (string, error)
}
func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, error) {
if err := p.Validate(ResourceBasedPolicyType); err != nil {
return nil, err
}
var engineChain chain.Chain
for _, statement := range p.Statement {
status := formStatus(statement)
action, actionInverted := statement.action()
ruleAction := chain.Actions{Inverted: actionInverted, Names: formNativeActionNames(action)}
if len(ruleAction.Names) == 0 {
continue
}
resource, resourceInverted := statement.resource()
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver)
if err != nil {
return nil, err
}
groupedConditions, err := convertToNativeChainCondition(statement.Conditions, resolver)
if err != nil {
return nil, err
}
splitConditions := splitGroupedConditions(groupedConditions)
principals, principalCondFn, err := getNativePrincipalsAndConditionFunc(statement, resolver)
if err != nil {
return nil, err
}
for _, groupedResource := range groupedResources {
for _, principal := range principals {
for _, conditions := range splitConditions {
ruleConditions := append([]chain.Condition{principalCondFn(principal)}, groupedResource.Conditions...)
r := chain.Rule{
Status: status,
Actions: ruleAction,
Resources: chain.Resources{
Inverted: resourceInverted,
Names: groupedResource.Names,
},
Condition: append(ruleConditions, conditions...),
}
engineChain.Rules = append(engineChain.Rules, r)
}
}
}
}
if len(engineChain.Rules) == 0 {
return nil, ErrActionsNotApplicable
}
return &engineChain, nil
}
func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeResolver) ([]string, formPrincipalConditionFunc, error) {
var principals []string
var op chain.ConditionType
statementPrincipal, inverted := statement.principal()
if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
principals = []string{Wildcard}
op = chain.CondStringLike
} else {
for principalType, principal := range statementPrincipal {
if principalType != AWSPrincipalType {
return nil, nil, fmt.Errorf("unsupported principal type '%s'", principalType)
}
parsedPrincipal, err := formNativePrincipal(principal, resolver)
if err != nil {
return nil, nil, fmt.Errorf("parse principal: %w", err)
}
principals = append(principals, parsedPrincipal...)
}
op = chain.CondStringEquals
if inverted {
op = chain.CondStringNotEquals
}
}
return principals, func(principal string) chain.Condition {
return chain.Condition{
Op: op,
Object: chain.ObjectRequest,
Key: native.PropertyKeyActorPublicKey,
Value: principal,
}
}, nil
}
func convertToNativeChainCondition(c Conditions, resolver NativeResolver) ([]GroupedConditions, error) {
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
for i := range gr.Conditions {
if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
gr.Conditions[i].Key = native.PropertyKeyActorPublicKey
val, err := formPrincipalKey(gr.Conditions[i].Value, resolver)
if err != nil {
return GroupedConditions{}, err
}
gr.Conditions[i].Value = val
}
}
return gr, nil
})
}
type GroupedResources struct {
Names []string
Conditions []chain.Condition
}
func formNativeResourceNamesAndConditions(names []string, resolver NativeResolver) ([]GroupedResources, error) {
res := make([]GroupedResources, 0, len(names))
var combined []string
for i := range names {
bkt, obj, err := parseResourceAsS3ARN(names[i])
if err != nil {
return nil, err
}
if bkt == Wildcard {
res = res[:0]
return append(res, GroupedResources{Names: []string{native.ResourceFormatAllObjects}}), nil
}
cnrID, err := resolver.GetBucketCID(bkt)
if err != nil {
return nil, err
}
resource := fmt.Sprintf(native.ResourceFormatRootContainerObjects, cnrID)
if obj == Wildcard {
combined = append(combined, resource)
continue
}
res = append(res, GroupedResources{
Names: []string{resource},
Conditions: []chain.Condition{
{
Op: chain.CondStringLike,
Object: chain.ObjectResource,
Key: PropertyKeyFilePath,
Value: obj,
},
},
})
}
if len(combined) != 0 {
res = append(res, GroupedResources{Names: combined})
}
return res, nil
}
func formNativePrincipal(principal []string, resolver NativeResolver) ([]string, error) {
res := make([]string, len(principal))
var err error
for i := range principal {
if res[i], err = formPrincipalKey(principal[i], resolver); err != nil {
return nil, err
}
}
return res, nil
}
func formPrincipalKey(principal string, resolver NativeResolver) (string, error) {
account, user, err := parsePrincipalAsIAMUser(principal)
if err != nil {
return "", err
}
key, err := resolver.GetUserKey(account, user)
if err != nil {
return "", fmt.Errorf("get user key: %w", err)
}
return key, nil
}
func formNativeActionNames(names []string) []string {
res := make([]string, 0, len(names))
for i := range names {
trimmed := strings.TrimPrefix(names[i], s3ActionPrefix)
if trimmed == Wildcard {
return []string{Wildcard}
}
res = append(res, actionToOpMap[trimmed]...)
}
return res
}