Skip to content
Merged
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
4 changes: 4 additions & 0 deletions deployments/common/crds/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ spec:
properties:
authEndpoint:
type: string
authExtraArgs:
type: array
items:
type: string
clientID:
type: string
clientSecret:
Expand Down
4 changes: 4 additions & 0 deletions deployments/helm-chart/crds/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ spec:
properties:
authEndpoint:
type: string
authExtraArgs:
type: array
items:
type: string
clientID:
type: string
clientSecret:
Expand Down
1 change: 1 addition & 0 deletions docs/content/configuration/policy-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ The OIDC policy defines a few internal locations that can't be customized: `/_jw
|``clientID`` | The client ID provided by your OpenID Connect provider. | ``string`` | Yes |
|``clientSecret`` | The name of the Kubernetes secret that stores the client secret provided by your OpenID Connect provider. It must be in the same namespace as the Policy resource. The secret must be of the type ``nginx.org/oidc``, and the secret under the key ``client-secret``, otherwise the secret will be rejected as invalid. | ``string`` | Yes |
|``authEndpoint`` | URL for the authorization endpoint provided by your OpenID Connect provider. | ``string`` | Yes |
|``authExtraArgs`` | A list of extra URL arguments to pass to the authorization endpoint provided by your OpenID Connect provider. Arguments must be URL encoded, multiple arguments may be included in the list, for example ``[ arg1=value1, arg2=value2 ]`` | ``string[]`` | No |
|``tokenEndpoint`` | URL for the token endpoint provided by your OpenID Connect provider. | ``string`` | Yes |
|``jwksURI`` | URL for the JSON Web Key Set (JWK) document provided by your OpenID Connect provider. | ``string`` | Yes |
|``scope`` | List of OpenID Connect scopes. Possible values are ``openid``, ``profile``, ``email``, ``address`` and ``phone``. The scope ``openid`` always needs to be present and others can be added concatenating them with a ``+`` sign, for example ``openid+profile+email``. The default is ``openid``. | ``string`` | No |
Expand Down
4 changes: 4 additions & 0 deletions internal/configs/oidc/openid_connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ function getAuthZArgs(r) {
var nonceHash = h.digest('base64url');
var authZArgs = "?response_type=code&scope=" + r.variables.oidc_scopes + "&client_id=" + r.variables.oidc_client + "&redirect_uri=" + r.variables.redirect_base + r.variables.redir_location + "&nonce=" + nonceHash;

if (r.variables.oidc_authz_extra_args) {
authZArgs += "&" + r.variables.oidc_authz_extra_args;
}

r.headersOut['Set-Cookie'] = [
"auth_redir=" + r.variables.request_uri + "; " + r.variables.oidc_cookie_flags,
"auth_nonce=" + noncePlain + "; " + r.variables.oidc_cookie_flags
Expand Down
1 change: 1 addition & 0 deletions internal/configs/version2/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ type OIDC struct {
TokenEndpoint string
RedirectURI string
ZoneSyncLeeway int
AuthExtraArgs string
}

// WAF defines WAF configuration.
Expand Down
1 change: 1 addition & 0 deletions internal/configs/version2/nginx-plus.virtualserver.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ server {
set $zone_sync_leeway {{ $oidc.ZoneSyncLeeway }};

set $oidc_authz_endpoint "{{ $oidc.AuthEndpoint }}";
set $oidc_authz_extra_args "{{ $oidc.AuthExtraArgs }}";
set $oidc_token_endpoint "{{ $oidc.TokenEndpoint }}";
set $oidc_jwt_keyfile "{{ $oidc.JwksURI }}";
set $oidc_scopes "{{ $oidc.Scope }}";
Expand Down
5 changes: 5 additions & 0 deletions internal/configs/virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1025,9 +1025,14 @@ func (p *policiesCfg) addOIDCConfig(
if scope == "" {
scope = "openid"
}
authExtraArgs := ""
if oidc.AuthExtraArgs != nil {
authExtraArgs = strings.Join(oidc.AuthExtraArgs, "&")
}

oidcPolCfg.oidc = &version2.OIDC{
AuthEndpoint: oidc.AuthEndpoint,
AuthExtraArgs: authExtraArgs,
TokenEndpoint: oidc.TokenEndpoint,
JwksURI: oidc.JWKSURI,
ClientID: oidc.ClientID,
Expand Down
17 changes: 9 additions & 8 deletions pkg/apis/configuration/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,14 +472,15 @@ type EgressMTLS struct {

// OIDC defines an Open ID Connect policy.
type OIDC struct {
AuthEndpoint string `json:"authEndpoint"`
TokenEndpoint string `json:"tokenEndpoint"`
JWKSURI string `json:"jwksURI"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
Scope string `json:"scope"`
RedirectURI string `json:"redirectURI"`
ZoneSyncLeeway *int `json:"zoneSyncLeeway"`
AuthEndpoint string `json:"authEndpoint"`
TokenEndpoint string `json:"tokenEndpoint"`
JWKSURI string `json:"jwksURI"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
Scope string `json:"scope"`
RedirectURI string `json:"redirectURI"`
ZoneSyncLeeway *int `json:"zoneSyncLeeway"`
AuthExtraArgs []string `json:"authExtraArgs"`
}

// WAF defines an WAF policy.
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/configuration/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions pkg/apis/configuration/validation/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ func validateOIDC(oidc *v1.OIDC, fieldPath *field.Path) field.ErrorList {
allErrs = append(allErrs, validatePositiveIntOrZero(*oidc.ZoneSyncLeeway, fieldPath.Child("zoneSyncLeeway"))...)
}

if oidc.AuthExtraArgs != nil {
allErrs = append(allErrs, validateQueryString(strings.Join(oidc.AuthExtraArgs, "&"), fieldPath.Child("authExtraArgs"))...)
}

allErrs = append(allErrs, validateURL(oidc.AuthEndpoint, fieldPath.Child("authEndpoint"))...)
allErrs = append(allErrs, validateURL(oidc.TokenEndpoint, fieldPath.Child("tokenEndpoint"))...)
allErrs = append(allErrs, validateURL(oidc.JWKSURI, fieldPath.Child("jwksURI"))...)
Expand Down Expand Up @@ -367,6 +371,17 @@ func validateURL(name string, fieldPath *field.Path) field.ErrorList {
return allErrs
}

func validateQueryString(queryString string, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

_, err := url.ParseQuery(queryString)
if err != nil {
return append(allErrs, field.Invalid(fieldPath, queryString, err.Error()))
}

return allErrs
}

func validatePortNumber(port string, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
portInt, _ := strconv.Atoi(port)
Expand Down
54 changes: 54 additions & 0 deletions pkg/apis/configuration/validation/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func TestValidatePolicy(t *testing.T) {
Spec: v1.PolicySpec{
OIDC: &v1.OIDC{
AuthEndpoint: "https://foo.bar/auth",
AuthExtraArgs: []string{"foo=bar"},
TokenEndpoint: "https://foo.bar/token",
JWKSURI: "https://foo.bar/certs",
ClientID: "random-string",
Expand Down Expand Up @@ -210,6 +211,24 @@ func TestValidatePolicyFails(t *testing.T) {
enableOIDC: true,
msg: "OIDC policy with invalid ZoneSyncLeeway",
},
{
policy: &v1.Policy{
Spec: v1.PolicySpec{
OIDC: &v1.OIDC{
AuthEndpoint: "https://foo.bar/auth",
AuthExtraArgs: []string{"foo;bar"},
TokenEndpoint: "https://foo.bar/token",
JWKSURI: "https://foo.bar/certs",
ClientID: "random-string",
ClientSecret: "random-secret",
Scope: "openid",
},
},
},
isPlus: true,
enableOIDC: true,
msg: "OIDC policy with invalid AuthExtraArgs",
},
}
for _, test := range tests {
err := ValidatePolicy(test.policy, test.isPlus, test.enableOIDC, test.enableAppProtect)
Expand Down Expand Up @@ -872,6 +891,7 @@ func TestValidateOIDCValid(t *testing.T) {
{
oidc: &v1.OIDC{
AuthEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
AuthExtraArgs: []string{"foo=bar", "baz=zot"},
TokenEndpoint: "https://oauth2.googleapis.com/token",
JWKSURI: "https://www.googleapis.com/oauth2/v3/certs",
ClientID: "random-string",
Expand All @@ -897,6 +917,7 @@ func TestValidateOIDCValid(t *testing.T) {
{
oidc: &v1.OIDC{
AuthEndpoint: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/auth",
AuthExtraArgs: []string{"kc_idp_hint=foo"},
TokenEndpoint: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/token",
JWKSURI: "http://keycloak.default.svc.cluster.local:8080/auth/realms/master/protocol/openid-connect/certs",
ClientID: "bar",
Expand Down Expand Up @@ -1012,6 +1033,18 @@ func TestValidateOIDCInvalid(t *testing.T) {
},
msg: "invalid chars in clientID",
},
{
oidc: &v1.OIDC{
AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth",
AuthExtraArgs: []string{"foo;bar"},
TokenEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token",
JWKSURI: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs",
ClientID: "foobar",
ClientSecret: "secret",
Scope: "openid",
},
msg: "invalid chars in authExtraArgs",
},
{
oidc: &v1.OIDC{
AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth",
Expand Down Expand Up @@ -1097,6 +1130,27 @@ func TestValidateURL(t *testing.T) {
}
}

func TestValidateQueryStringt(t *testing.T) {
t.Parallel()
validInput := []string{"foo=bar", "foo", "foo=bar&baz=zot", "foo=bar&foo=baz", "foo=bar%3Bbaz"}

for _, test := range validInput {
allErrs := validateQueryString(test, field.NewPath("authExtraArgs"))
if len(allErrs) != 0 {
t.Errorf("validateQueryString(%q) returned errors %v for valid input", allErrs, test)
}
}

invalidInput := []string{"foo=bar;baz"}

for _, test := range invalidInput {
allErrs := validateQueryString(test, field.NewPath("authExtraArgs"))
if len(allErrs) == 0 {
t.Errorf("validateQueryString(%q) didn't return error for invalid input", test)
}
}
}

func TestValidateWAF(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down