Compare commits

..

3 Commits

Author SHA1 Message Date
Tobias Kussel
a14da73ccc add additional credential encoding for curl
Curl seems not to like full percent-encoding of all characters which might be related to https://github.com/curl/curl/issues/5448#event-3371269895. The version in this PR escapes a lot and strictly follows RFC3986 section 2.3 for unescaped characters
2026-03-05 14:31:05 +01:00
Tobias Kussel
467613ad31 fix proxy credential escaping 2026-03-05 14:21:37 +01:00
Tobias Kussel
82ee757e17 Use new proxy escaping and forward-proxy test image 2026-03-04 09:26:22 +01:00
7 changed files with 141 additions and 119 deletions

View File

@@ -1,104 +0,0 @@
version: "3.7"
services:
ovis-traefik-forward-auth:
image: docker.verbis.dkfz.de/cache/oauth2-proxy/oauth2-proxy:latest
environment:
- http_proxy=http://forward_proxy:3128
- https_proxy=http://forward_proxy:3128
- OAUTH2_PROXY_PROVIDER=oidc
- OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
- OAUTH2_PROXY_OIDC_ISSUER_URL=${OIDC_PRIVATE_URL}
- OAUTH2_PROXY_CLIENT_ID=${OIDC_PRIVATE_CLIENT_ID}
- OAUTH2_PROXY_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
- OAUTH2_PROXY_COOKIE_SECRET=${OVIS_AUTH_COOKIE_SECRET}
- OAUTH2_PROXY_COOKIE_NAME=_BRIDGEHEAD_oauth2_ovis
- OAUTH2_PROXY_COOKIE_DOMAINS=.${HOST}
- OAUTH2_PROXY_COOKIE_REFRESH=4m
- OAUTH2_PROXY_COOKIE_EXPIRE=24h
- OAUTH2_PROXY_HTTP_ADDRESS=:4180
- OAUTH2_PROXY_REVERSE_PROXY=true
- OAUTH2_PROXY_WHITELIST_DOMAINS=.${HOST}
- OAUTH2_PROXY_UPSTREAMS=static://202
- OAUTH2_PROXY_EMAIL_DOMAINS=*
- OAUTH2_PROXY_SCOPE=openid profile email
- OAUTH2_PROXY_SET_AUTHORIZATION_HEADER=true
- OAUTH2_PROXY_SET_XAUTHREQUEST=true
- OAUTH2_PROXY_ALLOWED_GROUPS=${OIDC_USER_GROUP}
- OAUTH2_PROXY_OIDC_GROUPS_CLAIM=${OIDC_GROUP_CLAIM}
- OAUTH2_PROXY_PROXY_PREFIX=/oauth2-ovis
labels:
- "traefik.enable=true"
- "traefik.http.services.ovis-traefik-forward-auth.loadbalancer.server.port=4180"
- "traefik.http.routers.ovis-traefik-forward-auth.rule=Host(`${HOST}`) && PathPrefix(`/oauth2-ovis`)"
- "traefik.http.routers.ovis-traefik-forward-auth.tls=true"
- "traefik.http.middlewares.traefik-forward-auth-ovis.forwardauth.address=http://ovis-traefik-forward-auth:4180"
- "traefik.http.middlewares.traefik-forward-auth-ovis.forwardauth.authResponseHeaders=Authorization"
depends_on:
forward_proxy:
condition: service_healthy
ovis-backend-database-mongodb:
image: docker.verbis.dkfz.de/ovis/ovis-backend-mongodb:latest
container_name: bridgehead-ccp-ovis-mongo
ovis-backend-mongodb-data-preprocessing:
image: docker.verbis.dkfz.de/ovis/ovis-backend-preprocessor:latest
container_name: bridgehead-ccp-ovis-preprocessing
environment:
ADDRESS: mongodb://ovis-backend-database-mongodb:27017
depends_on:
- ovis-backend-database-mongodb
healthcheck:
test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:9000/health', res => process.exit(res.statusCode===200?0:1)).on('error', () => process.exit(1));\""]
interval: 10s
timeout: 5s
retries: 6
start_period: 5s
ovis-backend-data-import:
image: docker.verbis.dkfz.de/ovis/ovis-backend-data-import-ccp:latest
container_name: bridgehead-ccp-ovis-import
depends_on:
ovis-backend-mongodb-data-preprocessing:
condition: service_healthy
environment:
FHIR_SERVER_URL: http://bridgehead-ccp-blaze:8080/fhir
ovis-backend-apollo:
image: docker.verbis.dkfz.de/ovis/ovis-backend-apollo:latest
container_name: bridgehead-ccp-ovis-backend
environment:
ADDRESS: mongodb://ovis-backend-database-mongodb:27017
depends_on:
- ovis-backend-database-mongodb
- ovis-backend-mongodb-data-preprocessing
- ovis-backend-data-import
healthcheck:
test: ["CMD-SHELL", "test -d /app/node_modules/mongodb"]
interval: 10s
timeout: 5s
retries: 5
# Internal only - no direct Traefik exposure. GraphQL is accessed via frontend internal proxy.
ovis-frontend:
image: docker.verbis.dkfz.de/ovis/ovis-frontend:latest
container_name: bridgehead-ccp-ovis-frontend
environment:
OVIS_PUBLIC_BASE_PATH: /ccp-ovis
PUBLIC_LOGIN_ENABLED: "false"
PUBLIC_OVIS_IMPORT: ccp
ORIGIN: https://${HOST}
depends_on:
ovis-backend-apollo:
condition: service_healthy
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.ovis-frontend-ccp-slash-redirect.redirectregex.regex=^https?://([^/]+)/ccp-ovis$"
- "traefik.http.middlewares.ovis-frontend-ccp-slash-redirect.redirectregex.replacement=https://$${1}/ccp-ovis/"
- "traefik.http.middlewares.ovis-frontend-ccp-slash-redirect.redirectregex.permanent=true"
- "traefik.http.routers.ovis-frontend-ccp.tls=true"
- "traefik.http.routers.ovis-frontend-ccp.rule=PathPrefix(`/ccp-ovis`)"
- "traefik.http.routers.ovis-frontend-ccp.middlewares=traefik-forward-auth-ovis,ovis-frontend-ccp-slash-redirect"
- "traefik.http.services.ovis-frontend-ccp.loadbalancer.server.port=5173"

