-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add TLS scanner for External Secrets Operator #82876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,10 @@ base_images: | |
| name: "4.22" | ||
| namespace: ocp | ||
| tag: base-rhel9 | ||
| cli-operator-sdk: | ||
| name: cli-operator-sdk | ||
| namespace: ocp | ||
| tag: v1.39.2 | ||
| nested-podman: | ||
| name: nested-podman | ||
| namespace: ci | ||
|
|
@@ -11,6 +15,10 @@ base_images: | |
| name: "4.19" | ||
| namespace: origin | ||
| tag: operator-sdk | ||
| tls-scanner-tool: | ||
| name: tls-scanner | ||
| namespace: tls-scanner | ||
| tag: tls-scanner-tool | ||
| binary_build_commands: make build | ||
| build_root: | ||
| from_repository: true | ||
|
|
@@ -247,6 +255,199 @@ tests: | |
| requests: | ||
| cpu: 100m | ||
| workflow: generic-claim | ||
| - always_run: false | ||
| as: tls-scanner | ||
| optional: true | ||
| steps: | ||
| cluster_profile: openshift-org-aws | ||
| dependencies: | ||
| OO_BUNDLE: external-secrets-operator-bundle | ||
| env: | ||
| OO_INSTALL_MODE: AllNamespaces | ||
| OO_INSTALL_NAMESPACE: external-secrets-operator | ||
| OO_SECURITY_CONTEXT: restricted | ||
| SCAN_NAMESPACE: external-secrets-operator,external-secrets | ||
| SCANNER_CPU: "1" | ||
| SCANNER_MEMORY: 1Gi | ||
| TLS_13_ENABLE_TLS_ADHERENCE: "true" | ||
| TLS_13_TLS_ADHERENCE_POLICY: StrictAllComponents | ||
| test: | ||
| - ref: tls-13 | ||
| - as: deploy-operand | ||
| cli: latest | ||
| commands: |- | ||
| set -o errexit | ||
| set -o nounset | ||
| set -o pipefail | ||
|
|
||
| echo "Waiting for external-secrets-operator controller-manager..." | ||
| oc wait --for=condition=Available -n external-secrets-operator deployment/external-secrets-operator-controller-manager --timeout=5m | ||
|
|
||
| echo "Creating ExternalSecretsConfig CR to trigger operand deployment..." | ||
| oc apply -f - <<EOF | ||
| apiVersion: operator.openshift.io/v1alpha1 | ||
| kind: ExternalSecretsConfig | ||
| metadata: | ||
| name: cluster | ||
| spec: {} | ||
| EOF | ||
|
|
||
| echo "Waiting for external-secrets namespace to be created..." | ||
| for i in $(seq 1 60); do | ||
| if oc get namespace external-secrets 2>/dev/null; then | ||
| echo "Namespace external-secrets found" | ||
| break | ||
| fi | ||
| echo "Waiting for namespace (attempt ${i}/60)..." | ||
| sleep 5 | ||
| done | ||
|
|
||
| echo "Waiting for external-secrets operand deployments..." | ||
| oc wait --for=create -n external-secrets deployment/external-secrets --timeout=5m | ||
| oc wait --for=create -n external-secrets deployment/external-secrets-webhook --timeout=5m | ||
| oc wait --for=create -n external-secrets deployment/external-secrets-cert-controller --timeout=5m | ||
| oc wait --for=condition=Available -n external-secrets deployment/external-secrets --timeout=5m | ||
| oc wait --for=condition=Available -n external-secrets deployment/external-secrets-webhook --timeout=5m | ||
| oc wait --for=condition=Available -n external-secrets deployment/external-secrets-cert-controller --timeout=5m | ||
|
|
||
| echo "Verifying webhook TLS endpoint is live..." | ||
| WEBHOOK_SVC=$(oc get validatingwebhookconfigurations -l app=external-secrets -o jsonpath='{.items[0].webhooks[0].clientConfig.service.name}' 2>/dev/null || true) | ||
| WEBHOOK_NS=$(oc get validatingwebhookconfigurations -l app=external-secrets -o jsonpath='{.items[0].webhooks[0].clientConfig.service.namespace}' 2>/dev/null || true) | ||
| if [[ -n "${WEBHOOK_SVC}" && -n "${WEBHOOK_NS}" ]]; then | ||
| WEBHOOK_PORT=$(oc get validatingwebhookconfigurations -l app=external-secrets -o jsonpath='{.items[0].webhooks[0].clientConfig.service.port}' 2>/dev/null || echo "443") | ||
| echo "Found webhook service: ${WEBHOOK_NS}/${WEBHOOK_SVC}:${WEBHOOK_PORT}" | ||
| oc get svc -n "${WEBHOOK_NS}" "${WEBHOOK_SVC}" || true | ||
| oc get endpoints -n "${WEBHOOK_NS}" "${WEBHOOK_SVC}" || true | ||
| else | ||
| echo "Warning: could not discover ValidatingWebhookConfiguration with label app=external-secrets" | ||
| oc get validatingwebhookconfigurations -o name || true | ||
| fi | ||
|
Comment on lines
+313
to
+324
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Fail when the TLS paths are not exercised. The webhook checks ignore missing configuration, services, and endpoints. The reconciliation loop also exits successfully when The workflow can therefore run Proposed failure checks if [[ -n "${WEBHOOK_SVC}" && -n "${WEBHOOK_NS}" ]]; then
WEBHOOK_PORT=$(oc get validatingwebhookconfigurations -l app=external-secrets -o jsonpath='{.items[0].webhooks[0].clientConfig.service.port}' 2>/dev/null || echo "443")
echo "Found webhook service: ${WEBHOOK_NS}/${WEBHOOK_SVC}:${WEBHOOK_PORT}"
- oc get svc -n "${WEBHOOK_NS}" "${WEBHOOK_SVC}" || true
- oc get endpoints -n "${WEBHOOK_NS}" "${WEBHOOK_SVC}" || true
+ oc get svc -n "${WEBHOOK_NS}" "${WEBHOOK_SVC}"
+ ENDPOINT_IPS=$(oc get endpoints -n "${WEBHOOK_NS}" "${WEBHOOK_SVC}" -o jsonpath='{.subsets[*].addresses[*].ip}')
+ [[ -n "${ENDPOINT_IPS}" ]] || {
+ echo "Webhook service has no ready endpoints" >&2
+ exit 1
+ }
else
- echo "Warning: could not discover ValidatingWebhookConfiguration with label app=external-secrets"
+ echo "Could not discover ValidatingWebhookConfiguration with label app=external-secrets" >&2
oc get validatingwebhookconfigurations -o name || true
+ exit 1
fi
...
done
+ if [[ "${STATUS}" != "True" ]]; then
+ oc describe externalsecret tls-scan-dummy -n tls-scanner-exercise || true
+ exit 1
+ fiAlso applies to: 363-372 🤖 Prompt for AI Agents |
||
|
|
||
| echo "Creating a test namespace with a SecretStore to exercise the webhook admission path..." | ||
| oc create namespace tls-scanner-exercise || true | ||
| oc apply -f - <<EOF | ||
| apiVersion: external-secrets.io/v1 | ||
| kind: SecretStore | ||
| metadata: | ||
| name: tls-scan-dummy | ||
| namespace: tls-scanner-exercise | ||
| spec: | ||
| provider: | ||
| fake: | ||
| data: | ||
| - key: tls-scan-test | ||
| value: dummy | ||
| EOF | ||
|
|
||
| echo "Creating an ExternalSecret to trigger webhook validation and controller reconciliation..." | ||
| oc apply -f - <<EOF | ||
| apiVersion: external-secrets.io/v1 | ||
| kind: ExternalSecret | ||
| metadata: | ||
| name: tls-scan-dummy | ||
| namespace: tls-scanner-exercise | ||
| spec: | ||
| refreshInterval: 1h | ||
| secretStoreRef: | ||
| name: tls-scan-dummy | ||
| kind: SecretStore | ||
| target: | ||
| name: tls-scan-dummy-secret | ||
| data: | ||
| - secretKey: test | ||
| remoteRef: | ||
| key: tls-scan-test | ||
| EOF | ||
|
|
||
| echo "Waiting for ExternalSecret to be processed..." | ||
| for i in $(seq 1 30); do | ||
| STATUS=$(oc get externalsecret tls-scan-dummy -n tls-scanner-exercise -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "Unknown") | ||
| if [[ "${STATUS}" == "True" ]]; then | ||
| echo "ExternalSecret reconciled successfully — webhook + controller TLS paths exercised" | ||
| break | ||
| fi | ||
| echo "ExternalSecret status: ${STATUS} (attempt ${i}/30)..." | ||
| sleep 5 | ||
| done | ||
| echo "==> Generating self-signed TLS materials for bitwarden-sdk-server..." | ||
| CERT_DIR=$(mktemp -d) | ||
| export TLS_SECRET_NAME="bitwarden-tls-cert" | ||
| openssl ecparam -genkey -name prime256v1 -noout -out "${CERT_DIR}/ca.key" 2>/dev/null | ||
| openssl req -new -x509 -key "${CERT_DIR}/ca.key" -sha256 \ | ||
| -subj "/O=external-secrets-e2e/CN=bitwarden-e2e-ca" \ | ||
| -days 1 -out "${CERT_DIR}/ca.crt" 2>/dev/null | ||
|
|
||
| cat > "${CERT_DIR}/san.cnf" <<'SANEOF' | ||
| [req] | ||
| distinguished_name = req_dn | ||
| req_extensions = v3_req | ||
| prompt = no | ||
| [req_dn] | ||
| O = external-secrets-e2e | ||
| CN = bitwarden-sdk-server | ||
| [v3_req] | ||
| basicConstraints = CA:FALSE | ||
| keyUsage = digitalSignature, keyEncipherment | ||
| extendedKeyUsage = serverAuth | ||
| subjectAltName = @alt_names | ||
| [alt_names] | ||
| DNS.1 = bitwarden-sdk-server.external-secrets.svc.cluster.local | ||
| DNS.2 = external-secrets-bitwarden-sdk-server.external-secrets.svc.cluster.local | ||
| DNS.3 = localhost | ||
| IP.1 = 127.0.0.1 | ||
| IP.2 = ::1 | ||
| SANEOF | ||
|
|
||
| openssl ecparam -genkey -name prime256v1 -noout -out "${CERT_DIR}/tls.key" 2>/dev/null | ||
| openssl req -new -key "${CERT_DIR}/tls.key" -config "${CERT_DIR}/san.cnf" -out "${CERT_DIR}/tls.csr" 2>/dev/null | ||
| openssl x509 -req -in "${CERT_DIR}/tls.csr" -CA "${CERT_DIR}/ca.crt" -CAkey "${CERT_DIR}/ca.key" \ | ||
| -CAcreateserial -out "${CERT_DIR}/tls.crt" -days 1 -sha256 \ | ||
| -extensions v3_req -extfile "${CERT_DIR}/san.cnf" 2>/dev/null | ||
|
|
||
| echo "Creating TLS secret '${TLS_SECRET_NAME}' in external-secrets namespace..." | ||
| oc delete secret "${TLS_SECRET_NAME}" -n external-secrets --ignore-not-found 2>/dev/null | ||
| oc create secret generic "${TLS_SECRET_NAME}" -n external-secrets \ | ||
| --from-file=tls.crt="${CERT_DIR}/tls.crt" \ | ||
| --from-file=tls.key="${CERT_DIR}/tls.key" \ | ||
| --from-file=ca.crt="${CERT_DIR}/ca.crt" | ||
| rm -rf "${CERT_DIR}" | ||
|
|
||
| echo "==> Enabling Bitwarden plugin with TLS secretRef..." | ||
| oc patch externalsecretsconfig cluster --type=merge -p "{ | ||
| \"spec\": { | ||
| \"plugins\": { | ||
| \"bitwardenSecretManagerProvider\": { | ||
| \"mode\": \"Enabled\", | ||
| \"secretRef\": { \"name\": \"${TLS_SECRET_NAME}\" } | ||
| } | ||
| } | ||
| } | ||
| }" | ||
| sleep 10 | ||
| echo "ExternalSecretsConfig status after patch:" | ||
| oc get externalsecretsconfig cluster -o jsonpath='{.status.conditions[*].type}' 2>/dev/null || true | ||
| echo "" | ||
| oc get externalsecretsconfig cluster -o yaml 2>/dev/null | grep -A5 'conditions:' || true | ||
| oc wait externalsecretsconfig/cluster --for=condition=Available --timeout=180s || { | ||
| echo "Warning: ExternalSecretsConfig did not reach Available condition, checking bitwarden deployment directly..." | ||
| oc get externalsecretsconfig cluster -o yaml 2>/dev/null | grep -A20 'status:' || true | ||
| } | ||
| oc rollout status deployment/bitwarden-sdk-server -n external-secrets --timeout=180s | ||
|
|
||
| echo "Listing all TLS-relevant services the scanner will probe:" | ||
| echo "--- external-secrets-operator namespace ---" | ||
| oc get svc -n external-secrets-operator -o wide 2>/dev/null || true | ||
| echo "--- external-secrets namespace ---" | ||
| oc get svc -n external-secrets -o wide 2>/dev/null || true | ||
| echo "--- bitwarden-sdk-server service ---" | ||
| oc get svc -n external-secrets bitwarden-sdk-server -o wide 2>/dev/null || true | ||
| oc get endpoints -n external-secrets bitwarden-sdk-server 2>/dev/null || true | ||
| from: src | ||
| resources: | ||
| requests: | ||
| cpu: 100m | ||
| - ref: tls-scanner-run | ||
| workflow: optional-operators-ci-operator-sdk-aws | ||
| zz_generated_metadata: | ||
| branch: main | ||
| org: openshift | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.