Generate Policy Skill
Help users validate container images with Conforma policies. The user doesn't need to know anything about Rego, Conforma, SBOMs, or policy configuration - this skill handles everything.
What the User Provides
| Input | Description | Example |
|---|---|---|
| Policy set name | Descriptive name for the policy set | release_policies |
| Image reference | Container image to validate (optional) | quay.io/org/app@sha256:abc123... |
| Public key | Cosign public key file (optional) | cosign.pub |
| Validation requirements | What they want to check | "Ensure npm packages come from registry.npmjs.org" |
| Builder IDs | Allowed build service identifiers (SLSA provenance) | ["https://tekton.dev/chains/v2"] |
| Source repository | Expected source repo for provenance correlation (SLSA provenance) | https://github.com/org/repo |
| Build type | Expected build system type (SLSA provenance) | "tekton.dev/v1/PipelineRun" |
See Policy Requirements Template for a complete requirements checklist.
What to Generate
When a user requests policy validation, generate these artifacts:
| Artifact | Description |
|---|---|
Policy rule (.rego) |
Standalone Rego v1 rule |
Test file (_test.rego) |
Comprehensive tests for the rule |
Policy config (policy.yaml) |
Conforma configuration referencing data files |
Data file (data/*.yaml) |
Rule data configuration (e.g., allowed registries) |
| Conforma command | Ready-to-run ec validate image command |
| Instructions | Clear next steps for the user |
Workflow
- Gather requirements - Ask what the user wants to validate
- Prompt for required inputs - See domain references for what to ask (e.g.,
allowed_package_sourcesfor SBOM rules) - Generate artifacts - Create files with directory matching package name (for Rego lint compliance)
- VERIFY: Run OPA tests - Run
ec opa test ./policy -vand ensure ALL tests pass - VERIFY: Run EC validation - If image provided, run
ec validate imageand verify expected results - Report results - Only after verification passes, provide the Conforma command and next steps
⚠️ MANDATORY VERIFICATION STEPS ⚠️
YOU ARE NOT DONE UNTIL THESE STEPS PASS. DO NOT REPORT SUCCESS TO THE USER UNTIL VERIFICATION IS COMPLETE.
Step 1: Run OPA Tests (REQUIRED)
After creating all Rego files, you MUST run the tests:
cd <policy_set_directory>
ec opa test ./policy -v --ignore '*.rego' --ignore 'lib/*'
Note: We exclude lib/* because sbom.rego uses ec.oci.blob() which OPA can't parse.
Tests must mock data.lib.sbom.spdx_sboms directly.
Requirements:
- ALL tests must pass (exit code 0)
- If tests fail, FIX the code and re-run until they pass
- DO NOT skip this step or report success if tests fail
Step 2: Run EC Validation (REQUIRED if image provided)
If the user provided an image reference, you MUST validate the policy against it:
cd <policy_set_directory>
ec validate image \
--image <IMAGE_REFERENCE> \
--policy policy.yaml \
--public-key cosign.pub \
--ignore-rekor \
--output text \
--info
Requirements:
- Verify the output matches expectations:
- If the policy should FAIL (e.g., image from wrong registry), confirm violations appear
- If the policy should PASS, confirm no violations
- If results don't match expectations, FIX the policy and re-run
- DO NOT skip this step or report success if results are unexpected
Step 3: Debug Unexpected Results
If validation doesn't produce expected results:
- Extract and examine the actual SBOM data from the image
- Compare SBOM structure to what the policy expects
- Adjust the policy to match the actual data structure
- Re-run validation to confirm
- Repeat until expected results are achieved
Directory Structure
For Rego lint compliance, directory path must match package name. Use a descriptive top-level directory name, with rules organized under a policy/ subdirectory:
<descriptive_name>/ # e.g., release_policies, sbom_validation
├── policy.yaml # Conforma configuration
├── cosign.pub # Public key (if provided)
├── data/ # Rule data files (referenced from policy.yaml)
│ └── <rule_data>.yaml # YAML file with rule_data configuration
└── policy/ # Contains all rule directories
├── lib/ # Shared library (required for SBOM rules)
│ └── sbom.rego # SBOM access library
└── <rule_name>/ # Directory matches package name
├── <rule_name>.rego # package <rule_name>
└── <rule_name>_test.rego # package <rule_name>_test
Example - For release policies with a package_sources rule:
release_policies/
├── policy.yaml
├── cosign.pub
├── data/
│ └── allowed_sources.yaml # rule_data for allowed package sources
└── policy/
├── lib/
│ └── sbom.rego # package lib.sbom
└── package_sources/
├── package_sources.rego # package package_sources
└── package_sources_test.rego # package package_sources_test
SBOM Access Library (Required for SBOM Rules)
IMPORTANT: SBOMs are NOT directly embedded in attestations. In Konflux/RHTAP builds, the SBOM is stored as an OCI blob and referenced from the SLSA provenance attestation. The library fetches these using ec.oci.blob().
For any rule that validates SBOM data, you MUST:
Create the SBOM library at
policy/lib/sbom.regousing the SBOM Library TemplateInclude the lib and data in policy.yaml:
sources: - name: my-rules data: - ./data # Rule data files policy: - ./policy/lib # SBOM access library - ./policy/my_ruleImport and use in your rules:
import data.lib.sbom deny contains result if { some s in sbom.spdx_sboms # Fetches from OCI blobs via ec.oci.blob() some pkg in s.packages # validation logic }Mock the SBOM data in tests (CRITICAL):
test_my_rule if { sboms := [_mock_spdx_sbom(packages)] count(my_rule.deny) == 1 with data.lib.sbom.spdx_sboms as sboms # MUST mock - bypasses ec.oci.blob() }
How it works:
- The library uses
ec.oci.blob()to fetch SBOMs from OCI blobs referenced in SLSA provenance ec.oci.blob()is an EC built-in function that OPA doesn't understand- Tests MUST mock
data.lib.sbom.spdx_sbomsdirectly to bypass the library implementation - At EC runtime, the library fetches real SBOMs from OCI registries
Testing Note: OPA tests will fail if they try to evaluate sbom.rego directly because OPA can't parse ec.oci.blob(). Always mock data.lib.sbom.spdx_sboms in tests.
DO NOT iterate directly over input.attestations for SBOM data - always use the library.
SLSA Provenance Access (For Provenance Rules)
IMPORTANT: SLSA provenance rules do NOT use the SBOM library (policy/lib/sbom.rego) and do not require a local policy/lib/ directory. Instead, they use runtime-provided helpers from data.lib to access provenance attestations.
For any rule that validates SLSA provenance data:
Use
data.libhelpers to access attestations —import data.liband uselib.slsa_provenance_attestations,lib.pipelinerun_attestations, andlib.attestation_materials(att)for version-agnostic access. See SLSA Quick Reference for patterns.Define a local
_builder_idhelper —data.libdoes not provide a cross-version builder ID helper. See SLSA Quick Reference for the dual-version pattern.No local library directory needed — directory structure is simpler than SBOM rules:
build_policies/ ├── policy.yaml ├── data/ │ └── allowed_builders.yaml └── policy/ └── builder_verification/ ├── builder_verification.rego └── builder_verification_test.regoPolicy config — no library path entry needed:
sources: - name: my-rules data: - ./data policy: - ./policy/builder_verification # No ./policy/lib needed
Required Inputs for SLSA Provenance Rules
When generating SLSA provenance rules, prompt the user for these inputs. See SLSA Provenance Structure Reference for full details.
| Rule Type | Required Input | Description | Example |
|---|---|---|---|
| Builder ID validation | allowed_builder_ids |
Accepted builder identifiers | ["https://tekton.dev/chains/v2"] |
| Attestation type validation | allowed_predicate_types |
Accepted in-toto predicate types | ["https://slsa.dev/provenance/v1"] |
| Build type validation | allowed_provenance_build_types |
Accepted build type strings | ["tekton.dev/v1/PipelineRun"] |
| Source correlation | supported_vcs |
Supported VCS types | ["git"] |
| Source correlation | supported_digests |
Supported digest algorithms | ["sha1", "gitCommit", "sha256"] |
| Pipeline run params | pipeline_run_params |
Expected PipelineRun parameter names | ["git-repo", "git-revision", "output-image"] |
Conforma Command Template
Run from within the policy set directory (e.g., cd release_policies):
ec validate image \
--image <IMAGE_REFERENCE> \
--policy policy.yaml \
--public-key cosign.pub \
--ignore-rekor \
--output text \
--info
Important: The policy.yaml references rules with relative paths (./policy/<rule_name>), so the command must be run from the policy set directory where policy.yaml is located.
Reference Documents
Core References
- Rego Patterns Reference - Rego v1 syntax, metadata format, result construction
- Test Patterns Reference - OPA testing, mock helpers, required test cases
- Policy Configuration Reference - Policy config structure, ruleData, collections
Domain-Specific References
SBOM Validation
- SBOM Structure Reference - SPDX/CycloneDX paths, required inputs
- SPDX 2.3 Schema, CycloneDX 1.5 Schema
SLSA Provenance
- SLSA Provenance Structure Reference - v1.0/v0.2 field paths, required inputs, dual-version patterns
Templates
- Rule Template - Generic Rego v1 rule
- Test Template - Test file with mock helpers
- SBOM Rule Template - Package source validation example
- SBOM Library Template - Standalone SBOM access library (REQUIRED for SBOM rules)
- Policy Config Template - Conforma policy configuration
- Rule Data Template - Data file for rule configuration
Quick References
Rego v1 Essentials (SBOM Rule)
package <rule_name>
import rego.v1
import data.lib.sbom # Use the SBOM library
# METADATA
# title: Rule Title
# description: >-
# What the rule checks.
# custom:
# short_name: rule_name
# failure_msg: "Error: %s"
# solution: How to fix.
deny contains result if {
# Use sbom.spdx_sboms or sbom.cyclonedx_sboms (NOT input.attestations)
some s in sbom.spdx_sboms
some pkg in s.packages
# validation logic
result := {
"code": "<rule_name>.rule_name",
"msg": sprintf("Error: %s", [value]),
"severity": "failure",
}
}
Policy Config Essentials
name: <policy_name>
description: <description>
sources:
- name: <source_name>
data:
- ./data # Directory containing rule_data YAML files
policy:
- ./policy/lib # SBOM library (required for SBOM rules)
- ./policy/<rule_name> # Reference each rule directory under policy/
config:
include:
- "<rule_name>.short_name"
Data File Format
Rule data is stored in separate YAML files under the data/ directory:
# data/allowed_sources.yaml
rule_data:
allowed_package_sources:
- "^https://registry\\.npmjs\\.org/"
- "^https://proxy\\.golang\\.org/"
The data is accessed in Rego rules via data.rule_data:
allowed := object.get(data.rule_data, "allowed_package_sources", [])
SBOM Quick Reference
Access SBOMs (use lib.sbom - handles OCI blob fetching):
import data.lib.sbom
# SPDX SBOMs
some s in sbom.spdx_sboms
some pkg in s.packages
# CycloneDX SBOMs
some s in sbom.cyclonedx_sboms
some component in s.components
SPDX Structure:
- Packages:
s.packages - PURL:
pkg.externalRefs[].referenceLocatorwherereferenceType == "purl" - License:
pkg.licenseDeclared
CycloneDX Structure:
- Components:
s.components - PURL:
component.purl - Distribution URL:
component.externalReferences[].urlwheretype == "distribution"
Rego v1 Essentials (SLSA Provenance Rule)
package <rule_name>
import rego.v1
import data.lib
# METADATA
# title: Rule Title
# description: >-
# What the rule checks.
# custom:
# short_name: rule_name
# failure_msg: "Error: %s"
# solution: How to fix.
deny contains result if {
# Use runtime-provided helpers (NOT direct input.attestations)
some att in lib.pipelinerun_attestations
# Get builder ID (dual-version support)
builder_id := _builder_id(att)
# Validate against allowed list
allowed_ids := object.get(data.rule_data, "allowed_builder_ids", [])
not builder_id in allowed_ids
result := {
"code": "<rule_name>.rule_name",
"msg": sprintf("Error: builder %s is not allowed", [builder_id]),
"severity": "failure",
}
}
# Helper: extract builder ID (v0.2 first, then v1.0)
_builder_id(att) := builder_id if {
builder_id := att.statement.predicate.builder.id
} else := builder_id if {
builder_id := att.statement.predicate.runDetails.builder.id
}
SLSA Dual-Version Support
SLSA v1.0 and v0.2 use different field paths. Use helper functions for version-agnostic access:
| Data Element | v1.0 Path | v0.2 Path |
|---|---|---|
| Builder ID | predicate.runDetails.builder.id |
predicate.builder.id |
| Build type | predicate.buildDefinition.buildType |
predicate.buildType |
| Materials | predicate.buildDefinition.resolvedDependencies |
predicate.materials |
| Build finished | predicate.runDetails.metadata.finishedOn |
predicate.metadata.buildFinishedOn |
SLSA Data File Format
# data/allowed_builders.yaml
rule_data:
allowed_builder_ids:
- "https://tekton.dev/chains/v2"
allowed_predicate_types:
- "https://slsa.dev/provenance/v1"
- "https://slsa.dev/provenance/v0.2"
The data is accessed in Rego rules via data.rule_data:
allowed_ids := object.get(data.rule_data, "allowed_builder_ids", [])
allowed_types := object.get(data.rule_data, "allowed_predicate_types", [])
SLSA Quick Reference
Access provenance attestations (use data.lib helpers):
import data.lib
# All SLSA provenance (both v1.0 and v0.2)
some att in lib.slsa_provenance_attestations
# PipelineRun attestations only (latest per version)
some att in lib.pipelinerun_attestations
Predicate type validation (against configured allowlist):
allowed_types := object.get(data.rule_data, "allowed_predicate_types", [])
not att.statement.predicateType in allowed_types
Builder ID (local helper, dual-version):
_builder_id(att) := builder_id if {
builder_id := att.statement.predicate.builder.id
} else := builder_id if {
builder_id := att.statement.predicate.runDetails.builder.id
}
Source materials (use lib.attestation_materials):
materials := lib.attestation_materials(att)
some material in materials
startswith(material.uri, "git+")
Build type (dual-version):
_build_type(att) := att.statement.predicate.buildType if {
att.statement.predicateType == "https://slsa.dev/provenance/v0.2"
} else := att.statement.predicate.buildDefinition.buildType
Iterative Refinement
Users can refine generated policies through follow-up conversational requests. This workflow supports incremental changes without regenerating all artifacts from scratch.
How It Works
- Initial generation — Follow the standard workflow to generate all artifacts
- User requests a change — e.g., "pin the builder to our Tekton instance", "add an exception for test builds", "tighten the allowed registries"
- Modify existing files — Edit the generated
.rego,_test.rego, and data files in place rather than regenerating from scratch - Re-run verification — After every refinement, re-run both verification steps:
ec opa test ./policy -v --ignore 'lib/*'(OPA tests)ec validate image ...(EC validation, if image provided)
- Report updated results — Only report success after verification passes
Guidelines
- Preserve existing work — Modify the specific rule, test, or data file affected by the request. Do not regenerate unrelated files.
- Maintain test coverage — When modifying a rule, update the corresponding tests to cover the new behavior. Add new test cases for exceptions or additional conditions.
- Update data files — If the refinement changes configuration values (e.g., adding a builder ID), update the data YAML file rather than hardcoding values in Rego.
- Cumulative changes — Each refinement builds on the previous state. Track all changes so verification reflects the full policy, not just the latest change.