warden-worker/README.md
2025-11-29 05:49:28 +00:00

12 KiB

Warden: A Bitwarden-compatible server for Cloudflare Workers

This project provides a self-hosted, Bitwarden-compatible server that can be deployed to Cloudflare Workers for free. It's designed to be low-maintenance, allowing you to "deploy and forget" without worrying about server management or recurring costs.

Why another Bitwarden server?

While projects like Vaultwarden provide excellent self-hosted solutions, they still require you to manage a server or VPS. This can be a hassle, and if you forget to pay for your server, you could lose access to your passwords.

Warden aims to solve this problem by leveraging the Cloudflare Workers ecosystem. By deploying Warden to a Cloudflare Worker and using Cloudflare D1 for storage, you can have a completely free, serverless, and low-maintenance Bitwarden server.

Features

  • Core Vault Functionality: All your basic vault operations are supported, including creating, reading, updating, and deleting ciphers and folders.
  • TOTP Support: Store and generate Time-based One-Time Passwords for your accounts.
  • Bitwarden Compatible: Works with the official Bitwarden browser extensions and Android app (iOS is untested).
  • Free to Host: Runs on Cloudflare's free tier.
  • Low Maintenance: Deploy it once and forget about it.
  • Secure: Your data is stored in your own Cloudflare D1 database.
  • Easy to Deploy: Get up and running in minutes with the Wrangler CLI.

Current Status

This project is not yet feature-complete. It currently supports the core functionality of a personal vault, including TOTP. However, it does not support the following features:

  • Sharing
  • Bitwarden Send
  • Organizations
  • Other Bitwarden advanced features

There are no immediate plans to implement these features. The primary goal of this project is to provide a simple, free, and low-maintenance personal password manager.

Compatibility

  • Browser Extensions: Chrome, Firefox, Safari, etc.
  • Android App: The official Bitwarden Android app.
  • iOS App: Untested. If you have an iOS device, please test and report your findings!

Getting Started

Prerequisites

  • A Cloudflare account.
  • The Wrangler CLI installed and configured.

