generated from TrueCloudLab/basic
[#70] iam: Support aws:MultiFactorAuthPresent key #70
4 changed files with 91 additions and 0 deletions
|
@ -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 {
|
||||||
fyrchik marked this conversation as resolved
Outdated
|
|||||||
|
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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/*"
|
||||||
|
|
Loading…
Reference in a new issue
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.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 suppose it's ok to use the same loop