Skip to content

fix(docker): stop shipping the Firebase credential inside the image - #42

Open
AndreaDiazCorreia wants to merge 1 commit into
mainfrom
fix/docker-drop-baked-secrets
Open

fix(docker): stop shipping the Firebase credential inside the image#42
AndreaDiazCorreia wants to merge 1 commit into
mainfrom
fix/docker-drop-baked-secrets

Conversation

@AndreaDiazCorreia

Copy link
Copy Markdown
Member

Dockerfile copied secrets/ into a layer, so the Firebase service-account private key shipped with the image. docker save and docker history reach it without ever running the container, and image access is governed by registry membership rather than by anything this repo controls. That set changed recently when the app moved to a shared Fly organisation, which is what moved this up the list.

The credential now arrives at runtime

Two forms, exactly one required:

Variable Use when
FIREBASE_SERVICE_ACCOUNT_JSON The credential itself. Preferred on Fly.io, where a secret already is an environment variable.
FIREBASE_SERVICE_ACCOUNT_PATH A path to a mounted file. Preferred for docker-compose, systemd and Kubernetes.

The inline form leads for a specific reason rather than taste: the container now runs as UID 10001, and a file the platform mounts carries ownership and mode this project does not control. Fly's docs do not specify either for [[files]], and the failure mode of guessing wrong is silent — FCM starts disabled and every push is dropped. An environment variable is readable by the process whatever its UID, and it is how SERVER_PRIVATE_KEY already travels.

load_service_account takes both values as arguments instead of reading the environment itself, so precedence is tested without mutating process-wide state. An empty inline value is treated as absent rather than as a parse failure, since a half-set variable should fall back to a working file rather than break.

A second exposure, not in the issue

There was no .dockerignore. The whole build context — secrets/, .env, target/, .git/ — was sent to the daemon on every build, and Fly builds on a remote builder by default, so that content left the machine each deploy. Removing the COPY closes the image; .dockerignore closes the context. The Dockerfile only needs Cargo.toml, Cargo.lock, src/ and config/.

Making the failure loud

Removing the COPY makes "no credential" a likely deployment slip rather than a corner case, and the existing behaviour was to log a warn! and carry on — an instance that accepts registrations and delivers nothing, with one line of scrollback as the only symptom. Two changes:

  • main.rs logs at error! and names both variables. The server still starts, because a Nostr listener and an HTTP API without push beat no server at all.
  • deploy-fly.sh refuses to deploy when neither credential secret exists. Its REQUIRED_SECRETS list previously demanded FIREBASE_SERVICE_ACCOUNT_PATH, which would have blocked every deploy using the inline form.

Container hardening

  • Runs as UID/GID 10001. The group is created explicitly: useradd --uid alone picks the GID from the system range, so USER 10001:10001 would have named a group absent from /etc/group. Caught while re-reading the file, not by a build.
  • WORKDIR /app with data/ owned by the runtime user, so the UnifiedPush endpoint store still works if that backend is ever enabled.
  • HEALTHCHECK against /api/health, plus [[http_service.checks]] in fly.toml. These are not redundant: Fly ignores Docker health checks and runs its own, and fly status currently reports no checks at all. curl is added solely for the Docker form; the tradeoff is called out in a comment so it can be rejected.

Also fixed

docker-compose.yml was already broken for FCM before this change: it bind-mounted the credential but never set FIREBASE_SERVICE_ACCOUNT_PATH, so the service account resolved to None every time. It also forced UNIFIEDPUSH_ENABLED=true. Both corrected, with the UID 10001 readability requirement documented next to the mount.

Verification

56 tests pass (7 new, covering precedence, the empty-value fallback, and every failure path), cargo fmt --check and cargo clippy --all-targets clean, deploy-fly.sh passes bash -n, fly.toml parses.

The image itself is unverified. Docker was not available in the environment this was written in, and for a Dockerfile change that is a real gap. Worth running before merge:

docker build -t mostro-push-test .
docker run --rm mostro-push-test id               # expect uid=10001(mostro) gid=10001(mostro)
docker run --rm mostro-push-test ls /secrets      # expect: No such file or directory
docker run --rm mostro-push-test ls -ld /app/data # expect owner 10001

Merge order

Touches src/push/fcm.rs, as does #41. The production change is a small isolated hunk, but both PRs append a test module to the end of that file, so a conflict there is certain — mechanical to resolve by concatenating. Merge #41 first.

Set FIREBASE_SERVICE_ACCOUNT_JSON on Fly before deploying an image built from this Dockerfile. deploy-fly.sh now blocks the deploy if it is missing, but the ordering is secret first, deploy second.

Closes #15

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 28 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 82034b7d-b86f-4301-be99-e45409616f40

📥 Commits

Reviewing files that changed from the base of the PR and between 24769fa and 47b77e0.

📒 Files selected for processing (9)
  • .dockerignore
  • Dockerfile
  • deploy-fly.sh
  • docker-compose.yml
  • docs/configuration.md
  • docs/deployment.md
  • fly.toml
  • src/main.rs
  • src/push/fcm.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47b77e0e9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deploy-fly.sh
Comment on lines +63 to +65
if grep -qx "${secret}" <<< "${configured_secret_names}"; then
credential_present=true
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require an inline credential for standard Fly deploys

When an existing Fly app has only the previously required FIREBASE_SERVICE_ACCOUNT_PATH secret, this check still marks the credential as present and proceeds. However, the updated Dockerfile no longer copies the referenced file, and fly.toml defines no file or volume mount, so the server starts with FCM disabled and silently drops all FCM pushes. The Fly wrapper should require FIREBASE_SERVICE_ACCOUNT_JSON, unless it can verify that an external file mount has actually been provisioned.

Useful? React with 👍 / 👎.

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.

[P2] [security] Docker container runs as root and copies secrets/ into the image

1 participant