[#70] iam: Support aws:MultiFactorAuthPresent key #70

Merged
dkirillov merged 1 commit from mbiryukova/policy-engine:feature/mfa_condition_key into master 2024-04-17 06:36:21 +00:00
4 changed files with 91 additions and 0 deletions

View file

@ -1,6 +1,7 @@
package iam package iam
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
@ -80,6 +81,8 @@ var objectNativeOperations = map[string]struct{}{
native.MethodHashObject: {}, native.MethodHashObject: {},
} }
var errConditionKeyNotApplicable = errors.New("condition key is not applicable")
type NativeResolver interface { type NativeResolver interface {
GetUserKey(account, name string) (string, error) GetUserKey(account, name string) (string, error)
GetBucketInfo(bucket string) (*BucketInfo, error) GetBucketInfo(bucket string) (*BucketInfo, error)
@ -123,6 +126,9 @@ func ConvertToNativeChain(p Policy, resolver NativeResolver) (*chain.Chain, erro
groupedConditions, err := convertToNativeChainCondition(statement.Conditions, resolver) groupedConditions, err := convertToNativeChainCondition(statement.Conditions, resolver)
if err != nil { if err != nil {
if errors.Is(err, errConditionKeyNotApplicable) {
continue
}
return nil, err return nil, err
} }
splitConditions := splitGroupedConditions(groupedConditions) splitConditions := splitGroupedConditions(groupedConditions)
@ -219,6 +225,9 @@ func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeRes
func convertToNativeChainCondition(c Conditions, resolver NativeResolver) ([]GroupedConditions, error) { func convertToNativeChainCondition(c Conditions, resolver NativeResolver) ([]GroupedConditions, error) {
return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) { return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
for i := range gr.Conditions { for i := range gr.Conditions {
if gr.Conditions[i].Key == condKeyAWSMFAPresent {
return GroupedConditions{}, errConditionKeyNotApplicable
}
if gr.Conditions[i].Key == condKeyAWSPrincipalARN { if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
gr.Conditions[i].Key = native.PropertyKeyActorPublicKey gr.Conditions[i].Key = native.PropertyKeyActorPublicKey
val, err := formPrincipalKey(gr.Conditions[i].Value, resolver) val, err := formPrincipalKey(gr.Conditions[i].Value, resolver)

View file

@ -7,6 +7,8 @@ import (
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3" "git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
) )
const condKeyAWSMFAPresent = "aws:MultiFactorAuthPresent"
var actionToS3OpMap = map[string][]string{ var actionToS3OpMap = map[string][]string{
s3ActionAbortMultipartUpload: {s3ActionAbortMultipartUpload}, s3ActionAbortMultipartUpload: {s3ActionAbortMultipartUpload},
s3ActionCreateBucket: {s3ActionCreateBucket}, s3ActionCreateBucket: {s3ActionCreateBucket},
@ -175,6 +177,9 @@ func convertToS3ChainCondition(c Conditions, resolver S3Resolver) ([]GroupedCond
} }
gr.Conditions[i].Value = val gr.Conditions[i].Value = val
} }
if gr.Conditions[i].Key == condKeyAWSMFAPresent {
gr.Conditions[i].Key = s3.PropertyKeyAccessBoxAttrMFA
}
} }
return gr, nil return gr, nil

View file

@ -1732,6 +1732,80 @@ func TestTagsConditions(t *testing.T) {
require.ElementsMatch(t, expectedConditions, nativeChain.Rules[0].Condition) require.ElementsMatch(t, expectedConditions, nativeChain.Rules[0].Condition)
} }
func TestMFACondition(t *testing.T) {
policy := `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
`
expectedConditions := []chain.Condition{
{
Op: chain.CondStringEqualsIgnoreCase,
Object: chain.ObjectRequest,
Key: s3.PropertyKeyAccessBoxAttrMFA,
Value: "true",
},
}
var p Policy
err := json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
s3Chain, err := ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Len(t, s3Chain.Rules, 1)
require.ElementsMatch(t, expectedConditions, s3Chain.Rules[0].Condition)
_, err = ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.ErrorIs(t, err, ErrActionsNotApplicable)
policy = `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
`
expectedConditions[0].Value = "false"
err = json.Unmarshal([]byte(policy), &p)
require.NoError(t, err)
s3Chain, err = ConvertToS3Chain(p, newMockUserResolver(nil, nil, ""))
require.NoError(t, err)
require.Len(t, s3Chain.Rules, 1)
require.ElementsMatch(t, expectedConditions, s3Chain.Rules[0].Condition)
_, err = ConvertToNativeChain(p, newMockUserResolver(nil, nil, ""))
require.ErrorIs(t, err, ErrActionsNotApplicable)
}
func requireChainRulesMatch(t *testing.T, expected, actual []chain.Rule) { func requireChainRulesMatch(t *testing.T, expected, actual []chain.Rule) {
require.Equal(t, len(expected), len(actual), "length of chain rules differ") require.Equal(t, len(expected), len(actual), "length of chain rules differ")

View file

@ -11,6 +11,9 @@ const (
PropertyKeyFormatResourceTag = "aws:ResourceTag/%s" PropertyKeyFormatResourceTag = "aws:ResourceTag/%s"
PropertyKeyFormatRequestTag = "aws:RequestTag/%s" PropertyKeyFormatRequestTag = "aws:RequestTag/%s"
PropertyKeyAccessBoxAttrMFA = "AccessBox-Attribute/IAM-MFA"
PropertyKeyFormatAccessBoxAttr = "AccessBox-Attribute/%s"
ResourceFormatS3All = "arn:aws:s3:::*" ResourceFormatS3All = "arn:aws:s3:::*"
ResourceFormatS3Bucket = "arn:aws:s3:::%s" ResourceFormatS3Bucket = "arn:aws:s3:::%s"
ResourceFormatS3BucketObjects = "arn:aws:s3:::%s/*" ResourceFormatS3BucketObjects = "arn:aws:s3:::%s/*"