Skip to content

Add SNI to NIC JWT Policy #7993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/crd/bases/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ spec:
type: string
secret:
type: string
sniEnabled:
description: Enables SNI (Server Name Indication) for the JWT
policy. This is useful when the remote server requires SNI to
serve the correct certificate.
type: boolean
sniName:
description: The SNI name to use when connecting to the remote
server. If not set, the hostname from the ``jwksURI`` will be
used.
type: string
token:
type: string
type: object
Expand Down
10 changes: 10 additions & 0 deletions deploy/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@ spec:
type: string
secret:
type: string
sniEnabled:
description: Enables SNI (Server Name Indication) for the JWT
policy. This is useful when the remote server requires SNI to
serve the correct certificate.
type: boolean
sniName:
description: The SNI name to use when connecting to the remote
server. If not set, the hostname from the ``jwksURI`` will be
used.
type: string
token:
type: string
type: object
Expand Down
4 changes: 4 additions & 0 deletions internal/configs/version2/__snapshots__/templates_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,8 @@ server {
proxy_set_header Content-Length "";
proxy_cache jwks_uri_cafe;
proxy_cache_valid 200 12h;
proxy_ssl_server_name on;
proxy_ssl_name sni.idp.spec.example.com;
proxy_set_header Host idp.spec.example.com;
set $idp_backend idp.spec.example.com;
proxy_pass https://$idp_backend:443/spec-keys;
Expand All @@ -1125,6 +1127,8 @@ server {
proxy_set_header Content-Length "";
proxy_cache jwks_uri_cafe;
proxy_cache_valid 200 12h;
proxy_ssl_server_name on;
proxy_ssl_name sni.idp.spec.example.com;
proxy_set_header Host idp.route.example.com;
set $idp_backend idp.route.example.com;
proxy_pass http://$idp_backend:80/route-keys;
Expand Down
10 changes: 6 additions & 4 deletions internal/configs/version2/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,10 +438,12 @@ type JWTAuth struct {

// JwksURI defines the components of a JwksURI
type JwksURI struct {
JwksScheme string
JwksHost string
JwksPort string
JwksPath string
JwksScheme string
JwksHost string
JwksPort string
JwksPath string
JwksSNIName string
JwksSNIEnabled bool
}

// BasicAuth refers to basic HTTP authentication mechanism options
Expand Down
6 changes: 6 additions & 0 deletions internal/configs/version2/nginx-plus.virtualserver.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ server {
proxy_cache_valid 200 12h;
{{- end }}
{{- with .JwksURI }}
{{- if .JwksSNIEnabled }}
proxy_ssl_server_name on;
{{- if .JwksSNIName }}
proxy_ssl_name {{ .JwksSNIName }};
{{- end }}
{{- end }}
proxy_set_header Host {{ .JwksHost }};
set $idp_backend {{ .JwksHost }};
proxy_pass {{ .JwksScheme}}://$idp_backend{{ if .JwksPort }}:{{ .JwksPort }}{{ end }}{{ .JwksPath }};
Expand Down
29 changes: 21 additions & 8 deletions internal/configs/version2/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,15 @@ func TestExecuteVirtualServerTemplateWithJWKSWithToken(t *testing.T) {
if !bytes.Contains(got, []byte("proxy_cache_valid 200 12h;")) {
t.Error("want `proxy_cache_valid 200 12h;` in generated template")
}

if !bytes.Contains(got, []byte("proxy_ssl_server_name on;")) {
t.Error("want `proxy_ssl_server_name on;` in generated template")
}

if !bytes.Contains(got, []byte("proxy_ssl_name sni.idp.spec.example.com;")) {
t.Error("want `proxy_ssl_name sni.idp.spec.example.com;` in generated template")
}

snaps.MatchSnapshot(t, string(got))
t.Log(string(got))
}
Expand Down Expand Up @@ -2345,10 +2354,12 @@ var (
Token: "$http_token",
KeyCache: "1h",
JwksURI: JwksURI{
JwksScheme: "https",
JwksHost: "idp.spec.example.com",
JwksPort: "443",
JwksPath: "/spec-keys",
JwksScheme: "https",
JwksHost: "idp.spec.example.com",
JwksPort: "443",
JwksPath: "/spec-keys",
JwksSNIEnabled: true,
JwksSNIName: "sni.idp.spec.example.com",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haywoodsh same test at the route level line#2371

},
},
"default/jwt-policy-route": {
Expand All @@ -2357,10 +2368,12 @@ var (
Token: "$http_token",
KeyCache: "1h",
JwksURI: JwksURI{
JwksScheme: "http",
JwksHost: "idp.route.example.com",
JwksPort: "80",
JwksPath: "/route-keys",
JwksScheme: "http",
JwksHost: "idp.route.example.com",
JwksPort: "80",
JwksPath: "/route-keys",
JwksSNIEnabled: true,
JwksSNIName: "sni.idp.spec.example.com",
},
},
},
Expand Down
10 changes: 6 additions & 4 deletions internal/configs/virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1169,10 +1169,12 @@ func (p *policiesCfg) addJWTAuthConfig(
uri, _ := url.Parse(jwtAuth.JwksURI)

JwksURI := &version2.JwksURI{
JwksScheme: uri.Scheme,
JwksHost: uri.Hostname(),
JwksPort: uri.Port(),
JwksPath: uri.Path,
JwksScheme: uri.Scheme,
JwksHost: uri.Hostname(),
JwksPort: uri.Port(),
JwksPath: uri.Path,
JwksSNIName: jwtAuth.SNIName,
JwksSNIEnabled: jwtAuth.SNIEnabled,
}

p.JWTAuth.Auth = &version2.JWTAuth{
Expand Down
28 changes: 17 additions & 11 deletions internal/configs/virtualserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5641,9 +5641,11 @@ func TestGenerateVirtualServerConfigJWKSPolicy(t *testing.T) {
},
Spec: conf_v1.PolicySpec{
JWTAuth: &conf_v1.JWTAuth{
Realm: "Spec Realm API",
JwksURI: "https://idp.spec.example.com:443/spec-keys",
KeyCache: "1h",
Realm: "Spec Realm API",
JwksURI: "https://idp.spec.example.com:443/spec-keys",
KeyCache: "1h",
SNIEnabled: true,
SNIName: "idp.spec.example.com",
},
},
},
Expand Down Expand Up @@ -5713,10 +5715,12 @@ func TestGenerateVirtualServerConfigJWKSPolicy(t *testing.T) {
Realm: "Spec Realm API",
KeyCache: "1h",
JwksURI: version2.JwksURI{
JwksScheme: "https",
JwksHost: "idp.spec.example.com",
JwksPort: "443",
JwksPath: "/spec-keys",
JwksScheme: "https",
JwksHost: "idp.spec.example.com",
JwksPort: "443",
JwksPath: "/spec-keys",
JwksSNIEnabled: true,
JwksSNIName: "idp.spec.example.com",
},
},
"default/jwt-policy-route": {
Expand All @@ -5736,10 +5740,12 @@ func TestGenerateVirtualServerConfigJWKSPolicy(t *testing.T) {
Realm: "Spec Realm API",
KeyCache: "1h",
JwksURI: version2.JwksURI{
JwksScheme: "https",
JwksHost: "idp.spec.example.com",
JwksPort: "443",
JwksPath: "/spec-keys",
JwksScheme: "https",
JwksHost: "idp.spec.example.com",
JwksPort: "443",
JwksPath: "/spec-keys",
JwksSNIName: "idp.spec.example.com",
JwksSNIEnabled: true,
},
},
JWKSAuthEnabled: true,
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/configuration/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,10 @@ type JWTAuth struct {
Token string `json:"token"`
JwksURI string `json:"jwksURI"`
KeyCache string `json:"keyCache"`
// Enables SNI (Server Name Indication) for the JWT policy. This is useful when the remote server requires SNI to serve the correct certificate.
SNIEnabled bool `json:"sniEnabled"`
// The SNI name to use when connecting to the remote server. If not set, the hostname from the ``jwksURI`` will be used.
SNIName string `json:"sniName"`
}

// BasicAuth holds HTTP Basic authentication configuration
Expand Down
29 changes: 28 additions & 1 deletion pkg/apis/configuration/validation/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"unicode"

validation2 "github.com/nginx/kubernetes-ingress/internal/validation"
v1 "github.com/nginx/kubernetes-ingress/pkg/apis/configuration/v1"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
Expand Down Expand Up @@ -201,6 +202,17 @@ func validateJWT(jwt *v1.JWTAuth, fieldPath *field.Path) field.ErrorList {
return allErrs
}

if jwt.JwksURI == "" {
// If JwksURI is not set, then none of the SNI fields should be set.
if jwt.SNIEnabled {
return append(allErrs, field.Forbidden(fieldPath.Child("sniEnabled"), "sniEnabled can only be set when JwksURI is set"))
}

if jwt.SNIName != "" {
return append(allErrs, field.Forbidden(fieldPath.Child("sniName"), "sniName can only be set when JwksURI is set"))
}
}

// Verify a case when using JWKS
if jwt.JwksURI != "" {
allErrs = append(allErrs, validateURL(jwt.JwksURI, fieldPath.Child("JwksURI"))...)
Expand All @@ -213,7 +225,22 @@ func validateJWT(jwt *v1.JWTAuth, fieldPath *field.Path) field.ErrorList {
if jwt.KeyCache == "" {
allErrs = append(allErrs, field.Required(fieldPath.Child("keyCache"), "key cache must be set, example value: 1h"))
}
return allErrs

// if SNI server name is provided, but SNI is not enabled, return an error
if jwt.SNIName != "" && !jwt.SNIEnabled {
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("sniServerName"), "sniServerName can only be set when sniEnabled is true"))
}

// if SNI is enabled and SNI server name is provided, make sure it's a valid URI
if jwt.SNIEnabled && jwt.SNIName != "" {
err := validation2.ValidateURI(jwt.SNIName,
validation2.WithAllowedSchemes("https"),
validation2.WithUserAllowed(false),
validation2.WithDefaultScheme("https"))
if err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("sniServerName"), jwt.SNIName, "sniServerName is not a valid URI"))
}
}
}
return allErrs
}
Expand Down
Loading