diff --git a/deploy/linux/sbin/bj-keymedia-install b/deploy/linux/sbin/bj-keymedia-install index 8bdf2c3..96e4d71 100755 --- a/deploy/linux/sbin/bj-keymedia-install +++ b/deploy/linux/sbin/bj-keymedia-install @@ -18,6 +18,33 @@ ACTION="${1:-}"; STAGING="${2:-}"; PROFILE="${3:-}" TARGET_MNT="${BJ_TARGET_MNT:-}"; TARGET_DEV="${BJ_TARGET_DEV:-}" err(){ echo "{\"ok\":false,\"error\":\"$1\"}"; exit 1; } +# attach_keymedia DEV — ключевая флешка постоянно доступна ИШ, только чтение, под igate (как на рабочем сервере): +# строка fstab по UUID на $DEFAULT_KEYMEDIA с автомонтированием и ссылка реестра ключей Валидаты пользователя igate +# на флешку. Прежний fstab — копией /etc/fstab.bak-bj-<время>. Уже подключена эта же флешка — ничего не меняет. +attach_keymedia(){ + local dev="$1" uuid fst ig_uid ig_gid opts unit + [ -b "$dev" ] || return 0 + uuid=$(blkid -s UUID -o value "$dev" 2>/dev/null); fst=$(blkid -s TYPE -o value "$dev" 2>/dev/null) + [ -n "$uuid" ] && [ -n "$fst" ] || return 0 + ig_uid=$(id -u igate 2>/dev/null) || return 0; ig_gid=$(id -g igate) + if ! grep -Eq "^UUID=$uuid[[:space:]]+$DEFAULT_KEYMEDIA[[:space:]]" /etc/fstab; then + cp -a /etc/fstab "/etc/fstab.bak-bj-$(date +%Y%m%d-%H%M%S)" + sed -i "\#[[:space:]]$DEFAULT_KEYMEDIA[[:space:]]#d" /etc/fstab + case "$fst" in vfat|exfat|ntfs*) opts="ro,uid=$ig_uid,gid=$ig_gid,fmask=0133,dmask=0022" ;; *) opts="ro" ;; esac + printf 'UUID=%s %s %s %s,nofail,x-systemd.automount,x-systemd.device-timeout=10 0 0\n' "$uuid" "$DEFAULT_KEYMEDIA" "$fst" "$opts" >> /etc/fstab + umount "$DEFAULT_KEYMEDIA" 2>/dev/null; mkdir -p "$DEFAULT_KEYMEDIA" + unit=$(systemd-escape -p --suffix=automount "$DEFAULT_KEYMEDIA") + systemctl daemon-reload; systemctl restart "$unit" 2>/dev/null + fi + install -d -o igate -g igate -m 700 /var/lib/igate/.Validata /var/lib/igate/.Validata/vdkeys + if [ ! -e /var/lib/igate/.Validata/vdkeys/vdkeys ]; then + ln -s "$DEFAULT_KEYMEDIA/vdkeys" /var/lib/igate/.Validata/vdkeys/vdkeys; chown -h igate:igate /var/lib/igate/.Validata/vdkeys/vdkeys + fi + ls "$DEFAULT_KEYMEDIA" >/dev/null 2>&1 # разбудить автомонтирование + KEYMEDIA_UUID="$uuid" +} +KEYMEDIA_UUID="" + 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 @@ -120,6 +147,12 @@ if [ "$ACTION" = "stage-media" ]; then exit 0 fi +# use-media — ключ на флешке уже подготовлен (например, флешка с другого сервера bj): использовать как есть. +# На флешку НИЧЕГО не пишется: справочник пересобирается в копии на диске (staging от stage-media), из неё — +# профиль ИШ; ключ Валидата читает с флешки сама. +NOWRITE=0 +if [ "$ACTION" = "use-media" ]; then NOWRITE=1; ACTION=install; 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) @@ -140,6 +173,13 @@ fi # ---- целевая точка монтирования (флешка) ---- TMP_MNT=""; UMOUNT_AFTER=0 +if [ "$NOWRITE" = 1 ]; then + [ -n "$PROFILE" ] || PROFILE="PrUser${USERNUM:-_$(date -u +%Y%m%d-%H%M%S)}" + case "$PROFILE" in *[!A-Za-z0-9_-]*) err "недопустимое имя профиля" ;; esac + BACKUP=""; TARGET_MNT="флешка не менялась" + FLASH_SPR="$SPRDIR"; CRL_SRC="$STAGING" # копия на диске + finish(){ :; } +else [ -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" @@ -165,13 +205,19 @@ 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 +FLASH_SPR="$DEST/$SPRNAME"; CRL_SRC="$DEST" +# записано — временное монтирование снять: дальше флешка подключается постоянно (только чтение) +[ "$UMOUNT_AFTER" = 1 ] && { umount "$TMP_MNT" 2>/dev/null; rmdir "$TMP_MNT" 2>/dev/null; UMOUNT_AFTER=0; } +fi +# ключевая флешка — постоянно для ИШ (справочник подписывается ключом с неё) +[ -n "$TARGET_DEV" ] && attach_keymedia "$TARGET_DEV" # ---- зарегистрировать ключевой носитель (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" +REBUILD_RES="skip" 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 @@ -185,7 +231,7 @@ if [ "$REBUILD_RES" = "ok" ] && [ -d "$(dirname "$IGATE_PROFILES")" ]; then 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 + find "$CRL_SRC" -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 ИШ (идемпотентно) @@ -248,5 +294,5 @@ PY 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" +printf '{"ok":true,"profile":"%s","spr":"%s","vdk":"%s","backup":"%s","target":"%s","rebuild":"%s","igate":"%s","igate_profile_dir":"%s","keymedia":"%s","nowrite":%s}\n' \ + "$PROFILE" "$SPRNAME" "$VDK_NAME" "$BACKUP" "$TARGET_MNT" "$REBUILD_RES" "$IGATE_RES" "$IGATE_PROFILES/$PROFILE" "$KEYMEDIA_UUID" "$([ "$NOWRITE" = 1 ] && echo true || echo false)" diff --git a/internal/lkgateway/keywizard.go b/internal/lkgateway/keywizard.go index d319566..08c27f1 100644 --- a/internal/lkgateway/keywizard.go +++ b/internal/lkgateway/keywizard.go @@ -274,6 +274,66 @@ func (h *setupHandlers) renderKeyWizard(w http.ResponseWriter, r *http.Request) render(w, h.tpl.a.keyWizard, *d) } +// keyWizardUseMedia — «использовать ключ с подготовленной флешки как есть»: на флешку ничего не пишется. +// Справочник для ИШ собирается из её копии на диске, флешка подключается к ИШ постоянно и только на чтение. +func (h *setupHandlers) keyWizardUseMedia(w http.ResponseWriter, r *http.Request, staging string) { + h.keyWiz.mu.Lock() + dev, prof := h.keyWiz.MediaDevice, h.keyWiz.MediaProfile + h.keyWiz.mu.Unlock() + found := false + for _, d := range listFlashDrives() { + found = found || d.Device == dev + } + if dev == "" || !found { + h.keyWizFlash(w, r, "Флешка с ключом не найдена — вставьте её и нажмите «Использовать ключ с флешки» ещё раз.") + return + } + contour := r.FormValue("contour") + if contour != "prod" { + contour = "test3" + } + ctx, cancel := context.WithTimeout(r.Context(), 3*time.Minute) + defer cancel() + res, err := runKeymediaOp(ctx, "use-media", map[string]string{"staging": staging, "profile": prof, "target_dev": dev, "channel": contourChannel(contour)}) + h.keyWiz.mu.Lock() + if err != nil || res["ok"] != true { + msg := errStr(err) + if e, ok := res["error"].(string); ok { + msg = e + } + h.keyWiz.set(1, "error", "Ключ с флешки не подключён: "+msg) + h.keyWiz.mu.Unlock() + h.keyWizFlash(w, r, "Ключ с флешки не подключён: "+msg) + return + } + if p, ok := res["profile"].(string); ok { + h.keyWiz.Profile = p + } + rebuild, _ := res["rebuild"].(string) + igate, _ := res["igate"].(string) + h.keyWiz.set(1, "ok", "Флешка "+dev+" подключена к ИШ только для чтения — на неё ничего не записано") + h.keyWiz.set(2, "ok", fmt.Sprintf("Справочник для ИШ собран на диске (%s); профиль «%s» %s", rebuildRu(rebuild), h.keyWiz.Profile, igateRu(igate))) + h.keyWiz.set(3, "active", "") + h.keyWiz.mu.Unlock() + + h.reactivateCryptoProfile(ctx) + ishOK, ishMsg := h.restartAndVerifyISH(ctx) + h.keyWiz.mu.Lock() + if !ishOK { + h.keyWiz.set(3, "error", "ИШ не подтвердил готовность: "+ishMsg) + h.keyWiz.mu.Unlock() + h.keyWizFlash(w, r, "Ключ подключён, но ИШ не готов: "+ishMsg) + return + } + h.keyWiz.set(3, "ok", "ИШ перезапущен и работает: "+ishMsg) + h.keyWiz.set(4, "ok", "Ключ с флешки подключён — проверьте подключения на странице «Статус»") + h.keyWiz.Done = true + h.keyWiz.mu.Unlock() + _ = os.RemoveAll(staging) + refreshCertStore(h.rc) + http.Redirect(w, r, keyWizBack(r)+"flash="+urlQ("Готово! Ключ с флешки подключён к ИШ как есть — флешка не менялась."), http.StatusSeeOther) +} + // keyWizardFromMedia — POST /admin/setup/keywizard/from-media: ключ уже на флешке (подготовленной bj раньше // или с ключом и справочником от НРД). Флешка только читается: ключ и справочник копируются во временный // каталог — дальше обычная установка на эту же флешку под тем же именем профиля (с резервной копией флешки). @@ -316,6 +376,10 @@ func (h *setupHandlers) keyWizardFromMedia(w http.ResponseWriter, r *http.Reques h.keyWiz.reset() h.keyWiz.StagingID, h.keyWiz.VDK = id, vdk h.keyWiz.MediaDevice, h.keyWiz.MediaProfile = drive.Device, prof + // флешка уже подготовлена: используется как есть — этапы называются по-другому, записи на флешку нет + h.keyWiz.Steps[0].Title = "Ключ найден на подготовленной флешке" + h.keyWiz.Steps[1].Title = "Флешка подключается как есть — на неё ничего не записывается" + h.keyWiz.Steps[2].Title = "Справочник сертификатов для ИШ — на диске сервера" h.keyWiz.set(0, "ok", fmt.Sprintf("Ключ найден на флешке %s: %s, справочник %s%s", fallback(drive.Model, drive.Device), vdk, spr, prefixed(", профиль ", prof))) h.keyWiz.set(1, "active", "") h.keyWiz.mu.Unlock() @@ -416,6 +480,10 @@ func (h *setupHandlers) keyWizardInstall(w http.ResponseWriter, r *http.Request) return } staging := filepath.Join("/var/lib/bj/media/iso", id) + if r.FormValue("as_is") == "1" { + h.keyWizardUseMedia(w, r, staging) + return + } // Выбор флешки и имя профиля из формы. profileName := strings.TrimSpace(r.FormValue("profile_name")) diff --git a/internal/lkgateway/onboarding_test.go b/internal/lkgateway/onboarding_test.go index c4c0e5d..a2f4f5f 100644 --- a/internal/lkgateway/onboarding_test.go +++ b/internal/lkgateway/onboarding_test.go @@ -463,6 +463,15 @@ func TestDumpOnboardingPages(t *testing.T) { h.renderWizard(w, httptest.NewRequest(http.MethodGet, fmt.Sprintf("/admin/wizard?step=%d", step), nil)) _ = os.WriteFile(filepath.Join(dir, fmt.Sprintf("wizard-%02d.html", step)), w.Body.Bytes(), 0o644) } + // шаг «Ключ подписи»: ключ найден на подготовленной флешке — используется как есть + h.keyWiz = newKeyWizardState() + h.keyWiz.StagingID, h.keyWiz.VDK, h.keyWiz.MediaDevice, h.keyWiz.MediaProfile = "media-test", "CKJCI7AYNIOSNLXK.vdk", "/dev/sdb1", "PrUser1046" + h.keyWiz.Steps[0].Title, h.keyWiz.Steps[1].Title = "Ключ найден на подготовленной флешке", "Флешка подключается как есть — на неё ничего не записывается" + h.keyWiz.set(0, "ok", "Ключ найден на флешке USB2FlashStorage: CKJCI7AYNIOSNLXK.vdk, справочник spr1046, профиль PrUser1046") + wk := httptest.NewRecorder() + h.renderWizard(wk, httptest.NewRequest(http.MethodGet, "/admin/wizard?step=8", nil)) + _ = os.WriteFile(filepath.Join(dir, "wizard-08-media.html"), wk.Body.Bytes(), 0o644) + h.keyWiz = nil // шаг QUIK после проверки подключения — выбор «Как bj работает с QUIK» q := rc.Snapshot().Quik q.Server, q.User, q.Verified = "91.209.122.88", "BROKER_88", true diff --git a/internal/lkgateway/web/templates/part_keywiz.html b/internal/lkgateway/web/templates/part_keywiz.html index 2b952e3..06f71e7 100644 --- a/internal/lkgateway/web/templates/part_keywiz.html +++ b/internal/lkgateway/web/templates/part_keywiz.html @@ -42,6 +42,29 @@ +{{else if and $s.StagingID $s.MediaDevice}} + {{/* ключ с подготовленной флешки — используется как есть, на флешку ничего не пишется */}} +
+

