#!/bin/bash # bj-keymedia-install — установка ключа Валидаты на съёмный носитель. # Сохраняет структуру архива НРД (key.reg + spr/ + vdkeys/) в профиль на # носителе, ПЕРЕСОБИРАЕТ совместимый справочник сертификатов (архивный gdbm НРД # несовместим — bj-validata-rebuild-gdbm чинит), и формирует профиль для ИШ. # inspect # install [profile_name] # restart-ish set -uo pipefail DEFAULT_KEYMEDIA=/var/lib/igate/keymedia BACKUP_DIR=/var/lib/bj/key-backups IGATE_PROFILES=/var/lib/igate/profiles IGATE_INI=/var/lib/igate/.Validata/zpki.ini VDLIB=/opt/Validata/VDCSP/lib/amd64 ZPKI=/opt/Validata/VDCSP/bin/amd64/zpki1utl REBUILD=/usr/local/sbin/bj-validata-rebuild-gdbm ACTION="${1:-}"; STAGING="${2:-}"; PROFILE="${3:-}" TARGET_MNT="${BJ_TARGET_MNT:-}"; TARGET_DEV="${BJ_TARGET_DEV:-}" err(){ echo "{\"ok\":false,\"error\":\"$1\"}"; exit 1; } if [ "$ACTION" = "restart-ish" ]; then systemctl restart igate.service >/dev/null 2>&1 || systemctl restart igate-svc >/dev/null 2>&1 || true echo '{"ok":true,"action":"restart-ish"}'; exit 0 fi # ---- сертификаты НРД в справочнике активного профиля ИШ ---- prof_dir(){ PROFILE_NAME=$(su -s /bin/sh postgres -c "psql -d igate -tAc \"select value from parameters where name='cryptography.profile'\"" 2>/dev/null | tr -d '[:space:]') [ -n "$PROFILE_NAME" ] || err "профиль ИШ не задан (cryptography.profile)" PROFDIR="$IGATE_PROFILES/$PROFILE_NAME" [ -f "$PROFDIR/local.gdbm" ] && [ -f "$PROFDIR/local.pse" ] || err "нет справочника в $PROFDIR" } certs_list_json(){ python3 - "$PROFDIR/local.pse" "$PROFDIR/local.gdbm" <<'PY2' import sys,subprocess,json,re out=[]; seen=set() for p in sys.argv[1:]: store=p.rsplit('.',1)[-1] try: d=open(p,'rb').read() except Exception: continue i=0 while True: i=d.find(b'\x30\x82',i) if i<0: break L=int.from_bytes(d[i+2:i+4],'big'); b=d[i:i+4+L] if 100"$BK/rebuild.log") echo "$OUT" | grep -q '"ok":true' || { restore; echo "$OUT"; exit 1; } chown igate:igate "$PROFDIR"/local.*; chmod 644 "$PROFDIR"/local.* # оракул: подписать файл этим профилем (без GUI). Провал → откат. T=$(mktemp -d); echo test > "$T/f"; chown -R igate:igate "$T" SIG=$(su -s /bin/sh igate -c "HOME=/var/lib/igate LD_LIBRARY_PATH=$VDLIB $ZPKI -sign -profile '$PROFILE_NAME' -registry -in '$T/f' -out '$T/s' -sendcert" 2>&1); RC=$? SIGOK=0; [ $RC -eq 0 ] && [ -s "$T/s" ] && SIGOK=1; echo "$SIG" | grep -qiE 'ошибк|error' && SIGOK=0 printf '%s\n' "$SIG" > "$BK/sign-check.log"; rm -rf "$T" if [ $SIGOK -ne 1 ]; then restore; systemctl restart igate.service >/dev/null 2>&1; err "проверка подписи после пересборки не прошла — справочник откачен: $(echo "$SIG" | grep -iE 'ошибк|error|Результат' | head -1 | tr -d '"\\')"; fi systemctl restart igate.service >/dev/null 2>&1 || systemctl restart igate-svc >/dev/null 2>&1 || true sleep 5; ACT=$(systemctl is-active igate.service 2>/dev/null || echo unknown) echo "{\"ok\":true,\"profile\":\"$PROFILE_NAME\",\"backup\":\"$BK\",\"igate\":\"$ACT\",\"rebuild\":$OUT,\"certs\":$(certs_list_json)}"; exit 0 fi [ -n "$STAGING" ] && [ -d "$STAGING" ] || err "staging не найден" case "$STAGING" in /var/lib/bj/media/iso/*) : ;; *) err "staging вне зоны" ;; esac VDK=$(find "$STAGING" -iname '*.vdk' | head -1) GDBM=$(find "$STAGING" -iname '*.gdbm' | head -1) [ -n "$VDK" ] || err "в архиве нет .vdk" VDK_NAME=$(basename "$VDK") SPRDIR=""; [ -n "$GDBM" ] && SPRDIR=$(dirname "$GDBM") # .../spr1046 SPRNAME=""; [ -n "$SPRDIR" ] && SPRNAME=$(basename "$SPRDIR") # spr1046 USERNUM=$(echo "$SPRNAME" | grep -oE '[0-9]+' | head -1) # 1046 if [ "$ACTION" = "inspect" ]; then printf '{"ok":true,"vdk":"%s","profile_dir":"%s","has_gdbm":%s,"has_crl":%s}\n' \ "$VDK_NAME" "$SPRNAME" "$([ -n "$GDBM" ]&&echo true||echo false)" \ "$([ -n "$(find "$STAGING" -iname '*.crl' | head -1)" ]&&echo true||echo false)" exit 0 fi [ "$ACTION" = "install" ] || err "неизвестное действие" # ---- целевая точка монтирования (флешка) ---- TMP_MNT=""; UMOUNT_AFTER=0 [ -z "$TARGET_MNT" ] && [ -z "$TARGET_DEV" ] && TARGET_MNT="$DEFAULT_KEYMEDIA" if [ -z "$TARGET_MNT" ] && [ -n "$TARGET_DEV" ]; then TMP_MNT="/run/bj-keymedia-$$"; mkdir -p "$TMP_MNT" mount "$TARGET_DEV" "$TMP_MNT" || err "не смонтировать $TARGET_DEV" TARGET_MNT="$TMP_MNT"; UMOUNT_AFTER=1 fi [ -d "$TARGET_MNT" ] || err "целевой носитель недоступен" TS=$(date -u +%Y%m%d-%H%M%S); mkdir -p "$BACKUP_DIR" BACKUP="$BACKUP_DIR/keymedia-$TS.tar.gz" tar czf "$BACKUP" -C "$TARGET_MNT" . 2>/dev/null || err "бэкап не удался" REMOUNTED=0 mount | grep -q " $TARGET_MNT .*[(,]ro[,)]" && { mount -o remount,rw "$TARGET_MNT" || err "remount rw не удался"; REMOUNTED=1; } finish(){ sync; [ "$REMOUNTED" = 1 ] && mount -o remount,ro "$TARGET_MNT" 2>/dev/null; [ "$UMOUNT_AFTER" = 1 ] && { umount "$TMP_MNT" 2>/dev/null; rmdir "$TMP_MNT" 2>/dev/null; }; } trap finish EXIT # имя профиля: задано пользователем, иначе PrUser [ -n "$PROFILE" ] || PROFILE="PrUser${USERNUM:-_$TS}" case "$PROFILE" in *[!A-Za-z0-9_-]*) err "недопустимое имя профиля" ;; esac DEST="$TARGET_MNT/$PROFILE" rm -rf "$DEST"; mkdir -p "$DEST" "$TARGET_MNT/vdkeys" cp -rf "$STAGING"/. "$DEST"/ 2>/dev/null # key.reg + spr/ + vdkeys/ cp -f "$VDK" "$TARGET_MNT/vdkeys/$VDK_NAME" sync # ---- зарегистрировать ключевой носитель (vdmkdev читает ключ с флешки) ---- systemctl restart vdmkdev.service >/dev/null 2>&1 || /etc/init.d/vdmkdev restart >/dev/null 2>&1 || true sleep 2 # ---- ПЕРЕСОБРАТЬ совместимый справочник на флешке (архивный gdbm malformed) ---- REBUILD_RES="skip"; FLASH_SPR="$DEST/$SPRNAME" if [ -n "$SPRNAME" ] && [ -x "$REBUILD" ] && [ -f "$FLASH_SPR/local.pse" ]; then RB=$("$REBUILD" "$FLASH_SPR" 2>&1) if echo "$RB" | grep -q '"ok":true'; then REBUILD_RES="ok"; else REBUILD_RES="fail: $(echo "$RB"|tr -d '\n"'|head -c160)"; fi sync fi # ---- сформировать профиль для ИШ (стораны на диске, ключ остаётся на флешке) ---- IGATE_RES="skip" if [ "$REBUILD_RES" = "ok" ] && [ -d "$(dirname "$IGATE_PROFILES")" ]; then IGP="$IGATE_PROFILES/$PROFILE" rm -rf "$IGP"; mkdir -p "$IGP" cp -f "$FLASH_SPR/local.pse" "$FLASH_SPR/local.gdbm" "$IGP"/ 2>/dev/null # CRL-файлы рядом (если есть) — для офлайн-валидации find "$DEST" -iname '*.crl' -exec cp -f {} "$IGP"/ \; 2>/dev/null chown -R igate:igate "$IGP" 2>/dev/null chmod 644 "$IGP"/local.* 2>/dev/null # зарегистрировать профиль в zpki.ini ИШ (идемпотентно) if [ -f "$IGATE_INI" ]; then python3 - "$IGATE_INI" "$PROFILE" "$IGP" <<'PY' && IGATE_RES="ok" || IGATE_RES="ini-fail" import sys ini,name,igp=sys.argv[1],sys.argv[2],sys.argv[3] L=open(ini).read().split("\n") # убрать прежний блок профиля с тем же именем out=[]; skip=False i=0 while i/dev/null 2>&1; then IGATE_RES="connected" fi fi fi finish; trap - EXIT printf '{"ok":true,"profile":"%s","spr":"%s","vdk":"%s","backup":"%s","target":"%s","rebuild":"%s","igate":"%s","igate_profile_dir":"%s"}\n' \ "$PROFILE" "$SPRNAME" "$VDK_NAME" "$BACKUP" "$TARGET_MNT" "$REBUILD_RES" "$IGATE_RES" "$IGATE_PROFILES/$PROFILE"