Files
Bridge-and-Join-s/internal/lkgateway/proxy.go
T
zuevav 10305a095e Первичная настройка, повторная проверка: копии без ключевых носителей СКЗИ (--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
2026-10-05 17:33:10 +03:00

132 lines
4.9 KiB
Go

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))
})
}