HTTPS: http:// перебрасывается на https://; имя сервера — с проверкой DNS; скрипты DEPO при обновлении

Порт HTTPS принимает и http://: 308 на https:// того же имени и порта
(было «Client sent an HTTP request to an HTTPS server»).

Доменное имя сервера сохраняется, только если оно есть в DNS и указывает
на этот сервер; иначе — что добавить/исправить (запись A → IP сервера).
Сообщение об успехе — с адресом https://имя:порт/ и честно про
самоподписанный сертификат.

Обновление bj с новыми процедурами DEPO: отпечаток установленного набора
(depo_procs_applied) не совпадает со встроенным — на главной и в
настройках плашка «требуются повышенные права на базу данных» с вводом
логина/пароля администратора SQL (не сохраняются) и «Применить скрипты».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-22 13:22:34 +03:00
parent b652baba63
commit 1952f020a1
10 changed files with 222 additions and 18 deletions
+86
View File
@@ -7,6 +7,7 @@ package lkgateway
// ходят по HTTP на localhost и не перенаправляются.
import (
"bufio"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@@ -245,6 +246,7 @@ func startHTTPS(rc *RuntimeConfig, handler http.Handler) *http.Server {
return nil
}
httpsUp.Store(true)
ln = newHTTPSSniffListener(ln, rc.Snapshot().HTTPS.port())
go func() {
log.Printf("lk-gateway: listen https %s", srv.Addr)
if err := srv.ServeTLS(ln, "", ""); err != nil && err != http.ErrServerClosed {
@@ -326,3 +328,87 @@ func registerHTTPS(mux *http.ServeMux, rc *RuntimeConfig) {
setupFlash(w, r, "HTTPS: создан новый самоподписанный сертификат на имя сервера и его адреса")
})
}
// httpsSniffListener — порт HTTPS принимает и обычный http://: такой запрос перенаправляется (308) на
// https:// того же имени и порта, вместо ответа Go «Client sent an HTTP request to an HTTPS server».
// TLS-соединение узнаётся по первому байту (0x16 — handshake) и идёт дальше без изменений.
type httpsSniffListener struct {
net.Listener
conns chan net.Conn
errs chan error
port int
}
func newHTTPSSniffListener(ln net.Listener, port int) net.Listener {
l := &httpsSniffListener{Listener: ln, conns: make(chan net.Conn, 16), errs: make(chan error, 1), port: port}
go func() {
for {
c, err := ln.Accept()
if err != nil {
l.errs <- err
return
}
go l.sniff(c)
}
}()
return l
}
func (l *httpsSniffListener) Accept() (net.Conn, error) {
select {
case c := <-l.conns:
return c, nil
case err := <-l.errs:
return nil, err
}
}
func (l *httpsSniffListener) sniff(c net.Conn) {
_ = c.SetReadDeadline(time.Now().Add(10 * time.Second))
br := bufio.NewReader(c)
b, err := br.Peek(1)
if err != nil {
_ = c.Close()
return
}
_ = c.SetReadDeadline(time.Time{})
if b[0] == 0x16 {
l.conns <- &peekedConn{Conn: c, r: br}
return
}
defer c.Close()
_ = c.SetReadDeadline(time.Now().Add(10 * time.Second))
req, err := http.ReadRequest(br)
if err != nil {
return
}
host := req.Host
if h, _, err := net.SplitHostPort(host); err == nil {
host = h
}
if net.ParseIP(host) == nil && !validHostname(strings.ToLower(host)) {
host = "localhost"
}
target := "https://" + net.JoinHostPort(host, strconv.Itoa(l.port)) + req.URL.RequestURI()
_, _ = c.Write([]byte("HTTP/1.1 308 Permanent Redirect\r\nLocation: " + target + "\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"))
}
// peekedConn — соединение, у которого уже прочитан (подсмотрен) первый байт.
type peekedConn struct {
net.Conn
r *bufio.Reader
}
func (p *peekedConn) Read(b []byte) (int, error) { return p.r.Read(b) }
// serverIPs — адреса этого сервера (для проверки, куда указывает доменное имя).
func serverIPs() []string {
var out []string
addrs, _ := net.InterfaceAddrs()
for _, a := range addrs {
if n, ok := a.(*net.IPNet); ok && !n.IP.IsLoopback() && !n.IP.IsLinkLocalUnicast() {
out = append(out, n.IP.String())
}
}
return out
}