#!/bin/bash # bj-onec-admin — вход 1С в базу PostgreSQL bj для проверки поручений в 1С (через root-демон bj-keymedia). # access — логин onec_bj с паролем, вход в базу bj только с IP сервера 1С и только по TLS: # свои строки в начале pg_hba.conf (# bj-onec), PostgreSQL слушает адрес сервера bj, # с которого виден сервер 1С (перезапуск PostgreSQL — только если этот адрес не слушался), # порт 5432 в ufw для IP 1С, если ufw включён. BJ_EXTRA: {"password":"…","ips":"10.0.0.5/32,…"} # revoke — закрыть вход: NOLOGIN, строки # bj-onec из pg_hba.conf и правила ufw убрать # Права на объекты onec.* выдаёт сам bj (он их владелец). set -uo pipefail q() { python3 -c "import json,sys;print(json.dumps(sys.argv[1],ensure_ascii=False))" "$1"; } ok() { echo "{\"ok\":true,\"output\":$(q "$1")${2:-}}"; exit 0; } err() { echo "{\"ok\":false,\"error\":$(q "$1")}"; exit 0; } x() { python3 -c "import json,os,sys;print(json.loads(os.environ.get('BJ_EXTRA') or '{}').get(sys.argv[1],''))" "$1" 2>/dev/null; } cd / psqlq() { runuser -u postgres -- psql -X -q -At -v ON_ERROR_STOP=1 "$@"; } LOGIN=onec_bj DB=bj MARK='# bj-onec' hba=$(psqlq -d postgres -c 'show hba_file' 2>&1) || err "PostgreSQL недоступен: $hba" [ -f "$hba" ] || err "не найден pg_hba.conf ($hba)" # hba_set "строки" — заменить наш блок (пусто — убрать) в начале pg_hba.conf hba_set() { [ -f "$hba.bak-bj-onec" ] || cp -a "$hba" "$hba.bak-bj-onec" python3 - "$hba" "$MARK" "$1" <<'PY' || return 1 import sys p, mark, new = sys.argv[1], sys.argv[2], sys.argv[3] old = [l for l in open(p).read().split("\n") if not l.rstrip().endswith(mark)] add = [l for l in new.split("\n") if l.strip()] open(p, "w").write("\n".join(add + old).rstrip("\n") + "\n") PY } case "${1:-}" in access) pw=$(x password); ips=$(x ips) [[ "$pw" =~ ^[A-Za-z0-9]{20,64}$ ]] || err "пароль: 20–64 латинских букв и цифр" list=() for ip in ${ips//,/ }; do [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/(2[4-9]|3[0-2])$ ]] || err "недопустимый адрес 1С: $ip" list+=("$ip") done [ ${#list[@]} -gt 0 ] || err "не указан IP-адрес сервера 1С" [ "$(psqlq -d postgres -c 'show ssl')" = on ] || err "в PostgreSQL выключено шифрование (ssl = off) — 1С подключается только по TLS" psqlq -d postgres -c "select 1 from pg_database where datname='$DB'" | grep -q 1 || err "нет базы $DB" # логин: только вход, без прав на создание чего-либо; пароль — через переменную psql (не в командной строке) printf '%s\n' \ "DO \$\$BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$LOGIN') THEN CREATE ROLE $LOGIN; END IF; END\$\$;" \ "ALTER ROLE $LOGIN WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 5 PASSWORD :'pw';" \ "ALTER ROLE $LOGIN SET statement_timeout = '30s';" \ "ALTER ROLE $LOGIN SET search_path = onec;" \ | psqlq -d postgres -v pw="$pw" >/dev/null 2>&1 || err "логин $LOGIN не заведён" # в схеме public базы bj создавать объекты может только bj (у PUBLIC право снимаем) psqlq -d "$DB" -c "GRANT USAGE, CREATE ON SCHEMA public TO bj" -c "REVOKE CREATE ON SCHEMA public FROM PUBLIC" >/dev/null 2>&1 \ || err "права на схему public в базе $DB не изменены" lines="" for ip in "${list[@]}"; do lines+="hostssl $DB $LOGIN $ip md5 $MARK"$'\n'; done lines+="host all $LOGIN 0.0.0.0/0 reject $MARK"$'\n'"host all $LOGIN ::/0 reject $MARK" hba_set "$lines" || err "не удалось записать $hba" # адрес сервера bj, с которого виден сервер 1С first=${list[0]%/*} src=$(ip -o route get "$first" 2>/dev/null | sed -n 's/.* src \([0-9.]*\).*/\1/p' | head -1) [ -n "$src" ] || err "нет маршрута до сервера 1С ($first)" cur=$(psqlq -d postgres -c 'show listen_addresses') restart=no case ",${cur// /}," in *,\*,*|*,"$src",*|*,0.0.0.0,*) ;; *) new="${cur// /},$src" printf '%s\n' "ALTER SYSTEM SET listen_addresses = :'v';" | psqlq -d postgres -v v="$new" >/dev/null 2>&1 || err "listen_addresses не изменён" restart=yes ;; esac if [ "$restart" = yes ]; then systemctl restart postgresql >/dev/null 2>&1 || err "PostgreSQL не перезапустился (listen_addresses = $new)" for i in $(seq 1 30); do psqlq -d postgres -c 'select 1' >/dev/null 2>&1 && break; sleep 1; done else psqlq -d postgres -c 'select pg_reload_conf()' >/dev/null 2>&1 || err "PostgreSQL не перечитал pg_hba.conf" fi fw="" if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then for ip in "${list[@]}"; do ufw allow from "$ip" to any port 5432 proto tcp comment bj-onec >/dev/null 2>&1; done fw="; порт 5432 открыт в ufw" fi port=$(psqlq -d postgres -c 'show port') note="логин $LOGIN, вход с ${list[*]} по TLS, сервер $src:$port" [ "$restart" = yes ] && note+="; PostgreSQL теперь слушает $src (перезапущен)" ok "$note$fw" ",\"host\":$(q "$src"),\"port\":$(q "$port")" ;; revoke) psqlq -d postgres -c "DO \$\$BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$LOGIN') THEN ALTER ROLE $LOGIN NOLOGIN; END IF; END\$\$;" >/dev/null 2>&1 \ || err "вход логином $LOGIN не закрыт" # открытые сеансы 1С — завершить psqlq -d postgres -c "select pg_terminate_backend(pid) from pg_stat_activity where usename = '$LOGIN'" >/dev/null 2>&1 hba_set "" || err "не удалось записать $hba" psqlq -d postgres -c 'select pg_reload_conf()' >/dev/null 2>&1 if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then while n=$(ufw status numbered 2>/dev/null | grep 'bj-onec' | head -1 | sed -n 's/^\[ *\([0-9]*\)\].*/\1/p') && [ -n "$n" ]; do ufw --force delete "$n" >/dev/null 2>&1 || break done fi ok "вход логином $LOGIN закрыт" ;; *) err "команда: access | revoke" ;; esac