diff --git a/docs/content/configuration/policy-resource.md b/docs/content/configuration/policy-resource.md index 75daacc7df..fb90e7e058 100644 --- a/docs/content/configuration/policy-resource.md +++ b/docs/content/configuration/policy-resource.md @@ -442,7 +442,7 @@ The OIDC policy defines a few internal locations that can't be customized: `/_jw |``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 | +|``scope`` | List of OpenID Connect scopes. The scope ``openid`` always needs to be present and others can be added concatenating them with a ``+`` sign, for example ``openid+profile+email``, ``openid+email+userDefinedScope``. The default is ``openid``. | ``string`` | No | |``redirectURI`` | Allows overriding the default redirect URI. The default is ``/_codexch``. | ``string`` | No | |``zoneSyncLeeway`` | Specifies the maximum timeout in milliseconds for synchronizing ID/access tokens and shared values between Ingress Controller pods. The default is ``200``. | ``int`` | No | |``accessTokenEnable`` | Option of whether Bearer token is used to authorize NGINX to access protected backend. | ``boolean`` | No | diff --git a/pkg/apis/configuration/validation/common.go b/pkg/apis/configuration/validation/common.go index e094b1fa95..b66fcdaaa4 100644 --- a/pkg/apis/configuration/validation/common.go +++ b/pkg/apis/configuration/validation/common.go @@ -105,6 +105,7 @@ func validateStringWithVariables(str string, fieldPath *field.Path, specialVars return field.ErrorList{field.Invalid(fieldPath, str, "must not end with $")} } + allErrs := field.ErrorList{} for i, c := range str { if c == '$' { msg := "variables must be enclosed in curly braces, for example ${host}" @@ -119,7 +120,6 @@ func validateStringWithVariables(str string, fieldPath *field.Path, specialVars } } - allErrs := field.ErrorList{} nginxVars := captureVariables(str) for _, nVar := range nginxVars { special := false @@ -157,7 +157,6 @@ func validateOffset(offset string, fieldPath *field.Path) field.ErrorList { if offset == "" { return nil } - if _, err := configs.ParseOffset(offset); err != nil { msg := validation.RegexError(offsetErrMsg, configs.OffsetFmt, "16", "32k", "64M", "2G") return field.ErrorList{field.Invalid(fieldPath, offset, msg)} diff --git a/pkg/apis/configuration/validation/policy.go b/pkg/apis/configuration/validation/policy.go index 73bddd0d15..c855c6ce41 100644 --- a/pkg/apis/configuration/validation/policy.go +++ b/pkg/apis/configuration/validation/policy.go @@ -7,6 +7,7 @@ import ( "regexp" "strconv" "strings" + "unicode" v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" "k8s.io/apimachinery/pkg/util/validation" @@ -185,25 +186,21 @@ func validateJWT(jwt *v1.JWTAuth, fieldPath *field.Path) field.ErrorList { } func validateBasic(basic *v1.BasicAuth, fieldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} + if basic.Secret == "" { + return field.ErrorList{field.Required(fieldPath.Child("secret"), "")} + } + allErrs := field.ErrorList{} if basic.Realm != "" { allErrs = append(allErrs, validateRealm(basic.Realm, fieldPath.Child("realm"))...) } - - if basic.Secret == "" { - return append(allErrs, field.Required(fieldPath.Child("secret"), "")) - } - allErrs = append(allErrs, validateSecretName(basic.Secret, fieldPath.Child("secret"))...) - - return allErrs + return append(allErrs, validateSecretName(basic.Secret, fieldPath.Child("secret"))...) } func validateIngressMTLS(ingressMTLS *v1.IngressMTLS, fieldPath *field.Path) field.ErrorList { if ingressMTLS.ClientCertSecret == "" { return field.ErrorList{field.Required(fieldPath.Child("clientCertSecret"), "")} } - allErrs := validateSecretName(ingressMTLS.ClientCertSecret, fieldPath.Child("clientCertSecret")) allErrs = append(allErrs, validateIngressMTLSVerifyClient(ingressMTLS.VerifyClient, fieldPath.Child("verifyClient"))...) if ingressMTLS.VerifyDepth != nil { @@ -223,10 +220,7 @@ func validateEgressMTLS(egressMTLS *v1.EgressMTLS, fieldPath *field.Path) field. if egressMTLS.VerifyDepth != nil { allErrs = append(allErrs, validatePositiveIntOrZero(*egressMTLS.VerifyDepth, fieldPath.Child("verifyDepth"))...) } - - allErrs = append(allErrs, validateSSLName(egressMTLS.SSLName, fieldPath.Child("sslName"))...) - - return allErrs + return append(allErrs, validateSSLName(egressMTLS.SSLName, fieldPath.Child("sslName"))...) } func validateOIDC(oidc *v1.OIDC, fieldPath *field.Path) field.ErrorList { @@ -264,9 +258,7 @@ func validateOIDC(oidc *v1.OIDC, fieldPath *field.Path) field.ErrorList { allErrs = append(allErrs, validateURL(oidc.TokenEndpoint, fieldPath.Child("tokenEndpoint"))...) allErrs = append(allErrs, validateURL(oidc.JWKSURI, fieldPath.Child("jwksURI"))...) allErrs = append(allErrs, validateSecretName(oidc.ClientSecret, fieldPath.Child("clientSecret"))...) - allErrs = append(allErrs, validateClientID(oidc.ClientID, fieldPath.Child("clientID"))...) - - return allErrs + return append(allErrs, validateClientID(oidc.ClientID, fieldPath.Child("clientID"))...) } func validateWAF(waf *v1.WAF, fieldPath *field.Path) field.ErrorList { @@ -296,7 +288,6 @@ func validateWAF(waf *v1.WAF, fieldPath *field.Path) field.ErrorList { if waf.SecurityLog != nil { allErrs = append(allErrs, validateLogConf(waf.SecurityLog.ApLogConf, waf.SecurityLog.LogDest, fieldPath.Child("securityLog"))...) } - return allErrs } @@ -317,43 +308,49 @@ func validateLogConf(logConf, logDest string, fieldPath *field.Path) field.Error } func validateClientID(client string, fieldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - // isValidHeaderValue checks for $ and " in the string if isValidHeaderValue(client) != nil { - allErrs = append(allErrs, field.Invalid( + return field.ErrorList{field.Invalid( fieldPath, client, `invalid string. String must contain valid ASCII characters, must have all '"' escaped and must not contain any '$' or end with an unescaped '\' - `)) + `)} } - - return allErrs + return nil } -var validScopes = map[string]bool{ - "openid": true, - "profile": true, - "email": true, - "address": true, - "phone": true, +// Allowed unicode ranges in OIDC scope tokens. +// Ref. https://datatracker.ietf.org/doc/html/rfc6749#section-3.3 +var validOIDCScopeRanges = &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x21, 0x21, 1}, + {0x23, 0x5B, 1}, + {0x5D, 0x7E, 1}, + }, } -// https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims +// validateOIDCScope takes a scope representing OIDC scope tokens and +// checks if the scope is valid. OIDC scope must contain scope token +// "openid". Additionally, custom scope tokens can be added to the scope. +// +// Ref: +// - https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims +// +// Scope tokens must be separated by "+", and the "+" can't be a part of the token. func validateOIDCScope(scope string, fieldPath *field.Path) field.ErrorList { if !strings.Contains(scope, "openid") { - return field.ErrorList{field.Required(fieldPath, "openid scope")} + return field.ErrorList{field.Required(fieldPath, "openid is required")} } - allErrs := field.ErrorList{} - s := strings.Split(scope, "+") - for _, v := range s { - if !validScopes[v] { - msg := fmt.Sprintf("invalid Scope. Accepted scopes are: %v", mapToPrettyString(validScopes)) - allErrs = append(allErrs, field.Invalid(fieldPath, v, msg)) + for _, token := range strings.Split(scope, "+") { + for _, v := range token { + if !unicode.Is(validOIDCScopeRanges, v) { + msg := fmt.Sprintf("not allowed character %v in scope %s", v, scope) + return field.ErrorList{field.Invalid(fieldPath, scope, msg)} + } } } - return allErrs + return nil } func validateURL(name string, fieldPath *field.Path) field.ErrorList { @@ -460,7 +457,6 @@ func validateRateLimitZoneSize(zoneSize string, fieldPath *field.Path) field.Err if err == nil && kbZoneSizeNum < 32 || mbErr == nil && mbZoneSizeNum == 0 { allErrs = append(allErrs, field.Invalid(fieldPath, zoneSize, "must be greater than 31k")) } - return allErrs } @@ -478,13 +474,11 @@ func validateRateLimitKey(key string, fieldPath *field.Path, isPlus bool) field. if key == "" { return field.ErrorList{field.Required(fieldPath, "")} } - allErrs := field.ErrorList{} if err := ValidateEscapedString(key, `Hello World! \n`, `\"${request_uri}\" is unavailable. \n`); err != nil { allErrs = append(allErrs, field.Invalid(fieldPath, key, err.Error())) } - allErrs = append(allErrs, validateStringWithVariables(key, fieldPath, rateLimitKeySpecialVariables, rateLimitKeyVariables, isPlus)...) - return allErrs + return append(allErrs, validateStringWithVariables(key, fieldPath, rateLimitKeySpecialVariables, rateLimitKeyVariables, isPlus)...) } var jwtTokenSpecialVariables = []string{"arg_", "http_", "cookie_"} @@ -508,12 +502,12 @@ func validateJWTToken(token string, fieldPath *field.Path) field.ErrorList { break } } - if special { - // validateJWTToken is called only when NGINX Plus is running - return validateSpecialVariable(nVar, fieldPath, true) - } else { + + if !special { return field.ErrorList{field.Invalid(fieldPath, token, "must only have special vars")} } + // validateJWTToken is called only when NGINX Plus is running + return validateSpecialVariable(nVar, fieldPath, true) } var validLogLevels = map[string]bool{ diff --git a/pkg/apis/configuration/validation/policy_test.go b/pkg/apis/configuration/validation/policy_test.go index c0babb99f2..4d715faec3 100644 --- a/pkg/apis/configuration/validation/policy_test.go +++ b/pkg/apis/configuration/validation/policy_test.go @@ -7,7 +7,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) -func TestValidatePolicy(t *testing.T) { +func TestValidatePolicy_PassesOnValidInput(t *testing.T) { t.Parallel() tests := []struct { policy *v1.Policy @@ -84,7 +84,7 @@ func TestValidatePolicy(t *testing.T) { } } -func TestValidatePolicyFails(t *testing.T) { +func TestValidatePolicy_FailsOnInvalidInput(t *testing.T) { t.Parallel() tests := []struct { policy *v1.Policy @@ -242,7 +242,7 @@ func TestValidatePolicyFails(t *testing.T) { } } -func TestValidateAccessControl(t *testing.T) { +func TestValidateAccessControl_PassesOnValidInput(t *testing.T) { t.Parallel() validInput := []*v1.AccessControl{ { @@ -267,7 +267,7 @@ func TestValidateAccessControl(t *testing.T) { } } -func TestValidateAccessControlFails(t *testing.T) { +func TestValidateAccessControl_FailsOnInvalidInput(t *testing.T) { t.Parallel() tests := []struct { accessControl *v1.AccessControl @@ -309,19 +309,7 @@ func TestValidateAccessControlFails(t *testing.T) { } } -func TestValidateRate_ErrorsOnBogusRate(t *testing.T) { - t.Parallel() - - invalidRates := []string{"", "bogus"} - for _, v := range invalidRates { - allErrs := validateRate(v, field.NewPath("rate")) - if len(allErrs) == 0 { - t.Errorf("want err on invalid rate: %q, got nil", v) - } - } -} - -func TestValidateRateLimit(t *testing.T) { +func TestValidateRateLimit_PassesOnValidInput(t *testing.T) { t.Parallel() dryRun := true noDelay := false @@ -374,7 +362,7 @@ func createInvalidRateLimit(f func(r *v1.RateLimit)) *v1.RateLimit { return validRateLimit } -func TestValidateRateLimitFails(t *testing.T) { +func TestValidateRateLimit_FailsOnInvalidInput(t *testing.T) { t.Parallel() tests := []struct { rateLimit *v1.RateLimit @@ -434,7 +422,7 @@ func TestValidateRateLimitFails(t *testing.T) { } } -func TestValidateJWT(t *testing.T) { +func TestValidateJWT_PassesOnValidInput(t *testing.T) { t.Parallel() tests := []struct { jwt *v1.JWTAuth @@ -473,7 +461,7 @@ func TestValidateJWT(t *testing.T) { } } -func TestValidateJWTFails(t *testing.T) { +func TestValidateJWT_FailsOnInvalidInput(t *testing.T) { t.Parallel() tests := []struct { msg string @@ -576,7 +564,7 @@ func TestValidateJWTFails(t *testing.T) { } } -func TestValidateIPorCIDR_PassesOnValidInout(t *testing.T) { +func TestValidateIPorCIDR_PassesOnValidInput(t *testing.T) { t.Parallel() validInput := []string{ "192.168.1.1", @@ -591,20 +579,6 @@ func TestValidateIPorCIDR_PassesOnValidInout(t *testing.T) { t.Errorf("validateIPorCIDR(%q) returned errors %v for valid input", input, allErrs) } } - - invalidInput := []string{ - "localhost", - "192.168.1.0/", - "2001:0db8:::1", - "2001:0db8::/", - } - - for _, input := range invalidInput { - allErrs := validateIPorCIDR(input, field.NewPath("ipOrCIDR")) - if len(allErrs) == 0 { - t.Errorf("validateIPorCIDR(%q) returned no errors for invalid input", input) - } - } } func TestValidateIPorCIDR_FailsOnInvalidInput(t *testing.T) { @@ -644,7 +618,6 @@ func TestValidateRate_PassesOnValidInput(t *testing.T) { func TestValidateRate_ErrorsOnInvalidInput(t *testing.T) { t.Parallel() - invalidInput := []string{ "10s", "10r/", @@ -685,7 +658,7 @@ func TestValidatePositiveInt_ErrorsOnInvalidInput(t *testing.T) { } } -func TestValidateRateLimitZoneSize(t *testing.T) { +func TestValidateRateLimitZoneSize_PassesOnValidInput(t *testing.T) { t.Parallel() validInput := []string{"32", "32k", "32K", "10m"} @@ -706,8 +679,21 @@ func TestValidateRateLimitZoneSize(t *testing.T) { } } -func TestValidateRateLimitLogLevel(t *testing.T) { +func TestValidateRateLimitZoneSize_FailsOnInvalidInput(t *testing.T) { t.Parallel() + invalidInput := []string{"", "31", "31k", "0", "0M"} + + for _, test := range invalidInput { + allErrs := validateRateLimitZoneSize(test, field.NewPath("size")) + if len(allErrs) == 0 { + t.Errorf("validateRateLimitZoneSize(%q) didn't return error for invalid input", test) + } + } +} + +func TestValidateRateLimitLogLevel_PassesOnValidInput(t *testing.T) { + t.Parallel() + validInput := []string{"error", "info", "warn", "notice"} for _, test := range validInput { @@ -716,6 +702,10 @@ func TestValidateRateLimitLogLevel(t *testing.T) { t.Errorf("validateRateLimitLogLevel(%q) returned an error for valid input", test) } } +} + +func TestValidateRateLimitLogLevel_FailsOnInvalidInput(t *testing.T) { + t.Parallel() invalidInput := []string{"warn ", "info error", ""} @@ -727,7 +717,7 @@ func TestValidateRateLimitLogLevel(t *testing.T) { } } -func TestValidateJWTToken(t *testing.T) { +func TestValidateJWTToken_PassesOnValidInput(t *testing.T) { t.Parallel() validTests := []struct { token string @@ -756,7 +746,10 @@ func TestValidateJWTToken(t *testing.T) { t.Errorf("validateJWTToken(%v) returned an error for valid input for the case of %v", test.token, test.msg) } } +} +func TestValidateJWTToken_FailsOnInvalidInput(t *testing.T) { + t.Parallel() invalidTests := []struct { token string msg string @@ -790,7 +783,7 @@ func TestValidateJWTToken(t *testing.T) { } } -func TestValidateIngressMTLS(t *testing.T) { +func TestValidateIngressMTLS_PassesOnValidInput(t *testing.T) { t.Parallel() tests := []struct { ing *v1.IngressMTLS @@ -827,7 +820,7 @@ func TestValidateIngressMTLS(t *testing.T) { } } -func TestValidateIngressMTLSInvalid(t *testing.T) { +func TestValidateIngressMTLS_FailsOnInvalidInput(t *testing.T) { t.Parallel() tests := []struct { ing *v1.IngressMTLS @@ -869,7 +862,7 @@ func TestValidateIngressMTLSInvalid(t *testing.T) { } } -func TestValidateIngressMTLSVerifyClient(t *testing.T) { +func TestValidateIngressMTLSVerifyClient_PassesOnValidInput(t *testing.T) { t.Parallel() validInput := []string{"on", "off", "optional", "optional_no_ca"} @@ -879,7 +872,10 @@ func TestValidateIngressMTLSVerifyClient(t *testing.T) { t.Errorf("validateIngressMTLSVerifyClient(%q) returned errors %v for valid input", allErrs, test) } } +} +func TestValidateIngressMTLSVerifyClient_FailsOnInvalidInput(t *testing.T) { + t.Parallel() invalidInput := []string{"true", "false"} for _, test := range invalidInput { @@ -890,7 +886,7 @@ func TestValidateIngressMTLSVerifyClient(t *testing.T) { } } -func TestValidateEgressMTLS(t *testing.T) { +func TestValidateEgressMTLS_PassesOnValidInput(t *testing.T) { t.Parallel() tests := []struct { eg *v1.EgressMTLS @@ -932,7 +928,7 @@ func TestValidateEgressMTLS(t *testing.T) { } } -func TestValidateEgressMTLSInvalid(t *testing.T) { +func TestValidateEgressMTLS_FailsOnInvalidInput(t *testing.T) { t.Parallel() tests := []struct { eg *v1.EgressMTLS @@ -974,7 +970,7 @@ func TestValidateEgressMTLSInvalid(t *testing.T) { } } -func TestValidateOIDCValid(t *testing.T) { +func TestValidateOIDC_PassesOnValidOIDC(t *testing.T) { t.Parallel() tests := []struct { oidc *v1.OIDC @@ -1033,6 +1029,18 @@ func TestValidateOIDCValid(t *testing.T) { }, msg: "ip address", }, + { + oidc: &v1.OIDC{ + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + 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: "client", + ClientSecret: "secret", + Scope: "openid+offline_access", + AccessTokenEnable: true, + }, + msg: "offline access scope", + }, } for _, test := range tests { @@ -1043,7 +1051,45 @@ func TestValidateOIDCValid(t *testing.T) { } } -func TestValidateOIDCInvalid(t *testing.T) { +func TestValidateOIDCScope_ErrorsOnInvalidInput(t *testing.T) { + t.Parallel() + + invalidInput := []string{ + "", + " ", + "openid+scope\x5c", + "mycustom\x7fscope", + "openid+myscope\x20", + "openid+cus\x19tom", + } + + for _, v := range invalidInput { + allErrs := validateOIDCScope(v, field.NewPath("scope")) + if len(allErrs) == 0 { + t.Error("want err on invalid scope, got no error") + } + } +} + +func TestValidateOIDCScope_PassesOnValidInput(t *testing.T) { + t.Parallel() + + validInput := []string{ + "openid", + "validScope+openid", + "SecondScope+openid+CustomScope", + "validScope\x26+openid", + "openid+my\x33scope", + } + for _, v := range validInput { + allErrs := validateOIDCScope(v, field.NewPath("scope")) + if len(allErrs) != 0 { + t.Errorf("want no err, got %v", allErrs) + } + } +} + +func TestValidateOIDC_FailsOnInvalidOIDC(t *testing.T) { t.Parallel() tests := []struct { oidc *v1.OIDC @@ -1055,6 +1101,30 @@ func TestValidateOIDCInvalid(t *testing.T) { }, msg: "missing required field auth", }, + { + oidc: &v1.OIDC{ + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + 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: "client", + ClientSecret: "secret", + Scope: "bogus", + AccessTokenEnable: true, + }, + msg: "missing openid in scope", + }, + { + oidc: &v1.OIDC{ + AuthEndpoint: "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth", + 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: "client", + ClientSecret: "secret", + Scope: "openid+bogus\x7f", + AccessTokenEnable: true, + }, + msg: "invalid unicode in scope", + }, { oidc: &v1.OIDC{ AuthEndpoint: "https://login.microsoftonline.com/dd-fff-eee-1234-9be/oauth2/v2.0/authorize", @@ -1185,6 +1255,7 @@ func TestValidatePortNumber_ErrorsOnInvalidPort(t *testing.T) { func TestValidateClientID(t *testing.T) { t.Parallel() + validInput := []string{"myid", "your.id", "id-sf-sjfdj.com", "foo_bar~vni"} for _, test := range validInput { @@ -1193,7 +1264,10 @@ func TestValidateClientID(t *testing.T) { t.Errorf("validateClientID(%q) returned errors %v for valid input", allErrs, test) } } +} +func TestValidateClientID_FailsOnInvalidInput(t *testing.T) { + t.Parallel() invalidInput := []string{"$boo", "foo$bar", `foo_bar"vni`, `client\`} for _, test := range invalidInput { @@ -1204,29 +1278,9 @@ func TestValidateClientID(t *testing.T) { } } -func TestValidateOIDCScope(t *testing.T) { - t.Parallel() - validInput := []string{"openid", "openid+profile", "openid+email", "openid+phone"} - - for _, test := range validInput { - allErrs := validateOIDCScope(test, field.NewPath("scope")) - if len(allErrs) != 0 { - t.Errorf("validateOIDCScope(%q) returned errors %v for valid input", allErrs, test) - } - } - - invalidInput := []string{"profile", "openid+web", `openid+foobar.com`} - - for _, test := range invalidInput { - allErrs := validateOIDCScope(test, field.NewPath("scope")) - if len(allErrs) == 0 { - t.Errorf("validateOIDCScope(%q) didn't return error for invalid input", test) - } - } -} - func TestValidateURL_PassesOnValidInput(t *testing.T) { t.Parallel() + validInput := []string{ "http://google.com/auth", "https://foo.bar/baz", @@ -1242,7 +1296,7 @@ func TestValidateURL_PassesOnValidInput(t *testing.T) { } } -func TestValidateURL_ErrorsOnInvalidInput(t *testing.T) { +func TestValidateURL_FailsOnInvalidInput(t *testing.T) { t.Parallel() invalidInput := []string{ @@ -1264,8 +1318,9 @@ func TestValidateURL_ErrorsOnInvalidInput(t *testing.T) { } } -func TestValidateQueryStringt(t *testing.T) { +func TestValidateQueryString_PassesOnValidInput(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 { @@ -1274,6 +1329,10 @@ func TestValidateQueryStringt(t *testing.T) { t.Errorf("validateQueryString(%q) returned errors %v for valid input", allErrs, test) } } +} + +func TestValidateQueryString_FailsOnInvalidInput(t *testing.T) { + t.Parallel() invalidInput := []string{"foo=bar;baz"} @@ -1285,7 +1344,7 @@ func TestValidateQueryStringt(t *testing.T) { } } -func TestValidateWAF(t *testing.T) { +func TestValidateWAF_PassesOnValidInput(t *testing.T) { t.Parallel() tests := []struct { waf *v1.WAF @@ -1425,3 +1484,21 @@ func TestValidateWAF_FailsOnInvalidApPolicy(t *testing.T) { } } } + +func TestValidateBasic_PassesOnNotEmptySecret(t *testing.T) { + t.Parallel() + + errList := validateBasic(&v1.BasicAuth{Realm: "", Secret: "secret"}, field.NewPath("secret")) + if len(errList) != 0 { + t.Errorf("want no errors, got %v", errList) + } +} + +func TestValidateBasic_FailsOnMissingSecret(t *testing.T) { + t.Parallel() + + errList := validateBasic(&v1.BasicAuth{Realm: "realm", Secret: ""}, field.NewPath("secret")) + if len(errList) == 0 { + t.Error("want error on invalid input") + } +}