feat: add SQLite persistence and REST API for temporary whitelisting

- 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
This commit is contained in:
db123-test
2026-05-04 09:08:11 +03:30
parent a5c9c42533
commit 515de33837
18 changed files with 467 additions and 254 deletions

View File

@@ -29,7 +29,7 @@ curl -X POST http://127.0.0.1:8080/api/whitelist/temp \
-d '{"ip":"203.0.113.10","ttl_seconds":300,"reason":"admin laptop"}'
```
### GET /api/whitelist
### GET /api/whitelist/temp/list
List all currently active temporary whitelisted IPs.
@@ -47,7 +47,7 @@ List all currently active temporary whitelisted IPs.
]
```
### DELETE /api/whitelist/{ip}
### DELETE /api/whitelist/temp/{ip}
Remove a temporary whitelisted IP.
@@ -55,6 +55,67 @@ Remove a temporary whitelisted IP.
**Response:** 204 No Content
### POST /api/whitelist/perm
Add a permanent whitelisted IP or CIDR range.
**Authorization:** Bearer `{API_TOKEN}`
**Request body:**
```json
{
"entry": "1.2.3.4"
}
```
**Response:** 204 No Content
**Example:**
```bash
curl -X POST http://127.0.0.1:8080/api/whitelist/perm \
-H "Authorization: Bearer CHANGE_ME" \
-H "Content-Type: application/json" \
-d '{"entry":"203.0.113.10"}'
```
### DELETE /api/whitelist/perm/{entry}
Remove a permanent whitelisted IP or CIDR range.
**Authorization:** Bearer `{API_TOKEN}`
**Response:** 204 No Content
### GET /api/logs
Retrieve audit log entries.
**Authorization:** Bearer `{API_TOKEN}`
**Response body:**
```json
[
{
"timestamp": "2024-01-01T12:00:00Z",
"action": "add_temp",
"ip": "1.2.3.4",
"reason": "admin laptop",
"ttl_seconds": 300,
"api_client_ip": "10.0.0.1"
}
]
```
**Example:**
```bash
curl -X GET http://127.0.0.1:8080/api/logs \
-H "Authorization: Bearer CHANGE_ME"
```
### GET /auth
The NGINX auth_request endpoint. Do NOT call this directly.
@@ -67,19 +128,10 @@ 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.
- The auth endpoint only checks IP whitelisting (no Basic Auth fallback).
- Always serve the auth endpoint over TLS.

View File

@@ -76,22 +76,9 @@ nginx -s reload
|----------|-------------|---------|
| 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 |
| AUTH_GATE_DB_PATH | SQLite database path | ./data/auth-gate.db |
| 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
@@ -109,5 +96,4 @@ For production, use the Dockerfile from this repository. It:
- 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).
- Mounts /tmp as tmpfs (no persistence).

View File

@@ -79,6 +79,21 @@ A shell script would work but:
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:
@@ -94,33 +109,12 @@ If you need persistence across restarts, add a disk-backed store later.
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.

View File

@@ -7,7 +7,6 @@
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.
@@ -16,17 +15,15 @@
### Permanent whitelist
- Loaded from a file mounted as a volume.
- The file is polled every 30 seconds (configurable).
- Stored in SQLite `permanent_whitelist` table.
- Only single IPs and CIDR ranges (as strings) are supported.
- The file is reloaded only when its mtime changes.
- Added/removed via the `/api/whitelist/perm` API.
### Temporary whitelist
- In-memory store.
- Stored in SQLite `temp_whitelist` table.
- Entries expire after their TTL.
- The cleanup goroutine runs every 60 seconds (configurable) to remove expired entries.
- No persistence across restarts.
## API key
@@ -37,23 +34,18 @@ in the Authorization header.
- Use a strong random string.
- Rotate it regularly.
## Basic Auth
## Audit log
The Basic Auth credentials are set via environment variables. The username is `admin`
by default, and the password is `changeme` by default.
All whitelist operations are logged to the `audit_log` table in SQLite.
Each entry records:
- 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.
- `action` — what happened (`add_temp`, `delete_temp`, `add_perm`, `delete_perm`, `expire_temp`)
- `ip` or `cidr` — the affected IP or CIDR
- `ttl_seconds` — for temp entries
- `reason` — the reason provided
- `api_client_ip` — the IP that made the API call
## 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.
Retrieve logs via the `/api/logs` endpoint.
## Graceful shutdown
@@ -81,4 +73,4 @@ This ensures Docker deployments can shut down cleanly.
- 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.
- We don't support gRPC. The auth-gate service is a simple REST API.