Repository navigation
#609 Run the web container as a non-root user - #639
Merged
Merged
Conversation
PR #632 made every file input the GOV.UK enhanced File upload, whose script moves the input's id onto its button, so Page.Locator("#bundle") resolved to a <button> and SetInputFilesAsync failed with "Node is not an HTMLInputElement". Every whole-suite run on main since 16 Oct has reported this one failure. Target the native input by name, as the rest of the suite does. Refs #501, #380 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red on purpose: the Web Dockerfile has no USER line and neither AKS module sets run_as_non_root. The next commits turn it green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
USER $APP_UID in the base stage, as the worker image already does, so the runtime stage CI deploys runs as uid 1654 instead of root. The VS Code debug stage takes root back for its apt-get install and hands it over again after the copy. Nothing the app does at run time writes outside what that user can write: data-protection keys go to blob storage and upload buffering goes to /tmp. Proven locally: id inside the built image is uid=1654(app), /healthcheck answers 200 and the E2E smoke set passes against the container. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both images now carry a numeric USER, so Kubernetes can refuse to start either pod as root. The shared aks/application module exposes the flag on every branch the environments pin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test only looked for "USER $APP_UID" inside base, so a later USER root in base, Web runtime or worker final, or a comment naming the directive, still passed. It now takes the last real USER directive across base plus the shipped stage, matches directives (not comments) in the debug-stage and stage-header regexes, anchors the run_as_non_root check, and the class comment no longer claims a root image starts fine on AKS (it is refused). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review app for PR 639 was deleted |
E2E tests passed358 passed, 0 failed, 2 skipped in 13 min 31 s Commit |
Contributor
Author
|
Review-app proof so far (commit c79b88a):
I don't have cluster access from this machine. @davidgouge, could you run these two commands and paste the output here? They are the direct make review get-cluster-credentials PR_NUMBER=639
kubectl -n schoolstandards-development exec deployment/check-performance-data-pr-639 -- id
kubectl -n schoolstandards-development get pod -l app=check-performance-data-pr-639 -o jsonpath='{.items[0].spec.containers[0].securityContext}'Expected output: |
# Conflicts: # tests/DfE.CheckPerformanceData.E2ETests/Admin/ContentStagingImportProblemsTests.cs
paulc1983
approved these changes
Oct 19, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #609. Part of #607.
Why: least privilege. The worker image already ran as the aspnet image's
appuser; the web image had noUSERline, so it ran as root.What
USER $APP_UIDin the Web Dockerfile'sbasestage. That is uid 1654, numeric so that Kubernetes'runAsNonRootcheck can read it. The VS Codedebug-runtimestage switches to root for its apt-get install and back to the app user afterwards.run_as_non_root = trueonmodule "web_application"andmodule "rules_engine_worker". The vendoredaks/applicationmodule has this variable onmain,testingandstable(checked withgh api:variables.tf:185, wired into the containersecurity_contextatresources.tf:188).ContainerUserSourceTests(unit, source-assertion), which checks that:USERdirective in each shipped stage (Webruntime, workerfinal, bothFROM base) is$APP_UID;basestage declares it;run_as_non_root = true(on an uncommented line).Mutation-checked. Each of these fails the test: a
USER rootadded toruntime, commenting out thebasedirective, and commenting out the Terraform flag.One sentence in
docs/technical-stakeholders-functional-overview.md.Also: a one-line E2E locator fix for
ContentStagingImportProblemsTests. That test comes from 501 Obey the URL segment rules when editing and importing #629, and #380 File uploads use the GOV.UK enhanced control so Dragon users can choose a file #632's enhanced file upload moved thebundleid onto a button, so#bundlebecomesinput[name='bundle']. It has been the one red test on main's whole-suite runs since 16 Oct. It is a separate commit. Refs CMS - segment editing should obey the segment formatting rules #501.Nothing writes to disk at run time:
PersistKeysToAzureBlobStorage; the framework fallback/home/appis owned byapp);/tmp(1777,TMPDIRunset);ContentRootPathuse is a read;/appis root-owned 755 and nothing writes there.Proven locally
docker run --rm --entrypoint id cypd_web:latestgivesuid=1654(app) gid=1654(app) groups=1654(app), andConfig.Useris1654.docker compose exec web idgives the same uid,/healthcheckreturns 200, and the web log has no permission errors.e2e-testscontainer against the non-root web container: Passed 57, Failed 0.ContentStagingImportProblemsTestsfailed before the fix ("Node is not an HTMLInputElement") and passes after it.terraform fmt -check(1.14.5) is clean.Rollback note: after this merges, any web image built before it has no
USER, so redeploying one with the new Terraform fails withCreateContainerConfigError: container has runAsNonRoot and image will run as root. The rolling update keeps the old pods, so this is not an outage. Rolling the web image back past this PR needsrun_as_non_rootreverted in the same deploy.After merge (development): review apps don't enable DfE Analytics federated auth, so the first place the web pod reads the workload-identity token as uid 1654 is development. Once development has deployed, check that BigQuery
web_requestrows still arrive, or that the pod logs show no analytics or credential errors. A failure there would be silent: events dropped, but no outage.Done when (from the issue):
idin the review app's web pod shows uid 1654 (see the comment below once the review app is up);run_as_non_root = true;