LINUX.ORG.RU
ФорумAdmin

Развёртывание защищённого HTTPS-прокси с авторизацией на Debian и Ubuntu с помощью скрипта одной командой

 , ,


0

1

Организация собственного HTTPS-прокси остаётся востребованной задачей — как для обеспечения контролируемого доступа к внешним ресурсам, так и для изоляции сетевого трафика. Ниже приведён практический подход к развёртыванию такого решения на базе свободного ПО с использованием автоматического выпуска TLS-сертификатов.

Архитектура решения

Итоговая система строится по принципу разделения ответственности между компонентами:

  • Squid выполняет роль классического HTTP-прокси и обрабатывает авторизацию пользователей;
  • stunnel обеспечивает TLS-шифрование и принимает входящие HTTPS-подключения;
  • Certbot автоматически получает и обновляет сертификаты от Let’s Encrypt;
  • утилита htpasswd используется для управления учётными данными.

Трафик клиента проходит следующую цепочку:

Клиент → HTTPS (8443)
            ↓
        stunnel (TLS termination)
            ↓
        Squid (127.0.0.1:3128)
            ↓
        Интернет

Таким образом:

  • внешний доступ осуществляется только через TLS;
  • сам прокси-движок изолирован и недоступен извне;
  • авторизация реализуется на уровне Squid.

Доменное имя и сертификаты

Для выпуска сертификата требуется доменное имя. В случаях, когда у администратора отсутствует собственный домен, можно воспользоваться бесплатным сервисом динамического DNS — DuckDNS.

Он позволяет:

  • быстро зарегистрировать поддомен вида example.duckdns.org;
  • автоматически обновлять IP-адрес;
  • использовать его для выпуска сертификатов через Let’s Encrypt.

Это особенно актуально для:

  • домашних серверов;
  • облачных инстансов без закреплённого домена;
  • временных инфраструктур.

Совместимость с актуальными дистрибутивами

Скрипт ниже адаптирован для:

  • Debian 12 (Bookworm)
  • Debian 13 (Trixie)
  • Ubuntu 22.04 LTS
  • Ubuntu 24.04 LTS

Скрипт автоматического развёртывания

Как использовать:

chmod +x setup-proxy.sh
sudo ./setup-proxy.sh proxy.example.com your@email.com

Код самого скрипта:

#!/usr/bin/env bash
set -Eeuo pipefail

DOMAIN="${1:-}"
EMAIL="${2:-}"

if [[ -z "$DOMAIN" || -z "$EMAIL" ]]; then
  echo "Usage: $0 <domain> <email>"
  exit 1
fi

PASSFILE="/etc/squid/passwd"
STUNNEL_PEM="/etc/stunnel/stunnel.pem"
STUNNEL_CONF="/etc/stunnel/stunnel.conf"
STUNNEL_PID="/var/lib/stunnel4/stunnel.pid"
RENEW_HOOK="/etc/letsencrypt/renewal-hooks/deploy/stunnel-reload.sh"

log() {
  echo "[$1] $2"
}

require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    echo "Run this script as root"
    exit 1
  fi
}

detect_auth_bin() {
  local candidates=(
    /usr/lib/squid/basic_ncsa_auth
    /usr/libexec/squid/basic_ncsa_auth
    /usr/lib64/squid/basic_ncsa_auth
  )

  local bin
  for bin in "${candidates[@]}"; do
    if [[ -x "$bin" ]]; then
      echo "$bin"
      return 0
    fi
  done

  echo "Cannot find basic_ncsa_auth binary" >&2
  exit 1
}

