9737c787f9
Инфраструктура 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>
97 lines
2.9 KiB
Go
97 lines
2.9 KiB
Go
// Command bj-license-server — онлайн-сервис учёта и отзыва лицензий.
|
|
//
|
|
// Базовая модель лицензирования офлайновая: bj-server проверяет подпись и
|
|
// срок сам. Этот сервер нужен для:
|
|
// - реестра выданных лицензий (учёт);
|
|
// - ОТЗЫВА (revocation) до окончания срока;
|
|
// - проверки клиентом «не отозвана ли» (опциональный online-чек).
|
|
//
|
|
// Хранилище — JSON-файл со списком отозванных ID (для каркаса; в проде —
|
|
// PostgreSQL). API:
|
|
//
|
|
// GET /v1/check?id=<license-id> → {"revoked":bool}
|
|
// GET /healthz
|
|
//
|
|
// Управление отзывом — правкой файла revoked.json (или будущим admin API).
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type store struct {
|
|
mu sync.RWMutex
|
|
path string
|
|
revoked map[string]bool
|
|
}
|
|
|
|
func newStore(path string) *store {
|
|
s := &store{path: path, revoked: map[string]bool{}}
|
|
s.load()
|
|
return s
|
|
}
|
|
|
|
func (s *store) load() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
b, err := os.ReadFile(s.path)
|
|
if err != nil {
|
|
return // файла нет — пустой список
|
|
}
|
|
var ids []string
|
|
if err := json.Unmarshal(b, &ids); err != nil {
|
|
log.Printf("license-server: разбор %s: %v", s.path, err)
|
|
return
|
|
}
|
|
s.revoked = map[string]bool{}
|
|
for _, id := range ids {
|
|
s.revoked[id] = true
|
|
}
|
|
log.Printf("license-server: загружено отозванных лицензий: %d", len(s.revoked))
|
|
}
|
|
|
|
func (s *store) isRevoked(id string) bool {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.revoked[id]
|
|
}
|
|
|
|
func main() {
|
|
addr := flag.String("addr", ":8091", "адрес прослушивания")
|
|
file := flag.String("revoked", "./revoked.json", "JSON-файл со списком отозванных license ID")
|
|
flag.Parse()
|
|
|
|
st := newStore(*file)
|
|
|
|
// Перечитываем файл отзывов раз в минуту (горячее применение).
|
|
go func() {
|
|
t := time.NewTicker(time.Minute)
|
|
defer t.Stop()
|
|
for range t.C {
|
|
st.load()
|
|
}
|
|
}()
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) })
|
|
mux.HandleFunc("/v1/check", func(w http.ResponseWriter, r *http.Request) {
|
|
id := r.URL.Query().Get("id")
|
|
if id == "" {
|
|
http.Error(w, "id required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]bool{"revoked": st.isRevoked(id)})
|
|
})
|
|
|
|
log.Printf("license-server: слушаю %s, отзывы из %s", *addr, *file)
|
|
srv := &http.Server{Addr: *addr, Handler: mux, ReadHeaderTimeout: 10 * time.Second}
|
|
log.Fatal(srv.ListenAndServe())
|
|
}
|