270 lines
9.9 KiB
Go
270 lines
9.9 KiB
Go
package proxymap
|
||
|
||
import (
|
||
"crypto/tls"
|
||
"io/ioutil"
|
||
"net"
|
||
"net/http"
|
||
"net/http/httputil"
|
||
"net/url"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"acme-reverseproxy/nfs"
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
// stripHostPort возвращает только имя хоста, если в строке был порт.
|
||
func stripHostPort(host string) string {
|
||
h, _, err := net.SplitHostPort(host)
|
||
if err != nil {
|
||
return host
|
||
}
|
||
return h
|
||
}
|
||
|
||
// tlsServerName задаёт SNI для исходящего TLS к бэкенду.
|
||
// При подключении по IP многие серверы отклоняют SNI с IP (tls: unrecognized name);
|
||
// в этом случае используем публичное имя из ключа маппинга — как правило, оно совпадает с CN/SAN сертификата бэкенда.
|
||
func tlsServerName(frontendKey string, u *url.URL) string {
|
||
backend := u.Hostname()
|
||
if ip := net.ParseIP(backend); ip != nil {
|
||
return stripHostPort(frontendKey)
|
||
}
|
||
return backend
|
||
}
|
||
|
||
func ToReverseProxyMap(m map[string]string) (ReverseProxyMap, error) {
|
||
rpm := ReverseProxyMap{}
|
||
for k, v := range m {
|
||
rpURL, err := url.Parse(v)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
proxy := httputil.NewSingleHostReverseProxy(rpURL)
|
||
|
||
// Настройка транспорта для поддержки HTTP и HTTPS бэкендов
|
||
if rpURL.Scheme == "https" {
|
||
sn := tlsServerName(k, rpURL)
|
||
logrus.Infof("Configuring HTTPS transport for backend: %s (TLS ServerName/SNI: %s)", rpURL.String(), sn)
|
||
// Создаем транспорт с поддержкой TLS для HTTPS бэкендов
|
||
transport := &http.Transport{
|
||
Proxy: http.ProxyFromEnvironment,
|
||
TLSClientConfig: &tls.Config{
|
||
ServerName: sn,
|
||
InsecureSkipVerify: false,
|
||
},
|
||
// Настройки подключения
|
||
MaxIdleConns: 100,
|
||
MaxIdleConnsPerHost: 100,
|
||
IdleConnTimeout: 90 * time.Second,
|
||
DisableCompression: true,
|
||
ForceAttemptHTTP2: true,
|
||
}
|
||
proxy.Transport = transport
|
||
} else {
|
||
logrus.Debugf("Configuring HTTP transport for backend: %s", rpURL.String())
|
||
// Для HTTP используем стандартный транспорт
|
||
transport := &http.Transport{
|
||
Proxy: http.ProxyFromEnvironment,
|
||
// Настройки подключения
|
||
MaxIdleConns: 100,
|
||
MaxIdleConnsPerHost: 100,
|
||
IdleConnTimeout: 90 * time.Second,
|
||
}
|
||
proxy.Transport = transport
|
||
}
|
||
|
||
// Настройка директив для правильной работы прокси
|
||
originalDirector := proxy.Director
|
||
proxy.Director = func(req *http.Request) {
|
||
originalDirector(req)
|
||
// httputil.NewSingleHostReverseProxy уже правильно устанавливает Host и URL
|
||
// Логируем проксирование
|
||
logrus.Infof("Proxying request: %s %s -> %s%s (Request Host: %s, Target: %s, Scheme: %s)",
|
||
req.Method, req.URL.Path, rpURL.Scheme, req.URL.Path, req.Host, rpURL.Host, rpURL.Scheme)
|
||
}
|
||
|
||
// Обработка ошибок проксирования
|
||
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
||
logrus.Errorf("Reverse proxy error for %s -> %s: %v", r.Host, rpURL.String(), err)
|
||
http.Error(w, "Bad Gateway", http.StatusBadGateway)
|
||
}
|
||
|
||
// Логирование успешных ответов
|
||
originalModifyResponse := proxy.ModifyResponse
|
||
proxy.ModifyResponse = func(resp *http.Response) error {
|
||
if originalModifyResponse != nil {
|
||
if err := originalModifyResponse(resp); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
logrus.Debugf("Proxy response: %s %s -> status %d",
|
||
resp.Request.Method, resp.Request.URL.Path, resp.StatusCode)
|
||
return nil
|
||
}
|
||
|
||
rpm[k] = proxy
|
||
logrus.Infof("Configured reverse proxy: %s -> %s (scheme: %s)", k, rpURL.String(), rpURL.Scheme)
|
||
}
|
||
return rpm, nil
|
||
}
|
||
|
||
type ReverseProxyMap map[string]http.Handler
|
||
|
||
func NewReverseProxiesHandler(rpm ReverseProxyMap) ReverseProxiesHandler {
|
||
return &reverseProxiesHandler{
|
||
Map: rpm,
|
||
NotFoundHandler: http.NotFoundHandler(),
|
||
WellKnownDir: "/tmp/.well-known",
|
||
}
|
||
}
|
||
|
||
func NewReverseProxiesHandlerWithWellKnown(rpm ReverseProxyMap, wellKnownDir string) ReverseProxiesHandler {
|
||
return &reverseProxiesHandler{
|
||
Map: rpm,
|
||
NotFoundHandler: http.NotFoundHandler(),
|
||
WellKnownDir: wellKnownDir,
|
||
}
|
||
}
|
||
|
||
type ReverseProxiesHandler interface {
|
||
http.Handler
|
||
}
|
||
|
||
type reverseProxiesHandler struct {
|
||
Map ReverseProxyMap
|
||
NotFoundHandler http.Handler
|
||
WellKnownDir string
|
||
}
|
||
|
||
func (rph reverseProxiesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
logrus.Infof("Incoming request: %s %s from %s (Host: %s)", r.Method, r.URL.Path, r.RemoteAddr, r.Host)
|
||
|
||
if strings.HasPrefix(r.URL.Path, "/.well-known/acme-wellknown/") {
|
||
logrus.Infof("Handling .well-known/acme-wellknown request: %s", r.URL.Path)
|
||
rph.handleWellKnown(w, r)
|
||
return
|
||
}
|
||
|
||
host, _, err := net.SplitHostPort(r.Host)
|
||
if err != nil {
|
||
if addrErr, ok := err.(*net.AddrError); ok && !strings.Contains(addrErr.Err, "missing port") {
|
||
logrus.Errorf("Error splitting host/port: %T %#v", err, err)
|
||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logrus.Debugf("Using %q as hostname (no port)", r.Host)
|
||
host = r.Host
|
||
}
|
||
|
||
logrus.Infof("Proxying request: Host=%s, Path=%s, ResolvedHost=%s", r.Host, r.URL.Path, host)
|
||
|
||
// Try exact match first
|
||
if v, ok := rph.Map[r.Host]; ok {
|
||
logrus.Infof("Found exact match for host %s, forwarding to backend", r.Host)
|
||
v.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
|
||
// Try with resolved host (without port)
|
||
if v, ok := rph.Map[host]; ok {
|
||
logrus.Infof("Found match for resolved host %s, forwarding to backend", host)
|
||
v.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
|
||
logrus.Warnf("No mapping found for host: %s (resolved: %s). Available hosts: %v",
|
||
r.Host, host, rph.getHostList())
|
||
|
||
if rph.NotFoundHandler == nil {
|
||
http.NotFoundHandler().ServeHTTP(w, r)
|
||
return
|
||
}
|
||
rph.NotFoundHandler.ServeHTTP(w, r)
|
||
}
|
||
|
||
func (rph reverseProxiesHandler) getHostList() []string {
|
||
hosts := make([]string, 0, len(rph.Map))
|
||
for host := range rph.Map {
|
||
hosts = append(hosts, host)
|
||
}
|
||
return hosts
|
||
}
|
||
|
||
func (rph reverseProxiesHandler) handleWellKnown(w http.ResponseWriter, r *http.Request) {
|
||
// Проверяем и при необходимости монтируем NFS перед обработкой
|
||
if err := nfs.EnsureMounted(); err != nil {
|
||
logrus.Errorf("Failed to ensure NFS is mounted: %v", err)
|
||
http.Error(w, "NFS mount error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
path := strings.TrimPrefix(r.URL.Path, "/.well-known/acme-wellknown/")
|
||
if path == "" || strings.Contains(path, "..") {
|
||
logrus.Warnf("Invalid .well-known path: %s", r.URL.Path)
|
||
http.Error(w, "Invalid path", http.StatusBadRequest)
|
||
return
|
||
}
|
||
fullPath := filepath.Join(rph.WellKnownDir, path)
|
||
logrus.Infof("Well-known request: method=%s, path=%s, fullPath=%s", r.Method, path, fullPath)
|
||
switch r.Method {
|
||
case "GET", "HEAD":
|
||
data, err := ioutil.ReadFile(fullPath)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
http.NotFound(w, r)
|
||
return
|
||
}
|
||
logrus.Errorf("Error reading file %s: %v", fullPath, err)
|
||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "application/octet-stream")
|
||
w.WriteHeader(http.StatusOK)
|
||
if r.Method == "GET" {
|
||
w.Write(data)
|
||
}
|
||
case "POST", "PUT":
|
||
data, err := ioutil.ReadAll(r.Body)
|
||
if err != nil {
|
||
logrus.Errorf("Error reading request body: %v", err)
|
||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
dir := filepath.Dir(fullPath)
|
||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||
logrus.Errorf("Error creating directory %s: %v", dir, err)
|
||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
|
||
logrus.Errorf("Error writing file %s: %v", fullPath, err)
|
||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
w.WriteHeader(http.StatusOK)
|
||
if r.Method == "PUT" {
|
||
w.Write([]byte("File created/updated successfully"))
|
||
} else {
|
||
w.Write([]byte("File created successfully"))
|
||
}
|
||
case "DELETE":
|
||
if err := os.Remove(fullPath); err != nil {
|
||
if os.IsNotExist(err) {
|
||
http.NotFound(w, r)
|
||
return
|
||
}
|
||
logrus.Errorf("Error deleting file %s: %v", fullPath, err)
|
||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
w.WriteHeader(http.StatusOK)
|
||
w.Write([]byte("File deleted successfully"))
|
||
default:
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
}
|
||
}
|