markhoerth opened a new pull request, #12446:
URL: https://github.com/apache/gravitino/pull/12446

   ### What changes were proposed in this pull request?
   
   Makes the MCP server image runnable as a non-root user, using the same setup 
the other Gravitino images already have.
   
   | Change | Detail |
   | --- | --- |
   | Move off `/root` | `/root/mcp-server` becomes `/opt/mcp-server`, updating 
`WORKDIR`, both `COPY` destinations, and the `cd` in `start-mcp-server.sh` |
   | Adopt the existing non-root setup | `COPY --chmod=775`, `useradd -u 1000 
-g 0 -M -s /sbin/nologin gravitino`, and `USER 1000`, as in the `gravitino`, 
`iceberg-rest-server` and `lance-rest-server` Dockerfiles |
   | Recursive `chmod` | `chmod -R 775`, rather than the non-recursive form 
those images use |
   | Pin the uv cache | `ENV UV_CACHE_DIR=/opt/mcp-server/.cache/uv` |
   
   Two of those differ from the sibling images, and both are required here 
rather than stylistic.
   
   The `chmod` is recursive because `uv venv`, `uv sync` and `uv pip install -e 
.` run after the `COPY` and create `.venv` and the uv cache as root at mode 
755. `COPY --chmod` cannot reach them, so a non-recursive `chmod 775` on the 
top directory alone would leave both inaccessible to an arbitrary UID.
   
   `UV_CACHE_DIR` is needed because uv defaults its cache to `$HOME/.cache/uv`. 
A user with no passwd entry gets `HOME=/`, which is not writable, so even after 
the working directory moved the entrypoint still failed:
   
   ```
   error: Failed to initialize cache at `/.cache/uv`
     Caused by: failed to create directory `/.cache/uv`: Permission denied
   ```
   
   Pinning the cache inside the application directory means it is populated as 
root at build time and picked up by the same `chmod`.
   
   No application code changes, no change to the base image, no change to the 
entrypoint's behaviour beyond the paths it depends on, and no Helm chart 
changes.
   
   ### Why are the changes needed?
   
   The image could only run as root. Setting `runAsUser: 1001` on the container 
made it crash immediately:
   
   ```
   start-mcp-server.sh: line 21: cd: /root/mcp-server: Permission denied
   error: failed to open file `/root/mcp-server/uv.toml`: Permission denied (os 
error 13)
   ```
   
   `/root` is mode 0700 in the `python:3.10` base, so a process running as any 
other user cannot enter its own working directory. A `USER` directive alone 
would not have fixed it; the application had to move.
   
   Kubernetes deployments increasingly require a non-root container, and 
OpenShift enforces it: its default security context constraint assigns an 
arbitrary UID and refuses an image that needs root. Membership of gid 0, with 
group permissions matching user permissions, is what makes an arbitrary 
assigned UID work, which is why the other Gravitino images are built that way.
   
   The Helm charts set a non-root security context on the other components. The 
MCP server is currently the exception that has to be left out.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes, two.
   
   The image runs as uid 1000 by default instead of root. A deployment that 
depends on running as root needs updating.
   
   The application lives at `/opt/mcp-server` instead of `/root/mcp-server`. A 
deployment that mounts a volume into the old path needs updating.
   
   No API or configuration property changes.
   
   ### How was this patch tested?
   
   Built locally and run as several users. All exited 0 and printed usage with 
no permission error:
   
   ```shell
   docker run --rm --user 1000    <image> --help
   docker run --rm --user 1001    <image> --help
   docker run --rm --user 4238721 <image> --help   # simulates OpenShift's 
arbitrary UID
   docker run --rm                <image> --help
   ```
   
   The arbitrary high UID is the case the gid 0 group ownership exists for, and 
the one a plain `--user 1000` does not exercise.
   
   `--help` alone was not treated as sufficient, since startup touches paths it 
does not. The server was also started for real in streamable-http mode as uid 
4238721, and reached `Application startup complete` with no permission errors, 
confirming `.venv` and the uv cache are reachable at runtime.
   
   Inside the running container the identity is `uid=4238721 gid=0(root)`, and 
`/opt/mcp-server`, `/opt/mcp-server/.venv` and `/opt/mcp-server/.cache/uv` are 
all `drwxrwxr-x root root`.
   
   The original failure was found on a Kubernetes cluster, by patching a 
running MCP deployment with `runAsUser: 1001` and `runAsNonRoot: true`, which 
produced the CrashLoopBackOff and the two lines quoted above.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to