Skip to content

Commit 614dc0a

Browse files
committed
Enhance OIDCNative validation and error handling; add tests for missing secrets and invalid configurations, address copilot feedback
1 parent 9c4039e commit 614dc0a

7 files changed

Lines changed: 121 additions & 3 deletions

File tree

internal/configs/policy.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,8 +904,20 @@ func resolveOIDCNativeClientSecret(
904904
res.isError = true
905905
return "", false
906906
}
907+
if secretRef.Secret == nil {
908+
res.addWarningf("OIDCNative policy %s references an invalid secret %s: secret doesn't exist", polKey, secretKey)
909+
res.isError = true
910+
return "", false
911+
}
912+
913+
clientSecretBytes, ok := secretRef.Secret.Data[ClientSecretKey]
914+
if !ok {
915+
res.addWarningf("OIDCNative policy %s references a secret %s missing '%s' key", polKey, secretKey, ClientSecretKey)
916+
res.isError = true
917+
return "", false
918+
}
907919

908-
return string(secretRef.Secret.Data[ClientSecretKey]), true
920+
return string(clientSecretBytes), true
909921
}
910922

911923
func resolveOIDCNativeTrustedCert(

internal/configs/policy_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4835,6 +4835,93 @@ func TestGeneratePoliciesFails(t *testing.T) {
48354835
},
48364836
msg: "oidcNative missing secret",
48374837
},
4838+
{
4839+
policyRefs: []conf_v1.PolicyReference{
4840+
{
4841+
Name: "oidc-native-policy",
4842+
Namespace: "default",
4843+
},
4844+
},
4845+
policies: map[string]*conf_v1.Policy{
4846+
"default/oidc-native-policy": {
4847+
ObjectMeta: meta_v1.ObjectMeta{
4848+
Name: "oidc-native-policy",
4849+
Namespace: "default",
4850+
},
4851+
Spec: conf_v1.PolicySpec{
4852+
OIDCNative: &conf_v1.OIDCNative{
4853+
Issuer: "https://accounts.google.com",
4854+
ClientID: "my-client",
4855+
ClientSecret: "oidc-secret",
4856+
},
4857+
},
4858+
},
4859+
},
4860+
policyOpts: policyOptions{
4861+
secretRefs: map[string]*secrets.SecretReference{
4862+
"default/oidc-secret": {
4863+
Secret: &api_v1.Secret{
4864+
Type: secrets.SecretTypeOIDC,
4865+
Data: map[string][]byte{},
4866+
},
4867+
},
4868+
},
4869+
},
4870+
context: "route",
4871+
expected: policiesCfg{
4872+
ErrorReturn: &version2.Return{
4873+
Code: 500,
4874+
},
4875+
},
4876+
expectedWarnings: Warnings{
4877+
nil: {
4878+
`OIDCNative policy default/oidc-native-policy references a secret default/oidc-secret missing 'client-secret' key`,
4879+
},
4880+
},
4881+
msg: "oidcNative secret missing client-secret key",
4882+
},
4883+
{
4884+
policyRefs: []conf_v1.PolicyReference{
4885+
{
4886+
Name: "oidc-native-policy",
4887+
Namespace: "default",
4888+
},
4889+
},
4890+
policies: map[string]*conf_v1.Policy{
4891+
"default/oidc-native-policy": {
4892+
ObjectMeta: meta_v1.ObjectMeta{
4893+
Name: "oidc-native-policy",
4894+
Namespace: "default",
4895+
},
4896+
Spec: conf_v1.PolicySpec{
4897+
OIDCNative: &conf_v1.OIDCNative{
4898+
Issuer: "https://accounts.google.com",
4899+
ClientID: "my-client",
4900+
ClientSecret: "oidc-secret",
4901+
},
4902+
},
4903+
},
4904+
},
4905+
policyOpts: policyOptions{
4906+
secretRefs: map[string]*secrets.SecretReference{
4907+
"default/oidc-secret": {
4908+
Secret: nil,
4909+
},
4910+
},
4911+
},
4912+
context: "route",
4913+
expected: policiesCfg{
4914+
ErrorReturn: &version2.Return{
4915+
Code: 500,
4916+
},
4917+
},
4918+
expectedWarnings: Warnings{
4919+
nil: {
4920+
`OIDCNative policy default/oidc-native-policy references an invalid secret default/oidc-secret: secret doesn't exist`,
4921+
},
4922+
},
4923+
msg: "oidcNative secret reference with nil Secret",
4924+
},
48384925
{
48394926
policyRefs: []conf_v1.PolicyReference{
48404927
{

internal/configs/version2/__snapshots__/templates_test.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4558,7 +4558,7 @@ oidc_provider oidc_default_my_provider_default_cafe {
45584558
client_id my-client-id;
45594559
client_secret "my-resolved-secret";
45604560
config_url https://accounts.google.com/.well-known/openid-configuration;
4561-
scope openid+profile;
4561+
scope openid profile;
45624562
redirect_uri /callback;
45634563
logout_uri /logout;
45644564
post_logout_uri /logged_out;

internal/configs/version2/templates_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3855,7 +3855,7 @@ func TestVirtualServerForNginxPlusWithOIDCNative(t *testing.T) {
38553855
ClientID: "my-client-id",
38563856
ClientSecret: "my-resolved-secret",
38573857
ConfigURL: "https://accounts.google.com/.well-known/openid-configuration",
3858-
Scope: "openid+profile",
3858+
Scope: "openid profile",
38593859
RedirectURI: "/callback",
38603860
CookieName: "MY_SESSION",
38613861
ExtraAuthArgs: "prompt=login",

pkg/apis/configuration/v1/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ type PolicySpec struct {
801801
// +kubebuilder:validation:XValidation:rule="(self.sslVerify == true) || (self.sslVerify == false && !has(self.trustedCertSecret))",message="trustedCertSecret can be set only if sslVerify is true"
802802
OIDC *OIDC `json:"oidc"`
803803
// The OpenID Connect policy configures NGINX to authenticate client requests by validating a JWT token against an OAuth2/OIDC token provider, such as Auth0 or Keycloak. NGINX Plus native.
804+
// +kubebuilder:validation:XValidation:rule="(self.sslVerify == true) || (self.sslVerify == false && !has(self.trustedCertSecret))",message="trustedCertSecret can be set only if sslVerify is true"
804805
OIDCNative *OIDCNative `json:"oidcNative"`
805806
// The WAF policy configures WAF and log configuration policies for NGINX AppProtect
806807
WAF *WAF `json:"waf"`

pkg/apis/configuration/validation/policy.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,11 @@ func validateOIDCNative(oidcNative *v1.OIDCNative, fieldPath *field.Path) field.
477477

478478
allErrs := field.ErrorList{}
479479

480+
if oidcNative.SSLVerify != nil && !*oidcNative.SSLVerify && oidcNative.TrustedCertSecret != "" {
481+
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("trustedCertSecret"),
482+
"trustedCertSecret can be set only if sslVerify is true"))
483+
}
484+
480485
// Issuer hostname/port validation goes beyond the CRD pattern (which only checks https:// + host).
481486
allErrs = append(allErrs, validateIssuerURL(oidcNative.Issuer, fieldPath.Child("issuer"))...)
482487
// ClientID char validation (rejects $, unescaped \, etc.) — not expressible in a simple CRD pattern.
@@ -1063,6 +1068,9 @@ func validateIssuerURL(issuer string, fieldPath *field.Path) field.ErrorList {
10631068
if u.Host == "" {
10641069
return field.ErrorList{field.Invalid(fieldPath, issuer, "hostname required")}
10651070
}
1071+
if u.RawQuery != "" || u.Fragment != "" || u.User != nil {
1072+
return field.ErrorList{field.Invalid(fieldPath, issuer, "must not include userinfo, query, or fragment")}
1073+
}
10661074

10671075
host, port, err := net.SplitHostPort(u.Host)
10681076
if err != nil {

pkg/apis/configuration/validation/policy_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,6 +2215,16 @@ func TestValidateOIDCNative_FailsOnInvalidInput(t *testing.T) {
22152215
fieldPath: "oidcNative.scope",
22162216
msg: "openid must be a complete scope token",
22172217
},
2218+
{
2219+
oidcNative: &v1.OIDCNative{Issuer: "https://accounts.google.com", ClientID: "my-client", SSLVerify: new(bool), TrustedCertSecret: "my-ca"},
2220+
fieldPath: "oidcNative.trustedCertSecret",
2221+
msg: "trustedCertSecret set when sslVerify is false",
2222+
},
2223+
{
2224+
oidcNative: &v1.OIDCNative{Issuer: "https://accounts.google.com?query=1", ClientID: "my-client"},
2225+
fieldPath: "oidcNative.issuer",
2226+
msg: "issuer contains query parameter",
2227+
},
22182228
}
22192229

22202230
for _, test := range tests {

0 commit comments

Comments
 (0)