diff --git a/weed/iam/policy/policy_engine.go b/weed/iam/policy/policy_engine.go index f53768bb4..4b82a5ad0 100644 --- a/weed/iam/policy/policy_engine.go +++ b/weed/iam/policy/policy_engine.go @@ -473,8 +473,11 @@ func (e *PolicyEngine) EvaluateTrustPolicy(ctx context.Context, trustPolicy *Pol return nil, fmt.Errorf("trust policy cannot be nil") } + // A trust policy is deny-by-default, whatever the engine's DefaultEffect: + // a role may be assumed only by a principal a trust statement explicitly + // allows. result := &EvaluationResult{ - Effect: Effect(e.config.DefaultEffect), + Effect: EffectDeny, EvaluationDetails: &EvaluationDetails{ Principal: evalCtx.Principal, Action: evalCtx.Action, @@ -511,7 +514,7 @@ func (e *PolicyEngine) EvaluateTrustPolicy(ctx context.Context, trustPolicy *Pol // AWS IAM evaluation logic: // 1. If there's an explicit Deny, the result is Deny // 2. If there's an Allow and no Deny, the result is Allow - // 3. Otherwise, use the default effect + // 3. Otherwise, the implicit deny stands if explicitDeny { result.Effect = EffectDeny } else if hasAllow { diff --git a/weed/iam/policy/trust_policy_implicit_deny_test.go b/weed/iam/policy/trust_policy_implicit_deny_test.go new file mode 100644 index 000000000..2058bcf7c --- /dev/null +++ b/weed/iam/policy/trust_policy_implicit_deny_test.go @@ -0,0 +1,80 @@ +package policy + +import ( + "context" + "encoding/json" + "testing" +) + +const subjectBoundTrustPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow", + "Principal":{"Federated":"https://oidc.example"},"Action":["sts:AssumeRoleWithWebIdentity"], + "Condition":{"StringEquals":{"oidc:sub":"spiffe://example.org/ns/app/sa/app"}}}]}` + +func webIdentityContext(sub string) *EvaluationContext { + return &EvaluationContext{ + Principal: "web-identity-user", + Action: "sts:AssumeRoleWithWebIdentity", + Resource: "arn:aws:iam::role/app", + RequestContext: map[string]interface{}{ + "aws:FederatedProvider": "https://oidc.example", + "oidc:iss": "https://oidc.example", + "oidc:sub": sub, + }, + } +} + +// A trust policy is deny-by-default. The engine's DefaultEffect decides +// requests no identity policy speaks to; it must not let a principal the trust +// policy does not allow assume the role. +func TestEvaluateTrustPolicyIsImplicitDenyWhateverTheDefaultEffect(t *testing.T) { + var trust PolicyDocument + if err := json.Unmarshal([]byte(subjectBoundTrustPolicy), &trust); err != nil { + t.Fatalf("parse test trust policy: %v", err) + } + for _, defaultEffect := range []string{"Allow", "Deny"} { + t.Run("defaultEffect="+defaultEffect, func(t *testing.T) { + engine := NewPolicyEngine() + if err := engine.Initialize(&PolicyEngineConfig{DefaultEffect: defaultEffect, StoreType: "memory"}); err != nil { + t.Fatalf("initialize policy engine: %v", err) + } + + res, err := engine.EvaluateTrustPolicy(context.Background(), &trust, webIdentityContext("spiffe://example.org/ns/other/sa/x")) + if err != nil { + t.Fatalf("evaluate trust policy: %v", err) + } + if res.Effect != EffectDeny { + t.Errorf("a subject the trust policy does not allow got %s", res.Effect) + } + + res, err = engine.EvaluateTrustPolicy(context.Background(), &trust, webIdentityContext("spiffe://example.org/ns/app/sa/app")) + if err != nil { + t.Fatalf("evaluate trust policy: %v", err) + } + if res.Effect != EffectAllow { + t.Errorf("the subject the trust policy allows got %s", res.Effect) + } + }) + } +} + +func TestEvaluateTrustPolicyExplicitDenyWins(t *testing.T) { + var trust PolicyDocument + doc := `{"Version":"2012-10-17","Statement":[ + {"Effect":"Allow","Principal":{"Federated":"https://oidc.example"},"Action":["sts:AssumeRoleWithWebIdentity"]}, + {"Effect":"Deny","Principal":{"Federated":"https://oidc.example"},"Action":["sts:AssumeRoleWithWebIdentity"], + "Condition":{"StringEquals":{"oidc:sub":"spiffe://example.org/ns/app/sa/app"}}}]}` + if err := json.Unmarshal([]byte(doc), &trust); err != nil { + t.Fatalf("parse test trust policy: %v", err) + } + engine := NewPolicyEngine() + if err := engine.Initialize(&PolicyEngineConfig{DefaultEffect: "Allow", StoreType: "memory"}); err != nil { + t.Fatalf("initialize policy engine: %v", err) + } + res, err := engine.EvaluateTrustPolicy(context.Background(), &trust, webIdentityContext("spiffe://example.org/ns/app/sa/app")) + if err != nil { + t.Fatalf("evaluate trust policy: %v", err) + } + if res.Effect != EffectDeny { + t.Errorf("explicit Deny did not win: %s", res.Effect) + } +}