Skip to content

Commit 96044a3

Browse files
javorszkyhaywoodshvepatel
authored
Add SNI to NIC JWT Policy (#7993)
* Add SNI related options and validation * Add the new options to the crd definitions * Update snaps * Rename sniServerName to sniName * Add unit tests for sni * Move SNI into JwksURI struct * If JwksURI is not set, SNI should not be set * add test to routes and include docs to crd Signed-off-by: Haywood Shannon <[email protected]> Signed-off-by: Haywood Shannon <[email protected]> * add unit test for validation Signed-off-by: Haywood Shannon <[email protected]> Signed-off-by: Haywood Shannon <[email protected]> * test codecov Signed-off-by: Haywood Shannon <[email protected]> Signed-off-by: Haywood Shannon <[email protected]> * edit tests Signed-off-by: Haywood Shannon <[email protected]> Signed-off-by: Haywood Shannon <[email protected]> * remove redundant tests Signed-off-by: Haywood Shannon <[email protected]> Signed-off-by: Haywood Shannon <[email protected]> --------- Signed-off-by: Haywood Shannon <[email protected]> Co-authored-by: Haywood Shannon <[email protected]> Co-authored-by: Venktesh Shivam Patel <[email protected]>
1 parent ab54158 commit 96044a3

File tree

11 files changed

+280
-28
lines changed

11 files changed

+280
-28
lines changed

config/crd/bases/k8s.nginx.org_policies.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ spec:
141141
type: string
142142
secret:
143143
type: string
144+
sniEnabled:
145+
description: Enables SNI (Server Name Indication) for the JWT
146+
policy. This is useful when the remote server requires SNI to
147+
serve the correct certificate.
148+
type: boolean
149+
sniName:
150+
description: The SNI name to use when connecting to the remote
151+
server. If not set, the hostname from the ``jwksURI`` will be
152+
used.
153+
type: string
144154
token:
145155
type: string
146156
type: object

deploy/crds.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,16 @@ spec:
303303
type: string
304304
secret:
305305
type: string
306+
sniEnabled:
307+
description: Enables SNI (Server Name Indication) for the JWT
308+
policy. This is useful when the remote server requires SNI to
309+
serve the correct certificate.
310+
type: boolean
311+
sniName:
312+
description: The SNI name to use when connecting to the remote
313+
server. If not set, the hostname from the ``jwksURI`` will be
314+
used.
315+
type: string
306316
token:
307317
type: string
308318
type: object

internal/configs/version2/__snapshots__/templates_test.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,8 @@ server {
11151115
proxy_set_header Content-Length "";
11161116
proxy_cache jwks_uri_cafe;
11171117
proxy_cache_valid 200 12h;
1118+
proxy_ssl_server_name on;
1119+
proxy_ssl_name sni.idp.spec.example.com;
11181120
proxy_set_header Host idp.spec.example.com;
11191121
set $idp_backend idp.spec.example.com;
11201122
proxy_pass https://$idp_backend:443/spec-keys;
@@ -1125,6 +1127,8 @@ server {
11251127
proxy_set_header Content-Length "";
11261128
proxy_cache jwks_uri_cafe;
11271129
proxy_cache_valid 200 12h;
1130+
proxy_ssl_server_name on;
1131+
proxy_ssl_name sni.idp.spec.example.com;
11281132
proxy_set_header Host idp.route.example.com;
11291133
set $idp_backend idp.route.example.com;
11301134
proxy_pass http://$idp_backend:80/route-keys;

internal/configs/version2/http.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,10 +438,12 @@ type JWTAuth struct {
438438

439439
// JwksURI defines the components of a JwksURI
440440
type JwksURI struct {
441-
JwksScheme string
442-
JwksHost string
443-
JwksPort string
444-
JwksPath string
441+
JwksScheme string
442+
JwksHost string
443+
JwksPort string
444+
JwksPath string
445+
JwksSNIName string
446+
JwksSNIEnabled bool
445447
}
446448

447449
// BasicAuth refers to basic HTTP authentication mechanism options

internal/configs/version2/nginx-plus.virtualserver.tmpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ server {
237237
proxy_cache_valid 200 12h;
238238
{{- end }}
239239
{{- with .JwksURI }}
240+
{{- if .JwksSNIEnabled }}
241+
proxy_ssl_server_name on;
242+
{{- if .JwksSNIName }}
243+
proxy_ssl_name {{ .JwksSNIName }};
244+
{{- end }}
245+
{{- end }}
240246
proxy_set_header Host {{ .JwksHost }};
241247
set $idp_backend {{ .JwksHost }};
242248
proxy_pass {{ .JwksScheme}}://$idp_backend{{ if .JwksPort }}:{{ .JwksPort }}{{ end }}{{ .JwksPath }};

internal/configs/version2/templates_test.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,15 @@ func TestExecuteVirtualServerTemplateWithJWKSWithToken(t *testing.T) {
737737
if !bytes.Contains(got, []byte("proxy_cache_valid 200 12h;")) {
738738
t.Error("want `proxy_cache_valid 200 12h;` in generated template")
739739
}
740+
741+
if !bytes.Contains(got, []byte("proxy_ssl_server_name on;")) {
742+
t.Error("want `proxy_ssl_server_name on;` in generated template")
743+
}
744+
745+
if !bytes.Contains(got, []byte("proxy_ssl_name sni.idp.spec.example.com;")) {
746+
t.Error("want `proxy_ssl_name sni.idp.spec.example.com;` in generated template")
747+
}
748+
740749
snaps.MatchSnapshot(t, string(got))
741750
t.Log(string(got))
742751
}
@@ -2345,10 +2354,12 @@ var (
23452354
Token: "$http_token",
23462355
KeyCache: "1h",
23472356
JwksURI: JwksURI{
2348-
JwksScheme: "https",
2349-
JwksHost: "idp.spec.example.com",
2350-
JwksPort: "443",
2351-
JwksPath: "/spec-keys",
2357+
JwksScheme: "https",
2358+
JwksHost: "idp.spec.example.com",
2359+
JwksPort: "443",
2360+
JwksPath: "/spec-keys",
2361+
JwksSNIEnabled: true,
2362+
JwksSNIName: "sni.idp.spec.example.com",
23522363
},
23532364
},
23542365
"default/jwt-policy-route": {
@@ -2357,10 +2368,12 @@ var (
23572368
Token: "$http_token",
23582369
KeyCache: "1h",
23592370
JwksURI: JwksURI{
2360-
JwksScheme: "http",
2361-
JwksHost: "idp.route.example.com",
2362-
JwksPort: "80",
2363-
JwksPath: "/route-keys",
2371+
JwksScheme: "http",
2372+
JwksHost: "idp.route.example.com",
2373+
JwksPort: "80",
2374+
JwksPath: "/route-keys",
2375+
JwksSNIEnabled: true,
2376+
JwksSNIName: "sni.idp.spec.example.com",
23642377
},
23652378
},
23662379
},

internal/configs/virtualserver.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,10 +1169,12 @@ func (p *policiesCfg) addJWTAuthConfig(
11691169
uri, _ := url.Parse(jwtAuth.JwksURI)
11701170

11711171
JwksURI := &version2.JwksURI{
1172-
JwksScheme: uri.Scheme,
1173-
JwksHost: uri.Hostname(),
1174-
JwksPort: uri.Port(),
1175-
JwksPath: uri.Path,
1172+
JwksScheme: uri.Scheme,
1173+
JwksHost: uri.Hostname(),
1174+
JwksPort: uri.Port(),
1175+
JwksPath: uri.Path,
1176+
JwksSNIName: jwtAuth.SNIName,
1177+
JwksSNIEnabled: jwtAuth.SNIEnabled,
11761178
}
11771179

11781180
p.JWTAuth.Auth = &version2.JWTAuth{

internal/configs/virtualserver_test.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5641,9 +5641,11 @@ func TestGenerateVirtualServerConfigJWKSPolicy(t *testing.T) {
56415641
},
56425642
Spec: conf_v1.PolicySpec{
56435643
JWTAuth: &conf_v1.JWTAuth{
5644-
Realm: "Spec Realm API",
5645-
JwksURI: "https://idp.spec.example.com:443/spec-keys",
5646-
KeyCache: "1h",
5644+
Realm: "Spec Realm API",
5645+
JwksURI: "https://idp.spec.example.com:443/spec-keys",
5646+
KeyCache: "1h",
5647+
SNIEnabled: true,
5648+
SNIName: "idp.spec.example.com",
56475649
},
56485650
},
56495651
},
@@ -5713,10 +5715,12 @@ func TestGenerateVirtualServerConfigJWKSPolicy(t *testing.T) {
57135715
Realm: "Spec Realm API",
57145716
KeyCache: "1h",
57155717
JwksURI: version2.JwksURI{
5716-
JwksScheme: "https",
5717-
JwksHost: "idp.spec.example.com",
5718-
JwksPort: "443",
5719-
JwksPath: "/spec-keys",
5718+
JwksScheme: "https",
5719+
JwksHost: "idp.spec.example.com",
5720+
JwksPort: "443",
5721+
JwksPath: "/spec-keys",
5722+
JwksSNIEnabled: true,
5723+
JwksSNIName: "idp.spec.example.com",
57205724
},
57215725
},
57225726
"default/jwt-policy-route": {
@@ -5736,10 +5740,12 @@ func TestGenerateVirtualServerConfigJWKSPolicy(t *testing.T) {
57365740
Realm: "Spec Realm API",
57375741
KeyCache: "1h",
57385742
JwksURI: version2.JwksURI{
5739-
JwksScheme: "https",
5740-
JwksHost: "idp.spec.example.com",
5741-
JwksPort: "443",
5742-
JwksPath: "/spec-keys",
5743+
JwksScheme: "https",
5744+
JwksHost: "idp.spec.example.com",
5745+
JwksPort: "443",
5746+
JwksPath: "/spec-keys",
5747+
JwksSNIName: "idp.spec.example.com",
5748+
JwksSNIEnabled: true,
57435749
},
57445750
},
57455751
JWKSAuthEnabled: true,

pkg/apis/configuration/v1/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,10 @@ type JWTAuth struct {
657657
Token string `json:"token"`
658658
JwksURI string `json:"jwksURI"`
659659
KeyCache string `json:"keyCache"`
660+
// Enables SNI (Server Name Indication) for the JWT policy. This is useful when the remote server requires SNI to serve the correct certificate.
661+
SNIEnabled bool `json:"sniEnabled"`
662+
// The SNI name to use when connecting to the remote server. If not set, the hostname from the ``jwksURI`` will be used.
663+
SNIName string `json:"sniName"`
660664
}
661665

662666
// BasicAuth holds HTTP Basic authentication configuration

pkg/apis/configuration/validation/policy.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"unicode"
1111

12+
validation2 "github.com/nginx/kubernetes-ingress/internal/validation"
1213
v1 "github.com/nginx/kubernetes-ingress/pkg/apis/configuration/v1"
1314
"k8s.io/apimachinery/pkg/util/validation"
1415
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -198,6 +199,16 @@ func validateJWT(jwt *v1.JWTAuth, fieldPath *field.Path) field.ErrorList {
198199
if jwt.KeyCache != "" {
199200
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("keyCache"), "key cache must not be used when using Secret"))
200201
}
202+
203+
// If JwksURI is not set, then none of the SNI fields should be set.
204+
if jwt.SNIEnabled {
205+
return append(allErrs, field.Forbidden(fieldPath.Child("sniEnabled"), "sniEnabled can only be set when JwksURI is set"))
206+
}
207+
208+
if jwt.SNIName != "" {
209+
return append(allErrs, field.Forbidden(fieldPath.Child("sniName"), "sniName can only be set when JwksURI is set"))
210+
}
211+
201212
return allErrs
202213
}
203214

@@ -213,7 +224,22 @@ func validateJWT(jwt *v1.JWTAuth, fieldPath *field.Path) field.ErrorList {
213224
if jwt.KeyCache == "" {
214225
allErrs = append(allErrs, field.Required(fieldPath.Child("keyCache"), "key cache must be set, example value: 1h"))
215226
}
216-
return allErrs
227+
228+
// if SNI server name is provided, but SNI is not enabled, return an error
229+
if jwt.SNIName != "" && !jwt.SNIEnabled {
230+
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("sniServerName"), "sniServerName can only be set when sniEnabled is true"))
231+
}
232+
233+
// if SNI is enabled and SNI server name is provided, make sure it's a valid URI
234+
if jwt.SNIEnabled && jwt.SNIName != "" {
235+
err := validation2.ValidateURI(jwt.SNIName,
236+
validation2.WithAllowedSchemes("https"),
237+
validation2.WithUserAllowed(false),
238+
validation2.WithDefaultScheme("https"))
239+
if err != nil {
240+
allErrs = append(allErrs, field.Invalid(fieldPath.Child("sniServerName"), jwt.SNIName, "sniServerName is not a valid URI"))
241+
}
242+
}
217243
}
218244
return allErrs
219245
}

0 commit comments

Comments
 (0)