Первичная настройка, повторная проверка: копии без ключевых носителей СКЗИ (--one-file-system, без keymedia), без отпечатка железа и списка компонентов; восстановление на новый сервер — пароль базы bj из копии, база и пользователь ИШ; ключ для контура без канала запоминается и подключается при настройке канала; первый настроенный канал становится рабочим контуром; справочник сертификатов перечитывается после ключа и смены контура; шлюз НРД — шаг 7, ключ — шаг 8 (ключ ставится в профиль ИШ); подсказка контура ключа по сертификату; ИШ: параметры работающего шлюза не перезаписываются; без чужого кода отправителя и получателя по умолчанию; прокси для выхода в интернет; установка с образа без интернета; HTTPS — новый сертификат при смене адреса; bj-network — без cloud-init; проверка готовности: справочник участников, сетевые доступы, учётные записи, последний обмен с НРД
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package lkgateway
|
||||
|
||||
// Прокси для выхода в интернет (05.10.2026). Во многих организациях сервер ходит в интернет только через
|
||||
// прокси: без него не активировать ключ продукта, не получить обновления, не скачать ИШ и документы НРД.
|
||||
// Прокси из настроек действует на всё, что bj открывает в интернете; адреса локальной сети (шлюз НРД, учёт,
|
||||
// модули, свой кабинет) — напрямую. Не задан — как раньше (переменные окружения службы).
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProxySettings — прокси организации.
|
||||
type ProxySettings struct {
|
||||
Address string `json:"address,omitempty"` // http://proxy.corp.local:3128
|
||||
User string `json:"user,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
NoProxy string `json:"no_proxy,omitempty"` // ещё адреса напрямую, через запятую: corp.local, 10.20.0.5
|
||||
}
|
||||
|
||||
// url — адрес прокси с логином (nil — прокси не задан).
|
||||
func (p ProxySettings) url() *url.URL {
|
||||
a := strings.TrimSpace(p.Address)
|
||||
if a == "" {
|
||||
return nil
|
||||
}
|
||||
if !strings.Contains(a, "://") {
|
||||
a = "http://" + a
|
||||
}
|
||||
u, err := url.Parse(a)
|
||||
if err != nil || u.Host == "" {
|
||||
return nil
|
||||
}
|
||||
if p.User != "" {
|
||||
u.User = url.UserPassword(p.User, p.Password)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// proxyBypass — адрес локальной сети или из списка «напрямую».
|
||||
func proxyBypass(host, extra string) bool {
|
||||
host = strings.ToLower(strings.TrimSpace(host))
|
||||
if host == "" || host == "localhost" || !strings.Contains(host, ".") || strings.HasSuffix(host, ".local") {
|
||||
return true
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast()
|
||||
}
|
||||
for _, x := range strings.Split(extra, ",") {
|
||||
x = strings.ToLower(strings.TrimPrefix(strings.TrimSpace(x), "."))
|
||||
if x != "" && (host == x || strings.HasSuffix(host, "."+x)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// proxyFunc — прокси из настроек bj (без них — напрямую).
|
||||
func proxyFunc(rc *RuntimeConfig) func(*http.Request) (*url.URL, error) {
|
||||
return func(req *http.Request) (*url.URL, error) {
|
||||
p := rc.Snapshot().Proxy
|
||||
u := p.url()
|
||||
if u == nil || proxyBypass(req.URL.Hostname(), p.NoProxy) {
|
||||
return nil, nil
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
|
||||
// applyProxy — прокси настроек для всех HTTP-запросов bj (сервер лицензий, обновления, сайт НРД).
|
||||
func applyProxy(rc *RuntimeConfig) {
|
||||
if t, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
env, mine := t.Proxy, proxyFunc(rc)
|
||||
t.Proxy = func(req *http.Request) (*url.URL, error) {
|
||||
if rc.Snapshot().Proxy.url() != nil {
|
||||
return mine(req)
|
||||
}
|
||||
if env != nil {
|
||||
return env(req)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if t, ok := noProxyClient.Transport.(*http.Transport); ok {
|
||||
t.Proxy = proxyFunc(rc) // документы НРД: прокси окружения не берём (он может блокировать nsd.ru), прокси bj — да
|
||||
}
|
||||
}
|
||||
|
||||
func registerProxy(mux *http.ServeMux, rc *RuntimeConfig) {
|
||||
mux.HandleFunc("/admin/setup/proxy", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
p := rc.Snapshot().Proxy
|
||||
p.Address = strings.TrimSpace(r.FormValue("address"))
|
||||
p.User = strings.TrimSpace(r.FormValue("user"))
|
||||
if pw := r.FormValue("password"); pw != "" || p.User == "" {
|
||||
p.Password = pw // пароль в страницу не выводится: пусто — прежний
|
||||
}
|
||||
p.NoProxy = strings.TrimSpace(r.FormValue("no_proxy"))
|
||||
if p.Address != "" && p.url() == nil {
|
||||
setupFlash(w, r, "Прокси: адрес вида http://proxy.example.ru:3128")
|
||||
return
|
||||
}
|
||||
if err := rc.UpdateProxy(p); err != nil {
|
||||
setupFlash(w, r, "Прокси: "+err.Error())
|
||||
return
|
||||
}
|
||||
if p.Address == "" {
|
||||
setupFlash(w, r, "Прокси выключен: bj выходит в интернет напрямую")
|
||||
return
|
||||
}
|
||||
// проверка: сервер лицензий через прокси
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://lic.zetit.ru/healthz", nil)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
setupFlash(w, r, "Прокси сохранён, но сервер лицензий через него недоступен: "+err.Error())
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
setupFlash(w, r, fmt.Sprintf("Прокси сохранён: сервер лицензий через него отвечает (HTTP %d)", resp.StatusCode))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user