2.8 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-gate 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 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 a file watcher?
The permanent whitelist file is the source of truth for permanent whitelisted IPs. We poll the file every 30 seconds (configurable) and reload the in-memory store if the file has changed.
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 a database?
For our use case (a handful of IPs), a database is overkill. We chose an in-memory map + periodic cleanup.
Why not use Redis?
For our use case, Redis is overkill. We chose an in-memory map + periodic cleanup.
Why not use SQLite?
For our use case, SQLite is overkill. We chose an in-memory map + periodic cleanup.
Why not use a key-value store?
For our use case, a key-value store is overkill. We chose an in-memory map + periodic cleanup.