Deployment

  1. Clone the repository:

    git clone https://github.com/your-username/warden-worker.git
    cd warden-worker
    
  2. Create a D1 Database:

    wrangler d1 create warden-db
    
  3. Configure your Database ID:

    When you create a D1 database, Wrangler will output the database_id. To avoid committing this secret to your repository, this project uses an environment variable to configure the database ID.

    You have two options:

    Option 1: (Recommended) Use a .env file:

    Create a file named .env in the root of the project and add the following line, replacing the placeholder with your actual database_id:

    D1_DATABASE_ID="your-database-id-goes-here"
    

    Make sure to add the .env file to your .gitignore file to prevent it from being committed to git.

    Option 2: Set an environment variable in your shell:

    You can set the environment variable in your shell before deploying:

    export D1_DATABASE_ID="your-database-id-goes-here"
    wrangler deploy
    
  4. Deploy the worker:

    wrangler deploy
    

    This will deploy the worker and set up the necessary database tables.

  5. Set environment variables

  • ALLOWED_EMAILS your-email@example.com
  • JWT_SECRET a long random string
  • JWT_REFRESH_SECRET a long random string
  1. Configure your Bitwarden client:

    In your Bitwarden client, go to the self-hosted login screen and enter the URL of your deployed worker (e.g., https://warden-worker.your-username.workers.dev).

Configuration

This project requires minimal configuration. The main configuration is done in the wrangler.toml file, where you specify your D1 database binding.

Other Environment Variables

You can configure the following environment variables in wrangler.toml under the [vars] section, or set them via Cloudflare Dashboard:

  • TRASH_AUTO_DELETE_DAYS (Optional, Default: 30)

    Number of days to keep soft-deleted items before automatically purging them. When a cipher is deleted, it's marked with a deleted_at timestamp (soft delete). After the specified number of days, the item will be permanently removed from the database.

    • Set to 0 or a negative value to disable automatic purging
    • Defaults to 30 days if not specified
    • Example: TRASH_AUTO_DELETE_DAYS = "7" to keep deleted items for 7 days
  • IMPORT_BATCH_SIZE (Optional, Default: 30)

    Number of records to process in each batch when importing data. This helps manage memory usage and processing time for large imports.

    • Set to 0 to disable batching (all records imported in a single batch)
    • Defaults to 30 records per batch if not specified
    • Example: IMPORT_BATCH_SIZE = "50" to process 50 records per batch

Scheduled Tasks (Cron)

The worker includes a scheduled task that runs automatically to clean up soft-deleted items. By default, this task runs daily at 03:00 UTC.

  • Automatic Cleanup: The scheduled task automatically purges ciphers that have been soft-deleted for longer than the TRASH_AUTO_DELETE_DAYS period
  • Schedule: Configured in wrangler.toml under [triggers] section with cron expression "0 3 * * *" (daily at 03:00 UTC)

You can modify the cron schedule in wrangler.toml if you want to run the cleanup task at a different time or frequency. See Cloudflare Cron Triggers documentation for cron expression syntax.

Database Backup (GitHub Actions)

This project includes a GitHub Action workflow that automatically backs up your D1 database to S3-compatible storage daily. The backup runs at 04:00 UTC (1 hour after the cleanup task).

⚠️ Important Notes:

  • Manual trigger required for first run: You must manually trigger the Action once (GitHub Actions → Backup D1 Database to S3 → Run workflow) before scheduled backups will run automatically.
  • Ensure your S3 bucket is set to private access to prevent data leaks and avoid unnecessary public traffic costs.
  • ⚠️ CRITICAL: Do NOT use R2 from the same Cloudflare account as your Worker for backups. If your Cloudflare account gets suspended or banned, you will lose access to both your Worker and your backup storage, resulting in complete data loss. Always use a separate Cloudflare account or a different S3-compatible storage provider (AWS S3, Backblaze B2, MinIO, etc.) for backups to ensure redundancy and disaster recovery.

Required Secrets for Backup

Add the following secrets to your GitHub repository (Settings > Secrets and variables > Actions):

Secret Required Description
S3_ACCESS_KEY_ID yes Your S3 access key ID
S3_SECRET_ACCESS_KEY yes Your S3 secret access key
S3_BUCKET yes The S3 bucket name for storing backups
S3_REGION yes The S3 region (e.g., us-east-1). If unsure, use auto
S3_ENDPOINT no Custom S3 endpoint URL. Defaults to AWS S3 if not set. Required for S3-compatible services (MinIO, Cloudflare R2, Backblaze B2, etc.)
BACKUP_ENCRYPTION_KEY no Optional encryption passphrase. If set, backups will be encrypted with AES-256. Strongly recommended since the database contains unencrypted user metadata (emails, item counts)

Backup Features

  • Automatic Daily Backups: Production database is backed up daily at 04:00 UTC
  • Manual Trigger: You can manually trigger a backup from the GitHub Actions tab
  • Environment Selection: When triggering manually, you can choose to backup either production or dev database
  • Compression: Backups are compressed using gzip to save storage space
  • Optional Encryption: If BACKUP_ENCRYPTION_KEY is set, backups are encrypted with AES-256-CBC (PBKDF2 key derivation, 100k iterations)
  • Automatic Cleanup: Old backups older than 30 days are automatically deleted
  • S3-Compatible: Works with AWS S3, Cloudflare R2, MinIO, Backblaze B2, and any S3-compatible storage

Backup File Location

Backups are stored in your S3 bucket with the following structure:

# Unencrypted backups
s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz

# Encrypted backups (when BACKUP_ENCRYPTION_KEY is set)
s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc

Decrypting Backups

If you enabled encryption, use the following command to decrypt a backup:

openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 \
  -in vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc \
  -out backup.sql.gz \
  -pass pass:"YOUR_ENCRYPTION_KEY"

# Then decompress
gunzip backup.sql.gz

Restoring Database to Cloudflare D1

To restore your D1 database from a backup:

  1. Download the backup from S3:

    # Using AWS CLI
    aws s3 cp s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc ./
    
    # Or with custom endpoint (e.g., R2, MinIO)
    aws s3 cp s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc ./ \
      --endpoint-url https://your-s3-endpoint.com
    
  2. Decrypt the backup (if encrypted):

    openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 \
      -in vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc \
      -out backup.sql.gz \
      -pass pass:"YOUR_ENCRYPTION_KEY"
    
  3. Decompress the backup:

    gunzip backup.sql.gz
    
  4. Restore to Cloudflare D1:

    First, find your database name using wrangler:

    wrangler d1 list
    

    This will show a table with your databases. Look for the name column (e.g., warden-db for production or warden-dev for dev).

    Then restore the backup:

    # Replace DATABASE_NAME with your actual database name (e.g., warden-db)
    
    # First, you may want to clear the existing database (optional, use with caution!)
    # wrangler d1 execute DATABASE_NAME --remote --command "DELETE FROM ciphers; DELETE FROM folders; DELETE FROM users;"
    
    # Import the backup
    wrangler d1 execute DATABASE_NAME --remote --file=backup.sql
    

    Note: The --remote flag is required to execute against your production D1 database. Without it, the command will run against the local development database.

Local Development with D1

You can run this Worker locally with full D1 database support using Wrangler. This is useful for development, testing, or as a temporary fallback when Cloudflare services are unavailable.

To run locally with your production data (useful as emergency fallback):

  1. Download and decrypt your backup (follow the steps above)

  2. Import the backup to local D1:

    # Without --remote flag, this imports to local database
    wrangler d1 execute vault1 --file=backup.sql
    
  3. Start the local server with persistence:

    wrangler dev --persist
    
  4. Configure your Bitwarden client to use http://localhost:8787 (or your local network IP for mobile devices)

Accessing Local SQLite Database Directly

The local D1 database is stored as a SQLite file. You can access it directly:

# Find the database file
ls .wrangler/state/v3/d1/

# Open with SQLite CLI
sqlite3 .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite

# Example: List all users
sqlite> SELECT email FROM users;

Note: The local development environment requires Node.js and Wrangler installed. The Worker runs in a simulated environment using workerd, Cloudflare's open-source Workers runtime.

Contributing

Contributions are welcome! If you find a bug, have a feature request, or want to improve the code, please open an issue or submit a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.