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 {
|
||||||
|
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