feat: живой цикл M2M с НРД + мастер установки ключа на флешку

Инфраструктура M2M (живой обмен с НРД через ИШ):
- обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение
  ответа; INFO → ждём Decision; идемпотентность поллера
- fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO
- сырой XML ответа НРД в карточке заявки (для пересылки в ТП)
- тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes,
  4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта
- редирект из теста сразу в карточку заявки

Мастер установки ключа Валидаты на флешку (admin/setup/keywizard):
- пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник
  сертификатов (CRL) → перезапуск+проверка ИШ → готово
- привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен,
  bj-server остаётся в песочнице
- сохранение структуры профиля архива (spr<N>), перечисление съёмных USB

Прочее:
- пакет-доказательство для ТП НРД + форма регистрации участника M2M
- эталонные образцы робота (DOC/m2m_robot_samples)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-06-19 00:03:21 +03:00
parent 6e503433d4
commit 9737c787f9
110 changed files with 10771 additions and 1690 deletions
+54 -14
View File
@@ -26,10 +26,30 @@ type Settings struct {
LK LKSettings `json:"lk"`
CACerts CACertsSettings `json:"ca_certs"`
News NewsSettings `json:"news"`
Update UpdateSettings `json:"update"`
License LicenseSettings `json:"license"`
LastTest *TestRunResult `json:"last_test,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
}
// LicenseSettings — лицензионный ключ (подписанный токен).
type LicenseSettings struct {
Key string `json:"key"` // компактный токен payload.signature.keyid
PublicKey string `json:"public_key"` // base64 (если не зашит в бинарь)
}
// UpdateSettings — авто-обновления из артефактории (#18/#20).
type UpdateSettings struct {
BaseURL string `json:"base_url"` // https://updates.example.com
Channel string `json:"channel"` // "stable" | "beta"
PublicKey string `json:"public_key"` // base64 Ed25519 (если не зашит в бинарь)
AutoCheck bool `json:"auto_check"` // проверять автоматически
LastCheck time.Time `json:"last_check"` // когда последний раз проверяли
LastResult string `json:"last_result"` // текст результата проверки
Available string `json:"available_version"` // доступная версия (если новее)
Notes string `json:"notes,omitempty"` // заметки доступного релиза
}
// NewsSettings — лента новостей (события системы, окна техработ, обновления
// документации НРД). События добавляются вручную через UI или автоматически
// doc-watcher'ом и cron-задачами. Каждое событие может быть скрыто (Dismissed)
@@ -64,8 +84,7 @@ type DocSource struct {
// CACertsSettings — URL'ы для авто-загрузки сертификатов УЦ НРД и нашего
// УЦ. Список редактируется пользователем; раз в сутки фоновая горутина
// перекачивает каждый URL и переустанавливает сертификат, если он
// поменялся. Все сертификаты идут в mroot/uRoot хранилища КриптоПро.
// перекачивает каждый URL и сохраняет сертификат, если он поменялся.
type CACertsSettings struct {
URLs []string `json:"urls"`
AutoUpdate bool `json:"auto_update"`
@@ -91,12 +110,12 @@ type PostgresSettings struct {
DSN string `json:"dsn"`
}
// CryptoSettings — путь к JCP, провайдер, лицензионный ключ.
// CryptoSettings — путь к PKCS#11 модулю и тип провайдера.
type CryptoSettings struct {
Provider string `json:"provider"` // "stub" | "cryptopro" | "validata" | "vipnet"
Provider string `json:"provider"` // "stub" | "validata"
SocketPath string `json:"socket_path"` // UDS crypto-service
JCPPath string `json:"jcp_path"` // путь до jcp.jar
LicenseKey string `json:"license_key"` // лицензионный ключ КриптоПро
ModulePath string `json:"module_path"` // путь до .so модуля PKCS#11
Profile string `json:"profile"` // активный профиль Валидаты (имя из pki1.conf)
}
// NSDSettings — профиль и подключение к ИШ НРД.
@@ -104,6 +123,12 @@ type NSDSettings struct {
Profile string `json:"profile"` // "guest-gost", "test3-gost", ...
IGWBaseURL string `json:"igw_base_url"` // http://host:port
KeyContainer string `json:"key_container"` // имя контейнера (на стороне ИШ)
// Депозитарные реквизиты клиента — откуда списываются бумаги
// (SettlementLocation в M2MTransferRequest). Из договора/письма НРД.
DeponentCode string `json:"deponent_code"` // депкод, напр. MC0413600000
AccountID string `json:"account_id"` // депозитарный счёт
SectionID string `json:"section_id"` // раздел депозитарного счёта
}
// LKSettings — настройки callback в ЛК клиента.
@@ -165,6 +190,24 @@ func (r *RuntimeConfig) UpdatePostgres(s PostgresSettings) error {
return r.save()
}
// SaveLicense сохраняет лицензионные настройки.
func (r *RuntimeConfig) SaveLicense(s LicenseSettings) error {
r.mu.Lock()
r.data.License = s
r.data.UpdatedAt = time.Now().UTC()
r.mu.Unlock()
return r.save()
}
// SaveUpdateSettings сохраняет настройки авто-обновлений.
func (r *RuntimeConfig) SaveUpdateSettings(s UpdateSettings) error {
r.mu.Lock()
r.data.Update = s
r.data.UpdatedAt = time.Now().UTC()
r.mu.Unlock()
return r.save()
}
// UpdateCrypto сохраняет crypto-настройки.
func (r *RuntimeConfig) UpdateCrypto(s CryptoSettings) error {
r.mu.Lock()
@@ -307,7 +350,7 @@ func (r *RuntimeConfig) ReadinessSummary() []Readiness {
},
{
Name: "crypto-service",
Configured: s.Crypto.Provider != "" && s.Crypto.Provider != "stub" && s.Crypto.JCPPath != "",
Configured: s.Crypto.Provider != "" && s.Crypto.Provider != "stub",
Ready: false,
Message: cryptoMsg(s.Crypto),
},
@@ -336,15 +379,12 @@ func posMsg(dsn string) string {
func cryptoMsg(c CryptoSettings) string {
if c.Provider == "" || c.Provider == "stub" {
return "Криптография не настроена (provider=stub). КриптоПро JCP не подключён."
return "Криптография не настроена (provider=stub) — реальная подпись недоступна."
}
if c.JCPPath == "" {
return "Провайдер " + c.Provider + ", но путь к JCP не задан."
if c.ModulePath == "" {
return "Провайдер " + c.Provider + ", путь к PKCS#11 модулю не задан."
}
if c.LicenseKey == "" {
return "Провайдер " + c.Provider + ", JCP есть, лицензия не введена."
}
return "Провайдер " + c.Provider + ", JCP подключён, лицензия введена."
return "Провайдер " + c.Provider + ", PKCS#11 модуль: " + c.ModulePath
}
func nsdMsg(n NSDSettings) string {