Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auth-tls configurations to tcp services #883

Merged
merged 2 commits into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/content/en/docs/configuration/keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -2539,7 +2539,9 @@ Due to the limited data that can be inspected on TCP requests, a limited number

* `Backend` and `Path` scoped configuration keys work, provided that they are not HTTP related - eg [Cors](#cors) and [HSTS](#hsts) are ignored by TCP services, on the other hand [balance algorithm](#balance-algorithm), [Allow list](#allowlist) and [Blue/green](#blue-green) work just like in the HTTP requests counterpart.
* All `Global` configuration keys related with the whole haproxy process will also be applied to TCP services, like max connections or syslog configurations.
* All `Host` scoped configuration keys are currently unsupported
* Regarding `Host` scoped configuration keys:
* on v0.13, all `Host` scoped configuration keys are unsupported
* on v0.14, [auth-tls](#auth-tls) are supported

Every TCP service port creates a dedicated haproxy frontend that can be [customized](#configuration-snippet) in three distinct ways:

Expand Down
2 changes: 2 additions & 0 deletions pkg/converters/helper_test/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type (
}
tlsMock struct {
TLSFilename string `yaml:",omitempty"`
CAFilename string `yaml:",omitempty"`
}
// tcp
tcpServiceMock struct {
Expand Down Expand Up @@ -165,6 +166,7 @@ func MarshalTCPServices(hatcpserviceports ...*hatypes.TCPServicePort) string {
ProxyProt: hasvc.ProxyProt,
TLS: tlsMock{
TLSFilename: hasvc.TLS.TLSFilename,
CAFilename: hasvc.TLS.CAFilename,
},
}
tcpServices = append(tcpServices, svc)
Expand Down
29 changes: 20 additions & 9 deletions pkg/converters/ingress/annotations/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ package annotations
import (
ingtypes "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/ingress/types"
convtypes "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/types"
"github.com/jcmoraisjr/haproxy-ingress/pkg/haproxy/types"
)

func (c *updater) buildHostAuthTLS(d *hostData) {
tlsSecret := d.mapper.Get(ingtypes.HostAuthTLSSecret)
func (c *updater) setAuthTLSConfig(mapper *Mapper, target *types.TLSConfig, hostname string) bool {
tlsSecret := mapper.Get(ingtypes.HostAuthTLSSecret)
if tlsSecret.Source == nil || tlsSecret.Value == "" {
return
return false
}
verify := d.mapper.Get(ingtypes.HostAuthTLSVerifyClient)
verify := mapper.Get(ingtypes.HostAuthTLSVerifyClient)
if verify.Value == "off" {
return
return false
}
tls := &d.host.TLS
tls := target
if cafile, crlfile, err := c.cache.GetCASecretPath(
tlsSecret.Source.Namespace,
tlsSecret.Value,
[]convtypes.TrackingRef{{Context: convtypes.ResourceHAHostname, UniqueName: d.host.Hostname}},
[]convtypes.TrackingRef{{Context: convtypes.ResourceHAHostname, UniqueName: hostname}},
); err == nil {
tls.CAFilename = cafile.Filename
tls.CAHash = cafile.SHA1Hash
Expand All @@ -43,15 +44,25 @@ func (c *updater) buildHostAuthTLS(d *hostData) {
} else {
c.logger.Error("error building TLS auth config on %s: %v", tlsSecret.Source, err)
}
if tls.CAFilename == "" && d.mapper.Get(ingtypes.HostAuthTLSStrict).Bool() {
if tls.CAFilename == "" && mapper.Get(ingtypes.HostAuthTLSStrict).Bool() {
// Here we have a misconfigured auth-tls and auth-tls-strict as `true`.
// Using a fake and self-generated CA so any connection attempt will fail with
// HTTP 495 (invalid crt) or 496 (crt wasn't provided) instead of allow the request.
tls.CAFilename = c.fakeCA.Filename
tls.CAHash = c.fakeCA.SHA1Hash
}
tls.CAVerifyOptional = verify.Value == "optional" || verify.Value == "optional_no_ca"
tls.CAErrorPage = d.mapper.Get(ingtypes.HostAuthTLSErrorPage).Value
return true
}

func (c *updater) buildTCPAuthTLS(d *tcpData) {
_ = c.setAuthTLSConfig(d.mapper, &d.tcpPort.TLS, d.tcpHost.Hostname())
}

func (c *updater) buildHostAuthTLS(d *hostData) {
if c.setAuthTLSConfig(d.mapper, &d.host.TLS.TLSConfig, d.host.Hostname) {
d.host.TLS.CAErrorPage = d.mapper.Get(ingtypes.HostAuthTLSErrorPage).Value
}
}

func (c *updater) buildHostCertSigner(d *hostData) {
Expand Down
16 changes: 14 additions & 2 deletions pkg/converters/ingress/annotations/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
type Updater interface {
UpdateGlobalConfig(haproxyConfig haproxy.Config, mapper *Mapper)
UpdateTCPPortConfig(tcp *hatypes.TCPServicePort, mapper *Mapper)
UpdateTCPHostConfig(host *hatypes.TCPServiceHost, mapper *Mapper)
UpdateTCPHostConfig(tcpPort *hatypes.TCPServicePort, tcpHost *hatypes.TCPServiceHost, mapper *Mapper)
UpdateHostConfig(host *hatypes.Host, mapper *Mapper)
UpdateBackendConfig(backend *hatypes.Backend, mapper *Mapper)
}
Expand Down Expand Up @@ -66,6 +66,12 @@ type globalData struct {
mapper *Mapper
}

type tcpData struct {
tcpPort *hatypes.TCPServicePort
tcpHost *hatypes.TCPServiceHost
mapper *Mapper
}

type hostData struct {
host *hatypes.Host
mapper *Mapper
Expand Down Expand Up @@ -186,7 +192,13 @@ func (c *updater) UpdateTCPPortConfig(tcp *hatypes.TCPServicePort, mapper *Mappe
tcp.ProxyProt = mapper.Get(ingtypes.TCPTCPServiceProxyProto).Bool()
}

func (c *updater) UpdateTCPHostConfig(host *hatypes.TCPServiceHost, mapper *Mapper) {
func (c *updater) UpdateTCPHostConfig(tcpPort *hatypes.TCPServicePort, tcpHost *hatypes.TCPServiceHost, mapper *Mapper) {
data := &tcpData{
tcpPort: tcpPort,
tcpHost: tcpHost,
mapper: mapper,
}
c.buildTCPAuthTLS(data)
}

func (c *updater) UpdateHostConfig(host *hatypes.Host, mapper *Mapper) {
Expand Down
29 changes: 13 additions & 16 deletions pkg/converters/ingress/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ func (c *converter) syncIngressHTTP(source *annotations.Source, ing *networking.
// tls secret
for _, hostname := range tls.Hosts {
host := c.addHost(hostname, source, annHost)
tlsPath := c.addTLS(source, hostname, tls.SecretName)
tlsPath := c.addTLS(source, tls.SecretName)
if host.TLS.TLSHash == "" {
host.TLS.TLSFilename = tlsPath.Filename
host.TLS.TLSHash = tlsPath.SHA1Hash
Expand Down Expand Up @@ -541,13 +541,14 @@ func (c *converter) syncIngressTCP(source *annotations.Source, ing *networking.I
}
}
}
addTCPTLSSecret := func(secretName, rawHostname string) {
for _, tls := range ing.Spec.TLS {
secretName := tls.SecretName
tcpPort := c.haproxy.TCPServices().FindTCPPort(tcpServicePort)
if tcpPort == nil {
c.logger.Warn("skipping TLS of tcp service on %v: backend was not configured", source)
return
}
tlsPath := c.addTLS(source, normalizeHostname(rawHostname, tcpServicePort), secretName)
tlsPath := c.addTLS(source, secretName)
if tcpPort.TLS.TLSHash == "" {
tcpPort.TLS.TLSFilename = tlsPath.Filename
tcpPort.TLS.TLSHash = tlsPath.SHA1Hash
Expand All @@ -562,14 +563,6 @@ func (c *converter) syncIngressTCP(source *annotations.Source, ing *networking.I
}
}
}
for _, tls := range ing.Spec.TLS {
if len(tls.Hosts) == 0 {
addTCPTLSSecret(tls.SecretName, "")
}
for _, hostname := range tls.Hosts {
addTCPTLSSecret(tls.SecretName, hostname)
}
}
}

func (c *converter) syncEndpoints() {
Expand All @@ -590,12 +583,11 @@ func (c *converter) fullSyncTCP() {
for _, tcpPort := range c.haproxy.TCPServices().Items() {
if ann, found := c.tcpsvcAnnotations[tcpPort]; found {
c.updater.UpdateTCPPortConfig(tcpPort, ann)
tcpHost := tcpPort.DefaultHost()
if tcpHost != nil {
c.updater.UpdateTCPHostConfig(tcpHost, ann)
if tcpHost := tcpPort.DefaultHost(); tcpHost != nil {
c.updater.UpdateTCPHostConfig(tcpPort, tcpHost, ann)
}
for _, tcpHost := range tcpPort.Hosts() {
c.updater.UpdateTCPHostConfig(tcpHost, ann)
c.updater.UpdateTCPHostConfig(tcpPort, tcpHost, ann)
}
}
}
Expand Down Expand Up @@ -907,7 +899,7 @@ func (c *converter) syncBackendEndpointHashes(backend *hatypes.Backend) {
}
}

func (c *converter) addTLS(source *annotations.Source, hostname, secretName string) convtypes.CrtFile {
func (c *converter) addTLS(source *annotations.Source, secretName string) convtypes.CrtFile {
if secretName != "" {
tlsFile, err := c.cache.GetTLSSecretPath(
source.Namespace,
Expand Down Expand Up @@ -964,6 +956,11 @@ func (c *converter) readAnnotations(source *annotations.Source, ann map[string]s
annTCP[key] = value
} else if _, isHostAnn := ingtypes.AnnHost[key]; isHostAnn {
annHost[key] = value
// TCP services read both TCP and Host scoped configuration keys
// in a single step. Our approach is to add all Host keys to the
// TCP mapper. We're concatenating them here instead of concatenate
// later when creating the TCP mapper.
annTCP[key] = value
} else {
annBack[key] = value
}
Expand Down
22 changes: 21 additions & 1 deletion pkg/converters/ingress/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package ingress

import (
"fmt"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -1818,6 +1819,20 @@ WARN skipping TLS secret 'tls2' of Ingress 'default/echo2': TLS of tcp service p
proxyprot: false
tls: {}`,
},
// 18
{
ing: [][]string{
{"7001", "/", "echo1:8080", "tls1", "ingress.kubernetes.io/" + ingtypes.HostAuthTLSSecret + "=ca"},
},
expect: `
- backends: []
defaultbackend: default_echo1_8080
port: 7001
proxyprot: false
tls:
tlsfilename: /tls/default/tls1.pem
cafilename: /tls/default/ca.pem`,
},
}
for i, test := range testCases {
c := setup(t)
Expand Down Expand Up @@ -2353,6 +2368,7 @@ func setup(t *testing.T) *testConfig {
cache: conv_helper.NewCacheMock(tracker),
logger: logger,
tracker: tracker,
updater: &updaterMock{},
}
c.createSvc1("system/default", "8080", "172.17.0.99")
return c
Expand Down Expand Up @@ -2621,7 +2637,11 @@ func (u *updaterMock) UpdateTCPPortConfig(tcp *hatypes.TCPServicePort, mapper *a
tcp.ProxyProt = mapper.Get(ingtypes.TCPTCPServiceProxyProto).Bool()
}

func (u *updaterMock) UpdateTCPHostConfig(tcp *hatypes.TCPServiceHost, mapper *annotations.Mapper) {
func (u *updaterMock) UpdateTCPHostConfig(tcpPort *hatypes.TCPServicePort, tcpHost *hatypes.TCPServiceHost, mapper *annotations.Mapper) {
caSecret := mapper.Get(ingtypes.HostAuthTLSSecret).Value
if caSecret != "" {
tcpPort.TLS.CAFilename = fmt.Sprintf("/tls/default/%s.pem", caSecret)
}
}

func (u *updaterMock) UpdateHostConfig(host *hatypes.Host, mapper *annotations.Mapper) {
Expand Down