diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml index de22a7f3..87d10257 100644 --- a/.github/workflows/superlinter.yml +++ b/.github/workflows/superlinter.yml @@ -31,6 +31,8 @@ jobs: VALIDATE_ALL_CODEBASE: true DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Exclude bash scripts with Helm templating (they contain {{ }} syntax) + FILTER_REGEX_EXCLUDE: .*\.sh\.tpl$ # These are the validation we disable atm VALIDATE_ANSIBLE: false VALIDATE_BASH: false diff --git a/charts/qtodo/files/spiffe-vault-client.py b/charts/qtodo/files/spiffe-vault-client.py index 9a726ed8..e07255d3 100644 --- a/charts/qtodo/files/spiffe-vault-client.py +++ b/charts/qtodo/files/spiffe-vault-client.py @@ -28,6 +28,12 @@ def __init__(self): self.credentials_file = os.getenv( "CREDENTIALS_FILE", "/etc/credentials.properties" ) + # ZTVP trusted CA bundle (preferred) + self.ztvp_ca_bundle = os.getenv( + "ZTVP_CA_BUNDLE", + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", + ) + # Service CA (fallback for backward compatibility) self.service_ca_file = os.getenv( "SERVICE_CA_FILE", "/run/secrets/kubernetes.io/serviceaccount/service-ca.crt", @@ -54,6 +60,7 @@ def __init__(self): logger.info(" VAULT_ROLE: %s", self.vault_role) logger.info(" DB_USERNAME: %s", self.db_username) logger.info(" CREDENTIALS_FILE: %s", self.credentials_file) + logger.info(" ZTVP_CA_BUNDLE: %s", self.ztvp_ca_bundle) logger.info(" SERVICE_CA_FILE: %s", self.service_ca_file) logger.info(" JWT_TOKEN_FILE: %s", self.jwt_token_file) @@ -63,10 +70,22 @@ def __init__(self): # Setup SSL context for CA verification self.ssl_context = ssl.create_default_context() - if os.path.exists(self.service_ca_file): + + # Try ZTVP CA bundle first (contains both ingress and service CAs) + if os.path.exists(self.ztvp_ca_bundle): + self.ssl_context.load_verify_locations(self.ztvp_ca_bundle) + logger.info("Loaded ZTVP trusted CA bundle from: %s", self.ztvp_ca_bundle) + # Fallback to service CA only (for backward compatibility) + elif os.path.exists(self.service_ca_file): self.ssl_context.load_verify_locations(self.service_ca_file) + logger.info("Loaded service CA from: %s", self.service_ca_file) else: - logger.warning("Service CA file not found, using default SSL context") + logger.warning( + "No CA certificates found at %s or %s. " + "Using default SSL context. SSL verification may fail.", + self.ztvp_ca_bundle, + self.service_ca_file, + ) def _make_http_request( self, url, method="GET", data=None, headers=None, timeout=30 @@ -111,11 +130,11 @@ def _make_http_request( "text": error_data, "json": lambda: (json.loads(error_data) if error_data else {}), } - except URLError as e: - logger.error("URL Error: %s", e) + except URLError: + logger.error("URL Error occurred") raise - except Exception as e: - logger.error("Request error: %s", e) + except Exception: + logger.error("Request error occurred") raise def get_spiffe_token(self): @@ -125,8 +144,8 @@ def get_spiffe_token(self): jwt_svid = source.read() logger.info("Successfully retrieved SPIFFE JWT token") return jwt_svid - except Exception as e: - logger.error("Failed to retrieve SPIFFE token: %s", e) + except Exception: + logger.error("Failed to retrieve SPIFFE token") raise def authenticate_with_vault(self): @@ -167,8 +186,8 @@ def authenticate_with_vault(self): return True - except Exception as e: - logger.error("Vault authentication error: %s", e) + except Exception: + logger.error("Vault authentication error occurred") raise def retrieve_vault_secret(self): @@ -204,8 +223,8 @@ def retrieve_vault_secret(self): return secret_data - except Exception as e: - logger.error("Secret retrieval error: %s", e) + except Exception: + logger.error("Secret retrieval error occurred") raise def extract_credentials(self, secret_data): @@ -222,8 +241,8 @@ def extract_credentials(self, secret_data): credentials["db-username"] = self.db_username return credentials - except Exception as e: - logger.error("Credential extraction error: %s", e) + except Exception: + logger.error("Credential extraction error occurred") raise def write_properties_file(self, credentials): @@ -243,8 +262,8 @@ def write_properties_file(self, credentials): logger.info("Credentials written to %s", self.credentials_file) - except Exception as e: - logger.error("Error writing properties file: %s", e) + except Exception: + logger.error("Error writing properties file") raise def is_token_renewal_needed(self): @@ -291,8 +310,8 @@ def renew_vault_token(self): ) return False - except Exception as e: - logger.warning("Token renewal error: %s. Re-authenticating...", e) + except Exception: + logger.warning("Token renewal error occurred. Re-authenticating...") return False def run(self, init=False): @@ -329,8 +348,8 @@ def run(self, init=False): except KeyboardInterrupt: logger.info("Received interrupt signal, shutting down...") break - except Exception as e: - logger.error("Error in main loop: %s", e) + except Exception: + logger.error("Error in main loop") logger.info("Retrying in 60 seconds...") time.sleep(60) @@ -352,7 +371,7 @@ def main(): manager = VaultCredentialManager() manager.run(args.init) except Exception as e: - logger.error("Failed to start credential manager: %s", e) + logger.error("Failed to start credential manager") raise SystemExit(1) from e diff --git a/charts/qtodo/templates/app-deployment.yaml b/charts/qtodo/templates/app-deployment.yaml index f1c78a69..afa00316 100644 --- a/charts/qtodo/templates/app-deployment.yaml +++ b/charts/qtodo/templates/app-deployment.yaml @@ -5,6 +5,7 @@ metadata: argocd.argoproj.io/sync-wave: '20' labels: app: qtodo + ztvp.io/uses-certificates: "true" name: qtodo namespace: qtodo spec: @@ -65,6 +66,8 @@ spec: value: /run/secrets/db-credentials/credentials.properties - name: JWT_TOKEN_FILE value: /svids/jwt.token + - name: ZTVP_CA_BUNDLE + value: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem volumeMounts: - name: svids mountPath: /svids @@ -72,6 +75,9 @@ spec: mountPath: /run/secrets/db-credentials - name: spiffe-vault-client mountPath: /opt/app-root/src + - name: ztvp-trusted-ca + mountPath: /etc/pki/ca-trust/extracted/pem + readOnly: true {{- end }} containers: {{- if and .Values.app.spire.enabled .Values.app.spire.sidecars }} @@ -113,6 +119,8 @@ spec: value: /run/secrets/db-credentials/credentials.properties - name: JWT_TOKEN_FILE value: /svids/jwt.token + - name: ZTVP_CA_BUNDLE + value: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem volumeMounts: - name: svids mountPath: /svids @@ -122,6 +130,9 @@ spec: - name: spiffe-vault-client mountPath: /opt/app-root/src readOnly: true + - name: ztvp-trusted-ca + mountPath: /etc/pki/ca-trust/extracted/pem + readOnly: true {{- end }} - name: qtodo image: {{ .Values.app.images.main.name }}:{{ .Values.app.images.main.tag }} @@ -195,4 +206,8 @@ spec: - name: spiffe-vault-client configMap: name: spiffe-vault-client + - name: ztvp-trusted-ca + configMap: + name: ztvp-trusted-ca + defaultMode: 420 {{- end }} diff --git a/charts/ztvp-certificates/Chart.yaml b/charts/ztvp-certificates/Chart.yaml new file mode 100644 index 00000000..0da9933d --- /dev/null +++ b/charts/ztvp-certificates/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +name: ztvp-certificates +description: Global CA certificate management for ZTVP pattern components +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - certificates + - tls + - ssl + - ca-bundle + - zero-trust +home: https://github.com/validatedpatterns/layered-zero-trust +sources: + - https://github.com/validatedpatterns/layered-zero-trust +maintainers: + - name: Zero Trust Validated Patterns Team + email: ztvp-arch-group@redhat.com + diff --git a/charts/ztvp-certificates/files/extract-certificates.sh.tpl b/charts/ztvp-certificates/files/extract-certificates.sh.tpl new file mode 100644 index 00000000..5db24275 --- /dev/null +++ b/charts/ztvp-certificates/files/extract-certificates.sh.tpl @@ -0,0 +1,400 @@ +#!/bin/bash +# shellcheck disable=SC1091,SC2050,SC2154 +# This is a Helm template file - shellcheck will fail on Helm syntax +set -e +{{- if .Values.debug.verbose }} +set -x +{{- end }} + +# Logging functions +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } +error() { echo "[ERROR] $*" >&2; } + +# Initialize variables +INGRESS_CA_FOUND=false +SERVICE_CA_FOUND=false +CUSTOM_CA_FOUND=false +CLUSTER_CA_FOUND=false +TEMP_DIR=$(mktemp -d) + +log "===========================================" +log "ZTVP CA Certificate Extraction" +log "===========================================" +log "Auto-detect: {{ .Values.autoDetect }}" +log "Custom CA: {{ .Values.customCA.secretRef.enabled }}" +log "Namespace: {{ .Values.global.namespace }}" +log "ConfigMap: {{ .Values.configMapName }}" + +# =================================================================== +# PHASE 1: Extract Custom CA (if configured) +# =================================================================== + +{{- if .Values.customCA.secretRef.enabled }} +log "Extracting custom CA from secret: {{ .Values.customCA.secretRef.namespace }}/{{ .Values.customCA.secretRef.name }}" +if oc get secret {{ .Values.customCA.secretRef.name }} -n {{ .Values.customCA.secretRef.namespace }} &>/dev/null; then + KEY="{{ .Values.customCA.secretRef.key }}" + ESCAPED_KEY="${KEY//./\\.}" + oc get secret {{ .Values.customCA.secretRef.name }} \ + -n {{ .Values.customCA.secretRef.namespace }} \ + -o "jsonpath={.data.${ESCAPED_KEY}}" | \ + base64 -d > "${TEMP_DIR}/custom-ca.crt" + CUSTOM_CA_FOUND=true + log "Custom CA extracted from secret successfully" +else + error "Custom secret not found: {{ .Values.customCA.secretRef.namespace }}/{{ .Values.customCA.secretRef.name }}" + exit 1 +fi +{{- end }} + +# =================================================================== +# PHASE 2: Extract Ingress CA (if auto-detect enabled) +# =================================================================== + +{{- if .Values.autoDetect }} +# Auto-detect from OpenShift +log "Auto-detecting ingress CA certificate" + +{{- if .Values.customSource.ingressCA.secretName }} +# Use custom source location +log "Using custom source: {{ .Values.customSource.ingressCA.secretNamespace }}/{{ .Values.customSource.ingressCA.secretName }}" +if oc get secret {{ .Values.customSource.ingressCA.secretName }} -n {{ .Values.customSource.ingressCA.secretNamespace }} &>/dev/null; then + oc get secret {{ .Values.customSource.ingressCA.secretName }} \ + -n {{ .Values.customSource.ingressCA.secretNamespace }} \ + -o jsonpath='{.data.{{ .Values.customSource.ingressCA.secretKey }}}' | \ + base64 -d > "${TEMP_DIR}/ingress-ca.crt" + INGRESS_CA_FOUND=true + log "Ingress CA extracted from custom source" +else + error "Custom ingress CA secret not found" +fi +{{- else }} +# Standard auto-detection logic +# Loop through all IngressControllers +INGRESSCONTROLLERS=$(oc get ingresscontroller -n openshift-ingress-operator -o name 2>/dev/null || echo "") + +if [[ -n "$INGRESSCONTROLLERS" ]]; then + INGRESS_INDEX=0 + for ic in $INGRESSCONTROLLERS; do + IC_NAME=$(echo "$ic" | cut -d'/' -f2) + log "Checking IngressController: $IC_NAME" + + # Check for custom certificate reference + CUSTOM_CERT=$(oc get ingresscontroller "$IC_NAME" -n openshift-ingress-operator \ + -o jsonpath='{.spec.defaultCertificate.name}' 2>/dev/null || echo "") + + if [[ -n "$CUSTOM_CERT" ]]; then + log "Found custom certificate reference: $CUSTOM_CERT" + if oc get secret "$CUSTOM_CERT" -n openshift-ingress &>/dev/null; then + oc get secret "$CUSTOM_CERT" -n openshift-ingress \ + -o jsonpath='{.data.tls\.crt}' | base64 -d > "${TEMP_DIR}/ingress-ca-${IC_NAME}.crt" + INGRESS_CA_FOUND=true + INGRESS_INDEX=$((INGRESS_INDEX + 1)) + log "Extracted ingress CA from $IC_NAME" + fi + else + # Try default router-certs secret for this IngressController + SECRET_NAME="router-certs-${IC_NAME}" + if oc get secret "$SECRET_NAME" -n openshift-ingress &>/dev/null; then + log "Found router secret: $SECRET_NAME" + oc get secret "$SECRET_NAME" -n openshift-ingress \ + -o jsonpath='{.data.tls\.crt}' | base64 -d > "${TEMP_DIR}/ingress-ca-${IC_NAME}.crt" + INGRESS_CA_FOUND=true + INGRESS_INDEX=$((INGRESS_INDEX + 1)) + fi + fi + done + + log "Extracted certificates from $INGRESS_INDEX IngressController(s)" +else + log "WARNING: No IngressControllers found" +fi +{{- end }} +{{- end }} + +# =================================================================== +# PHASE 3: Extract Service CA (if auto-detect enabled) +# =================================================================== + +{{- if .Values.autoDetect }} +log "Extracting OpenShift service CA" + +{{- if .Values.customSource.serviceCA.configMapName }} +# Use custom source location +log "Using custom service CA source: {{ .Values.customSource.serviceCA.configMapNamespace }}/{{ .Values.customSource.serviceCA.configMapName }}" +if oc get configmap {{ .Values.customSource.serviceCA.configMapName }} -n {{ .Values.customSource.serviceCA.configMapNamespace }} &>/dev/null; then + oc get configmap {{ .Values.customSource.serviceCA.configMapName }} \ + -n {{ .Values.customSource.serviceCA.configMapNamespace }} \ + -o jsonpath='{.data.{{ .Values.customSource.serviceCA.configMapKey }}}' \ + > "${TEMP_DIR}/service-ca.crt" + SERVICE_CA_FOUND=true + log "Service CA extracted from custom source" +else + log "WARNING: Custom service CA ConfigMap not found" +fi +{{- else }} +# Standard location +if oc get configmap openshift-service-ca.crt -n openshift-config &>/dev/null; then + oc get configmap openshift-service-ca.crt -n openshift-config \ + -o jsonpath='{.data.service-ca\.crt}' > "${TEMP_DIR}/service-ca.crt" + SERVICE_CA_FOUND=true + log "Service CA extracted successfully" +else + log "WARNING: Service CA not found (this may be expected in some environments)" +fi +{{- end }} +{{- end }} + +# =================================================================== +# PHASE 4: Extract Proxy/Cluster-wide Trusted CA Bundle +# =================================================================== + +{{- if .Values.autoDetect }} +log "Checking for cluster-wide trusted CA bundle" + +# Extract from openshift-config-managed/trusted-ca-bundle +if oc get configmap trusted-ca-bundle -n openshift-config-managed &>/dev/null; then + log "Found trusted-ca-bundle in openshift-config-managed" + oc get configmap trusted-ca-bundle -n openshift-config-managed \ + -o jsonpath='{.data.ca-bundle\.crt}' > "${TEMP_DIR}/trusted-ca-bundle.crt" 2>/dev/null || true + + # Check if we got valid content + if [[ -s "${TEMP_DIR}/trusted-ca-bundle.crt" ]]; then + CLUSTER_CA_FOUND=true + log "Cluster-wide trusted CA bundle extracted successfully" + else + log "trusted-ca-bundle ConfigMap exists but has no ca-bundle.crt data" + rm -f "${TEMP_DIR}/trusted-ca-bundle.crt" + fi +else + log "No cluster-wide trusted-ca-bundle found (this is normal for clusters without proxy configuration)" +fi +{{- end }} + +# =================================================================== +# PHASE 5: Load Additional Certificates (if configured) +# =================================================================== + +{{- if .Values.customCA.additionalCertificates }} +log "Loading {{ len .Values.customCA.additionalCertificates }} additional certificate(s) from secrets" + +{{- range $cert := .Values.customCA.additionalCertificates }} +log "Loading additional certificate: {{ $cert.name }}" +if oc get secret {{ $cert.secretRef.name }} -n {{ $cert.secretRef.namespace }} &>/dev/null; then + ADDL_KEY="{{ $cert.secretRef.key }}" + ADDL_ESCAPED_KEY="${ADDL_KEY//./\\.}" + oc get secret {{ $cert.secretRef.name }} \ + -n {{ $cert.secretRef.namespace }} \ + -o "jsonpath={.data.${ADDL_ESCAPED_KEY}}" | \ + base64 -d > "${TEMP_DIR}/{{ $cert.name }}.crt" + log "OK: Loaded additional certificate: {{ $cert.name }}" +else + log "WARNING: Additional certificate secret not found: {{ $cert.secretRef.namespace }}/{{ $cert.secretRef.name }}" + log "WARNING: Skipping {{ $cert.name }} and continuing with other certificates" +fi +{{- end }} +{{- end }} + +# =================================================================== +# PHASE 6: Validate Certificates +# =================================================================== + +{{- if .Values.validation.enabled }} +log "Validating extracted certificates" + +CERT_COUNT=0 +for cert_file in "${TEMP_DIR}"/*.crt; do + [[ -f "$cert_file" ]] || continue + + CERT_COUNT=$((CERT_COUNT + 1)) + + # Check minimum size + CERT_SIZE=$(wc -c < "$cert_file" 2>/dev/null || echo 0) + if [[ $CERT_SIZE -lt {{ .Values.validation.minSize }} ]]; then + error "Certificate too small: $cert_file ($CERT_SIZE bytes)" + exit 1 + fi + + {{- if .Values.validation.parseCheck }} + # Verify certificate can be parsed + if ! openssl x509 -in "$cert_file" -noout 2>/dev/null; then + error "Invalid certificate format: $cert_file" + exit 1 + fi + log "OK: Valid certificate: $(basename $cert_file)" + {{- end }} +done + +log "Validated $CERT_COUNT certificate(s)" +{{- end }} + +# =================================================================== +# PHASE 7: Combine into Bundle +# =================================================================== + +log "Creating combined CA bundle" + +# Combine all certificates +> "${TEMP_DIR}/tls-ca-bundle.pem" +for cert_file in "${TEMP_DIR}"/*.crt; do + [[ -f "$cert_file" ]] || continue + cat "$cert_file" >> "${TEMP_DIR}/tls-ca-bundle.pem" + echo "" >> "${TEMP_DIR}/tls-ca-bundle.pem" # Add blank line between certs +done + +# Verify bundle is not empty +BUNDLE_SIZE=$(wc -c < "${TEMP_DIR}/tls-ca-bundle.pem" 2>/dev/null || echo 0) +if [[ $BUNDLE_SIZE -lt 100 ]]; then + error "==================================================================" + error "No certificates found or bundle too small" + error "==================================================================" + error "" + error "Certificate Status:" + error " Custom CA found: $CUSTOM_CA_FOUND" + error " Ingress CA found: $INGRESS_CA_FOUND" + error " Service CA found: $SERVICE_CA_FOUND" + error " Bundle size: $BUNDLE_SIZE bytes" + error "" + error "To resolve this, provide a custom CA certificate via secret:" + error "" + error "1. Create a secret with your certificate:" + error " oc create secret generic custom-ca-bundle \\" + error " --from-file=ca.crt=/path/to/ca.crt \\" + error " -n openshift-config" + error "" + error "2. Configure values-hub.yaml:" + error " ztvp-certificates:" + error " overrides:" + error " - name: customCA.secretRef.enabled" + error " value: \"true\"" + error " - name: customCA.secretRef.name" + error " value: custom-ca-bundle" + error " - name: customCA.secretRef.namespace" + error " value: openshift-config" + error "" + error "For more information, see the chart documentation." + error "==================================================================" + exit 1 +fi + +log "Combined CA bundle created: $BUNDLE_SIZE bytes" + +# =================================================================== +# PHASE 8: Create ConfigMap +# =================================================================== + +log "Creating ConfigMap: {{ .Values.global.namespace }}/{{ .Values.configMapName }}" + +cat </dev/null; then + VERIFY_SIZE=$(oc get configmap {{ .Values.configMapName }} -n {{ .Values.global.namespace }} \ + -o jsonpath='{.data.tls-ca-bundle\.pem}' 2>/dev/null | wc -c || echo 0) + + if [[ $VERIFY_SIZE -lt 100 ]]; then + error "ConfigMap created but certificate data is missing or too small ($VERIFY_SIZE bytes)" + exit 1 + fi + + log "===========================================" + log "SUCCESS: CA certificate bundle configured" + log "===========================================" + log "ConfigMap: {{ .Values.global.namespace }}/{{ .Values.configMapName }}" + log "Bundle size: $VERIFY_SIZE bytes" + log "Custom CA: $CUSTOM_CA_FOUND" + log "Ingress CA: $INGRESS_CA_FOUND" + log "Service CA: $SERVICE_CA_FOUND" + log "Cluster CA: $CLUSTER_CA_FOUND" + log "===========================================" +else + error "Failed to create ConfigMap" + exit 1 +fi + +# =================================================================== +# PHASE 9: Automatic Rollout (if enabled) +# =================================================================== + +{{- if .Values.rollout.enabled }} +log "Triggering automatic rollout for consuming applications" +log "Rollout strategy: {{ .Values.rollout.strategy }}" + +ROLLOUT_COUNT=0 + +{{- if eq .Values.rollout.strategy "specific" }} +# Strategy: specific - restart named resources +{{- range .Values.rollout.targets }} +log "Restarting {{ .kind }}/{{ .name }} in namespace {{ .namespace }}" +if oc get {{ .kind | lower }} {{ .name }} -n {{ .namespace }} &>/dev/null; then + if oc rollout restart {{ .kind | lower }}/{{ .name }} -n {{ .namespace }}; then + log "OK: Successfully triggered rollout for {{ .kind }}/{{ .name }}" + ROLLOUT_COUNT=$((ROLLOUT_COUNT + 1)) + else + log "WARNING: Failed to restart {{ .kind }}/{{ .name }} (continuing anyway)" + fi +else + log "WARNING: {{ .kind }}/{{ .name }} not found in namespace {{ .namespace }}" +fi +{{- end }} + +{{- else }} +# Strategy: all or labeled - restart resources in distribution target namespaces +{{- range $ns := .Values.distribution.targetNamespaces }} +log "Processing namespace: {{ $ns }}" + +{{- range $kind := $.Values.rollout.resourceKinds }} +{{- if eq $.Values.rollout.strategy "labeled" }} +# Get resources with specific labels +LABEL_SELECTOR="{{ range $key, $value := $.Values.rollout.labelSelector }}{{ $key }}={{ $value }}{{ end }}" +log "Finding {{ $kind }}s with labels: $LABEL_SELECTOR" +RESOURCES=$(oc get {{ $kind | lower }} -n {{ $ns }} -l "$LABEL_SELECTOR" -o name 2>/dev/null || true) +{{- else }} +# Get all resources +log "Finding all {{ $kind }}s in namespace {{ $ns }}" +RESOURCES=$(oc get {{ $kind | lower }} -n {{ $ns }} -o name 2>/dev/null || true) +{{- end }} + +if [[ -n "$RESOURCES" ]]; then + for resource in $RESOURCES; do + log "Restarting $resource in namespace {{ $ns }}" + if oc rollout restart "$resource" -n {{ $ns }}; then + log "OK: Successfully triggered rollout for $resource" + ROLLOUT_COUNT=$((ROLLOUT_COUNT + 1)) + else + log "WARNING: Failed to restart $resource (continuing anyway)" + fi + done +else + log "No {{ $kind }}s found in namespace {{ $ns }}" +fi +{{- end }} +{{- end }} +{{- end }} + +log "Automatic rollout completed: $ROLLOUT_COUNT resource(s) restarted" +{{- end }} + diff --git a/charts/ztvp-certificates/templates/_helpers.tpl b/charts/ztvp-certificates/templates/_helpers.tpl new file mode 100644 index 00000000..6480f983 --- /dev/null +++ b/charts/ztvp-certificates/templates/_helpers.tpl @@ -0,0 +1,66 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "ztvp-certificates.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "ztvp-certificates.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ztvp-certificates.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "ztvp-certificates.labels" -}} +helm.sh/chart: {{ include "ztvp-certificates.chart" . }} +{{ include "ztvp-certificates.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "ztvp-certificates.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ztvp-certificates.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Service account name +*/}} +{{- define "ztvp-certificates.serviceAccountName" -}} +{{- printf "%s-ca-extractor" (include "ztvp-certificates.fullname" .) }} +{{- end }} + +{{/* +ACM hub template for reading ConfigMap data +Usage: include "ztvp-certificates.acmHubFromConfigMap" (dict "namespace" "openshift-config" "configmap" "ztvp-trusted-ca" "key" "tls-ca-bundle.pem") +Outputs: {{hub fromConfigMap "namespace" "configmap" "key" | autoindent hub}} +*/}} +{{- define "ztvp-certificates.acmHubFromConfigMap" -}} +{{- printf "{{hub fromConfigMap \"%s\" \"%s\" \"%s\" | autoindent hub}}" .namespace .configmap .key }} +{{- end }} + diff --git a/charts/ztvp-certificates/templates/ca-extraction-cronjob.yaml b/charts/ztvp-certificates/templates/ca-extraction-cronjob.yaml new file mode 100644 index 00000000..f18b2bd3 --- /dev/null +++ b/charts/ztvp-certificates/templates/ca-extraction-cronjob.yaml @@ -0,0 +1,68 @@ +{{- if and .Values.enabled .Values.cronJob.enabled }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-ca-extractor + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-8" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.cronJob.schedule | quote }} + successfulJobsHistoryLimit: {{ .Values.cronJob.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.cronJob.failedJobsHistoryLimit }} + concurrencyPolicy: {{ .Values.cronJob.concurrencyPolicy }} + {{- if .Values.cronJob.startingDeadlineSeconds }} + startingDeadlineSeconds: {{ .Values.cronJob.startingDeadlineSeconds }} + {{- end }} + suspend: {{ .Values.cronJob.suspend }} + jobTemplate: + metadata: + labels: + {{- include "ztvp-certificates.labels" . | nindent 8 }} + spec: + {{- if not .Values.debug.keepFailedJobs }} + ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} + {{- end }} + backoffLimit: {{ .Values.job.backoffLimit }} + template: + metadata: + labels: + {{- include "ztvp-certificates.selectorLabels" . | nindent 12 }} + spec: + serviceAccountName: {{ include "ztvp-certificates.serviceAccountName" . }} + restartPolicy: OnFailure + securityContext: + runAsNonRoot: true + runAsUser: 1001 + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault + volumes: + - name: script + configMap: + name: {{ include "ztvp-certificates.fullname" . }}-script + defaultMode: 0755 + containers: + - name: ca-extractor + image: {{ .Values.job.image.registry }}/{{ .Values.job.image.repository }}:{{ .Values.job.image.tag }} + imagePullPolicy: {{ .Values.job.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + runAsUser: 1001 + capabilities: + drop: + - ALL + runAsNonRoot: true + resources: + {{- toYaml .Values.job.resources | nindent 14 }} + volumeMounts: + - name: script + mountPath: /scripts + readOnly: true + command: + - /bin/bash + - /scripts/extract-certificates.sh +{{- end }} diff --git a/charts/ztvp-certificates/templates/ca-extraction-job-initial.yaml b/charts/ztvp-certificates/templates/ca-extraction-job-initial.yaml new file mode 100644 index 00000000..6702e825 --- /dev/null +++ b/charts/ztvp-certificates/templates/ca-extraction-job-initial.yaml @@ -0,0 +1,57 @@ +{{- if .Values.enabled }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-ca-extractor-initial + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-8" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} + app.kubernetes.io/component: initial-extraction +spec: + {{- if not .Values.debug.keepFailedJobs }} + ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} + {{- end }} + backoffLimit: {{ .Values.job.backoffLimit }} + template: + metadata: + labels: + {{- include "ztvp-certificates.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: initial-extraction + spec: + serviceAccountName: {{ include "ztvp-certificates.serviceAccountName" . }} + restartPolicy: OnFailure + securityContext: + runAsNonRoot: true + runAsUser: 1001 + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault + volumes: + - name: script + configMap: + name: {{ include "ztvp-certificates.fullname" . }}-script + defaultMode: 0755 + containers: + - name: ca-extractor + image: {{ .Values.job.image.registry }}/{{ .Values.job.image.repository }}:{{ .Values.job.image.tag }} + imagePullPolicy: {{ .Values.job.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + runAsUser: 1001 + capabilities: + drop: + - ALL + runAsNonRoot: true + resources: + {{- toYaml .Values.job.resources | nindent 10 }} + volumeMounts: + - name: script + mountPath: /scripts + readOnly: true + command: + - /bin/bash + - /scripts/extract-certificates.sh +{{- end }} diff --git a/charts/ztvp-certificates/templates/configmap-script.yaml b/charts/ztvp-certificates/templates/configmap-script.yaml new file mode 100644 index 00000000..802fda93 --- /dev/null +++ b/charts/ztvp-certificates/templates/configmap-script.yaml @@ -0,0 +1,17 @@ +{{- if .Values.enabled }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-script + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} + app.kubernetes.io/component: extraction-script +data: + extract-certificates.sh: | + {{- tpl (.Files.Get "files/extract-certificates.sh.tpl") . | nindent 4 }} +{{- end }} + diff --git a/charts/ztvp-certificates/templates/distribution-policy.yaml b/charts/ztvp-certificates/templates/distribution-policy.yaml new file mode 100644 index 00000000..a3f5a3c5 --- /dev/null +++ b/charts/ztvp-certificates/templates/distribution-policy.yaml @@ -0,0 +1,82 @@ +{{- if .Values.distribution.enabled }} +--- +apiVersion: policy.open-cluster-management.io/v1 +kind: Policy +metadata: + name: ztvp-certificates-distribution + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-5" + policy.open-cluster-management.io/standards: NIST-CSF + policy.open-cluster-management.io/categories: PR.DS Data Security + policy.open-cluster-management.io/controls: PR.DS-2 Data-in-transit +spec: + disabled: false + remediationAction: enforce + policy-templates: + - objectDefinition: + apiVersion: policy.open-cluster-management.io/v1 + kind: ConfigurationPolicy + metadata: + name: ztvp-ca-distribution + spec: + remediationAction: enforce + severity: high + namespaceSelector: + include: +{{- range .Values.distribution.targetNamespaces }} + - {{ . }} +{{- end }} + object-templates: + - complianceType: musthave + objectDefinition: + apiVersion: v1 + kind: ConfigMap + metadata: + name: ztvp-trusted-ca + labels: + app.kubernetes.io/managed-by: ztvp-certificates + ztvp.io/certificate-bundle: "true" + annotations: + ztvp.io/source: "openshift-config/ztvp-trusted-ca" + ztvp.io/distribution-method: "acm-policy" + data: + tls-ca-bundle.pem: | + {{ `{{hub fromConfigMap "` }}{{ $.Values.global.namespace }}{{ `" "` }}{{ $.Values.configMapName }}{{ `" "tls-ca-bundle.pem" | autoindent hub}}` }} +--- +apiVersion: policy.open-cluster-management.io/v1 +kind: PlacementBinding +metadata: + name: ztvp-certificates-distribution-binding + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-5" +bindingOverrides: + remediationAction: enforce +placementRef: + apiGroup: cluster.open-cluster-management.io + kind: Placement + name: ztvp-certificates-distribution-placement +subjects: + - apiGroup: policy.open-cluster-management.io + kind: Policy + name: ztvp-certificates-distribution +--- +apiVersion: cluster.open-cluster-management.io/v1beta1 +kind: Placement +metadata: + name: ztvp-certificates-distribution-placement + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-5" +spec: + predicates: + - requiredClusterSelector: + labelSelector: + matchExpressions: + - key: local-cluster + operator: In + values: + - "true" +{{- end }} + diff --git a/charts/ztvp-certificates/templates/managedclusterset-binding.yaml b/charts/ztvp-certificates/templates/managedclusterset-binding.yaml new file mode 100644 index 00000000..82ccf07f --- /dev/null +++ b/charts/ztvp-certificates/templates/managedclusterset-binding.yaml @@ -0,0 +1,17 @@ +{{- if .Values.distribution.enabled }} +{{- if eq .Values.distribution.method "acm-policy" }} +--- +apiVersion: cluster.open-cluster-management.io/v1beta2 +kind: ManagedClusterSetBinding +metadata: + name: default + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-6" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +spec: + clusterSet: default +{{- end }} +{{- end }} + diff --git a/charts/ztvp-certificates/templates/rbac.yaml b/charts/ztvp-certificates/templates/rbac.yaml new file mode 100644 index 00000000..94949dc7 --- /dev/null +++ b/charts/ztvp-certificates/templates/rbac.yaml @@ -0,0 +1,117 @@ +{{- if .Values.enabled }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "ztvp-certificates.serviceAccountName" . }} + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "ztvp-certificates.serviceAccountName" . }} + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "create", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "ztvp-certificates.serviceAccountName" . }} + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "ztvp-certificates.serviceAccountName" . }} +subjects: +- kind: ServiceAccount + name: {{ include "ztvp-certificates.serviceAccountName" . }} + namespace: {{ .Values.global.namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-ca-reader + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +rules: +# Read ingress CA from router secrets (dynamically discovered from any IngressController) +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] +# Read OpenShift service CA and cluster-wide trusted-ca-bundle +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list"] +# Read ingress controller configuration to detect custom certs +- apiGroups: ["operator.openshift.io"] + resources: ["ingresscontrollers"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-ca-reader + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "ztvp-certificates.fullname" . }}-ca-reader +subjects: +- kind: ServiceAccount + name: {{ include "ztvp-certificates.serviceAccountName" . }} + namespace: {{ .Values.global.namespace }} +{{- if .Values.rollout.enabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-rollout + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +rules: +# Allow triggering rollout restart for consuming applications +- apiGroups: ["apps"] + resources: ["deployments", "statefulsets", "daemonsets"] + verbs: ["get", "list", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-rollout + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "ztvp-certificates.fullname" . }}-rollout +subjects: +- kind: ServiceAccount + name: {{ include "ztvp-certificates.serviceAccountName" . }} + namespace: {{ .Values.global.namespace }} +{{- end }} +{{- end }} + diff --git a/charts/ztvp-certificates/values.yaml b/charts/ztvp-certificates/values.yaml new file mode 100644 index 00000000..354ee4c7 --- /dev/null +++ b/charts/ztvp-certificates/values.yaml @@ -0,0 +1,186 @@ +# Global certificate configuration for ZTVP pattern +# Provides CA certificates for SSL/TLS verification across all components + +# Global settings +global: + # Namespace where the source ConfigMap will be created + # Using openshift-config namespace (standard location for cluster-wide configuration) + namespace: openshift-config + +# Enable/disable certificate management +enabled: true + +# Automatically detect and extract CA certificates from OpenShift +# Recommended: true (works for most scenarios) +autoDetect: true + +# Custom CA certificate configuration +customCA: + # Primary custom CA: Reference an existing Kubernetes secret containing CA certificates + # To create the secret: + # Single cert: oc create secret generic custom-ca-bundle --from-file=ca.crt=/path/to/ca.crt -n openshift-config + # Multiple certs: cat cert1.crt cert2.crt cert3.crt > combined.crt && oc create secret generic custom-ca-bundle --from-file=ca.crt=combined.crt -n openshift-config + secretRef: + enabled: false + name: "" # Name of secret containing CA certificate(s) + namespace: "" # Namespace where secret is located + key: "ca.crt" # Key in secret containing the certificate(s) + # Example: + # secretRef: + # enabled: true + # name: custom-ca-bundle + # namespace: openshift-config + # key: ca.crt + + # Additional CA certificates: Reference multiple secrets + # Useful when you have multiple CA certificates in separate secrets + # Create secrets first: + # oc create secret generic --from-file=ca.crt=/path/to/cert.crt -n openshift-config + # Configure via overrides/values-ztvp-certificates.yaml (using extraValueFiles) + additionalCertificates: [] + # Example: + # additionalCertificates: + # - name: corporate-root-ca + # secretRef: + # name: corporate-root-ca + # namespace: openshift-config + # key: ca.crt + +# Custom source locations for auto-detection +# Override default locations where CA certificates are found +customSource: + # Where to find ingress CA certificate + ingressCA: + # Default: router-certs-default secret in openshift-ingress + secretName: "" # Leave empty to use auto-detection + secretNamespace: "" + secretKey: "tls.crt" + + # Where to find OpenShift service CA certificate + serviceCA: + # Default: openshift-service-ca.crt ConfigMap in openshift-config + configMapName: "" # Leave empty to use auto-detection + configMapNamespace: "" + configMapKey: "service-ca.crt" + +# CronJob configuration for automatic certificate extraction and rotation +# A one-time Job runs during initial installation (ArgoCD PreSync hook) +# The CronJob handles automatic rotation on a schedule +cronJob: + # Set to false to disable the CronJob (for debugging) + enabled: true + + # Schedule for certificate extraction (cron format) + # Default: Every day at 2 AM + # Examples: + # "0 2 * * *" - Daily at 2 AM + # "0 */6 * * *" - Every 6 hours + # "0 0 * * 0" - Weekly on Sunday at midnight + # "0 2 1 * *" - Monthly on 1st at 2 AM + schedule: "0 2 * * *" + + # CronJob specific settings + successfulJobsHistoryLimit: 3 # Keep last 3 successful jobs + failedJobsHistoryLimit: 1 # Keep last 1 failed job + concurrencyPolicy: Forbid # Don't allow concurrent runs + startingDeadlineSeconds: 300 # Start within 5 minutes of scheduled time + suspend: false # Set to true to temporarily disable + +# Job configuration (used by both Job and CronJob) +job: + # Container image for CA extraction + image: + registry: registry.redhat.io + repository: openshift4/ose-cli + tag: latest + pullPolicy: IfNotPresent + + # Resource limits + # Note: Processing 150+ certificates requires sufficient memory + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + + # Job retry policy + backoffLimit: 3 + + # TTL for completed jobs (cleanup) + ttlSecondsAfterFinished: 300 # 5 minutes + +# ConfigMap name that will be created in global.namespace +configMapName: ztvp-trusted-ca + +# Automatic rollout configuration +# After certificate extraction, automatically restart consuming applications +rollout: + enabled: true + + # Strategy: how to identify resources to restart + # - "all": Restart all Deployments/StatefulSets in target namespaces + # - "labeled": Restart only resources with specific labels + # - "specific": Restart specific named resources (see targets below) + strategy: labeled + + # Label selector for "labeled" strategy + # Only restart resources with these labels + labelSelector: + ztvp.io/uses-certificates: "true" + + # Specific targets for "specific" strategy + # List of deployments/statefulsets to restart after certificate update + targets: + - namespace: qtodo + kind: Deployment # Deployment, StatefulSet, DaemonSet + name: qtodo + + # Resource kinds to restart (applies to "all" and "labeled" strategies) + resourceKinds: + - Deployment + - StatefulSet + +# Annotations to add to the ConfigMap +configMapAnnotations: + argocd.argoproj.io/sync-options: Prune=false + +# Labels to add to the ConfigMap +configMapLabels: + app.kubernetes.io/name: ztvp-certificates + app.kubernetes.io/component: tls-certificates + app.kubernetes.io/managed-by: ztvp-certificate-manager + +# Enable validation of extracted certificates +validation: + enabled: true + # Minimum certificate size (bytes) to consider valid + minSize: 100 + # Verify certificate can be parsed by openssl + parseCheck: true + +# Distribution configuration +# Automatically distribute certificates to multiple namespaces +distribution: + # Enable distribution to target namespaces + enabled: true + + # Target namespaces where certificates should be distributed + # Each namespace will get a ConfigMap named: ztvp-trusted-ca- + targetNamespaces: + - qtodo + # - rhtpa # Keep RHTPA as-is for now; add when ready + # Add more namespaces as needed + + # Distribution method: ACM Policy distributes ConfigMaps to target namespaces + # Requires: ManagedClusterSetBinding in the namespace + method: "acm-policy" + +# Debugging options +debug: + # Enable verbose logging in extraction job + verbose: false + # Keep failed jobs for debugging (don't auto-cleanup) + keepFailedJobs: false + diff --git a/overrides/values-ztvp-certificates.yaml b/overrides/values-ztvp-certificates.yaml new file mode 100644 index 00000000..9ff55b00 --- /dev/null +++ b/overrides/values-ztvp-certificates.yaml @@ -0,0 +1,34 @@ +# Additional CA certificates for ztvp-certificates chart +# This file is referenced via extraValueFiles in values-hub.yaml +# +# To add additional certificates: +# 1. Create the secret: oc create secret generic --from-file=ca.crt=/path/to/cert.crt -n openshift-config +# 2. Uncomment and update additionalCertificates below +# 3. Trigger ArgoCD sync + +# Additional CA certificates (uncomment and configure as needed) +# customCA: +# additionalCertificates: +# - name: corporate-root-ca +# secretRef: +# name: corporate-root-ca +# namespace: openshift-config +# key: ca.crt +# - name: partner-ca +# secretRef: +# name: partner-ca +# namespace: openshift-config +# key: ca.crt + +# Automatic rollout configuration for applications consuming certificates +# When enabled, deployments with the label 'ztvp.io/uses-certificates: true' +# will be automatically restarted when certificates are updated +rollout: + enabled: true + strategy: labeled + labelSelector: + ztvp.io/uses-certificates: "true" + resourceKinds: + - Deployment + - StatefulSet + diff --git a/values-hub.yaml b/values-hub.yaml index 8e514075..3a86649e 100644 --- a/values-hub.yaml +++ b/values-hub.yaml @@ -156,6 +156,46 @@ clusterGroup: # - '/overrides/values-{{ $.Values.global.hubClusterDomain }}.yaml' # - '/overrides/values-{{ $.Values.global.localClusterDomain }}.yaml' applications: + ztvp-certificates: + name: ztvp-certificates + namespace: openshift-config + project: hub + path: charts/ztvp-certificates + annotations: + argocd.argoproj.io/sync-wave: "-10" + # Use extraValueFiles for complex nested structures like additionalCertificates and rollout + # The validated patterns framework only processes 'overrides' as --set parameters + # Edit /overrides/values-ztvp-certificates.yaml to configure: + # - Additional CA certificates (additionalCertificates array) + # - Automatic rollout restart for consuming applications + extraValueFiles: + - /overrides/values-ztvp-certificates.yaml + overrides: + # Job TTL: Retention time for completed CA extraction jobs + # Default: 300s (5 min). Extended for debugging/verification. + - name: job.ttlSecondsAfterFinished + value: "900" # 15 minutes + + # Debug settings: Enable for troubleshooting certificate extraction issues + # verbose: Enables bash 'set -x' for detailed script execution logging + # keepFailedJobs: Prevents failed jobs from auto-cleanup (useful for debugging) + # - name: debug.verbose + # value: "true" + # - name: debug.keepFailedJobs + # value: "true" + + # Primary custom CA: Use secretRef to reference an existing Kubernetes secret + # containing CA certificates. Uncomment to enable: + # Create secret: oc create secret generic custom-ca-bundle \ + # --from-file=ca.crt=/path/to/ca.crt -n openshift-config + # - name: customCA.secretRef.enabled + # value: "true" + # - name: customCA.secretRef.name + # value: custom-ca-bundle + # - name: customCA.secretRef.namespace + # value: openshift-config + # - name: customCA.secretRef.key + # value: ca.crt acm: name: acm namespace: open-cluster-management