feat: auto tls sni

Signed-off-by: Steven Kreitzer <skre@skre.me>
This commit is contained in:
Steven Kreitzer 2024-01-18 09:19:11 -06:00 committed by Devin
parent a665d3245a
commit b5be8a6fa4
5 changed files with 51 additions and 7 deletions

View File

@ -205,6 +205,8 @@ type OriginRequestConfig struct {
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader,omitempty"` HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader,omitempty"`
// Hostname on the origin server certificate. // Hostname on the origin server certificate.
OriginServerName *string `yaml:"originServerName" json:"originServerName,omitempty"` OriginServerName *string `yaml:"originServerName" json:"originServerName,omitempty"`
// Auto configure the Hostname on the origin server certificate.
MatchSNIToHost *bool `yaml:"matchSNItoHost" json:"matchSNItoHost,omitempty"`
// Path to the CA for the certificate of your origin. // Path to the CA for the certificate of your origin.
// This option should be used only if your certificate is not signed by Cloudflare. // This option should be used only if your certificate is not signed by Cloudflare.
CAPool *string `yaml:"caPool" json:"caPool,omitempty"` CAPool *string `yaml:"caPool" json:"caPool,omitempty"`

View File

@ -32,6 +32,7 @@ const (
ProxyKeepAliveTimeoutFlag = "proxy-keepalive-timeout" ProxyKeepAliveTimeoutFlag = "proxy-keepalive-timeout"
HTTPHostHeaderFlag = "http-host-header" HTTPHostHeaderFlag = "http-host-header"
OriginServerNameFlag = "origin-server-name" OriginServerNameFlag = "origin-server-name"
MatchSNIToHostFlag = "match-sni-to-host"
NoTLSVerifyFlag = "no-tls-verify" NoTLSVerifyFlag = "no-tls-verify"
NoChunkedEncodingFlag = "no-chunked-encoding" NoChunkedEncodingFlag = "no-chunked-encoding"
ProxyAddressFlag = "proxy-address" ProxyAddressFlag = "proxy-address"
@ -118,6 +119,7 @@ func originRequestFromSingleRule(c *cli.Context) OriginRequestConfig {
var keepAliveTimeout = defaultKeepAliveTimeout var keepAliveTimeout = defaultKeepAliveTimeout
var httpHostHeader string var httpHostHeader string
var originServerName string var originServerName string
var matchSNItoHost bool
var caPool string var caPool string
var noTLSVerify bool var noTLSVerify bool
var disableChunkedEncoding bool var disableChunkedEncoding bool
@ -150,6 +152,9 @@ func originRequestFromSingleRule(c *cli.Context) OriginRequestConfig {
if flag := OriginServerNameFlag; c.IsSet(flag) { if flag := OriginServerNameFlag; c.IsSet(flag) {
originServerName = c.String(flag) originServerName = c.String(flag)
} }
if flag := MatchSNIToHostFlag; c.IsSet(flag) {
matchSNItoHost = c.Bool(flag)
}
if flag := tlsconfig.OriginCAPoolFlag; c.IsSet(flag) { if flag := tlsconfig.OriginCAPoolFlag; c.IsSet(flag) {
caPool = c.String(flag) caPool = c.String(flag)
} }
@ -185,6 +190,7 @@ func originRequestFromSingleRule(c *cli.Context) OriginRequestConfig {
KeepAliveTimeout: keepAliveTimeout, KeepAliveTimeout: keepAliveTimeout,
HTTPHostHeader: httpHostHeader, HTTPHostHeader: httpHostHeader,
OriginServerName: originServerName, OriginServerName: originServerName,
MatchSNIToHost: matchSNItoHost,
CAPool: caPool, CAPool: caPool,
NoTLSVerify: noTLSVerify, NoTLSVerify: noTLSVerify,
DisableChunkedEncoding: disableChunkedEncoding, DisableChunkedEncoding: disableChunkedEncoding,
@ -229,6 +235,9 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
if c.OriginServerName != nil { if c.OriginServerName != nil {
out.OriginServerName = *c.OriginServerName out.OriginServerName = *c.OriginServerName
} }
if c.MatchSNIToHost != nil {
out.MatchSNIToHost = *c.MatchSNIToHost
}
if c.CAPool != nil { if c.CAPool != nil {
out.CAPool = *c.CAPool out.CAPool = *c.CAPool
} }
@ -287,6 +296,8 @@ type OriginRequestConfig struct {
HTTPHostHeader string `yaml:"httpHostHeader" json:"httpHostHeader"` HTTPHostHeader string `yaml:"httpHostHeader" json:"httpHostHeader"`
// Hostname on the origin server certificate. // Hostname on the origin server certificate.
OriginServerName string `yaml:"originServerName" json:"originServerName"` OriginServerName string `yaml:"originServerName" json:"originServerName"`
// Auto configure the Hostname on the origin server certificate.
MatchSNIToHost bool `yaml:"matchSNItoHost" json:"matchSNItoHost"`
// Path to the CA for the certificate of your origin. // Path to the CA for the certificate of your origin.
// This option should be used only if your certificate is not signed by Cloudflare. // This option should be used only if your certificate is not signed by Cloudflare.
CAPool string `yaml:"caPool" json:"caPool"` CAPool string `yaml:"caPool" json:"caPool"`
@ -362,6 +373,12 @@ func (defaults *OriginRequestConfig) setOriginServerName(overrides config.Origin
} }
} }
func (defaults *OriginRequestConfig) setMatchSNIToHost(overrides config.OriginRequestConfig) {
if val := overrides.MatchSNIToHost; val != nil {
defaults.MatchSNIToHost = *val
}
}
func (defaults *OriginRequestConfig) setCAPool(overrides config.OriginRequestConfig) { func (defaults *OriginRequestConfig) setCAPool(overrides config.OriginRequestConfig) {
if val := overrides.CAPool; val != nil { if val := overrides.CAPool; val != nil {
defaults.CAPool = *val defaults.CAPool = *val
@ -447,6 +464,7 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi
cfg.setTCPKeepAlive(overrides) cfg.setTCPKeepAlive(overrides)
cfg.setHTTPHostHeader(overrides) cfg.setHTTPHostHeader(overrides)
cfg.setOriginServerName(overrides) cfg.setOriginServerName(overrides)
cfg.setMatchSNIToHost(overrides)
cfg.setCAPool(overrides) cfg.setCAPool(overrides)
cfg.setNoTLSVerify(overrides) cfg.setNoTLSVerify(overrides)
cfg.setDisableChunkedEncoding(overrides) cfg.setDisableChunkedEncoding(overrides)
@ -501,6 +519,7 @@ func ConvertToRawOriginConfig(c OriginRequestConfig) config.OriginRequestConfig
KeepAliveTimeout: keepAliveTimeout, KeepAliveTimeout: keepAliveTimeout,
HTTPHostHeader: emptyStringToNil(c.HTTPHostHeader), HTTPHostHeader: emptyStringToNil(c.HTTPHostHeader),
OriginServerName: emptyStringToNil(c.OriginServerName), OriginServerName: emptyStringToNil(c.OriginServerName),
MatchSNIToHost: defaultBoolToNil(c.MatchSNIToHost),
CAPool: emptyStringToNil(c.CAPool), CAPool: emptyStringToNil(c.CAPool),
NoTLSVerify: defaultBoolToNil(c.NoTLSVerify), NoTLSVerify: defaultBoolToNil(c.NoTLSVerify),
DisableChunkedEncoding: defaultBoolToNil(c.DisableChunkedEncoding), DisableChunkedEncoding: defaultBoolToNil(c.DisableChunkedEncoding),

View File

@ -2,7 +2,9 @@ package ingress
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"net"
"net/http" "net/http"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -48,9 +50,28 @@ func (o *httpService) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("X-Forwarded-Host", req.Host) req.Header.Set("X-Forwarded-Host", req.Host)
req.Host = o.hostHeader req.Host = o.hostHeader
} }
if o.matchSNIToHost {
o.SetOriginServerName(req)
}
return o.transport.RoundTrip(req) return o.transport.RoundTrip(req)
} }
func (o *httpService) SetOriginServerName(req *http.Request) {
o.transport.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := o.transport.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
return tls.Client(conn, &tls.Config{
RootCAs: o.transport.TLSClientConfig.RootCAs,
InsecureSkipVerify: o.transport.TLSClientConfig.InsecureSkipVerify,
ServerName: req.Host,
}), nil
}
}
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) { func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
if o.defaultResp { if o.defaultResp {
o.log.Warn().Msgf(ErrNoIngressRulesCLI.Error()) o.log.Warn().Msgf(ErrNoIngressRulesCLI.Error())

View File

@ -68,9 +68,10 @@ func (o unixSocketPath) MarshalJSON() ([]byte, error) {
} }
type httpService struct { type httpService struct {
url *url.URL url *url.URL
hostHeader string hostHeader string
transport *http.Transport transport *http.Transport
matchSNIToHost bool
} }
func (o *httpService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error { func (o *httpService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
@ -80,6 +81,7 @@ func (o *httpService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRe
} }
o.hostHeader = cfg.HTTPHostHeader o.hostHeader = cfg.HTTPHostHeader
o.transport = transport o.transport = transport
o.matchSNIToHost = cfg.MatchSNIToHost
return nil return nil
} }

View File

@ -204,25 +204,25 @@ func TestMarshalJSON(t *testing.T) {
{ {
name: "Nil", name: "Nil",
path: nil, path: nil,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","matchSNItoHost":false,"caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
{ {
name: "Nil regex", name: "Nil regex",
path: &Regexp{Regexp: nil}, path: &Regexp{Regexp: nil},
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","matchSNItoHost":false,"caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
{ {
name: "Empty", name: "Empty",
path: &Regexp{Regexp: regexp.MustCompile("")}, path: &Regexp{Regexp: regexp.MustCompile("")},
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","matchSNItoHost":false,"caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
{ {
name: "Basic", name: "Basic",
path: &Regexp{Regexp: regexp.MustCompile("/echo")}, path: &Regexp{Regexp: regexp.MustCompile("/echo")},
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","matchSNItoHost":false,"caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
} }