Sync quickstart.sh with deploy.sh security fixes; add production + quickstart test scenarios

quickstart.sh:
- admin localhost:2019 (was 0.0.0.0:2019 — exposed admin API publicly)
- Add /_synapse/admin block returning 403
- Forward Host + X-Forwarded-Host headers to MAS on all proxy blocks
- handle /account/* (was handle_path — stripped prefix, broke MAS SPA routing)
- Add allow_guest_access/allow_public_rooms_* false to Synapse config
- sudo chown synapse/data/ after docker run generate (sed -i needs ownership)

deploy.sh:
- Add SKIP_START=true env var to skip docker compose up (enables config-only CI testing)

test_deploy.sh:
- Scenario P: production Caddyfile assertions (caddy/Caddyfile.production)
- Scenario Q: quickstart.sh config assertions
- assert_quickstart_configs(): 15 assertions covering all previously-missed security properties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wmair
2026-03-30 09:08:50 +02:00
parent 6d9b156738
commit 9dc60194c7
3 changed files with 123 additions and 6 deletions
+7
View File
@@ -1461,6 +1461,11 @@ echo ""
# Step 14: Start the stack
echo -e "${BLUE}[14/14] Starting the Matrix stack...${NC}"
if [[ "${SKIP_START:-false}" == "true" ]]; then
print_info "Skipping stack start (SKIP_START=true)"
else
print_info "Using compose file: ${COMPOSE_FILE}"
print_info "This may take a few minutes on first run..."
echo ""
@@ -1563,6 +1568,8 @@ if [[ "$DEPLOYMENT_MODE" == "local" ]]; then
echo ""
fi
fi # end SKIP_START
# ============================================================================
# PRODUCTION: Generate Caddy and Authelia configs for separate machines
# ============================================================================
+36 -6
View File
@@ -270,6 +270,7 @@ if [[ ! -f "synapse/data/homeserver.yaml" ]]; then
-e SYNAPSE_SERVER_NAME="${MATRIX_DOMAIN}" \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate 2>/dev/null
sudo chown -R "$(id -u):$(id -g)" synapse/data/
ok "Synapse config generated"
fi
@@ -297,6 +298,9 @@ database:
cp_max: 10
enable_registration: false
allow_guest_access: false
allow_public_rooms_without_auth: false
allow_public_rooms_over_federation: false
matrix_authentication_service:
enabled: true
@@ -337,7 +341,7 @@ fi
cat > caddy/Caddyfile << EOF
{
email ${LETSENCRYPT_EMAIL}
admin 0.0.0.0:2019
admin localhost:2019
}
${MATRIX_DOMAIN} {
@@ -370,6 +374,8 @@ ${MATRIX_DOMAIN} {
handle @compat {
header Access-Control-Allow-Origin "*"
reverse_proxy mas:8080 {
header_up Host {http.request.host}
header_up X-Forwarded-Host {http.request.host}
header_down -Access-Control-Allow-Origin
}
}
@@ -382,6 +388,11 @@ ${MATRIX_DOMAIN} {
}
}
# Block public access to Synapse admin API
handle /_synapse/admin* {
respond "Forbidden" 403
}
handle {
reverse_proxy synapse:8008
}
@@ -391,21 +402,33 @@ ${AUTH_DOMAIN} {
@disco path /.well-known/openid-configuration
handle @disco {
header Access-Control-Allow-Origin "*"
reverse_proxy mas:8080
reverse_proxy mas:8080 {
header_up Host {http.request.host}
header_up X-Forwarded-Host {http.request.host}
}
}
@oauth path /oauth2/*
route @oauth {
header Access-Control-Allow-Origin "*"
reverse_proxy mas:8080
reverse_proxy mas:8080 {
header_up Host {http.request.host}
header_up X-Forwarded-Host {http.request.host}
}
}
handle_path /account/* {
reverse_proxy mas:8080
handle /account/* {
reverse_proxy mas:8080 {
header_up Host {http.request.host}
header_up X-Forwarded-Host {http.request.host}
}
}
handle {
reverse_proxy mas:8080
reverse_proxy mas:8080 {
header_up Host {http.request.host}
header_up X-Forwarded-Host {http.request.host}
}
}
}
@@ -451,6 +474,11 @@ echo ""
# ── Start the stack ───────────────────────────────────────────────────────────
if [[ "${SKIP_START:-false}" == "true" ]]; then
ok "Skipping stack start (SKIP_START=true)"
echo ""
else
info "Starting PostgreSQL..."
sudo docker compose up -d postgres
@@ -469,6 +497,8 @@ info "Starting all services..."
sudo docker compose --profile single-machine up -d ${CORE_SERVICES}
echo ""
fi
# ── Summary ───────────────────────────────────────────────────────────────────
ok "Stack is up."
+80
View File
@@ -385,6 +385,40 @@ assert_endpoints() {
|| fail "Element Web root (no Element content in response)"
}
# ─── Quickstart config assertions ────────────────────────────────────────────
assert_quickstart_configs() {
local domain="$1"
local matrix_domain="matrix.${domain}"
local auth_domain="auth.${domain}"
header "Quickstart config assertions (domain=${domain})"
assert_file ".env" ".env generated"
assert_contains ".env" "DOMAIN=${domain}" ".env → DOMAIN"
assert_contains ".env" "MATRIX_DOMAIN=${matrix_domain}" ".env → MATRIX_DOMAIN"
assert_file "mas/config/config.yaml" "mas/config/config.yaml generated"
assert_contains "mas/config/config.yaml" "homeserver: '${matrix_domain}'" "MAS → homeserver"
assert_contains "mas/config/config.yaml" "name: adminapi" "MAS → adminapi listener"
assert_contains "mas/config/config.yaml" "public_base: 'https://${auth_domain}/'" "MAS → public_base"
assert_contains "mas/config/config.yaml" "issuer: 'https://${auth_domain}/'" "MAS → issuer"
assert_file "element/config/config.json" "element/config/config.json generated"
assert_file "synapse/data/homeserver.yaml" "synapse/data/homeserver.yaml generated"
assert_contains "synapse/data/homeserver.yaml" "enable_registration: false" "Synapse → registration disabled"
assert_contains "synapse/data/homeserver.yaml" "allow_guest_access: false" "Synapse → guest access disabled"
assert_contains "synapse/data/homeserver.yaml" "allow_public_rooms_without_auth: false" "Synapse → public rooms blocked"
assert_contains "synapse/data/homeserver.yaml" "allow_public_rooms_over_federation: false" "Synapse → public rooms over federation blocked"
assert_file "caddy/Caddyfile" "caddy/Caddyfile generated"
assert_contains "caddy/Caddyfile" "admin localhost:2019" "Caddyfile → admin API localhost only"
assert_contains "caddy/Caddyfile" "/_synapse/admin" "Caddyfile → synapse admin block present"
assert_contains "caddy/Caddyfile" "header_up X-Forwarded-Host" "Caddyfile → MAS proxy forwards X-Forwarded-Host"
assert_contains "caddy/Caddyfile" "handle /account/" "Caddyfile → /account/ uses handle (preserves prefix)"
assert_not_contains "caddy/Caddyfile" "handle_path /account/" "Caddyfile → /account/ not handle_path"
}
# ─── Run one full scenario ────────────────────────────────────────────────────
run_scenario() {
local name="$1"
@@ -459,6 +493,52 @@ run_scenario \
"2" \
"matrix.example.test"
# Scenario P — production Caddyfile generation (config only, no Let's Encrypt)
section "P · Production Caddyfile (config only)"
teardown_stack
cleanup_configs
info "Running deploy.sh production mode (piped stdin, SKIP_START=true)"
# Stdin answers in prompt order:
# [1] Deployment type: 2 (production)
# [2] Include Authelia? n
# [3] Enable Element Call? n
# [4] Custom Docker registry prefix: (empty)
# [5] Use hardened images? n
# [6] Base domain: example.com
# [7] Matrix subdomain: (empty → matrix)
# [8] Element subdomain: (empty → element)
# [9] Admin subdomain: (empty → admin)
# [10] Auth subdomain: (empty → auth)
# [11] Authelia subdomain: (empty → authelia)
# [12] SERVER_NAME choice: 1 (TLD: @user:example.com)
# [13] Matrix server address: (empty → 10.0.1.10)
# [14] Authelia server address: (empty → 10.0.1.20)
# [15] Let's Encrypt email: (empty → admin@example.com)
printf '%s\n' "2" "n" "n" "" "n" "example.com" "" "" "" "" "" "1" "" "" "" \
| SKIP_START=true bash deploy.sh
header "Production Caddyfile assertions"
assert_file "caddy/Caddyfile.production" "caddy/Caddyfile.production generated"
assert_contains "caddy/Caddyfile.production" "admin localhost:2019" "Caddyfile.production → admin API localhost only"
assert_contains "caddy/Caddyfile.production" "/_synapse/admin" "Caddyfile.production → synapse admin block present"
assert_contains "caddy/Caddyfile.production" "header_up X-Forwarded-Host" "Caddyfile.production → MAS proxy forwards X-Forwarded-Host"
assert_contains "caddy/Caddyfile.production" "handle /account/" "Caddyfile.production → /account/ uses handle (preserves prefix)"
assert_not_contains "caddy/Caddyfile.production" "handle_path /account/" "Caddyfile.production → /account/ not handle_path"
assert_contains "caddy/Caddyfile.production" '"m.authentication"' "Caddyfile.production → well-known includes m.authentication"
assert_contains "caddy/Caddyfile.production" "Access-Control-Allow-Origin" "Caddyfile.production → well-known has CORS header"
# Scenario Q — quickstart.sh config generation
section "Q · quickstart.sh (single-machine, config only)"
teardown_stack
cleanup_configs
info "Running quickstart.sh (piped stdin, SKIP_START=true)"
printf '%s\n' "example.test" "test@example.test" "n" \
| SKIP_START=true bash quickstart.sh
assert_quickstart_configs "example.test"
if [[ "$SKIP_INTEGRATION" != "true" ]]; then
warn "Quickstart endpoint tests skipped (stack not started in SKIP_START mode)"
fi
trap - EXIT
cleanup_on_exit
print_summary