From 1ec4d079391a840caee3849237f545e067efcf9a Mon Sep 17 00:00:00 2001 From: keyhan Date: Thu, 9 Jul 2026 18:07:48 +0330 Subject: [PATCH] Add egress proxy to user-app Kaniko build jobs. Inject registry-egress-proxy into Kaniko and network init containers so npm/apk/composer/pip/git clone work on restricted egress clusters. Co-authored-by: Cursor --- .../cloudhost-platform/templates/_helpers.tpl | 4 +++ backend/helm/cloudhost-platform/values.yaml | 3 ++ backend/src/build/build.service.spec.ts | 25 +++++++++++++++++ backend/src/build/build.service.ts | 28 +++++++++++++++---- backend/src/config/configuration.ts | 6 ++++ gitops/platform/values-abrban.example.yaml | 2 ++ 6 files changed, 62 insertions(+), 6 deletions(-) diff --git a/backend/helm/cloudhost-platform/templates/_helpers.tpl b/backend/helm/cloudhost-platform/templates/_helpers.tpl index f0539af..e42ecfb 100644 --- a/backend/helm/cloudhost-platform/templates/_helpers.tpl +++ b/backend/helm/cloudhost-platform/templates/_helpers.tpl @@ -123,4 +123,8 @@ PLATFORM_DOMAIN / preview domain from the first entry only. The panel host value: {{ .Values.build.images.alpineGit | quote }} - name: BASE_IMAGE_REGISTRY value: {{ .Values.build.baseImageRegistry | quote }} +{{- if .Values.build.egressProxySecret }} +- name: BUILD_EGRESS_PROXY_SECRET + value: {{ .Values.build.egressProxySecret | quote }} +{{- end }} {{- end }} diff --git a/backend/helm/cloudhost-platform/values.yaml b/backend/helm/cloudhost-platform/values.yaml index f96a78e..e62ed31 100644 --- a/backend/helm/cloudhost-platform/values.yaml +++ b/backend/helm/cloudhost-platform/values.yaml @@ -37,6 +37,9 @@ build: alpineGit: registry.abrban.com/proxy-dockerhub/alpine/git:2.43.0 # Prefix for Docker Hub images in generated user-app Dockerfiles (node, php, …) baseImageRegistry: registry.abrban.com/proxy-dockerhub/library + # Secret with HTTP_PROXY/HTTPS_PROXY for Kaniko build jobs (npm, apk, git clone). + # Set to registry-egress-proxy in production; leave empty when nodes have direct egress. + egressProxySecret: "" postgres: enabled: true diff --git a/backend/src/build/build.service.spec.ts b/backend/src/build/build.service.spec.ts index 08cff71..dc07854 100644 --- a/backend/src/build/build.service.spec.ts +++ b/backend/src/build/build.service.spec.ts @@ -137,4 +137,29 @@ describe('BuildService', () => { expect(dockerfile).toContain('dotnet publish "$CSPROJ"'); }); }); + + describe('egressProxyEnvFrom', () => { + it('returns secretRef when BUILD_EGRESS_PROXY_SECRET is set', () => { + const config = (service as any).configService as { get: jest.Mock }; + config.get.mockImplementation((key: string) => { + if (key === 'build.egressProxySecret') return 'registry-egress-proxy'; + return undefined; + }); + + expect((service as any).egressProxyEnvFrom()).toEqual([ + { secretRef: { name: 'registry-egress-proxy' } }, + ]); + }); + + it('returns undefined when egress proxy is disabled', () => { + const config = (service as any).configService as { get: jest.Mock }; + config.get.mockImplementation((key: string) => { + if (key === 'build.egressProxySecret') return ''; + return undefined; + }); + + expect((service as any).egressProxyEnvFrom()).toBeUndefined(); + expect((service as any).withEgressProxy({ name: 'kaniko' })).toEqual({ name: 'kaniko' }); + }); + }); }); diff --git a/backend/src/build/build.service.ts b/backend/src/build/build.service.ts index 06d60d8..0f3556a 100644 --- a/backend/src/build/build.service.ts +++ b/backend/src/build/build.service.ts @@ -91,6 +91,22 @@ export class BuildService { return this.baseImage(dockerHubFallback); } + /** + * Egress HTTP(S) proxy for build pods on restricted networks (Iran). + * Kaniko forwards these env vars into Dockerfile RUN steps (npm, apk, composer, pip). + */ + private egressProxyEnvFrom(): k8s.V1EnvFromSource[] | undefined { + const secretName = this.configService.get('build.egressProxySecret'); + if (!secretName?.trim()) return undefined; + return [{ secretRef: { name: secretName.trim() } }]; + } + + private withEgressProxy>(container: T): T { + const envFrom = this.egressProxyEnvFrom(); + if (!envFrom) return container; + return { ...container, envFrom }; + } + /** * Git branch names come from users and end up in a shell command — accept * only conservative ref characters and reject anything option-like. @@ -552,7 +568,7 @@ export class BuildService { }); // Add init container that unzips the source code from PVC - initContainers.push({ + initContainers.push(this.withEgressProxy({ name: 'unzip-source', image: this.resolveBuildImage('alpine', 'alpine:3.19'), imagePullPolicy: 'IfNotPresent', @@ -607,7 +623,7 @@ export class BuildService { }, { name: 'source-pvc', mountPath: '/source-pvc' }, ], - }); + })); } else if (hasGitUrl) { // Validate user-controlled values before they get anywhere near a shell. this.assertSafeGitUrl(app.gitUrl!); @@ -632,7 +648,7 @@ export class BuildService { } // Clone git repo into /workspace/source, then copy our generated Dockerfile - initContainers.push({ + initContainers.push(this.withEgressProxy({ name: 'git-clone', image: this.resolveBuildImage('alpineGit', 'alpine/git:2.43.0'), imagePullPolicy: 'IfNotPresent', @@ -670,7 +686,7 @@ export class BuildService { { name: 'workspace', mountPath: '/workspace-out' }, { name: 'dockerfile', mountPath: '/dockerfile' }, ], - }); + })); } // Kaniko container volume mounts @@ -719,7 +735,7 @@ export class BuildService { serviceAccountName: this.configService.get('build.serviceAccount'), initContainers: initContainers.length > 0 ? initContainers : undefined, containers: [ - { + this.withEgressProxy({ name: 'kaniko', image: this.getKanikoImage(), imagePullPolicy: 'IfNotPresent', @@ -735,7 +751,7 @@ export class BuildService { memory: this.configService.get('build.kaniko.memoryLimit') || '4Gi', }, }, - }, + }), ], restartPolicy: 'Never', volumes, diff --git a/backend/src/config/configuration.ts b/backend/src/config/configuration.ts index c2d8496..a1b4471 100644 --- a/backend/src/config/configuration.ts +++ b/backend/src/config/configuration.ts @@ -150,6 +150,12 @@ export default () => ({ alpine: (process.env.BUILD_ALPINE_IMAGE || 'registry.abrban.com/proxy-dockerhub/library/alpine:3.19').trim(), alpineGit: (process.env.BUILD_ALPINE_GIT_IMAGE || 'registry.abrban.com/proxy-dockerhub/alpine/git:2.43.0').trim(), }, + /** + * Secret name with HTTP_PROXY / HTTPS_PROXY / NO_PROXY for build pods + * (Kaniko RUN steps: npm, apk, composer, pip; init containers: apk, git clone). + * Empty = disabled (clusters with direct egress). + */ + egressProxySecret: (process.env.BUILD_EGRESS_PROXY_SECRET || '').trim(), /** Kaniko build container resources — tune for large images. */ kaniko: { cpuRequest: process.env.KANIKO_CPU_REQUEST || '500m', diff --git a/gitops/platform/values-abrban.example.yaml b/gitops/platform/values-abrban.example.yaml index 2d4bd5c..2a82d9c 100644 --- a/gitops/platform/values-abrban.example.yaml +++ b/gitops/platform/values-abrban.example.yaml @@ -35,6 +35,8 @@ build: alpine: registry.abrban.com/proxy-dockerhub/library/alpine:3.19 alpineGit: registry.abrban.com/proxy-dockerhub/alpine/git:2.43.0 baseImageRegistry: registry.abrban.com/proxy-dockerhub/library + # Kaniko + init containers (npm/apk/composer/pip/git clone) on restricted egress. + egressProxySecret: registry-egress-proxy postgres: enabled: true