Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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

- **`applyNetworkPolicy` in `KubernetesClientHelper`**: New method to create or update NetworkPolicies using the Kubernetes client API (same pattern as ConfigMap/Secret).

## [2.1.19]

### Changed

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
44 changes: 44 additions & 0 deletions src/deployment/rhdh/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export class RHDHDeployment {
await this._applyDynamicPlugins();
await this._deployWithOperator(this.deploymentConfig.subscription);
}
await this._applyNetworkPolicies();
await this.waitUntilReady();
},
);
Expand Down Expand Up @@ -284,6 +285,49 @@ export class RHDHDeployment {
this._log(`Helm deployment completed successfully`);
}

/**
* Apply NetworkPolicies that the chart's default-deny rules don't cover.
* 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<void> {
const namespace = this.deploymentConfig.namespace;

const matchLabels: Record<string, string> =
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",
kind: "NetworkPolicy",
metadata: {
name: "rhdh-allow-all-egress",
namespace,
},
spec: {
// TODO: narrow to explicit ports once all required destinations are mapped
podSelector: { matchLabels },
policyTypes: ["Egress"],
egress: [{}],
},
},
namespace,
);
this._log("Applied NetworkPolicy: rhdh-allow-all-egress");
}

private async _deployWithOperator(subscription: string): Promise<void> {
const subscriptionObject = (await mergeYamlFilesIfExists([
DEFAULT_CONFIG_PATHS.operator.subscription,
Expand Down
33 changes: 33 additions & 0 deletions src/utils/kubernetes-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 (
Expand Down Expand Up @@ -311,6 +313,37 @@ class KubernetesClientHelper {
}
}

async applyNetworkPolicy(
policy: k8s.V1NetworkPolicy,
namespace: string,
): Promise<void> {
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
*/
Expand Down
Loading