[#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
Showing only changes of commit 04a79f57ef - Show all commits

View file

@ -1,6 +1,7 @@
package iam
import (
"errors"
"fmt"
"strings"
@ -80,6 +81,8 @@ var objectNativeOperations = map[string]struct{}{
native.MethodHashObject: {},
}
var errConditionKeyNotApplicable = errors.New("condition key is not applicable")
type NativeResolver interface {
GetUserKey(account, name string) (string, 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)
if err != nil {
if errors.Is(err, errConditionKeyNotApplicable) {
continue
}
return nil, err
}
splitConditions := splitGroupedConditions(groupedConditions)
@ -219,6 +225,9 @@ func getNativePrincipalsAndConditionFunc(statement Statement, resolver NativeRes
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 == condKeyAWSMFAPresent {
fyrchik marked this conversation as resolved Outdated

These 5 lines can be replaced with a simple for loop, do we really need to use an external dependency here?
I see it is already used in another place, but it will be removed after we bumped minimum go version.

These 5 lines can be replaced with a simple `for` loop, do we really need to use an external dependency here? I see it is already used in another place, but it will be removed after we bumped minimum go version.

slices package contains this function too.

`slices` package contains this function too.

Probably we can use already existing for loop below (line 234)

Probably we can use already existing `for` loop below (line 234)

I thought to check key before possible arn resolving, but can use loop below if it doesn't make sense to separate

I thought to check key before possible arn resolving, but can use loop below if it doesn't make sense to separate

I suppose it's ok to use the same loop

I suppose it's ok to use the same loop
return GroupedConditions{}, errConditionKeyNotApplicable
}
if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
gr.Conditions[i].Key = native.PropertyKeyActorPublicKey
val, err := formPrincipalKey(gr.Conditions[i].Value, resolver)

View file

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

View file

@ -1732,6 +1732,80 @@ func TestTagsConditions(t *testing.T) {
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) {
require.Equal(t, len(expected), len(actual), "length of chain rules differ")

View file

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