From 58841c76ed70361acac7ea7bf23779b60b18dfc8 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 11:10:05 +0200 Subject: [PATCH 01/12] fix: allow in-cluster HTTP egress for Keycloak in CI The RHDH chart 2.y ships default-deny NetworkPolicies that only allow egress on ports 443, 53/5353, 5432, and 6379. Keycloak in CI uses a plain HTTP route (port 80), causing OIDC discovery to time out. Apply an external NetworkPolicy after deployment that allows TCP port 80 egress to in-cluster destinations (namespaceSelector: {}), so RHDH pods can reach Keycloak through the OpenShift router. Assisted-by: Claude --- src/deployment/rhdh/deployment.ts | 38 +++++++++++++++++++++++++++++++ src/utils/kubernetes-client.ts | 31 +++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index e409335..3cbfe17 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -71,6 +71,7 @@ export class RHDHDeployment { await this._applyDynamicPlugins(); await this._deployWithOperator(this.deploymentConfig.subscription); } + await this._applyNetworkPolicies(); await this.waitUntilReady(); }, ); @@ -284,6 +285,43 @@ export class RHDHDeployment { this._log(`Helm deployment completed successfully`); } + /** + * Apply NetworkPolicies that the chart's default-deny rules don't cover. + * The chart allows egress on 443 (HTTPS), 53/5353 (DNS), 5432 (PostgreSQL), + * and 6379 (Redis), but Keycloak in CI uses a plain HTTP route (port 80). + * + * TODO: Remove once the RHDH chart allows port 80 egress out of the box. + */ + private async _applyNetworkPolicies(): Promise { + const namespace = this.deploymentConfig.namespace; + await this.k8sClient.applyNetworkPolicy( + { + apiVersion: "networking.k8s.io/v1", + kind: "NetworkPolicy", + metadata: { + name: "rhdh-allow-http-egress", + namespace, + }, + spec: { + podSelector: { + matchLabels: { + "app.kubernetes.io/component": "backstage", + }, + }, + policyTypes: ["Egress"], + egress: [ + { + to: [{ namespaceSelector: {} }], + ports: [{ port: 80, protocol: "TCP" }], + }, + ], + }, + }, + namespace, + ); + this._log("Applied NetworkPolicy: rhdh-allow-http-egress"); + } + private async _deployWithOperator(subscription: string): Promise { const subscriptionObject = (await mergeYamlFilesIfExists([ DEFAULT_CONFIG_PATHS.operator.subscription, diff --git a/src/utils/kubernetes-client.ts b/src/utils/kubernetes-client.ts index 542e448..3ff6786 100644 --- a/src/utils/kubernetes-client.ts +++ b/src/utils/kubernetes-client.ts @@ -13,6 +13,7 @@ class KubernetesClientHelper { private _kc: k8s.KubeConfig; private _k8sApi: k8s.CoreV1Api; private _appsApi: k8s.AppsV1Api; + private _networkingApi: k8s.NetworkingV1Api; private _customObjectsApi: k8s.CustomObjectsApi; constructor() { @@ -22,6 +23,7 @@ class KubernetesClientHelper { try { this._k8sApi = this._kc.makeApiClient(k8s.CoreV1Api); this._appsApi = this._kc.makeApiClient(k8s.AppsV1Api); + this._networkingApi = this._kc.makeApiClient(k8s.NetworkingV1Api); this._customObjectsApi = this._kc.makeApiClient(k8s.CustomObjectsApi); } catch (error) { if ( @@ -311,6 +313,35 @@ class KubernetesClientHelper { } } + async applyNetworkPolicy( + policy: k8s.V1NetworkPolicy, + namespace: string, + ): Promise { + const name = policy.metadata!.name!; + try { + await this._networkingApi.replaceNamespacedNetworkPolicy({ + name, + namespace, + body: policy, + }); + console.log(`✓ Updated NetworkPolicy ${name} in namespace ${namespace}`); + } catch { + try { + await this._networkingApi.createNamespacedNetworkPolicy({ + namespace, + body: policy, + }); + console.log(`✓ Created NetworkPolicy ${name} in namespace ${namespace}`); + } catch (createError) { + console.error( + `✗ Failed to create/update NetworkPolicy ${name} in namespace ${namespace}:`, + createError instanceof Error ? createError.message : createError, + ); + throw createError; + } + } + } + /** * Delete a namespace and wait for it to be fully terminated */ From 747ec741273dd62acb3c2280ca0bb1463f9eae17 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 11:14:20 +0200 Subject: [PATCH 02/12] chore: bump version to 2.1.20 Assisted-by: Claude --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bc6d6b..6269799 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@red-hat-developer-hub/e2e-test-utils", - "version": "2.1.19", + "version": "2.1.20", "description": "Test utilities for RHDH E2E tests", "license": "Apache-2.0", "repository": { From 8d2dc9549bbbbb8d707fef71f58f6bfd55e2f4dd Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 11:16:46 +0200 Subject: [PATCH 03/12] docs: add changelog entry for 2.1.20 Assisted-by: Claude --- docs/changelog.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index b1444fd..072f5cc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,17 @@ All notable changes to this project will be documented in this file. -## [2.1.19] - Current +## [2.1.20] - Current + +### Fixed + +- **NetworkPolicy for Keycloak HTTP egress**: The RHDH chart 2.y default-deny NetworkPolicies block port 80 egress, but Keycloak in CI uses a plain HTTP route. Added an external NetworkPolicy after deployment that allows TCP port 80 egress to in-cluster destinations, fixing OIDC discovery timeouts. This is temporary until the chart allows port 80 egress out of the box. + +### Added + +- **`applyNetworkPolicy` in `KubernetesClientHelper`**: New method to create or update NetworkPolicies using the Kubernetes client API (same pattern as ConfigMap/Secret). + +## [2.1.19] ### Changed From f3c38e832b6365e7aec65aa113fcddf017b5b8b3 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 11:19:03 +0200 Subject: [PATCH 04/12] fix: suppress naming-convention lint for Kubernetes label key Assisted-by: Claude --- src/deployment/rhdh/deployment.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index 3cbfe17..33ecdc8 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -305,6 +305,7 @@ export class RHDHDeployment { spec: { podSelector: { matchLabels: { + // eslint-disable-next-line @typescript-eslint/naming-convention -- standard Kubernetes recommended label; dots/slashes violate camelCase rule "app.kubernetes.io/component": "backstage", }, }, From 077bf6680a85811c123ab721caf890e70cb2ebde Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 11:36:41 +0200 Subject: [PATCH 05/12] fix: prettier formatting in kubernetes-client.ts Assisted-by: Claude --- src/utils/kubernetes-client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/kubernetes-client.ts b/src/utils/kubernetes-client.ts index 3ff6786..07d3b98 100644 --- a/src/utils/kubernetes-client.ts +++ b/src/utils/kubernetes-client.ts @@ -331,7 +331,9 @@ class KubernetesClientHelper { namespace, body: policy, }); - console.log(`✓ Created NetworkPolicy ${name} in namespace ${namespace}`); + console.log( + `✓ Created NetworkPolicy ${name} in namespace ${namespace}`, + ); } catch (createError) { console.error( `✗ Failed to create/update NetworkPolicy ${name} in namespace ${namespace}:`, From cd45ca803f6cbab1ff1c64aa6b13a1fa0a8c8b6b Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 11:51:17 +0200 Subject: [PATCH 06/12] fix: use correct pod selector labels in NP per deployment method Match the chart's selectorLabels (name + instance + component) for Helm deployments and the operator's rhdh.redhat.com/app label for operator deployments, instead of only matching app.kubernetes.io/component. Assisted-by: Claude --- src/deployment/rhdh/deployment.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index 33ecdc8..f0a7a43 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -294,6 +294,20 @@ export class RHDHDeployment { */ private async _applyNetworkPolicies(): Promise { const namespace = this.deploymentConfig.namespace; + + const matchLabels: Record = + this.deploymentConfig.method === "operator" + ? // eslint-disable-next-line @typescript-eslint/naming-convention -- standard operator label; dots/slashes violate camelCase rule + { "rhdh.redhat.com/app": "backstage-developer-hub" } + : { + // eslint-disable-next-line @typescript-eslint/naming-convention -- standard Kubernetes recommended labels; dots/slashes violate camelCase rule + "app.kubernetes.io/name": "developer-hub", + // eslint-disable-next-line @typescript-eslint/naming-convention -- standard Kubernetes recommended labels; dots/slashes violate camelCase rule + "app.kubernetes.io/instance": "redhat-developer-hub", + // eslint-disable-next-line @typescript-eslint/naming-convention -- standard Kubernetes recommended labels; dots/slashes violate camelCase rule + "app.kubernetes.io/component": "backstage", + }; + await this.k8sClient.applyNetworkPolicy( { apiVersion: "networking.k8s.io/v1", @@ -303,12 +317,7 @@ export class RHDHDeployment { namespace, }, spec: { - podSelector: { - matchLabels: { - // eslint-disable-next-line @typescript-eslint/naming-convention -- standard Kubernetes recommended label; dots/slashes violate camelCase rule - "app.kubernetes.io/component": "backstage", - }, - }, + podSelector: { matchLabels }, policyTypes: ["Egress"], egress: [ { From d170c74327fd5b1e742bd4656cfbdf575d321d28 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 12:23:19 +0200 Subject: [PATCH 07/12] fix: drop namespaceSelector from port 80 NP Keycloak is accessed via an OpenShift Route (plain HTTP), which resolves to a router/LB IP. namespaceSelector only matches pod IPs, so it would not cover Route-based traffic. It would only work if RHDH accessed Keycloak via the internal Service DNS name. Assisted-by: Claude --- src/deployment/rhdh/deployment.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index f0a7a43..5c63ad6 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -319,12 +319,11 @@ export class RHDHDeployment { spec: { podSelector: { matchLabels }, policyTypes: ["Egress"], - egress: [ - { - to: [{ namespaceSelector: {} }], - ports: [{ port: 80, protocol: "TCP" }], - }, - ], + // No 'to' selector: Keycloak is accessed via an OpenShift Route + // (plain HTTP), which resolves to a router/LB IP — not a pod IP — + // so namespaceSelector would only work if accessed via the internal + // Service DNS name (e.g., keycloak.rhdh-keycloak.svc.cluster.local). + egress: [{ ports: [{ port: 80, protocol: "TCP" }] }], }, }, namespace, From 23046a42f83d08765ac3665e2fcca2c274b629bc Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 12:25:31 +0200 Subject: [PATCH 08/12] docs: update JSDoc to explain why broad port 80 egress is needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chart's OOTB port 80 NP uses namespaceSelector (in-cluster only), but Keycloak is accessed via the Route URL which resolves to a router/LB IP. This NP is not temporary — it remains necessary. Assisted-by: Claude --- src/deployment/rhdh/deployment.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index 5c63ad6..4fc2d13 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -288,9 +288,9 @@ export class RHDHDeployment { /** * Apply NetworkPolicies that the chart's default-deny rules don't cover. * The chart allows egress on 443 (HTTPS), 53/5353 (DNS), 5432 (PostgreSQL), - * and 6379 (Redis), but Keycloak in CI uses a plain HTTP route (port 80). - * - * TODO: Remove once the RHDH chart allows port 80 egress out of the box. + * and 6379 (Redis). Keycloak in CI uses a plain HTTP route (port 80) + * accessed via the external Route URL (router/LB IP), so the chart's + * in-cluster-only port 80 NP (namespaceSelector) does not cover it. */ private async _applyNetworkPolicies(): Promise { const namespace = this.deploymentConfig.namespace; From 3aef8623d2886b433febcc5b491131070d756c6d Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 14:11:25 +0200 Subject: [PATCH 09/12] Update docs/changelog.md --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 072f5cc..bae4ded 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. ### Fixed -- **NetworkPolicy for Keycloak HTTP egress**: The RHDH chart 2.y default-deny NetworkPolicies block port 80 egress, but Keycloak in CI uses a plain HTTP route. Added an external NetworkPolicy after deployment that allows TCP port 80 egress to in-cluster destinations, fixing OIDC discovery timeouts. This is temporary until the chart allows port 80 egress out of the box. +- **NetworkPolicy for Keycloak HTTP egress**: The RHDH chart 2.y default-deny NetworkPolicies block port 80 egress, but Keycloak in CI uses a plain HTTP route. Added an external NetworkPolicy after deployment that allows broad TCP port 80 egress to any destination, fixing OIDC discovery timeouts. ### Added From e1f763498af2d75ea594652f816e3d8cfb522bf4 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 15:03:51 +0200 Subject: [PATCH 10/12] temp: allow all egress to baseline NP impact on CI failures Assisted-by: Claude --- src/deployment/rhdh/deployment.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index 4fc2d13..98877a4 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -317,13 +317,10 @@ export class RHDHDeployment { namespace, }, spec: { + // TODO: narrow back to port 80 once baseline confirms no other NP gaps podSelector: { matchLabels }, policyTypes: ["Egress"], - // No 'to' selector: Keycloak is accessed via an OpenShift Route - // (plain HTTP), which resolves to a router/LB IP — not a pod IP — - // so namespaceSelector would only work if accessed via the internal - // Service DNS name (e.g., keycloak.rhdh-keycloak.svc.cluster.local). - egress: [{ ports: [{ port: 80, protocol: "TCP" }] }], + egress: [{}], }, }, namespace, From 8f08b8954dd2336af4dfaae7df9e82b1854ed9a7 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 17:43:25 +0200 Subject: [PATCH 11/12] fix: allow all egress from RHDH pods in CI The chart's default-deny NPs only allow egress on a few specific ports. E2E tests need access to many other services (Keycloak HTTP routes, ArgoCD, external APIs), so allow all egress in CI rather than chasing individual port gaps. Assisted-by: Claude --- src/deployment/rhdh/deployment.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index 98877a4..a7d1b55 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -287,10 +287,10 @@ export class RHDHDeployment { /** * Apply NetworkPolicies that the chart's default-deny rules don't cover. - * The chart allows egress on 443 (HTTPS), 53/5353 (DNS), 5432 (PostgreSQL), - * and 6379 (Redis). Keycloak in CI uses a plain HTTP route (port 80) - * accessed via the external Route URL (router/LB IP), so the chart's - * in-cluster-only port 80 NP (namespaceSelector) does not cover it. + * The chart ships default-deny NPs allowing egress only on specific ports + * (443, 53/5353, 5432, 6379). E2E tests talk to services on many other + * ports (Keycloak HTTP routes, ArgoCD, external APIs), so we allow all + * egress from the RHDH pods in CI. */ private async _applyNetworkPolicies(): Promise { const namespace = this.deploymentConfig.namespace; @@ -313,11 +313,11 @@ export class RHDHDeployment { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy", metadata: { - name: "rhdh-allow-http-egress", + name: "rhdh-allow-all-egress", namespace, }, spec: { - // TODO: narrow back to port 80 once baseline confirms no other NP gaps + // TODO: narrow to explicit ports once all required destinations are mapped podSelector: { matchLabels }, policyTypes: ["Egress"], egress: [{}], @@ -325,7 +325,7 @@ export class RHDHDeployment { }, namespace, ); - this._log("Applied NetworkPolicy: rhdh-allow-http-egress"); + this._log("Applied NetworkPolicy: rhdh-allow-all-egress"); } private async _deployWithOperator(subscription: string): Promise { From dc9ef8ae0119b413ec9bf6fc83d72db1561df5f1 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 24 Sep 2026 17:45:57 +0200 Subject: [PATCH 12/12] docs: update changelog to reflect allow-all-egress approach Assisted-by: Claude --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index bae4ded..64b0d00 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. ### Fixed -- **NetworkPolicy for Keycloak HTTP egress**: The RHDH chart 2.y default-deny NetworkPolicies block port 80 egress, but Keycloak in CI uses a plain HTTP route. Added an external NetworkPolicy after deployment that allows broad TCP port 80 egress to any destination, fixing OIDC discovery timeouts. +- **NetworkPolicy for CI egress**: The RHDH chart 2.y ships default-deny NetworkPolicies that only allow egress on specific ports (443, 53/5353, 5432, 6379). E2E tests need access to many other services (Keycloak HTTP routes, ArgoCD, external APIs), so we now apply a permissive `rhdh-allow-all-egress` NetworkPolicy after deployment. A TODO is kept to narrow this to explicit ports once all required destinations are mapped. ### Added