fix(deploy): select verified proxy subnet
CI and Deploy / test (pull_request) Successful in 5m21s
CI and Deploy / test (push) Successful in 5m18s
CI and Deploy / deploy (pull_request) Has been skipped
CI and Deploy / deploy (push) Failing after 2m38s

This commit is contained in:
cesnimda
2026-08-24 20:53:51 +02:00
parent c4b81918c4
commit fb90d0ad33
2 changed files with 66 additions and 3 deletions
+64 -1
View File
@@ -97,16 +97,79 @@ resolve_existing_web_proxy_subnet() {
fi fi
local project_name="${COMPOSE_PROJECT_NAME:-$(basename "$PWD")}" network_name subnet local project_name="${COMPOSE_PROJECT_NAME:-$(basename "$PWD")}" network_name subnet
local labelled_networks labelled_network_id labelled_project
network_name="${project_name}_web_proxy" network_name="${project_name}_web_proxy"
if ! docker network inspect "$network_name" >/dev/null 2>&1; then if ! docker network inspect "$network_name" >/dev/null 2>&1; then
return 0 # Compose projects may have been created from a differently named checkout.
# A unique network carrying Compose's logical `web_proxy` label is the same
# production fact even when its generated resource name differs.
labelled_networks=""
if [ -z "${COMPOSE_PROJECT_NAME:-}" ]; then
labelled_networks="$(docker network ls --filter label=com.docker.compose.network=web_proxy -q 2>/dev/null || true)"
fi
if [ "$(printf '%s\n' "$labelled_networks" | sed '/^$/d' | wc -l | tr -d '[:space:]')" = "1" ]; then
labelled_network_id="$(printf '%s\n' "$labelled_networks" | sed '/^$/d')"
labelled_project="$(docker network inspect -f '{{index .Labels "com.docker.compose.project"}}' "$labelled_network_id" 2>/dev/null || true)"
if [[ "$labelled_project" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]]; then
export COMPOSE_PROJECT_NAME="$labelled_project"
network_name="$labelled_network_id"
else
network_name=""
fi
else
network_name=""
fi
fi fi
subnet=""
if [ -n "$network_name" ]; then
subnet="$(docker network inspect -f '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' "$network_name" 2>/dev/null | head -n 1 | tr -d '[:space:]')" subnet="$(docker network inspect -f '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' "$network_name" 2>/dev/null | head -n 1 | tr -d '[:space:]')"
fi
if [[ "$subnet" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[12][0-9]|3[0-2])$ ]]; then if [[ "$subnet" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[12][0-9]|3[0-2])$ ]]; then
export WEB_PROXY_SUBNET="$subnet" export WEB_PROXY_SUBNET="$subnet"
echo "Reusing WEB_PROXY_SUBNET from the existing ${network_name} Docker network." echo "Reusing WEB_PROXY_SUBNET from the existing ${network_name} Docker network."
return 0
fi fi
# First creation of the isolated proxy network: use the documented default
# only after proving it does not overlap any network currently on this host.
local candidate="172.31.250.0/29" existing_output existing
local candidate_start candidate_end existing_start existing_end
local -a network_ids=()
mapfile -t network_ids < <(docker network ls -q 2>/dev/null)
if [ "${#network_ids[@]}" -eq 0 ]; then
return 0
fi
if ! existing_output="$(docker network inspect -f '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' "${network_ids[@]}" 2>/dev/null)"; then
return 0
fi
read -r candidate_start candidate_end < <(ipv4_cidr_bounds "$candidate") || return 0
while IFS= read -r existing; do
[[ "$existing" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[12][0-9]|3[0-2])$ ]] || continue
read -r existing_start existing_end < <(ipv4_cidr_bounds "$existing") || continue
if (( candidate_start <= existing_end && existing_start <= candidate_end )); then
return 0
fi
done <<< "$existing_output"
export WEB_PROXY_SUBNET="$candidate"
echo "Using non-overlapping WEB_PROXY_SUBNET ${candidate} after Docker network inventory."
}
ipv4_cidr_bounds() {
local cidr="$1" address prefix a b c d value ip size start
address="${cidr%/*}"
prefix="${cidr#*/}"
IFS='.' read -r a b c d <<< "$address"
for value in "$a" "$b" "$c" "$d"; do
[[ "$value" =~ ^[0-9]{1,3}$ ]] && ((10#$value <= 255)) || return 1
done
[[ "$prefix" =~ ^[0-9]+$ ]] && ((10#$prefix <= 32)) || return 1
ip=$(( (10#$a << 24) + (10#$b << 16) + (10#$c << 8) + 10#$d ))
size=$(( 1 << (32 - 10#$prefix) ))
start=$(( ip & ~(size - 1) ))
printf '%s %s\n' "$start" "$((start + size - 1))"
} }
validate_deploy_config() { validate_deploy_config() {
+1 -1
View File
@@ -32,7 +32,7 @@ No image was pulled and no production or persistent service was changed. Ephemer
## Limitations and production gates ## Limitations and production gates
- No Traefik configuration exists in this repository. Verify its exact `Host()` rule, TLS route, replacement of forwarding headers, selected Docker network, and hostile-Host rejection on the operator host. - No Traefik configuration exists in this repository. Verify its exact `Host()` rule, TLS route, replacement of forwarding headers, selected Docker network, and hostile-Host rejection on the operator host.
- `WEB_PROXY_SUBNET` must be chosen after production Docker-network inventory; the example value is not a production fact. Deploys may recover a missing value only from the existing Compose `web_proxy` network, preserving the already-running production CIDR without guessing a new one. - `WEB_PROXY_SUBNET` must be chosen after production Docker-network inventory. Deploys recover a missing value from a uniquely labelled existing Compose `web_proxy` network. On its first creation, the documented `172.31.250.0/29` candidate is accepted only after a complete Docker network inventory proves it does not overlap; unreadable or overlapping inventory remains fail-closed.
- Host firewall and `docker ps`/published-port state are unverified. - Host firewall and `docker ps`/published-port state are unverified.
- The exact `nginx:1.29.8-alpine` base image was not installed locally. Syntax was checked with the existing local nginx frontend image; approved CI must build the pinned Dockerfile. - The exact `nginx:1.29.8-alpine` base image was not installed locally. Syntax was checked with the existing local nginx frontend image; approved CI must build the pinned Dockerfile.
- A complete local proxy/browser smoke was not run because rebuilding the pinned container would require an unavailable base image/package access. No browser claim is made. - A complete local proxy/browser smoke was not run because rebuilding the pinned container would require an unavailable base image/package access. No browser claim is made.