From a610292ea23479cfce9b22df316daa1237ad8928 Mon Sep 17 00:00:00 2001 From: Alex Angin Date: Thu, 16 Oct 2025 00:50:33 -0400 Subject: [PATCH] Feature: credentials page & vaultwarden compose setup (#3534) Co-authored-by: Suchintan --- .env.example | 30 ++- .gitattributes | 27 +++ .yamlfmt | 10 + bitwarden-cli-server/Dockerfile | 29 +++ bitwarden-cli-server/README.md | 104 ++++++++++ bitwarden-cli-server/entrypoint.sh | 183 ++++++++++++++++++ docker-compose.yml | 57 +++++- fern/credentials/bitwarden.mdx | 106 ++++++++++ skyvern-frontend/src/router.tsx | 11 ++ .../routes/credentials/CredentialsPage.tsx | 37 ++++ skyvern-frontend/src/routes/root/SideNav.tsx | 6 + 11 files changed, 595 insertions(+), 5 deletions(-) create mode 100644 .gitattributes create mode 100644 .yamlfmt create mode 100644 bitwarden-cli-server/Dockerfile create mode 100644 bitwarden-cli-server/README.md create mode 100644 bitwarden-cli-server/entrypoint.sh diff --git a/.env.example b/.env.example index a338241e..11bf6c25 100644 --- a/.env.example +++ b/.env.example @@ -112,4 +112,32 @@ ANALYTICS_ID="anonymous" OP_SERVICE_ACCOUNT_TOKEN="" # Enable recording skyvern logs as artifacts -ENABLE_LOG_ARTIFACTS=false \ No newline at end of file +ENABLE_LOG_ARTIFACTS=false + +# ============================================================================= +# SKYVERN BITWARDEN CONFIGURATION +# ============================================================================= +# Your organization ID in official Bitwarden server or vaultwarden (if using organizations) +SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here + +# These should match the values for bitwarden cli server for consistency +SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here +SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here +SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here + +# The CLI server will run on localhost:8002 by default +# Optional, because by default Bitwarden is used directly +# BITWARDEN_SERVER=http://localhost +# BITWARDEN_SERVER_PORT=8002 + +# ============================================================================= +# OPTIONAL: ADDITIONAL SKYVERN CONFIGURATION +# ============================================================================= +# If you need to override the default Bitwarden server settings in Skyvern +# These will be automatically set by the Docker Compose, but you can override them here + +# Maximum number of retries for Bitwarden operations +# BITWARDEN_MAX_RETRIES=3 + +# Timeout in seconds for Bitwarden operations +# BITWARDEN_TIMEOUT_SECONDS=60 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a5aab2e3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,27 @@ +# Set default behavior to automatically normalize line endings +* text=auto + +# Force Unix LF line endings for shell scripts +*.sh text eol=lf +bitwarden-cli-server/entrypoint.sh text eol=lf + +# Force Unix LF line endings for Python files +*.py text eol=lf + +# Force Unix LF line endings for Docker files +Dockerfile text eol=lf +*.dockerfile text eol=lf + +# Force Unix LF line endings for YAML and config files +*.yml text eol=lf +*.yaml text eol=lf +*.json text eol=lf +*.md text eol=lf + +# Binary files +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary diff --git a/.yamlfmt b/.yamlfmt new file mode 100644 index 00000000..509e8773 --- /dev/null +++ b/.yamlfmt @@ -0,0 +1,10 @@ +# yamlfmt configuration +# Force Unix LF line endings for all YAML files +line_ending: lf + +# Additional formatting options +formatter: + type: basic + indent: 2 + include_document_start: false + pad_line_comments: 1 diff --git a/bitwarden-cli-server/Dockerfile b/bitwarden-cli-server/Dockerfile new file mode 100644 index 00000000..85d56b21 --- /dev/null +++ b/bitwarden-cli-server/Dockerfile @@ -0,0 +1,29 @@ +FROM node:24-alpine + +# Install dependencies +# Install Bitwarden CLI +RUN apk add --no-cache curl bash && \ + npm install -g @bitwarden/cli + +# Create non-root user for security +# Create directory for Bitwarden config +RUN addgroup -g 1001 -S bw && adduser -S bw -u 1001 -G bw && \ + mkdir -p /app/.config && \ + chown -R bw:bw /app/.config + +# Copy entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Switch to non-root user +USER bw +WORKDIR /app + +# Expose port for bw serve +EXPOSE 8087 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8087/status || exit 1 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/bitwarden-cli-server/README.md b/bitwarden-cli-server/README.md new file mode 100644 index 00000000..05ff7e60 --- /dev/null +++ b/bitwarden-cli-server/README.md @@ -0,0 +1,104 @@ +# Bitwarden CLI Server for Skyvern + +This Docker setup provides a Bitwarden CLI server with `bw serve` functionality that enables Skyvern to work with vaultwarden (or official Bitwarden) instances. + +## Architecture + +```text +Usual setup (in cloud): +Skyvern → official Bitwarden + +Local from docker compose: +Skyvern → bw serve (CLI Server) → vaultwarden Server +``` + +The CLI server acts as a bridge between Skyvern and vaultwarden, providing the REST API endpoints that Skyvern expects. + +## Setup + +This container is part of the main Skyvern Docker Compose setup. Configure your environment variables in the main `.env` file: + +```bash +# Skyvern Bitwarden Configuration +SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here +SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here +SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here +SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here + +# Vaultwarden Configuration +BW_HOST=https://your-vaultwarden-server.com +BW_CLIENTID=${SKYVERN_AUTH_BITWARDEN_CLIENT_ID} +BW_CLIENTSECRET=${SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET} +BW_PASSWORD=${SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD} +``` + +Then start the service: + +```bash +docker-compose up -d bitwarden-cli +``` + +## Available Endpoints + +Once running, the CLI server provides these endpoints on port 8002: + +- `GET /status` - Check server status +- `POST /unlock` - Unlock vault +- `GET /list/object/items` - List vault items +- `GET /object/item/{id}` - Get specific item +- `POST /object/item` - Create new item +- `GET /object/template/item` - Get item template +- And more... + +## Troubleshooting + +### Container won't start + +1. **Check logs**: + ```bash + docker-compose -f docker-compose.bitwarden.yml logs bitwarden-cli + ``` + +2. **Common issues**: + - Invalid API credentials + - Wrong vaultwarden server URL + - Network connectivity issues + - Incorrect master password + +### Health check fails + +The container includes a health check that calls `/status`. If it fails: + +1. Check if the CLI server is actually running inside the container +2. Verify the unlock process succeeded +3. Check network configuration + +### API calls fail + +1. **Test the CLI server directly**: + ```bash + # Check status + curl http://localhost:8002/status + + # List items (after unlock) + curl http://localhost:8002/list/object/items + ``` + +2. **Check Skyvern configuration**: + - Ensure `BITWARDEN_SERVER` points to the CLI server + - Verify `BITWARDEN_SERVER_PORT` is correct + +## Security Notes + +- The container runs as a non-root user for security +- Only binds to localhost by default +- API credentials are passed via environment variables +- Consider using Docker secrets for production deployments + +## Production Considerations + +1. **Secrets Management**: Use Docker secrets or external secret management +2. **Monitoring**: Add proper logging and monitoring +3. **Backup**: Ensure your vaultwarden instance is properly backed up +4. **Updates**: Regularly update the Bitwarden CLI version +5. **Network Security**: Use proper network isolation and firewalls \ No newline at end of file diff --git a/bitwarden-cli-server/entrypoint.sh b/bitwarden-cli-server/entrypoint.sh new file mode 100644 index 00000000..ce315637 --- /dev/null +++ b/bitwarden-cli-server/entrypoint.sh @@ -0,0 +1,183 @@ +#!/bin/bash +set -euo pipefail + +# Color codes for better logging +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Update log function to use color codes +log() { + echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" +} + +log_error() { + echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" +} + +log "Starting entrypoint script..." +log "Current user: $(whoami)" +log "Current directory: $(pwd)" + +# Check required environment variables +if [[ -z "${BW_HOST:-}" ]]; then + log_error "BW_HOST environment variable is required" + exit 1 +fi + +if [[ -z "${BW_CLIENTID:-}" ]]; then + log_error "BW_CLIENTID environment variable is required" + exit 1 +fi + +if [[ -z "${BW_CLIENTSECRET:-}" ]]; then + log_error "BW_CLIENTSECRET environment variable is required" + exit 1 +fi + +if [[ -z "${BW_PASSWORD:-}" ]]; then + log_error "BW_PASSWORD environment variable is required" + exit 1 +fi + +# Test network connectivity first +log "Testing connectivity to vaultwarden server: $BW_HOST" +if ! curl -s --connect-timeout 10 "$BW_HOST" > /dev/null; then + log_warning "Cannot reach $BW_HOST - this might be normal if the server doesn't respond to GET requests" +fi + +# Logout first to clear any existing session +log "Logging out to clear any existing session..." +bw logout > /dev/null 2>&1 || true # Ignore errors if not logged in + +# Configure Bitwarden CLI to use vaultwarden server +log "Configuring Bitwarden CLI to use server: $BW_HOST" + +# Temporarily disable pipefail to capture the output properly +set +e +config_output=$(bw config server "$BW_HOST" 2>&1) +config_result=$? +set -e + +log "Config command result: $config_result" +log "Config command output: $config_output" + +if [[ $config_result -ne 0 ]]; then + log_error "Failed to configure server. Error output:" + log_error "$config_output" + exit 1 +fi + +log_success "Server configuration successful" + +# Login using API key with retry logic for rate limiting +log "Logging in to Bitwarden using API key..." + +# Retry login with exponential backoff +max_retries=3 +retry_count=0 +login_success=false + +while [[ $retry_count -lt $max_retries ]]; do + if [[ $retry_count -gt 0 ]]; then + delay=$((retry_count * retry_count * 5)) # 5, 20, 45 seconds + log "Rate limited. Waiting ${delay} seconds before retry $((retry_count + 1))/$max_retries..." + sleep $delay + fi + + set +e + login_output=$(bw login --apikey 2>&1) + login_result=$? + set -e + + log "Login attempt $((retry_count + 1)): result=$login_result" + log "Login output: '$login_output'" + + if [[ $login_result -eq 0 ]]; then + login_success=true + break + elif [[ "$login_output" == *"Rate limit exceeded"* ]]; then + log_warning "Rate limit exceeded on attempt $((retry_count + 1))" + ((retry_count++)) + else + log_error "Failed to login with API key. Error output:" + log_error "$login_output" + log_error "Please check:" + log_error "1. BW_HOST is correct and accessible: $BW_HOST" + log_error "2. BW_CLIENTID is valid: ${BW_CLIENTID:0:20}..." + log_error "3. BW_CLIENTSECRET is correct" + log_error "4. API key is enabled in vaultwarden" + exit 1 + fi +done + +if [[ "$login_success" != "true" ]]; then + log_error "Failed to login after $max_retries attempts due to rate limiting" + log_error "Please wait a few minutes and try again" + exit 1 +fi + +log_success "Successfully logged in" + +# Now unlock to get the session token +log "Unlocking vault to get session token..." +set +e +unlock_output=$(bw unlock --passwordenv BW_PASSWORD --raw 2>&1) +unlock_result=$? +set -e + +log "Unlock command result: $unlock_result" +log "Unlock command output: '$unlock_output'" + +if [[ $unlock_result -ne 0 ]]; then + log_error "Failed to unlock vault. Error output:" + log_error "$unlock_output" + log_error "Please check BW_PASSWORD is correct" + exit 1 +fi + +# Extract session token from unlock output +export BW_SESSION="$unlock_output" +log "Session token length: ${#BW_SESSION}" + +if [[ -z "$BW_SESSION" ]]; then + log_error "Session token is empty after unlock" + log_error "Raw unlock output was: '$unlock_output'" + exit 1 +fi + +log_success "Vault unlocked successfully" + +# Sync vault +log "Syncing vault..." +bw sync --session "$BW_SESSION" > /dev/null 2>&1 || { + log_warning "Sync failed, but continuing anyway" +} + +log_success "Vault sync completed" + +# Start the server +log "Starting Bitwarden CLI server on port 8087..." +log "Server will be accessible at http://localhost:8087" +log "Available endpoints:" +log " - GET /status - Check server status" +log " - POST /unlock - Unlock vault" +log " - GET /list/object/items - List vault items" +log " - GET /object/item/{id} - Get specific item" +log " - And more..." + +# Start bw serve with proper error handling +exec bw serve --hostname 0.0.0.0 --port 8087 --session "$BW_SESSION" || { + log_error "Failed to start Bitwarden CLI server" + exit 1 +} diff --git a/docker-compose.yml b/docker-compose.yml index b4dfc93c..662f5b2b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -114,10 +114,11 @@ services: # Bitwarden Settings # If you are looking to integrate Skyvern with a password manager (eg Bitwarden), you can use the following environment variables. # - BITWARDEN_SERVER=http://localhost # OPTIONAL IF YOU ARE SELF HOSTING BITWARDEN - # - BITWARDEN_SERVER_PORT=8002 # OPTIONAL IF YOU ARE SELF HOSTING BITWARDEN - # - BITWARDEN_CLIENT_ID=FILL_ME_IN_PLEASE - # - BITWARDEN_CLIENT_SECRET=FILL_ME_IN_PLEASE - # - BITWARDEN_MASTER_PASSWORD=FILL_ME_IN_PLEASE + # - BITWARDEN_SERVER_PORT=8002 # IF YOU ARE SELF HOSTING BITWARDEN AND USE THIS COMPOSE FILE, PORT IS 8002 UNLESS CHANGED + # - SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here + # - SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here + # - SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here + # - SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here # 1Password Integration # If you are looking to integrate Skyvern with 1Password, you can use the following environment variables. @@ -158,3 +159,51 @@ services: depends_on: skyvern: condition: service_healthy + +# uncomment for local usage of `vaultwarden` & bitwarden-cli - see more at: https://github.com/dani-garcia/vaultwarden +# First this container needs to be started and configured to sign up, create master password and organization +# Once created, under SETTINGS/SECURITY/KEYS/API you should be able to get client id and secret for CLI & Skyvern integrations +# vaultwarden: +# image: vaultwarden/server:latest-alpine +# container_name: vaultwarden +# restart: unless-stopped +# environment: +# # DOMAIN: "https://vaultwarden.example.com" # required when using a reverse proxy; your domain; vaultwarden needs to know it's https to work properly with attachments +# SIGNUPS_ALLOWED: "true" # Deactivate this with "false" after you have created your account so that no strangers can register +# volumes: +# - ~/vw-data/:/data/ # the path before the : can be changed +# ports: +# - 127.0.0.1:11002:80 # you can replace the 11002 with your preferred port + +# Bitwarden CLI Server (provides REST API endpoints for Skyvern) +# Once you have master password and api credentials, you can set them below and this CLI should start providing secure access for Skyvern to Vaultwarden +# bitwarden-cli: +# build: +# context: ./bitwarden-cli-server +# dockerfile: Dockerfile +# environment: +# # Vaultwarden server URL +# BW_HOST: "http://vaultwarden" +# # API credentials for vaultwarden +# BW_CLIENTID: "user.your-client-id-here" +# BW_CLIENTSECRET: "your-client-secret-here" +# # Master password for unlocking vault +# BW_PASSWORD: "your-master-password-here" +# ports: +# # Bind to localhost only for security +# - "127.0.0.1:8002:8087" +# restart: unless-stopped +# healthcheck: +# test: [ "CMD", "curl", "-f", "http://localhost:8087/status" ] +# interval: 30s +# timeout: 10s +# retries: 5 +# start_period: 30s +# depends_on: +# vaultwarden: +# condition: service_healthy +# volumes: +# # Optional: persist Bitwarden CLI config +# - ~/bitwarden-cli-config:/app/.config +# labels: +# - "traefik.enable=false" # Don't expose via reverse proxy diff --git a/fern/credentials/bitwarden.mdx b/fern/credentials/bitwarden.mdx index 03bbf351..323d818f 100644 --- a/fern/credentials/bitwarden.mdx +++ b/fern/credentials/bitwarden.mdx @@ -40,3 +40,109 @@ Please contact sales@skyvern.com to set up the integration for this step. ### Bitwarden Integration in Open Source + +Skyvern can integrate with self-hosted Bitwarden-compatible services like [vaultwarden](https://github.com/dani-garcia/vaultwarden). Since vaultwarden only implements the client API (not the server endpoints), we use a Bitwarden CLI server as a bridge. + +#### Architecture + +```text +Skyvern → bw serve (CLI Server) → vaultwarden +``` + +The CLI server provides the REST API endpoints that Skyvern expects, while connecting to your vaultwarden instance. + +#### Quick Setup + +**Step 1: Get vaultwarden API Credentials** + +1. Log into your vaultwarden web interface +2. Go to **Account Settings → Security → API Key** +3. Click **View API Key** +4. Save the `client_id` and `client_secret` + +**Step 2: Configure Environment Variables** + +Add these to your `.env` file: + +```bash +# Skyvern Bitwarden Configuration +SKYVERN_AUTH_BITWARDEN_ORGANIZATION_ID=your-org-id-here +SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD=your-master-password-here +SKYVERN_AUTH_BITWARDEN_CLIENT_ID=user.your-client-id-here +SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET=your-client-secret-here + +# Vaultwarden Configuration +BW_HOST=https://your-vaultwarden-server.com +BW_CLIENTID=${SKYVERN_AUTH_BITWARDEN_CLIENT_ID} +BW_CLIENTSECRET=${SKYVERN_AUTH_BITWARDEN_CLIENT_SECRET} +BW_PASSWORD=${SKYVERN_AUTH_BITWARDEN_MASTER_PASSWORD} + +# CLI Server Configuration (defaults are correct) +BITWARDEN_SERVER=http://localhost +BITWARDEN_SERVER_PORT=8002 +``` + +**Step 3: Start the Services** + +The Bitwarden CLI server is included in the main Docker Compose setup: + +```bash +docker-compose up -d bitwarden-cli +``` + +**Step 4: Verify Setup** + +Test that the CLI server is working: + +```bash +# Check status +curl http://localhost:8002/status + +# List items from your vault +curl http://localhost:8002/list/object/items +``` + +#### How It Works + +1. **vaultwarden** - Your existing password manager server +2. **bitwarden-cli container** - Runs `bw serve` to provide REST API endpoints +3. **Skyvern** - Uses the CLI server's REST API to access credentials + +#### Available API Endpoints + +The CLI server provides these endpoints on port 8002: + +- `GET /status` - Server status +- `POST /unlock` - Unlock vault +- `GET /list/object/items` - List all items +- `GET /object/item/{id}` - Get specific item +- `POST /object/item` - Create new item +- `GET /object/template/item` - Get item template + +#### Troubleshooting + +**CLI Server Won't Start** + +Check the container logs: +```bash +docker-compose logs bitwarden-cli +``` + +Common issues: +- Invalid API credentials +- Wrong vaultwarden server URL +- Network connectivity issues +- Incorrect master password + +**Skyvern Can't Connect** + +1. Verify CLI server is running: `curl http://localhost:8002/status` +2. Check that `BITWARDEN_SERVER=http://localhost` and `BITWARDEN_SERVER_PORT=8002` +3. Ensure proper organization ID and credentials are set + +#### Security Notes + +- The CLI container runs as a non-root user +- Only binds to localhost by default for security +- Vault remains encrypted until explicitly unlocked +- Uses API key authentication with vaultwarden diff --git a/skyvern-frontend/src/router.tsx b/skyvern-frontend/src/router.tsx index cbf15c21..edc930f8 100644 --- a/skyvern-frontend/src/router.tsx +++ b/skyvern-frontend/src/router.tsx @@ -26,6 +26,7 @@ import { WorkflowRunOverview } from "./routes/workflows/workflowRun/WorkflowRunO import { WorkflowRunRecording } from "./routes/workflows/workflowRun/WorkflowRunRecording"; import { WorkflowRunCode } from "@/routes/workflows/workflowRun/WorkflowRunCode"; import { DebugStoreProvider } from "@/store/DebugStoreContext"; +import { CredentialsPage } from "@/routes/credentials/CredentialsPage.tsx"; const router = createBrowserRouter([ { @@ -201,6 +202,16 @@ const router = createBrowserRouter([ }, ], }, + { + path: "credentials", + element: , + children: [ + { + index: true, + element: , + }, + ], + }, ], }, ]); diff --git a/skyvern-frontend/src/routes/credentials/CredentialsPage.tsx b/skyvern-frontend/src/routes/credentials/CredentialsPage.tsx index d571455e..56d99ab2 100644 --- a/skyvern-frontend/src/routes/credentials/CredentialsPage.tsx +++ b/skyvern-frontend/src/routes/credentials/CredentialsPage.tsx @@ -56,6 +56,43 @@ function CredentialsPage() { + + {/* Footer note */} +
+
+ Note: This feature requires a Bitwarden-compatible + server ({" "} + + self-hosted Bitwarden + {" "} + ) or{" "} + + this community version + {" "} + or a paid Bitwarden account. Make sure the relevant + `SKYVERN_AUTH_BITWARDEN_*` environment variables are configured. See + details{" "} + + here + + . +
+
); } diff --git a/skyvern-frontend/src/routes/root/SideNav.tsx b/skyvern-frontend/src/routes/root/SideNav.tsx index 2d5cd09b..6c2c49f2 100644 --- a/skyvern-frontend/src/routes/root/SideNav.tsx +++ b/skyvern-frontend/src/routes/root/SideNav.tsx @@ -7,6 +7,7 @@ import { GearIcon, LightningBoltIcon, } from "@radix-ui/react-icons"; +import { KeyIcon } from "@/components/icons/KeyIcon.tsx"; function SideNav() { const { collapsed } = useSidebarStore(); @@ -45,6 +46,11 @@ function SideNav() { to: "/settings", icon: , }, + { + label: "Credentials", + to: "/credentials", + icon: , + }, ]} />