forked from Mxmilu666/frp
feat(proxy): add AutoTLS support for HTTPS plugins
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -42,3 +42,6 @@ client.key
|
|||||||
|
|
||||||
# AI
|
# AI
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
|
|
||||||
|
# TLS
|
||||||
|
.autotls-cache
|
||||||
@@ -19,7 +19,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -69,6 +71,7 @@ func NewProxy(
|
|||||||
|
|
||||||
baseProxy := BaseProxy{
|
baseProxy := BaseProxy{
|
||||||
baseCfg: pxyConf.GetBaseConfig(),
|
baseCfg: pxyConf.GetBaseConfig(),
|
||||||
|
configurer: pxyConf,
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
@@ -87,6 +90,7 @@ func NewProxy(
|
|||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
baseCfg *v1.ProxyBaseConfig
|
baseCfg *v1.ProxyBaseConfig
|
||||||
|
configurer v1.ProxyConfigurer
|
||||||
clientCfg *v1.ClientCommonConfig
|
clientCfg *v1.ClientCommonConfig
|
||||||
encryptionKey []byte
|
encryptionKey []byte
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
@@ -106,6 +110,7 @@ func (pxy *BaseProxy) Run() error {
|
|||||||
if pxy.baseCfg.Plugin.Type != "" {
|
if pxy.baseCfg.Plugin.Type != "" {
|
||||||
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{
|
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{
|
||||||
Name: pxy.baseCfg.Name,
|
Name: pxy.baseCfg.Name,
|
||||||
|
HostAllowList: pxy.getPluginHostAllowList(),
|
||||||
VnetController: pxy.vnetController,
|
VnetController: pxy.vnetController,
|
||||||
}, pxy.baseCfg.Plugin.ClientPluginOptions)
|
}, pxy.baseCfg.Plugin.ClientPluginOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -116,6 +121,39 @@ func (pxy *BaseProxy) Run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) getPluginHostAllowList() []string {
|
||||||
|
dedupHosts := make([]string, 0)
|
||||||
|
addHost := func(host string) {
|
||||||
|
host = strings.TrimSpace(strings.ToLower(host))
|
||||||
|
if host == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// autocert.HostWhitelist only supports exact host names.
|
||||||
|
if strings.Contains(host, "*") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !slices.Contains(dedupHosts, host) {
|
||||||
|
dedupHosts = append(dedupHosts, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cfg := pxy.configurer.(type) {
|
||||||
|
case *v1.HTTPProxyConfig:
|
||||||
|
for _, host := range cfg.CustomDomains {
|
||||||
|
addHost(host)
|
||||||
|
}
|
||||||
|
case *v1.HTTPSProxyConfig:
|
||||||
|
for _, host := range cfg.CustomDomains {
|
||||||
|
addHost(host)
|
||||||
|
}
|
||||||
|
case *v1.TCPMuxProxyConfig:
|
||||||
|
for _, host := range cfg.CustomDomains {
|
||||||
|
addHost(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dedupHosts
|
||||||
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) Close() {
|
func (pxy *BaseProxy) Close() {
|
||||||
if pxy.proxyPlugin != nil {
|
if pxy.proxyPlugin != nil {
|
||||||
pxy.proxyPlugin.Close()
|
pxy.proxyPlugin.Close()
|
||||||
|
|||||||
@@ -329,6 +329,14 @@ type = "https2http"
|
|||||||
localAddr = "127.0.0.1:80"
|
localAddr = "127.0.0.1:80"
|
||||||
crtPath = "./server.crt"
|
crtPath = "./server.crt"
|
||||||
keyPath = "./server.key"
|
keyPath = "./server.key"
|
||||||
|
# autoTLS can replace crtPath/keyPath and automatically apply/renew certificates.
|
||||||
|
# [proxies.plugin.autoTLS]
|
||||||
|
# enable = true
|
||||||
|
# email = "admin@example.com"
|
||||||
|
# cacheDir = "./.autotls-cache"
|
||||||
|
# hostAllowList is optional. If omitted, frpc will use customDomains automatically.
|
||||||
|
# hostAllowList = ["test.yourdomain.com"]
|
||||||
|
# caDirURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
hostHeaderRewrite = "127.0.0.1"
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
@@ -341,6 +349,14 @@ type = "https2https"
|
|||||||
localAddr = "127.0.0.1:443"
|
localAddr = "127.0.0.1:443"
|
||||||
crtPath = "./server.crt"
|
crtPath = "./server.crt"
|
||||||
keyPath = "./server.key"
|
keyPath = "./server.key"
|
||||||
|
# autoTLS can replace crtPath/keyPath and automatically apply/renew certificates.
|
||||||
|
# [proxies.plugin.autoTLS]
|
||||||
|
# enable = true
|
||||||
|
# email = "admin@example.com"
|
||||||
|
# cacheDir = "./.autotls-cache"
|
||||||
|
# hostAllowList is optional. If omitted, frpc will use customDomains automatically.
|
||||||
|
# hostAllowList = ["test.yourdomain.com"]
|
||||||
|
# caDirURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
hostHeaderRewrite = "127.0.0.1"
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
@@ -373,6 +389,14 @@ type = "tls2raw"
|
|||||||
localAddr = "127.0.0.1:80"
|
localAddr = "127.0.0.1:80"
|
||||||
crtPath = "./server.crt"
|
crtPath = "./server.crt"
|
||||||
keyPath = "./server.key"
|
keyPath = "./server.key"
|
||||||
|
# autoTLS can replace crtPath/keyPath and automatically apply/renew certificates.
|
||||||
|
# [proxies.plugin.autoTLS]
|
||||||
|
# enable = true
|
||||||
|
# email = "admin@example.com"
|
||||||
|
# cacheDir = "./.autotls-cache"
|
||||||
|
# hostAllowList is optional. If omitted, frpc will use customDomains automatically.
|
||||||
|
# hostAllowList = ["test.yourdomain.com"]
|
||||||
|
# caDirURL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
[[proxies]]
|
[[proxies]]
|
||||||
name = "secret_tcp"
|
name = "secret_tcp"
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ type HTTPProxyPluginOptions struct {
|
|||||||
|
|
||||||
func (o *HTTPProxyPluginOptions) Complete() {}
|
func (o *HTTPProxyPluginOptions) Complete() {}
|
||||||
|
|
||||||
|
type AutoTLSOptions struct {
|
||||||
|
Enable bool `json:"enable,omitempty"`
|
||||||
|
// Contact email for certificate expiration and important notices.
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
// Directory used to cache ACME account and certificates.
|
||||||
|
CacheDir string `json:"cacheDir,omitempty"`
|
||||||
|
// ACME directory URL, e.g. Let's Encrypt staging/prod endpoint.
|
||||||
|
CADirURL string `json:"caDirURL,omitempty"`
|
||||||
|
// Restrict certificate issuance to the listed domains.
|
||||||
|
HostAllowList []string `json:"hostAllowList,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type HTTPS2HTTPPluginOptions struct {
|
type HTTPS2HTTPPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalAddr string `json:"localAddr,omitempty"`
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
@@ -125,6 +137,7 @@ type HTTPS2HTTPPluginOptions struct {
|
|||||||
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||||
CrtPath string `json:"crtPath,omitempty"`
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
KeyPath string `json:"keyPath,omitempty"`
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
AutoTLS *AutoTLSOptions `json:"autoTLS,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *HTTPS2HTTPPluginOptions) Complete() {
|
func (o *HTTPS2HTTPPluginOptions) Complete() {
|
||||||
@@ -139,6 +152,7 @@ type HTTPS2HTTPSPluginOptions struct {
|
|||||||
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||||
CrtPath string `json:"crtPath,omitempty"`
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
KeyPath string `json:"keyPath,omitempty"`
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
AutoTLS *AutoTLSOptions `json:"autoTLS,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *HTTPS2HTTPSPluginOptions) Complete() {
|
func (o *HTTPS2HTTPSPluginOptions) Complete() {
|
||||||
@@ -180,10 +194,11 @@ type UnixDomainSocketPluginOptions struct {
|
|||||||
func (o *UnixDomainSocketPluginOptions) Complete() {}
|
func (o *UnixDomainSocketPluginOptions) Complete() {}
|
||||||
|
|
||||||
type TLS2RawPluginOptions struct {
|
type TLS2RawPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalAddr string `json:"localAddr,omitempty"`
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
CrtPath string `json:"crtPath,omitempty"`
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
KeyPath string `json:"keyPath,omitempty"`
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
AutoTLS *AutoTLSOptions `json:"autoTLS,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *TLS2RawPluginOptions) Complete() {}
|
func (o *TLS2RawPluginOptions) Complete() {}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ package validation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
)
|
)
|
||||||
@@ -49,6 +51,9 @@ func validateHTTPS2HTTPPluginOptions(c *v1.HTTPS2HTTPPluginOptions) error {
|
|||||||
if c.LocalAddr == "" {
|
if c.LocalAddr == "" {
|
||||||
return errors.New("localAddr is required")
|
return errors.New("localAddr is required")
|
||||||
}
|
}
|
||||||
|
if err := validateAutoTLSOptions(c.AutoTLS, c.CrtPath, c.KeyPath); err != nil {
|
||||||
|
return fmt.Errorf("invalid autoTLS options: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +61,9 @@ func validateHTTPS2HTTPSPluginOptions(c *v1.HTTPS2HTTPSPluginOptions) error {
|
|||||||
if c.LocalAddr == "" {
|
if c.LocalAddr == "" {
|
||||||
return errors.New("localAddr is required")
|
return errors.New("localAddr is required")
|
||||||
}
|
}
|
||||||
|
if err := validateAutoTLSOptions(c.AutoTLS, c.CrtPath, c.KeyPath); err != nil {
|
||||||
|
return fmt.Errorf("invalid autoTLS options: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,5 +85,29 @@ func validateTLS2RawPluginOptions(c *v1.TLS2RawPluginOptions) error {
|
|||||||
if c.LocalAddr == "" {
|
if c.LocalAddr == "" {
|
||||||
return errors.New("localAddr is required")
|
return errors.New("localAddr is required")
|
||||||
}
|
}
|
||||||
|
if err := validateAutoTLSOptions(c.AutoTLS, c.CrtPath, c.KeyPath); err != nil {
|
||||||
|
return fmt.Errorf("invalid autoTLS options: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAutoTLSOptions(c *v1.AutoTLSOptions, crtPath, keyPath string) error {
|
||||||
|
if c == nil || !c.Enable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if crtPath != "" || keyPath != "" {
|
||||||
|
return errors.New("crtPath and keyPath must be empty when autoTLS.enable is true")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.CacheDir) == "" {
|
||||||
|
return errors.New("autoTLS.cacheDir is required when autoTLS.enable is true")
|
||||||
|
}
|
||||||
|
if len(c.HostAllowList) > 0 {
|
||||||
|
for _, host := range c.HostAllowList {
|
||||||
|
if strings.TrimSpace(host) == "" {
|
||||||
|
return errors.New("autoTLS.hostAllowList cannot contain empty domain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
211
pkg/plugin/client/autotls.go
Normal file
211
pkg/plugin/client/autotls.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
// Copyright 2026 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildAutoTLSServerConfigWithHosts(pluginName string, auto *v1.AutoTLSOptions, fallbackHosts []string) (*tls.Config, error) {
|
||||||
|
if auto == nil || !auto.Enable {
|
||||||
|
return nil, fmt.Errorf("插件 %s 未启用 autoTLS", pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(auto.CacheDir, 0o700); err != nil {
|
||||||
|
return nil, fmt.Errorf("插件 %s 创建 autoTLS 缓存目录失败: %w", pluginName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostSet := make(map[string]struct{})
|
||||||
|
hosts := make([]string, 0, len(auto.HostAllowList))
|
||||||
|
addHost := func(host string) {
|
||||||
|
host = strings.TrimSpace(strings.ToLower(host))
|
||||||
|
if host == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.Contains(host, "*") {
|
||||||
|
log.Warnf("[autoTLS][%s] 域名 [%s] 含通配符,自动申请不支持,已忽略", pluginName, host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := hostSet[host]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hostSet[host] = struct{}{}
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range auto.HostAllowList {
|
||||||
|
addHost(host)
|
||||||
|
}
|
||||||
|
if len(hosts) == 0 {
|
||||||
|
for _, host := range fallbackHosts {
|
||||||
|
addHost(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(hosts) == 0 {
|
||||||
|
return nil, fmt.Errorf("插件 %s 的 hostAllowList 为空;请设置 autoTLS.hostAllowList 或 customDomains", pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := &autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
Email: strings.TrimSpace(auto.Email),
|
||||||
|
HostPolicy: autocert.HostWhitelist(hosts...),
|
||||||
|
}
|
||||||
|
caDirURL := strings.TrimSpace(auto.CADirURL)
|
||||||
|
if caDirURL != "" {
|
||||||
|
manager.Client = &acme.Client{DirectoryURL: caDirURL}
|
||||||
|
} else {
|
||||||
|
caDirURL = autocert.DefaultACMEDirectory
|
||||||
|
}
|
||||||
|
managedHosts := make(map[string]struct{}, len(hosts))
|
||||||
|
for _, host := range hosts {
|
||||||
|
managedHosts[host] = struct{}{}
|
||||||
|
}
|
||||||
|
var warmupInProgress sync.Map
|
||||||
|
var warmupMissLogged sync.Map
|
||||||
|
manager.Cache = &autoTLSCache{
|
||||||
|
inner: autocert.DirCache(auto.CacheDir),
|
||||||
|
managedHosts: managedHosts,
|
||||||
|
pluginName: pluginName,
|
||||||
|
caDirURL: caDirURL,
|
||||||
|
warmupInProgress: &warmupInProgress,
|
||||||
|
warmupMissLogged: &warmupMissLogged,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := manager.TLSConfig()
|
||||||
|
log.Infof("[autoTLS][%s] 已启用 autoTLS,管理域名=%v,缓存目录=%s", pluginName, hosts, auto.CacheDir)
|
||||||
|
|
||||||
|
var readySeen sync.Map
|
||||||
|
|
||||||
|
handleCertReady := func(host string, cert *tls.Certificate) {
|
||||||
|
var (
|
||||||
|
notAfter time.Time
|
||||||
|
hasExpiry bool
|
||||||
|
)
|
||||||
|
if t, ok := getCertificateNotAfter(cert); ok {
|
||||||
|
notAfter = t
|
||||||
|
hasExpiry = true
|
||||||
|
}
|
||||||
|
|
||||||
|
_, readyLogged := readySeen.LoadOrStore(host, struct{}{})
|
||||||
|
if hasExpiry {
|
||||||
|
if !readyLogged {
|
||||||
|
log.Infof("[autoTLS][%s] 域名 [%s] 证书已就绪,过期时间 %s", pluginName, host, notAfter.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
} else if !readyLogged {
|
||||||
|
log.Infof("[autoTLS][%s] 域名 [%s] 证书已就绪", pluginName, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.GetCertificate = func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
host := strings.TrimSpace(strings.ToLower(hello.ServerName))
|
||||||
|
if host == "" {
|
||||||
|
host = "<空SNI>"
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := manager.GetCertificate(hello)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[autoTLS][%s] 获取域名 [%s] 证书失败: %v", pluginName, host, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handleCertReady(host, cert)
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warm up certificates in background after startup.
|
||||||
|
for _, host := range hosts {
|
||||||
|
h := host
|
||||||
|
go func() {
|
||||||
|
// Leave time for listener setup and route registration.
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
warmupMissLogged.Delete(h)
|
||||||
|
warmupInProgress.Store(h, struct{}{})
|
||||||
|
cert, err := manager.GetCertificate(&tls.ClientHelloInfo{ServerName: h})
|
||||||
|
warmupInProgress.Delete(h)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[autoTLS][%s] 域名 [%s] 预申请失败: %v", pluginName, h, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleCertReady(h, cert)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCertificateNotAfter(cert *tls.Certificate) (time.Time, bool) {
|
||||||
|
if cert == nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
if cert.Leaf != nil {
|
||||||
|
return cert.Leaf.NotAfter, true
|
||||||
|
}
|
||||||
|
if len(cert.Certificate) == 0 {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
return leaf.NotAfter, true
|
||||||
|
}
|
||||||
|
|
||||||
|
type autoTLSCache struct {
|
||||||
|
inner autocert.Cache
|
||||||
|
managedHosts map[string]struct{}
|
||||||
|
pluginName string
|
||||||
|
caDirURL string
|
||||||
|
warmupInProgress *sync.Map
|
||||||
|
warmupMissLogged *sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *autoTLSCache) Get(ctx context.Context, key string) ([]byte, error) {
|
||||||
|
data, err := c.inner.Get(ctx, key)
|
||||||
|
if err != autocert.ErrCacheMiss {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
host := strings.TrimSuffix(key, "+rsa")
|
||||||
|
if _, ok := c.managedHosts[host]; !ok {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
if _, warming := c.warmupInProgress.Load(host); !warming {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
if _, loaded := c.warmupMissLogged.LoadOrStore(host, struct{}{}); !loaded {
|
||||||
|
log.Infof("[autoTLS][%s] 开始预申请域名 [%s] 证书,申请方式=TLS-ALPN-01,caDirURL=%s", c.pluginName, host, c.caDirURL)
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *autoTLSCache) Put(ctx context.Context, key string, data []byte) error {
|
||||||
|
return c.inner.Put(ctx, key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *autoTLSCache) Delete(ctx context.Context, key string) error {
|
||||||
|
return c.inner.Delete(ctx, key)
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ type HTTPS2HTTPPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPS2HTTPPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPS2HTTPPluginOptions)
|
opts := options.(*v1.HTTPS2HTTPPluginOptions)
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
|
|
||||||
@@ -84,9 +84,18 @@ func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugi
|
|||||||
rp.ServeHTTP(w, r)
|
rp.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
var tlsConfig *tls.Config
|
||||||
if err != nil {
|
var err error
|
||||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
if p.opts.AutoTLS != nil && p.opts.AutoTLS.Enable {
|
||||||
|
tlsConfig, err = buildAutoTLSServerConfigWithHosts(pluginCtx.Name, p.opts.AutoTLS, pluginCtx.HostAllowList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("build autoTLS config error: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tlsConfig, err = transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.s = &http.Server{
|
p.s = &http.Server{
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ type HTTPS2HTTPSPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPS2HTTPSPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
|
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@@ -90,9 +90,18 @@ func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plug
|
|||||||
rp.ServeHTTP(w, r)
|
rp.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
var tlsConfig *tls.Config
|
||||||
if err != nil {
|
var err error
|
||||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
if p.opts.AutoTLS != nil && p.opts.AutoTLS.Enable {
|
||||||
|
tlsConfig, err = buildAutoTLSServerConfigWithHosts(pluginCtx.Name, p.opts.AutoTLS, pluginCtx.HostAllowList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("build autoTLS config error: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tlsConfig, err = transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.s = &http.Server{
|
p.s = &http.Server{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
|
|
||||||
type PluginContext struct {
|
type PluginContext struct {
|
||||||
Name string
|
Name string
|
||||||
|
HostAllowList []string
|
||||||
VnetController *vnet.Controller
|
VnetController *vnet.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,16 +39,25 @@ type TLS2RawPlugin struct {
|
|||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTLS2RawPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
func NewTLS2RawPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.TLS2RawPluginOptions)
|
opts := options.(*v1.TLS2RawPluginOptions)
|
||||||
|
|
||||||
p := &TLS2RawPlugin{
|
p := &TLS2RawPlugin{
|
||||||
opts: opts,
|
opts: opts,
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
var tlsConfig *tls.Config
|
||||||
if err != nil {
|
var err error
|
||||||
return nil, err
|
if p.opts.AutoTLS != nil && p.opts.AutoTLS.Enable {
|
||||||
|
tlsConfig, err = buildAutoTLSServerConfigWithHosts(pluginCtx.Name, p.opts.AutoTLS, pluginCtx.HostAllowList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tlsConfig, err = transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p.tlsConfig = tlsConfig
|
p.tlsConfig = tlsConfig
|
||||||
return p, nil
|
return p, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user