diff --git a/internal/lkgateway/setup.go b/internal/lkgateway/setup.go index 9550f5d..20287f4 100644 --- a/internal/lkgateway/setup.go +++ b/internal/lkgateway/setup.go @@ -4,9 +4,13 @@ import ( "context" "errors" "fmt" + "io" + "log" "net/http" "net/url" "os" + "os/exec" + "path/filepath" "strings" "time" @@ -40,11 +44,145 @@ func registerSetup(mux *http.ServeMux, a *admin, rc *RuntimeConfig, svc *Service mux.HandleFunc("/admin/setup/postgres", h.savePostgres) mux.HandleFunc("/admin/setup/crypto", h.saveCrypto) mux.HandleFunc("/admin/setup/crypto/check", h.checkCrypto) + mux.HandleFunc("/admin/setup/crypto/activate", h.activateLicense) + mux.HandleFunc("/admin/setup/crypto/install", h.installCryptoPro) mux.HandleFunc("/admin/setup/nsd", h.saveNSD) mux.HandleFunc("/admin/setup/lk", h.saveLK) mux.HandleFunc("/admin/setup/test-run", h.testRun) } +// installCryptoPro — POST /admin/setup/crypto/install (multipart). +// Принимает tar или tar.gz архив с дистрибутивом КриптоПро CSP (как +// linux-amd64.tgz с cryptopro.ru), распаковывает в /tmp/bj-cryptopro, +// находит все .rpm файлы и устанавливает через sudo rpm -i. +// На РЕД ОС / ALT / ROSA это даёт рабочий /opt/cprocsp/. +func (h *setupHandlers) installCryptoPro(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method", http.StatusMethodNotAllowed) + return + } + // Архив КриптоПро ~50-100 МБ — поднимем лимит до 256 МБ. + if err := r.ParseMultipartForm(256 << 20); err != nil { + setupFlash(w, r, "Установка: ошибка чтения формы: "+err.Error()) + return + } + file, header, err := r.FormFile("dist") + if err != nil { + setupFlash(w, r, "Установка: выберите файл архива дистрибутива (.tar/.tgz/.tar.gz/.rpm)") + return + } + defer file.Close() + + dir := "/tmp/bj-cryptopro" + _ = os.RemoveAll(dir) + if err := os.MkdirAll(dir, 0o755); err != nil { + setupFlash(w, r, "Установка: не получилось создать "+dir+": "+err.Error()) + return + } + dst := filepath.Join(dir, filepath.Base(header.Filename)) + out, err := os.Create(dst) + if err != nil { + setupFlash(w, r, "Установка: не получилось создать "+dst+": "+err.Error()) + return + } + if _, err := io.Copy(out, file); err != nil { + out.Close() + setupFlash(w, r, "Установка: ошибка записи файла: "+err.Error()) + return + } + out.Close() + + // Распаковка (если .tar/.tgz/.tar.gz). + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute) + defer cancel() + if strings.HasSuffix(strings.ToLower(dst), ".rpm") { + // Один rpm — установим напрямую. + } else if strings.HasSuffix(strings.ToLower(dst), ".tar.gz") || strings.HasSuffix(strings.ToLower(dst), ".tgz") { + if untar, err := runCmdInDir(ctx, dir, "tar", "-xzf", dst); err != nil { + setupFlash(w, r, "Установка: распаковка .tgz упала: "+err.Error()+" / вывод: "+untar) + return + } + } else if strings.HasSuffix(strings.ToLower(dst), ".tar") { + if untar, err := runCmdInDir(ctx, dir, "tar", "-xf", dst); err != nil { + setupFlash(w, r, "Установка: распаковка .tar упала: "+err.Error()+" / вывод: "+untar) + return + } + } else { + setupFlash(w, r, "Установка: неизвестный формат файла, нужен .tar/.tgz/.tar.gz/.rpm") + return + } + + // Найти все .rpm в директории. + var rpms []string + _ = filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { + if err == nil && !info.IsDir() && strings.HasSuffix(p, ".rpm") { + rpms = append(rpms, p) + } + return nil + }) + if len(rpms) == 0 { + setupFlash(w, r, "Установка: после распаковки .rpm файлы не найдены в "+dir) + return + } + + // sudo rpm -i <все rpm>. На РЕД ОС иногда нужен --nosignature --nodeps. + args := append([]string{"rpm", "-Uvh", "--replacepkgs", "--nosignature"}, rpms...) + output, err := runCmd(ctx, "sudo", args...) + if err != nil { + setupFlash(w, r, "Установка: rpm -i упал: "+err.Error()+" / вывод: "+strings.TrimSpace(output)) + return + } + setupFlash(w, r, "КриптоПро CSP установлен. Файлов rpm: "+fmt.Sprint(len(rpms))+". Теперь введите серийник и нажмите «Активировать лицензию». Вывод rpm: "+strings.TrimSpace(output)) +} + +// runCmdInDir выполняет команду в указанной рабочей директории. +func runCmdInDir(ctx context.Context, dir, name string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Dir = dir + out, err := cmd.CombinedOutput() + return string(out), err +} + +// runCmd выполняет команду и возвращает stdout+stderr строкой. +func runCmd(ctx context.Context, name string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, name, args...) + out, err := cmd.CombinedOutput() + return string(out), err +} + +// activateLicense — POST /admin/setup/crypto/activate. Принимает серийный +// номер из формы, вызывает cpconfig -license -set, возвращает результат +// во flash. Если КриптоПро CSP не установлен — даёт ссылку на инструкцию. +func (h *setupHandlers) activateLicense(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method", http.StatusMethodNotAllowed) + return + } + serial := strings.TrimSpace(r.FormValue("license_key")) + if serial == "" { + setupFlash(w, r, "Активация лицензии: введите серийный номер в поле выше") + return + } + cpconfig := "/opt/cprocsp/sbin/amd64/cpconfig" + if _, err := os.Stat(cpconfig); err != nil { + setupFlash(w, r, "КриптоПро CSP не установлен ("+cpconfig+" не найден). Раздел /admin/help/cryptopro — команды установки и копирования дистрибутива на ВМ.") + return + } + ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) + defer cancel() + out, err := runCmd(ctx, cpconfig, "-license", "-set", serial) + if err != nil { + setupFlash(w, r, "Активация лицензии не прошла: "+err.Error()+" / вывод: "+strings.TrimSpace(out)) + return + } + cur := h.rc.Snapshot().Crypto + cur.LicenseKey = serial + if err := h.rc.UpdateCrypto(cur); err != nil { + log.Printf("activateLicense: UpdateCrypto: %v", err) + } + setupFlash(w, r, "Лицензия КриптоПро активирована. Вывод cpconfig: "+strings.TrimSpace(out)) +} + // checkCrypto — POST /admin/setup/crypto/check. Запускает Health() // текущего провайдера PKCS#11 без изменения настроек. func (h *setupHandlers) checkCrypto(w http.ResponseWriter, r *http.Request) { diff --git a/internal/lkgateway/web/templates/admin_setup.html b/internal/lkgateway/web/templates/admin_setup.html index bb10084..f27ac07 100644 --- a/internal/lkgateway/web/templates/admin_setup.html +++ b/internal/lkgateway/web/templates/admin_setup.html @@ -80,6 +80,22 @@ Загрузит PKCS#11 модуль, опросит список токенов, покажет результат сверху страницы. + +
+

Установка КриптоПро CSP

+

Дистрибутив с cryptopro.ru (например, linux-amd64.tgz или linux-amd64.tar для РЕД ОС/ALT/ROSA). Загрузите файл здесь — он будет распакован и установлен через sudo rpm -Uvh. Установка длится ~30 секунд.

+
+ + +
+ +
+

Активация лицензии

+
+ + +
+

Вызовет cpconfig -license -set и сохранит серийник. Если КриптоПро CSP ещё не установлен — покажет инструкцию.