Skip to content

Commit df71ef4

Browse files
authored
Add support to provide TLS for Gateway (#3900)
Problem: Users want to be able to specify their gateway's identity when communicating to the backend pods. Solution: Add a field to provide a secret name that stores that gateways cert and key to be used when doing TLS handshake with backend pods.
1 parent 0212980 commit df71ef4

File tree

13 files changed

+1394
-710
lines changed

13 files changed

+1394
-710
lines changed

internal/controller/manager.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,10 @@ func StartManager(cfg config.Config) error {
140140
GenericValidator: genericValidator,
141141
PolicyValidator: policyManager,
142142
},
143-
EventRecorder: recorder,
144-
MustExtractGVK: mustExtractGVK,
145-
PlusSecrets: plusSecrets,
143+
EventRecorder: recorder,
144+
MustExtractGVK: mustExtractGVK,
145+
PlusSecrets: plusSecrets,
146+
ExperimentalFeatures: cfg.ExperimentalFeatures,
146147
})
147148

148149
var handlerCollector handlerMetricsCollector = collectors.NewControllerNoopCollector()

internal/controller/nginx/config/base_http_config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type AccessLog struct {
1919
type httpConfig struct {
2020
DNSResolver *dataplane.DNSResolverConfig
2121
AccessLog *AccessLog
22+
GatewaySecretID dataplane.SSLKeyPairID
2223
Includes []shared.Include
2324
NginxReadinessProbePort int32
2425
IPFamily shared.IPFamily
@@ -35,6 +36,7 @@ func executeBaseHTTPConfig(conf dataplane.Configuration) []executeResult {
3536
IPFamily: getIPFamily(conf.BaseHTTPConfig),
3637
DNSResolver: conf.BaseHTTPConfig.DNSResolver,
3738
AccessLog: buildAccessLog(conf.Logging.AccessLog),
39+
GatewaySecretID: conf.BaseHTTPConfig.GatewaySecretID,
3840
}
3941

4042
results := make([]executeResult, 0, len(includes)+1)

internal/controller/nginx/config/base_http_config_template.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ access_log {{ .AccessLog.Path }} {{ .AccessLog.FormatName }};
6161
{{- end }}
6262
{{- end }}
6363
64+
{{- if $.GatewaySecretID }}
65+
# Gateway Certificate
66+
proxy_ssl_certificate /etc/nginx/secrets/{{ $.GatewaySecretID }}.pem;
67+
proxy_ssl_certificate_key /etc/nginx/secrets/{{ $.GatewaySecretID }}.pem;
68+
{{- end }}
69+
6470
{{ range $i := .Includes -}}
6571
include {{ $i.Name }};
6672
{{ end -}}

internal/controller/nginx/config/base_http_config_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,49 @@ func TestExecuteBaseHttp_DNSResolver(t *testing.T) {
356356
})
357357
}
358358
}
359+
360+
func TestExecuteBaseHttp_GatewaySecretID(t *testing.T) {
361+
t.Parallel()
362+
363+
tests := []struct {
364+
name string
365+
expectedConfig string
366+
conf dataplane.Configuration
367+
}{
368+
{
369+
name: "with GatewaySecretID",
370+
conf: dataplane.Configuration{
371+
BaseHTTPConfig: dataplane.BaseHTTPConfig{
372+
GatewaySecretID: "client-secret",
373+
},
374+
},
375+
expectedConfig: "proxy_ssl_certificate /etc/nginx/secrets/client-secret.pem;" +
376+
"\nproxy_ssl_certificate_key /etc/nginx/secrets/client-secret.pem;",
377+
},
378+
{
379+
name: "without GatewaySecretID",
380+
conf: dataplane.Configuration{
381+
BaseHTTPConfig: dataplane.BaseHTTPConfig{
382+
GatewaySecretID: "",
383+
},
384+
},
385+
expectedConfig: "",
386+
},
387+
}
388+
389+
for _, test := range tests {
390+
t.Run(test.name, func(t *testing.T) {
391+
t.Parallel()
392+
g := NewWithT(t)
393+
394+
res := executeBaseHTTPConfig(test.conf)
395+
g.Expect(res).To(HaveLen(1))
396+
397+
httpConfig := string(res[0].data)
398+
399+
if test.expectedConfig != "" {
400+
g.Expect(httpConfig).To(ContainSubstring(test.expectedConfig))
401+
}
402+
})
403+
}
404+
}

