chore(deploy): обновление ИШ v107, keymedia, экспортёр QUIK, fail2ban для bj
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+208
@@ -0,0 +1,208 @@
|
||||
#!/bin/bash
|
||||
# bj-keymedia-install — установка ключа Валидаты на съёмный носитель.
|
||||
# Сохраняет структуру архива НРД (key.reg + spr<N>/ + vdkeys/) в профиль на
|
||||
# носителе, ПЕРЕСОБИРАЕТ совместимый справочник сертификатов (архивный gdbm НРД
|
||||
# несовместим — bj-validata-rebuild-gdbm чинит), и формирует профиль для ИШ.
|
||||
# inspect <staging>
|
||||
# install <staging> [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<L<20000 and len(b)==4+L:
|
||||
r=subprocess.run(['openssl','x509','-inform','DER','-noout','-subject','-issuer','-serial','-dates','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
|
||||
if r.returncode==0:
|
||||
o=r.stdout.decode('utf-8','replace'); g=lambda k:(re.search('^'+k+'=(.*)$',o,re.M) or [None,''])[1].strip()
|
||||
cn=lambda v:(re.search(r'CN=([^,]+)',v) or [None,''])[1].strip()
|
||||
sn=g('serial').upper()
|
||||
if sn in seen: i+=4; continue
|
||||
seen.add(sn)
|
||||
out.append({'kind':'x509','store':store,'subject':g('subject'),'subject_cn':cn(g('subject')),'issuer_cn':cn(g('issuer')),'serial':sn,'not_before':g('notBefore'),'not_after':g('notAfter'),'self_signed':g('subject')==g('issuer')})
|
||||
else:
|
||||
r=subprocess.run(['openssl','crl','-inform','DER','-noout','-issuer','-nextupdate','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
|
||||
if r.returncode==0:
|
||||
o=r.stdout.decode('utf-8','replace'); m=re.search(r'CN=([^,\n]+)',o); n=re.search(r'nextUpdate=(.*)',o)
|
||||
out.append({'kind':'crl','store':store,'issuer_cn':(m.group(1).strip() if m else ''),'not_after':(n.group(1).strip() if n else '')})
|
||||
i+=4
|
||||
print(json.dumps(out,ensure_ascii=False))
|
||||
PY2
|
||||
}
|
||||
if [ "$ACTION" = "certs-list" ]; then
|
||||
prof_dir
|
||||
echo "{\"ok\":true,\"profile\":\"$PROFILE_NAME\",\"dir\":\"$PROFDIR\",\"certs\":$(certs_list_json)}"; exit 0
|
||||
fi
|
||||
if [ "$ACTION" = "certs-apply" ]; then
|
||||
prof_dir
|
||||
case "$STAGING" in /var/lib/bj/nsd-certs-apply/*) [ -d "$STAGING" ] || err "нет папки с сертификатами" ;; *) err "staging вне зоны" ;; esac
|
||||
TS=$(date +%Y%m%d-%H%M%S); BK="$BACKUP_DIR/certs-$PROFILE_NAME-$TS"; mkdir -p "$BK"
|
||||
cp -a "$PROFDIR"/local.pse "$PROFDIR"/local.gdbm "$BK"/ || err "бэкап справочника не удался"
|
||||
restore(){ cp -a "$BK"/local.pse "$BK"/local.gdbm "$PROFDIR"/; chown igate:igate "$PROFDIR"/local.*; chmod 644 "$PROFDIR"/local.*; }
|
||||
OUT=$(EXTRA_DIR="$STAGING" EXCLUDE_SERIALS="${BJ_EXTRA:-}" "$REBUILD" "$PROFDIR" 2>"$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>
|
||||
[ -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<N>/ + 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<len(L):
|
||||
l=L[i]
|
||||
if l.strip().startswith("[Profile_") :
|
||||
# прочитать имя блока
|
||||
blk=[l]; j=i+1
|
||||
while j<len(L) and not L[j].strip().startswith("["):
|
||||
blk.append(L[j]); j+=1
|
||||
if any(b.strip()==f"Name = {name}" for b in blk):
|
||||
i=j; continue # выкинуть старый одноимённый
|
||||
out+=blk; i=j; continue
|
||||
out.append(l); i+=1
|
||||
L=out
|
||||
# найти max индекс Profile_N
|
||||
idx=-1
|
||||
for l in L:
|
||||
s=l.strip()
|
||||
if s.startswith("[Profile_") and s.endswith("]"):
|
||||
try: idx=max(idx,int(s[len("[Profile_"):-1]))
|
||||
except: pass
|
||||
n=idx+1
|
||||
# нарастить Count в секции верхнеуровневых профилей (последний 'Count = K' до первого Profile_)
|
||||
firstp=next((k for k,l in enumerate(L) if l.strip().startswith("[Profile_")), len(L))
|
||||
for k in range(firstp-1,-1,-1):
|
||||
if L[k].strip().startswith("Count = "):
|
||||
try: L[k]=f"Count = {int(L[k].split('=')[1])+1}"
|
||||
except: pass
|
||||
break
|
||||
L+=[f"[Profile_{n}]",f"Name = {name}","Count = 2",
|
||||
f"store_0 = pse://signed//{igp}/local.pse",
|
||||
f"store_1 = file://{igp}/local.gdbm"]
|
||||
open(ini,"w").write("\n".join(L))
|
||||
PY
|
||||
fi
|
||||
# подключить профиль к ИШ: cryptography.profile в БД igate (канал 33)
|
||||
if [ "$IGATE_RES" = "ok" ]; then
|
||||
if su -s /bin/sh postgres -c "psql -d igate -tAc \"update parameters set value='$PROFILE' where name='cryptography.profile'\"" >/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"
|
||||
Reference in New Issue
Block a user