- Migrate IP-based temporary whitelisting from file to SQLite storage - Add REST API endpoints for managing temporary and permanent whitelists - Create `.env.example` with required environment variables - Document API endpoints in README.md and docs/api.md - Add new dependency `modernc.org/sqlite` for SQLite support - Update deployment and security documentation
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-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 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.