Skip to content

Commit

Permalink
Dynamic reload of SSL certificates for NGINX Plus (#4764)
Browse files Browse the repository at this point in the history
ssl certificate lazy loading for nginx plus
  • Loading branch information
oseoin authored Dec 12, 2023
1 parent a122d11 commit bd29988
Show file tree
Hide file tree
Showing 41 changed files with 1,077 additions and 354 deletions.
1 change: 1 addition & 0 deletions charts/nginx-ingress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ The following tables lists the configurable parameters of the NGINX Ingress Cont
|`controller.defaultHTTPListenerPort` | Sets the port for the HTTP `default_server` listener. | 80 |
|`controller.defaultHTTPSListenerPort` | Sets the port for the HTTPS `default_server` listener. | 443 |
|`controller.readOnlyRootFilesystem` | Configure root filesystem as read-only and add volumes for temporary data. | false |
|`controller.enableSSLDynamicReload` | Enable lazy loading for SSL Certificates for NGINX Plus. | true |
|`rbac.create` | Configures RBAC. | true |
|`prometheus.create` | Expose NGINX or NGINX Plus metrics in the Prometheus format. | true |
|`prometheus.port` | Configures the port to scrape the metrics. | 9113 |
Expand Down
1 change: 1 addition & 0 deletions charts/nginx-ingress/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,5 @@ Build the args for the service binary.
- -ready-status={{ .Values.controller.readyStatus.enable }}
- -ready-status-port={{ .Values.controller.readyStatus.port }}
- -enable-latency-metrics={{ .Values.controller.enableLatencyMetrics }}
- -ssl-dynamic-reload={{ .Values.controller.enableSSLDynamicReload }}
{{- end -}}
8 changes: 8 additions & 0 deletions charts/nginx-ingress/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,14 @@
"examples": [
false
]
},
"enableSSLDynamicReload": {
"type": "boolean",
"default": true,
"title": "Enable dynamic certificate reloads for NGINX Plus",
"examples": [
true
]
}
},
"examples": [
Expand Down
3 changes: 3 additions & 0 deletions charts/nginx-ingress/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,9 @@ controller:
## Configure root filesystem as read-only and add volumes for temporary data.
readOnlyRootFilesystem: false

## Enable dynamic reloading of certificates for NGINX Plus
enableSSLDynamicReload: true

rbac:
## Configures RBAC.
create: true
Expand Down
11 changes: 11 additions & 0 deletions cmd/nginx-ingress/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
)

const (
dynamicSSLReloadParam = "ssl-dynamic-reload"
)

