From 7d1ecaac7c304862a1ae86a27be1f8d07e107db2 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 21 Jul 2018 17:11:08 -0300 Subject: [PATCH] Add stats-ssl-cert configmap option --- README.md | 6 ++++- pkg/common/ingress/controller/controller.go | 30 ++++++++++++--------- pkg/common/ingress/types_equals.go | 8 ++++++ pkg/controller/config.go | 15 +++++++++++ pkg/types/equal.go | 3 +++ pkg/types/types.go | 2 ++ rootfs/etc/haproxy/template/haproxy.tmpl | 6 ++++- 7 files changed, 56 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e320b408c..ebeb92ea8 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ configmap with `haproxy.tmpl` key mounting into `/etc/haproxy/template` will wor The following annotations are supported: * `[0]` only in `canary` tag +* `[1]` only in `snapshot` tag ||Name|Data|Usage| |---|---|---|:---:| @@ -253,6 +254,7 @@ A ConfigMap can be created with `kubectl create configmap`. The following parameters are supported: * `[0]` only in `canary` tag +* `[1]` only in `snapshot` tag ||Name|Type|Default| |---|---|---|---| @@ -296,6 +298,7 @@ The following parameters are supported: ||[`stats-auth`](#stats)|user:passwd|no auth| ||[`stats-port`](#stats)|port number|`1936`| ||[`stats-proxy-protocol`](#stats)|[true\|false]|`false`| +|`[1]`|[`stats-ssl-cert`](#stats)|namespace/secret name|no ssl/plain http| ||[`syslog-endpoint`](#syslog-endpoint)|IP:port (udp)|do not log| ||[`tcp-log-format`](#log-format)|tcp log format|HAProxy default log format| ||[`timeout-client-fin`](#timeout)|time with suffix|`50s`| @@ -545,11 +548,12 @@ to https if TLS is [configured](https://github.com/kubernetes/ingress/tree/maste ### stats -Configurations of the HAProxy status page: +Configurations of the HAProxy statistics page: * `stats-auth`: Enable basic authentication with clear-text password - `:` * `stats-port`: Change the port HAProxy should listen to requests * `stats-proxy-protocol`: Define if the stats endpoint should enforce the PROXY protocol +* `stats-ssl-cert`: Optional namespace/secret-name of `tls.crt` and `tls.key` pair used to enable SSL on stats page. Plain http will be used if not provided, the secret wasn't found or the secret doesn't have a crt/key pair. ### syslog-endpoint diff --git a/pkg/common/ingress/controller/controller.go b/pkg/common/ingress/controller/controller.go index 49add7cae..53d433d62 100644 --- a/pkg/common/ingress/controller/controller.go +++ b/pkg/common/ingress/controller/controller.go @@ -739,20 +739,10 @@ func (ic *GenericController) getBackendServers(ingresses []*extensions.Ingress) // GetAuthCertificate is used by the auth-tls annotations to get a cert from a secret func (ic GenericController) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) { - if _, exists := ic.sslCertTracker.Get(name); !exists { - ic.syncSecret(name) - } - - _, err := ic.listers.Secret.GetByName(name) + cert, err := ic.GetCertificate(name) if err != nil { - return &resolver.AuthSSLCert{}, fmt.Errorf("unexpected error: %v", err) + return &resolver.AuthSSLCert{}, err } - - bc, exists := ic.sslCertTracker.Get(name) - if !exists { - return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exist", name) - } - cert := bc.(*ingress.SSLCert) return &resolver.AuthSSLCert{ Secret: name, CAFileName: cert.CAFileName, @@ -760,6 +750,22 @@ func (ic GenericController) GetAuthCertificate(name string) (*resolver.AuthSSLCe }, nil } +// GetCertificate get a SSLCert object from a secret name +func (ic *GenericController) GetCertificate(name string) (*ingress.SSLCert, error) { + crt, exists := ic.sslCertTracker.Get(name) + if !exists { + ic.syncSecret(name) + crt, exists = ic.sslCertTracker.Get(name) + } + if exists { + return crt.(*ingress.SSLCert), nil + } + if _, err := ic.listers.Secret.GetByName(name); err != nil { + return nil, err + } + return nil, fmt.Errorf("secret '%v' have neither ca.crt nor tls.crt/tls.key pair", name) +} + // GetFullResourceName add the currentNamespace prefix if name doesn't provide one // and AllowCrossNamespace is allowing this func (ic GenericController) GetFullResourceName(name, currentNamespace string) string { diff --git a/pkg/common/ingress/types_equals.go b/pkg/common/ingress/types_equals.go index 61da81eb0..f0bf62847 100644 --- a/pkg/common/ingress/types_equals.go +++ b/pkg/common/ingress/types_equals.go @@ -513,3 +513,11 @@ func (l4b1 *ProxyProtocol) Equal(l4b2 *ProxyProtocol) bool { } return true } + +// Equal tests for equality between two SSLCert types +func (c1 *SSLCert) Equal(c2 *SSLCert) bool { + if c1.PemSHA != c2.PemSHA { + return false + } + return true +} diff --git a/pkg/controller/config.go b/pkg/controller/config.go index 57ae6ba2a..8142dcc06 100644 --- a/pkg/controller/config.go +++ b/pkg/controller/config.go @@ -76,6 +76,7 @@ func newControllerConfig(ingressConfig *ingress.Configuration, haproxyController UDPEndpoints: cfg.ingress.UDPEndpoints, PassthroughBackends: cfg.ingress.PassthroughBackends, HAPassthrough: cfg.haPassthrough, + StatsSSLCert: cfg.statsSSLCert(), Cfg: cfg.haproxyConfig, DNSResolvers: cfg.DNSResolvers, }, nil @@ -124,6 +125,7 @@ func newHAProxyConfig(haproxyController *HAProxyController) *types.HAProxyConfig HTTPStoHTTPPort: 0, StatsPort: 1936, StatsAuth: "", + StatsSSLCert: "", CookieKey: "Ingress", DynamicScaling: false, StatsSocket: "/var/run/haproxy-stats.sock", @@ -225,6 +227,19 @@ func (cfg *haConfig) createDNSResolvers() { cfg.DNSResolvers = resolvers } +func (cfg *haConfig) statsSSLCert() *ingress.SSLCert { + secretName := cfg.haproxyConfig.StatsSSLCert + if secretName == "" { + return &ingress.SSLCert{} + } + sslCert, err := cfg.haproxyController.controller.GetCertificate(secretName) + if err != nil { + glog.Warningf("error loading stats cert/key: %v", err) + return &ingress.SSLCert{} + } + return sslCert +} + func (cfg *haConfig) createHAProxyServers() { haServers := make([]*types.HAProxyServer, 0, len(cfg.ingress.Servers)) haPassthrough := make([]*types.HAProxyPassthrough, 0, len(cfg.ingress.PassthroughBackends)) diff --git a/pkg/types/equal.go b/pkg/types/equal.go index bea6df3a1..a092eff8e 100644 --- a/pkg/types/equal.go +++ b/pkg/types/equal.go @@ -51,5 +51,8 @@ func (c1 *ControllerConfig) Equal(c2 *ControllerConfig) bool { if !reflect.DeepEqual(c1.Cfg, c2.Cfg) { return false } + if !c1.StatsSSLCert.Equal(c2.StatsSSLCert) { + return false + } return true } diff --git a/pkg/types/types.go b/pkg/types/types.go index b264da14c..cb1ca1249 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -42,6 +42,7 @@ type ( UDPEndpoints []ingress.L4Service PassthroughBackends []*ingress.SSLPassthroughBackend HAPassthrough []*HAProxyPassthrough + StatsSSLCert *ingress.SSLCert Cfg *HAProxyConfig BackendSlots map[string]*HAProxyBackendSlots DNSResolvers map[string]dnsresolvers.DNSResolver @@ -77,6 +78,7 @@ type ( HTTPStoHTTPPort int `json:"https-to-http-port"` StatsPort int `json:"stats-port"` StatsAuth string `json:"stats-auth"` + StatsSSLCert string `json:"stats-ssl-cert"` CookieKey string `json:"cookie-key"` DynamicScaling bool `json:"dynamic-scaling"` StatsSocket string diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 015a76024..f4928835f 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -528,7 +528,11 @@ listen error503noendpoints ###### Stats page ###### listen stats - bind {{ $cfg.BindIPAddrStats }}:{{ $cfg.StatsPort }}{{ if $cfg.StatsProxyProtocol }} accept-proxy{{ end }} +{{- $ssl := $ing.StatsSSLCert }} +{{- if ne $ssl.PemSHA "" }} + # CRT PEM checksum: {{ $ssl.PemSHA }} +{{- end }} + bind {{ $cfg.BindIPAddrStats }}:{{ $cfg.StatsPort }}{{ if ne $ssl.PemFileName "" }} ssl crt {{ $ssl.PemFileName }}{{ end }}{{ if $cfg.StatsProxyProtocol }} accept-proxy{{ end }} mode http stats enable stats realm HAProxy\ Statistics