Files
Bridge-and-Join-s/deploy/linux/sbin/bj-onec-admin
fontvielle 21d5f33394 1С: проверка поручений МНП перед отправкой в НРД
Часть модуля onec-back, по умолчанию выключена, включается в настройках
(«Проверка поручений в 1С»). Исходящий перевод после блокировки бумаг в
МНП ждёт решения 1С и в НРД не уходит, пока 1С не подтвердит каждое
поручение; при отказе bj снимает поручения в QUIK с причиной из 1С и
отклоняет перевод в журнале и кабинете клиента.

- onec.instr / onec.instr_queue / onec.set_decision в базе bj: 1С читает
  очередь и пишет решение под логином onec_bj (только эти права); чтение
  очереди отмечает связь с 1С.
- bj-onec-admin (root, через bj-keymedia): логин onec_bj, вход только с
  IP 1С по TLS (pg_hba.conf), listen_addresses, ufw; revoke закрывает вход.
- onecLoop: отправка подтверждённых, отказы, снятие клиентом в QUIK,
  поручения из терминала, сроки решения и потеря связи — оповещения.
- Карточка заявки: блок «Проверка 1С», решение оператора вместо 1С.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-22 20:13:59 +03:00

105 lines
6.6 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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