diff --git a/CHANGELOG.md b/CHANGELOG.md index f5cbb29..da8d78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## HEAD (Unreleased) +- Fix panic when a stack policy with a "remediate" level reports a violation + (https://github.com/pulumi/pulumi-policy/pull/339). + --- ## 1.10.0 (2024-02-20) diff --git a/sdk/nodejs/policy/server.ts b/sdk/nodejs/policy/server.ts index 2e99d17..4219060 100644 --- a/sdk/nodejs/policy/server.ts +++ b/sdk/nodejs/policy/server.ts @@ -340,11 +340,15 @@ function makeAnalyzeStackRpcFun( const ds: Diagnostic[] = []; try { for (const p of policies) { - const enforcementLevel: EnforcementLevel = + let enforcementLevel: EnforcementLevel = policyPackConfig[p.name]?.enforcementLevel || p.enforcementLevel || policyPackEnforcementLevel; if (enforcementLevel === "disabled" || !isStackPolicy(p)) { continue; } + if (enforcementLevel === "remediate") { + // Stack policies cannot be remediated, so treat the level as mandatory. + enforcementLevel = "mandatory"; + } const reportViolation: ReportViolation = (message, urn) => { let violationMessage = p.description; diff --git a/sdk/python/lib/pulumi_policy/policy.py b/sdk/python/lib/pulumi_policy/policy.py index 05a4faf..6239f5d 100644 --- a/sdk/python/lib/pulumi_policy/policy.py +++ b/sdk/python/lib/pulumi_policy/policy.py @@ -796,6 +796,9 @@ def AnalyzeStack(self, request, _context): enforcement_level = self._get_enforcement_level(policy) if enforcement_level == EnforcementLevel.DISABLED or not isinstance(policy, StackValidationPolicy): continue + if enforcement_level == EnforcementLevel.REMEDIATE: + # Stack policies cannot be remediated, so treat the level as mandatory. + enforcement_level = EnforcementLevel.MANDATORY report_violation = self._create_report_violation(diagnostics, policy.name, policy.description, enforcement_level) diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 840c802..7181eea 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -455,6 +455,14 @@ func TestValidateStack(t *testing.T) { { WantErrors: nil, }, + // Test scenario 10: a stack validation with enforcement level of "remediate" is treated as "mandatory". + { + WantErrors: []string{ + "[mandatory] dynamic-no-foo-with-value-bar", + "Prohibits setting foo to 'bar' on dynamic resources.", + "'foo' must not have the value 'bar'.", + }, + }, }) } diff --git a/tests/integration/validate_stack/policy-pack-python/__main__.py b/tests/integration/validate_stack/policy-pack-python/__main__.py index da2e0d3..60c2667 100644 --- a/tests/integration/validate_stack/policy-pack-python/__main__.py +++ b/tests/integration/validate_stack/policy-pack-python/__main__.py @@ -40,6 +40,12 @@ def no_randomstrings(args: StackValidationArgs, report_violation: ReportViolatio if r.resource_type == "random:index/randomString:RandomString": report_violation("RandomString resources are not allowed.") +def dynamic_no_foo_with_value_bar(args: StackValidationArgs, report_violation: ReportViolation): + for r in args.resources: + if r.resource_type == "pulumi-nodejs:dynamic:Resource": + if "foo" in r.props and r.props["foo"] == "bar": + report_violation("'foo' must not have the value 'bar'.") + PolicyPack( name="validate-stack-test-policy", enforcement_level=EnforcementLevel.MANDATORY, @@ -71,5 +77,12 @@ def no_randomstrings(args: StackValidationArgs, report_violation: ReportViolatio description="Prohibits RandomString resources.", validate=no_randomstrings, ), + # Stack policies with an enforcement level of remediate are treated as mandatory. + StackValidationPolicy( + name="dynamic-no-foo-with-value-bar", + description="Prohibits setting foo to 'bar' on dynamic resources.", + enforcement_level=EnforcementLevel.REMEDIATE, + validate=dynamic_no_foo_with_value_bar, + ), ], ) diff --git a/tests/integration/validate_stack/policy-pack/index.ts b/tests/integration/validate_stack/policy-pack/index.ts index 1733000..39156f0 100644 --- a/tests/integration/validate_stack/policy-pack/index.ts +++ b/tests/integration/validate_stack/policy-pack/index.ts @@ -145,5 +145,26 @@ new PolicyPack("validate-stack-test-policy", { } }), }, + // Stack policies with an enforcement level of remediate are treated as mandatory. + { + name: "dynamic-no-foo-with-value-bar", + description: "Prohibits setting foo to 'bar' on dynamic resources.", + enforcementLevel: "remediate", + validateStack: (args, reportViolation) => { + for (const r of args.resources) { + // FIXME: We don't have any outputs during previews and aren't merging + // inputs, so just skip for now if we have an empty props. + if (Object.keys(r.props).length === 0) { + continue; + } + + if (r.type === "pulumi-nodejs:dynamic:Resource") { + if (r.props.foo === "bar") { + reportViolation("'foo' must not have the value 'bar'."); + } + } + } + }, + }, ], }); diff --git a/tests/integration/validate_stack/program/index.ts b/tests/integration/validate_stack/program/index.ts index 66c6263..dbaf146 100644 --- a/tests/integration/validate_stack/program/index.ts +++ b/tests/integration/validate_stack/program/index.ts @@ -58,4 +58,12 @@ switch (testScenario) { const y = new random.RandomInteger("y", { min: 0, max: 10 }); const z = new random.RandomInteger("z", { min: 0, max: 10 }); break; + + case 10: + // Create a resource that will cause a stack policy with an + // enforcement level of "remediate" to report a violation + // successfully. It should be treated as "mandatory" rather + // than "remediate". + const d = new Resource("d", { foo: "bar" }); + break; }