Шаг 2 — использовать ключ с флешки

+

На флешке уже подготовленный ключ: {{fallbackTpl $s.VDK "—"}}{{if $s.MediaProfile}}, профиль {{$s.MediaProfile}}{{end}}. + bj подключит его к шлюзу НРД как есть: на флешку ничего не записывается — справочник для ИШ собирается на диске сервера, флешка подключается только для чтения.

+
+ {{template "keyWizRet" .}} + +
+ + {{$c := .SuggestContour}}{{if not $c}}{{$c = .WantContour}}{{end}}{{if not $c}}{{$c = currentContour}}{{end}} + {{if .SuggestIssuer}}

Сертификат от «{{.SuggestIssuer}}» — это {{if eq .SuggestContour "test3"}}тестовый{{else}}боевой{{end}} контур.

{{end}} + + +
+ +
+
{{template "keyWizRet" .}} + +
+
{{else if $s.StagingID}} {{/* Архив загружен — выбор флешки + запись */}}
@@ -107,7 +130,7 @@
-

Ключ уже на флешке (готовили раньше или получили с ключом и справочником): bj возьмёт ключ и справочник с флешки — она только читается — и подключит их к шлюзу НРД. Архив не нужен.

+

У меня уже есть подготовленная флешка — использовать ключ с неё (например, флешка с другого сервера bj). Флешка используется как есть: bj её только читает, ничего на неё не записывает. Архив не нужен.

{{if .Drives}}
{{template "keyWizRet" .}} @@ -118,7 +141,7 @@ {{$d.Device}}{{if $d.IsKeymedia}} · сейчас ключевой носитель ИШ{{end}} {{end}} - +
{{else}}

Флешка не найдена — вставьте её в сервер и обновите страницу.

{{end}}