var (
healthStatus = flag.Bool("health-status", false,
`Add a location based on the value of health-status-uri to the default server. The location responds with the 200 status code for any request.
Expand Down Expand Up @@ -195,6 +199,8 @@ var (

defaultHTTPSListenerPort = flag.Int("default-https-listener-port", 443, "Sets a custom port for the HTTPS `default_server`. [1024 - 65535]")

enableDynamicSSLReload = flag.Bool(dynamicSSLReloadParam, true, "Enable reloading of SSL Certificates without restarting the NGINX process. Requires -nginx-plus")

startupCheckFn func() error
)

Expand Down Expand Up @@ -269,6 +275,11 @@ func parseFlags() {
if *ingressLink != "" && *externalService != "" {
glog.Fatal("ingresslink and external-service cannot both be set")
}

if *enableDynamicSSLReload && !*nginxPlus {
glog.V(3).Infof("%s flag requires -nginx-plus and will not be enabled", dynamicSSLReloadParam)
*enableDynamicSSLReload = false
}
}

func initialChecks() {
Expand Down
25 changes: 14 additions & 11 deletions cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ func main() {
EnableOIDC: *enableOIDC,
SSLRejectHandshake: sslRejectHandshake,
EnableCertManager: *enableCertManager,
DynamicSSLReload: *enableDynamicSSLReload,
StaticSSLPath: nginxManager.GetSecretsDir(),
}

processNginxConfig(staticCfgParams, cfgParams, templateExecutor, nginxManager)
Expand All @@ -132,17 +134,18 @@ func main() {

plusCollector, syslogListener, latencyCollector := createPlusAndLatencyCollectors(registry, constLabels, kubeClient, plusClient, staticCfgParams.NginxServiceMesh)
cnf := configs.NewConfigurator(configs.ConfiguratorParams{
NginxManager: nginxManager,
StaticCfgParams: staticCfgParams,
Config: cfgParams,
TemplateExecutor: templateExecutor,
TemplateExecutorV2: templateExecutorV2,
LatencyCollector: latencyCollector,
LabelUpdater: plusCollector,
IsPlus: *nginxPlus,
IsWildcardEnabled: isWildcardEnabled,
IsPrometheusEnabled: *enablePrometheusMetrics,
IsLatencyMetricsEnabled: *enableLatencyMetrics,
NginxManager: nginxManager,
StaticCfgParams: staticCfgParams,
Config: cfgParams,
TemplateExecutor: templateExecutor,
TemplateExecutorV2: templateExecutorV2,
LatencyCollector: latencyCollector,
LabelUpdater: plusCollector,
IsPlus: *nginxPlus,
IsWildcardEnabled: isWildcardEnabled,
IsPrometheusEnabled: *enablePrometheusMetrics,
IsLatencyMetricsEnabled: *enableLatencyMetrics,
IsDynamicSSLReloadEnabled: *enableDynamicSSLReload,
})

controllerNamespace := os.Getenv("POD_NAMESPACE")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,3 +527,11 @@ Sets the port for the HTTPS `default_server` listener.
Default `443`.

<a name="cmdoption-default-https-listener-port"></a>

### -ssl-dynamic-reload

Used to activate or deactivate lazy loading for SSL Certificates for NGINX Plus.

The default value is `true` when using NGINX Plus.

<a name="cmdoption-ssl-dynamic-reload"></a>
15 changes: 15 additions & 0 deletions internal/configs/commonhelpers/common_template_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Package commonhelpers contains template helpers used in v1 and v2
package commonhelpers

import (
"strings"
)

// MakeSecretPath will return the path to the secret with the base secrets
// path replaced with the given variable
func MakeSecretPath(path, defaultPath, variable string, useVariable bool) string {
if useVariable {
return strings.Replace(path, defaultPath, variable, 1)
}
return path
}
63 changes: 63 additions & 0 deletions internal/configs/commonhelpers/common_template_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package commonhelpers

import (
"bytes"
"html/template"
"testing"
)

var helperFunctions = template.FuncMap{
"makeSecretPath": MakeSecretPath,
}

func TestMakeSecretPath(t *testing.T) {
t.Parallel()

tmpl := newMakeSecretPathTemplate(t)
testCases := []struct {
Secret string
Path string
Variable string
Enabled bool
expected string
}{
{
Secret: "/etc/nginx/secret/thing.crt",
Path: "/etc/nginx/secret",
Variable: "$secrets_path",
Enabled: true,
expected: "$secrets_path/thing.crt",
},
{
Secret: "/etc/nginx/secret/thing.crt",
Path: "/etc/nginx/secret",
Variable: "$secrets_path",
Enabled: false,
expected: "/etc/nginx/secret/thing.crt",
},
{
Secret: "/etc/nginx/secret/thing.crt",
expected: "/etc/nginx/secret/thing.crt",
},
}

for _, tc := range testCases {
var buf bytes.Buffer
err := tmpl.Execute(&buf, tc)
if err != nil {
t.Fatalf("Failed to execute the template %v", err)
}
if buf.String() != tc.expected {
t.Errorf("Template generated wrong config, got '%v' but expected '%v'.", buf.String(), tc.expected)
}
}
}

func newMakeSecretPathTemplate(t *testing.T) *template.Template {
t.Helper()
tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(`{{makeSecretPath .Secret .Path .Variable .Enabled}}`)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
return tmpl
}
2 changes: 2 additions & 0 deletions internal/configs/config_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ type StaticConfigParams struct {
EnableOIDC bool
SSLRejectHandshake bool
EnableCertManager bool
DynamicSSLReload bool
StaticSSLPath string
}

// GlobalConfigParams holds global configuration parameters. For now, it only holds listeners.
Expand Down
2 changes: 2 additions & 0 deletions internal/configs/configmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,8 @@ func GenerateNginxMainConfig(staticCfgParams *StaticConfigParams, config *Config
InternalRouteServerName: staticCfgParams.InternalRouteServerName,
LatencyMetrics: staticCfgParams.EnableLatencyMetrics,
OIDC: staticCfgParams.EnableOIDC,
DynamicSSLReloadEnabled: staticCfgParams.DynamicSSLReload,
StaticSSLPath: staticCfgParams.StaticSSLPath,
}
return nginxCfg
}
Loading

0 comments on commit bd29988

Please sign in to comment.