diff --git a/ci-operator/config/openshift/tls-scanner/openshift-tls-scanner-main.yaml b/ci-operator/config/openshift/tls-scanner/openshift-tls-scanner-main.yaml index 19787405d8bf7..a8631e2b4686f 100644 --- a/ci-operator/config/openshift/tls-scanner/openshift-tls-scanner-main.yaml +++ b/ci-operator/config/openshift/tls-scanner/openshift-tls-scanner-main.yaml @@ -3,6 +3,10 @@ base_images: name: "5.0" namespace: ocp tag: base-rhel9 + hypershift-operator: + name: hypershift-operator + namespace: hypershift + tag: latest ocp_builder_rhel-9-golang-1.25-openshift-4.22: name: builder namespace: ocp @@ -129,6 +133,21 @@ tests: test: - ref: tls-scanner-run workflow: openshift-e2e-aws-ovn-tls-13 +- as: periodic-hypershift-tls + interval: 72h + reporter_config: + channel: '#forum-case' + job_states_to_report: + - success + - failure + - error + report_template: '{{if eq .Status.State "success"}} :white_check_mark: Job *{{.Spec.Job}}* + ended with *{{.Status.State}}*. <{{.Status.URL}}|View logs> {{else}} :warning: + Job *{{.Spec.Job}}* ended with *{{.Status.State}}*. <{{.Status.URL}}|View logs> + {{end}}' + steps: + cluster_profile: hypershift-aws + workflow: tls-scanner-hypershift-aws zz_generated_metadata: branch: main org: openshift diff --git a/ci-operator/jobs/openshift/tls-scanner/openshift-tls-scanner-main-periodics.yaml b/ci-operator/jobs/openshift/tls-scanner/openshift-tls-scanner-main-periodics.yaml index 1280d69642045..1011db130462a 100644 --- a/ci-operator/jobs/openshift/tls-scanner/openshift-tls-scanner-main-periodics.yaml +++ b/ci-operator/jobs/openshift/tls-scanner/openshift-tls-scanner-main-periodics.yaml @@ -93,6 +93,100 @@ periodics: - name: result-aggregator secret: secretName: result-aggregator +- agent: kubernetes + cluster: build03 + decorate: true + decoration_config: + sparse_checkout_files: + - Dockerfile + extra_refs: + - base_ref: main + org: openshift + repo: tls-scanner + sparse_checkout_files: + - Dockerfile + interval: 72h + labels: + ci-operator.openshift.io/cloud: hypershift-aws + ci-operator.openshift.io/cloud-cluster-profile: hypershift-aws + ci.openshift.io/generator: prowgen + pj-rehearse.openshift.io/can-be-rehearsed: "true" + name: periodic-ci-openshift-tls-scanner-main-periodic-hypershift-tls + reporter_config: + slack: + channel: '#forum-case' + job_states_to_report: + - success + - failure + - error + report_template: '{{if eq .Status.State "success"}} :white_check_mark: Job *{{.Spec.Job}}* + ended with *{{.Status.State}}*. <{{.Status.URL}}|View logs> {{else}} :warning: + Job *{{.Spec.Job}}* ended with *{{.Status.State}}*. <{{.Status.URL}}|View + logs> {{end}}' + spec: + containers: + - args: + - --gcs-upload-secret=/secrets/gcs/service-account.json + - --image-import-pull-secret=/etc/pull-secret/.dockerconfigjson + - --lease-server-credentials-file=/etc/boskos/credentials + - --report-credentials-file=/etc/report/credentials + - --secret-dir=/secrets/ci-pull-credentials + - --target=periodic-hypershift-tls + command: + - ci-operator + env: + - name: HTTP_SERVER_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + image: quay-proxy.ci.openshift.org/openshift/ci:ci_ci-operator_latest + imagePullPolicy: Always + name: "" + ports: + - containerPort: 8080 + name: http + resources: + requests: + cpu: 10m + volumeMounts: + - mountPath: /etc/boskos + name: boskos + readOnly: true + - mountPath: /secrets/ci-pull-credentials + name: ci-pull-credentials + readOnly: true + - mountPath: /secrets/gcs + name: gcs-credentials + readOnly: true + - mountPath: /secrets/manifest-tool + name: manifest-tool-local-pusher + readOnly: true + - mountPath: /etc/pull-secret + name: pull-secret + readOnly: true + - mountPath: /etc/report + name: result-aggregator + readOnly: true + serviceAccountName: ci-operator + volumes: + - name: boskos + secret: + items: + - key: credentials + path: credentials + secretName: boskos-credentials + - name: ci-pull-credentials + secret: + secretName: ci-pull-credentials + - name: manifest-tool-local-pusher + secret: + secretName: manifest-tool-local-pusher + - name: pull-secret + secret: + secretName: registry-pull-credentials + - name: result-aggregator + secret: + secretName: result-aggregator - agent: kubernetes cluster: build03 decorate: true diff --git a/ci-operator/step-registry/hypershift/modern-tls/OWNERS b/ci-operator/step-registry/hypershift/modern-tls/OWNERS new file mode 100644 index 0000000000000..6364a26c9f53a --- /dev/null +++ b/ci-operator/step-registry/hypershift/modern-tls/OWNERS @@ -0,0 +1,4 @@ +approvers: +- gangwgr +reviewers: +- gangwgr diff --git a/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-commands.sh b/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-commands.sh new file mode 100755 index 0000000000000..af80d03dd983d --- /dev/null +++ b/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-commands.sh @@ -0,0 +1,114 @@ +#!/bin/bash +set -euo pipefail + +echo "Configuring Modern TLS Security Profile for HyperShift cluster..." + +export KUBECONFIG=${SHARED_DIR}/kubeconfig + +if [[ -f "${SHARED_DIR}/cluster-name" ]]; then + HOSTED_CLUSTER_NAME="$(<"${SHARED_DIR}/cluster-name")" + HOSTED_CLUSTER_NAMESPACE="clusters" +else + HOSTED_CLUSTER_NAME=$(oc get hostedcluster -A -o jsonpath='{.items[0].metadata.name}') + HOSTED_CLUSTER_NAMESPACE=$(oc get hostedcluster -A -o jsonpath='{.items[0].metadata.namespace}') +fi + +if [[ -z "${HOSTED_CLUSTER_NAME}" ]]; then + echo "Error: Could not find HostedCluster" + exit 1 +fi + +HCP_NAMESPACE=$(oc get hostedcontrolplane -A -o jsonpath="{.items[?(@.metadata.name==\"${HOSTED_CLUSTER_NAME}\")].metadata.namespace}" 2>/dev/null || true) +if [[ -z "${HCP_NAMESPACE}" ]]; then + HCP_NAMESPACE="clusters-${HOSTED_CLUSTER_NAME}" +fi + +echo "Found HostedCluster: ${HOSTED_CLUSTER_NAME} in namespace ${HOSTED_CLUSTER_NAMESPACE}" +echo "Hosted control plane namespace: ${HCP_NAMESPACE}" + +kas_generation="$(oc get deployment -n "${HCP_NAMESPACE}" kube-apiserver -o jsonpath='{.metadata.generation}')" + +echo "Applying Modern TLS Security Profile to HostedCluster..." +oc patch hostedcluster -n "${HOSTED_CLUSTER_NAMESPACE}" "${HOSTED_CLUSTER_NAME}" --type=merge -p '{ + "spec": { + "configuration": { + "apiServer": { + "tlsSecurityProfile": { + "type": "Modern", + "modern": {} + } + } + } + } +}' + +hc_tls_profile=$(oc get hostedcluster -n "${HOSTED_CLUSTER_NAMESPACE}" "${HOSTED_CLUSTER_NAME}" -o jsonpath='{.spec.configuration.apiServer.tlsSecurityProfile.type}') +if [[ "${hc_tls_profile}" != "Modern" ]]; then + echo "Error: HostedCluster TLS Security Profile is '${hc_tls_profile}', expected 'Modern'" + exit 1 +fi +echo "✓ HostedCluster spec.configuration.apiServer.tlsSecurityProfile.type is Modern" + +echo "Waiting for kube-apiserver to reconcile the TLS profile..." +rollout_deadline=$((SECONDS + 300)) +while (( SECONDS < rollout_deadline )); do + current_generation="$(oc get deployment -n "${HCP_NAMESPACE}" kube-apiserver -o jsonpath='{.metadata.generation}')" + if (( current_generation > kas_generation )); then + echo "kube-apiserver generation changed (${kas_generation} -> ${current_generation}), waiting for rollout..." + oc rollout status deployment -n "${HCP_NAMESPACE}" kube-apiserver --timeout=15m + break + fi + sleep 15 +done +if (( SECONDS >= rollout_deadline )); then + echo "kube-apiserver generation unchanged after 5m; continuing with TLS verification" +fi + +verify_modern_tls_endpoint() { + local api_server api_host api_port + + api_server="$(oc whoami --show-server)" + api_host="${api_server#https://}" + api_host="${api_host%:*}" + api_port="${api_server##*:}" + + echo "Verifying Modern TLS profile on API server endpoint ${api_host}:${api_port}..." + if ! echo | openssl s_client -connect "${api_host}:${api_port}" -tls1_3 -servername "${api_host}" 2>/dev/null | grep -qE 'Protocol.*TLSv1\.3'; then + echo "Error: API server does not negotiate TLS 1.3" + return 1 + fi + if echo | openssl s_client -connect "${api_host}:${api_port}" -tls1_2 -servername "${api_host}" 2>/dev/null | grep -qE 'Protocol.*TLSv1\.2'; then + echo "Error: API server still negotiates TLS 1.2 (expected Modern profile)" + return 1 + fi + echo "✓ API server endpoint enforces Modern TLS (TLS 1.3 only)" +} + +export KUBECONFIG=${SHARED_DIR}/nested_kubeconfig + +echo "Waiting for guest cluster APIServer to reflect Modern TLS profile..." +for i in {1..40}; do + tls_profile=$(oc get apiserver/cluster -o jsonpath='{.spec.tlsSecurityProfile.type}' 2>/dev/null || echo "") + if [[ "$tls_profile" == "Modern" ]]; then + echo "✓ Guest cluster APIServer tlsSecurityProfile.type is Modern" + echo "✓ Modern TLS Security Profile successfully applied" + exit 0 + fi + if verify_modern_tls_endpoint; then + echo "Guest cluster APIServer tlsSecurityProfile.type is '${tls_profile}' (HyperShift may not mirror this field)" + echo "✓ Modern TLS Security Profile successfully applied" + exit 0 + fi + echo "Waiting for Modern TLS profile to propagate (attempt $i/40)..." + sleep 15 +done + +tls_profile=$(oc get apiserver/cluster -o jsonpath='{.spec.tlsSecurityProfile.type}' 2>/dev/null || echo "NotFound") +if [[ "$tls_profile" == "Modern" ]]; then + echo "✓ Modern TLS Security Profile successfully applied" + exit 0 +fi + +echo "Guest cluster APIServer tlsSecurityProfile.type is '${tls_profile}'" +verify_modern_tls_endpoint +echo "✓ Modern TLS Security Profile successfully applied" diff --git a/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-ref.metadata.json b/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-ref.metadata.json new file mode 100644 index 0000000000000..9d45ebb49d983 --- /dev/null +++ b/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-ref.metadata.json @@ -0,0 +1,11 @@ +{ + "path": "hypershift/modern-tls/hypershift-modern-tls-ref.yaml", + "owners": { + "approvers": [ + "gangwgr" + ], + "reviewers": [ + "gangwgr" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-ref.yaml b/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-ref.yaml new file mode 100644 index 0000000000000..919a4eefcac4c --- /dev/null +++ b/ci-operator/step-registry/hypershift/modern-tls/hypershift-modern-tls-ref.yaml @@ -0,0 +1,23 @@ +ref: + as: hypershift-modern-tls + from_image: + namespace: ocp + name: "5.0" + tag: cli + commands: hypershift-modern-tls-commands.sh + resources: + requests: + cpu: 100m + memory: 200Mi + documentation: |- + Configures Modern TLS Security Profile on a HyperShift hosted cluster. + + This step patches the HostedCluster CR to enable Modern TLS profile, which + enforces TLS 1.3 for all API server connections. It waits for the hosted + control plane kube-apiserver to roll out on the management cluster, then + verifies the profile on the guest APIServer object or via a TLS 1.3 endpoint + probe when HyperShift does not mirror the field into the guest cluster. + + Note: Due to current HyperShift limitations, the Modern TLS profile may not + propagate to etcd and some other control plane components. This is a known + issue being tracked upstream. diff --git a/ci-operator/step-registry/tls/scanner/hypershift-aws/OWNERS b/ci-operator/step-registry/tls/scanner/hypershift-aws/OWNERS new file mode 100644 index 0000000000000..8d50643e36ca1 --- /dev/null +++ b/ci-operator/step-registry/tls/scanner/hypershift-aws/OWNERS @@ -0,0 +1,8 @@ +approvers: + - richardsonnick + - rhmdnd + - smith-xyz +reviewers: + - richardsonnick + - rhmdnd + - smith-xyz diff --git a/ci-operator/step-registry/tls/scanner/hypershift-aws/tls-scanner-hypershift-aws-workflow.metadata.json b/ci-operator/step-registry/tls/scanner/hypershift-aws/tls-scanner-hypershift-aws-workflow.metadata.json new file mode 100644 index 0000000000000..2debde89f7586 --- /dev/null +++ b/ci-operator/step-registry/tls/scanner/hypershift-aws/tls-scanner-hypershift-aws-workflow.metadata.json @@ -0,0 +1,15 @@ +{ + "path": "tls/scanner/hypershift-aws/tls-scanner-hypershift-aws-workflow.yaml", + "owners": { + "approvers": [ + "richardsonnick", + "rhmdnd", + "smith-xyz" + ], + "reviewers": [ + "richardsonnick", + "rhmdnd", + "smith-xyz" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/tls/scanner/hypershift-aws/tls-scanner-hypershift-aws-workflow.yaml b/ci-operator/step-registry/tls/scanner/hypershift-aws/tls-scanner-hypershift-aws-workflow.yaml new file mode 100644 index 0000000000000..555210716e1b0 --- /dev/null +++ b/ci-operator/step-registry/tls/scanner/hypershift-aws/tls-scanner-hypershift-aws-workflow.yaml @@ -0,0 +1,29 @@ +workflow: + as: tls-scanner-hypershift-aws + documentation: |- + The HyperShift AWS TLS scanner workflow provisions an ephemeral HyperShift + hosted cluster with Modern TLS Security Profile enabled and runs the TLS + scanner against both clusters involved in hosted control planes. + + HyperShift uses two clusters: + - Management cluster: hosts the hosted control plane pods (kube-apiserver, + etcd, oauth-openshift) in the clusters- namespace on a shared cluster + - Guest (hosted) cluster: the actual OpenShift cluster with worker nodes and + no in-cluster control plane pods + + The workflow configures Modern TLS (TLS 1.3) on the hosted cluster and scans + TLS endpoints in both the management control plane and the guest cluster. + + The management cluster is hosted in the `osd-hypershift` AWS account and + destroyed after the scan completes. + steps: + pre: + - ref: ipi-install-rbac + - chain: hypershift-setup-root-management-cluster + - chain: hypershift-aws-create + - ref: hypershift-modern-tls + test: + - ref: tls-scanner-hypershift-run + post: + - chain: hypershift-dump + - chain: hypershift-aws-destroy diff --git a/ci-operator/step-registry/tls/scanner/hypershift-run/OWNERS b/ci-operator/step-registry/tls/scanner/hypershift-run/OWNERS new file mode 100644 index 0000000000000..8d50643e36ca1 --- /dev/null +++ b/ci-operator/step-registry/tls/scanner/hypershift-run/OWNERS @@ -0,0 +1,8 @@ +approvers: + - richardsonnick + - rhmdnd + - smith-xyz +reviewers: + - richardsonnick + - rhmdnd + - smith-xyz diff --git a/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-commands.sh b/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-commands.sh new file mode 120000 index 0000000000000..13743a4edb908 --- /dev/null +++ b/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-commands.sh @@ -0,0 +1 @@ +../run/tls-scanner-run-commands.sh \ No newline at end of file diff --git a/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-ref.metadata.json b/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-ref.metadata.json new file mode 100644 index 0000000000000..c9c30a9a696df --- /dev/null +++ b/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-ref.metadata.json @@ -0,0 +1,15 @@ +{ + "path": "tls/scanner/hypershift-run/tls-scanner-hypershift-run-ref.yaml", + "owners": { + "approvers": [ + "richardsonnick", + "rhmdnd", + "smith-xyz" + ], + "reviewers": [ + "richardsonnick", + "rhmdnd", + "smith-xyz" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-ref.yaml b/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-ref.yaml new file mode 100644 index 0000000000000..962465de31798 --- /dev/null +++ b/ci-operator/step-registry/tls/scanner/hypershift-run/tls-scanner-hypershift-run-ref.yaml @@ -0,0 +1,70 @@ +ref: + as: tls-scanner-hypershift-run + from: cli + cli: latest + commands: tls-scanner-hypershift-run-commands.sh + env: + - name: TLS_SCANNER_RUN_HYPERSHIFT + default: "true" + documentation: |- + When true, run TLS scans against both HyperShift management and guest + clusters in a single step. + - name: SCAN_NAMESPACE + default: "" + documentation: "Comma-separated list of namespaces to scan. Empty means scan all namespaces." + - name: PQC_CHECK + default: "false" + documentation: "Set to 'true' to check post-quantum cryptography readiness (scanner will only check TLS 1.3 and mlkem/mlkem25519 support)." + - name: SCAN_LIMIT_IPS + default: "" + documentation: "Max IPs to scan (empty/0 = no limit). Bounds actual TLS scans after full endpoint discovery." + - name: SCANNER_CPU + default: "4" + documentation: "CPU request/limit for the scanner pod on the management cluster." + - name: SCANNER_MEMORY + default: "4Gi" + documentation: "Memory request/limit for the scanner pod on the management cluster." + - name: SCANNER_CPU_GUEST + default: "1" + documentation: |- + CPU request/limit for the scanner pod on the guest cluster. Defaults to 1 + because HyperShift guest workers (e.g. m5.xlarge) cannot schedule a 4 CPU + Guaranteed pod. + - name: SCANNER_MEMORY_GUEST + default: "2Gi" + documentation: "Memory request/limit for the scanner pod on the guest cluster." + - name: TLS_PROFILE_TYPE + default: "Modern" + documentation: |- + Expected TLS profile for compliance checks on HyperShift clusters. The + hosted cluster APIServer CR and shared management cluster APIServer CR do + not reflect the HostedCluster Modern TLS configuration, so the scanner + uses this value instead of querying the API. + dependencies: + - env: RELEASE_IMAGE_LATEST + name: release:latest + - env: OPENSHIFT_UPGRADE_RELEASE_IMAGE_OVERRIDE + name: release:latest + - env: OPENSHIFT_INSTALL_RELEASE_IMAGE_OVERRIDE + name: release:latest + - env: PULL_SPEC_TLS_SCANNER_TOOL + name: tls-scanner-tool + resources: + requests: + cpu: 100m + memory: 200Mi + timeout: 8h0m0s + grace_period: 5m0s + documentation: |- + Runs the TLS scanner against both HyperShift clusters. + + HyperShift jobs involve two distinct clusters: + - management: shared cluster hosting the hosted control plane pods + (kube-apiserver, etcd, oauth-openshift in clusters-) + - guest: the hosted cluster with worker nodes and no in-cluster control plane + + Each scan writes artifacts under tls-scanner/