forked from TrueCloudLab/policy-engine
355 lines
9.9 KiB
Go
355 lines
9.9 KiB
Go
package iam
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
|
)
|
|
|
|
const PropertyKeyFilePath = "FilePath"
|
|
|
|
var supportedActionToNativeOpMap = map[string][]string{
|
|
supportedS3NativeActionDeleteObject: {native.MethodDeleteObject, native.MethodHeadObject},
|
|
supportedS3NativeActionHeadObject: {native.MethodHeadObject},
|
|
supportedS3NativeActionGetObject: {native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
|
supportedS3NativeActionPutObject: {native.MethodPutObject},
|
|
supportedS3NativeActionListBucket: {native.MethodGetContainer, native.MethodGetObject, native.MethodHeadObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject},
|
|
|
|
supportedS3NativeActionCreateBucket: {native.MethodPutContainer},
|
|
supportedS3NativeActionDeleteBucket: {native.MethodDeleteContainer},
|
|
supportedS3NativeActionListAllMyBucket: {native.MethodListContainers},
|
|
supportedS3NativeActionPutBucketACL: {native.MethodSetContainerEACL},
|
|
supportedS3NativeActionGetBucketACL: {native.MethodGetContainerEACL},
|
|
}
|
|
|
|
var containerNativeOperations = map[string]struct{}{
|
|
native.MethodPutContainer: {},
|
|
native.MethodDeleteContainer: {},
|
|
native.MethodGetContainer: {},
|
|
native.MethodListContainers: {},
|
|
native.MethodSetContainerEACL: {},
|
|
native.MethodGetContainerEACL: {},
|
|
}
|
|
|
|
var objectNativeOperations = map[string]struct{}{
|
|
native.MethodGetObject: {},
|
|
native.MethodPutObject: {},
|
|
native.MethodHeadObject: {},
|
|
native.MethodDeleteObject: {},
|
|
native.MethodSearchObject: {},
|
|
native.MethodRangeObject: {},
|
|
native.MethodHashObject: {},
|
|
}
|
|
|
|
const (
|
|
supportedS3NativeActionDeleteObject = "s3:DeleteObject"
|
|
supportedS3NativeActionGetObject = "s3:GetObject"
|
|
supportedS3NativeActionHeadObject = "s3:HeadObject"
|
|
supportedS3NativeActionPutObject = "s3:PutObject"
|
|
supportedS3NativeActionListBucket = "s3:ListBucket"
|
|
|
|
supportedS3NativeActionCreateBucket = "s3:CreateBucket"
|
|
supportedS3NativeActionDeleteBucket = "s3:DeleteBucket"
|
|
supportedS3NativeActionListAllMyBucket = "s3:ListAllMyBuckets"
|
|
supportedS3NativeActionPutBucketACL = "s3:PutBucketAcl"
|
|
supportedS3NativeActionGetBucketACL = "s3:GetBucketAcl"
|
|
)
|
|
|
|
type NativeResolver interface {
|
|
GetUserKey(account, name string) (string, error)
|
|
GetBucketInfo(bucket string) (*BucketInfo, error)
|
|
}
|
|
|
|
type BucketInfo struct {
|
|
Namespace string
|
|
Container string
|
|
}
|
|
|
|
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()
|
|
nativeActions, err := formNativeActionNames(action)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ruleAction := chain.Actions{Inverted: actionInverted, Names: nativeActions}
|
|
if len(ruleAction.Names) == 0 {
|
|
continue
|
|
}
|
|
|
|
resource, resourceInverted := statement.resource()
|
|
groupedResources, err := formNativeResourceNamesAndConditions(resource, resolver, getActionTypes(nativeActions))
|
|
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 {
|
|
var principalCondition []chain.Condition
|
|
if principal != Wildcard {
|
|
principalCondition = []chain.Condition{principalCondFn(principal)}
|
|
}
|
|
|
|
ruleConditions := append(principalCondition, 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 getActionTypes(nativeActions []string) ActionTypes {
|
|
var res ActionTypes
|
|
for _, action := range nativeActions {
|
|
if res.Object && res.Container {
|
|
break
|
|
}
|
|
|
|
_, isObj := objectNativeOperations[action]
|
|
_, isCnr := containerNativeOperations[action]
|
|
|
|
res.Object = isObj || action == Wildcard
|
|
res.Container = isCnr || action == Wildcard
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
type ActionTypes struct {
|
|
Object bool
|
|
Container bool
|
|
}
|
|
|
|
func formNativeResourceNamesAndConditions(names []string, resolver NativeResolver, actionTypes ActionTypes) ([]GroupedResources, error) {
|
|
if !actionTypes.Object && !actionTypes.Container {
|
|
return nil, ErrActionsNotApplicable
|
|
}
|
|
|
|
res := make([]GroupedResources, 0, len(names))
|
|
|
|
var combined []string
|
|
|
|
for _, resource := range names {
|
|
if err := validateResource(resource); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resource == Wildcard {
|
|
res = res[:0]
|
|
return append(res, formWildcardNativeResource(actionTypes)), nil
|
|
}
|
|
|
|
if !strings.HasPrefix(resource, s3ResourcePrefix) {
|
|
continue
|
|
}
|
|
|
|
var bkt, obj string
|
|
s3Resource := strings.TrimPrefix(resource, s3ResourcePrefix)
|
|
if s3Resource == Wildcard {
|
|
res = res[:0]
|
|
return append(res, formWildcardNativeResource(actionTypes)), nil
|
|
}
|
|
|
|
if sepIndex := strings.Index(s3Resource, "/"); sepIndex < 0 {
|
|
bkt = s3Resource
|
|
} else {
|
|
bkt = s3Resource[:sepIndex]
|
|
obj = s3Resource[sepIndex+1:]
|
|
if len(obj) == 0 {
|
|
obj = Wildcard
|
|
}
|
|
}
|
|
|
|
bktInfo, err := resolver.GetBucketInfo(bkt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if obj == Wildcard && actionTypes.Object { // this corresponds to arn:aws:s3:::BUCKET/ or arn:aws:s3:::BUCKET/*
|
|
combined = append(combined, fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container))
|
|
continue
|
|
}
|
|
if obj == "" && actionTypes.Container { // this corresponds to arn:aws:s3:::BUCKET
|
|
combined = append(combined, fmt.Sprintf(native.ResourceFormatNamespaceContainer, bktInfo.Namespace, bktInfo.Container))
|
|
continue
|
|
}
|
|
|
|
res = append(res, GroupedResources{
|
|
Names: []string{fmt.Sprintf(native.ResourceFormatNamespaceContainerObjects, bktInfo.Namespace, bktInfo.Container)},
|
|
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 formWildcardNativeResource(actionTypes ActionTypes) GroupedResources {
|
|
groupedNames := make([]string, 0, 2)
|
|
if actionTypes.Object {
|
|
groupedNames = append(groupedNames, native.ResourceFormatAllObjects)
|
|
}
|
|
if actionTypes.Container {
|
|
groupedNames = append(groupedNames, native.ResourceFormatAllContainers)
|
|
}
|
|
|
|
return GroupedResources{Names: groupedNames}
|
|
}
|
|
|
|
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, error) {
|
|
res := make([]string, 0, len(names))
|
|
|
|
for _, action := range names {
|
|
if err := validateAction(action); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if action == Wildcard {
|
|
return []string{Wildcard}, nil
|
|
}
|
|
|
|
if !strings.HasPrefix(action, s3ActionPrefix) {
|
|
continue
|
|
}
|
|
|
|
if strings.TrimPrefix(action, s3ActionPrefix) == Wildcard {
|
|
return []string{Wildcard}, nil
|
|
}
|
|
|
|
res = append(res, supportedActionToNativeOpMap[action]...)
|
|
}
|
|
|
|
return res, nil
|
|
}
|