You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
10 KiB
421 lines
10 KiB
package policy
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestPrincipalMatching tests the matchesPrincipal method
|
|
func TestPrincipalMatching(t *testing.T) {
|
|
engine := setupTestPolicyEngine(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
principal interface{}
|
|
evalCtx *EvaluationContext
|
|
want bool
|
|
}{
|
|
{
|
|
name: "plain wildcard principal",
|
|
principal: "*",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "structured wildcard federated principal",
|
|
principal: map[string]interface{}{
|
|
"Federated": "*",
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "wildcard in array",
|
|
principal: map[string]interface{}{
|
|
"Federated": []interface{}{"specific-provider", "*"},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "specific federated provider match",
|
|
principal: map[string]interface{}{
|
|
"Federated": "https://example.com/oidc",
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://example.com/oidc",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "specific federated provider no match",
|
|
principal: map[string]interface{}{
|
|
"Federated": "https://example.com/oidc",
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://other.com/oidc",
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "array with specific provider match",
|
|
principal: map[string]interface{}{
|
|
"Federated": []string{"https://provider1.com", "https://provider2.com"},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://provider2.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "AWS principal match",
|
|
principal: map[string]interface{}{
|
|
"AWS": "arn:aws:iam::123456789012:user/alice",
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:PrincipalArn": "arn:aws:iam::123456789012:user/alice",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "Service principal match",
|
|
principal: map[string]interface{}{
|
|
"Service": "s3.amazonaws.com",
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:PrincipalServiceName": "s3.amazonaws.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := engine.matchesPrincipal(tt.principal, tt.evalCtx)
|
|
assert.Equal(t, tt.want, result, "Principal matching failed for: %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestEvaluatePrincipalValue tests the evaluatePrincipalValue method
|
|
func TestEvaluatePrincipalValue(t *testing.T) {
|
|
engine := setupTestPolicyEngine(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
principalValue interface{}
|
|
contextKey string
|
|
evalCtx *EvaluationContext
|
|
want bool
|
|
}{
|
|
{
|
|
name: "wildcard string",
|
|
principalValue: "*",
|
|
contextKey: "aws:FederatedProvider",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "specific string match",
|
|
principalValue: "https://example.com",
|
|
contextKey: "aws:FederatedProvider",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "specific string no match",
|
|
principalValue: "https://example.com",
|
|
contextKey: "aws:FederatedProvider",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://other.com",
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "wildcard in array",
|
|
principalValue: []interface{}{"provider1", "*"},
|
|
contextKey: "aws:FederatedProvider",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "array match",
|
|
principalValue: []string{"provider1", "provider2", "provider3"},
|
|
contextKey: "aws:FederatedProvider",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "provider2",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "array no match",
|
|
principalValue: []string{"provider1", "provider2"},
|
|
contextKey: "aws:FederatedProvider",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "provider3",
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "missing context key",
|
|
principalValue: "specific-value",
|
|
contextKey: "aws:FederatedProvider",
|
|
evalCtx: &EvaluationContext{
|
|
RequestContext: map[string]interface{}{},
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := engine.evaluatePrincipalValue(tt.principalValue, tt.evalCtx, tt.contextKey)
|
|
assert.Equal(t, tt.want, result, "Principal value evaluation failed for: %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestTrustPolicyEvaluation tests the EvaluateTrustPolicy method
|
|
func TestTrustPolicyEvaluation(t *testing.T) {
|
|
engine := setupTestPolicyEngine(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
trustPolicy *PolicyDocument
|
|
evalCtx *EvaluationContext
|
|
wantEffect Effect
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "wildcard federated principal allows any provider",
|
|
trustPolicy: &PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: map[string]interface{}{
|
|
"Federated": "*",
|
|
},
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
},
|
|
},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
Action: "sts:AssumeRoleWithWebIdentity",
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://any-provider.com",
|
|
},
|
|
},
|
|
wantEffect: EffectAllow,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "specific federated principal matches",
|
|
trustPolicy: &PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: map[string]interface{}{
|
|
"Federated": "https://example.com/oidc",
|
|
},
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
},
|
|
},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
Action: "sts:AssumeRoleWithWebIdentity",
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://example.com/oidc",
|
|
},
|
|
},
|
|
wantEffect: EffectAllow,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "specific federated principal does not match",
|
|
trustPolicy: &PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: map[string]interface{}{
|
|
"Federated": "https://example.com/oidc",
|
|
},
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
},
|
|
},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
Action: "sts:AssumeRoleWithWebIdentity",
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://other.com/oidc",
|
|
},
|
|
},
|
|
wantEffect: EffectDeny,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "plain wildcard principal",
|
|
trustPolicy: &PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: "*",
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
},
|
|
},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
Action: "sts:AssumeRoleWithWebIdentity",
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://any-provider.com",
|
|
},
|
|
},
|
|
wantEffect: EffectAllow,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "trust policy with conditions",
|
|
trustPolicy: &PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: map[string]interface{}{
|
|
"Federated": "*",
|
|
},
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
Condition: map[string]map[string]interface{}{
|
|
"StringEquals": {
|
|
"oidc:aud": "my-app-id",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
Action: "sts:AssumeRoleWithWebIdentity",
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://provider.com",
|
|
"oidc:aud": "my-app-id",
|
|
},
|
|
},
|
|
wantEffect: EffectAllow,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "trust policy condition not met",
|
|
trustPolicy: &PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: map[string]interface{}{
|
|
"Federated": "*",
|
|
},
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
Condition: map[string]map[string]interface{}{
|
|
"StringEquals": {
|
|
"oidc:aud": "my-app-id",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
evalCtx: &EvaluationContext{
|
|
Action: "sts:AssumeRoleWithWebIdentity",
|
|
RequestContext: map[string]interface{}{
|
|
"aws:FederatedProvider": "https://provider.com",
|
|
"oidc:aud": "wrong-app-id",
|
|
},
|
|
},
|
|
wantEffect: EffectDeny,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := engine.EvaluateTrustPolicy(context.Background(), tt.trustPolicy, tt.evalCtx)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.wantEffect, result.Effect, "Trust policy evaluation failed for: %s", tt.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetPrincipalContextKey tests the context key mapping
|
|
func TestGetPrincipalContextKey(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
principalType string
|
|
want string
|
|
}{
|
|
{
|
|
name: "Federated principal",
|
|
principalType: "Federated",
|
|
want: "aws:FederatedProvider",
|
|
},
|
|
{
|
|
name: "AWS principal",
|
|
principalType: "AWS",
|
|
want: "aws:PrincipalArn",
|
|
},
|
|
{
|
|
name: "Service principal",
|
|
principalType: "Service",
|
|
want: "aws:PrincipalServiceName",
|
|
},
|
|
{
|
|
name: "Custom principal type",
|
|
principalType: "CustomType",
|
|
want: "aws:PrincipalCustomType",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := getPrincipalContextKey(tt.principalType)
|
|
assert.Equal(t, tt.want, result, "Context key mapping failed for: %s", tt.name)
|
|
})
|
|
}
|
|
}
|