Files
auth-proxy/docs/design.md

2.6 KiB

Design Decisions

Why Go?

  • Single binary — no external dependencies.
  • Small memory footprint — ~10 MB for a typical deployment.
  • Fast startup — < 1 second.
  • Simple to compile and distribute.

Why not NGINX auth_basic?

NGINX's auth_basic is simpler but doesn't support:

  • IP whitelisting (you'd need the geo module or a third-party module).
  • Temporary whitelisting (requires config reload).
  • API-based management.

Our auth-proxy service fills these gaps while keeping the architecture simple.

Why not Authelia?

Authelia is a full-featured self-hosted auth portal with:

  • SSO integration
  • 2FA
  • MFA
  • LDAP
  • OIDC

But for our use case:

  • It's heavier (requires Postgres, Redis, etc.).
  • It doesn't support temporary whitelisting out of the box.
  • It requires a login portal.

We wanted something simpler.

Why not Authentik?

Authentik is a full-featured identity provider with:

  • SSO
  • MFA
  • OIDC
  • LDAP
  • SCIM

But it's too heavy for our use case. We just need IP whitelisting + Basic Auth.

Why not Pomerium?

Pomerium is a zero-trust proxy with:

  • OIDC
  • MFA
  • IP-based access control
  • Policy engine

But it's overkill for our use case. We just need IP whitelisting + Basic Auth.

Why not oauth2-proxy?

oauth2-proxy is a reverse proxy with:

  • OIDC
  • SSO
  • MFA

But it's designed for OIDC, not for IP whitelisting.

Why not a custom script?

A shell script would work but:

  • No type safety.
  • No built-in HTTP server (need to use Python/Node).
  • No standard library (need to install packages).
  • No built-in JSON encoding/decoding.

Go's standard library is sufficient for our needs.

Why SQLite?

SQLite provides:

  • Single file — no external dependencies.
  • Persistence across restarts.
  • ACID transactions.
  • Simple WAL mode for concurrent reads.

For a handful of IPs (dozens at most), SQLite is:

  • Simple to deploy.
  • Fast (single-file, no network).
  • No external dependencies.

Why in-memory temporary whitelist?

For a handful of IPs (dozens at most), an in-memory map is:

  • Simple to implement.
  • Fast (O(1) lookup).
  • No external dependencies.

If you need persistence across restarts, add a disk-backed store later.

Why a background cleanup goroutine?

Checking expiry on every request adds latency (two map lookups per request). The cleanup goroutine is a one-time cost that keeps the hot path fast.

Why poll instead of inotify?

Polling is simpler and works inside Docker containers where inotify may not be available (no host filesystem access).

Why not use Redis?

For our use case, Redis is overkill. We chose an in-memory map + periodic cleanup.