Skip to content

Add reverse proxy support #38

@snvelichko

Description

@snvelichko

I tried to configure a reverse proxy so that requests coming to https://domain.name/ipc/camera_name/* are redirected to http://camera_ip/*. But there was a problem: majestic-webui does not support processing of X-Forwarded-* headers from a reverse proxy, and does not support the X-Forwarded-Prefix header. Incorrect links relative to the site root are formed in the response.

Example of nginx reverse proxy configuration:

server {
    listen 443 ssl;
    server_name domain.name;

    ssl_certificate /path/to/cert.crt;
    ssl_certificate_key /path/to/cert.key;

    location ^~ /ipc/cam24/ {
        rewrite ^/ipc/cam24/(.*)$ /$1 break;
        proxy_pass http://192.168.1.24/;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-Prefix /ipc/cam24;
    }
}

Example of how to process X-Forwarded-* headers:

reverse_proxy.cgi

#!/usr/bin/haserl
<%
TRUSTED_PROXIES="127.0.0.1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16"

is_ipv6() {
    case "$1" in
        *:*) return 0 ;;
        *) return 1 ;;
    esac
}

ip_to_decimal() {
    IFS=. read -r a b c d <<< "$1"
    echo $(( (a << 24) + (b << 16) + (c << 8) + d ))
}

ip_in_cidr() {
    ip="$1"
    cidr="$2"

    IFS=/ read network mask <<< "$cidr"

    ip_decimal=$(ip_to_decimal "$ip")
    network_decimal=$(ip_to_decimal "$network")
    mask_decimal=$((0xFFFFFFFF << (32 - mask)))

    if [ $((ip_decimal & mask_decimal)) -eq $((network_decimal & mask_decimal)) ]; then
        return 0
    else
        return 1
    fi
}

is_trusted_ip() {
    for proxy in $TRUSTED_PROXIES; do
        if echo "$proxy" | grep -q '/'; then
            if ip_in_cidr "$1" "$proxy"; then
                return 0
            fi
        else
            if [ "$1" = "$proxy" ]; then
                return 0
            fi
        fi
    done
    return 1
}

get_real_client_ip() {
    forwarded="$1"

    IFS=',' read -ra ips <<< "$forwarded"

    for i in "${!ips[@]}"; do
        ips[$i]="$(echo "${ips[$i]}" | xargs)"
    done

    real_ip=""
    for (( idx=${#ips[@]}-1 ; idx>=0 ; idx-- )); do
        ip="${ips[$idx]}"
        if is_trusted_ip "$ip"; then
            continue
        else
            real_ip="$ip"
            break
        fi
    done

    if [ -z "$real_ip" ] && [ "${#ips[@]}" -gt 0 ]; then
        real_ip="${ips[0]}"
    fi

    echo "$real_ip"
}

ORIGINAL_REMOTE_ADDR="$REMOTE_ADDR"
USE_X_FORWARDED=0

if ! is_ipv6 "$REMOTE_ADDR"; then
    if is_trusted_ip "$REMOTE_ADDR"; then
        USE_X_FORWARDED=1
    fi
fi

if [ "$USE_X_FORWARDED" -eq 1 ] && [ -n "$HTTP_X_FORWARDED_FOR" ]; then
    REMOTE_ADDR="$(get_real_client_ip "$HTTP_X_FORWARDED_FOR")"
fi

PROTO="http"
if [ -n "$HTTP_X_FORWARDED_PROTO" ]; then
    PROTO="$HTTP_X_FORWARDED_PROTO"
fi

HOST="$SERVER_NAME"
if [ -n "$HTTP_X_FORWARDED_HOST" ]; then
    HOST="$HTTP_X_FORWARDED_HOST"
fi

PORT="$SERVER_PORT"
if [ -n "$HTTP_X_FORWARDED_PORT" ]; then
    PORT="$HTTP_X_FORWARDED_PORT"
fi

PREFIX=""
if [ -n "$HTTP_X_FORWARDED_PREFIX" ]; then
    PREFIX="$HTTP_X_FORWARDED_PREFIX"
fi

if [ -n "$PREFIX" ] && [ "${PREFIX:0:1}" != "/" ]; then
    PREFIX="/$PREFIX"
fi

BASE_URL="${PROTO}://${HOST}"
if { [ "$PROTO" = "http" ] && [ "$PORT" != "80" ]; } || { [ "$PROTO" = "https" ] && [ "$PORT" != "443" ]; }; then
    BASE_URL="${BASE_URL}:${PORT}"
fi
BASE_URL="${BASE_URL}${PREFIX}"

echo "REMOTE_ADDR=$REMOTE_ADDR"
echo "PROTO=$PROTO"
echo "HOST=$HOST"
echo "PORT=$PORT"
echo "PREFIX=$PREFIX"
echo "BASE_URL=$BASE_URL"
%>

test.cgi

#!/usr/bin/haserl 
<%in reverse_proxy.cgi %>

<!DOCTYPE html>
<html lang="en" data-bs-theme="<%= ${webui_theme:=dark} %>">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><% html_title %></title>
    <link rel="stylesheet" href="<%= PREFIX  %>a/bootstrap.min.css">
    <link rel="stylesheet" href="<%= PREFIX  %>a/bootstrap.override.css">
    <script src="<%= PREFIX  %>a/bootstrap.bundle.min.js"></script>
    <script src="<%= PREFIX  %>a/main.js"></script>
</head>
<body>
    <p>Remote Address: <%= REMOTE_ADDR %></p>
    <p>Protocol: <%= PROTO %></p>
    <p>Host: <%= HOST %></p>
    <p>Port: <%= PORT %></p>
    <p>Prefix: <%= PREFIX %></p>
    <p>Base URL: <%= BASE_URL %></p>
    <p>Link: <a href="<%= PREFIX  %>mjpeg">Click here</a></p>
</body>

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions