A small nginx reverse-proxy configuration and Docker Compose setup for running a secure proxy in front of the Copilot API backend.
Features
- TLS-terminating nginx proxy with enhanced security headers
- Authentication gateway (auth_request) that delegates API key / JWT validation to a lightweight Python verifier service
- Secure upstream TLS to the copilot-api service
- Docker Compose for local development and deployment
- JWT token minting endpoint (/token) for exchanging API keys for short-lived JWT tokens
- Remote JWKS support with automatic fetching and caching
- Enhanced rate limiting at the nginx level
- Improved security with constant-time comparisons and algorithm whitelisting
Requirements
- Docker and Docker Compose
- A GitHub token if you run the embedded
copilot-apibuilder (setGITHUB_TOKEN)
Quickstart
- Copy the example environment file and set values:
cp .env.example .env
Edit
.envand populate the required variables described below. - Start the services: docker compose up -d --build
- Verify the proxy is serving HTTPS (adjust host/port as configured): curl -vk https://localhost:5000/
Configuration This repository composes three main services:
copilot-api— the upstream Copilot API service (built fromDockerfile.copilot).verifier— small FastAPI app that validates incoming requests using either API keys or JWT/JWKS.nginx— TLS-terminating reverse proxy that usesauth_requestto call theverifierservice.
Environment variables are documented in .env.example. Important variables include:
GITHUB_TOKEN— required by thecopilot-apibuilder if you run that service (set to a GitHub PAT with appropriate scopes).HOST_CERTS_DIR— host directory mounted into Nginx at/etc/nginx/certscontainingcopilot.crtandcopilot.key.API_KEY— API key accepted by theverifierservice. Provide at least one.JWKS_URL— (Optional) HTTPS URL to fetch JWKS from for remote JWKS support.JWKS_TTL— (Optional) Time-to-live for cached remote JWKS in seconds (default: 300).JWT_TTL— (Optional) Token time-to-live in seconds for JWT tokens minted by the /token endpoint (default: 3600).JWT_AUDIENCE— (Optional) Audience claim for JWT tokens.JWT_ISSUER— (Optional) Issuer claim for JWT tokens.
TLS / Certificates
- Provide TLS cert and key files in the host path referenced by
HOST_CERTS_DIR. They should be namedcopilot.crtandcopilot.key. - For development you may use self-signed certs, but do not disable TLS verification in production. If testing with Node.js clients only, you can temporarily set
NODE_TLS_REJECT_UNAUTHORIZED=0locally (not recommended for general use).
Development
- Tail logs during development: docker compose logs -f nginx verifier copilot-api
- The
verifierservice is implemented inverifier.py. It supports multiple verification modes:- Plaintext API keys via
API_KEYorAPI_KEYS(comma-separated). - Bcrypt-hashed keys loaded from
API_KEYS_FILE. Lines may beuser:$2b$...or a plaintext key. - JWT validation via local or remote JWKS (using
JWKS_URLwith automatic fetching and caching). - JWT token minting via the
/tokenendpoint for exchanging valid API keys for short-lived JWT tokens.
- Plaintext API keys via
Example API_KEYS_FILE entries (one per line):
alice:$2b$12$wV... (bcrypt hash of alice's key)
bob:$2b$12$7Q... (bcrypt hash of bob's key)
# a plaintext key (not recommended in production)
plainkey123
To generate a bcrypt hash locally (Python):
import bcrypt
pw = b"my-secret-key"
print(bcrypt.hashpw(pw, bcrypt.gensalt()).decode())
Make configuration changes in nginx.conf and restart the nginx service.
Authentication flow (enhanced verifier implementation)
When a client sends an authenticated request the flow is:
- Client -> Nginx
- Client calls the proxied API and includes credentials in the Authorization header (either
Bearer <token>or raw<token>).
- Client calls the proxied API and includes credentials in the Authorization header (either
- Nginx -> Verifier (auth_request)
- Nginx calls the verifier's
/verifyendpoint and forwards the Authorization header.
- Nginx calls the verifier's
- Verifier extracts token
- If header starts with
Bearer, the token portion is extracted.
- If header starts with
- Verifier decides path
- If token looks like a JWT (contains two dots):
a. Load JWKS (from remote URL if
JWKS_URLis configured, else local file). b. Parse JWT header, findkidin JWKS, convert JWK -> PEM, verify signature and claims (exp,aud,iss). c. On success return 200 with optionaluserset fromsubclaim. d. On failure return 401. - Else (not a JWT):
a. Check plaintext API keys (from
API_KEY,API_KEYS, or plaintext lines inAPI_KEYS_FILE) using constant-time comparison. b. If not found, iterate bcrypt hashes fromAPI_KEYS_FILEand runbcrypt.checkpw. If matched, return 200 and include associated user if present. c. If no match, return 401.
- If token looks like a JWT (contains two dots):
a. Load JWKS (from remote URL if
- Nginx enforces result
- Nginx allows the proxied request to proceed to upstream on 200. On 401/500 it denies the request.
Additionally, clients can exchange valid API keys for JWT tokens using the /token endpoint:
- Client -> Verifier
- Client makes a POST request to
/tokenwith a valid API key in the Authorization header.
- Client makes a POST request to
- Verifier validates API key
- Checks plaintext or bcrypt hashed keys as in the verification flow.
- Verifier mints JWT
- Finds a private JWK in the loaded JWKS.
- Signs a new JWT with configurable claims (
sub,aud,iss,exp). - Returns the signed JWT with metadata.
- Client receives JWT
- Can use the JWT for subsequent requests until expiration.
Mermaid flowchart (added to README):
flowchart TD
A["Client request\n(Authorization header)"] --> B["Nginx auth_request -> /verify"]
B --> C{Token has two dots?}
C -- Yes --> D["Verifier: load local JWKS\nfind JWK by kid"]
D --> E{Signature valid?}
E -- Yes --> F{Claims OK?}
F -- Yes --> G["200 OK\n(user = sub)"]
F -- No --> H["401 Invalid JWT claims"]
E -- No --> H
C -- No --> I["Verifier: check PLAINTEXT_KEYS"]
I -- Match --> G
I -- No --> J["Check BCRYPT_HASHED\nwith bcrypt.checkpw"]
J -- Match --> G
J -- No --> K["401 Invalid API key"]
G --> L["Nginx allows request to upstream"]
H --> M["Nginx denies request"]
K --> M
sequenceDiagram
participant Client
participant Nginx
participant Verifier
participant Upstream
Client->>Nginx: HTTPS request (Authorization header)
Nginx->>Verifier: auth_request /verify (forwards headers)
Verifier->>Verifier: Extract token from header
alt Token is JWT
Verifier->>Verifier: Load local JWKS and find key by kid
Verifier->>Verifier: Verify signature and claims (exp/aud/iss)
Verifier-->>Nginx: 200 OK (user=sub)
else Token is API key
Verifier->>Verifier: Check PLAINTEXT_KEYS
alt Plaintext match
Verifier-->>Nginx: 200 OK
else
Verifier->>Verifier: Check BCRYPT_HASHED via bcrypt.checkpw
alt Bcrypt match
Verifier-->>Nginx: 200 OK (user)
else
Verifier-->>Nginx: 401 Invalid API key
end
end
end
alt Verifier returned 200
Nginx->>Upstream: Proxy request to upstream
Upstream-->>Nginx: Response
Nginx-->>Client: Response
else Verifier returned 401/500
Nginx-->>Client: 401/500 error
end
Notes:
- JWKS must contain only public key material. For local-only setups place
jwks.jsonin the repo root or mount it at/certs/jwks.json. - Configure
JWT_AUDIENCEandJWT_ISSUERfor claim checks when issuing tokens. - The verifier will auto-reload
jwks.jsonwhen the file mtime changes.
Several security improvements have been implemented in the latest version:
API key verification now uses constant-time comparison (hmac.compare_digest) to prevent timing attacks that could potentially leak information about valid keys.
The JWT verification process now implements algorithm whitelisting to prevent algorithm confusion attacks. Only secure algorithms are allowed:
- RS256, RS384, RS512 (RSA PKCS#1 v1.5)
- ES256, ES384, ES512 (ECDSA)
- PS256, PS384, PS512 (RSA PSS)
Nginx configuration now includes additional security headers:
- Strict-Transport-Security (HSTS) for enforced HTTPS
- X-XSS-Protection to prevent cross-site scripting attacks
- Referrer-Policy to control referrer information
- Content-Security-Policy to prevent unauthorized resource loading
Enhanced rate limiting has been implemented at the nginx level:
- API requests: 10 requests per second per IP
- Authentication requests: 5 requests per minute per IP
- Configurable burst limits to handle traffic spikes
The verifier service now includes a /token endpoint that allows clients with valid API keys to exchange them for short-lived JWT tokens. This provides a more secure authentication mechanism by using temporary tokens instead of long-lived API keys.
- Client makes a POST request to
/tokenwith a valid API key in the Authorization header - The verifier validates the API key (plaintext or bcrypt)
- If valid, the verifier signs a new JWT using a private JWK from the loaded JWKS
- The signed JWT is returned to the client with an expiration time (default 1 hour)
JWT_TTL- Token time-to-live in seconds (default: 3600)JWT_AUDIENCE- Audience claim for the JWTJWT_ISSUER- Issuer claim for the JWT
# Exchange API key for JWT token
curl -X POST https://localhost:5000/token \
-H "Authorization: Bearer YOUR_API_KEY"
# Response:
# {
# "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imt...",
# "expires_in": 3600,
# "kid": "key1",
# "alg": "RS256"
# }
# Use the JWT token for subsequent requests
curl https://localhost:5000/v1/completions \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Imt..."To use the /token endpoint, your JWKS file must contain at least one private key (with private parameters like d for RSA keys or k for symmetric keys). Public-only JWKS files can be used for JWT verification but not for token minting.
The verifier can now fetch JWKS from remote URLs in addition to local files:
JWKS_URL- HTTPS URL to fetch JWKS from (optional)JWKS_TTL- Time-to-live for cached remote JWKS in seconds (default: 300)
When JWKS_URL is configured, the verifier will:
- Attempt to fetch JWKS from the remote URL
- Cache the result with automatic refresh based on
JWKS_TTL - Fall back to local JWKS file if remote fetching fails
Security features:
- Only HTTPS URLs are allowed for remote JWKS fetching
- JWKS structure is validated before use
- Algorithm whitelisting prevents algorithm confusion attacks
Contributing Contributions welcome. Open an issue or submit a pull request.
License MIT