internal/controller/state/conditions/conditions.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ const (
146146
// parametersRef resource is invalid.
147147
GatewayReasonParamsRefInvalid v1.GatewayConditionReason = "ParametersRefInvalid"
148148

149+
// GatewayReasonSecretRefInvalid is used with the "GatewayResolvedRefs" condition when the
150+
// secretRef resource is invalid.
151+
GatewayReasonSecretRefInvalid v1.GatewayConditionReason = "SecretRefInvalid"
152+
153+
// GatewayReasonSecretRefNotPermitted is used with the "GatewayResolvedRefs" condition when the
154+
// secretRef resource is not permitted by any ReferenceGrant.
155+
GatewayReasonSecretRefNotPermitted v1.GatewayConditionReason = "SecretRefNotPermitted"
156+
149157
// PolicyReasonAncestorLimitReached is used with the "PolicyAccepted" condition when a policy
150158
// cannot be applied because the ancestor status list has reached the maximum size of 16.
151159
PolicyReasonAncestorLimitReached v1.PolicyConditionReason = "AncestorLimitReached"
@@ -292,6 +300,27 @@ func NewGatewayClassUnsupportedVersion(recommendedVersion string) []Condition {
292300
}
293301
}
294302

303+
// NewGatewaySecretRefNotPermitted returns Condition that indicates that the Gateway references a TLS secret that is not
304+
// permitted by any ReferenceGrant.
305+
func NewGatewaySecretRefNotPermitted(msg string) Condition {
306+
return Condition{
307+
Type: string(GatewayReasonResolvedRefs),
308+
Status: metav1.ConditionFalse,
309+
Reason: string(GatewayReasonSecretRefNotPermitted),
310+
Message: msg,
311+
}
312+
}
313+
314+
// NewGatewaySecretRefInvalid returns Condition that indicates that the Gateway references a TLS secret that is invalid.
315+
func NewGatewaySecretRefInvalid(msg string) Condition {
316+
return Condition{
317+
Type: string(GatewayReasonResolvedRefs),
318+
Status: metav1.ConditionFalse,
319+
Reason: string(GatewayReasonSecretRefInvalid),
320+
Message: msg,
321+
}
322+
}
323+
295324
// NewGatewayClassConflict returns a Condition that indicates that the GatewayClass is not accepted
296325
// due to a conflict with another GatewayClass.
297326
func NewGatewayClassConflict() Condition {
@@ -846,6 +875,25 @@ func NewGatewayInvalid(msg string) []Condition {
846875
}
847876
}
848877

