enable TLS client forwarding
Copys the incoming TLS client certificate to the outgoing request. The backend can then use this certificate for client authentication ie. k8s client cert authentication
This commit is contained in:
parent
7399a83c74
commit
3048509807
4 changed files with 99 additions and 38 deletions
|
@ -94,7 +94,7 @@ Separate multiple rule values by `,` (comma) in order to enable ANY semantics (i
|
||||||
|
|
||||||
Separate multiple rule values by `;` (semicolon) in order to enable ALL semantics (i.e., forward a request if all rules match).
|
Separate multiple rule values by `;` (semicolon) in order to enable ALL semantics (i.e., forward a request if all rules match).
|
||||||
|
|
||||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
You can optionally enable `passHostHeader` to forward client `Host` header to the backend. You can also optionally enable `passTLSCert` to forward TLS Client certificates to the backend.
|
||||||
|
|
||||||
Following is the list of existing matcher rules along with examples:
|
Following is the list of existing matcher rules along with examples:
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ Here is an example of frontends definition:
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
|
passTLSCert = true
|
||||||
priority = 10
|
priority = 10
|
||||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
|
@ -161,8 +162,8 @@ As seen in the previous example, you can combine multiple rules.
|
||||||
In TOML file, you can use multiple routes:
|
In TOML file, you can use multiple routes:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[frontends.frontend3]
|
[frontends.frontend3]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend3.routes.test_1]
|
[frontends.frontend3.routes.test_1]
|
||||||
rule = "Host:test3.localhost"
|
rule = "Host:test3.localhost"
|
||||||
[frontends.frontend3.routes.test_2]
|
[frontends.frontend3.routes.test_2]
|
||||||
|
@ -173,8 +174,8 @@ Here `frontend3` will forward the traffic to the `backend2` if the rules `Host:t
|
||||||
You can also use the notation using a `;` separator, same result:
|
You can also use the notation using a `;` separator, same result:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[frontends.frontend3]
|
[frontends.frontend3]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend3.routes.test_1]
|
[frontends.frontend3.routes.test_1]
|
||||||
rule = "Host:test3.localhost;Path:/test"
|
rule = "Host:test3.localhost;Path:/test"
|
||||||
```
|
```
|
||||||
|
@ -182,11 +183,11 @@ backend = "backend2"
|
||||||
Finally, you can create a rule to bind multiple domains or Path to a frontend, using the `,` separator:
|
Finally, you can create a rule to bind multiple domains or Path to a frontend, using the `,` separator:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
rule = "Host:test1.localhost,test2.localhost"
|
rule = "Host:test1.localhost,test2.localhost"
|
||||||
[frontends.frontend3]
|
[frontends.frontend3]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend3.routes.test_1]
|
[frontends.frontend3.routes.test_1]
|
||||||
rule = "Path:/test1,/test2"
|
rule = "Path:/test1,/test2"
|
||||||
```
|
```
|
||||||
|
@ -218,7 +219,7 @@ By default, routes will be sorted (in descending order) using rules length (to a
|
||||||
You can customize priority by frontend:
|
You can customize priority by frontend:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[frontends]
|
[frontends]
|
||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
priority = 10
|
priority = 10
|
||||||
|
|
|
@ -89,6 +89,7 @@ entryPoint = "https"
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
|
passTLSCert = true
|
||||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
|
|
|
@ -418,6 +418,33 @@ func (server *Server) listenSignals() {
|
||||||
server.Stop()
|
server.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createClientTLSConfig(tlsOption *TLS) (*tls.Config, error) {
|
||||||
|
if tlsOption == nil {
|
||||||
|
return nil, errors.New("no TLS provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := tlsOption.Certificates.CreateTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tlsOption.ClientCAFiles) > 0 {
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
for _, caFile := range tlsOption.ClientCAFiles {
|
||||||
|
data, err := ioutil.ReadFile(caFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !pool.AppendCertsFromPEM(data) {
|
||||||
|
return nil, errors.New("invalid certificate(s) in " + caFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.RootCAs = pool
|
||||||
|
}
|
||||||
|
config.BuildNameToCertificate()
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI
|
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI
|
||||||
func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) {
|
func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) {
|
||||||
if tlsOption == nil {
|
if tlsOption == nil {
|
||||||
|
@ -500,6 +527,7 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,6 +577,17 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration)
|
||||||
return serverEntryPoints
|
return serverEntryPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clientTLSRoundTripper is used for forwarding client authentication to
|
||||||
|
// backend server
|
||||||
|
func clientTLSRoundTripper(config *tls.Config) http.RoundTripper {
|
||||||
|
if config == nil {
|
||||||
|
return http.DefaultTransport
|
||||||
|
}
|
||||||
|
return &http.Transport{
|
||||||
|
TLSClientConfig: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
||||||
// provider configurations.
|
// provider configurations.
|
||||||
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (map[string]*serverEntryPoint, error) {
|
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (map[string]*serverEntryPoint, error) {
|
||||||
|
@ -565,12 +604,6 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
|
|
||||||
log.Debugf("Creating frontend %s", frontendName)
|
log.Debugf("Creating frontend %s", frontendName)
|
||||||
|
|
||||||
fwd, err := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
|
|
||||||
log.Errorf("Skipping frontend %s...", frontendName)
|
|
||||||
continue frontend
|
|
||||||
}
|
|
||||||
if len(frontend.EntryPoints) == 0 {
|
if len(frontend.EntryPoints) == 0 {
|
||||||
log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s", frontendName, globalConfiguration.DefaultEntryPoints)
|
log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s", frontendName, globalConfiguration.DefaultEntryPoints)
|
||||||
log.Errorf("Skipping frontend %s...", frontendName)
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
|
@ -618,6 +651,31 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
}
|
}
|
||||||
if backends[entryPointName+frontend.Backend] == nil {
|
if backends[entryPointName+frontend.Backend] == nil {
|
||||||
log.Debugf("Creating backend %s", frontend.Backend)
|
log.Debugf("Creating backend %s", frontend.Backend)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
err error
|
||||||
|
lb http.Handler
|
||||||
|
)
|
||||||
|
|
||||||
|
if frontend.PassTLSCert {
|
||||||
|
tlsConfig, err = createClientTLSConfig(entryPoint.TLS)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create TLS config for frontend %s: %v", frontendName, err)
|
||||||
|
continue frontend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// passing nil will use the roundtripper http.DefaultTransport
|
||||||
|
rt := clientTLSRoundTripper(tlsConfig)
|
||||||
|
|
||||||
|
fwd, err := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader), forward.RoundTripper(rt))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
|
||||||
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
|
continue frontend
|
||||||
|
}
|
||||||
|
|
||||||
var rr *roundrobin.RoundRobin
|
var rr *roundrobin.RoundRobin
|
||||||
var saveFrontend http.Handler
|
var saveFrontend http.Handler
|
||||||
if server.accessLoggerMiddleware != nil {
|
if server.accessLoggerMiddleware != nil {
|
||||||
|
@ -627,6 +685,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
} else {
|
} else {
|
||||||
rr, _ = roundrobin.New(fwd)
|
rr, _ = roundrobin.New(fwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Backends[frontend.Backend] == nil {
|
if configuration.Backends[frontend.Backend] == nil {
|
||||||
log.Errorf("Undefined backend '%s' for frontend %s", frontend.Backend, frontendName)
|
log.Errorf("Undefined backend '%s' for frontend %s", frontend.Backend, frontendName)
|
||||||
log.Errorf("Skipping frontend %s...", frontendName)
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
|
@ -648,7 +707,6 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
sticky = roundrobin.NewStickySession(cookiename)
|
sticky = roundrobin.NewStickySession(cookiename)
|
||||||
}
|
}
|
||||||
|
|
||||||
var lb http.Handler
|
|
||||||
switch lbMethod {
|
switch lbMethod {
|
||||||
case types.Drr:
|
case types.Drr:
|
||||||
log.Debugf("Creating load-balancer drr")
|
log.Debugf("Creating load-balancer drr")
|
||||||
|
|
|
@ -60,6 +60,7 @@ type Frontend struct {
|
||||||
Backend string `json:"backend,omitempty"`
|
Backend string `json:"backend,omitempty"`
|
||||||
Routes map[string]Route `json:"routes,omitempty"`
|
Routes map[string]Route `json:"routes,omitempty"`
|
||||||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||||
|
PassTLSCert bool `json:"passTLSCert,omitempty"`
|
||||||
Priority int `json:"priority"`
|
Priority int `json:"priority"`
|
||||||
BasicAuth []string `json:"basicAuth"`
|
BasicAuth []string `json:"basicAuth"`
|
||||||
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"`
|
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"`
|
||||||
|
|
Loading…
Reference in a new issue