# Vaultwarden API Proxy A secure, lightweight Go API that acts as a proxy between your local apps and Vaultwarden (self-hosted Bitwarden server). Use it as a replacement for `.env` files in development and production environments. ## Features - **Secure**: API key authentication, IP whitelisting, constant-time comparison, no sensitive logging - **Lightweight**: ~15MB Docker image, minimal resource usage - **Fast**: Built-in caching with configurable TTL - **Simple**: Clean architecture, easy to deploy and maintain - **Production-ready**: Rate limiting, health checks, graceful shutdown, security headers - **IP Whitelisting**: Restrict access by IP/CIDR with optional GitHub Actions support ## Architecture ``` ┌─────────────┐ HTTPS + API Key ┌──────────────────┐ │ Your App │ ───────────────────────> │ Vaultwarden API │ └─────────────┘ │ Proxy │ └──────────────────┘ │ │ Vaultwarden API ▼ ┌──────────────────┐ │ Vaultwarden │ │ Server │ └──────────────────┘ ``` ## Project Structure ``` . ├── cmd/ │ └── api/ │ └── main.go # Application entry point ├── internal/ │ ├── auth/ │ │ └── middleware.go # API key authentication │ ├── config/ │ │ └── config.go # Configuration management │ ├── handlers/ │ │ └── handlers.go # HTTP handlers │ ├── ipwhitelist/ │ │ └── ipwhitelist.go # IP-based access control │ ├── validators/ │ │ └── validators.go # Input validation │ └── vaultwarden/ │ ├── client.go # Vaultwarden client │ ├── client_cli.go # CLI-based secret retrieval │ └── init.go # Bitwarden CLI initialization ├── pkg/ │ └── logger/ │ └── logger.go # Secure logging ├── .env.example # Environment variables template ├── .gitignore # Git ignore rules ├── Dockerfile # Multi-stage Docker build ├── docker-compose.yml # Docker Compose configuration ├── go.mod # Go module definition ├── go.sum # Go module checksums ├── Makefile # Build automation └── README.md # This file ``` ## Quick Start ### Prerequisites - Go 1.25+ (for local development) - Docker & Docker Compose (for containerized deployment) - Vaultwarden instance with API access ### Local Development 1. **Clone the repository** ```bash git clone https://github.com/turbootzz/vaultwarden-api.git cd vaultwarden-api ``` 2. **Create environment file** ```bash cp .env.example .env ``` 3. **Configure your `.env` file** ```bash # Generate a strong API key make generate-api-key # Edit .env and add: # - API_KEY (generated above) # - VAULTWARDEN_URL (your Vaultwarden instance URL) # - VAULTWARDEN_CLIENT_ID, VAULTWARDEN_CLIENT_SECRET, VAULTWARDEN_PASSWORD # (from Vaultwarden Account Settings → Security → Keys → View API Key) # - ALLOWED_IPS (your IP address or CIDR range for security) ``` 4. **Run the application** ```bash make run ``` ### Docker Deployment 1. **Build the Docker image** ```bash make docker-build ``` 2. **Run with Docker Compose** ```bash make docker-compose-up ``` ## API Endpoints ### Health Check ```bash curl http://localhost:8080/health ``` **Response:** ```json { "status": "ok", "service": "vaultwarden-api" } ``` ### Get Secret ```bash curl -H "Authorization: Bearer YOUR_API_KEY" \ http://localhost:8080/secret/DATABASE_URL ``` **Response:** ```json { "name": "DATABASE_URL", "value": "postgresql://user:pass@localhost:5432/db" } ``` ### Refresh Cache ```bash curl -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ http://localhost:8080/refresh ``` **Response:** ```json { "status": "ok", "message": "cache cleared successfully" } ``` ## Configuration All configuration is done via environment variables: | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `API_PORT` | No | `8080` | Port the API listens on | | `ENVIRONMENT` | No | `development` | Environment (development/production) | | `API_KEY` | **Yes** | - | API key for authentication (min 32 chars) | | `VAULTWARDEN_URL` | **Yes** | - | Vaultwarden instance URL | | `VAULTWARDEN_CLIENT_ID` | **Yes*** | - | Vaultwarden API client ID | | `VAULTWARDEN_CLIENT_SECRET` | **Yes*** | - | Vaultwarden API client secret | | `VAULTWARDEN_PASSWORD` | **Yes*** | - | Your Vaultwarden master password | | `VAULTWARDEN_ACCESS_TOKEN` | **Yes*** | - | Alternative: pre-generated session token | | `ALLOWED_IPS` | No | - | Comma-separated IPs/CIDRs to whitelist | | `ENABLE_GITHUB_IP_RANGES` | No | `false` | Allow GitHub Actions IPs (updates every 24h) | | `TRUSTED_PROXY_IP` | No | `localhost` | Trusted reverse proxy IPs/CIDRs | | `CORS_ALLOWED_ORIGINS` | No | `localhost:3000` | CORS allowed origins | | `CACHE_TTL` | No | `5m` | Cache duration (0 to disable) | | `READ_TIMEOUT` | No | `10s` | HTTP read timeout | | `WRITE_TIMEOUT` | No | `10s` | HTTP write timeout | | `DEBUG` | No | `false` | Enable debug logging | **\*Note:** Either use `VAULTWARDEN_CLIENT_ID` + `VAULTWARDEN_CLIENT_SECRET` + `VAULTWARDEN_PASSWORD` (recommended) OR `VAULTWARDEN_ACCESS_TOKEN` (will expire). ## Getting Vaultwarden API Credentials ### Recommended Method: API Key Authentication 1. **Get API Credentials from Vaultwarden Web UI**: - Log into your Vaultwarden instance - Go to **Account Settings** → **Security** → **Keys** - Click **View API Key** - You'll see: - `client_id` (starts with `user.`) - `client_secret` (long random string) - Add these to your `.env` as `VAULTWARDEN_CLIENT_ID` and `VAULTWARDEN_CLIENT_SECRET` - Add your master password as `VAULTWARDEN_PASSWORD` 2. **Benefits**: - ✅ Bypasses 2FA - ✅ Automatically logs in on startup - ✅ Never expires - ✅ More secure than session tokens ### Alternative Method: Session Token 1. **Generate a session token manually**: ```bash # Login and unlock your vault bw config server https://vault.yourdomain.com bw login SESSION_TOKEN=$(bw unlock --raw) echo $SESSION_TOKEN ``` 2. **Add to `.env`**: ```bash VAULTWARDEN_ACCESS_TOKEN=your-session-token-here ``` 3. **Limitations**: - ⚠️ Tokens expire after inactivity - ⚠️ Requires manual regeneration - ⚠️ Not recommended for production ## IP Whitelisting The API supports IP-based access control for enhanced security. ### Configuration **Allow specific IPs:** ```bash ALLOWED_IPS=203.0.113.50 ``` **Allow multiple IPs:** ```bash ALLOWED_IPS=203.0.113.50,198.51.100.25 ``` **Allow CIDR ranges:** ```bash ALLOWED_IPS=192.168.1.0/24 ``` **Mix IPs and CIDRs:** ```bash ALLOWED_IPS=203.0.113.50,192.168.1.0/24,10.0.0.0/8 ``` **Enable GitHub Actions (optional):** ```bash ENABLE_GITHUB_IP_RANGES=true ``` This automatically whitelists all GitHub Actions IP ranges and updates them every 24 hours. ### Trusted Proxies If you're running behind a reverse proxy (Nginx Proxy Manager, Traefik, Cloudflare), configure trusted proxies: **Single proxy IP:** ```bash TRUSTED_PROXY_IP=172.18.0.5 ``` **Docker network range:** ```bash TRUSTED_PROXY_IP=172.16.0.0/12 ``` **Multiple proxies:** ```bash TRUSTED_PROXY_IP=172.18.0.5,192.168.1.10 ``` **Important Notes:** - Invalid IPs/CIDRs are logged and ignored - Duplicates are automatically removed - The API extracts real client IPs from `X-Forwarded-For` header - If no whitelist is configured, all IPs are allowed (not recommended for production) ## Deployment Guide ### Deploy to Portainer 1. **Prepare your environment** - Ensure Vaultwarden is accessible from the Portainer host - Have your API credentials ready 2. **Create a new Stack in Portainer** - Navigate to Stacks → Add Stack - Name it `vaultwarden-api` - Paste the contents of `docker-compose.yml` 3. **Set environment variables in Portainer** - Click "Add environment variable" - Add all required variables (see Configuration table) - **RECOMMENDED**: Add `ALLOWED_IPS` with your IP address - **IMPORTANT**: Never hardcode secrets in the stack file 4. **Deploy the stack** - Click "Deploy the stack" - Monitor logs for any errors 5. **Set up reverse proxy** - Use Nginx Proxy Manager, Traefik, or similar - Configure HTTPS with Let's Encrypt - Point to `vaultwarden-api:8080` ### Deploy Behind Nginx Proxy Manager 1. **Add a new Proxy Host** - Domain: `api.yourdomain.com` - Forward to: `vaultwarden-api:8080` - Enable "Block Common Exploits" - Enable "Websockets Support" 2. **Configure SSL** - Request a new SSL certificate (Let's Encrypt) - Force SSL - Enable HTTP/2 3. **Add custom headers** (optional) ``` X-Real-IP $remote_addr X-Forwarded-For $proxy_add_x_forwarded_for X-Forwarded-Proto $scheme ``` ## Security Best Practices ### For Public Repositories - ✅ Use `.env.example` (committed) for documentation - ✅ Add `.env` to `.gitignore` (already done) - ✅ Never commit actual credentials - ✅ Use Portainer's environment variable feature - ✅ Rotate API keys regularly - ✅ Use strong, randomly generated API keys ### For Private Repositories - ✅ Still use `.env.example` for documentation - ✅ Still never commit `.env` files - ✅ Use secret managers (e.g., Docker secrets, Vault) - ✅ Implement additional authentication if needed - ✅ Monitor access logs ### Production Deployment 1. **Always use HTTPS** via reverse proxy 2. **Enable IP whitelisting** - Set `ALLOWED_IPS` to your trusted IPs/networks 3. **Configure trusted proxies** - Set `TRUSTED_PROXY_IP` to your reverse proxy range 4. **Restrict CORS** to specific domains via `CORS_ALLOWED_ORIGINS` 5. **Set ENVIRONMENT=production** to hide detailed error messages 6. **Use API key authentication** (not session tokens) for reliability 7. **Enable rate limiting** (already configured: 30 req/min) 8. **Use read-only filesystem** (already in docker-compose) 9. **Run as non-root user** (already in Dockerfile) 10. **Monitor logs** for suspicious activity and blocked IPs 11. **Use Docker secrets** instead of environment variables when possible 12. **Regular security updates** (rebuild images monthly) 13. **Enable DEBUG=false** in production (default) ## Usage Examples ### From a Shell Script ```bash #!/bin/bash API_KEY="your-api-key" API_URL="https://api.yourdomain.com" # Fetch database URL DB_URL=$(curl -s -H "Authorization: Bearer $API_KEY" \ "$API_URL/secret/DATABASE_URL" | jq -r '.value') echo "Database URL: $DB_URL" ``` ### From Python ```python import requests API_KEY = "your-api-key" API_URL = "https://api.yourdomain.com" def get_secret(name): headers = {"Authorization": f"Bearer {API_KEY}"} response = requests.get(f"{API_URL}/secret/{name}", headers=headers) return response.json()["value"] # Usage db_url = get_secret("DATABASE_URL") print(f"Database URL: {db_url}") ``` ### From Node.js ```javascript const axios = require('axios'); const API_KEY = 'your-api-key'; const API_URL = 'https://api.yourdomain.com'; async function getSecret(name) { const response = await axios.get(`${API_URL}/secret/${name}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); return response.data.value; } // Usage (async () => { const dbUrl = await getSecret('DATABASE_URL'); console.log(`Database URL: ${dbUrl}`); })(); ``` ## Makefile Commands ```bash make help # Show available commands make build # Build the binary make run # Run locally (requires .env) make dev # Run with hot reload make test # Run tests make clean # Clean build artifacts make docker-build # Build Docker image make docker-run # Run Docker container make docker-push # Push to Docker Hub make generate-api-key # Generate a random API key ``` ## Troubleshooting ### "Failed to load configuration: API_KEY is required" - Make sure you have a `.env` file in the project root - Copy `.env.example` to `.env` and fill in all required values ### "Secret not found" - Verify the secret name matches exactly (case-sensitive) - Check that your Vaultwarden token has access to the vault - Use the Vaultwarden web UI to confirm the secret exists ### "Vaultwarden api returned status 401" - Your credentials are invalid or expired - For API key method: Check `VAULTWARDEN_CLIENT_ID`, `VAULTWARDEN_CLIENT_SECRET`, `VAULTWARDEN_PASSWORD` - For session token: Generate a new `VAULTWARDEN_ACCESS_TOKEN` ### "IP blocked (not whitelisted)" - Your IP is not in the `ALLOWED_IPS` list - Check your current public IP: `curl ifconfig.me` - Add your IP to `ALLOWED_IPS` in `.env` or Portainer - If behind a proxy, ensure `TRUSTED_PROXY_IP` is set correctly - Enable `DEBUG=true` to see which IP is being detected ### "Failed to initialize Bitwarden CLI" - Ensure Bitwarden CLI is installed in the Docker container (already done) - Check that `VAULTWARDEN_URL` is accessible from the container - Verify your master password is correct - Check logs for specific error messages ### Docker build fails - Ensure you have the latest Go version in Dockerfile - Run `go mod tidy` before building - Check for network issues when downloading dependencies ## Recent Enhancements - ✅ **IP Whitelisting** - CIDR support, GitHub Actions IPs, automatic validation - ✅ **CLI Authentication** - Bitwarden CLI integration with API key support - ✅ **Trusted Proxies** - Proper client IP detection behind reverse proxies - ✅ **Input Validation** - Secure secret name validation - ✅ **Debug Logging** - Optional debug mode for troubleshooting ## Future Enhancements - [ ] Support for multiple Vaultwarden instances - [ ] JWT-based authentication (in addition to API keys) - [ ] Token rotation mechanism - [ ] Metrics and monitoring (Prometheus) - [ ] Support for secret versioning - [ ] Webhook notifications for secret changes - [ ] Redis-based distributed cache - [ ] Rate limiting per API key - [ ] Audit logging - [ ] GraphQL API ## Contributing Contributions are welcome! Please: 1. Fork the repository 2. Create a feature branch 3. Make your changes 4. Add tests if applicable 5. Submit a pull request ## License MIT License - see LICENSE file for details ## Disclaimer This is a security-sensitive tool. Please: - Review the code before deploying to production - Keep your API keys and tokens secure - Monitor access logs regularly - Report security issues privately --- **Made by Thijs Herman**