| name | validate-core-ocp |
| description | Use when the user asks to live-validate, reproduce, or red-team security findings for a CORE OpenShift payload component (a ClusterOperator such as console, ingress, monitoring, kube-apiserver, etcd) against a pre-provisioned isolated lab cluster — resolving the CO's namespaces and payload repos, generating a per-CO explicit-namespace targets.yaml from blast_radius.yaml, and running the validate-findings engine. NO cluster provisioning, NO OLM install, NO teardown — the component already runs in the payload. One invocation == one ClusterOperator on one long-lived lab cluster. |
Validate Core-OCP ClusterOperator (pre-provisioned lab cluster)
Sibling to validate-operator-live, for payload components instead of
layered OLM operators. Orchestration is resolve → scope → validate —
the provision/install/teardown stages are dropped because the component
under test ships in the cluster image and is already running.
Authorization: Project Glasswing core-OCP validation track (Jira
HCMSEC-3528). The target cluster MUST be a dedicated, network-isolated
lab install authorized for control-plane testing. This skill writes
authorized_by and expires into every generated targets.yaml; the
harness refuses to run past expiry.
Input
/validate-core-ocp <clusteroperator> [--version X.YY] [--kubeconfig PATH]
[--context CTX] [--replay-only] [--destructive]
[--expires YYYY-MM-DD]
| Flag | Effect |
|---|
<clusteroperator> | CO name as it appears in oc get co (e.g. console, ingress, kube-apiserver). |
--version | OCP minor for findings lookup (openshift-<v>-payload/). Default: derived from the live cluster's clusterversion. |
--kubeconfig | Path to the lab cluster's kubeconfig. Default: $KUBECONFIG. |
--context | Kubeconfig context. Default: current-context. |
--replay-only | Skip chain synthesis and novel hunting (recommended first pass). |
--destructive | Permit destructive-classified steps. Off by default. Tier-3 COs additionally have verbs_denied: [delete, patch, scale, drain] regardless. |
--expires | Engagement expiry stamped into targets.yaml. Default: today + 14d. |
Blast-radius tiers (blast_radius.yaml)
| Tier | COs | Default guard | Run order |
|---|
| 1 — leaf | console, monitoring, ingress, service-ca, storage, csi-snapshot-controller, cloud-credential, cloud-controller-manager, cluster-autoscaler, config-operator, kube-storage-version-migrator, image-registry, insights, marketplace, olm, openshift-samples, baremetal | verbs_denied: [delete] | first |
| 2 — node | machine-config, machine-api, machine-approver, control-plane-machine-set, network, dns, node-tuning, openshift-controller-manager | verbs_denied: [delete, scale] + node/machine off_limits | second |
| 3 — core | kube-apiserver, openshift-apiserver, kube-controller-manager, kube-scheduler, authentication, etcd, cluster-version | verbs_denied: [delete, patch, scale, drain] + secret/pod-delete off_limits | last; one at a time; expect cluster reinstall between destructive passes |
Tier-3 namespaces match scope.py:CONTROL_PLANE_NS_PATTERNS and are
hard-denied unless explicitly listed — gen_targets.py does that listing,
which is the only sanctioned unlock path.
Procedure
All paths relative to the workspace root.
0. Preflight
Verify the kubeconfig reaches the cluster and capture identity:
export KUBECONFIG=<path>
oc --context <ctx> get clusterversion version -o jsonpath='{.status.desired.version}'
oc --context <ctx> whoami
If the API serves a publicly-trusted named certificate (common on
devcluster.openshift.com), drop any stale certificate-authority-data
from the kubeconfig so oc falls through to system trust.
1. Resolve
python3 ai-security-harness/harnessing/validate-core-ocp/co_namespace_map.py <co> \
--version <X.YY> --context <ctx> --json
Yields {namespaces[], repos[], findings_sources}. If namespaces is
empty the CO is absent on this cluster (optional capability disabled) —
stop and pick a different CO or a different cluster.
2. Generate scope
python3 ai-security-harness/harnessing/validate-core-ocp/gen_targets.py \
--co <co> --context <ctx> --api <url> --out $OUT/targets.yaml
The output is an explicit per-CO namespace allowlist plus tier-appropriate
verbs_denied / off_limits. Review it before first live run on a new CO.
3. Validate
python3 ai-security-harness/harnessing/validate-findings/run.py "<findings_sources>" \
--targets $OUT/targets.yaml --context <ctx> \
[--replay-only] [--destructive] \
--name <co> --out $OUT
4. Record
Outputs land in analysis-results/validations/core-ocp-<version>/<co>/:
cluster.json # fingerprint + engagement record
targets.yaml # generated RoE
<co>-attack-plan.yaml
<co>-validation.{json,md}
validation-audit.jsonl
artifacts/
run.log run-summary.json
The campaign manifest row is core-ocp:<co> in
analysis-results/validations/_manifest/validation-manifest.csv.
Unattended driver
Steps 0–4 are scripted end-to-end:
bash ai-security-harness/harnessing/validate-core-ocp/run_one.sh <co> \
--kubeconfig <path> [--version X.YY] [--replay-only] [--destructive]
For a full payload sweep, iterate blast_radius.yaml tiers in order:
for co in $(yq '.tiers.1.members[]' blast_radius.yaml); do
bash run_one.sh "$co" --kubeconfig "$KC" --replay-only
done
Known limitations
- HTTP-surface findings (CWE-918/306/295/319/200/532) get an adapted
port-forward+http step only when an endpoint path is extractable from
the finding/threat-model text. CR-field SSRF (operator pattern) still
correctly skips.
- Console-style double-submit CSRF returns
403 invalid CSRFToken → currently
reported refuted; the probe needs a CSRF cookie+header round-trip to reach
a true verdict. Track as a follow-up to _http_adapted_step().
validation.schema.json lags report.py (extra fields). Schema validation
warns but does not block.