View File

@@ -1,9 +0,0 @@
#!/bin/bash -e
if [ -n "$ENABLE_OVIS" ]; then
log INFO "OVIS setup detected -- will start OVIS services with local oauth2-proxy middleware."
OVERRIDE+=" -f ./$PROJECT/modules/ovis-compose.yml"
add_private_oidc_redirect_url "/oauth2-ovis/callback"
add_private_oidc_redirect_url "/ccp-ovis*"
OVIS_AUTH_COOKIE_SECRET="$(generate_simple_password 'ovisCookieSecret' | head -c 16)"
fi

View File

@@ -9,6 +9,15 @@ detectCompose() {
fi
}
# Encodes all characters not in unrestricted character set of RFC3986 Section 2.3
urlencode() {
for ((i=0;i<${#1};i++)); do
local c=${1:i:1}
[[ "$c" =~ [a-zA-Z0-9._~-] ]] && printf '%s' "$c" || printf '%%%02X' "'$c"
done
echo
}
setupProxy() {
### Note: As the current data protection concepts do not allow communication via HTTP,
### we are not setting a proxy for HTTP requests.
@@ -22,9 +31,12 @@ setupProxy() {
HTTPS_PROXY_HOST="$(echo $hostport | sed -e 's,:.*,,g')"
HTTPS_PROXY_PORT="$(echo $hostport | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')"
if [[ ! -z "$HTTPS_PROXY_USERNAME" && ! -z "$HTTPS_PROXY_PASSWORD" ]]; then
local ESCAPED_PASSWORD="$(echo $HTTPS_PROXY_PASSWORD | od -An -v -t x1 | sed -e 's/[[:space:]]//g' -e 's/\([0-9a-f][0-9a-f]\)/%\1/g' | tr -d '\n')"
local CURL_ESCAPED_PW="$(urlencode $HTTPS_PROXY_PASSWORD)"
local proto="$(echo $HTTPS_PROXY_URL | grep :// | sed -e 's,^\(.*://\).*,\1,g')"
local fqdn="$(echo ${HTTPS_PROXY_URL/$proto/})"
HTTPS_PROXY_FULL_URL="$(echo $proto$HTTPS_PROXY_USERNAME:$HTTPS_PROXY_PASSWORD@$fqdn)"
HTTPS_PROXY_FULL_URL="$(echo $proto$HTTPS_PROXY_USERNAME:$ESCAPED_PASSWORD@$fqdn)"
CURL_HTTPS_PROXY_FULL_URL="$(echo $proto$HTTPS_PROXY_USERNAME:$CURL_ESCAPED_PW@$fqdn)"
https="authenticated"
else
HTTPS_PROXY_FULL_URL=$HTTPS_PROXY_URL
@@ -33,7 +45,7 @@ setupProxy() {
fi
log INFO "Configuring proxy servers: $http http proxy (we're not supporting unencrypted comms), $https https proxy"
export HTTPS_PROXY_HOST HTTPS_PROXY_PORT HTTPS_PROXY_FULL_URL
export HTTPS_PROXY_HOST HTTPS_PROXY_PORT HTTPS_PROXY_FULL_URL CURL_HTTPS_PROXY_FULL_URL
}
exitIfNotRoot() {

View File

@@ -47,8 +47,8 @@ function hc_send(){
if [ -n "$2" ]; then
MSG="$2\n\nDocker stats:\n$UPTIME"
echo -e "$MSG" | https_proxy=$HTTPS_PROXY_FULL_URL curl --max-time 5 -A "$USER_AGENT" -s -o /dev/null -X POST --data-binary @- "$HCURL"/"$1" || log WARN "Monitoring failed: Unable to send data to $HCURL/$1"
echo -e "$MSG" | https_proxy=$CURL_HTTPS_PROXY_FULL_URL curl --max-time 5 -A "$USER_AGENT" -s -o /dev/null -X POST --data-binary @- "$HCURL"/"$1" || log WARN "Monitoring failed: Unable to send data to $HCURL/$1"
else
https_proxy=$HTTPS_PROXY_FULL_URL curl --max-time 5 -A "$USER_AGENT" -s -o /dev/null "$HCURL"/"$1" || log WARN "Monitoring failed: Unable to send data to $HCURL/$1"
https_proxy=$CURL_HTTPS_PROXY_FULL_URL curl --max-time 5 -A "$USER_AGENT" -s -o /dev/null "$HCURL"/"$1" || log WARN "Monitoring failed: Unable to send data to $HCURL/$1"
fi
}

View File

@@ -71,7 +71,7 @@ source ${PROJECT}/vars
if [ "${PROJECT}" != "minimal" ]; then
set +e
SERVERTIME="$(https_proxy=$HTTPS_PROXY_FULL_URL curl -m 5 -s -I $BROKER_URL_FOR_PREREQ 2>&1 | grep -i -e '^Date: ' | sed -e 's/^Date: //i')"
SERVERTIME="$(https_proxy=$CURL_HTTPS_PROXY_FULL_URL curl -m 5 -s -I $BROKER_URL_FOR_PREREQ 2>&1 | grep -i -e '^Date: ' | sed -e 's/^Date: //i')"
RET=$?
set -e
if [ $RET -ne 0 ]; then

123
lib/tests/test_proxyparsing.sh Executable file
View File

@@ -0,0 +1,123 @@
source ../functions.sh
test_setupProxy() {
# simple logger for tests
log() { :; }
local failures=0
local total=0
assert_eq() {
local label="$1" got="$2" expected="$3"
total=$((total + 1))
if [[ "$got" != "$expected" ]]; then
failures=$((failures + 1))
printf 'FAIL: %s\n got: %q\n expected: %q\n\n' "$label" "$got" "$expected"
else
printf 'ok: %s\n' "$label"
fi
}
run_case() {
local name="$1"
local url="$2"
local u="$3"
local p="$4"
local exp_host="$5"
local exp_port="$6"
local exp_full="$7"
HTTPS_PROXY_URL="$url"
HTTPS_PROXY_USERNAME="$u"
HTTPS_PROXY_PASSWORD="$p"
setupProxy >/dev/null 2>&1
assert_eq "$name host" "$HTTPS_PROXY_HOST" "$exp_host"
assert_eq "$name port" "$HTTPS_PROXY_PORT" "$exp_port"
assert_eq "$name full" "$HTTPS_PROXY_FULL_URL" "$exp_full"
}
echo "Running setupProxy tests..."
echo
# 1) Basic https host:port
run_case "basic https" \
"https://proxy.example.org:8443" "" "" \
"proxy.example.org" "8443" \
"https://proxy.example.org:8443"
# 2) https without port -> default 443
run_case "https no port" \
"https://proxy.example.org" "" "" \
"proxy.example.org" "443" \
"https://proxy.example.org"
# 3) no scheme, host:port -> defaults scheme=https
run_case "no scheme hostport" \
"proxy.example.org:3128" "" "" \
"proxy.example.org" "3128" \
"https://proxy.example.org:3128"
# 4) URL with path/query/fragment
run_case "ignores path" \
"https://proxy.example.org:8443/some/path?x=1#y" "" "" \
"proxy.example.org" "8443" \
"https://proxy.example.org:8443"
# 5) explicit env creds inserted
run_case "env creds override" \
"https://proxy.example.org:8443" "alice" "secret" \
"proxy.example.org" "8443" \
"https://alice:secret@proxy.example.org:8443"
# 6) embedded creds used if env creds absent
run_case "embedded creds" \
"https://bob:pw@proxy.example.org:8443" "" "" \
"proxy.example.org" "8443" \
"https://bob:pw@proxy.example.org:8443"
# 7) env creds override embedded creds
run_case "env overrides embedded" \
"https://bob:pw@proxy.example.org:8443" "alice" "secret" \
"proxy.example.org" "8443" \
"https://alice:secret@proxy.example.org:8443"
# 8) IPv6 literal with port
run_case "ipv6 with port" \
"https://[2001:db8::1]:8080" "" "" \
"2001:db8::1" "8080" \
"https://[2001:db8::1]:8080"
# 9) IPv6 literal without port -> default 443
run_case "ipv6 no port" \
"https://[2001:db8::1]" "" "" \
"2001:db8::1" "443" \
"https://[2001:db8::1]"
# 10) http scheme rejected -> outputs empty
HTTPS_PROXY_URL="http://proxy.example.org:8080"
HTTPS_PROXY_USERNAME=""
HTTPS_PROXY_PASSWORD=""
setupProxy >/dev/null 2>&1
assert_eq "http rejected host" "${HTTPS_PROXY_HOST:-}" ""
assert_eq "http rejected port" "${HTTPS_PROXY_PORT:-}" ""
assert_eq "http rejected full" "${HTTPS_PROXY_FULL_URL:-}" ""
# 11) empty URL -> outputs empty but no failure
HTTPS_PROXY_URL=""
setupProxy >/dev/null 2>&1
assert_eq "empty url host" "${HTTPS_PROXY_HOST:-}" ""
assert_eq "empty url port" "${HTTPS_PROXY_PORT:-}" ""
assert_eq "empty url full" "${HTTPS_PROXY_FULL_URL:-}" ""
echo
echo "Tests complete: $((total - failures))/$total passed."
if (( failures > 0 )); then
echo "Some tests failed."
return 1
fi
return 0
}
test_setupProxy

View File

@@ -32,7 +32,7 @@ services:
forward_proxy:
container_name: bridgehead-forward-proxy
image: docker.verbis.dkfz.de/cache/samply/bridgehead-forward-proxy:latest
image: samply/bridgehead-forward-proxy:pr-16
environment:
HTTPS_PROXY: ${HTTPS_PROXY_URL}
HTTPS_PROXY_USERNAME: ${HTTPS_PROXY_USERNAME}