diff --git a/documentation/annotations.md b/documentation/annotations.md index e1adf582..2a6bc220 100644 --- a/documentation/annotations.md +++ b/documentation/annotations.md @@ -24,6 +24,7 @@ This is autogenerated from [doc.yaml](doc.yaml). Description can be found in [ge | [client-ca](#authentication) | string | | ssl-offloading |:large_blue_circle:|:white_circle:|:white_circle:| | [client-crt-optional](#authentication) | [bool](#bool) | "false" | client-ca |:large_blue_circle:|:white_circle:|:white_circle:| | [client-strict-sni](#ssl-offloading) | [bool](#bool) | "false" | client-ca |:large_blue_circle:|:white_circle:|:white_circle:| +| [generate-certificates-signer](#ssl-offloading) | string | | |:large_blue_circle:|:white_circle:|:white_circle:| | [cors-enable](#CORS) | [bool](#bool) | "false" | |:large_blue_circle:|:large_blue_circle:|:white_circle:| | [cors-allow-origin](#CORS) | string | "*" | cors-enable |:large_blue_circle:|:large_blue_circle:|:white_circle:| | [cors-allow-methods](#CORS) | string | "*" | cors-enable |:large_blue_circle:|:large_blue_circle:|:white_circle:| @@ -1698,6 +1699,32 @@ Example: client-strict-sni: true ``` +##### `generate-certificates-signer` + + Specifies the Kubernetes secret containing the CA certificate file used to sign automatically generated certificates. + When this annotation is set, HAProxy's generate-certificates feature is automatically enabled on the HTTPS frontend bind line, + allowing HAProxy to automatically generate certificates for incoming TLS connections using the provided CA for signing. + + Available on: `configmap` + + :information_source: The secret should contain the CA certificate and key in PEM format. + + :information_source: The secret format should be namespace/secret-name. + + :information_source: When this annotation is configured, the generate-certificates option is automatically added to the bind line in the HTTPS frontend when SSL offload is enabled. + + :information_source: HAProxy will use this CA to sign certificates generated for incoming TLS connections. + +Possible values: + +- Name of Kubernetes secret in format namespace/secret-name + +Example: + +```yaml +generate-certificates-signer: "default/ca-signing-cert" +``` + ##### `ssl-certificate` Sets the name of the Kubernetes secret that contains both the TLS key and certificate. diff --git a/documentation/doc.yaml b/documentation/doc.yaml index 1f401147..258c6d72 100644 --- a/documentation/doc.yaml +++ b/documentation/doc.yaml @@ -686,6 +686,27 @@ annotations: version_min: "1.8" example: - "client-strict-sni: true" + - title: generate-certificates-signer + type: string + group: ssl-offloading + dependencies: "" + default: "" + description: + - Specifies the Kubernetes kubernetes.io/tls type secret containing the CA certificate file used to sign automatically generated certificates. + - When this annotation is set, HAProxy's generate-certificates feature is automatically enabled on the HTTPS frontend bind line. + - This allows HAProxy to automatically generate certificates for incoming TLS connections using the provided CA for signing. + - The secret should contain the CA certificate and key in PEM format. + tip: + - The secret format should be namespace/secret-name or just secret-name. + - When this annotation is configured, the generate-certificates option is automatically added to the bind line in the HTTPS frontend when SSL offload is enabled. + - HAProxy will use this CA to sign certificates generated for incoming TLS connections. + values: + - Name of Kubernetes secret in format namespace/secret-name + applies_to: + - configmap + version_min: "1.11" + example: + - 'generate-certificates-signer: "default/ca-signing-cert"' - title: cors-enable type: bool group: CORS diff --git a/pkg/handler/https.go b/pkg/handler/https.go index 6edcf5e5..2bdffff2 100644 --- a/pkg/handler/https.go +++ b/pkg/handler/https.go @@ -34,15 +34,16 @@ import ( ) type HTTPS struct { - AddrIPv4 string - AddrIPv6 string - CertDir string - alpn string - Port int64 - Enabled bool - IPv4 bool - IPv6 bool - strictSNI bool + AddrIPv4 string + AddrIPv6 string + CertDir string + alpn string + Port int64 + Enabled bool + IPv4 bool + IPv6 bool + strictSNI bool + generateCertificatesSigner string } //nolint:golint, stylecheck @@ -170,11 +171,32 @@ func (handler *HTTPS) Update(k store.K8s, h haproxy.HAProxy, a annotations.Annot handler.strictSNI, err = annotations.Bool("client-strict-sni", k.ConfigMaps.Main.Annotations) logger.Error(err) + // Handle generate-certificates-signer secret + handler.generateCertificatesSigner = "" + var notFound store.ErrNotFound + secret, annErr := annotations.Secret("generate-certificates-signer", "", k, k.ConfigMaps.Main.Annotations) + if annErr != nil { + if errors.Is(annErr, notFound) { + logger.Debugf("generate-certificates-signer not configured: %s", annErr) + } else { + err = fmt.Errorf("generate-certificates-signer: %w", annErr) + return err + } + } + if secret != nil { + caFile, certErr := h.Certificates.AddSecret(secret, certs.FT_CERT) + if certErr != nil { + err = fmt.Errorf("generate-certificates-signer: %w", certErr) + return err + } + handler.generateCertificatesSigner = caFile + } + // ssl-offload sslOffloadEnabled := h.FrontendSSLOffloadEnabled(h.FrontHTTPS) if h.FrontCertsInUse() { if !sslOffloadEnabled { - logger.Panic(h.FrontendEnableSSLOffload(h.FrontHTTPS, handler.CertDir, handler.alpn, handler.strictSNI)) + logger.Panic(h.FrontendEnableSSLOffload(h.FrontHTTPS, handler.CertDir, handler.alpn, handler.strictSNI, handler.generateCertificatesSigner)) instance.Reload("SSL offload enabled") } err := handler.handleClientTLSAuth(k, h) @@ -268,7 +290,7 @@ func (handler *HTTPS) toggleSSLPassthrough(passthrough bool, h haproxy.HAProxy) } } if h.FrontendSSLOffloadEnabled(h.FrontHTTPS) || h.FrontCertsInUse() { - logger.Panic(h.FrontendEnableSSLOffload(h.FrontHTTPS, handler.CertDir, handler.alpn, handler.strictSNI)) + logger.Panic(h.FrontendEnableSSLOffload(h.FrontHTTPS, handler.CertDir, handler.alpn, handler.strictSNI, handler.generateCertificatesSigner)) } return nil } diff --git a/pkg/handler/tcp-services.go b/pkg/handler/tcp-services.go index fda7364e..f98f218b 100644 --- a/pkg/handler/tcp-services.go +++ b/pkg/handler/tcp-services.go @@ -160,7 +160,7 @@ func (handler TCPServices) createTCPFrontend(h haproxy.HAProxy, frontend models. })) } if sslOffload { - errors.Add(h.FrontendEnableSSLOffload(frontend.Name, handler.CertDir, "", false)) + errors.Add(h.FrontendEnableSSLOffload(frontend.Name, handler.CertDir, "", false, "")) } if errors.Result() != nil { err = fmt.Errorf("error configuring tcp frontend: %w", err) @@ -191,7 +191,7 @@ func (handler TCPServices) updateTCPFrontend(k store.K8s, h haproxy.HAProxy, fro return err } if !binds[0].Ssl && p.sslOffload { - err = h.FrontendEnableSSLOffload(frontend.Name, handler.CertDir, "", false) + err = h.FrontendEnableSSLOffload(frontend.Name, handler.CertDir, "", false, "") if err != nil { err = fmt.Errorf("failed to enable SSL offload: %w", err) return err diff --git a/pkg/haproxy/api/api.go b/pkg/haproxy/api/api.go index 8f502f62..2d9d3b5d 100644 --- a/pkg/haproxy/api/api.go +++ b/pkg/haproxy/api/api.go @@ -66,7 +66,7 @@ type HAProxyClient interface { //nolint:interfacebloat FrontendsGet() (models.Frontends, error) FrontendGet(frontendName string) (models.Frontend, error) FrontendEdit(frontend models.FrontendBase) error - FrontendEnableSSLOffload(frontendName string, certDir string, alpn string, strictSNI bool) (err error) + FrontendEnableSSLOffload(frontendName string, certDir string, alpn string, strictSNI bool, generateCertificatesSigner string) (err error) FrontendDisableSSLOffload(frontendName string) (err error) FrontendSSLOffloadEnabled(frontendName string) bool Bind diff --git a/pkg/haproxy/api/frontend.go b/pkg/haproxy/api/frontend.go index 37040db5..b65988ea 100644 --- a/pkg/haproxy/api/frontend.go +++ b/pkg/haproxy/api/frontend.go @@ -73,7 +73,7 @@ func (c *clientNative) FrontendEdit(frontend models.FrontendBase) error { return configuration.EditFrontend(frontend.Name, f, c.activeTransaction, 0) } -func (c *clientNative) FrontendEnableSSLOffload(frontendName string, certDir string, alpn string, strictSNI bool) (err error) { +func (c *clientNative) FrontendEnableSSLOffload(frontendName string, certDir string, alpn string, strictSNI bool, generateCertificatesSigner string) (err error) { binds, err := c.FrontendBindsGet(frontendName) if err != nil { return err @@ -81,6 +81,12 @@ func (c *clientNative) FrontendEnableSSLOffload(frontendName string, certDir str for _, bind := range binds { bind.Ssl = true bind.SslCertificate = certDir + if generateCertificatesSigner != "" { + bind.GenerateCertificates = true + bind.CaSignFile = generateCertificatesSigner + } else { + bind.GenerateCertificates = false + } if alpn != "" { bind.Alpn = alpn bind.StrictSni = strictSNI @@ -103,8 +109,10 @@ func (c *clientNative) FrontendDisableSSLOffload(frontendName string) (err error bind.SslCafile = "" bind.Verify = "" bind.SslCertificate = "" + bind.CaSignFile = "" bind.Alpn = "" bind.StrictSni = false + bind.GenerateCertificates = false err = c.FrontendBindEdit(frontendName, *bind) } if err != nil {