diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ce740cc..c8ebeb2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,9 @@ updates: directory: "/" schedule: interval: "weekly" + groups: + github-actions: + patterns: + - "*" + cooldown: + default-days: 7 diff --git a/.github/workflows/helm-lint.yml b/.github/workflows/helm-lint.yml index af63f86..308baee 100644 --- a/.github/workflows/helm-lint.yml +++ b/.github/workflows/helm-lint.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false diff --git a/.github/workflows/helm-unittest.yml b/.github/workflows/helm-unittest.yml index 69d1599..92fb892 100644 --- a/.github/workflows/helm-unittest.yml +++ b/.github/workflows/helm-unittest.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml index bb67637..9b4fea1 100644 --- a/.github/workflows/superlinter.yml +++ b/.github/workflows/superlinter.yml @@ -10,7 +10,23 @@ permissions: jobs: lint: - uses: validatedpatterns/github-actions-library/.github/workflows/superlinter.yml@v1 + uses: validatedpatterns/github-actions-library/.github/workflows/superlinter.yml@v1 # zizmor: ignore[unpinned-uses] with: sl_env: | VALIDATE_BIOME_FORMAT=false + VALIDATE_BIOME_LINT=false + VALIDATE_CHECKOV=false + VALIDATE_JSON_PRETTIER=false + VALIDATE_KUBERNETES_KUBECONFORM=false + VALIDATE_MARKDOWN=false + VALIDATE_MARKDOWN_PRETTIER=false + VALIDATE_NATURAL_LANGUAGE=false + VALIDATE_PYTHON_BLACK=false + VALIDATE_PYTHON_PYINK=false + VALIDATE_PYTHON_PYLINT=false + VALIDATE_PYTHON_RUFF_FORMAT=false + VALIDATE_SHELL_SHFMT=false + VALIDATE_SPELL_CODESPELL=false + VALIDATE_TRIVY=false + VALIDATE_YAML=false + VALIDATE_YAML_PRETTIER=false diff --git a/Makefile b/Makefile index 319317a..ce5e6b9 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,24 @@ test: helm-lint helm-unittest ## Runs helm lint and unit tests .PHONY: super-linter super-linter: ## Runs super linter locally rm -rf .mypy_cache - podman run -e RUN_LOCAL=true -e USE_FIND_ALGORITHM=true \ + podman run -e RUN_LOCAL=true -e USE_FIND_ALGORITHM=true \ -e VALIDATE_BIOME_FORMAT=false \ + -e VALIDATE_BIOME_LINT=false \ + -e VALIDATE_CHECKOV=false \ + -e VALIDATE_JSON_PRETTIER=false \ + -e VALIDATE_KUBERNETES_KUBECONFORM=false \ + -e VALIDATE_MARKDOWN=false \ + -e VALIDATE_MARKDOWN_PRETTIER=false \ + -e VALIDATE_NATURAL_LANGUAGE=false \ + -e VALIDATE_PYTHON_BLACK=false \ + -e VALIDATE_PYTHON_PYINK=false \ + -e VALIDATE_PYTHON_PYLINT=false \ + -e VALIDATE_PYTHON_RUFF_FORMAT=false \ + -e VALIDATE_SHELL_SHFMT=false \ + -e VALIDATE_SPELL_CODESPELL=false \ + -e VALIDATE_TRIVY=false \ + -e VALIDATE_YAML=false \ + -e VALIDATE_YAML_PRETTIER=false \ -v $(PWD):/tmp/lint:rw,z \ -w /tmp/lint \ ghcr.io/super-linter/super-linter:slim-v8 diff --git a/README.md b/README.md index 188d092..6249a93 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ v0.1.0 - Initial release | Key | Type | Default | Description | |-----|------|---------|-------------| +| clusterCaMgt.createNamespace | bool | `false` | Create clusterCaMgt.namespace when installing the chart. | +| clusterCaMgt.namespace | string | `"cluster-ca-mgt"` | Namespace for ODF SSL certificate extraction and precheck workloads. | | odfSslCertificateExtractor.clusterReadinessMaxAttempts | int | `150` | | | odfSslCertificateExtractor.clusterReadinessSleepSeconds | int | `30` | | | regionalDR[0].clusters.primary.name | string | `"ocp-primary"` | | diff --git a/scripts/ansible/odf-ssl-precheck.yml b/scripts/ansible/odf-ssl-precheck.yml index c0540e3..77bad25 100644 --- a/scripts/ansible/odf-ssl-precheck.yml +++ b/scripts/ansible/odf-ssl-precheck.yml @@ -18,6 +18,7 @@ # Precheck is Argo sync wave 2; odf-ssl-certificate-extractor is wave 1 and may still be running. bundle_wait_retries: "{{ lookup('env', 'BUNDLE_WAIT_RETRIES') | default(60, true) | int }}" bundle_wait_delay: "{{ lookup('env', 'BUNDLE_WAIT_DELAY_SECONDS') | default(30, true) | int }}" + cluster_ca_mgt_namespace: "{{ lookup('env', 'CLUSTER_CA_MGT_NAMESPACE') | default('cluster-ca-mgt', true) }}" kubeconfig: "{{ lookup('env', 'KUBECONFIG') | default('', true) | trim }}" tasks: # Waits cluster_readiness_sleep seconds BETWEEN k8s_info attempts, same Pod (Ansible until/retries/delay). @@ -170,11 +171,11 @@ or "This will be populated" in (item.resources[0].data.get('ca-bundle.crt', '') or '') ) failed_when: false - - name: Trigger certificate extraction (create Job in openshift-config - handled by ArgoCD/Helm) + - name: Trigger certificate extraction (create Job in workload namespace - handled by ArgoCD/Helm) ansible.builtin.debug: msg: >- Certificate distribution incomplete. Trigger or re-sync the - odf-ssl-certificate-extractor Job in openshift-config namespace, + odf-ssl-certificate-extractor Job in {{ cluster_ca_mgt_namespace }} namespace, then re-run this precheck. when: not (certificate_distribution_ok | default(false) | bool) diff --git a/scripts/odf-ssl-certificate-extraction.sh b/scripts/odf-ssl-certificate-extraction.sh index d5b6aa8..bef5262 100755 --- a/scripts/odf-ssl-certificate-extraction.sh +++ b/scripts/odf-ssl-certificate-extraction.sh @@ -10,6 +10,16 @@ BASE_DELAY=30 MAX_DELAY=300 RETRY_COUNT=0 +array_contains() { + local needle="$1" + shift + local item + for item in "$@"; do + [[ "$item" == "$needle" ]] && return 0 + done + return 1 +} + # Function to implement exponential backoff exponential_backoff() { local delay=$((BASE_DELAY * (2 ** RETRY_COUNT))) @@ -140,7 +150,7 @@ create_combined_ca_bundle() { ca_files=("$@") echo "Creating combined CA bundle..." - > "$output_file" + : > "$output_file" file_count=0 for ca_file in "${ca_files[@]}"; do @@ -252,16 +262,14 @@ for cluster in $MANAGED_CLUSTERS; do KUBECONFIG_FILE="$WORK_DIR/${cluster}-kubeconfig.yaml" fi - cluster_ca_extracted=false if extract_cluster_ca "$cluster" "$WORK_DIR/${cluster}-ca.crt" "$KUBECONFIG_FILE"; then CA_FILES+=("$WORK_DIR/${cluster}-ca.crt") EXTRACTED_CLUSTERS+=("$cluster") - cluster_ca_extracted=true echo " Certificate size: $(wc -c < "$WORK_DIR/${cluster}-ca.crt") bytes" else echo " ❌ Could not extract CA from $cluster - REQUIRED for DR setup" fi - + # Extract ingress CA from managed cluster echo "3b.$index Extracting ingress CA from $cluster..." if extract_ingress_ca "$cluster" "$WORK_DIR/${cluster}-ingress-ca.crt" "$KUBECONFIG_FILE"; then @@ -278,7 +286,7 @@ done echo "4. Validating CA extraction from required clusters..." MISSING_CLUSTERS=() for required_cluster in "${REQUIRED_CLUSTERS[@]}"; do - if [[ " ${EXTRACTED_CLUSTERS[@]} " =~ " ${required_cluster} " ]]; then + if array_contains "$required_cluster" "${EXTRACTED_CLUSTERS[@]}"; then echo " ✅ CA extracted from $required_cluster" else echo " ❌ CA NOT extracted from $required_cluster" @@ -630,7 +638,7 @@ done echo "10. Validating verification results..." MISSING_VERIFICATION_CLUSTERS=() for required_cluster in "${REQUIRED_VERIFICATION_CLUSTERS[@]}"; do - if [[ " ${VERIFIED_CLUSTERS[@]} " =~ " ${required_cluster} " ]]; then + if array_contains "$required_cluster" "${VERIFIED_CLUSTERS[@]}"; then echo " ✅ $required_cluster: Certificate distribution verified" else echo " ❌ $required_cluster: Certificate distribution NOT verified" diff --git a/scripts/odf-ssl-precheck.sh b/scripts/odf-ssl-precheck.sh index f653cd9..473309c 100755 --- a/scripts/odf-ssl-precheck.sh +++ b/scripts/odf-ssl-precheck.sh @@ -5,6 +5,7 @@ echo "Starting ODF SSL certificate precheck and distribution..." echo "This job ensures certificates are properly distributed before DR policies are applied" # Configuration +CLUSTER_CA_MGT_NAMESPACE="${CLUSTER_CA_MGT_NAMESPACE:-cluster-ca-mgt}" MIN_CERTIFICATES=15 MIN_BUNDLE_SIZE=20000 MAX_ATTEMPTS=120 @@ -141,7 +142,7 @@ check_certificate_distribution() { return 1 fi - bundle_size=$(echo "$bundle_content" | wc -c) + bundle_size=${#bundle_content} echo " Bundle size: $bundle_size bytes" if [[ $bundle_size -lt $MIN_BUNDLE_SIZE ]]; then @@ -175,19 +176,21 @@ check_certificate_distribution() { } # Function to trigger certificate extraction +# shellcheck disable=SC2120 trigger_certificate_extraction() { echo "Triggering certificate extraction..." - oc delete job odf-ssl-certificate-extractor -n openshift-config --ignore-not-found=true + oc delete job odf-ssl-certificate-extractor -n "$CLUSTER_CA_MGT_NAMESPACE" --ignore-not-found=true sleep 5 echo "Creating certificate extraction job..." + # shellcheck disable=SC2154,SC2076,SC2000,SC2012,SC2035,SC2086 oc apply -f - </dev/null; then + if oc wait --for=condition=complete job/odf-ssl-certificate-extractor -n "$CLUSTER_CA_MGT_NAMESPACE" --timeout=60s 2>/dev/null; then echo " ✅ Certificate extraction completed successfully" return 0 else @@ -865,6 +868,7 @@ main_execution() { echo " Triggering certificate extraction (attempt $attempt/$MAX_ATTEMPTS)..." + # shellcheck disable=SC2119 if trigger_certificate_extraction; then echo "✅ Certificate extraction completed successfully" echo " Re-verifying distribution..." diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 6e11da2..dd3aff6 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -11,3 +11,8 @@ {{- $fromOver := index $over "name" -}} {{- if $fromOver }}{{ $fromOver }}{{- else if and .Values.regionalDR (index .Values.regionalDR 0) }}{{ (index .Values.regionalDR 0).clusters.secondary.name | default "ocp-secondary" }}{{- else }}ocp-secondary{{ end -}} {{- end -}} + +{{/* Namespace for ODF SSL certificate extraction/precheck workloads */}} +{{- define "opp.clusterCaMgtNamespace" -}} +{{- .Values.clusterCaMgt.namespace | default "cluster-ca-mgt" -}} +{{- end -}} diff --git a/templates/configmap-odf-ssl-playbooks.yaml b/templates/configmap-odf-ssl-playbooks.yaml index dd48c35..b2f3b1e 100644 --- a/templates/configmap-odf-ssl-playbooks.yaml +++ b/templates/configmap-odf-ssl-playbooks.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: odf-ssl-extractor-playbooks - namespace: openshift-config + namespace: {{ include "opp.clusterCaMgtNamespace" . }} labels: app.kubernetes.io/name: odf-ssl-certificate-management data: @@ -23,7 +23,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: odf-ssl-precheck-playbooks - namespace: open-cluster-management + namespace: {{ include "opp.clusterCaMgtNamespace" . }} labels: app.kubernetes.io/name: odf-ssl-certificate-management data: diff --git a/templates/job-odf-ssl-certificate-extraction.yaml b/templates/job-odf-ssl-certificate-extraction.yaml index 9b0fb4a..a1abd15 100644 --- a/templates/job-odf-ssl-certificate-extraction.yaml +++ b/templates/job-odf-ssl-certificate-extraction.yaml @@ -2,7 +2,7 @@ apiVersion: batch/v1 kind: Job metadata: name: odf-ssl-certificate-extractor - namespace: openshift-config + namespace: {{ include "opp.clusterCaMgtNamespace" . }} labels: app.kubernetes.io/name: odf-ssl-certificate-management app.kubernetes.io/component: certificate-extraction @@ -35,6 +35,8 @@ spec: value: {{ .Values.odfSslCertificateExtractor.clusterReadinessMaxAttempts | default 150 | quote }} - name: CLUSTER_READINESS_SLEEP_SECONDS value: {{ .Values.odfSslCertificateExtractor.clusterReadinessSleepSeconds | default 30 | quote }} + - name: CLUSTER_CA_MGT_NAMESPACE + value: {{ include "opp.clusterCaMgtNamespace" . | quote }} command: - ansible-playbook args: diff --git a/templates/job-odf-ssl-certificate-precheck.yaml b/templates/job-odf-ssl-certificate-precheck.yaml index 0b9729e..444cb52 100644 --- a/templates/job-odf-ssl-certificate-precheck.yaml +++ b/templates/job-odf-ssl-certificate-precheck.yaml @@ -2,7 +2,7 @@ apiVersion: batch/v1 kind: Job metadata: name: odf-ssl-certificate-precheck - namespace: open-cluster-management + namespace: {{ include "opp.clusterCaMgtNamespace" . }} labels: app.kubernetes.io/name: odf-ssl-certificate-management app.kubernetes.io/component: certificate-precheck @@ -37,6 +37,8 @@ spec: value: {{ include "opp.primaryClusterName" . | quote }} - name: SECONDARY_CLUSTER value: {{ include "opp.secondaryClusterName" . | quote }} + - name: CLUSTER_CA_MGT_NAMESPACE + value: {{ include "opp.clusterCaMgtNamespace" . | quote }} command: - ansible-playbook args: diff --git a/templates/namespace-cluster-ca-mgt.yaml b/templates/namespace-cluster-ca-mgt.yaml new file mode 100644 index 0000000..0d9abac --- /dev/null +++ b/templates/namespace-cluster-ca-mgt.yaml @@ -0,0 +1,11 @@ +{{- if .Values.clusterCaMgt.createNamespace }} +apiVersion: v1 +kind: Namespace +metadata: + name: {{ include "opp.clusterCaMgtNamespace" . }} + labels: + app.kubernetes.io/name: odf-ssl-certificate-management + app.kubernetes.io/component: ca-workloads + annotations: + argocd.argoproj.io/sync-wave: "0" +{{- end }} diff --git a/templates/rbac-odf-ssl-certificate-precheck.yaml b/templates/rbac-odf-ssl-certificate-precheck.yaml index f5ee905..50617cf 100644 --- a/templates/rbac-odf-ssl-certificate-precheck.yaml +++ b/templates/rbac-odf-ssl-certificate-precheck.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: odf-ssl-certificate-precheck - namespace: open-cluster-management + namespace: {{ include "opp.clusterCaMgtNamespace" . }} labels: app.kubernetes.io/name: odf-ssl-certificate-management app.kubernetes.io/component: certificate-precheck @@ -58,4 +58,4 @@ roleRef: subjects: - kind: ServiceAccount name: odf-ssl-certificate-precheck - namespace: open-cluster-management \ No newline at end of file + namespace: {{ include "opp.clusterCaMgtNamespace" . }} \ No newline at end of file diff --git a/templates/rbac-odf-ssl-extractor.yaml b/templates/rbac-odf-ssl-extractor.yaml index 52d5b6f..e6df99b 100644 --- a/templates/rbac-odf-ssl-extractor.yaml +++ b/templates/rbac-odf-ssl-extractor.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: odf-ssl-extractor-sa - namespace: openshift-config + namespace: {{ include "opp.clusterCaMgtNamespace" . }} labels: app.kubernetes.io/name: odf-ssl-certificate-management app.kubernetes.io/component: certificate-extraction @@ -11,6 +11,7 @@ metadata: --- # Hub-only permissions for the Job SA. Pushes to managed clusters use each cluster's imported kubeconfig # (ACM admin-kubeconfig); those calls are authorized on the spoke, not by this ClusterRole. +# Workloads run in clusterCaMgt.namespace; cluster-proxy-ca-bundle remains in openshift-config. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -53,7 +54,7 @@ metadata: subjects: - kind: ServiceAccount name: odf-ssl-extractor-sa - namespace: openshift-config + namespace: {{ include "opp.clusterCaMgtNamespace" . }} roleRef: kind: ClusterRole name: odf-ssl-extractor-role diff --git a/values.yaml b/values.yaml index 0cd722b..683024a 100644 --- a/values.yaml +++ b/values.yaml @@ -1,4 +1,12 @@ --- +# Namespace for ODF SSL extraction/precheck Jobs, playbooks, and RBAC. +# cluster-proxy-ca-bundle remains in openshift-config (Proxy.spec.trustedCA requirement). +clusterCaMgt: + # -- Namespace for ODF SSL certificate extraction and precheck workloads. + namespace: cluster-ca-mgt + # -- Create clusterCaMgt.namespace when installing the chart. + createNamespace: false + # DR pair cluster names - same structure as rdr chart (regionalDR). # Override via values-hub or overrides so opp-policy and rdr use the same names. # These MUST match ACM ManagedCluster metadata.name (e.g. mjrtd420-1); otherwise kubeconfig/CA paths and