Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{$MCP_DOMAIN} {
encode zstd gzip
reverse_proxy metasploit-mcp:8085
}
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
73 changes: 73 additions & 0 deletions docker-compose.vps.yml
Original file line number Diff line number Diff line change
@@ -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:
61 changes: 61 additions & 0 deletions scripts/bootstrap-vps.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF
MCP_DOMAIN=$MCP_DOMAIN
MSF_PASSWORD=$MSF_PASSWORD
MCP_AUTH_TOKEN=$MCP_AUTH_TOKEN
MCP_ALLOW_ACTIVE_ACTIONS=false
MCP_ALLOW_SESSION_CONTROL=false
MCP_ALLOW_PAYLOAD_GENERATION=false
MCP_ALLOW_LISTENER_CONTROL=false
EOF

chmod 600 .env
echo "Created .env with random secrets."
else
echo ".env already exists; keeping existing configuration."
fi

echo "Pulling Metasploit and Caddy images..."
docker compose -f docker-compose.vps.yml --env-file .env pull

echo "Building MCP image..."
docker compose -f docker-compose.vps.yml --env-file .env build --pull metasploit-mcp

echo "Starting stack..."
docker compose -f docker-compose.vps.yml --env-file .env up -d

echo
echo "Stack status:"
docker compose -f docker-compose.vps.yml --env-file .env ps

echo
echo "MCP URL: https://$(grep '^MCP_DOMAIN=' .env | cut -d= -f2-)"
echo
echo "Health check:"
DOMAIN="$(grep '^MCP_DOMAIN=' .env | cut -d= -f2-)"
curl -fsS "https://$DOMAIN/healthz"
echo
Loading