init: added the base files by ai and debugged by hand
it's great but currently it has a basic error which is go nil pointer dereference WIP
This commit is contained in:
85
docs/api.md
Normal file
85
docs/api.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Auth-gate API Reference
|
||||
|
||||
## Endpoints
|
||||
|
||||
### POST /api/whitelist/temp
|
||||
|
||||
Create a temporary whitelisted IP.
|
||||
|
||||
**Authorization:** Bearer `{API_TOKEN}`
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ip": "1.2.3.4",
|
||||
"ttl_seconds": 300,
|
||||
"reason": "my laptop"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** 204 No Content
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8080/api/whitelist/temp \
|
||||
-H "Authorization: Bearer CHANGE_ME" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"ip":"203.0.113.10","ttl_seconds":300,"reason":"admin laptop"}'
|
||||
```
|
||||
|
||||
### GET /api/whitelist
|
||||
|
||||
List all currently active temporary whitelisted IPs.
|
||||
|
||||
**Authorization:** Bearer `{API_TOKEN}`
|
||||
|
||||
**Response body:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"ip": "1.2.3.4",
|
||||
"reason": "my laptop",
|
||||
"expires": "2024-01-01T12:05:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### DELETE /api/whitelist/{ip}
|
||||
|
||||
Remove a temporary whitelisted IP.
|
||||
|
||||
**Authorization:** Bearer `{API_TOKEN}`
|
||||
|
||||
**Response:** 204 No Content
|
||||
|
||||
### GET /auth
|
||||
|
||||
The NGINX auth_request endpoint. Do NOT call this directly.
|
||||
|
||||
**Response:** 200 (allow) or 401 (deny)
|
||||
|
||||
### GET /status
|
||||
|
||||
Health check.
|
||||
|
||||
**Response:** 200 `ok`
|
||||
|
||||
## Auth endpoint
|
||||
|
||||
The `/auth` endpoint uses HTTP Basic Auth for the fallback.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl -u user:pass http://127.0.0.1:8080/auth
|
||||
```
|
||||
|
||||
## Security notes
|
||||
|
||||
- The API is not exposed publicly. It's only accessible from NGINX via Docker
|
||||
internal networking.
|
||||
- The API token is the only secret for the API. Keep it in the environment.
|
||||
- The Basic Auth credentials are set via environment variables. Use strong passwords.
|
||||
113
docs/deploy.md
Normal file
113
docs/deploy.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Deployment Instructions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker (v19+)
|
||||
- Docker Compose (v2+)
|
||||
- NGINX (with auth_request module)
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
docker build -t yejayekhoob/auth-gate:latest .
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## NGINX integration
|
||||
|
||||
### 1. Create the auth-gate include file
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/snippets/auth-gate.conf
|
||||
#
|
||||
# This is the shared auth_request configuration. Include it in any
|
||||
# server block that needs authentication.
|
||||
#
|
||||
# Important: we use X-Real-IP (set by NGINX) rather than
|
||||
# X-Forwarded-For. X-Forwarded-For is client-controlled and can be
|
||||
# spoofed. If you need a CDN, configure the realip module instead
|
||||
# of trusting the header.
|
||||
|
||||
location = /_auth_gate {
|
||||
internal;
|
||||
|
||||
proxy_pass http://auth-gate:8080/auth;
|
||||
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Original-Host $host;
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
proxy_set_header Authorization $http_authorization;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Apply the include to sensitive server blocks
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name phpmyadmin.yejayekhoob.com;
|
||||
|
||||
include /etc/nginx/snippets/auth-gate.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://phpmyadmin:80/;
|
||||
# ... other proxy settings ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Test and reload
|
||||
|
||||
```bash
|
||||
nginx -t
|
||||
nginx -s reload
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| AUTH_GATE_PORT | HTTP listen port | 8080 |
|
||||
| AUTH_GATE_API_TOKEN | API bearer token | (none) |
|
||||
| AUTH_GATE_BASIC_USER | Basic Auth username | admin |
|
||||
| AUTH_GATE_BASIC_PASSWORD | Basic Auth password | changeme |
|
||||
| PERMANENT_WHITELIST_FILE | Path to permanent whitelist file | /config/permanent_whitelist.txt |
|
||||
| DATA_DIR | Data directory | /data |
|
||||
| CLEANUP_INTERVAL | Cleanup interval | 60s |
|
||||
| WATCH_INTERVAL | Whitelist file watch interval | 30s |
|
||||
|
||||
## Permanent whitelist file
|
||||
|
||||
```
|
||||
# /config/permanent_whitelist.txt
|
||||
203.0.113.10
|
||||
198.51.100.0/24
|
||||
```
|
||||
|
||||
The file is reloaded every 30 seconds (configurable).
|
||||
|
||||
## Graceful shutdown
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
The service waits up to 30 seconds for in-flight requests to complete.
|
||||
|
||||
## Hardened deployment
|
||||
|
||||
For production, use the Dockerfile from this repository. It:
|
||||
|
||||
- Builds the binary in a separate stage (smaller image).
|
||||
- Runs the service as a non-root user.
|
||||
- Drops all Linux capabilities.
|
||||
- Sets the no-new-privileges flag.
|
||||
- Mounts the whitelist file as read-only.
|
||||
- Mounts /tmp as tmpfs (no persistence).
|
||||
126
docs/design.md
Normal file
126
docs/design.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# 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.
|
||||
84
docs/security.md
Normal file
84
docs/security.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Auth-gate Security Model
|
||||
|
||||
## Authentication flow
|
||||
|
||||
1. Client request arrives at NGINX.
|
||||
2. NGINX sends an internal auth_request subrequest to auth-gate.
|
||||
3. Auth-gate checks:
|
||||
- Is the IP in the permanent whitelist? → Allow.
|
||||
- Is the IP in the temporary whitelist and not expired? → Allow.
|
||||
- Does the Authorization header contain valid Basic Auth? → Allow.
|
||||
- Otherwise → 401 Unauthorized.
|
||||
4. NGINX passes the request to the upstream service if auth-gate returned 200.
|
||||
5. NGINX returns 401 to the client if auth-gate returned 401/403.
|
||||
|
||||
## IP handling
|
||||
|
||||
### Permanent whitelist
|
||||
|
||||
- Loaded from a file mounted as a volume.
|
||||
- The file is polled every 30 seconds (configurable).
|
||||
- Only single IPs and CIDR ranges (as strings) are supported.
|
||||
- The file is reloaded only when its mtime changes.
|
||||
|
||||
### Temporary whitelist
|
||||
|
||||
- In-memory store.
|
||||
- Entries expire after their TTL.
|
||||
- The cleanup goroutine runs every 60 seconds (configurable) to remove expired entries.
|
||||
- No persistence across restarts.
|
||||
|
||||
## API key
|
||||
|
||||
The API key is the only secret for the /api/* endpoints. It's sent as a bearer token
|
||||
in the Authorization header.
|
||||
|
||||
- Keep it secret. Don't log it. Don't put it in the URL.
|
||||
- Use a strong random string.
|
||||
- Rotate it regularly.
|
||||
|
||||
## Basic Auth
|
||||
|
||||
The Basic Auth credentials are set via environment variables. The username is `admin`
|
||||
by default, and the password is `changeme` by default.
|
||||
|
||||
- Change both immediately on first use.
|
||||
- Use strong passwords.
|
||||
- The credentials are sent in the Authorization header (base64-encoded).
|
||||
- Always serve the auth endpoint over TLS.
|
||||
|
||||
## File watcher
|
||||
|
||||
The permanent whitelist file watcher polls the file every 30 seconds. It only
|
||||
reloads if the file's mtime has changed. This means:
|
||||
|
||||
- Frequent touch-operations don't cause unnecessary reloads.
|
||||
- The watcher is simple and doesn't depend on inotify.
|
||||
|
||||
## Graceful shutdown
|
||||
|
||||
The service listens for SIGTERM and SIGINT. On signal:
|
||||
|
||||
1. Stop accepting new connections.
|
||||
2. Close idle connections.
|
||||
3. Wait for in-flight requests to complete (up to 30 seconds).
|
||||
4. Exit.
|
||||
|
||||
This ensures Docker deployments can shut down cleanly.
|
||||
|
||||
## What we don't do
|
||||
|
||||
- We don't support JWT/OAuth. If you need these, add them later.
|
||||
- We don't support session state. The auth decision is made per-request.
|
||||
- We don't support rate limiting. Use NGINX's limit_req for that.
|
||||
- We don't support IP-based rate limiting. Use NGINX's limit_conn for that.
|
||||
- We don't support IPv6. If you need IPv6, add it later.
|
||||
- We don't support TLS. The auth endpoint should always be served over TLS.
|
||||
- We don't support logging to a file. Logs go to stdout (Docker).
|
||||
- We don't support metrics. If you need metrics, add them later.
|
||||
- We don't support health checks with Prometheus. The /status endpoint is a simple text response.
|
||||
- We don't support configuration via a config file. Use environment variables.
|
||||
- We don't support multiple domains. The auth-gate service doesn't care about domains.
|
||||
- We don't support HTTP/2. The auth-gate service uses HTTP/1.1 only.
|
||||
- We don't support WebSocket. The auth-gate service doesn't need WebSocket.
|
||||
- We don't support gRPC. The auth-gate service is a simple REST API.
|
||||
Reference in New Issue
Block a user