Skip to content

Commit b3bb684

Browse files
p-rogcursoragent
andauthored
Replace deprecated ACS init-bundle with Cluster Registration Secret (CRS) (#148)
* Adding an option to ACS to use cluster CA, not self signed, for ACS Central * Adding explanation how ACS handles two OCP routes * switching from the internal OCP image registry (image-registry.openshift-image-registry.svc:5000/openshift/cli) to the public Red Hat registry (registry.redhat.io/openshift4/ose-cli) * UX polish, the user-facing URL becomes central.apps.<domain> which is simpler and more intuitive * Using the newest OC CLI image, built based on RHEL9 * feat: replace deprecated ACS init-bundle with CRS approach Replace the init-bundle Job with a Cluster Registration Secret (CRS) job per RHACS 4.10 recommendations. CRS uses a short-lived token instead of long-lived certificates, improving security posture. The CRS can be revoked after cluster registration without disconnecting the secured cluster. - Rename create-cluster-init-bundle.yaml to create-cluster-registration-secret.yaml - Switch API from /v1/cluster-init/init-bundles to /v1/cluster-init/crs - Simplify job logic: remove bundle listing/deletion/Python parsing - Add dual idempotency check (sensor-tls OR cluster-registration-secret) - Update comments in values files to reference CRS Co-authored-by: Cursor <cursoragent@cursor.com> * fix: handle existing init-bundle/CRS name collision during migration The CRS API rejects names that collide with existing init-bundles or CRS entries. Add logic to revoke legacy init-bundles and stale CRS entries with the same cluster name before generating a new CRS. Uses printf-based Python file writing to avoid YAML block scalar indentation issues with Helm template rendering. Tested on live cluster: CRS generation, init-bundle revocation, sensor registration via CRS init container all verified working. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: bump ACS chart versions and appVersion to 4.11 - acs-central: 1.0.0 -> 1.1.0 (CRS replaces deprecated init-bundle) - acs-secured-cluster: 1.0.0 -> 1.0.1 (comment updates only) - appVersion: 4.9 -> 4.11 (reflects current ACS stable stream) Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ddb1051 commit b3bb684

7 files changed

Lines changed: 171 additions & 157 deletions

File tree

charts/acs-central/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: v2
22
name: acs-central
33
description: Red Hat Advanced Cluster Security Central Services
44
type: application
5-
version: 1.0.0
6-
appVersion: "4.9"
5+
version: 1.1.0
6+
appVersion: "4.11"
77
keywords:
88
- security
99
- compliance

charts/acs-central/templates/jobs/create-cluster-init-bundle.yaml

Lines changed: 0 additions & 149 deletions
This file was deleted.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# NOTE: This CRS (Cluster Registration Secret) is for the local cluster (hub) only.
2+
# For multi-cluster deployments, create separate CRS jobs
3+
# for each remote secured cluster with their specific cluster names.
4+
#
5+
# CRS replaces the deprecated init-bundle approach (RHACS 4.10+).
6+
# Unlike init bundles which contain long-lived certificates, a CRS contains
7+
# a token that the secured cluster uses to request its own certificates.
8+
# The CRS can be revoked after registration without disconnecting the cluster.
9+
10+
{{- if .Values.central.enabled }}
11+
apiVersion: batch/v1
12+
kind: Job
13+
metadata:
14+
name: create-cluster-registration-secret
15+
namespace: {{ .Release.Namespace }}
16+
labels:
17+
{{- include "acs-central.labels" . | nindent 4 }}
18+
annotations:
19+
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
20+
argocd.argoproj.io/sync-wave: "43"
21+
argocd.argoproj.io/hook: Sync
22+
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
23+
spec:
24+
template:
25+
metadata:
26+
name: create-cluster-registration-secret
27+
labels:
28+
app: create-cluster-registration-secret
29+
{{- include "acs-central.labels" . | nindent 8 }}
30+
spec:
31+
containers:
32+
- image: {{ .Values.integration.keycloak.jobImage.registry }}/{{ .Values.integration.keycloak.jobImage.repository }}:{{ .Values.integration.keycloak.jobImage.tag }}
33+
imagePullPolicy: {{ .Values.integration.keycloak.jobImage.pullPolicy }}
34+
env:
35+
- name: PASSWORD
36+
valueFrom:
37+
secretKeyRef:
38+
name: {{ .Values.central.adminPassword.secretName }}
39+
key: {{ .Values.central.adminPassword.secretKey }}
40+
command:
41+
- /bin/bash
42+
- -c
43+
- |
44+
#!/usr/bin/env bash
45+
46+
# Skip if cluster is already registered (init-bundle) or CRS already applied
47+
if kubectl get secret/sensor-tls &> /dev/null; then
48+
echo "Cluster already registered (sensor-tls exists), skipping"
49+
exit 0
50+
fi
51+
if kubectl get secret/cluster-registration-secret &> /dev/null; then
52+
echo "CRS already applied (cluster-registration-secret exists), skipping"
53+
exit 0
54+
fi
55+
56+
echo "Waiting for ACS Central to be ready..."
57+
attempt_counter=0
58+
max_attempts=30
59+
60+
until $(curl -k --output /dev/null --silent --head --fail https://central); do
61+
if [ ${attempt_counter} -eq ${max_attempts} ]; then
62+
echo "Max attempts reached waiting for Central"
63+
exit 1
64+
fi
65+
66+
printf '.'
67+
attempt_counter=$(($attempt_counter+1))
68+
sleep 10
69+
done
70+
71+
echo ""
72+
echo "Central is ready"
73+
74+
CLUSTER_NAME="{{ .Values.clusterName | default .Values.global.localClusterName }}"
75+
echo "Generating cluster registration secret for: $CLUSTER_NAME"
76+
77+
# Helper script to find an entry by name in a JSON list response
78+
printf '%s\n' \
79+
'import json, sys' \
80+
'data = json.load(open(sys.argv[1]))' \
81+
'for item in data.get("items", []):' \
82+
' if item.get("name") == sys.argv[2]:' \
83+
' print(item["id"])' \
84+
' break' \
85+
> /tmp/find_entry.py
86+
87+
# Revoke any existing init-bundle with the same name (migration from init-bundle to CRS).
88+
# The CRS API rejects names that collide with existing init-bundles or CRS entries.
89+
curl -k -s -u "admin:$PASSWORD" \
90+
https://central/v1/cluster-init/init-bundles > /tmp/bundles_list.json
91+
92+
EXISTING_BUNDLE_ID=$(python3 /tmp/find_entry.py /tmp/bundles_list.json "$CLUSTER_NAME" 2>/dev/null)
93+
94+
if [ -n "$EXISTING_BUNDLE_ID" ]; then
95+
echo "Revoking legacy init-bundle '$CLUSTER_NAME' (ID: $EXISTING_BUNDLE_ID)..."
96+
curl -k -s -X PATCH -u "admin:$PASSWORD" \
97+
-H "Content-Type: application/json" \
98+
--data "{\"ids\":[\"$EXISTING_BUNDLE_ID\"],\"confirmImpactedClustersIds\":[]}" \
99+
https://central/v1/cluster-init/init-bundles/revoke > /tmp/revoke_result.json
100+
echo "Legacy init-bundle revoked"
101+
sleep 2
102+
fi
103+
104+
# Revoke any existing CRS with the same name (handles re-run after partial failure)
105+
curl -k -s -u "admin:$PASSWORD" \
106+
https://central/v1/cluster-init/crs > /tmp/crs_list.json
107+
108+
EXISTING_CRS_ID=$(python3 /tmp/find_entry.py /tmp/crs_list.json "$CLUSTER_NAME" 2>/dev/null)
109+
110+
if [ -n "$EXISTING_CRS_ID" ]; then
111+
echo "Revoking existing CRS '$CLUSTER_NAME' (ID: $EXISTING_CRS_ID)..."
112+
curl -k -s -X PATCH -u "admin:$PASSWORD" \
113+
-H "Content-Type: application/json" \
114+
--data "{\"ids\":[\"$EXISTING_CRS_ID\"],\"confirmImpactedClustersIds\":[]}" \
115+
https://central/v1/cluster-init/crs/revoke > /tmp/crs_revoke_result.json
116+
echo "Existing CRS revoked"
117+
sleep 2
118+
fi
119+
120+
curl -k -o /tmp/crs.json -X POST \
121+
-u "admin:$PASSWORD" \
122+
-H "Content-Type: application/json" \
123+
--data "{\"name\":\"$CLUSTER_NAME\"}" \
124+
https://central/v1/cluster-init/crs
125+
126+
if [ $? -ne 0 ]; then
127+
echo "Failed to generate CRS"
128+
cat /tmp/crs.json
129+
exit 1
130+
fi
131+
132+
if ! python3 -c 'import sys, json; json.load(open("/tmp/crs.json"))["crs"]' &> /dev/null; then
133+
echo "API response does not contain crs field:"
134+
cat /tmp/crs.json | python3 -m json.tool 2>/dev/null || cat /tmp/crs.json
135+
exit 1
136+
fi
137+
138+
echo "CRS received"
139+
140+
echo "Applying cluster registration secret..."
141+
cat /tmp/crs.json | python3 -c 'import sys, json; print(json.load(sys.stdin)["crs"])' | base64 -d | oc apply -f -
142+
143+
if [ $? -eq 0 ]; then
144+
echo "Cluster registration secret applied successfully"
145+
146+
if oc get SecuredCluster stackrox-secured-cluster-services &> /dev/null; then
147+
oc label SecuredCluster stackrox-secured-cluster-services cluster-init-job-status=created --overwrite
148+
echo "SecuredCluster labeled for reconciliation"
149+
fi
150+
else
151+
echo "Failed to apply cluster registration secret"
152+
exit 1
153+
fi
154+
155+
echo "ACS cluster registration secret configuration complete"
156+
name: create-cluster-registration-secret
157+
dnsPolicy: ClusterFirst
158+
restartPolicy: Never
159+
serviceAccount: {{ .Values.integration.keycloak.serviceAccountName }}
160+
serviceAccountName: {{ .Values.integration.keycloak.serviceAccountName }}
161+
terminationGracePeriodSeconds: 30
162+
{{- end }}

charts/acs-central/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Default values for ACS Central Services
22
# This chart deploys the Central and Scanner components
33
#
4-
# Cluster name for init bundle
4+
# Cluster name for CRS (Cluster Registration Secret) registration
55
clusterName: "" # Will be set from global.localClusterName by pattern framework
66

77
global:

charts/acs-secured-cluster/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: v2
22
name: acs-secured-cluster
33
description: Red Hat Advanced Cluster Security Secured Cluster Services
44
type: application
5-
version: 1.0.0
6-
appVersion: "4.9"
5+
version: 1.0.1
6+
appVersion: "4.11"
77
keywords:
88
- security
99
- runtime-protection

charts/acs-secured-cluster/values.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ clusterName: "" # Will be overridden by values-hub.yaml
99
# Central endpoint configuration
1010
centralEndpoint: "" # e.g., central-stackrox.apps.cluster.example.com:443
1111

12-
# Init bundle secret (from Vault)
12+
# Cluster registration is handled by the CRS job in acs-central chart.
13+
# The CRS (Cluster Registration Secret) replaces the deprecated init-bundle approach.
14+
# Legacy init-bundle values kept for reference:
1315
initBundle:
1416
useExternalSecret: true
1517
secretName: collector-tls
16-
# If not using external secret, provide bundle data
1718
data: ""
1819

1920
# Sensor configuration

values-hub.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ clusterGroup:
595595
value: "ztvp"
596596
- name: integration.keycloak.clientId
597597
value: "acs-central"
598-
# Must match acs-secured-cluster clusterName (init bundle API name)
598+
# Must match acs-secured-cluster clusterName (CRS registration name)
599599
- name: clusterName
600600
value: hub
601601
# ACS to scan images stored in Quay (Uncomment to enable)

0 commit comments

Comments
 (0)