878+
// NewGatewayUnsupportedValue returns Conditions that indicate that a field of the Gateway has an unsupported value.
879+
// Unsupported means that the value is not supported by the implementation under certain conditions or invalid.
880+
func NewGatewayUnsupportedValue(msg string) []Condition {
881+
return []Condition{
882+
{
883+
Type: string(v1.GatewayConditionAccepted),
884+
Status: metav1.ConditionFalse,
885+
Reason: string(GatewayReasonUnsupportedValue),
886+
Message: msg,
887+
},
888+
{
889+
Type: string(v1.GatewayConditionProgrammed),
890+
Status: metav1.ConditionFalse,
891+
Reason: string(GatewayReasonUnsupportedValue),
892+
Message: msg,
893+
},
894+
}
895+
}
896+
849897
// NewGatewayUnsupportedAddress returns a Condition that indicates the Gateway is not accepted because it
850898
// contains an address type that is not supported.
851899
func NewGatewayUnsupportedAddress(msg string) Condition {

internal/controller/state/dataplane/configuration.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ func BuildConfiguration(
8585
gateway,
8686
serviceResolver,
8787
g.ReferencedServices,
88-
baseHTTPConfig.IPFamily),
88+
baseHTTPConfig.IPFamily,
89+
),
8990
BackendGroups: backendGroups,
90-
SSLKeyPairs: buildSSLKeyPairs(g.ReferencedSecrets, gateway.Listeners),
91+
SSLKeyPairs: buildSSLKeyPairs(g.ReferencedSecrets, gateway),
9192
CertBundles: buildCertBundles(
9293
buildRefCertificateBundles(g.ReferencedSecrets, g.ReferencedCaCertConfigMaps),
9394
backendGroups,
@@ -252,14 +253,14 @@ func buildStreamUpstreams(
252253
}
253254

254255
// buildSSLKeyPairs builds the SSLKeyPairs from the Secrets. It will only include Secrets that are referenced by
255-
// valid listeners, so that we don't include unused Secrets in the configuration of the data plane.
256+
// valid gateway and its listeners, so that we don't include unused Secrets in the configuration of the data plane.
256257
func buildSSLKeyPairs(
257258
secrets map[types.NamespacedName]*graph.Secret,
258-
listeners []*graph.Listener,
259+
gateway *graph.Gateway,
259260
) map[SSLKeyPairID]SSLKeyPair {
260261
keyPairs := make(map[SSLKeyPairID]SSLKeyPair)
261262

262-
for _, l := range listeners {
263+
for _, l := range gateway.Listeners {
263264
if l.Valid && l.ResolvedSecret != nil {
264265
id := generateSSLKeyPairID(*l.ResolvedSecret)
265266
secret := secrets[*l.ResolvedSecret]
@@ -272,6 +273,15 @@ func buildSSLKeyPairs(
272273
}
273274
}
274275

276+
if gateway.Valid && gateway.SecretRef != nil {
277+
id := generateSSLKeyPairID(*gateway.SecretRef)
278+
secret := secrets[*gateway.SecretRef]
279+
keyPairs[id] = SSLKeyPair{
280+
Cert: secret.CertBundle.Cert.TLSCert,
281+
Key: secret.CertBundle.Cert.TLSPrivateKey,
282+
}
283+
}
284+
275285
return keyPairs
276286
}
277287

@@ -1058,6 +1068,10 @@ func buildBaseHTTPConfig(
10581068
NginxReadinessProbePort: DefaultNginxReadinessProbePort,
10591069
}
10601070

1071+
if gateway.Valid && gateway.SecretRef != nil {
1072+
baseConfig.GatewaySecretID = generateSSLKeyPairID(*gateway.SecretRef)
1073+
}
1074+
10611075
// safe to access EffectiveNginxProxy since we only call this function when the Gateway is not nil.
10621076
np := gateway.EffectiveNginxProxy
10631077
if np == nil {
@@ -1081,8 +1095,20 @@ func buildBaseHTTPConfig(
10811095
}
10821096
}
10831097

1098+
if port := getNginxReadinessProbePort(np); port != 0 {
1099+
baseConfig.NginxReadinessProbePort = port
1100+
}
1101+
10841102
baseConfig.RewriteClientIPSettings = buildRewriteClientIPConfig(np.RewriteClientIP)
10851103

1104+
baseConfig.DNSResolver = buildDNSResolverConfig(np.DNSResolver)
1105+
1106+
return baseConfig
1107+
}
1108+
1109+
func getNginxReadinessProbePort(np *graph.EffectiveNginxProxy) int32 {
1110+
var port int32
1111+
10861112
if np.Kubernetes != nil {
10871113
var containerSpec *ngfAPIv1alpha2.ContainerSpec
10881114
if np.Kubernetes.Deployment != nil {
@@ -1091,13 +1117,10 @@ func buildBaseHTTPConfig(
10911117
containerSpec = &np.Kubernetes.DaemonSet.Container
10921118
}
10931119
if containerSpec != nil && containerSpec.ReadinessProbe != nil && containerSpec.ReadinessProbe.Port != nil {
1094-
baseConfig.NginxReadinessProbePort = *containerSpec.ReadinessProbe.Port
1120+
port = *containerSpec.ReadinessProbe.Port
10951121
}
10961122
}
1097-
1098-
baseConfig.DNSResolver = buildDNSResolverConfig(np.DNSResolver)
1099-
1100-
return baseConfig
1123+
return port
11011124
}
11021125

11031126
// buildBaseStreamConfig generates the base stream context config that should be applied to all stream servers.

0 commit comments

Comments
 (0)