feat(admin): копирование контейнеров КриптоПро с флешки в HDIMAGE + уточнение PKI по докам НРД
Сканирование смонтированных USB-носителей (/run/media/$USER, /media, /mnt) на папки вида name.000 с *.key — это «контейнер КриптоПро на флешке». В шаге 3 wizard'а и в /admin/setup появилась таблица найденных контейнеров с кнопкой «Скопировать в локальное хранилище» (копирует папку в /var/opt/cprocsp/keys/$USER/, после чего контейнер виден как \\.\HDIMAGE\name и работает без вставленной флешки). Дедуп по AlreadyImported — если папка уже есть в HDIMAGE, кнопка не показывается. Сертификат из контейнера импортируется отдельно через certmgr -inst -cont (это пока вне UI, подсказка в текст flash-сообщения). Кроме того — переписан help-блок в шаге 3 wizard'a на основании «Инструккия M2M.pdf» (стр. 11, 16-19): явно расписано, что в режиме ИШ подписывает сам ИШ (наш ключ в ИШ), а в режиме прямого ONYX — bj-server. Таблица «что куда грузить»: УЦ МБ в mroot, УЦ НРД в mroot+uRoot, наш сертификат в uMy только если без ИШ. Сертификаты с Рутокена явно отмечены как «не грузить — сами появятся».
This commit is contained in:
@@ -0,0 +1,190 @@
|
|||||||
|
package lkgateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlashContainer — найденный на смонтированной флешке контейнер КриптоПро.
|
||||||
|
// КриптоПро CSP под Linux ожидает контейнер в виде папки <name>.000 с
|
||||||
|
// файлами header.key/masks.key/name.key/primary.key/primary2.key.
|
||||||
|
type FlashContainer struct {
|
||||||
|
// Mountpoint — путь смонтированной флешки, например /run/media/user/USB.
|
||||||
|
Mountpoint string
|
||||||
|
// Path — полный путь до папки <name>.000.
|
||||||
|
Path string
|
||||||
|
// Name — имя контейнера (без суффикса .000).
|
||||||
|
Name string
|
||||||
|
// Files — список файлов в контейнере (для дисплея).
|
||||||
|
Files []string
|
||||||
|
// AlreadyImported — true, если папка <name>.000 уже есть в локальном
|
||||||
|
// хранилище /var/opt/cprocsp/keys/<user>/.
|
||||||
|
AlreadyImported bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanFlashContainers ищет контейнеры формата <name>.000 на типичных
|
||||||
|
// точках монтирования USB-носителей в Linux: /run/media/<user>/* и
|
||||||
|
// /media/<user>/* и /media/*. Возвращает список найденных контейнеров.
|
||||||
|
func scanFlashContainers() []FlashContainer {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
roots := []string{
|
||||||
|
filepath.Join("/run/media", u.Username),
|
||||||
|
filepath.Join("/media", u.Username),
|
||||||
|
"/media",
|
||||||
|
"/mnt",
|
||||||
|
}
|
||||||
|
localKeysDir := filepath.Join("/var/opt/cprocsp/keys", u.Username)
|
||||||
|
|
||||||
|
var out []FlashContainer
|
||||||
|
for _, root := range roots {
|
||||||
|
entries, err := os.ReadDir(root)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
if !e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mountpoint := filepath.Join(root, e.Name())
|
||||||
|
out = append(out, findContainersAt(mountpoint, localKeysDir)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func findContainersAt(mountpoint, localKeysDir string) []FlashContainer {
|
||||||
|
var out []FlashContainer
|
||||||
|
// Ищем папки <name>.000 на верхнем уровне и на 1 уровне вглубь.
|
||||||
|
_ = filepath.Walk(mountpoint, func(p string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Глубже 2 уровней не лезем (на флешке могут быть личные папки).
|
||||||
|
rel, _ := filepath.Rel(mountpoint, p)
|
||||||
|
if strings.Count(rel, string(filepath.Separator)) > 2 {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if !info.IsDir() || !strings.HasSuffix(strings.ToLower(p), ".000") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Проверяем, что внутри лежат файлы вида *.key.
|
||||||
|
entries, _ := os.ReadDir(p)
|
||||||
|
var files []string
|
||||||
|
hasKey := false
|
||||||
|
for _, ent := range entries {
|
||||||
|
files = append(files, ent.Name())
|
||||||
|
if strings.HasSuffix(strings.ToLower(ent.Name()), ".key") {
|
||||||
|
hasKey = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasKey {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := strings.TrimSuffix(filepath.Base(p), ".000")
|
||||||
|
fc := FlashContainer{
|
||||||
|
Mountpoint: mountpoint,
|
||||||
|
Path: p,
|
||||||
|
Name: name,
|
||||||
|
Files: files,
|
||||||
|
}
|
||||||
|
// Проверка: уже скопирован в локальное хранилище?
|
||||||
|
if _, err := os.Stat(filepath.Join(localKeysDir, name+".000")); err == nil {
|
||||||
|
fc.AlreadyImported = true
|
||||||
|
}
|
||||||
|
out = append(out, fc)
|
||||||
|
return filepath.SkipDir
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyContainerToLocal копирует папку <name>.000 с флешки в локальное
|
||||||
|
// хранилище КриптоПро /var/opt/cprocsp/keys/<user>/<name>.000. После
|
||||||
|
// этого контейнер виден как \\.\HDIMAGE\<name> и работает даже без
|
||||||
|
// вставленной флешки.
|
||||||
|
func copyContainerToLocal(srcDir string) (string, error) {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
localKeysDir := filepath.Join("/var/opt/cprocsp/keys", u.Username)
|
||||||
|
if err := os.MkdirAll(localKeysDir, 0o700); err != nil {
|
||||||
|
return "", fmt.Errorf("создать %s: %w", localKeysDir, err)
|
||||||
|
}
|
||||||
|
base := filepath.Base(srcDir)
|
||||||
|
dstDir := filepath.Join(localKeysDir, base)
|
||||||
|
if _, err := os.Stat(dstDir); err == nil {
|
||||||
|
return "", fmt.Errorf("контейнер %s уже существует в локальном хранилище", dstDir)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dstDir, 0o700); err != nil {
|
||||||
|
return "", fmt.Errorf("создать %s: %w", dstDir, err)
|
||||||
|
}
|
||||||
|
entries, err := os.ReadDir(srcDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src, err := os.Open(filepath.Join(srcDir, e.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
dst, err := os.OpenFile(filepath.Join(dstDir, e.Name()),
|
||||||
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
src.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(dst, src); err != nil {
|
||||||
|
src.Close()
|
||||||
|
dst.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
src.Close()
|
||||||
|
dst.Close()
|
||||||
|
}
|
||||||
|
return dstDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyContainer — POST /admin/setup/crypto/copy-container.
|
||||||
|
// Параметр src — путь до папки <name>.000 на флешке.
|
||||||
|
func (h *setupHandlers) copyContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := strings.TrimSpace(r.FormValue("src"))
|
||||||
|
if src == "" {
|
||||||
|
setupFlash(w, r, "Копирование контейнера: не указан путь")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Минимальная защита: ожидаем .000 в конце пути.
|
||||||
|
if !strings.HasSuffix(strings.ToLower(src), ".000") {
|
||||||
|
setupFlash(w, r, "Копирование контейнера: путь должен заканчиваться на .000")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(src); err != nil {
|
||||||
|
setupFlash(w, r, "Копирование контейнера: исходная папка недоступна: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst, err := copyContainerToLocal(src)
|
||||||
|
if err != nil {
|
||||||
|
setupFlash(w, r, "Копирование контейнера: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Дадим CSP несколько мс «заметить» новый контейнер (не критично).
|
||||||
|
_, cancel := context.WithTimeout(r.Context(), 100*time.Millisecond)
|
||||||
|
cancel()
|
||||||
|
setupFlash(w, r, "Контейнер скопирован в "+dst+". Теперь он виден как \\\\.\\HDIMAGE\\"+strings.TrimSuffix(filepath.Base(dst), ".000")+" и работает без вставленной флешки. Импортируйте сертификат: certmgr -inst -cont '\\\\.\\HDIMAGE\\"+strings.TrimSuffix(filepath.Base(dst), ".000")+"' -store uMy.")
|
||||||
|
}
|
||||||
+25
-18
@@ -74,6 +74,9 @@ func registerSetup(mux *http.ServeMux, a *admin, rc *RuntimeConfig, svc *Service
|
|||||||
mux.HandleFunc("/admin/setup/cacerts", h.saveCACerts)
|
mux.HandleFunc("/admin/setup/cacerts", h.saveCACerts)
|
||||||
mux.HandleFunc("/admin/setup/cacerts/fetch", h.fetchCACertsNow)
|
mux.HandleFunc("/admin/setup/cacerts/fetch", h.fetchCACertsNow)
|
||||||
|
|
||||||
|
// Копирование контейнера КриптоПро с флешки в локальное хранилище.
|
||||||
|
mux.HandleFunc("/admin/setup/crypto/copy-container", h.copyContainer)
|
||||||
|
|
||||||
// Пошаговый мастер настройки для нетехнических пользователей.
|
// Пошаговый мастер настройки для нетехнических пользователей.
|
||||||
mux.HandleFunc("/admin/wizard", h.renderWizard)
|
mux.HandleFunc("/admin/wizard", h.renderWizard)
|
||||||
}
|
}
|
||||||
@@ -84,6 +87,7 @@ type WizardData struct {
|
|||||||
Step int
|
Step int
|
||||||
Settings Settings
|
Settings Settings
|
||||||
Certs []cryptocli.Certificate
|
Certs []cryptocli.Certificate
|
||||||
|
FlashContainers []FlashContainer
|
||||||
Flash string
|
Flash string
|
||||||
CryptoProInstalled bool
|
CryptoProInstalled bool
|
||||||
CryptoProVersion string
|
CryptoProVersion string
|
||||||
@@ -106,10 +110,11 @@ func (h *setupHandlers) renderWizard(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
s := h.rc.Snapshot()
|
s := h.rc.Snapshot()
|
||||||
d := WizardData{
|
d := WizardData{
|
||||||
page: nowPage("Мастер настройки", "wizard"),
|
page: nowPage("Мастер настройки", "wizard"),
|
||||||
Settings: s,
|
Settings: s,
|
||||||
Certs: h.listCertsForUI(),
|
Certs: h.listCertsForUI(),
|
||||||
Flash: r.URL.Query().Get("flash"),
|
FlashContainers: scanFlashContainers(),
|
||||||
|
Flash: r.URL.Query().Get("flash"),
|
||||||
}
|
}
|
||||||
d.Done.Postgres = s.Postgres.DSN != ""
|
d.Done.Postgres = s.Postgres.DSN != ""
|
||||||
d.Done.Crypto = s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.JCPPath != ""
|
d.Done.Crypto = s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.JCPPath != ""
|
||||||
@@ -401,13 +406,14 @@ func (h *setupHandlers) checkCrypto(w http.ResponseWriter, r *http.Request) {
|
|||||||
// SetupData — данные для шаблона admin_setup.html.
|
// SetupData — данные для шаблона admin_setup.html.
|
||||||
type SetupData struct {
|
type SetupData struct {
|
||||||
page
|
page
|
||||||
Settings Settings
|
Settings Settings
|
||||||
Readiness []Readiness
|
Readiness []Readiness
|
||||||
ReadyCount int
|
ReadyCount int
|
||||||
TotalCount int
|
TotalCount int
|
||||||
Certificates []cryptocli.Certificate
|
Certificates []cryptocli.Certificate
|
||||||
Flash string
|
FlashContainers []FlashContainer
|
||||||
Error string
|
Flash string
|
||||||
|
Error string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *setupHandlers) renderSetup(w http.ResponseWriter, _ *http.Request, flash string) {
|
func (h *setupHandlers) renderSetup(w http.ResponseWriter, _ *http.Request, flash string) {
|
||||||
@@ -420,13 +426,14 @@ func (h *setupHandlers) renderSetup(w http.ResponseWriter, _ *http.Request, flas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
data := SetupData{
|
data := SetupData{
|
||||||
page: nowPage("Настройка", "setup"),
|
page: nowPage("Настройка", "setup"),
|
||||||
Settings: s,
|
Settings: s,
|
||||||
Readiness: r,
|
Readiness: r,
|
||||||
ReadyCount: ready,
|
ReadyCount: ready,
|
||||||
TotalCount: len(r),
|
TotalCount: len(r),
|
||||||
Certificates: h.listCertsForUI(),
|
Certificates: h.listCertsForUI(),
|
||||||
Flash: flash,
|
FlashContainers: scanFlashContainers(),
|
||||||
|
Flash: flash,
|
||||||
}
|
}
|
||||||
if errVal := errMsgFromQuery(_q(w)); errVal != "" {
|
if errVal := errMsgFromQuery(_q(w)); errVal != "" {
|
||||||
data.Error = errVal
|
data.Error = errVal
|
||||||
|
|||||||
@@ -147,6 +147,37 @@
|
|||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Контейнеры КриптоПро на флешке -->
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="dot {{if .FlashContainers}}ok{{else}}warn{{end}}"></span>Контейнеры на USB-носителях (флешка/Рутокен)</h2>
|
||||||
|
{{if .FlashContainers}}
|
||||||
|
<p class="muted">Найдено {{len .FlashContainers}} контейнер(а) формата <code>name.000</code> на смонтированных USB-носителях. Кнопка ниже копирует папку в <code>/var/opt/cprocsp/keys/$USER/</code> — после этого контейнер виден как <code>\\.\HDIMAGE\name</code> и работает без вставленной флешки.</p>
|
||||||
|
<table style="margin-top:8px">
|
||||||
|
<thead><tr><th>Носитель</th><th>Имя контейнера</th><th>Файлы</th><th>Статус</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .FlashContainers}}
|
||||||
|
<tr>
|
||||||
|
<td><code style="font-size:12px">{{.Mountpoint}}</code></td>
|
||||||
|
<td><strong>{{.Name}}</strong></td>
|
||||||
|
<td><span class="muted" style="font-size:11px">{{len .Files}} файлов</span></td>
|
||||||
|
<td>{{if .AlreadyImported}}<span style="color:var(--ok)">уже в HDIMAGE</span>{{else}}<span class="muted">только на флешке</span>{{end}}</td>
|
||||||
|
<td>
|
||||||
|
{{if not .AlreadyImported}}
|
||||||
|
<form method="post" action="/admin/setup/crypto/copy-container" style="margin:0">
|
||||||
|
<input type="hidden" name="src" value="{{.Path}}">
|
||||||
|
<button type="submit" class="btn" style="background:var(--ok);color:#0a0f1a;padding:6px 12px;font-size:12px;font-weight:600">Скопировать в локальное хранилище</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{else}}
|
||||||
|
<p class="muted">Подключённые USB-носители с контейнерами КриптоПро (папки <code>name.000</code> с *.key) не обнаружены. Поиск идёт в <code>/run/media/$USER/</code>, <code>/media/$USER/</code>, <code>/media/</code>, <code>/mnt/</code>. Вставьте флешку и обновите страницу.</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Авто-загрузка сертификатов УЦ НРД -->
|
<!-- Авто-загрузка сертификатов УЦ НРД -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2><span class="dot {{if .Settings.CACerts.URLs}}ok{{else}}warn{{end}}"></span>Сертификаты УЦ (НРД и др.) — авто-загрузка</h2>
|
<h2><span class="dot {{if .Settings.CACerts.URLs}}ok{{else}}warn{{end}}"></span>Сертификаты УЦ (НРД и др.) — авто-загрузка</h2>
|
||||||
|
|||||||
@@ -150,13 +150,23 @@
|
|||||||
<p>Импортируйте сертификаты вашей организации и сертификаты УЦ НРД (для проверки квитанций).</p>
|
<p>Импортируйте сертификаты вашей организации и сертификаты УЦ НРД (для проверки квитанций).</p>
|
||||||
|
|
||||||
<div class="help-block">
|
<div class="help-block">
|
||||||
<strong>Какие сертификаты нужны?</strong>
|
<strong>Что говорят документы НРД (<code>DOC/Инструккия M2M.pdf</code>, стр. 11, 16-19):</strong>
|
||||||
<ol style="margin:6px 0 0 16px">
|
<ul style="margin:6px 0 6px 16px">
|
||||||
<li>Ваш сертификат организации с приватным ключом (<code>.pfx</code> / <code>.p12</code> на диске или контейнер на Рутокене) — для подписи отправляемых пакетов.</li>
|
<li>Наши пакеты должны быть подписаны сертификатом <strong>УЦ МБ</strong> (Удостоверяющий центр Московской Биржи).</li>
|
||||||
<li>Корневой сертификат вашего УЦ (<code>.cer</code>) — в хранилище <code>mroot</code>.</li>
|
<li>В режиме <strong>ИШ НРД</strong>: подписывает <em>сам ИШ</em> — наш ключ настраивается <em>в ИШ</em>, не здесь. Bj-server нужен только для проверки квитанций НРД и (опц.) расшифровки 4BROKER01.</li>
|
||||||
<li>Корневой и подписной сертификаты УЦ НРД — для проверки квитанций. Скачиваются с <a href="https://www.nsd.ru/workflow/system/cryptography/" target="_blank">nsd.ru/workflow/system/cryptography/</a></li>
|
<li>В режиме <strong>прямого ONYX без ИШ</strong>: bj-server подписывает сам — нужен наш ключ с приватной частью.</li>
|
||||||
</ol>
|
</ul>
|
||||||
<strong>Где взять?</strong> Сертификат вашей организации — у вашего УЦ (Контур, СКБ Контур, ИнфоТеКС, КриптоПро УЦ, …). Сертификаты УЦ НРД — на сайте НРД (см. выше).
|
<strong>Что куда загружать (по режиму):</strong>
|
||||||
|
<table style="margin-top:6px;font-size:13px">
|
||||||
|
<thead><tr><th>Что</th><th>Зачем</th><th>Куда</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Корневой сертификат <strong>УЦ МБ</strong> (<a href="https://ca.moex.com/" target="_blank">ca.moex.com</a>)</td><td>проверка цепочки нашей подписи и подписей контрагентов</td><td><code>mroot</code></td></tr>
|
||||||
|
<tr><td>Корневой и подписной <strong>УЦ НРД</strong> (<a href="https://www.nsd.ru/workflow/system/cryptography/" target="_blank">nsd.ru/workflow/system/cryptography/</a>)</td><td>проверка квитанций от НРД</td><td><code>mroot</code> + <code>uRoot</code></td></tr>
|
||||||
|
<tr><td>Наш сертификат + ключ <em>(только если без ИШ)</em></td><td>подпись отправляемых пакетов + расшифровка 4BROKER01</td><td><code>uMy</code> — с приватным ключом</td></tr>
|
||||||
|
<tr><td>Сертификаты с Рутокена</td><td>сами появятся в таблице ниже после подключения USB</td><td>не грузить</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="muted" style="margin-top:6px">Полный регламент PKI — в «Правилах ЭДО НРД» и «Руководстве по установке ИШ» (<a href="https://www.nsd.ru/ru/documents/workflow/" target="_blank">nsd.ru/ru/documents/workflow/</a>) — в наших PDF этого не описано.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 style="margin-top:18px">Импорт сертификата</h3>
|
<h3 style="margin-top:18px">Импорт сертификата</h3>
|
||||||
@@ -181,6 +191,35 @@
|
|||||||
<button type="submit" class="btn">Импортировать</button>
|
<button type="submit" class="btn">Импортировать</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<h3 style="margin-top:18px">Контейнеры на подключённых носителях (флешка/Рутокен)</h3>
|
||||||
|
{{if .FlashContainers}}
|
||||||
|
<p class="muted">Найдено {{len .FlashContainers}} контейнер(а) формата <code>name.000</code> на смонтированных USB-носителях. Нажмите «Скопировать в локальное хранилище» — папка будет перенесена в <code>/var/opt/cprocsp/keys/$USER/</code>, после чего контейнер виден как <code>\\.\HDIMAGE\name</code> и работает даже без вставленной флешки.</p>
|
||||||
|
<table style="margin-top:8px">
|
||||||
|
<thead><tr><th>Носитель</th><th>Имя контейнера</th><th>Файлы</th><th>Статус</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .FlashContainers}}
|
||||||
|
<tr>
|
||||||
|
<td><code style="font-size:12px">{{.Mountpoint}}</code></td>
|
||||||
|
<td><strong>{{.Name}}</strong></td>
|
||||||
|
<td><span class="muted" style="font-size:11px">{{len .Files}} файлов</span></td>
|
||||||
|
<td>{{if .AlreadyImported}}<span style="color:var(--ok)">уже в HDIMAGE</span>{{else}}<span class="muted">только на флешке</span>{{end}}</td>
|
||||||
|
<td>
|
||||||
|
{{if not .AlreadyImported}}
|
||||||
|
<form method="post" action="/admin/setup/crypto/copy-container" style="margin:0">
|
||||||
|
<input type="hidden" name="src" value="{{.Path}}">
|
||||||
|
<button type="submit" class="btn" style="background:var(--ok);padding:6px 12px;font-size:12px">Скопировать в локальное хранилище</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="muted" style="margin-top:8px">После копирования: импортировать сертификат из контейнера командой <code>certmgr -inst -cont '\\.\HDIMAGE\{имя}' -store uMy</code> — это пропишет сертификат в видимое хранилище. (UI-кнопку для этого добавим следующим шагом.)</p>
|
||||||
|
{{else}}
|
||||||
|
<p class="muted">Подключённые USB-носители с контейнерами КриптоПро формата <code>name.000</code> не обнаружены. Поиск идёт в <code>/run/media/$USER/</code>, <code>/media/$USER/</code>, <code>/media/</code>, <code>/mnt/</code>. Вставьте флешку с контейнером и обновите страницу — контейнер появится в этой таблице автоматически.</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<h3 style="margin-top:18px">Авто-загрузка сертификатов УЦ НРД</h3>
|
<h3 style="margin-top:18px">Авто-загрузка сертификатов УЦ НРД</h3>
|
||||||
<p class="muted">Самый простой способ — добавить прямые URL <code>.cer</code>-файлов УЦ НРД (с <a href="https://www.nsd.ru/workflow/system/cryptography/" target="_blank" rel="noopener">nsd.ru/workflow/system/cryptography/</a>) и включить авто-обновление. Раз в сутки система перепроверит и переустановит изменённые сертификаты.</p>
|
<p class="muted">Самый простой способ — добавить прямые URL <code>.cer</code>-файлов УЦ НРД (с <a href="https://www.nsd.ru/workflow/system/cryptography/" target="_blank" rel="noopener">nsd.ru/workflow/system/cryptography/</a>) и включить авто-обновление. Раз в сутки система перепроверит и переустановит изменённые сертификаты.</p>
|
||||||
<form method="post" action="/admin/setup/cacerts" style="margin-top:8px;display:grid;gap:10px">
|
<form method="post" action="/admin/setup/cacerts" style="margin-top:8px;display:grid;gap:10px">
|
||||||
|
|||||||
Reference in New Issue
Block a user