commit bf3ed29434c41916f6580073c218eba1012f9eb1 Author: Paul Kartchner Date: Wed Dec 10 03:41:06 2025 +0000 Initial Vaultwarden configuration with Amazon SES email - Docker Compose setup with Traefik integration - Amazon SES SMTP configuration (credentials in .env - not committed) - Email verification and testing scripts - Security: .gitignore excludes sensitive data (.env, data/, emailproxy-config/) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76e9fb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Sensitive configuration files +.env +*.env + +# Vaultwarden data directory (contains database and user data) +data/ + +# Email proxy configuration (contains OAuth credentials) +emailproxy-config/ + +# Temporary files +*.log +*.tmp + +# Python cache +__pycache__/ +*.pyc diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6bb2c73 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,60 @@ +services: + vaultwarden: + image: vaultwarden/server:latest + container_name: vaultwarden + restart: always + networks: + - traefik + volumes: + - ./data:/data + - /etc/localtime:/etc/localtime:ro + environment: + - DATABASE_URL=${DATABASE_URL} + - ADMIN_TOKEN=${ADMIN_TOKEN} + - SIGNUPS_ALLOWED=false + - DOMAIN=https://${DOMAIN} + - SMTP_HOST=${SMTP_HOST} + - SMTP_FROM=${SMTP_FROM} + - SMTP_PORT=${SMTP_PORT} + - SMTP_SECURITY=${SMTP_SECURITY} + - SMTP_USERNAME=${SMTP_USERNAME} + - SMTP_PASSWORD=${SMTP_PASSWORD} + labels: + - "traefik.enable=true" + + # HTTP to HTTPS redirect + - "traefik.http.routers.vaultwarden-http.rule=Host(`${DOMAIN}`)" + - "traefik.http.routers.vaultwarden-http.entrypoints=http" + - "traefik.http.routers.vaultwarden-http.service=vaultwarden" + - "traefik.http.routers.vaultwarden-http.priority=100" + - "traefik.http.routers.vaultwarden-http.middlewares=redirect-to-https@docker" + + # Main HTTP/HTTPS router + - "traefik.http.routers.vaultwarden.rule=Host(`${DOMAIN}`)" + - "traefik.http.routers.vaultwarden.entrypoints=https" + - "traefik.http.routers.vaultwarden.tls=true" + - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt" + - "traefik.http.routers.vaultwarden.service=vaultwarden" + - "traefik.http.services.vaultwarden.loadbalancer.server.port=80" + - "traefik.http.routers.vaultwarden.middlewares=geoblock@file,secure-headers@file,crowdsec-bouncer@file" + + # WebSocket router (required for real-time sync) + - "traefik.http.routers.vaultwarden-ws.rule=Host(`${DOMAIN}`) && Path(`/notifications/hub`)" + - "traefik.http.routers.vaultwarden-ws.entrypoints=https" + - "traefik.http.routers.vaultwarden-ws.tls=true" + - "traefik.http.routers.vaultwarden-ws.tls.certresolver=letsencrypt" + - "traefik.http.routers.vaultwarden-ws.service=vaultwarden-ws" + - "traefik.http.services.vaultwarden-ws.loadbalancer.server.port=3012" + - "traefik.http.routers.vaultwarden-ws.middlewares=geoblock@file,crowdsec-bouncer@file" + + # Admin panel router (restricted to internal IPs) + - "traefik.http.routers.vaultwarden-admin.rule=Host(`${DOMAIN}`) && PathPrefix(`/admin`)" + - "traefik.http.routers.vaultwarden-admin.entrypoints=https" + - "traefik.http.routers.vaultwarden-admin.tls=true" + - "traefik.http.routers.vaultwarden-admin.tls.certresolver=letsencrypt" + - "traefik.http.routers.vaultwarden-admin.service=vaultwarden" + - "traefik.http.routers.vaultwarden-admin.middlewares=internal-whitelist@file,crowdsec-bouncer@file" + +networks: + traefik: + external: true diff --git a/get_gmail_refresh_token.py b/get_gmail_refresh_token.py new file mode 100755 index 0000000..a8adda8 --- /dev/null +++ b/get_gmail_refresh_token.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +""" +Gmail OAuth2 Refresh Token Generator for Vaultwarden +This script helps you generate a refresh token for Gmail SMTP OAuth2 authentication. +""" + +from google_auth_oauthlib.flow import InstalledAppFlow +import sys + +SCOPES = ['https://www.googleapis.com/auth/gmail.send'] + +def get_refresh_token(client_id, client_secret): + """Generate OAuth2 refresh token for Gmail""" + + client_config = { + "installed": { + "client_id": client_id, + "client_secret": client_secret, + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "redirect_uris": ["http://localhost:8080/"] + } + } + + print("=" * 70) + print("Gmail OAuth2 Refresh Token Generator") + print("=" * 70) + print() + print("IMPORTANT: Make sure you've added this redirect URI to your Google Cloud Console:") + print(" http://localhost:8080/") + print() + print("A browser window will open for you to authorize the application.") + print("Please sign in with your Google Workspace account and grant access.") + print() + + try: + flow = InstalledAppFlow.from_client_config(client_config, SCOPES) + creds = flow.run_local_server(port=8080) + + print() + print("=" * 70) + print("SUCCESS! OAuth2 credentials obtained") + print("=" * 70) + print() + print("Add these to your Vaultwarden .env file:") + print() + print(f"SMTP_AUTH_MECHANISM=Xoauth2") + print(f"SMTP_OAUTH2_CLIENT_ID={client_id}") + print(f"SMTP_OAUTH2_CLIENT_SECRET={client_secret}") + print(f"SMTP_OAUTH2_REFRESH_TOKEN={creds.refresh_token}") + print() + print("=" * 70) + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + print() + print("Please enter your Google Cloud OAuth2 credentials:") + print("(You can get these from https://console.cloud.google.com)") + print() + + client_id = input("Enter your OAuth2 Client ID: ").strip() + client_secret = input("Enter your OAuth2 Client Secret: ").strip() + + if not client_id or not client_secret: + print("Error: Both Client ID and Client Secret are required!", file=sys.stderr) + sys.exit(1) + + get_refresh_token(client_id, client_secret) diff --git a/hash_token.py b/hash_token.py new file mode 100644 index 0000000..19fc202 --- /dev/null +++ b/hash_token.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +import sys +try: + from argon2 import PasswordHasher + from argon2.low_level import Type +except ImportError: + print("Installing argon2-cffi...") + import subprocess + subprocess.check_call([sys.executable, "-m", "pip", "install", "argon2-cffi"]) + from argon2 import PasswordHasher + from argon2.low_level import Type + +# OWASP preset parameters +ph = PasswordHasher( + time_cost=3, + memory_cost=65536, + parallelism=4, + hash_len=32, + salt_len=16, + type=Type.ID +) + +token = "gOvhQKzn47nfapPN6bDduFIM+HHYaIPu32n7/R/CjLZjfGFl1vUidpLByX1eu67s" +hash_result = ph.hash(token) + +# Replace $ with $$ for .env file format +env_hash = hash_result.replace('$', '$$') + +print("Argon2id hash generated successfully!") +print() +print("For .env file (with escaped $$):") +print(env_hash) +print() +print("For config.json (no escaping):") +print(hash_result)