From b4a6a0b4db9d789808f0c32b37d7f6e853cb4a1e Mon Sep 17 00:00:00 2001 From: Ciara Stacke Date: Tue, 24 Jun 2025 15:27:13 +0100 Subject: [PATCH 1/3] Build WAF image in pipeline --- .github/workflows/build.yml | 16 +++++++++++----- .github/workflows/ci.yml | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d79025472a..c2c995ad89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,7 +87,7 @@ jobs: token_format: access_token workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY }} service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - if: ${{ github.event_name != 'pull_request' && contains(inputs.image, 'plus') }} + if: ${{ github.event_name != 'pull_request' && (contains(inputs.image, 'plus') || inputs.image == 'plus-waf') }} - name: Login to GAR uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 @@ -95,7 +95,7 @@ jobs: registry: us-docker.pkg.dev username: oauth2accesstoken password: ${{ steps.auth.outputs.access_token }} - if: ${{ github.event_name != 'pull_request' && contains(inputs.image, 'plus') }} + if: ${{ github.event_name != 'pull_request' && (contains(inputs.image, 'plus') || inputs.image == 'plus-waf') }} - name: Docker meta id: meta @@ -106,7 +106,9 @@ jobs: name=ghcr.io/${{ github.repository_owner }}/nginx-gateway-fabric,enable=${{ inputs.image == 'ngf' && github.event_name != 'pull_request' }} name=ghcr.io/${{ github.repository_owner }}/nginx-gateway-fabric/nginx,enable=${{ inputs.image == 'nginx' && github.event_name != 'pull_request' }} name=docker-mgmt.nginx.com/nginx-gateway-fabric/nginx-plus,enable=${{ inputs.image == 'plus' && github.event_name != 'pull_request' }} + name=docker-mgmt.nginx.com/nginx-gateway-fabric/nginx-plus-nap-waf,enable=${{ inputs.image == 'plus-waf' && github.event_name != 'pull_request' }} name=us-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/nginx-gateway-fabric/nginx-plus,enable=${{ inputs.image == 'plus' && github.event_name != 'pull_request' }} + name=us-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/nginx-gateway-fabric/nginx-plus-nap-waf,enable=${{ inputs.image == 'plus-waf' && github.event_name != 'pull_request' }} name=localhost:5000/nginx-gateway-fabric/${{ inputs.image }} flavor: | latest=${{ (inputs.tag != '' && 'true') || 'auto' }} @@ -134,7 +136,7 @@ jobs: - name: Build Docker Image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: - file: build/Dockerfile${{ inputs.image == 'nginx' && '.nginx' || '' }}${{ inputs.image == 'plus' && '.nginxplus' || '' }} + file: build/Dockerfile${{ inputs.image == 'nginx' && '.nginx' || '' }}${{ (inputs.image == 'plus' || inputs.image == 'plus-waf') && '.nginxplus' || '' }} context: "." target: ${{ inputs.image == 'ngf' && 'goreleaser' || '' }} tags: ${{ steps.meta.outputs.tags }} @@ -146,17 +148,20 @@ jobs: cache-to: type=gha,scope=${{ inputs.image }},mode=max pull: true no-cache: ${{ github.event_name != 'pull_request' }} - sbom: true + sbom: ${{ inputs.image != 'plus-waf' }} provenance: mode=max build-args: | NJS_DIR=internal/controller/nginx/modules/src NGINX_CONF_DIR=internal/controller/nginx/conf BUILD_AGENT=gha + ${{ inputs.image == 'plus-waf' && 'ALPINE_VERSION=3.19' || '' }} + ${{ inputs.image == 'plus-waf' && 'INCLUDE_NAP_WAF=true' || '' }} secrets: | ${{ contains(inputs.image, 'plus') && format('"nginx-repo.crt={0}"', secrets.NGINX_CRT) || '' }} ${{ contains(inputs.image, 'plus') && format('"nginx-repo.key={0}"', secrets.NGINX_KEY) || '' }} - name: Inspect SBOM and output manifest + if: ${{ inputs.image != 'plus-waf' }} run: | docker buildx imagetools inspect localhost:5000/nginx-gateway-fabric/${{ inputs.image }}:${{ steps.meta.outputs.version }} --format '{{ json (index .SBOM "linux/amd64").SPDX }}' > sbom-${{ inputs.image }}.json docker buildx imagetools inspect localhost:5000/nginx-gateway-fabric/${{ inputs.image }}:${{ steps.meta.outputs.version }} --raw @@ -169,6 +174,7 @@ jobs: only-fixed: true add-cpes-if-none: true fail-build: false + if: inputs.image != 'plus-waf' - name: Upload scan result to GitHub Security tab uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 @@ -176,4 +182,4 @@ jobs: with: sarif_file: ${{ steps.scan.outputs.sarif }} category: build-${{ inputs.image }} - if: always() + if: always() && inputs.image != 'plus-waf' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfdc580e66..3e195639e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -222,6 +222,20 @@ jobs: id-token: write # for docker/login to login to NGINX registry secrets: inherit + build-plus-waf: + name: Build Plus WAF images + needs: [vars, binary] + uses: ./.github/workflows/build.yml + with: + image: plus-waf + platforms: "linux/amd64" + permissions: + contents: read # for docker/build-push-action to read repo content + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + packages: write # for docker/build-push-action to push to GHCR + id-token: write # for docker/login to login to NGINX registry + secrets: inherit + functional-tests: name: Functional tests needs: [vars, build-oss, build-plus] From e40b61f1407076eaf08c949ae43c1fc07bf5a417 Mon Sep 17 00:00:00 2001 From: Ciara Stacke Date: Mon, 30 Jun 2025 15:47:35 +0100 Subject: [PATCH 2/3] Use syft for SBOM when image is nap-waf --- .github/workflows/build.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2c995ad89..647e9f4f40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -148,7 +148,7 @@ jobs: cache-to: type=gha,scope=${{ inputs.image }},mode=max pull: true no-cache: ${{ github.event_name != 'pull_request' }} - sbom: ${{ inputs.image != 'plus-waf' }} + sbom: true provenance: mode=max build-args: | NJS_DIR=internal/controller/nginx/modules/src @@ -161,9 +161,23 @@ jobs: ${{ contains(inputs.image, 'plus') && format('"nginx-repo.key={0}"', secrets.NGINX_KEY) || '' }} - name: Inspect SBOM and output manifest - if: ${{ inputs.image != 'plus-waf' }} run: | + if [[ "${{ inputs.image }}" == "plus-waf" ]]; then + # For plus-waf, use syft directly + echo "Generating SBOM for plus-waf using syft..." + + # Install syft if not available + if ! command -v syft >/dev/null 2>&1; then + curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin + fi + + # Generate SBOM using syft directly for plus-waf (known to work with NAP WAF) + syft localhost:5000/nginx-gateway-fabric/${{ inputs.image }}:${{ steps.meta.outputs.version }} -o spdx-json > sbom-${{ inputs.image }}.json + echo "Generated SBOM using syft for plus-waf" + else + # For other images, use the standard Docker buildx approach docker buildx imagetools inspect localhost:5000/nginx-gateway-fabric/${{ inputs.image }}:${{ steps.meta.outputs.version }} --format '{{ json (index .SBOM "linux/amd64").SPDX }}' > sbom-${{ inputs.image }}.json + fi docker buildx imagetools inspect localhost:5000/nginx-gateway-fabric/${{ inputs.image }}:${{ steps.meta.outputs.version }} --raw - name: Scan SBOM @@ -174,7 +188,6 @@ jobs: only-fixed: true add-cpes-if-none: true fail-build: false - if: inputs.image != 'plus-waf' - name: Upload scan result to GitHub Security tab uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 @@ -182,4 +195,4 @@ jobs: with: sarif_file: ${{ steps.scan.outputs.sarif }} category: build-${{ inputs.image }} - if: always() && inputs.image != 'plus-waf' + if: always() && steps.scan.conclusion == 'success' From 6c04c85e24a04e5ffc796a0f9dffa7ed8c87626b Mon Sep 17 00:00:00 2001 From: Ciara Stacke Date: Tue, 1 Jul 2025 11:04:04 +0100 Subject: [PATCH 3/3] Add default NAP WAF image to provisioner --- internal/controller/provisioner/objects.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/controller/provisioner/objects.go b/internal/controller/provisioner/objects.go index 9cd5702d32..8590a779b0 100644 --- a/internal/controller/provisioner/objects.go +++ b/internal/controller/provisioner/objects.go @@ -33,9 +33,10 @@ const ( defaultServiceType = corev1.ServiceTypeLoadBalancer defaultServicePolicy = corev1.ServiceExternalTrafficPolicyLocal - defaultNginxImagePath = "ghcr.io/nginx/nginx-gateway-fabric/nginx" - defaultNginxPlusImagePath = "private-registry.nginx.com/nginx-gateway-fabric/nginx-plus" - defaultImagePullPolicy = corev1.PullIfNotPresent + defaultNginxImagePath = "ghcr.io/nginx/nginx-gateway-fabric/nginx" + defaultNginxPlusImagePath = "private-registry.nginx.com/nginx-gateway-fabric/nginx-plus" + defaultNginxPlusWafImagePath = "private-registry.nginx.com/nginx-gateway-fabric/nginx-plus-nap-waf" + defaultImagePullPolicy = corev1.PullIfNotPresent // WAF container defaults. defaultWAFEnforcerImagePath = "private-registry.nginx.com/nap/waf-enforcer" @@ -988,6 +989,10 @@ func (p *NginxProvisioner) buildImage(nProxyCfg *graph.EffectiveNginxProxy) (str image = defaultNginxPlusImagePath } + if graph.WAFEnabledForNginxProxy(nProxyCfg) { + image = defaultNginxPlusWafImagePath + } + getImageAndPullPolicy := func(container ngfAPIv1alpha2.ContainerSpec) (string, string, corev1.PullPolicy) { if container.Image != nil { if container.Image.Repository != nil {