Skip to content

fix(sbomify): REDIS_URL in compose + coreutils in keycloak image#75

Merged
mrdavidlaing merged 1 commit into
mainfrom
fix/compose-redis-url-and-bootstrap-busybox
Apr 19, 2026
Merged

fix(sbomify): REDIS_URL in compose + coreutils in keycloak image#75
mrdavidlaing merged 1 commit into
mainfrom
fix/compose-redis-url-and-bootstrap-busybox

Conversation

@mrdavidlaing

Copy link
Copy Markdown
Contributor

Summary

Three chained issues that prevent the published quickstart from completing an OIDC login. Users who deploy the stack hit a server 500 as soon as they click "sign in" and try to authenticate — before this PR the quickstart wasn't actually usable end-to-end, even after #73 got the stack to boot.

1. Server 500 on OIDC callback — wrong Redis env var

sbomify/settings.py:359 reads REDIS_URL (falling back to redis://localhost:6379). The compose file set REDIS_HOST=sbomify-redis:6379, which nothing reads. Every cache call inside sbomify failed with Error 111 connecting to localhost:6379. Connection refused. allauth's verify_jti hits cache.add() during the OIDC callback, so login blew up:

django_redis.exceptions.ConnectionInterrupted: Redis ConnectionError:
Error 111 connecting to localhost:6379. Connection refused.
  …
  File ".../allauth/socialaccount/internal/jwtkit.py", line 76, in verify_jti
      if not cache.add(key=key, value=True, timeout=timeout):

Fix: rename REDIS_HOST: ${REDIS_HOST:-sbomify-redis:6379}REDIS_URL: ${REDIS_URL:-redis://sbomify-redis:6379} in x-common-env.

2. keycloak-bootstrap exits 127 on re-run

The idempotent "client already exists" branch of keycloak-bootstrap.sh pipes through tr -d '"' to parse a UUID out of CSV. tr isn't in the sbomify-keycloak image — minimalBusybox curates applets by CVE surface and tr wasn't on the allow-list. First boot (create path) worked fine, but recreating the container after the first provision (say, during a config reload or after a docker compose up -d --force-recreate) exits 127 and blocks the service_completed_successfully dependency gate on sbomify-backend.

3. kcadm.sh noise on every call

Keycloak's upstream kcadm.sh-wrapped starts with case "$(uname) in … *) readlink -f "$0". Neither binary was in PATH inside the image, so every invocation printed:

kcadm.sh-wrapped: line 2: uname: command not found
kcadm.sh-wrapped: line 15: readlink: command not found

Once uname started returning Linux, the case fell through to the default branch and called busybox readlink -f, which doesn't support -freadlink: invalid option -- 'f' on every call. Cosmetic, but it drowns out the real bootstrap output.

Fix

Added pkgs.coreutils to apps/sbomify/images/sbomify-keycloak.nix. This gives the keycloak image GNU tr, uname, and readlink -f without touching the global minimalBusybox allow-list — other images (postgres, redis, minio, sbomify-app, caddy) stay on the lean minimal shell, only the keycloak image carries the ~1.7 MB coreutils weight it needs because it ships an upstream wrapper that assumes GNU semantics.

Test plan

  • Reproduced the Redis 500 against the published sbomify-v26.1.0-20260419.2 compose release by clicking through the OIDC login flow in a browser (as jdoe / foobar123). Got HTTP 500 on /accounts/oidc/keycloak/login/callback/; backend log shows the django_redis.exceptions.ConnectionInterrupted trace quoted above.
  • Verified fix feat: Compliance infrastructure for curated Nixpkgs #1 end-to-end in an isolated compose project with only the compose change applied (original published keycloak image): REDIS_URL reaches sbomify-backend, Django cache talks to sbomify-redis:6379, cache.set/get/add all succeed.
  • Rebuilt sbomify-keycloak-image locally with the coreutils change (nix build .#sbomify-keycloak-image), loaded into docker, ran full stack:
    • First-run keycloak-bootstrap log is now silent — no uname: command not found, no readlink: command not found, no invalid option. Exit 0.
    • Force-recreated the bootstrap container to exercise the "client already exists" branch: exit 0 (was 127 before). Client UUID resolved correctly, realm/client/users still present and consistent.
  • Backend healthy, curl /UuPha8mu/ → 200, Django cache reachable, full OIDC login flow completes (with the matching combined fix this is unblocked).
  • Torn down all test stacks with down -v, no residual state.

Notes

  • Reaches end users on the next release (quickstart URL pins the compose file + image tags as release assets).
  • Left APP_BASE_URL port-coupling alone — separate issue, deserves its own PR with a proper override story.

🤖 Generated with Claude Code

Fixes three issues that together make the published quickstart unable
to complete the OIDC login flow:

1. Server 500 on OIDC callback — sbomify settings.py reads REDIS_URL
   and falls back to redis://localhost:6379 when unset; our compose
   file was setting REDIS_HOST (which nothing reads) to the docker
   hostname. django-redis cache then failed every operation with
   "Error 111 connecting to localhost:6379. Connection refused."
   allauth's verify_jti calls cache.add() during the OIDC callback,
   so the whole login chain blew up. Renamed REDIS_HOST → REDIS_URL
   in x-common-env and pointed it at redis://sbomify-redis:6379.

2. keycloak-bootstrap exit 127 on re-run — the idempotent branch for
   an existing client in keycloak-bootstrap.sh pipes through `tr -d '"'`
   to extract a UUID from CSV output, but tr isn't in the sbomify-
   keycloak image (minimalBusybox curates applets by CVE surface and
   tr wasn't included). Recreating the container after first provision
   would exit 127 and break the `depends_on: service_completed_successfully`
   gate on sbomify-backend.

3. kcadm.sh noise — Keycloak's upstream kcadm.sh wrapper calls
   $(uname) and `readlink -f "$0"` on every invocation. Without
   those binaries in PATH, every kcadm.sh call printed
   "uname: command not found" and "readlink: command not found"
   (or "invalid option -- 'f'" once uname returned Linux and the
   busybox readlink got selected). Cosmetic but obscures the real
   bootstrap output and makes debugging harder.

Resolved #2 and #3 together by adding pkgs.coreutils to the
sbomify-keycloak image packages. Kept minimalBusybox untouched so
other images (postgres, redis, minio, sbomify-app, caddy) stay lean;
only keycloak carries the extra coreutils weight (~1.7MB), which it
needs because it embeds an upstream wrapper that assumes GNU
coreutils semantics.

Verified against the published sbomify-v26.1.0-20260419.2 compose
release:
  - REDIS_URL fix: `docker compose up -d` (patched compose, unchanged
    images) → cache.set/get/add all succeed, Django talks to
    sbomify-redis:6379.
  - coreutils fix: rebuilt sbomify-keycloak-image locally, reran the
    stack → first-run bootstrap log is silent (no "not found" /
    "invalid option" lines), exit 0; force-recreated the bootstrap
    container to hit the re-run path → exit 0 (previously 127),
    client still exists and is correctly updated.

Co-Authored-By: Yakoff (Claude) <noreply@anthropic.com>
@mrdavidlaing mrdavidlaing merged commit f368a1c into main Apr 19, 2026
8 checks passed
@mrdavidlaing mrdavidlaing deleted the fix/compose-redis-url-and-bootstrap-busybox branch April 19, 2026 21:45
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.

1 participant