generate_password() {
  local pass=""
  while [[ ${#pass} -lt 56 ]]; do
    pass="$(openssl rand -base64 64 | tr -d '\n' | tr -dc 'A-Za-z0-9' | head -c 56)"
  done
  printf '%s\n' "$pass"
}

ensure_passfile_owner() {
  if id proxy >/dev/null 2>&1; then
    chown proxy:proxy "$PASSFILE"
  else
    chown root:root "$PASSFILE"
  fi
}

write_squid_config() {
  local auth_bin="$1"

  cat > /etc/squid/squid.conf <<EOF
auth_param basic program $auth_bin $PASSFILE
auth_param basic realm proxy
auth_param basic credentialsttl 2 hours

acl authenticated proxy_auth REQUIRED
acl SSL_ports port 443
acl CONNECT method CONNECT

http_port 127.0.0.1:3128

http_access allow authenticated
http_access deny all

http_access allow CONNECT SSL_ports
http_access deny CONNECT !SSL_ports

via off
forwarded_for delete

access_log /var/log/squid/access.log
cache_log /var/log/squid/cache.log
pid_filename /run/squid.pid
EOF
}

write_stunnel_config() {
  mkdir -p /var/lib/stunnel4
  chown root:root /var/lib/stunnel4
  chmod 755 /var/lib/stunnel4

  cat > "$STUNNEL_CONF" <<EOF
foreground = no
pid = $STUNNEL_PID

[https-proxy]
accept = 0.0.0.0:8443
connect = 127.0.0.1:3128
cert = $STUNNEL_PEM
EOF
}

write_renew_hook() {
  mkdir -p "$(dirname "$RENEW_HOOK")"

  cat > "$RENEW_HOOK" <<EOF
#!/usr/bin/env bash
set -Eeuo pipefail

CERT="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
KEY="/etc/letsencrypt/live/$DOMAIN/privkey.pem"
PEM="$STUNNEL_PEM"

cat "\$CERT" "\$KEY" > "\$PEM"
chmod 600 "\$PEM"

systemctl restart stunnel4
EOF

  chmod +x "$RENEW_HOOK"
}

obtain_or_refresh_certificate() {
  local cert="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
  local key="/etc/letsencrypt/live/$DOMAIN/privkey.pem"

  systemctl stop stunnel4 >/dev/null 2>&1 || true

  if [[ -f "$cert" && -f "$key" ]]; then
    certbot certonly --standalone --keep-until-expiring -d "$DOMAIN" --agree-tos -m "$EMAIL" --non-interactive
  else
    certbot certonly --standalone -d "$DOMAIN" --agree-tos -m "$EMAIL" --non-interactive
  fi

  if [[ ! -f "$cert" || ! -f "$key" ]]; then
    echo "Certificate files were not created: $cert / $key" >&2
    exit 1
  fi

  cat "$cert" "$key" > "$STUNNEL_PEM"
  chmod 600 "$STUNNEL_PEM"
}

enable_stunnel() {
  if [[ -f /etc/default/stunnel4 ]]; then
    if grep -q '^ENABLED=' /etc/default/stunnel4; then
      sed -i 's/^ENABLED=.*/ENABLED=1/' /etc/default/stunnel4
    else
      echo 'ENABLED=1' >> /etc/default/stunnel4
    fi
  fi
}

free_port_8443_if_needed() {
  local pids=""
  pids="$(ss -ltnp '( sport = :8443 )' 2>/dev/null | awk 'NR>1 {print $NF}' | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)"

  if [[ -n "$pids" ]]; then
    echo "Port 8443 is already in use by PID(s): $pids"
    echo "Stopping stunnel4 service and killing stale listeners on 8443..."
    systemctl stop stunnel4 >/dev/null 2>&1 || true
    for pid in $pids; do
      kill "$pid" >/dev/null 2>&1 || true
    done
    sleep 1
  fi
}

show_port_8443_listener() {
  echo
  echo "Listener on 8443:"
  ss -ltnp '( sport = :8443 )' || true
}

main() {
  require_root

  log "1/9" "Installing packages..."
  apt update
  DEBIAN_FRONTEND=noninteractive apt install -y \
    squid \
    apache2-utils \
    certbot \
    stunnel4 \
    openssl \
    iproute2

  log "2/9" "Generating users..."
  rm -f "$PASSFILE"

  declare -A CREDS

  for i in 1 2 3 4 5; do
    user="user$i"
    pass="$(generate_password)"
    CREDS["$user"]="$pass"

    if [[ "$i" -eq 1 ]]; then
      htpasswd -b -c "$PASSFILE" "$user" "$pass"
    else
      htpasswd -b "$PASSFILE" "$user" "$pass"
    fi
  done

  chmod 640 "$PASSFILE"
  ensure_passfile_owner

  log "3/9" "Configuring Squid..."
  AUTH_BIN="$(detect_auth_bin)"
  write_squid_config "$AUTH_BIN"
  systemctl enable squid
  systemctl restart squid

  log "4/9" "Obtaining certificate..."
  obtain_or_refresh_certificate

  log "5/9" "Configuring stunnel..."
  write_stunnel_config
  enable_stunnel

  log "6/9" "Freeing TCP port 8443 if needed..."
  free_port_8443_if_needed

  log "7/9" "Starting stunnel..."
  systemctl enable stunnel4
  systemctl restart stunnel4

  log "8/9" "Setting up certificate renewal hook..."
  write_renew_hook

  log "9/9" "Checking services..."
  systemctl --no-pager --full status squid stunnel4 >/dev/null

  show_port_8443_listener

  echo
  echo "====== USERS ======"
  for user in user1 user2 user3 user4 user5; do
    echo "$user : ${CREDS[$user]}"
  done

  echo
  echo "Proxy endpoint:"
  echo "https://<user>:<pass>@$DOMAIN:8443"
}

main "$@"

Эксплуатационные особенности

Решение не требует ручного сопровождения после установки:

  • сертификаты обновляются автоматически через systemd-таймер Certbot;
  • при обновлении выполняется hook, пересобирающий PEM-файл и перезапускающий stunnel;
  • Squid не требует перезапуска, так как не работает с TLS напрямую.

Заключение

Представленная схема демонстрирует практичный и воспроизводимый способ развёртывания защищённого прокси-сервиса с минимальными зависимостями. Использование Let’s Encrypt и DuckDNS позволяет отказаться от платных сертификатов и доменной инфраструктуры, сохраняя при этом промышленный уровень безопасности.

Подход масштабируется, легко автоматизируется и может служить базой для более сложных сетевых решений — включая фильтрацию трафика, аудит и контроль доступа.

PS. Скрипт протестирован на Debain 12.

Перемещено hobbit из admin

★★★★★

Последнее исправление: unclestephen (всего исправлений: 8)
Ответ на: комментарий от Dimez

но работы всё равно было проделано много, в этом ценность – debug

и текст помимо скрипта набран самостоятельно

unclestephen ★★★★★
() автор топика

duckdns, letencrypt

промышленный уровень безопасности.

выбери одно, корректируй промпт

gagarin0
()
DEBIAN_FRONTEND=noninteractive apt install -y 
squid apache2-utils certbot stunnel4 openssl

Мне кажется, или разбивать эту строку на 2 явно не следовало?

hobbit ★★★★★
()
Ответ на: комментарий от unclestephen

Штука может и годная может и нет, я согласен, что пользы от LLM много, изредка даже при кодингге/скриптинге. Проблема не в LLM, а в пользователях и том, как они это делают и потом разносят по интернету без явного и чёткого указания «далее следует нейровысер». И, к огромному сожалению, ты такой не один.

CrX ★★★★★
()

На статью всё-таки не тянет. В форуме может пригодиться.

hobbit ★★★★★
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария