Skip to content

Conversation

lyoung-confluent
Copy link
Contributor

Description

In #6070 a change was made inject a "fake" USER <name> line when reconstructing the Dockerfile from an OCI/Docker image manifest/config.

However, it only used this value when a USER <name> directive was not found in the history. This is incorrect, in practice the Config.User value always takes precedence/is used by a container runtime. This PR ensures that if a value is configured, it is always appended to the reconstructed history.

We ran into this because we had an odd image which contained USER root in the Dockerfile history but was actually configured to run as nonroot in the final rendered image config (not built via Buildkit).

Related issues

I didn't create an issue for this, just the fix PR.

Related PRs

Checklist

  • I've read the guidelines for contributing to this repository.
  • I've followed the conventions in the PR title.
  • I've added tests that prove my fix is effective or that my feature works.
  • I've updated the documentation with the relevant information (if needed).
  • I've added usage information (if the PR introduces new options)
  • I've included a "before" and "after" example to the description (if the PR is a user interface change).

@nikpivkin
Copy link
Contributor

nikpivkin commented Jun 19, 2025

I think this is possible if the config was changed manually.

FROM alpine:latest

RUN adduser -D builder

USER builder
RUN echo "Current user in RUN:" && whoami

USER root
RUN echo "Current user in RUN:" && whoami

CMD ["sh", "-c", "echo Current user in container: $(whoami)"]

Steps to reproduce:

docker build -t example-root-last-user .
❯ docker run --rm example-root-last-user
Current user in container: root
❯ docker history example-root-last-user
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
faead2736fe4   20 seconds ago   CMD ["sh" "-c" "echo Current user in contain…   0B        buildkit.dockerfile.v0
<missing>      20 seconds ago   RUN /bin/sh -c echo "Current user in RUN:" &…   0B        buildkit.dockerfile.v0
<missing>      20 seconds ago   USER root                                       0B        buildkit.dockerfile.v0
<missing>      20 seconds ago   RUN /bin/sh -c echo "Current user in RUN:" &…   0B        buildkit.dockerfile.v0
<missing>      20 seconds ago   USER builder                                    0B        buildkit.dockerfile.v0
<missing>      25 minutes ago   RUN /bin/sh -c adduser -D builder # buildkit    3.03kB    buildkit.dockerfile.v0
<missing>      4 months ago     CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
<missing>      4 months ago     ADD alpine-minirootfs-3.21.3-aarch64.tar.gz …   8.17MB    buildkit.dockerfile.v0
❯ docker inspect example-root-last-user --format '{{.Config.User}}'
root
❯ tar -xf example.tar -C example_unpack
❯ cat example_unpack/manifest.json| jq '.[0].Config'
"blobs/sha256/faead2736fe4759e3f17c21690b7910214e8896047f9f38feadda090a0a3d53a"
# patch config and change `root` to `builder`
❯ tar -cf example_modified.tar -C example_unpack .
❯ docker load -i example_modified.tar
The image example-root-last-user:latest already exists, renaming the old one with ID sha256:faead2736fe4759e3f17c21690b7910214e8896047f9f38feadda090a0a3d53a to empty string
Loaded image: example-root-last-user:latest
❯ docker run --rm example-root-last-user
Current user in container: builder
❯ trivy image --scanners vuln --image-config-scanners misconfig example-root-last-user -q

Report Summary

┌────────────────────────────────────────┬────────────┬─────────────────┐
│                 Target                 │    Type    │ Vulnerabilities │
├────────────────────────────────────────┼────────────┼─────────────────┤
│ example-root-last-user (alpine 3.21.3) │   alpine   │        0        │
├────────────────────────────────────────┼────────────┼─────────────────┤
│ example-root-last-user                 │ dockerfile │        -        │
└────────────────────────────────────────┴────────────┴─────────────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)


example-root-last-user (dockerfile)

Tests: 27 (SUCCESSES: 25, FAILURES: 2)
Failures: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 1, CRITICAL: 0)

