Реализация на отдельных двух машинах с протестированным взаимодействием по проверке сообщений

This commit is contained in:
mi
2026-08-19 18:24:00 +03:00
parent bbef7a30c9
commit c7a80e7256
103 changed files with 3457 additions and 3725 deletions
@@ -0,0 +1,588 @@
#!/usr/bin/env bash
# Создание OS-пользователя tunnel на VM2 для SSH local port forwarding к PostgreSQL/PgBouncer.
#
# Контракт: arch-06 — только local forwarding, PermitOpen allow-list, без sudo/shell.
# На VM2 по умолчанию AllowTcpForwarding=no; исключение только для tunnel.
#
# Использование (на VM2 под root):
# TUNNEL_AUTHORIZED_KEY_FILE=/root/bootstrap/tunnel.pub \
# PG_HOST=192.168.0.210 \
# PG_PORT=6433 \
# bash setup-tunnel-user.sh
#
# Опционально:
# TUNNEL_USER=tunnel
# TUNNEL_SOURCE_CIDRS="203.0.113.10/32,198.51.100.0/24" # ограничить источники SSH
# TUNNEL_ALLOW_ANY_SOURCE=true # осознанно разрешить ключу вход с любого адреса
# EXTRA_PERMIT_OPEN="192.168.0.210:5433" # доп. endpoint (direct PG)
# SSHD_MAIN_CONF=/etc/ssh/sshd_config.d/00-han-chat-vm2.conf
#
# По умолчанию TUNNEL_SOURCE_CIDRS обязателен. Разрешение любого источника
# требует явного TUNNEL_ALLOW_ANY_SOURCE=true и должно компенсироваться SG/firewall.
set -Eeuo pipefail
IFS=$'\n\t'
TUNNEL_USER="${TUNNEL_USER:-tunnel}"
TUNNEL_AUTHORIZED_KEY_FILE="${TUNNEL_AUTHORIZED_KEY_FILE:-}"
TUNNEL_SOURCE_CIDRS="${TUNNEL_SOURCE_CIDRS:-}"
TUNNEL_ALLOW_ANY_SOURCE="${TUNNEL_ALLOW_ANY_SOURCE:-false}"
PG_HOST="${PG_HOST:-192.168.0.210}"
PG_PORT="${PG_PORT:-6433}"
EXTRA_PERMIT_OPEN="${EXTRA_PERMIT_OPEN:-}"
SSHD_MAIN_CONF="${SSHD_MAIN_CONF:-/etc/ssh/sshd_config.d/00-han-chat-vm2.conf}"
SSHD_TUNNEL_CONF="/etc/ssh/sshd_config.d/50-han-tunnel-user.conf"
AUTHORIZED_KEYS_DIR="/etc/ssh/authorized_keys"
AUTHORIZED_KEYS_FILE="${AUTHORIZED_KEYS_DIR}/${TUNNEL_USER}"
declare -a PERMIT_OPENS=()
log() { printf '[setup-tunnel] %s\n' "$*"; }
step() { printf '\n=== %s ===\n' "$*"; }
die() { printf '[setup-tunnel] ERROR: %s\n' "$*" >&2; exit 1; }
require_root() {
[[ "$(id -u)" -eq 0 ]] || die "Скрипт нужно запускать от root"
}
require_commands() {
local command
for command in awk chmod cp getent grep id install mktemp mv passwd python3 rm \
ssh-keygen sshd stat systemctl useradd usermod; do
command -v "$command" >/dev/null 2>&1 || die "Не найдена обязательная команда: ${command}"
done
}
validate_tunnel_user() {
[[ "$TUNNEL_USER" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] \
|| die "Некорректное имя пользователя: ${TUNNEL_USER}"
}
validate_source_policy() {
[[ "$TUNNEL_ALLOW_ANY_SOURCE" == "true" || "$TUNNEL_ALLOW_ANY_SOURCE" == "false" ]] \
|| die "TUNNEL_ALLOW_ANY_SOURCE должен быть true или false"
if [[ -n "$TUNNEL_SOURCE_CIDRS" && "$TUNNEL_ALLOW_ANY_SOURCE" == "true" ]]; then
die "Задайте либо TUNNEL_SOURCE_CIDRS, либо TUNNEL_ALLOW_ANY_SOURCE=true, но не оба"
fi
if [[ -z "$TUNNEL_SOURCE_CIDRS" && "$TUNNEL_ALLOW_ANY_SOURCE" != "true" ]]; then
die "TUNNEL_SOURCE_CIDRS обязателен; для осознанного отказа задайте TUNNEL_ALLOW_ANY_SOURCE=true"
fi
if [[ "$TUNNEL_ALLOW_ANY_SOURCE" == "true" ]]; then
log "ВНИМАНИЕ: источник SSH-ключа не ограничен; доступ должен ограничиваться SG/firewall"
fi
}
validate_cidrs() {
local cidrs=$1
local err normalized
[[ -n "$cidrs" ]] || return 0
[[ "$cidrs" =~ ^[0-9A-Fa-f:./,[:space:]]+$ ]] \
|| die "TUNNEL_SOURCE_CIDRS содержит недопустимые символы"
if ! err=$(python3 - "$cidrs" 2>&1 <<'PY'
import ipaddress
import sys
parts = [raw.strip() for raw in sys.argv[1].split(",")]
if not parts or any(not part for part in parts):
print("TUNNEL_SOURCE_CIDRS содержит пустой элемент", file=sys.stderr)
raise SystemExit(1)
for cidr in parts:
try:
ipaddress.ip_network(cidr, strict=False)
except ValueError as exc:
print(f"Некорректный CIDR: {cidr} ({exc})", file=sys.stderr)
raise SystemExit(1) from exc
PY
); then
die "${err:-TUNNEL_SOURCE_CIDRS содержит некорректный CIDR}"
fi
normalized=$(python3 - "$cidrs" <<'PY'
import ipaddress
import sys
print(",".join(
str(ipaddress.ip_network(part.strip(), strict=False))
for part in sys.argv[1].split(",")
))
PY
)
TUNNEL_SOURCE_CIDRS=$normalized
}
validate_host_port() {
local host=$1 port=$2 label=$3
[[ -n "$host" ]] || die "${label}: host пустой"
[[ "$host" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$ ]] \
|| die "${label}: host содержит недопустимые символы"
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) \
|| die "${label}: некорректный port ${port}"
}
parse_host_port_item() {
local item=$1 label=$2
local host port
item="${item//[[:space:]]/}"
[[ -n "$item" ]] || die "${label}: пустой host:port"
[[ "$item" == *:* ]] || die "${label}: ожидается host:port, получено ${item}"
host="${item%%:*}"
port="${item##*:}"
[[ "$host" != "$port" ]] || die "${label}: отсутствует port в ${item}"
validate_host_port "$host" "$port" "$label"
printf '%s:%s\n' "$host" "$port"
}
collect_permit_opens() {
local item host_port existing duplicate
local -a extra=()
validate_host_port "$PG_HOST" "$PG_PORT" "PG"
PERMIT_OPENS=("${PG_HOST}:${PG_PORT}")
if [[ -n "$EXTRA_PERMIT_OPEN" ]]; then
IFS=',' read -r -a extra <<< "$EXTRA_PERMIT_OPEN"
((${#extra[@]} > 0)) || die "EXTRA_PERMIT_OPEN не содержит endpoint"
for item in "${extra[@]}"; do
host_port=$(parse_host_port_item "$item" "EXTRA_PERMIT_OPEN")
duplicate=false
for existing in "${PERMIT_OPENS[@]}"; do
[[ "$existing" == "$host_port" ]] && duplicate=true
done
if [[ "$duplicate" == "false" ]]; then
PERMIT_OPENS+=("$host_port")
fi
done
fi
}
validate_key_file() {
local file=$1
local lines
[[ -n "$file" && -f "$file" && -s "$file" ]] \
|| die "Не найден TUNNEL_AUTHORIZED_KEY_FILE: ${file:-<empty>}"
ssh-keygen -l -f "$file" >/dev/null 2>&1 || die "Некорректный SSH public key: ${file}"
mapfile -t lines < <(grep -Ev '^[[:space:]]*(#|$)' "$file")
((${#lines[@]} == 1)) || die "Файл ключа должен содержать ровно одну строку без комментариев: ${file}"
[[ "${lines[0]}" =~ ^ssh-ed25519[[:space:]]+[A-Za-z0-9+/=]+([[:space:]].*)?$ ]] \
|| die "Разрешён только чистый ssh-ed25519 без SSH-опций: ${file}"
}
read_ed25519_public_key() {
local file=$1
local line key_type key_data ignored_comment
line=$(grep -Ev '^[[:space:]]*(#|$)' "$file")
IFS=$' \t' read -r key_type key_data ignored_comment <<< "$line"
[[ "$key_type" == "ssh-ed25519" ]] || die "Разрешён только ssh-ed25519"
[[ "$key_data" =~ ^[A-Za-z0-9+/=]+$ ]] || die "Некорректное тело публичного ключа"
ED25519_KEY_TYPE=$key_type
ED25519_KEY_DATA=$key_data
# Входной комментарий не влияет на авторизацию и намеренно не переносится.
ED25519_KEY_COMMENT=han-vm2-db-tunnel
}
build_authorized_keys_line() {
local key_file=$1
local from_prefix="" options host_port
read_ed25519_public_key "$key_file"
options='restrict,port-forwarding'
if [[ -n "$TUNNEL_SOURCE_CIDRS" ]]; then
from_prefix="from=\"${TUNNEL_SOURCE_CIDRS}\","
fi
for host_port in "${PERMIT_OPENS[@]}"; do
options+=",permitopen=\"${host_port}\""
done
if [[ -n "$from_prefix" ]]; then
printf '%s%s %s %s %s\n' \
"$from_prefix" "$options" "$ED25519_KEY_TYPE" "$ED25519_KEY_DATA" "$ED25519_KEY_COMMENT"
else
printf '%s %s %s %s\n' \
"$options" "$ED25519_KEY_TYPE" "$ED25519_KEY_DATA" "$ED25519_KEY_COMMENT"
fi
}
create_or_update_user() {
step "Создание пользователя ${TUNNEL_USER}"
local actual_home actual_shell primary_group home_owner
if id "$TUNNEL_USER" >/dev/null 2>&1; then
IFS=: read -r _ _ _ _ _ actual_home actual_shell < <(getent passwd "$TUNNEL_USER")
primary_group=$(id -gn "$TUNNEL_USER")
[[ "$actual_home" == "/home/${TUNNEL_USER}" ]] \
|| die "Существующий ${TUNNEL_USER} имеет неожиданный home: ${actual_home}"
[[ "$primary_group" == "$TUNNEL_USER" ]] \
|| die "Существующий ${TUNNEL_USER} имеет неожиданную primary group: ${primary_group}"
[[ "$actual_shell" == "/usr/sbin/nologin" ]] \
|| die "Отказываюсь переоборудовать существующего ${TUNNEL_USER} с shell ${actual_shell}"
[[ -d "$actual_home" && ! -L "$actual_home" ]] \
|| die "Home ${actual_home} отсутствует, не является каталогом или является симлинком"
home_owner=$(stat -c '%U' "$actual_home")
[[ "$home_owner" == "$TUNNEL_USER" ]] \
|| die "Home ${actual_home} принадлежит ${home_owner}, ожидался ${TUNNEL_USER}"
log "Найден ранее созданный выделенный пользователь ${TUNNEL_USER}"
else
getent group "$TUNNEL_USER" >/dev/null 2>&1 \
&& die "Группа ${TUNNEL_USER} уже существует без одноимённого пользователя"
useradd --create-home --user-group --home-dir "/home/${TUNNEL_USER}" \
--shell /usr/sbin/nologin "$TUNNEL_USER"
fi
passwd -l "$TUNNEL_USER" >/dev/null
usermod --lock --groups "" "$TUNNEL_USER"
[[ "$(id -nG "$TUNNEL_USER")" == "$TUNNEL_USER" ]] \
|| die "Не удалось удалить дополнительные группы пользователя ${TUNNEL_USER}"
}
build_authorized_keys_candidate() {
local target=$1
build_authorized_keys_line "$TUNNEL_AUTHORIZED_KEY_FILE" > "$target"
# sshd читает AuthorizedKeysFile с правами целевого пользователя.
# Файл остаётся root-owned и поэтому 0644 не позволяет tunnel изменить ключ.
chmod 0644 "$target"
}
build_sshd_tunnel_candidate() {
local target=$1 host_port
{
printf 'Match User %s\n' "$TUNNEL_USER"
printf ' AuthorizedKeysFile %s/%%u\n' "$AUTHORIZED_KEYS_DIR"
echo ' AllowTcpForwarding local'
echo ' AllowStreamLocalForwarding no'
echo ' GatewayPorts no'
echo ' AllowAgentForwarding no'
echo ' X11Forwarding no'
echo ' PermitTTY no'
echo ' PermitTunnel no'
echo ' PermitUserRC no'
echo ' MaxSessions 0'
printf ' PermitOpen'
for host_port in "${PERMIT_OPENS[@]}"; do
printf ' %s' "$host_port"
done
printf '\nMatch all\n'
} > "$target"
chmod 0644 "$target"
}
build_main_config_candidate() {
local target=$1 allow_line user
local -a allow_users=()
[[ -f "$SSHD_MAIN_CONF" ]] || die "Не найден ${SSHD_MAIN_CONF}"
[[ ! -L "$SSHD_MAIN_CONF" ]] || die "${SSHD_MAIN_CONF} не должен быть симлинком"
! grep -Eq '^[[:space:]]*Match([[:space:]]|$)' "$SSHD_MAIN_CONF" \
|| die "${SSHD_MAIN_CONF} должен содержать только глобальные настройки, без Match"
python3 - "$SSHD_MAIN_CONF" "$target" "$TUNNEL_USER" <<'PY'
from pathlib import Path
import re
import sys
source = Path(sys.argv[1])
target = Path(sys.argv[2])
user = sys.argv[3]
lines = source.read_text(encoding="utf-8").splitlines(keepends=True)
matches = [
index for index, line in enumerate(lines)
if re.match(r"^\s*AllowUsers\s+", line)
]
if len(matches) != 1:
print(
f"{source}: ожидалась ровно одна глобальная директива AllowUsers, найдено {len(matches)}",
file=sys.stderr,
)
raise SystemExit(1)
index = matches[0]
tokens = lines[index].split()
if user not in tokens[1:]:
newline = "\n" if lines[index].endswith("\n") else ""
lines[index] = f"AllowUsers {' '.join(tokens[1:] + [user])}{newline}"
Path(target).write_text("".join(lines), encoding="utf-8")
PY
allow_line=$(awk '/^[[:space:]]*AllowUsers[[:space:]]+/ {print; exit}' "$target")
IFS=' ' read -r -a allow_users <<< "$allow_line"
((${#allow_users[@]} >= 2)) || die "AllowUsers не содержит пользователей"
for user in "${allow_users[@]:1}"; do
[[ "$user" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] \
|| die "Неподдерживаемый шаблон AllowUsers: ${user}; перечислите реальные локальные учётные записи"
getent passwd "$user" >/dev/null \
|| die "AllowUsers содержит несуществующего пользователя: ${user}"
done
chmod 0644 "$target"
}
verify_endpoint_from_host() {
local endpoint=$1
local host="${endpoint%%:*}"
local port="${endpoint##*:}"
if command -v nc >/dev/null 2>&1; then
nc -z -w 5 "$host" "$port" \
&& log "TCP ${endpoint} доступен" \
|| die "С VM2 нет TCP-доступа к ${endpoint}; сначала почините private network/firewall"
elif command -v timeout >/dev/null 2>&1; then
timeout 5 bash -c 'echo > "/dev/tcp/$1/$2"' _ "$host" "$port" 2>/dev/null \
&& log "TCP ${endpoint} доступен" \
|| die "С VM2 нет TCP-доступа к ${endpoint}; сначала почините private network/firewall"
else
log "nc и timeout не установлены; пропускаю TCP-проверку"
fi
}
verify_network_from_host() {
local endpoint
step "Проверка сетевой доступности разрешённых endpoint с VM2"
for endpoint in "${PERMIT_OPENS[@]}"; do
verify_endpoint_from_host "$endpoint"
done
}
atomic_install() {
local source=$1 target=$2 mode=$3
local temporary
temporary=$(mktemp "${target}.tmp.XXXXXX") || return 1
if ! install -o root -g root -m "$mode" "$source" "$temporary"; then
rm -f -- "$temporary"
return 1
fi
if ! mv -Tf -- "$temporary" "$target"; then
rm -f -- "$temporary"
return 1
fi
}
backup_file() {
local target=$1 name=$2 backup_dir=$3
if [[ -e "$target" || -L "$target" ]]; then
cp -a -- "$target" "${backup_dir}/${name}"
: > "${backup_dir}/${name}.existed"
fi
}
restore_file() {
local target=$1 name=$2 backup_dir=$3
rm -f -- "$target"
if [[ -f "${backup_dir}/${name}.existed" ]]; then
cp -a -- "${backup_dir}/${name}" "$target"
fi
}
rollback_transaction() {
local backup_dir=$1
log "Откат SSH-конфигурации и authorized_keys"
restore_file "$SSHD_MAIN_CONF" main.conf "$backup_dir"
restore_file "$SSHD_TUNNEL_CONF" tunnel.conf "$backup_dir"
restore_file "$AUTHORIZED_KEYS_FILE" authorized_keys "$backup_dir"
}
assert_effective_value() {
local config=$1 key=$2 expected=$3 actual
actual=$(awk -v key="$key" '$1 == key {$1=""; sub(/^ /, ""); print; exit}' <<< "$config")
if [[ "$actual" != "$expected" ]]; then
log "Эффективный sshd ${key}=${actual:-<empty>}, ожидалось ${expected}"
return 1
fi
}
verify_effective_sshd_config() {
local effective endpoint permit_open authorized_keys_value allow_user_found=false
local -a effective_permit_opens=()
effective=$(sshd -T -C "user=${TUNNEL_USER},host=localhost,addr=127.0.0.1") \
|| return 1
assert_effective_value "$effective" allowtcpforwarding local || return 1
assert_effective_value "$effective" allowstreamlocalforwarding no || return 1
assert_effective_value "$effective" allowagentforwarding no || return 1
assert_effective_value "$effective" x11forwarding no || return 1
assert_effective_value "$effective" gatewayports no || return 1
assert_effective_value "$effective" permittty no || return 1
assert_effective_value "$effective" permittunnel no || return 1
assert_effective_value "$effective" permituserrc no || return 1
assert_effective_value "$effective" maxsessions 0 || return 1
assert_effective_value "$effective" passwordauthentication no || return 1
assert_effective_value "$effective" kbdinteractiveauthentication no || return 1
assert_effective_value "$effective" pubkeyauthentication yes || return 1
authorized_keys_value=$(awk '
$1 == "authorizedkeysfile" {$1=""; sub(/^ /, ""); print; exit}
' <<< "$effective")
if [[ "$authorized_keys_value" != "${AUTHORIZED_KEYS_DIR}/%u" \
&& "$authorized_keys_value" != "$AUTHORIZED_KEYS_FILE" ]]; then
log "Эффективный AuthorizedKeysFile=${authorized_keys_value:-<empty>}, ожидался root-owned путь"
return 1
fi
permit_open=$(awk '
$1 == "permitopen" {
for (i = 2; i <= NF; i++) values = values " " $i
}
END {sub(/^ /, "", values); print values}
' <<< "$effective")
IFS=' ' read -r -a effective_permit_opens <<< "$permit_open"
if ((${#effective_permit_opens[@]} != ${#PERMIT_OPENS[@]})); then
log "Эффективный PermitOpen содержит неожиданный набор: ${permit_open:-<empty>}"
return 1
fi
for endpoint in "${PERMIT_OPENS[@]}"; do
if [[ " ${permit_open} " != *" ${endpoint} "* ]]; then
log "Эффективный PermitOpen не содержит ${endpoint}"
return 1
fi
done
if awk -v user="$TUNNEL_USER" '
$1 == "allowusers" {
for (i = 2; i <= NF; i++) if ($i == user) found = 1
}
END {exit !found}
' <<< "$effective"; then
allow_user_found=true
fi
if [[ "$allow_user_found" != "true" ]]; then
log "Эффективный AllowUsers не содержит ${TUNNEL_USER}"
return 1
fi
}
detect_ssh_service() {
if systemctl cat ssh.service >/dev/null 2>&1; then
printf 'ssh\n'
elif systemctl cat sshd.service >/dev/null 2>&1; then
printf 'sshd\n'
else
die "Не найден systemd-сервис ssh.service или sshd.service"
fi
}
prepare_authorized_keys_dir() {
local owner mode
if [[ -e "$AUTHORIZED_KEYS_DIR" || -L "$AUTHORIZED_KEYS_DIR" ]]; then
[[ -d "$AUTHORIZED_KEYS_DIR" && ! -L "$AUTHORIZED_KEYS_DIR" ]] \
|| die "${AUTHORIZED_KEYS_DIR} должен быть обычным каталогом, не симлинком"
owner=$(stat -c '%U:%G' "$AUTHORIZED_KEYS_DIR")
[[ "$owner" == "root:root" ]] \
|| die "${AUTHORIZED_KEYS_DIR} принадлежит ${owner}, ожидался root:root"
mode=$(stat -c '%a' "$AUTHORIZED_KEYS_DIR")
(( (8#$mode & 8#022) == 0 )) \
|| die "${AUTHORIZED_KEYS_DIR} доступен для записи группе или остальным: mode ${mode}"
else
install -d -o root -g root -m 0755 "$AUTHORIZED_KEYS_DIR"
fi
}
install_and_reload_sshd() {
step "Атомарная установка, проверка и reload sshd"
local work_dir=$1 main_candidate=$2 tunnel_candidate=$3 key_candidate=$4
local ssh_service
ssh_service=$(detect_ssh_service)
prepare_authorized_keys_dir
backup_file "$SSHD_MAIN_CONF" main.conf "$work_dir"
backup_file "$SSHD_TUNNEL_CONF" tunnel.conf "$work_dir"
backup_file "$AUTHORIZED_KEYS_FILE" authorized_keys "$work_dir"
if ! atomic_install "$main_candidate" "$SSHD_MAIN_CONF" 0644; then
rollback_transaction "$work_dir"
die "Не удалось установить ${SSHD_MAIN_CONF}; выполнен откат"
fi
if ! atomic_install "$tunnel_candidate" "$SSHD_TUNNEL_CONF" 0644; then
rollback_transaction "$work_dir"
die "Не удалось установить ${SSHD_TUNNEL_CONF}; выполнен откат"
fi
if ! sshd -t; then
rollback_transaction "$work_dir"
die "sshd -t не прошёл; исходная конфигурация восстановлена"
fi
if ! verify_effective_sshd_config; then
rollback_transaction "$work_dir"
die "Эффективная конфигурация sshd не прошла проверку; выполнен откат"
fi
if ! atomic_install "$key_candidate" "$AUTHORIZED_KEYS_FILE" 0644; then
rollback_transaction "$work_dir"
die "Не удалось установить ${AUTHORIZED_KEYS_FILE}; выполнен откат"
fi
if ! systemctl reload "$ssh_service"; then
rollback_transaction "$work_dir"
if sshd -t; then
systemctl reload "$ssh_service" \
|| log "КРИТИЧНО: не удалось reload исходной конфигурации ${ssh_service}"
else
log "КРИТИЧНО: исходная конфигурация после отката не проходит sshd -t"
fi
die "Reload ${ssh_service} не выполнен; конфигурация восстановлена"
fi
log "sshd reload выполнен; ключ хранится в root-owned ${AUTHORIZED_KEYS_FILE}"
}
print_usage() {
cat <<EOF
Готово.
Проверка с вашей рабочей машины (не закрывая текущую admin-сессию):
ssh -i ~/.ssh/han_vm2_tunnel -N \\
-L 16433:${PG_HOST}:${PG_PORT} \\
${TUNNEL_USER}@<VM2_PUBLIC_IP>
В другом терминале:
nc -zv 127.0.0.1 16433
Подключение к БД через туннель (пример):
psql "postgresql://<USER>:<PASSWORD>@127.0.0.1:16433/<DB>?sslmode=verify-full&sslrootcert=<PATH_TO_CA>"
Что запрещено для ${TUNNEL_USER}:
- sudo / shell / доступ к секретам приложения
- remote forwarding (-R), произвольный SOCKS, agent/X11
- forwarding на адреса вне PermitOpen
Политика источника ключа:
- CIDR: ${TUNNEL_SOURCE_CIDRS:-не задан}
- осознанно разрешён любой источник: ${TUNNEL_ALLOW_ANY_SOURCE}
EOF
}
main() {
local work_dir main_candidate tunnel_candidate key_candidate
require_root
require_commands
validate_tunnel_user
validate_source_policy
validate_cidrs "$TUNNEL_SOURCE_CIDRS"
validate_key_file "$TUNNEL_AUTHORIZED_KEY_FILE"
collect_permit_opens
verify_network_from_host
create_or_update_user
work_dir=$(mktemp -d /run/setup-tunnel-user.XXXXXX)
trap "rm -rf -- '$work_dir'" EXIT
main_candidate="${work_dir}/main.conf.candidate"
tunnel_candidate="${work_dir}/tunnel.conf.candidate"
key_candidate="${work_dir}/authorized_keys.candidate"
build_main_config_candidate "$main_candidate"
build_sshd_tunnel_candidate "$tunnel_candidate"
build_authorized_keys_candidate "$key_candidate"
install_and_reload_sshd \
"$work_dir" "$main_candidate" "$tunnel_candidate" "$key_candidate"
print_usage
}
main "$@"