diff --git a/charts/acs-central/Chart.yaml b/charts/acs-central/Chart.yaml index de4093f5..7e550c62 100644 --- a/charts/acs-central/Chart.yaml +++ b/charts/acs-central/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: acs-central description: Red Hat Advanced Cluster Security Central Services type: application -version: 1.0.0 -appVersion: "4.9" +version: 1.1.0 +appVersion: "4.11" keywords: - security - compliance diff --git a/charts/acs-central/templates/jobs/create-cluster-init-bundle.yaml b/charts/acs-central/templates/jobs/create-cluster-init-bundle.yaml deleted file mode 100644 index bf1bbfa0..00000000 --- a/charts/acs-central/templates/jobs/create-cluster-init-bundle.yaml +++ /dev/null @@ -1,149 +0,0 @@ -# NOTE: This init bundle is for the local cluster (hub) only. -# For multi-cluster deployments, create separate init bundle jobs -# for each remote secured cluster with their specific cluster names. -# The cluster name is sanitized to replace underscores and dots with hyphens. -# - -{{- if .Values.central.enabled }} -apiVersion: batch/v1 -kind: Job -metadata: - name: create-cluster-init-bundle - namespace: {{ .Release.Namespace }} - labels: - {{- include "acs-central.labels" . | nindent 4 }} - annotations: - argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true - argocd.argoproj.io/sync-wave: "43" - argocd.argoproj.io/hook: Sync - argocd.argoproj.io/hook-delete-policy: BeforeHookCreation -spec: - template: - metadata: - name: create-cluster-init-bundle - labels: - app: create-cluster-init-bundle - {{- include "acs-central.labels" . | nindent 8 }} - spec: - containers: - - image: {{ .Values.integration.keycloak.jobImage.registry }}/{{ .Values.integration.keycloak.jobImage.repository }}:{{ .Values.integration.keycloak.jobImage.tag }} - imagePullPolicy: {{ .Values.integration.keycloak.jobImage.pullPolicy }} - env: - - name: PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.central.adminPassword.secretName }} - key: {{ .Values.central.adminPassword.secretKey }} - command: - - /bin/bash - - -c - - | - #!/usr/bin/env bash - - # Check if init bundle already exists - if kubectl get secret/sensor-tls &> /dev/null; then - echo "✅ Cluster init bundle already configured, skipping" - exit 0 - fi - - echo "🔄 Waiting for ACS Central to be ready..." - attempt_counter=0 - max_attempts=30 - - until $(curl -k --output /dev/null --silent --head --fail https://central); do - if [ ${attempt_counter} -eq ${max_attempts} ]; then - echo "❌ Max attempts reached waiting for Central" - exit 1 - fi - - printf '.' - attempt_counter=$(($attempt_counter+1)) - sleep 10 - done - - echo "" - echo "✅ Central is ready" - - CLUSTER_NAME="{{ .Values.clusterName | default .Values.global.localClusterName }}" - echo "🔄 Checking for existing init bundles..." - - # List existing init bundles - curl -k -s -u "admin:$PASSWORD" \ - https://central/v1/cluster-init/init-bundles > /tmp/bundles_list.json - - # Check if bundle with this cluster name already exists - printf '%s\n' \ - 'import sys, json' \ - 'try:' \ - ' data = json.load(open("/tmp/bundles_list.json"))' \ - ' items = data.get("items", [])' \ - ' for item in items:' \ - ' if item.get("name") == sys.argv[1]:' \ - ' print(item.get("id", ""))' \ - ' break' \ - 'except:' \ - ' pass' \ - > /tmp/find_bundle.py - - EXISTING_BUNDLE_ID=$(python3 /tmp/find_bundle.py "$CLUSTER_NAME" 2>/dev/null) - - # If bundle exists, delete it first - if [ ! -z "$EXISTING_BUNDLE_ID" ]; then - echo "⚠️ Init bundle '$CLUSTER_NAME' already exists (ID: $EXISTING_BUNDLE_ID), deleting..." - curl -k -X DELETE -u "admin:$PASSWORD" \ - "https://central/v1/cluster-init/init-bundles/$EXISTING_BUNDLE_ID" \ - -o /tmp/delete_result.json - echo "✅ Existing bundle deleted" - sleep 2 - fi - - # Generate new init bundle via API - echo "🔄 Generating cluster init bundle..." - export DATA="{\"name\":\"$CLUSTER_NAME\"}" - - curl -k -o /tmp/bundle.json -X POST \ - -u "admin:$PASSWORD" \ - -H "Content-Type: application/json" \ - --data "$DATA" \ - https://central/v1/cluster-init/init-bundles - - if [ $? -ne 0 ]; then - echo "❌ Failed to generate init bundle" - cat /tmp/bundle.json - exit 1 - fi - - # Check if response contains kubectlBundle - if ! python3 -c 'import sys, json; json.load(open("/tmp/bundle.json"))["kubectlBundle"]' &> /dev/null; then - echo "❌ API response does not contain kubectlBundle:" - cat /tmp/bundle.json | python3 -m json.tool - exit 1 - fi - - echo "✅ Bundle received" - - # Extract and apply bundle - echo "🔄 Applying init bundle secrets..." - cat /tmp/bundle.json | python3 -c 'import sys, json; print(json.load(sys.stdin)["kubectlBundle"])' | base64 -d | oc apply -f - - - if [ $? -eq 0 ]; then - echo "✅ Init bundle secrets applied successfully" - - # Label SecuredCluster to trigger reconciliation - if oc get SecuredCluster stackrox-secured-cluster-services &> /dev/null; then - oc label SecuredCluster stackrox-secured-cluster-services cluster-init-job-status=created --overwrite - echo "✅ SecuredCluster labeled for reconciliation" - fi - else - echo "❌ Failed to apply init bundle" - exit 1 - fi - - echo "🎉 ACS cluster init bundle configuration complete" - name: create-cluster-init-bundle - dnsPolicy: ClusterFirst - restartPolicy: Never - serviceAccount: {{ .Values.integration.keycloak.serviceAccountName }} - serviceAccountName: {{ .Values.integration.keycloak.serviceAccountName }} - terminationGracePeriodSeconds: 30 -{{- end }} \ No newline at end of file diff --git a/charts/acs-central/templates/jobs/create-cluster-registration-secret.yaml b/charts/acs-central/templates/jobs/create-cluster-registration-secret.yaml new file mode 100644 index 00000000..5ff1aa2a --- /dev/null +++ b/charts/acs-central/templates/jobs/create-cluster-registration-secret.yaml @@ -0,0 +1,162 @@ +# NOTE: This CRS (Cluster Registration Secret) is for the local cluster (hub) only. +# For multi-cluster deployments, create separate CRS jobs +# for each remote secured cluster with their specific cluster names. +# +# CRS replaces the deprecated init-bundle approach (RHACS 4.10+). +# Unlike init bundles which contain long-lived certificates, a CRS contains +# a token that the secured cluster uses to request its own certificates. +# The CRS can be revoked after registration without disconnecting the cluster. + +{{- if .Values.central.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: create-cluster-registration-secret + namespace: {{ .Release.Namespace }} + labels: + {{- include "acs-central.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true + argocd.argoproj.io/sync-wave: "43" + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + template: + metadata: + name: create-cluster-registration-secret + labels: + app: create-cluster-registration-secret + {{- include "acs-central.labels" . | nindent 8 }} + spec: + containers: + - image: {{ .Values.integration.keycloak.jobImage.registry }}/{{ .Values.integration.keycloak.jobImage.repository }}:{{ .Values.integration.keycloak.jobImage.tag }} + imagePullPolicy: {{ .Values.integration.keycloak.jobImage.pullPolicy }} + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.central.adminPassword.secretName }} + key: {{ .Values.central.adminPassword.secretKey }} + command: + - /bin/bash + - -c + - | + #!/usr/bin/env bash + + # Skip if cluster is already registered (init-bundle) or CRS already applied + if kubectl get secret/sensor-tls &> /dev/null; then + echo "Cluster already registered (sensor-tls exists), skipping" + exit 0 + fi + if kubectl get secret/cluster-registration-secret &> /dev/null; then + echo "CRS already applied (cluster-registration-secret exists), skipping" + exit 0 + fi + + echo "Waiting for ACS Central to be ready..." + attempt_counter=0 + max_attempts=30 + + until $(curl -k --output /dev/null --silent --head --fail https://central); do + if [ ${attempt_counter} -eq ${max_attempts} ]; then + echo "Max attempts reached waiting for Central" + exit 1 + fi + + printf '.' + attempt_counter=$(($attempt_counter+1)) + sleep 10 + done + + echo "" + echo "Central is ready" + + CLUSTER_NAME="{{ .Values.clusterName | default .Values.global.localClusterName }}" + echo "Generating cluster registration secret for: $CLUSTER_NAME" + + # Helper script to find an entry by name in a JSON list response + printf '%s\n' \ + 'import json, sys' \ + 'data = json.load(open(sys.argv[1]))' \ + 'for item in data.get("items", []):' \ + ' if item.get("name") == sys.argv[2]:' \ + ' print(item["id"])' \ + ' break' \ + > /tmp/find_entry.py + + # Revoke any existing init-bundle with the same name (migration from init-bundle to CRS). + # The CRS API rejects names that collide with existing init-bundles or CRS entries. + curl -k -s -u "admin:$PASSWORD" \ + https://central/v1/cluster-init/init-bundles > /tmp/bundles_list.json + + EXISTING_BUNDLE_ID=$(python3 /tmp/find_entry.py /tmp/bundles_list.json "$CLUSTER_NAME" 2>/dev/null) + + if [ -n "$EXISTING_BUNDLE_ID" ]; then + echo "Revoking legacy init-bundle '$CLUSTER_NAME' (ID: $EXISTING_BUNDLE_ID)..." + curl -k -s -X PATCH -u "admin:$PASSWORD" \ + -H "Content-Type: application/json" \ + --data "{\"ids\":[\"$EXISTING_BUNDLE_ID\"],\"confirmImpactedClustersIds\":[]}" \ + https://central/v1/cluster-init/init-bundles/revoke > /tmp/revoke_result.json + echo "Legacy init-bundle revoked" + sleep 2 + fi + + # Revoke any existing CRS with the same name (handles re-run after partial failure) + curl -k -s -u "admin:$PASSWORD" \ + https://central/v1/cluster-init/crs > /tmp/crs_list.json + + EXISTING_CRS_ID=$(python3 /tmp/find_entry.py /tmp/crs_list.json "$CLUSTER_NAME" 2>/dev/null) + + if [ -n "$EXISTING_CRS_ID" ]; then + echo "Revoking existing CRS '$CLUSTER_NAME' (ID: $EXISTING_CRS_ID)..." + curl -k -s -X PATCH -u "admin:$PASSWORD" \ + -H "Content-Type: application/json" \ + --data "{\"ids\":[\"$EXISTING_CRS_ID\"],\"confirmImpactedClustersIds\":[]}" \ + https://central/v1/cluster-init/crs/revoke > /tmp/crs_revoke_result.json + echo "Existing CRS revoked" + sleep 2 + fi + + curl -k -o /tmp/crs.json -X POST \ + -u "admin:$PASSWORD" \ + -H "Content-Type: application/json" \ + --data "{\"name\":\"$CLUSTER_NAME\"}" \ + https://central/v1/cluster-init/crs + + if [ $? -ne 0 ]; then + echo "Failed to generate CRS" + cat /tmp/crs.json + exit 1 + fi + + if ! python3 -c 'import sys, json; json.load(open("/tmp/crs.json"))["crs"]' &> /dev/null; then + echo "API response does not contain crs field:" + cat /tmp/crs.json | python3 -m json.tool 2>/dev/null || cat /tmp/crs.json + exit 1 + fi + + echo "CRS received" + + echo "Applying cluster registration secret..." + cat /tmp/crs.json | python3 -c 'import sys, json; print(json.load(sys.stdin)["crs"])' | base64 -d | oc apply -f - + + if [ $? -eq 0 ]; then + echo "Cluster registration secret applied successfully" + + if oc get SecuredCluster stackrox-secured-cluster-services &> /dev/null; then + oc label SecuredCluster stackrox-secured-cluster-services cluster-init-job-status=created --overwrite + echo "SecuredCluster labeled for reconciliation" + fi + else + echo "Failed to apply cluster registration secret" + exit 1 + fi + + echo "ACS cluster registration secret configuration complete" + name: create-cluster-registration-secret + dnsPolicy: ClusterFirst + restartPolicy: Never + serviceAccount: {{ .Values.integration.keycloak.serviceAccountName }} + serviceAccountName: {{ .Values.integration.keycloak.serviceAccountName }} + terminationGracePeriodSeconds: 30 +{{- end }} diff --git a/charts/acs-central/values.yaml b/charts/acs-central/values.yaml index a609df8b..4d7e4787 100644 --- a/charts/acs-central/values.yaml +++ b/charts/acs-central/values.yaml @@ -1,7 +1,7 @@ # Default values for ACS Central Services # This chart deploys the Central and Scanner components # -# Cluster name for init bundle +# Cluster name for CRS (Cluster Registration Secret) registration clusterName: "" # Will be set from global.localClusterName by pattern framework global: diff --git a/charts/acs-secured-cluster/Chart.yaml b/charts/acs-secured-cluster/Chart.yaml index 0874c87a..d7585836 100644 --- a/charts/acs-secured-cluster/Chart.yaml +++ b/charts/acs-secured-cluster/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: acs-secured-cluster description: Red Hat Advanced Cluster Security Secured Cluster Services type: application -version: 1.0.0 -appVersion: "4.9" +version: 1.0.1 +appVersion: "4.11" keywords: - security - runtime-protection diff --git a/charts/acs-secured-cluster/values.yaml b/charts/acs-secured-cluster/values.yaml index c53a32bd..cc54222e 100644 --- a/charts/acs-secured-cluster/values.yaml +++ b/charts/acs-secured-cluster/values.yaml @@ -9,11 +9,12 @@ clusterName: "" # Will be overridden by values-hub.yaml # Central endpoint configuration centralEndpoint: "" # e.g., central-stackrox.apps.cluster.example.com:443 -# Init bundle secret (from Vault) +# Cluster registration is handled by the CRS job in acs-central chart. +# The CRS (Cluster Registration Secret) replaces the deprecated init-bundle approach. +# Legacy init-bundle values kept for reference: initBundle: useExternalSecret: true secretName: collector-tls - # If not using external secret, provide bundle data data: "" # Sensor configuration diff --git a/values-hub.yaml b/values-hub.yaml index 6ddd6c07..5a9d832d 100644 --- a/values-hub.yaml +++ b/values-hub.yaml @@ -595,7 +595,7 @@ clusterGroup: value: "ztvp" - name: integration.keycloak.clientId value: "acs-central" - # Must match acs-secured-cluster clusterName (init bundle API name) + # Must match acs-secured-cluster clusterName (CRS registration name) - name: clusterName value: hub # ACS to scan images stored in Quay (Uncomment to enable)