diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..465c82d --- /dev/null +++ b/Caddyfile @@ -0,0 +1,4 @@ +{$MCP_DOMAIN} { + encode zstd gzip + reverse_proxy metasploit-mcp:8085 +} diff --git a/README.md b/README.md index 208d016..162c028 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,48 @@ Check liveness: curl http://127.0.0.1:8085/healthz ``` + +### One-command VPS deployment + +For a phone-friendly deployment, the repository includes a complete Docker Compose stack with: + +- Metasploit RPC running privately on the Compose network +- Metasploit MCP running behind the RPC service +- Caddy providing HTTPS on ports 80/443 +- Bearer authentication required for MCP HTTP access +- Persistent Metasploit and payload volumes +- Automatic container restarts + +The stack uses Rapid7's official Metasploit Framework image and pins it to a known release. (See the official image and release tags.) + +Requirements on the VPS: + +- Linux VPS with Docker Engine and Docker Compose v2 +- Public DNS A record pointing the chosen MCP domain to the VPS +- TCP ports 80 and 443 allowed through the VPS firewall +- amd64 CPU for the currently pinned Rapid7 image tag + +Deploy: + +```bash +git clone https://github.com/tupsonik/MetasploitMCP.git +cd MetasploitMCP +bash scripts/bootstrap-vps.sh +``` + +The bootstrap script creates `.env` with random Metasploit and MCP secrets when it does not already exist, builds the MCP image, pulls the Metasploit/Caddy images, and starts the stack. + +Check the stack: + +```bash +docker compose -f docker-compose.vps.yml --env-file .env ps +curl -fsS https://YOUR_DOMAIN/healthz +``` + +The Metasploit RPC port is intentionally not published to the VPS host. Only the MCP HTTPS endpoint is exposed publicly, and MCP bearer authentication remains enabled. + +For the first assessment, keep the high-impact capabilities disabled and enable only the specific capability needed for an authorized test. + ## Testing This project includes comprehensive unit and integration tests to ensure reliability and maintainability. diff --git a/docker-compose.vps.yml b/docker-compose.vps.yml new file mode 100644 index 0000000..2a2e9af --- /dev/null +++ b/docker-compose.vps.yml @@ -0,0 +1,73 @@ +services: + metasploit-rpc: + image: metasploitframework/metasploit-framework:6.5.5 + restart: unless-stopped + environment: + MSF_RPC_USER: msf + MSF_RPC_PASS: ${MSF_PASSWORD:?Set MSF_PASSWORD in .env} + command: + - msfrpcd + - -a + - 0.0.0.0 + - -p + - "55553" + - -S + - -f + expose: + - "55553" + volumes: + - msf-data:/home/msf/.msf4 + healthcheck: + test: + - CMD-SHELL + - ruby -rsocket -e 's=TCPSocket.new("127.0.0.1",55553); s.close' + interval: 10s + timeout: 5s + retries: 12 + start_period: 20s + + metasploit-mcp: + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + environment: + MSF_PASSWORD: ${MSF_PASSWORD:?Set MSF_PASSWORD in .env} + MSF_SERVER: metasploit-rpc + MSF_PORT: "55553" + MSF_SSL: "false" + MCP_REQUIRE_AUTH: "true" + MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN:?Set MCP_AUTH_TOKEN in .env} + MCP_ALLOW_ACTIVE_ACTIONS: ${MCP_ALLOW_ACTIVE_ACTIONS:-false} + MCP_ALLOW_SESSION_CONTROL: ${MCP_ALLOW_SESSION_CONTROL:-false} + MCP_ALLOW_PAYLOAD_GENERATION: ${MCP_ALLOW_PAYLOAD_GENERATION:-false} + MCP_ALLOW_LISTENER_CONTROL: ${MCP_ALLOW_LISTENER_CONTROL:-false} + PAYLOAD_SAVE_DIR: /app/payloads + depends_on: + metasploit-rpc: + condition: service_healthy + expose: + - "8085" + volumes: + - payloads:/app/payloads + + caddy: + image: caddy:2-alpine + restart: unless-stopped + environment: + MCP_DOMAIN: ${MCP_DOMAIN:?Set MCP_DOMAIN in .env} + depends_on: + - metasploit-mcp + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + +volumes: + msf-data: + payloads: + caddy-data: + caddy-config: diff --git a/scripts/bootstrap-vps.sh b/scripts/bootstrap-vps.sh new file mode 100644 index 0000000..2ce2a41 --- /dev/null +++ b/scripts/bootstrap-vps.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +cd "$(dirname "$0")/.." + +if ! command -v docker >/dev/null 2>&1; then + echo "Docker is required. Install Docker Engine + Compose plugin first." + exit 1 +fi + +if ! docker compose version >/dev/null 2>&1; then + echo "Docker Compose v2 is required." + exit 1 +fi + +if [[ ! -f .env ]]; then + read -r -p "MCP domain (DNS A record must already point here): " MCP_DOMAIN + if [[ -z "$MCP_DOMAIN" ]]; then + echo "MCP_DOMAIN is required." + exit 1 + fi + + MSF_PASSWORD="$(openssl rand -base64 36 | tr -dc 'A-Za-z0-9' | head -c 32)" + MCP_AUTH_TOKEN="$(openssl rand -hex 32)" + + cat > .env <