AVD-DS-0002 (HIGH): Last USER command in Dockerfile should not be 'root'
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.

See https://avd.aquasec.com/misconfig/ds002
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 example-root-last-user:4
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   4 [ USER root
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

@DmitriyLewen
Copy link
Contributor

config was changed manually.

Using --user builder(see description of #6070) and fixing this container as an image shows a similar result.

@@ -119,7 +117,7 @@ func imageConfigToDockerfile(cfg *v1.ConfigFile) []byte {
dockerfile.WriteString(strings.TrimSpace(createdBy) + "\n")
}

if !userFound && cfg.Config.User != "" {
if cfg.Config.User != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little worried that we'll add another user to the resulting dockerfile.
e.g. for this example result Dockerfile will be:

...
USER builder
USER root
...
USER builder

@nikpivkin Is this okay for misconfig scan?
Will we get some unexpected result for one of the rules?

Copy link
Contributor Author

@lyoung-confluent lyoung-confluent Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already valid to have multiple USER lines in a Dockerfile, for example this is commonly used to "sudo" and run a command during a build, ex:

FROM some-image-that-uses-non-root-already

USER root
RUN apt install foobar
USER nonroot

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, you are right.
i confused myself 😄
I thought about this case again, we inserted the user on the last line, and the dockerfile supports multiple "USER" lines

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this okay for misconfig scan?

Yeah, that's okay.

Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>
Copy link
Contributor

@DmitriyLewen DmitriyLewen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lyoung-confluent Thanks for your contribution!

@DmitriyLewen DmitriyLewen added this pull request to the merge queue Jun 19, 2025
Merged via the queue into aquasecurity:main with commit 371b8cc Jun 19, 2025
12 checks passed
@nikpivkin
Copy link
Contributor

Using --user builder(see description of #6070) and fixing this container as an image shows a similar result.

Interesting, I hadn't heard of the commit command before.

alexlebens pushed a commit to alexlebens/infrastructure that referenced this pull request Jul 5, 2025
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [mirror.gcr.io/aquasec/trivy](https://www.aquasec.com/products/trivy/) ([source](https://github.com/aquasecurity/trivy)) | minor | `0.63.0` -> `0.64.1` |

---

### Release Notes

<details>
<summary>aquasecurity/trivy (mirror.gcr.io/aquasec/trivy)</summary>

### [`v0.64.1`](https://github.com/aquasecurity/trivy/releases/tag/v0.64.1)

[Compare Source](aquasecurity/trivy@v0.64.0...v0.64.1)

#### Changelog

- [`86ee3c1`](aquasecurity/trivy@86ee3c1) release: v0.64.1 \[release/v0.64] ([#&#8203;9122](aquasecurity/trivy#9122))
- [`4e12722`](aquasecurity/trivy@4e12722) fix(misconf): skip rewriting expr if attr is nil \[backport: release/v0.64] ([#&#8203;9127](aquasecurity/trivy#9127))
- [`9a7d384`](aquasecurity/trivy@9a7d384) fix(cli): Add more non-sensitive flags to telemetry \[backport: release/v0.64] ([#&#8203;9124](aquasecurity/trivy#9124))
- [`53adfba`](aquasecurity/trivy@53adfba) fix(rootio): check full version to detect `root.io` packages \[backport: release/v0.64] ([#&#8203;9120](aquasecurity/trivy#9120))
- [`8cf1bf9`](aquasecurity/trivy@8cf1bf9) fix(alma): parse epochs from rpmqa file \[backport: release/v0.64] ([#&#8203;9119](aquasecurity/trivy#9119))

### [`v0.64.0`](https://github.com/aquasecurity/trivy/blob/HEAD/CHANGELOG.md#0640-2025-06-30)

[Compare Source](aquasecurity/trivy@v0.63.0...v0.64.0)

##### Features

- **cli:** add version constraints to annoucements ([#&#8203;9023](aquasecurity/trivy#9023)) ([19efa9f](aquasecurity/trivy@19efa9f))
- **java:** dereference all maven settings.xml env placeholders ([#&#8203;9024](aquasecurity/trivy#9024)) ([5aade69](aquasecurity/trivy@5aade69))
- **misconf:** add OpenTofu file extension support ([#&#8203;8747](aquasecurity/trivy#8747)) ([57801d0](aquasecurity/trivy@57801d0))
- **misconf:** normalize CreatedBy for buildah and legacy docker builder ([#&#8203;8953](aquasecurity/trivy#8953)) ([65e155f](aquasecurity/trivy@65e155f))
- **redhat:** Add EOL date for RHEL 10. ([#&#8203;8910](aquasecurity/trivy#8910)) ([48258a7](aquasecurity/trivy@48258a7))
- reject unsupported artifact types in remote image retrieval ([#&#8203;9052](aquasecurity/trivy#9052)) ([1e1e1b5](aquasecurity/trivy@1e1e1b5))
- **sbom:** add manufacturer field to CycloneDX tools metadata ([#&#8203;9019](aquasecurity/trivy#9019)) ([41d0f94](aquasecurity/trivy@41d0f94))
- **terraform:** add partial evaluation for policy templates ([#&#8203;8967](aquasecurity/trivy#8967)) ([a9f7dcd](aquasecurity/trivy@a9f7dcd))
- **ubuntu:** add end of life date for Ubuntu 25.04 ([#&#8203;9077](aquasecurity/trivy#9077)) ([367564a](aquasecurity/trivy@367564a))
- **ubuntu:** add eol date for 20.04-ESM ([#&#8203;8981](aquasecurity/trivy#8981)) ([87118a0](aquasecurity/trivy@87118a0))
- **vuln:** add Root.io support for container image scanning ([#&#8203;9073](aquasecurity/trivy#9073)) ([3a0ec0f](aquasecurity/trivy@3a0ec0f))

##### Bug Fixes

- Add missing version check flags ([#&#8203;8951](aquasecurity/trivy#8951)) ([ef5f8de](aquasecurity/trivy@ef5f8de))
- **cli:** add some values to the telemetry call ([#&#8203;9056](aquasecurity/trivy#9056)) ([fd2bc91](aquasecurity/trivy@fd2bc91))
- Correctly check for semver versions for trivy version check ([#&#8203;8948](aquasecurity/trivy#8948)) ([b813527](aquasecurity/trivy@b813527))
- don't show corrupted trivy-db warning for first run ([#&#8203;8991](aquasecurity/trivy#8991)) ([4ed78e3](aquasecurity/trivy@4ed78e3))
- **misconf:** .Config.User always takes precedence over USER in .History ([#&#8203;9050](aquasecurity/trivy#9050)) ([371b8cc](aquasecurity/trivy@371b8cc))
- **misconf:** correct Azure value-to-time conversion in AsTimeValue ([#&#8203;9015](aquasecurity/trivy#9015)) ([40d017b](aquasecurity/trivy@40d017b))
- **misconf:** move disabled checks filtering after analyzer scan ([#&#8203;9002](aquasecurity/trivy#9002)) ([a58c36d](aquasecurity/trivy@a58c36d))
- **misconf:** reduce log noise on incompatible check ([#&#8203;9029](aquasecurity/trivy#9029)) ([99c5151](aquasecurity/trivy@99c5151))
- **nodejs:** correctly parse `packages` array of `bun.lock` file ([#&#8203;8998](aquasecurity/trivy#8998)) ([875ec3a](aquasecurity/trivy@875ec3a))
- **report:** don't panic when report contains vulns, but doesn't contain packages for `table` format ([#&#8203;8549](aquasecurity/trivy#8549)) ([87fda76](aquasecurity/trivy@87fda76))
- **sbom:** remove unnecessary OS detection check in SBOM decoding ([#&#8203;9034](aquasecurity/trivy#9034)) ([198789a](aquasecurity/trivy@198789a))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xLjMiLCJ1cGRhdGVkSW5WZXIiOiI0MS4xLjMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImltYWdlIl19-->

Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/812
Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net>
Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants