diff --git a/README.md b/README.md index 891c147..cd5d361 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ If you want to define your custom variables see [Define custom variables](#defin | RABBITMQ_IP_WHITELIST | 123.456.123.422, 423.534.223.234 | IP Addresses (separated by comma) for which is the RabbitMQ Management accessible | All | | USING_CLOUDFLARE | _1_ OR _0_ | Set to 1 if your site is using Cloudflare (enables IP whitelisting) | production/devel | | MCP_INGRESS_ENABLED | _1_ OR _0_ | Set to 0 to disable the separate ingress publishing the MCP endpoints without HTTP basic auth (default: 1) | production/devel | +| MCP_IP_WHITELIST | 203.0.113.0/24, 198.51.100.10/32 | VPN egress IP ranges allowed to access MCP; when empty, MCP access is not restricted by source IP | production/devel | *1) Credentials can be generated in Gitlab (Settings -> Repository -> Deploy Tokens) with `read_registry` scope only diff --git a/UPGRADE.md b/UPGRADE.md index 80e09aa..ad454a0 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -10,6 +10,10 @@ 2. Run `composer update shopsys/deployment` 3. Check files in mentioned pull requests and if you have any of them extended in your project, apply changes manually +## Upgrade from v5.2.0 to v5.3.0 + +- set `MCP_IP_WHITELIST` to a comma-separated list of VPN egress IP addresses or CIDR ranges to restrict access to the MCP ingress; an empty value preserves the current publicly reachable MCP ingress + ## Upgrade from v5.1.0 to v5.2.0 - the MCP server endpoints (`/_mcp`, `/mcp/oauth`, `/.well-known/oauth-authorization-server`, `/.well-known/oauth-protected-resource`) are now published through a separate `eshop-mcp` ingress without HTTP basic auth, so external MCP clients (e.g. Claude Code) can authenticate with their own Bearer token diff --git a/deploy/parts/domains.sh b/deploy/parts/domains.sh index 0354926..4ddae97 100644 --- a/deploy/parts/domains.sh +++ b/deploy/parts/domains.sh @@ -22,6 +22,10 @@ USING_CLOUDFLARE=${USING_CLOUDFLARE:-0} # Global switch to disable publishing of the MCP ingress without HTTP basic auth (default: enabled) MCP_INGRESS_ENABLED=${MCP_INGRESS_ENABLED:-1} +# IP ranges allowed to access the MCP ingress, typically VPN egress ranges. +# An empty value keeps the MCP ingress publicly reachable as before. +MCP_IP_WHITELIST=$(echo "${MCP_IP_WHITELIST:-}" | tr -d ' ' | sed 's/,\+/,/g;s/^,//;s/,$//') + # Domains to exclude from Cloudflare IP whitelisting if [ -z "${CLOUDFLARE_EXCLUDED_DOMAINS}" ]; then CLOUDFLARE_EXCLUDED_DOMAINS=() @@ -213,8 +217,18 @@ done # - server-scoped nginx settings of the main ingress (e.g. the Cloudflare # real_ip_header server-snippet) apply to the MCP locations automatically, because # ingress-nginx merges all ingresses of the same hostname into a single server block. +function configure_mcp_ingress() { + local TARGET_FILEPATH="${1}" + + if [ -n "${MCP_IP_WHITELIST}" ]; then + MCP_IP_WHITELIST="${MCP_IP_WHITELIST}" yq e -i ' + .metadata.annotations."nginx.ingress.kubernetes.io/whitelist-source-range" = strenv(MCP_IP_WHITELIST) + ' "${TARGET_FILEPATH}" + fi +} + if [ "${MCP_INGRESS_ENABLED}" = "1" ]; then - render_ingress ".ingress-mcp.yaml" "ingress-mcp.yaml" "eshop-mcp" 0 + render_ingress ".ingress-mcp.yaml" "ingress-mcp.yaml" "eshop-mcp" 0 configure_mcp_ingress fi echo -e "[${GREEN}OK${NO_COLOR}]" diff --git a/tests/scenarios/basic-production/deploy-project.sh b/tests/scenarios/basic-production/deploy-project.sh index 65c8654..b86c4f3 100644 --- a/tests/scenarios/basic-production/deploy-project.sh +++ b/tests/scenarios/basic-production/deploy-project.sh @@ -5,6 +5,7 @@ source "$(dirname "$0")/../../lib/scenario-base.sh" # Scenario-specific configuration DOMAINS=(DOMAIN_HOSTNAME_1 DOMAIN_HOSTNAME_2) export RUNNING_PRODUCTION=1 +export MCP_IP_WHITELIST="203.0.113.0/24,198.51.100.10/32" ENABLE_AUTOSCALING=true declare -A CRON_INSTANCES=( diff --git a/tests/scenarios/basic-production/expected/webserver.yaml b/tests/scenarios/basic-production/expected/webserver.yaml index 0fa67f2..84fc3c1 100644 --- a/tests/scenarios/basic-production/expected/webserver.yaml +++ b/tests/scenarios/basic-production/expected/webserver.yaml @@ -751,6 +751,7 @@ metadata: annotations: ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: 203.0.113.0/24,198.51.100.10/32 name: eshop-mcp namespace: myproject-production spec: