From c6131d6374699c70ae3247e095c1bc4197fe60fc Mon Sep 17 00:00:00 2001 From: Raghav Kabra Date: Thu, 4 Jun 2026 10:34:56 +0530 Subject: [PATCH 1/3] Add OpenShift support to boundary-worker Helm chart --- templates/_helpers.tpl | 7 ++ templates/worker-deployment.yaml | 10 +- templates/worker-pvc.yaml | 4 + templates/worker-route.yaml | 28 +++++ templates/worker-service.yaml | 2 +- tests/unit/worker-deployment_test.yaml | 67 ++++++++++++ tests/unit/worker-pvc_test.yaml | 42 ++++++++ tests/unit/worker-route_test.yaml | 139 +++++++++++++++++++++++++ tests/unit/worker-service_test.yaml | 38 +++++++ values.openshift.yaml | 58 +++++++++++ values.yaml | 33 ++++++ 11 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 templates/worker-route.yaml create mode 100644 tests/unit/worker-route_test.yaml create mode 100644 values.openshift.yaml diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 83992bf..7241502 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -170,6 +170,13 @@ limits: memory: 256Mi {{- end }} +{{/* +Get the OpenShift Route name for the worker proxy port +*/}} +{{- define "boundary.worker.route.name" -}} +{{- printf "%s-proxy-route" (include "boundary.fullname" .) }} +{{- end }} + {{/* Get the service account name for the worker */}} diff --git a/templates/worker-deployment.yaml b/templates/worker-deployment.yaml index 797bffe..e7da0c5 100644 --- a/templates/worker-deployment.yaml +++ b/templates/worker-deployment.yaml @@ -10,7 +10,7 @@ metadata: {{- include "boundary.labels" . | nindent 4 }} app.kubernetes.io/component: worker spec: - replicas: 1 # Workers are not scaled horizontally + replicas: 1 selector: matchLabels: {{- include "boundary.worker.selectorLabels" . | nindent 6 }} @@ -33,13 +33,21 @@ spec: {{- end }} terminationGracePeriodSeconds: {{ .Values.worker.terminationGracePeriodSeconds }} securityContext: + {{- if .Values.openshift.enabled }} + {{- toYaml .Values.openshift.podSecurityContext | nindent 8 }} + {{- else }} {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- end }} containers: - name: worker image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: + {{- if .Values.openshift.enabled }} + {{- toYaml .Values.openshift.containerSecurityContext | nindent 12 }} + {{- else }} {{- toYaml .Values.containerSecurityContext | nindent 12 }} + {{- end }} env: - name: POD_NAME valueFrom: diff --git a/templates/worker-pvc.yaml b/templates/worker-pvc.yaml index c514547..0f7a330 100644 --- a/templates/worker-pvc.yaml +++ b/templates/worker-pvc.yaml @@ -14,7 +14,9 @@ metadata: spec: accessModes: - {{ .Values.worker.persistence.recording.accessMode }} + {{- if .Values.worker.persistence.recording.storageClass }} storageClassName: {{ .Values.worker.persistence.recording.storageClass }} + {{- end }} resources: requests: storage: {{ .Values.worker.persistence.recording.size }} @@ -37,7 +39,9 @@ metadata: spec: accessModes: - {{ .Values.worker.persistence.authStorage.accessMode }} + {{- if .Values.worker.persistence.authStorage.storageClass }} storageClassName: {{ .Values.worker.persistence.authStorage.storageClass }} + {{- end }} resources: requests: storage: {{ .Values.worker.persistence.authStorage.size }} diff --git a/templates/worker-route.yaml b/templates/worker-route.yaml new file mode 100644 index 0000000..a57bcb6 --- /dev/null +++ b/templates/worker-route.yaml @@ -0,0 +1,28 @@ +# Copyright IBM Corp. 2026 + +{{- if and .Values.openshift.enabled .Values.openshift.route.proxy.enabled }} +# Boundary Worker OpenShift Route +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "boundary.worker.route.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "boundary.labels" . | nindent 4 }} + app.kubernetes.io/component: worker + {{- with .Values.openshift.route.proxy.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.openshift.route.proxy.host }} + host: {{ .Values.openshift.route.proxy.host }} + {{- end }} + to: + kind: Service + name: {{ include "boundary.worker.proxy.serviceName" . }} + port: + targetPort: proxy + tls: + termination: {{ .Values.openshift.route.proxy.tls.termination }} +{{- end }} diff --git a/templates/worker-service.yaml b/templates/worker-service.yaml index 1dfe8c3..3fa3dab 100644 --- a/templates/worker-service.yaml +++ b/templates/worker-service.yaml @@ -15,7 +15,7 @@ metadata: {{- . | nindent 4 }} {{- end }} spec: - type: {{ .Values.worker.service.proxy.type }} + type: {{ if .Values.openshift.enabled }}ClusterIP{{ else }}{{ .Values.worker.service.proxy.type }}{{ end }} ports: - port: {{ .Values.worker.service.proxy.port }} targetPort: {{ .Values.worker.service.proxy.targetPort }} diff --git a/tests/unit/worker-deployment_test.yaml b/tests/unit/worker-deployment_test.yaml index ba76c8e..6de1432 100644 --- a/tests/unit/worker-deployment_test.yaml +++ b/tests/unit/worker-deployment_test.yaml @@ -583,3 +583,70 @@ tests: - equal: path: spec.template.spec.containers[0].env[3].name value: EXTRA_VAR + path: spec.selector.matchLabels["app.kubernetes.io/component"] + value: worker + + # ============================================================================ + # OPENSHIFT SECURITY CONTEXT TESTS + # ============================================================================ + + - it: should omit runAsUser and fsGroup from pod security context when openshift.enabled is true + template: worker-deployment.yaml + set: + openshift.enabled: true + asserts: + - isNull: + path: spec.template.spec.securityContext.runAsUser + - isNull: + path: spec.template.spec.securityContext.runAsGroup + - isNull: + path: spec.template.spec.securityContext.fsGroup + - equal: + path: spec.template.spec.securityContext.runAsNonRoot + value: true + + - it: should retain runAsUser and fsGroup in pod security context when openshift.enabled is false + template: worker-deployment.yaml + set: + openshift.enabled: false + asserts: + - equal: + path: spec.template.spec.securityContext.runAsUser + value: 100 + - equal: + path: spec.template.spec.securityContext.runAsGroup + value: 1000 + - equal: + path: spec.template.spec.securityContext.fsGroup + value: 1000 + + - it: should omit runAsUser and runAsGroup from container security context when openshift.enabled is true + template: worker-deployment.yaml + set: + openshift.enabled: true + asserts: + - isNull: + path: spec.template.spec.containers[0].securityContext.runAsUser + - isNull: + path: spec.template.spec.containers[0].securityContext.runAsGroup + - equal: + path: spec.template.spec.containers[0].securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - equal: + path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem + value: true + + - it: should retain runAsUser in container security context when openshift.enabled is false + template: worker-deployment.yaml + set: + openshift.enabled: false + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext.runAsUser + value: 100 + - equal: + path: spec.template.spec.containers[0].securityContext.runAsGroup + value: 1000 diff --git a/tests/unit/worker-pvc_test.yaml b/tests/unit/worker-pvc_test.yaml index 0c738f9..20a9319 100644 --- a/tests/unit/worker-pvc_test.yaml +++ b/tests/unit/worker-pvc_test.yaml @@ -204,3 +204,45 @@ tests: path: spec.storageClassName value: local-path documentIndex: 0 + + # ============================================================================ + # STORAGECLASS CONDITIONAL RENDERING TESTS (OpenShift / default SC) + # ============================================================================ + + - it: should omit storageClassName from recording PVC when storageClass is empty + set: + worker.persistence.recording.enabled: true + worker.persistence.recording.storageClass: "" + documentIndex: 0 + asserts: + - isNull: + path: spec.storageClassName + + - it: should set storageClassName on recording PVC when storageClass is provided + set: + worker.persistence.recording.enabled: true + worker.persistence.recording.storageClass: gp3-csi + documentIndex: 0 + asserts: + - equal: + path: spec.storageClassName + value: gp3-csi + + - it: should omit storageClassName from auth PVC when storageClass is empty + set: + worker.persistence.recording.enabled: false + worker.persistence.authStorage.storageClass: "" + documentIndex: 0 + asserts: + - isNull: + path: spec.storageClassName + + - it: should set storageClassName on auth PVC when storageClass is provided + set: + worker.persistence.recording.enabled: false + worker.persistence.authStorage.storageClass: thin-csi + documentIndex: 0 + asserts: + - equal: + path: spec.storageClassName + value: thin-csi diff --git a/tests/unit/worker-route_test.yaml b/tests/unit/worker-route_test.yaml new file mode 100644 index 0000000..1841a32 --- /dev/null +++ b/tests/unit/worker-route_test.yaml @@ -0,0 +1,139 @@ +# Copyright IBM Corp. 2026 + +suite: Test Worker OpenShift Route Configuration +templates: + - worker-route.yaml +tests: + # ============================================================================ + # ROUTE NOT RENDERED ON VANILLA KUBERNETES + # ============================================================================ + + - it: should not render a Route when openshift.enabled is false + set: + openshift.enabled: false + openshift.route.proxy.enabled: true + asserts: + - hasDocuments: + count: 0 + + - it: should not render a Route when openshift.route.proxy.enabled is false + set: + openshift.enabled: true + openshift.route.proxy.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should not render a Route when both flags are false + set: + openshift.enabled: false + openshift.route.proxy.enabled: false + asserts: + - hasDocuments: + count: 0 + + # ============================================================================ + # ROUTE RENDERED ON OPENSHIFT + # ============================================================================ + + - it: should render a Route when openshift.enabled and route.proxy.enabled are true + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + asserts: + - hasDocuments: + count: 1 + - isAPIVersion: + of: route.openshift.io/v1 + - isKind: + of: Route + + - it: should use passthrough TLS termination by default + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + asserts: + - equal: + path: spec.tls.termination + value: passthrough + + - it: should allow edge TLS termination override + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + openshift.route.proxy.tls.termination: edge + asserts: + - equal: + path: spec.tls.termination + value: edge + + - it: should not set spec.host when host is empty + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + openshift.route.proxy.host: "" + asserts: + - isNull: + path: spec.host + + - it: should set spec.host when a custom host is provided + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + openshift.route.proxy.host: "boundary.apps.cluster.example.com" + asserts: + - equal: + path: spec.host + value: "boundary.apps.cluster.example.com" + + - it: should target the proxy service + release: + name: my-worker + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + asserts: + - equal: + path: spec.to.kind + value: Service + - equal: + path: spec.to.name + value: my-worker-proxy + - equal: + path: spec.port.targetPort + value: proxy + + - it: should apply route annotations when provided + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + openshift.route.proxy.annotations: + haproxy.router.openshift.io/timeout: "2h" + asserts: + - equal: + path: metadata.annotations["haproxy.router.openshift.io/timeout"] + value: "2h" + + - it: should use the release namespace + release: + namespace: boundary-prod + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + asserts: + - equal: + path: metadata.namespace + value: boundary-prod + + - it: should carry standard labels + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/component"] + value: worker + - isNotNull: + path: metadata.labels["helm.sh/chart"] + - isNotNull: + path: metadata.labels["app.kubernetes.io/name"] diff --git a/tests/unit/worker-service_test.yaml b/tests/unit/worker-service_test.yaml index 83dbbf0..49fc7d8 100644 --- a/tests/unit/worker-service_test.yaml +++ b/tests/unit/worker-service_test.yaml @@ -181,3 +181,41 @@ tests: path: metadata.namespace value: custom-namespace documentIndex: 1 + + # ============================================================================ + # OPENSHIFT SERVICE TESTS + # ============================================================================ + + - it: should use ClusterIP for proxy service when openshift.enabled is true + set: + worker.service.proxy.enabled: true + worker.service.proxy.type: LoadBalancer + openshift.enabled: true + documentIndex: 0 + asserts: + - equal: + path: spec.type + value: ClusterIP + + - it: should use configured type for proxy service when openshift.enabled is false + set: + worker.service.proxy.enabled: true + worker.service.proxy.type: LoadBalancer + openshift.enabled: false + documentIndex: 0 + asserts: + - equal: + path: spec.type + value: LoadBalancer + + - it: should keep ops service type unchanged when openshift.enabled is true + set: + worker.service.proxy.enabled: true + worker.service.ops.enabled: true + worker.service.ops.type: ClusterIP + openshift.enabled: true + documentIndex: 1 + asserts: + - equal: + path: spec.type + value: ClusterIP diff --git a/values.openshift.yaml b/values.openshift.yaml new file mode 100644 index 0000000..e21b132 --- /dev/null +++ b/values.openshift.yaml @@ -0,0 +1,58 @@ +# Copyright IBM Corp. 2026 + +# These overrides are appropriate defaults for deploying this chart on OpenShift. +# Usage: +# helm install boundary-worker . \ +# --namespace boundary \ +# --create-namespace \ +# -f values.openshift.yaml \ +# --set-file worker.config=./worker.hcl + +openshift: + enabled: true + route: + proxy: + enabled: true + # Leave empty to let OpenShift auto-assign a hostname from the cluster's + # ingress domain (e.g. boundary-worker-proxy-boundary.apps.cluster.example.com). + host: "" + tls: + # passthrough keeps TLS end-to-end to the worker container. + # Use edge only if you terminate TLS at the OCP router and re-encrypt inward. + termination: passthrough + annotations: {} + +# ServiceAccount — create one so it can be bound to an SCC if needed. +# After installing, grant the restricted-v2 SCC if your namespace policy requires it: +# oc adm policy add-scc-to-user restricted-v2 -z boundary-worker -n +# +# SCC requirements for the boundary-worker pod: +# - runAsNonRoot: true (OCP assigns a UID from the namespace range) +# - allowPrivilegeEscalation: false +# - readOnlyRootFilesystem: true +# - All capabilities dropped +# The built-in "restricted-v2" SCC (OCP 4.11+) satisfies all of the above. +# On OCP 4.10 and earlier, use "restricted". +serviceAccount: + create: true + name: "boundary-worker" + annotations: {} + automountServiceAccountToken: false + +# Storage — set the OCP storage class appropriate for your platform. +# Examples: +# ROSA / AWS: gp3-csi +# OCP on vSphere: thin-csi +# OCP on Azure: managed-csi +# CRC (local): crc-csi-hostpath-provisioner +# Leave storageClass empty to use the cluster default StorageClass. +worker: + service: + proxy: + # On OpenShift the proxy port is exposed via the Route above; a LoadBalancer is not needed. + type: ClusterIP + persistence: + recording: + storageClass: "" + authStorage: + storageClass: "" diff --git a/values.yaml b/values.yaml index 64490dd..e45d720 100644 --- a/values.yaml +++ b/values.yaml @@ -175,6 +175,39 @@ serviceAccount: # iam.gke.io/gcp-service-account: boundary-worker@PROJECT.iam.gserviceaccount.com automountServiceAccountToken: false # Set to true when using IRSA or Workload Identity +# OpenShift configuration +# Set openshift.enabled=true when deploying on OpenShift. +# - Swaps the proxy Service type to ClusterIP (a Route handles external traffic instead). +# - Drops the fixed runAsUser/runAsGroup/fsGroup so OpenShift's SCC assigns a valid UID from the namespace range. +# - Renders an openshift.io/v1 Route for TCP-passthrough access to the proxy port. +openshift: + enabled: false + # Pod security context used instead of podSecurityContext when openshift.enabled=true. + # runAsUser/runAsGroup/fsGroup are intentionally absent so the namespace SCC assigns them. + podSecurityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + # Container security context used instead of containerSecurityContext when openshift.enabled=true. + containerSecurityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + route: + proxy: + enabled: true + # Leave empty to let OpenShift auto-assign a hostname. + host: "" + tls: + # passthrough keeps TLS end-to-end to the worker; use edge only if you terminate TLS at the router. + termination: passthrough + annotations: {} + # Optional configurations podAnnotations: {} nodeSelector: {} From 20a06babc64449f4c4edc9c0f84c76eef855d380 Mon Sep 17 00:00:00 2001 From: Raghav Kabra Date: Fri, 5 Jun 2026 12:17:43 +0530 Subject: [PATCH 2/3] fix: address PR review comments for OpenShift support --- templates/worker-deployment.yaml | 2 +- templates/worker-route.yaml | 2 +- tests/unit/worker-route_test.yaml | 12 +++++++++ values.openshift.yaml | 6 +++++ values.schema.json | 42 +++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/templates/worker-deployment.yaml b/templates/worker-deployment.yaml index e7da0c5..2c8bbae 100644 --- a/templates/worker-deployment.yaml +++ b/templates/worker-deployment.yaml @@ -10,7 +10,7 @@ metadata: {{- include "boundary.labels" . | nindent 4 }} app.kubernetes.io/component: worker spec: - replicas: 1 + replicas: 1 # Workers are not scaled horizontally selector: matchLabels: {{- include "boundary.worker.selectorLabels" . | nindent 6 }} diff --git a/templates/worker-route.yaml b/templates/worker-route.yaml index a57bcb6..bea9e83 100644 --- a/templates/worker-route.yaml +++ b/templates/worker-route.yaml @@ -6,7 +6,7 @@ apiVersion: route.openshift.io/v1 kind: Route metadata: name: {{ include "boundary.worker.route.name" . }} - namespace: {{ .Release.Namespace }} + namespace: {{ include "boundary.namespace" . }} labels: {{- include "boundary.labels" . | nindent 4 }} app.kubernetes.io/component: worker diff --git a/tests/unit/worker-route_test.yaml b/tests/unit/worker-route_test.yaml index 1841a32..4af4591 100644 --- a/tests/unit/worker-route_test.yaml +++ b/tests/unit/worker-route_test.yaml @@ -137,3 +137,15 @@ tests: path: metadata.labels["helm.sh/chart"] - isNotNull: path: metadata.labels["app.kubernetes.io/name"] + + - it: should use values.namespace over release namespace when set + release: + namespace: release-ns + set: + openshift.enabled: true + openshift.route.proxy.enabled: true + namespace: override-ns + asserts: + - equal: + path: metadata.namespace + value: override-ns diff --git a/values.openshift.yaml b/values.openshift.yaml index e21b132..6419f3a 100644 --- a/values.openshift.yaml +++ b/values.openshift.yaml @@ -8,6 +8,12 @@ # -f values.openshift.yaml \ # --set-file worker.config=./worker.hcl +# Placeholder image until an official OpenShift-certified image is published. +# Replace with the official Red Hat registry image once available. +image: + repository: public.ecr.aws/g0u5x5a3/boundary-ent + tag: "0.21.0-ubi-arm64" + openshift: enabled: true route: diff --git a/values.schema.json b/values.schema.json index 272baab..f64b08e 100644 --- a/values.schema.json +++ b/values.schema.json @@ -218,6 +218,48 @@ }, "namespace": { "type": "string" + }, + "openshift": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "podSecurityContext": { + "type": "object" + }, + "containerSecurityContext": { + "type": "object" + }, + "route": { + "type": "object", + "properties": { + "proxy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "tls": { + "type": "object", + "properties": { + "termination": { + "type": "string", + "enum": ["passthrough", "edge", "reencrypt"] + } + } + }, + "annotations": { + "type": "object" + } + } + } + } + } + } } } } \ No newline at end of file From bdb6ad7a58a2c3469d9edee8e30c994443d7186a Mon Sep 17 00:00:00 2001 From: Raghav Kabra Date: Fri, 12 Jun 2026 13:17:43 +0530 Subject: [PATCH 3/3] add unit and acceptance tests for OpenShift worker Helm chart --- .github/workflows/test.yml | 55 ++++ Makefile | 229 ++++++++++++- tests/acceptance/openshift-smoke-test.sh | 123 +++++++ .../openshift-tcp-target-conn-test.sh | 301 ++++++++++++++++++ tests/unit/worker-deployment_test.yaml | 2 - values.openshift.yaml | 3 +- values.yaml | 1 + 7 files changed, 709 insertions(+), 5 deletions(-) create mode 100755 tests/acceptance/openshift-smoke-test.sh create mode 100755 tests/acceptance/openshift-tcp-target-conn-test.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3d212fd..48e2ede 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,14 @@ on: description: "Run acceptance tests" type: boolean default: false + run_openshift_acceptance: + description: "Run OpenShift acceptance tests" + type: boolean + default: false + ocp_storage_class: + description: "OpenShift storage class override" + type: string + required: false permissions: contents: read @@ -133,6 +141,7 @@ jobs: name: Acceptance Test runs-on: ubuntu-latest needs: unit-test + if: github.event_name == 'workflow_dispatch' && github.event.inputs.run_acceptance == 'true' env: BOUNDARY_ADDR: ${{ secrets.BOUNDARY_ADDR }} BOUNDARY_AUTH_METHOD_ID: ${{ secrets.BOUNDARY_AUTH_METHOD_ID }} @@ -160,3 +169,49 @@ jobs: if: always() run: make acceptance-cleanup + openshift-acceptance-test: + name: OpenShift Acceptance Test + runs-on: ubuntu-latest + needs: unit-test + if: github.event_name == 'workflow_dispatch' && github.event.inputs.run_openshift_acceptance == 'true' + env: + BOUNDARY_ADDR: ${{ secrets.BOUNDARY_ADDR }} + BOUNDARY_AUTH_METHOD_ID: ${{ secrets.BOUNDARY_AUTH_METHOD_ID }} + BOUNDARY_CLUSTER_ID: ${{ secrets.BOUNDARY_CLUSTER_ID }} + BOUNDARY_LOGIN_NAME: ${{ secrets.BOUNDARY_LOGIN_NAME }} + BOUNDARY_PASSWORD: ${{ secrets.BOUNDARY_PASSWORD }} + BOUNDARY_TARGET_ID: ${{ secrets.BOUNDARY_TARGET_ID }} + OCP_STORAGE_CLASS: ${{ github.event.inputs.ocp_storage_class }} + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install oc CLI + run: | + curl -Lo /tmp/oc.tar.gz https://mirror.openshift.com/pub/openshift-v4/clients/ocp/stable/openshift-client-linux.tar.gz + sudo tar -xzf /tmp/oc.tar.gz -C /usr/local/bin oc + oc version --client + + - name: Setup Helm + run: make setup-helm + + - name: Install Boundary CLI + run: | + wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list + sudo apt update && sudo apt install -y boundary + boundary version + + - name: Login to OpenShift cluster + run: | + oc login "${{ secrets.OCP_SERVER }}" \ + --token="${{ secrets.OCP_TOKEN }}" \ + --insecure-skip-tls-verify=true + + - name: Run OpenShift acceptance tests + run: make openshift-acceptance-full + + - name: Cleanup + if: always() + run: make openshift-acceptance-cleanup + diff --git a/Makefile b/Makefile index 103ce7f..a0f3cbf 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,10 @@ endif # ================================ .PHONY: help format deps clean lint test unit-test worker-config .PHONY: setup-helm setup-kubeconform setup-trivy setup-kubescape setup-helm-unittest lint-helm-k8s trivy-scan kubescape-scan -.PHONY: acceptance-setup acceptance-cluster acceptance-helm acceptance-test acceptance-full acceptance-cleanup +.PHONY: acceptance-setup acceptance-cluster acceptance-helm acceptance-test acceptance-full acceptance-cleanup acceptance-all .PHONY: kind-matrix-test kind-matrix-cleanup +.PHONY: openshift-smoke-test openshift-helm openshift-acceptance-test openshift-acceptance-full openshift-acceptance-cleanup +.PHONY: crc-setup crc-helm crc-test crc-full crc-cleanup .PHONY: eks-setup eks-helm eks-test eks-full eks-cleanup .PHONY: tf-setup tf-destroy tf-output tf-plan .PHONY: aks-setup aks-helm aks-test aks-full aks-cleanup @@ -53,9 +55,24 @@ help: @echo " make acceptance-test - Run acceptance tests" @echo " make acceptance-full - Run full acceptance workflow (setup + worker-config + helm + tests)" @echo " make acceptance-cleanup - Delete acceptance cluster" + @echo " make acceptance-all - Run BOTH Kubernetes (KIND) and OpenShift (CRC) acceptance tests" @echo " make kind-matrix-test - Run tcp-target-conn-test.sh across the 2 KIND versions prior to latest (auto-resolved)" @echo " make kind-matrix-cleanup - Delete the acceptance cluster and cached KIND binaries" @echo "" + @echo "OpenShift Acceptance Testing targets:" + @echo " make openshift-smoke-test - Run OpenShift cluster + chart smoke test (standalone: installs+verifies+cleans up)" + @echo " make openshift-helm - Install Helm chart on an OpenShift cluster (values.openshift.yaml)" + @echo " make openshift-acceptance-test - Run full OpenShift acceptance test suite (chart must already be deployed)" + @echo " make openshift-acceptance-full - Full OpenShift workflow (worker-config + openshift-helm + tests)" + @echo " make openshift-acceptance-cleanup - Uninstall Helm release from OpenShift" + @echo "" + @echo "OpenShift Local (CRC) Acceptance Testing targets:" + @echo " make crc-setup - Start CRC cluster and configure oc context" + @echo " make crc-helm - Install Helm chart on CRC with values.openshift.yaml" + @echo " make crc-test - Run full OpenShift acceptance suite against CRC" + @echo " make crc-full - Full CRC workflow (crc-setup + worker-config + crc-helm + crc-test)" + @echo " make crc-cleanup - Uninstall Helm release and stop CRC cluster" + @echo "" @echo "AWS EKS Acceptance Testing targets (shell-based, legacy):" @echo " make eks-setup - Provision EKS cluster via Terraform (tf-setup)" @echo " make eks-helm - Install Helm chart with EKS values (gp3, NLB)" @@ -543,6 +560,202 @@ acceptance-test: @echo "✅ All acceptance tests passed!" @echo "" +# ================================ +# OpenShift Acceptance Testing Targets +# ================================ + +openshift-smoke-test: + @echo "================================" + @echo "OpenShift Worker Chart Smoke Test" + @echo "================================" + @echo "" + @command -v oc >/dev/null 2>&1 || (echo "❌ oc CLI not found. Run: oc login "; exit 1) + @if [ ! -f worker.hcl ]; then \ + echo "❌ worker.hcl not found. Run 'make worker-config' first"; \ + exit 1; \ + fi + @bash tests/acceptance/openshift-smoke-test.sh + +openshift-helm: + @echo "============================================" + @echo "Installing Helm Chart on OpenShift" + @echo "============================================" + @echo "" + @command -v helm >/dev/null 2>&1 || (echo "❌ Helm not found"; exit 1) + @[ -f worker.hcl ] || { echo "❌ worker.hcl not found. Run 'make worker-config' first"; exit 1; } + @echo "Installing boundary-worker chart with values.openshift.yaml..." + @EXTRA_ARGS=""; \ + if [ -n "$${OCP_STORAGE_CLASS:-}" ]; then \ + EXTRA_ARGS="$$EXTRA_ARGS --set worker.persistence.recording.storageClass=$${OCP_STORAGE_CLASS} --set worker.persistence.authStorage.storageClass=$${OCP_STORAGE_CLASS}"; \ + fi; \ + if [ -n "$${OCP_MEM_REQUEST:-}" ]; then \ + EXTRA_ARGS="$$EXTRA_ARGS --set worker.resources.requests.memory=$${OCP_MEM_REQUEST}"; \ + fi; \ + if [ -n "$${OCP_CPU_REQUEST:-}" ]; then \ + EXTRA_ARGS="$$EXTRA_ARGS --set worker.resources.requests.cpu=$${OCP_CPU_REQUEST}"; \ + fi; \ + if [ -n "$${OCP_MEM_LIMIT:-}" ]; then \ + EXTRA_ARGS="$$EXTRA_ARGS --set worker.resources.limits.memory=$${OCP_MEM_LIMIT}"; \ + fi; \ + if [ -n "$${OCP_CPU_LIMIT:-}" ]; then \ + EXTRA_ARGS="$$EXTRA_ARGS --set worker.resources.limits.cpu=$${OCP_CPU_LIMIT}"; \ + fi; \ + helm upgrade --install boundary-worker . \ + --namespace boundary \ + --create-namespace \ + -f values.openshift.yaml \ + --set-file worker.config=worker.hcl \ + --wait \ + --timeout 5m \ + $$EXTRA_ARGS + @echo "✅ Helm chart installed on OpenShift" + @echo "" + @oc get all -n boundary + +openshift-acceptance-test: + @echo "================================" + @echo "OpenShift Acceptance Test Suite" + @echo "================================" + @echo "" + @command -v oc >/dev/null 2>&1 || (echo "❌ oc CLI not found. Run: oc login "; exit 1) + @SKIP_HELM_INSTALL=true bash tests/acceptance/openshift-smoke-test.sh + @bash tests/acceptance/openshift-tcp-target-conn-test.sh + @bash tests/acceptance/cleanup-worker.sh + @echo "✅ All OpenShift acceptance tests passed!" + @echo "" + +openshift-acceptance-full: + @echo "================================" + @echo "Running Full OpenShift Acceptance Workflow" + @echo "================================" + @echo "" + @$(MAKE) worker-config + @$(MAKE) openshift-helm + @$(MAKE) openshift-acceptance-test + @echo "" + @echo "To cleanup, run: make openshift-acceptance-cleanup" + @echo "" + +openshift-acceptance-cleanup: + @echo "================================" + @echo "Cleaning up OpenShift Acceptance" + @echo "================================" + @echo "Cleaning up worker from Boundary cluster..." + @bash tests/acceptance/cleanup-worker.sh || true + @echo "" + @echo "Uninstalling Helm release..." + @helm uninstall boundary-worker --namespace boundary 2>/dev/null && echo "✅ Helm release uninstalled" || echo "⚠️ Helm release not found" + @rm -f worker.hcl + @rm -f /tmp/boundary-worker-id.txt + @echo "✅ OpenShift acceptance cleanup complete" + +# ================================ +# OpenShift Local (CRC) Targets +# ================================ + +crc-setup: + @echo "================================" + @echo "Setting up CRC (OpenShift Local)" + @echo "================================" + @echo "" + @command -v crc >/dev/null 2>&1 || (echo "❌ crc not installed. Download from: https://developers.redhat.com/products/openshift-local"; exit 1) + @command -v oc >/dev/null 2>&1 || (echo "❌ oc CLI not found. Run: eval $$(crc oc-env)"; exit 1) + @echo "✅ crc and oc are installed" + @echo "" + @echo "Starting CRC cluster (this may take several minutes)..." + @crc start + @echo "" + @echo "Configuring oc context..." + @eval $$(crc oc-env) && \ + KUBEADMIN_PASS=$$(crc console --credentials 2>/dev/null | grep 'kubeadmin' | sed 's/.*-p \([^ ]*\) .*/\1/') && \ + oc login -u kubeadmin -p "$$KUBEADMIN_PASS" \ + --insecure-skip-tls-verify=true \ + https://api.crc.testing:6443 + @oc cluster-info + @echo "✅ CRC cluster is ready" + @echo "" + @echo "Next steps:" + @echo " - Generate worker config: make worker-config" + @echo " - Install Helm chart: make crc-helm" + @echo " - Run tests: make crc-test" + @echo " - Full workflow: make crc-full" + +crc-helm: + @echo "============================================" + @echo "Installing Helm Chart on CRC" + @echo "============================================" + @echo "" + @command -v helm >/dev/null 2>&1 || (echo "❌ Helm not found"; exit 1) + @if [ ! -f worker.hcl ]; then \ + echo "❌ worker.hcl not found. Run 'make worker-config' first"; \ + exit 1; \ + fi + @echo "Installing boundary-worker chart with values.openshift.yaml..." + @helm upgrade --install boundary-worker . \ + --namespace boundary \ + --create-namespace \ + -f values.openshift.yaml \ + --set worker.persistence.recording.storageClass=crc-csi-hostpath-provisioner \ + --set worker.persistence.authStorage.storageClass=crc-csi-hostpath-provisioner \ + --set worker.resources.requests.memory=128Mi \ + --set worker.resources.requests.cpu=50m \ + --set worker.resources.limits.memory=512Mi \ + --set worker.resources.limits.cpu=200m \ + --set-file worker.config=worker.hcl \ + --wait \ + --timeout 5m + @echo "✅ Helm chart installed successfully" + @echo "" + @echo "Deployed resources:" + @oc get all -n boundary + @echo "" + @echo "Waiting for deployment to be ready..." + @oc wait --for=condition=available --timeout=5m \ + deployment/boundary-worker-deployment \ + -n boundary + @echo "✅ Deployment is ready" + +crc-test: + @echo "================================" + @echo "CRC OpenShift Acceptance Tests" + @echo "================================" + @echo "" + @command -v oc >/dev/null 2>&1 || (echo "❌ oc CLI not found. Run: eval $$(crc oc-env)"; exit 1) + @SKIP_HELM_INSTALL=true bash tests/acceptance/openshift-smoke-test.sh + @bash tests/acceptance/openshift-tcp-target-conn-test.sh + @bash tests/acceptance/cleanup-worker.sh + @echo "✅ All CRC acceptance tests passed!" + @echo "" + +crc-full: + @echo "================================" + @echo "Running Full CRC Workflow" + @echo "================================" + @echo "" + @$(MAKE) crc-setup + @$(MAKE) worker-config + @$(MAKE) crc-helm + @$(MAKE) crc-test + @echo "" + @echo "To cleanup, run: make crc-cleanup" + @echo "" + +crc-cleanup: + @echo "================================" + @echo "Cleaning up CRC" + @echo "================================" + @echo "Cleaning up worker from Boundary cluster..." + @bash tests/acceptance/cleanup-worker.sh || true + @echo "" + @echo "Uninstalling Helm release..." + @helm uninstall boundary-worker --namespace boundary 2>/dev/null && echo "✅ Helm release uninstalled" || echo "⚠️ Helm release not found" + @rm -f worker.hcl + @rm -f /tmp/boundary-worker-id.txt + @echo "" + @echo "Stopping CRC cluster..." + @crc stop && echo "✅ CRC cluster stopped" || echo "⚠️ CRC stop failed" + @echo "✅ CRC cleanup complete" + acceptance-full: @echo "================================" @@ -561,6 +774,20 @@ acceptance-full: @echo "To cleanup, run: make acceptance-cleanup" @echo "" +acceptance-all: + @echo "==================================================" + @echo "Running Full Acceptance Suite (Kubernetes + OpenShift)" + @echo "==================================================" + @echo "" + @echo "--- Phase 1: Kubernetes (KIND) Acceptance Tests ---" + @$(MAKE) acceptance-full + @echo "" + @echo "--- Phase 2: OpenShift (CRC) Acceptance Tests ---" + @$(MAKE) crc-full + @echo "" + @echo "✅ All acceptance tests passed (Kubernetes + OpenShift)!" + @echo "" + # ================================ # KIND Version Matrix Testing # ================================ diff --git a/tests/acceptance/openshift-smoke-test.sh b/tests/acceptance/openshift-smoke-test.sh new file mode 100755 index 0000000..c36c611 --- /dev/null +++ b/tests/acceptance/openshift-smoke-test.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# Copyright IBM Corp. 2026 + +# OpenShift Worker Chart — Acceptance Smoke Test +# Validates the Helm chart deploys correctly on OpenShift using values.openshift.yaml. + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test configuration +NAMESPACE="boundary" +HELM_RELEASE="boundary-worker" +WORKER_HCL="${WORKER_HCL:-./worker.hcl}" +TIMEOUT="${TIMEOUT:-300}" +DEPLOY="${HELM_RELEASE}-deployment" +# When SKIP_HELM_INSTALL=true the chart is already deployed (e.g. via crc-helm or +# openshift-helm). The smoke test then only verifies existing resources. +SKIP_HELM_INSTALL="${SKIP_HELM_INSTALL:-false}" + +echo "OpenShift Worker Chart — Acceptance Smoke Test" + +# Function to print test results +print_result() { + if [ $1 -eq 0 ]; then + echo -e "${GREEN}✅${NC} $2" + else + echo -e "${RED}❌ FAILED:${NC} $2" + exit 1 + fi +} + +# Test 1: Verify OpenShift cluster is accessible +echo "Test 1: Verifying OpenShift cluster accessibility..." +if oc cluster-info > /dev/null 2>&1; then + print_result 0 "OpenShift cluster accessible" +else + print_result 1 "OpenShift cluster is not accessible. Run: oc login " +fi +echo "" + +# Test 2: Install chart with values.openshift.yaml (skipped if SKIP_HELM_INSTALL=true) +if [ "${SKIP_HELM_INSTALL}" = "true" ]; then + echo "Test 2: Verifying existing Helm release (SKIP_HELM_INSTALL=true)..." + if helm status "${HELM_RELEASE}" -n "${NAMESPACE}" > /dev/null 2>&1; then + print_result 0 "Helm release '${HELM_RELEASE}' is deployed" + else + print_result 1 "Helm release '${HELM_RELEASE}' not found. Deploy it first (e.g. make crc-helm or make openshift-helm)" + fi +else + echo "Test 2: Installing chart with values.openshift.yaml..." + [ -f "${WORKER_HCL}" ] || { echo -e "${RED}❌ FAILED:${NC} worker.hcl not found at '${WORKER_HCL}'"; exit 1; } + if helm upgrade --install "${HELM_RELEASE}" . \ + --namespace "${NAMESPACE}" \ + --create-namespace \ + -f values.openshift.yaml \ + --set-file worker.config="${WORKER_HCL}" \ + --wait \ + --timeout "${TIMEOUT}s" \ + > /dev/null 2>&1; then + print_result 0 "Helm release '${HELM_RELEASE}' installed successfully" + else + print_result 1 "helm upgrade --install failed" + fi +fi +echo "" + +# Test 3: Verify deployment is available +echo "Test 3: Verifying deployment is available..." +if kubectl wait --for=condition=available \ + --timeout="${TIMEOUT}s" \ + deployment/"${DEPLOY}" \ + -n "${NAMESPACE}" > /dev/null 2>&1; then + print_result 0 "Deployment '${DEPLOY}' is available" +else + print_result 1 "Deployment '${DEPLOY}' did not become available" +fi +echo "" + +# Test 4: Verify OpenShift Route exists with passthrough TLS +echo "Test 4: Verifying OpenShift Route exists..." +ROUTE_NAME="${HELM_RELEASE}-proxy-route" +if oc get route "${ROUTE_NAME}" -n "${NAMESPACE}" > /dev/null 2>&1; then + print_result 0 "Route '${ROUTE_NAME}' exists" +else + print_result 1 "Route '${ROUTE_NAME}' not found" +fi +TLS_TERM=$(oc get route "${ROUTE_NAME}" -n "${NAMESPACE}" \ + -o jsonpath='{.spec.tls.termination}' 2>/dev/null || true) +if [ "${TLS_TERM}" = "passthrough" ]; then + print_result 0 "Route TLS termination is 'passthrough'" +else + print_result 1 "Expected TLS termination 'passthrough', got '${TLS_TERM:-empty}'" +fi +echo "" + +# Test 5: Verify proxy Service type is ClusterIP +echo "Test 5: Verifying proxy Service type is ClusterIP..." +PROXY_SVC="${HELM_RELEASE}-proxy" +SVC_TYPE=$(oc get service "${PROXY_SVC}" -n "${NAMESPACE}" \ + -o jsonpath='{.spec.type}' 2>/dev/null || true) +if [ "${SVC_TYPE}" = "ClusterIP" ]; then + print_result 0 "Proxy Service type is 'ClusterIP'" +else + print_result 1 "Expected 'ClusterIP', got '${SVC_TYPE:-empty}'" +fi +echo "" + +# Cleanup (only when this script managed the install) +if [ "${SKIP_HELM_INSTALL}" = "false" ]; then + echo "Cleaning up..." + if helm uninstall "${HELM_RELEASE}" -n "${NAMESPACE}" > /dev/null 2>&1; then + echo "✅ Helm release cleaned up" + else + echo -e "${YELLOW}⚠️ WARNING:${NC} Failed to uninstall Helm release" + fi + echo "" +fi +echo "✅ OpenShift Worker Chart Smoke Test passed!" diff --git a/tests/acceptance/openshift-tcp-target-conn-test.sh b/tests/acceptance/openshift-tcp-target-conn-test.sh new file mode 100755 index 0000000..0239b30 --- /dev/null +++ b/tests/acceptance/openshift-tcp-target-conn-test.sh @@ -0,0 +1,301 @@ +#!/bin/bash +# Copyright IBM Corp. 2026 + +# OpenShift Worker Chart — TCP Target Connection Test +# Scenarios: +# 1. Worker running on OpenShift cluster +# 2. Worker registers with Boundary cluster +# 3. Session creation validated via authorize-session +# 4. TCP connection & session field validation + +set -euo pipefail + +# ── Helpers ──────────────────────────────────────────────────────────────────── +pass() { echo " ✅ $1"; } +fail() { echo "❌ FAILED: $1"; exit 1; } +info() { echo " $1"; } +warn() { echo "⚠️ WARN: $1"; } + +# ── Cleanup trap: cancel any open sessions and kill background processes ────── +CONN_PID="" +CONN_SESSION_ID="" +SESSION_ID="" +CONN_OUT="" +_cleanup() { + if [ -n "${CONN_SESSION_ID}" ]; then + boundary sessions cancel \ + -id "${CONN_SESSION_ID}" \ + -addr "${BOUNDARY_ADDR:-}" \ + -token env://BOUNDARY_TOKEN >/dev/null 2>&1 || true + fi + if [ -n "${SESSION_ID}" ]; then + boundary sessions cancel \ + -id "${SESSION_ID}" \ + -addr "${BOUNDARY_ADDR:-}" \ + -token env://BOUNDARY_TOKEN >/dev/null 2>&1 || true + fi + [ -n "${CONN_PID}" ] && kill "${CONN_PID}" 2>/dev/null || true + [ -n "${CONN_OUT}" ] && rm -f "${CONN_OUT}" || true +} +trap _cleanup EXIT + +# ── Config ───────────────────────────────────────────────────────────────────── +NAMESPACE="boundary" +DEPLOY="boundary-worker-deployment" +# Allow callers to override the timeout. +# Default is 300s for standalone runs. +TIMEOUT="${TIMEOUT:-300}" + +# ── Load .env ───────────────────────────────────────────────────────────────── +if [ -f .env ]; then + set -o allexport + # shellcheck disable=SC1091 + source .env + set +o allexport +fi + +echo "OpenShift Worker Chart — TCP Target Connection Test Suite" +echo "" + +# Test 1: Worker running on OpenShift cluster +echo "Validating Worker Running on OpenShift Cluster..." +info "Checking OpenShift cluster accessibility..." +oc cluster-info >/dev/null 2>&1 \ + || fail "OpenShift cluster is not accessible. Run: oc login " +pass "OpenShift cluster accessible" +echo "" + +info "Checking worker deployment..." +oc get deployment "${DEPLOY}" -n "${NAMESPACE}" >/dev/null 2>&1 \ + || fail "Deployment '${DEPLOY}' not found in namespace '${NAMESPACE}'. Run: make openshift-acceptance-helm" +pass "Worker deployment '${DEPLOY}' exists" +echo "" + +info "Waiting for deployment to be available (timeout: ${TIMEOUT}s...)" +oc wait --for=condition=available \ + --timeout="${TIMEOUT}s" \ + deployment/"${DEPLOY}" \ + -n "${NAMESPACE}" >/dev/null 2>&1 \ + || fail "Worker deployment did not become available within ${TIMEOUT}s" +pass "Worker deployment is available" +echo "" + +POD=$(oc get pods \ + -n "${NAMESPACE}" \ + -l app.kubernetes.io/name=boundary-worker \ + --field-selector=status.phase=Running \ + -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) + +[ -n "${POD}" ] || fail "No running worker pod found" +pass "Worker pod running: ${POD}" +echo "" + +# Confirm proxy Service is ClusterIP on OpenShift (Route handles external traffic) +SVC_TYPE=$(oc get service boundary-worker-proxy -n "${NAMESPACE}" \ + -o jsonpath='{.spec.type}' 2>/dev/null || true) +[ "${SVC_TYPE}" = "ClusterIP" ] \ + || fail "Proxy Service type is '${SVC_TYPE}', expected 'ClusterIP' on OpenShift" +pass "Proxy Service type is ClusterIP" +echo "" + +# Test 2: Validate Worker Registration with Boundary Cluster +echo "Validating Worker Registration with Boundary Cluster..." +# Check required env vars +for var in BOUNDARY_ADDR BOUNDARY_AUTH_METHOD_ID BOUNDARY_LOGIN_NAME BOUNDARY_PASSWORD; do + [ -n "${!var:-}" ] || fail "'${var}' is not set. Check your .env file." +done +pass "Required environment variables are set" +info "Boundary address: ${BOUNDARY_ADDR}" +echo "" + +# Authenticate with Boundary +info "Authenticating with Boundary cluster..." +AUTH_OUT=$(boundary authenticate password \ + -addr "${BOUNDARY_ADDR}" \ + -auth-method-id "${BOUNDARY_AUTH_METHOD_ID}" \ + -login-name "${BOUNDARY_LOGIN_NAME}" \ + -password env://BOUNDARY_PASSWORD \ + -keyring-type=none 2>&1) || fail "Boundary authentication failed:\n${AUTH_OUT}" + +BOUNDARY_TOKEN=$(printf '%s\n' "${AUTH_OUT}" \ + | awk '/The token is:/ { getline; gsub(/^[[:space:]]+|[[:space:]]+$/, ""); print; exit }') +[ -n "${BOUNDARY_TOKEN}" ] || fail "Failed to extract auth token from authentication output" +export BOUNDARY_TOKEN +pass "Authenticated with Boundary cluster" +echo "" + +# ── Ops health endpoint check (port-forward to ClusterIP ops service) ──────── +info "Checking worker ops health endpoint (port 9203)..." +pkill -f "port-forward.*9203" 2>/dev/null || true +sleep 1 +oc port-forward \ + -n "${NAMESPACE}" \ + "pod/${POD}" 9203:9203 >/dev/null 2>&1 & +PF_PID=$! + +OPS_STATUS="" +for ((i=1; i<=15; i++)); do + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://localhost:9203/health 2>/dev/null || true) + if [ -n "${HTTP_CODE}" ] && [ "${HTTP_CODE}" != "000" ]; then + OPS_STATUS="${HTTP_CODE}" + break + fi + sleep 2 +done +kill "${PF_PID}" 2>/dev/null || true +wait "${PF_PID}" 2>/dev/null || true + +if [ -n "${OPS_STATUS}" ] && [ "${OPS_STATUS}" != "000" ]; then + info "Worker ops health endpoint responded with HTTP ${OPS_STATUS}" + pass "Worker ops health endpoint is reachable" +else + fail "Ops health endpoint /health on port 9203 did not respond — worker is not healthy" +fi +echo "" + +# ── Auth storage: confirm node enrollment was initiated ─────────────────────── +info "Checking worker auth storage (node enrollment)..." +AUTH_FILES=$(oc exec -n "${NAMESPACE}" "${POD}" \ + -- find /var/lib/boundary -type f 2>/dev/null | wc -l | tr -d ' ') || AUTH_FILES=0 + +if [ "${AUTH_FILES}" -gt 0 ]; then + pass "Worker auth storage populated (${AUTH_FILES} file(s)) — node enrollment initiated" +else + warn "Auth storage is empty; worker may not have started enrollment yet" +fi +echo "" + +# ── Boundary API: confirm worker record exists (activation token consumed) ──── +info "Verifying worker record exists in Boundary..." +WORKERS_JSON=$(boundary workers list \ + -scope-id global \ + -addr "${BOUNDARY_ADDR}" \ + -token env://BOUNDARY_TOKEN \ + -format json 2>&1) || fail "Failed to list workers from Boundary:\n${WORKERS_JSON}" + +WORKER_ID=$(printf '%s\n' "${WORKERS_JSON}" | python3 -c " +import json, sys +data = json.load(sys.stdin) +for w in data.get('items', []): + tags = w.get('canonical_tags', {}).get('type', []) + if 'worker' in tags and w.get('address'): + print(w.get('id', '')) + break +" 2>/dev/null || true) + +[ -n "${WORKER_ID}" ] || fail "No worker with 'worker' tag found in Boundary. Activation token may not have been consumed." +pass "Worker record exists in Boundary: ${WORKER_ID}" + +# Save WORKER_ID for cleanup +echo "${WORKER_ID}" > /tmp/boundary-worker-id.txt +echo "" + +# ── Log: confirm worker is reaching upstream ────────────────────────────────── +info "Checking worker is attempting upstream connection..." +echo "" +if oc -n "${NAMESPACE}" logs "${POD}" 2>/dev/null \ + | grep -q "Setting HCP Boundary cluster address\|upstream.*address\|upstreamDialerFunc"; then + pass "Worker is actively attempting upstream connection to Boundary cluster" +fi +echo "" + +# Test 3: Session creation +echo "Validating Session Creation..." +[ -n "${BOUNDARY_TARGET_ID:-}" ] \ + || fail "'BOUNDARY_TARGET_ID' is not set. Add it to your .env file." +pass "Target configured: ${BOUNDARY_TARGET_ID}" +echo "" +info "Authorizing session to target..." + +SESSION_OUT=$(boundary targets authorize-session \ + -id "${BOUNDARY_TARGET_ID}" \ + -addr "${BOUNDARY_ADDR}" \ + -token env://BOUNDARY_TOKEN \ + -format json 2>&1) || fail "authorize-session failed:\n${SESSION_OUT}" + +SESSION_ID=$(printf '%s\n' "${SESSION_OUT}" \ + | grep -o '"session_id":"[^"]*"' \ + | head -1 \ + | cut -d'"' -f4) + +[ -n "${SESSION_ID}" ] || fail "Failed to extract session_id from authorize-session response" + +SESSION_STATUS=$(printf '%s\n' "${SESSION_OUT}" \ + | grep -o '"status":"[^"]*"' \ + | head -1 \ + | cut -d'"' -f4 || true) +[ -n "${SESSION_STATUS}" ] && info "Session status: ${SESSION_STATUS}" +pass "Session authorized and validated" + +# Cancel the authorize-session token immediately — it is not used for the +# TCP connect test below (boundary connect creates its own session). +boundary sessions cancel \ + -id "${SESSION_ID}" \ + -addr "${BOUNDARY_ADDR}" \ + -token env://BOUNDARY_TOKEN >/dev/null 2>&1 || true +SESSION_ID="" # cleared so the EXIT trap does not double-cancel +echo "" + +# Test 4: TCP connection & session field validation +echo "TCP connection & session field validation..." +info "Establishing proxy connection..." +echo "" + +CONN_OUT=$(mktemp) +boundary connect \ + -target-id "${BOUNDARY_TARGET_ID}" \ + -addr "${BOUNDARY_ADDR}" \ + -token env://BOUNDARY_TOKEN > "${CONN_OUT}" 2>&1 & +CONN_PID=$! + +for i in $(seq 1 30); do + if grep -q "Session ID:" "${CONN_OUT}" 2>/dev/null; then break; fi + sleep 1 +done + +CONN_SESSION_ID=$(grep "Session ID:" "${CONN_OUT}" | awk '{print $NF}') +CONN_PROXY_ADDR=$(grep "Address:" "${CONN_OUT}" | awk '{print $NF}') +CONN_PROXY_PORT=$(grep "Port:" "${CONN_OUT}" | awk '{print $NF}') +CONN_PROXY_PROTO=$(grep "Protocol:" "${CONN_OUT}" | awk '{print $NF}') +CONN_PROXY_EXPIRY=$(grep "Expiration:" "${CONN_OUT}" | sed 's/.*Expiration:[[:space:]]*//') +CONN_LIMIT=$(grep "Connection Limit:" "${CONN_OUT}" | awk '{print $NF}') + +echo "Session Details-" +echo "Session ID: ${CONN_SESSION_ID:-MISSING}" +echo "Address: ${CONN_PROXY_ADDR:-MISSING}" +echo "Port: ${CONN_PROXY_PORT:-MISSING}" +echo "Protocol: ${CONN_PROXY_PROTO:-MISSING}" +echo "Expiration: ${CONN_PROXY_EXPIRY:-MISSING}" +echo "Connection Limit: ${CONN_LIMIT:-MISSING}" +echo "" + +CONN_PASS=1 +[ -n "${CONN_SESSION_ID}" ] || CONN_PASS=0 +[ -n "${CONN_PROXY_ADDR}" ] || CONN_PASS=0 +[ -n "${CONN_PROXY_PORT}" ] || CONN_PASS=0 +[ -n "${CONN_PROXY_PROTO}" ] || CONN_PASS=0 +[ -n "${CONN_PROXY_EXPIRY}" ] || CONN_PASS=0 +[ -n "${CONN_LIMIT}" ] || CONN_PASS=0 + +if [ -n "${CONN_SESSION_ID}" ]; then + info "Waiting 15 seconds before cancelling session..." + sleep 15 + info "Cancelling session ${CONN_SESSION_ID}..." + boundary sessions cancel \ + -id "${CONN_SESSION_ID}" \ + -addr "${BOUNDARY_ADDR}" \ + -token env://BOUNDARY_TOKEN >/dev/null 2>&1 || true + CONN_SESSION_ID="" # cleared so the EXIT trap does not double-cancel + pass "Session cancelled Successfully" + echo "" +fi +kill "${CONN_PID}" 2>/dev/null || true +wait "${CONN_PID}" 2>/dev/null || true +CONN_PID="" +rm -f "${CONN_OUT}" +CONN_OUT="" + +[ "${CONN_PASS}" -eq 1 ] || fail "One or more session fields were missing" +echo "" + +echo "✅ OpenShift Worker Chart TCP Target Connection Test passed!" diff --git a/tests/unit/worker-deployment_test.yaml b/tests/unit/worker-deployment_test.yaml index 6de1432..0e0accb 100644 --- a/tests/unit/worker-deployment_test.yaml +++ b/tests/unit/worker-deployment_test.yaml @@ -583,8 +583,6 @@ tests: - equal: path: spec.template.spec.containers[0].env[3].name value: EXTRA_VAR - path: spec.selector.matchLabels["app.kubernetes.io/component"] - value: worker # ============================================================================ # OPENSHIFT SECURITY CONTEXT TESTS diff --git a/values.openshift.yaml b/values.openshift.yaml index 6419f3a..6b75d9a 100644 --- a/values.openshift.yaml +++ b/values.openshift.yaml @@ -8,8 +8,7 @@ # -f values.openshift.yaml \ # --set-file worker.config=./worker.hcl -# Placeholder image until an official OpenShift-certified image is published. -# Replace with the official Red Hat registry image once available. +# TODO: remove before merging — replace with official OpenShift-certified image when available. image: repository: public.ecr.aws/g0u5x5a3/boundary-ent tag: "0.21.0-ubi-arm64" diff --git a/values.yaml b/values.yaml index e45d720..d126084 100644 --- a/values.yaml +++ b/values.yaml @@ -93,6 +93,7 @@ worker: # - Using ClusterIP with an Ingress controller # - Setting scheme to "internal" in annotations # - Implementing network policies + # Note: ignored when openshift.enabled=true — the proxy port is exposed via a Route instead. type: LoadBalancer port: 9202 targetPort: 9202