Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ Policies are written in [Rego](https://www.openpolicyagent.org/docs/latest/polic

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`.

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.

Risk templates should dedupe by individual cloud resource using the payload labels `resource_type` and `resource_id`.
163 changes: 154 additions & 9 deletions policies/cloud_custodian_resources_detected.rego
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import rego.v1
violation_id := "cloud_custodian_resource_non_compliant"
execution_violation_id := "cloud_custodian_resource_evaluation_failed"
unsupported_input_violation_id := "cloud_custodian_unsupported_input"
stderr_max_chars := 500

_label_schema := [
{
Expand Down Expand Up @@ -107,6 +108,7 @@ _check := object.get(input, "check", {})
_resource := object.get(input, "resource", {})
_assessment := object.get(input, "assessment", {})
_execution := object.get(input, "execution", {})
_raw_policy := object.get(input, "raw_policy", {})

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

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

_safe_stderr(value) := result if {
first_line := split(value, "\n")[0]
without_control_chars := regex.replace(first_line, "[[:cntrl:]]+", " ")
without_authorization := regex.replace(without_control_chars, "(?i)authorization[[:space:]]*:[[:space:]]*.*", "Authorization: <redacted>")
without_secret_values := regex.replace(without_authorization, "(?i)(password|token|secret|access[_-]?key)([[:space:]]*[:=][[:space:]]*)[^[:space:]]+", "$1$2<redacted>")
result := _truncate_error_detail(without_secret_values)
}

_truncate_error_detail(value) := value if {
count(value) <= stderr_max_chars
}

_truncate_error_detail(value) := sprintf("%s...", [substring(value, 0, stderr_max_chars)]) if {
count(value) > stderr_max_chars
}

check_name := _default_string(object.get(_check, "name", ""), "unknown-check")

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

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

execution_exit_code := object.get(_execution, "exit_code", "unknown-exit-code")

execution_error := _default_string(object.get(_execution, "error", ""), "")

execution_stderr := _safe_stderr(_default_string(object.get(_execution, "stderr", ""), ""))

execution_errors := object.get(_execution, "errors", [])

execution_error_messages_from_array := messages if {
is_array(execution_errors)
messages := [msg |
some msg in execution_errors
is_string(msg)
msg != ""
]
count(messages) > 0
} else := []

execution_error_messages := execution_error_messages_from_array if {
count(execution_error_messages_from_array) > 0
} else := [execution_error] if {
execution_error != ""
} else := [execution_stderr] if {
execution_stderr != ""
} else := []
Comment thread
gusfcarvalho marked this conversation as resolved.

execution_error_details := concat("; ", execution_error_messages) if {
count(execution_error_messages) > 0
} else := "no detailed error message was provided"

raw_policy_name := _default_string(object.get(_raw_policy, "name", ""), check_name)

raw_policy_resource := _default_string(object.get(_raw_policy, "resource", ""), resource_type)

non_compliance_message := _default_string(object.get(_raw_policy, "non_compliance_message", ""), "")

matched_resource_count := object.get(_assessment, "matched_resource_count", "unknown")

supported_input if {
input_schema_version == "v2"
input_source == "cloud-custodian"
Expand All @@ -196,8 +248,7 @@ has_execution_error if {
}

has_execution_error if {
is_array(execution_errors)
count(execution_errors) > 0
count(execution_error_messages_from_array) > 0
}

_base_labels := {
Expand Down Expand Up @@ -240,21 +291,62 @@ labels := object.union(
_region_label,
)

violation[{"id": violation_id, "remarks": msg}] if {
is_non_compliant if {
supported_input
assessment_status == "non_compliant"
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])
}

violation[{"id": execution_violation_id, "remarks": msg}] if {
is_compliant if {
supported_input
assessment_status == "compliant"
}

is_execution_failed if {
supported_input
has_execution_error
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])
}

violation[{"id": unsupported_input_violation_id, "remarks": msg}] if {
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 {
is_non_compliant
}

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

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 {
not supported_input
}

violation[{"id": violation_id, "remarks": non_compliant_remark}] if {
is_non_compliant
}

violation[{"id": execution_violation_id, "remarks": execution_error_remark}] if {
is_execution_failed
}

violation[{"id": unsupported_input_violation_id, "remarks": unsupported_input_remark}] if {
not supported_input
}

remarks := sprintf("%s %s", [non_compliant_remark, execution_error_remark]) if {
is_non_compliant
is_execution_failed
}

remarks := non_compliant_remark if {
is_non_compliant
not is_execution_failed
}

remarks := execution_error_remark if {
not is_non_compliant
is_execution_failed
}

remarks := unsupported_input_remark if {
not supported_input
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])
}

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

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 {
description_base := sprintf("Cloud Custodian check %q failed for resource %q.", [check_name, resource_ref]) if {
supported_input
is_non_compliant
}

description_base := sprintf("Cloud Custodian check %q could not evaluate resource %q.", [check_name, resource_ref]) if {
supported_input
not is_non_compliant
is_execution_failed
}

description_base := sprintf("Cloud Custodian check %q passed for resource %q.", [check_name, resource_ref]) if {
supported_input
is_compliant
not is_execution_failed
}

description_base := sprintf("Cloud Custodian check %q evaluated resource %q.", [check_name, resource_ref]) if {
supported_input
not is_non_compliant
not is_compliant
not is_execution_failed
}

has_non_compliance_message if {
is_non_compliant
non_compliance_message != ""
}

description_execution_error := sprintf("Execution errors: %s.", [execution_error_details]) if {
is_execution_failed
}

description := sprintf("%s %s %s", [description_base, non_compliance_message, description_execution_error]) if {
supported_input
has_non_compliance_message
is_execution_failed
}

description := sprintf("%s %s", [description_base, non_compliance_message]) if {
supported_input
has_non_compliance_message
not is_execution_failed
}

description := sprintf("%s %s", [description_base, description_execution_error]) if {
supported_input
not has_non_compliance_message
is_execution_failed
}
Comment thread
gusfcarvalho marked this conversation as resolved.

description := description_base if {
supported_input
not has_non_compliance_message
not is_execution_failed
}
Loading
Loading