Skip to content

Commit 2cd6f51

Browse files
authored
fix: add supports for parsing custom compliance messages (#4)
* fix: add supports for parsing custom compliance messages fix: adds better remarks and descriptions Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com> * fix: copilot issues Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com> * fix: copilot issues Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com> --------- Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
1 parent 0bd9235 commit 2cd6f51

3 files changed

Lines changed: 364 additions & 17 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ Policies are written in [Rego](https://www.openpolicyagent.org/docs/latest/polic
2222

2323
Use the standardized per-resource payload generated by the Cloud Custodian plugin as policy input. The plugin evaluates one resource/check pair at a time with `schema_version: v2`; matched resources are marked `assessment.status: non_compliant`, and baseline resources that did not match the Cloud Custodian check are marked `assessment.status: compliant`.
2424

25+
Cloud Custodian policies may include a plugin-only `non_compliance_message` string. When a resource is marked `non_compliant`, that message is appended to the evidence description. The plugin removes this field before executing the Cloud Custodian policy.
26+
2527
Risk templates should dedupe by individual cloud resource using the payload labels `resource_type` and `resource_id`.

policies/cloud_custodian_resources_detected.rego

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import rego.v1
55
violation_id := "cloud_custodian_resource_non_compliant"
66
execution_violation_id := "cloud_custodian_resource_evaluation_failed"
77
unsupported_input_violation_id := "cloud_custodian_unsupported_input"
8+
stderr_max_chars := 500
89

910
_label_schema := [
1011
{
@@ -107,6 +108,7 @@ _check := object.get(input, "check", {})
107108
_resource := object.get(input, "resource", {})
108109
_assessment := object.get(input, "assessment", {})
109110
_execution := object.get(input, "execution", {})
111+
_raw_policy := object.get(input, "raw_policy", {})
110112

111113
input_schema_version := _default_string(object.get(input, "schema_version", ""), "unknown-schema-version")
112114

@@ -127,6 +129,22 @@ _default_string(value, fallback) := fallback if {
127129
not is_string(value)
128130
}
129131

132+
_safe_stderr(value) := result if {
133+
first_line := split(value, "\n")[0]
134+
without_control_chars := regex.replace(first_line, "[[:cntrl:]]+", " ")
135+
without_authorization := regex.replace(without_control_chars, "(?i)authorization[[:space:]]*:[[:space:]]*.*", "Authorization: <redacted>")
136+
without_secret_values := regex.replace(without_authorization, "(?i)(password|token|secret|access[_-]?key)([[:space:]]*[:=][[:space:]]*)[^[:space:]]+", "$1$2<redacted>")
137+
result := _truncate_error_detail(without_secret_values)
138+
}
139+
140+
_truncate_error_detail(value) := value if {
141+
count(value) <= stderr_max_chars
142+
}
143+
144+
_truncate_error_detail(value) := sprintf("%s...", [substring(value, 0, stderr_max_chars)]) if {
145+
count(value) > stderr_max_chars
146+
}
147+
130148
check_name := _default_string(object.get(_check, "name", ""), "unknown-check")
131149

132150
resource_type := _default_string(object.get(_resource, "type", object.get(_check, "resource", "")), "unknown-resource-type")
@@ -178,10 +196,44 @@ inventory_status := _default_string(object.get(_assessment, "inventory_status",
178196

179197
execution_status := _default_string(object.get(_execution, "status", ""), "unknown-execution-status")
180198

199+
execution_exit_code := object.get(_execution, "exit_code", "unknown-exit-code")
200+
181201
execution_error := _default_string(object.get(_execution, "error", ""), "")
182202

203+
execution_stderr := _safe_stderr(_default_string(object.get(_execution, "stderr", ""), ""))
204+
183205
execution_errors := object.get(_execution, "errors", [])
184206

207+
execution_error_messages_from_array := messages if {
208+
is_array(execution_errors)
209+
messages := [msg |
210+
some msg in execution_errors
211+
is_string(msg)
212+
msg != ""
213+
]
214+
count(messages) > 0
215+
} else := []
216+
217+
execution_error_messages := execution_error_messages_from_array if {
218+
count(execution_error_messages_from_array) > 0
219+
} else := [execution_error] if {
220+
execution_error != ""
221+
} else := [execution_stderr] if {
222+
execution_stderr != ""
223+
} else := []
224+
225+
execution_error_details := concat("; ", execution_error_messages) if {
226+
count(execution_error_messages) > 0
227+
} else := "no detailed error message was provided"
228+
229+
raw_policy_name := _default_string(object.get(_raw_policy, "name", ""), check_name)
230+
231+
raw_policy_resource := _default_string(object.get(_raw_policy, "resource", ""), resource_type)
232+
233+
non_compliance_message := _default_string(object.get(_raw_policy, "non_compliance_message", ""), "")
234+
235+
matched_resource_count := object.get(_assessment, "matched_resource_count", "unknown")
236+
185237
supported_input if {
186238
input_schema_version == "v2"
187239
input_source == "cloud-custodian"
@@ -196,8 +248,7 @@ has_execution_error if {
196248
}
197249

198250
has_execution_error if {
199-
is_array(execution_errors)
200-
count(execution_errors) > 0
251+
count(execution_error_messages_from_array) > 0
201252
}
202253

203254
_base_labels := {
@@ -240,21 +291,62 @@ labels := object.union(
240291
_region_label,
241292
)
242293

243-
violation[{"id": violation_id, "remarks": msg}] if {
294+
is_non_compliant if {
244295
supported_input
245296
assessment_status == "non_compliant"
246-
msg := sprintf("Cloud Custodian check %q marked resource %q as non-compliant (matched=%v, inventory_status=%q).", [check_name, resource_ref, assessment_matched, inventory_status])
247297
}
248298

249-
violation[{"id": execution_violation_id, "remarks": msg}] if {
299+
is_compliant if {
300+
supported_input
301+
assessment_status == "compliant"
302+
}
303+
304+
is_execution_failed if {
250305
supported_input
251306
has_execution_error
252-
msg := sprintf("Cloud Custodian check %q failed while evaluating resource %q (execution_status=%q, error=%v, errors=%v).", [check_name, resource_ref, execution_status, execution_error, execution_errors])
253307
}
254308

255-
violation[{"id": unsupported_input_violation_id, "remarks": msg}] if {
309+
non_compliant_remark := sprintf("Resource %q failed Cloud Custodian policy %q (resource=%q); the resource was found by this policy run (matched=%v, inventory_status=%q, matched_resource_count=%v).", [resource_ref, raw_policy_name, raw_policy_resource, assessment_matched, inventory_status, matched_resource_count]) if {
310+
is_non_compliant
311+
}
312+
313+
execution_error_remark := sprintf("Cloud Custodian policy %q ran with errors while evaluating resource %q (execution_status=%q, exit_code=%v). Errors: %s.", [raw_policy_name, resource_ref, execution_status, execution_exit_code, execution_error_details]) if {
314+
is_execution_failed
315+
}
316+
317+
unsupported_input_remark := sprintf("Unsupported Cloud Custodian policy input: expected source=%q schema_version=%q but received source=%q schema_version=%q.", ["cloud-custodian", "v2", input_source, input_schema_version]) if {
318+
not supported_input
319+
}
320+
321+
violation[{"id": violation_id, "remarks": non_compliant_remark}] if {
322+
is_non_compliant
323+
}
324+
325+
violation[{"id": execution_violation_id, "remarks": execution_error_remark}] if {
326+
is_execution_failed
327+
}
328+
329+
violation[{"id": unsupported_input_violation_id, "remarks": unsupported_input_remark}] if {
330+
not supported_input
331+
}
332+
333+
remarks := sprintf("%s %s", [non_compliant_remark, execution_error_remark]) if {
334+
is_non_compliant
335+
is_execution_failed
336+
}
337+
338+
remarks := non_compliant_remark if {
339+
is_non_compliant
340+
not is_execution_failed
341+
}
342+
343+
remarks := execution_error_remark if {
344+
not is_non_compliant
345+
is_execution_failed
346+
}
347+
348+
remarks := unsupported_input_remark if {
256349
not supported_input
257-
msg := sprintf("Unsupported Cloud Custodian policy input: expected source=%q schema_version=%q but received source=%q schema_version=%q.", ["cloud-custodian", "v2", input_source, input_schema_version])
258350
}
259351

260352
title := "Cloud Custodian policy received unsupported input" if {
@@ -269,6 +361,59 @@ description := sprintf("Cloud Custodian policy expected source=%q schema_version
269361
not supported_input
270362
}
271363

272-
description := sprintf("Cloud Custodian check %q evaluated resource %q with assessment status %q and execution status %q.", [check_name, resource_ref, assessment_status, execution_status]) if {
364+
description_base := sprintf("Cloud Custodian check %q failed for resource %q.", [check_name, resource_ref]) if {
365+
supported_input
366+
is_non_compliant
367+
}
368+
369+
description_base := sprintf("Cloud Custodian check %q could not evaluate resource %q.", [check_name, resource_ref]) if {
370+
supported_input
371+
not is_non_compliant
372+
is_execution_failed
373+
}
374+
375+
description_base := sprintf("Cloud Custodian check %q passed for resource %q.", [check_name, resource_ref]) if {
376+
supported_input
377+
is_compliant
378+
not is_execution_failed
379+
}
380+
381+
description_base := sprintf("Cloud Custodian check %q evaluated resource %q.", [check_name, resource_ref]) if {
382+
supported_input
383+
not is_non_compliant
384+
not is_compliant
385+
not is_execution_failed
386+
}
387+
388+
has_non_compliance_message if {
389+
is_non_compliant
390+
non_compliance_message != ""
391+
}
392+
393+
description_execution_error := sprintf("Execution errors: %s.", [execution_error_details]) if {
394+
is_execution_failed
395+
}
396+
397+
description := sprintf("%s %s %s", [description_base, non_compliance_message, description_execution_error]) if {
398+
supported_input
399+
has_non_compliance_message
400+
is_execution_failed
401+
}
402+
403+
description := sprintf("%s %s", [description_base, non_compliance_message]) if {
404+
supported_input
405+
has_non_compliance_message
406+
not is_execution_failed
407+
}
408+
409+
description := sprintf("%s %s", [description_base, description_execution_error]) if {
410+
supported_input
411+
not has_non_compliance_message
412+
is_execution_failed
413+
}
414+
415+
description := description_base if {
273416
supported_input
417+
not has_non_compliance_message
418+
not is_execution_failed
274419
}

0 commit comments

Comments
 (0)