Skip to content

[FEATURE] Docker label ACLs: read labels from the container list and cache them #1184

Description

@lewisap

Is your feature request related to a problem? Please describe.

When ACLs come from Docker labels, every forward-auth request does a full uncached label lookup. DockerService.Lookup lists the containers, then calls ContainerInspect for each one until a container's tinyauth.apps.* labels match the domain (docker_service.go#L122-L154, v5.2.0). If no container matches, it inspects every container on the host needlessly.

On my host (34 containers, v5.2.0, Docker reached through a socket proxy via DOCKER_HOST), forward-auth latency changed in the following way once Docker access was enabled:

p50 p95
no Docker access (labels skipped) 0.042 ms 0.071 ms
Docker access, no matching labels 76 ms 84 ms

Each request to a protected app (page, scripts, images) goes through forward auth, so one page load can cause many of these lookups. Each lookup issues up to N+1 Docker API calls.

There is a second effect. Because labels are read through inspect, tinyauth needs access to GET /containers/{id}/json. That endpoint returns each container's full config, including its environment variables, which often hold secrets. That's why the socket-proxy allowlist in the access-controls docs (the one proposed in #618) has to allow containers/.*.

Describe the solution you'd like.

  1. Read labels from the list response. ContainerList (GET /containers/json) already returns each container's Labels (container.Summary.Labels), so the per-container inspect isn't needed to decode tinyauth.apps.*. That makes a lookup one API call instead of up to N+1. tinyauth would then only need GET /containers/json and /_ping, and socket-proxy users could stop allowing inspect, which exposes secrets.
  2. Cache the decoded apps. Build the label-to-app map once and refresh it on Docker events (GET /events filtered to type=container, on start, die and destroy), plus a periodic resync as a safety net. The Kubernetes provider already uses this watcher-and-resync pattern (kubernetes_service.go#L270-L335), so Docker would behave the same way. tinyauth already caches LDAP groups (LDAP_GROUPCACHETTL) and Tailscale lists (TAILSCALE_CACHEDURATION) for performance, and the documented socket-proxy allowlist already permits events.*, requiring no new proxy permissions.

(1) recovers most of the latency waste and removes the need for inspect. (2) makes label lookups only occur when a container changes.

Describe alternatives you've considered.

  • Static ACLs in config/env. These are checked before labels (GetAccessControls) and cost nothing, but a matching static app replaces label lookup for that domain entirely (no merging, as noted in feat: ACL labels from environment variables #422), so it doesn't seem like one can combine the two. Keeping ACLs next to each container's own definition is my main reason to use labels.
  • Adding labels to every protected app. The scan stops at the first match, so this lowers the average cost, but it still runs on every request, and hosts without labels still run a full scan.

Additional context

Related to #393: I think extending it such that a failed Docker connection at startup is logged at the warn level, not debug (docker_service.go#L47-L56), would also be of benefit for this feature. A cache can fail quietly: if the events stream drops, then ACLs go stale, and that likely should have the same warn-level visibility.

Human Written Confirmation

  • I confirm this request was written by me and not generated by an LLM or AI assistant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions