Fix golangci-lint errors (#10196)
* Fix golangci-lint errors Signed-off-by: z1cheng <imchench@gmail.com> * Fix dupl errors Signed-off-by: z1cheng <imchench@gmail.com> * Fix comments Signed-off-by: z1cheng <imchench@gmail.com> * Fix errcheck lint errors Signed-off-by: z1cheng <imchench@gmail.com> * Fix assert in e2e test Signed-off-by: z1cheng <imchench@gmail.com> * Not interrupt the waitForPodsReady Signed-off-by: z1cheng <imchench@gmail.com> * Replace string with constant Signed-off-by: z1cheng <imchench@gmail.com> * Fix comments Signed-off-by: z1cheng <imchench@gmail.com> * Revert write file permision Signed-off-by: z1cheng <imchench@gmail.com> --------- Signed-off-by: z1cheng <imchench@gmail.com>
This commit is contained in:
parent
46d87d3462
commit
b3060bfbd0
253 changed files with 2434 additions and 2113 deletions
|
|
@ -72,7 +72,7 @@ func (a alias) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
aliases := sets.NewString()
|
||||
for _, alias := range strings.Split(val, ",") {
|
||||
alias = strings.TrimSpace(alias)
|
||||
if len(alias) == 0 {
|
||||
if alias == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,9 +65,9 @@ func TestParse(t *testing.T) {
|
|||
if testCase.skipValidation {
|
||||
parser.EnableAnnotationValidation = false
|
||||
}
|
||||
defer func() {
|
||||
t.Cleanup(func() {
|
||||
parser.EnableAnnotationValidation = true
|
||||
}()
|
||||
})
|
||||
result, err := ap.Parse(ing)
|
||||
if (err != nil) != testCase.wantErr {
|
||||
t.Errorf("ParseAliasAnnotation() annotation: %s, error = %v, wantErr %v", testCase.annotations, err, testCase.wantErr)
|
||||
|
|
|
|||
|
|
@ -86,37 +86,36 @@ type Ingress struct {
|
|||
CorsConfig cors.Config
|
||||
CustomHTTPErrors []int
|
||||
DefaultBackend *apiv1.Service
|
||||
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
|
||||
FastCGI fastcgi.Config
|
||||
Denied *string
|
||||
ExternalAuth authreq.Config
|
||||
EnableGlobalAuth bool
|
||||
HTTP2PushPreload bool
|
||||
Opentracing opentracing.Config
|
||||
Opentelemetry opentelemetry.Config
|
||||
Proxy proxy.Config
|
||||
ProxySSL proxyssl.Config
|
||||
RateLimit ratelimit.Config
|
||||
GlobalRateLimit globalratelimit.Config
|
||||
Redirect redirect.Config
|
||||
Rewrite rewrite.Config
|
||||
Satisfy string
|
||||
ServerSnippet string
|
||||
ServiceUpstream bool
|
||||
SessionAffinity sessionaffinity.Config
|
||||
SSLPassthrough bool
|
||||
UsePortInRedirects bool
|
||||
UpstreamHashBy upstreamhashby.Config
|
||||
LoadBalancing string
|
||||
UpstreamVhost string
|
||||
Denylist ipdenylist.SourceRange
|
||||
XForwardedPrefix string
|
||||
SSLCipher sslcipher.Config
|
||||
Logs log.Config
|
||||
ModSecurity modsecurity.Config
|
||||
Mirror mirror.Config
|
||||
StreamSnippet string
|
||||
Allowlist ipallowlist.SourceRange
|
||||
FastCGI fastcgi.Config
|
||||
Denied *string
|
||||
ExternalAuth authreq.Config
|
||||
EnableGlobalAuth bool
|
||||
HTTP2PushPreload bool
|
||||
Opentracing opentracing.Config
|
||||
Opentelemetry opentelemetry.Config
|
||||
Proxy proxy.Config
|
||||
ProxySSL proxyssl.Config
|
||||
RateLimit ratelimit.Config
|
||||
GlobalRateLimit globalratelimit.Config
|
||||
Redirect redirect.Config
|
||||
Rewrite rewrite.Config
|
||||
Satisfy string
|
||||
ServerSnippet string
|
||||
ServiceUpstream bool
|
||||
SessionAffinity sessionaffinity.Config
|
||||
SSLPassthrough bool
|
||||
UsePortInRedirects bool
|
||||
UpstreamHashBy upstreamhashby.Config
|
||||
LoadBalancing string
|
||||
UpstreamVhost string
|
||||
Denylist ipdenylist.SourceRange
|
||||
XForwardedPrefix string
|
||||
SSLCipher sslcipher.Config
|
||||
Logs log.Config
|
||||
ModSecurity modsecurity.Config
|
||||
Mirror mirror.Config
|
||||
StreamSnippet string
|
||||
Allowlist ipallowlist.SourceRange
|
||||
}
|
||||
|
||||
// Extractor defines the annotation parsers to be used in the extraction of annotations
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ func (m mockCfg) GetService(name string) (*apiv1.Service, error) {
|
|||
}
|
||||
|
||||
func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
|
||||
if secret, _ := m.GetSecret(name); secret != nil {
|
||||
secret, err := m.GetSecret(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret != nil {
|
||||
return &resolver.AuthSSLCert{
|
||||
Secret: name,
|
||||
CAFileName: "/opt/ca.pem",
|
||||
|
|
@ -270,9 +274,9 @@ func TestCors(t *testing.T) {
|
|||
if r.CorsAllowCredentials != foo.credentials {
|
||||
t.Errorf("Returned %v but expected %v for Cors Credentials", r.CorsAllowCredentials, foo.credentials)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomHTTPErrors(t *testing.T) {
|
||||
ec := NewAnnotationExtractor(mockCfg{})
|
||||
ing := buildIngress()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ var (
|
|||
)
|
||||
|
||||
var AuthSecretConfig = parser.AnnotationConfig{
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
|
||||
Documentation: `This annotation defines the name of the Secret that contains the usernames and passwords which are granted access to the paths defined in the Ingress rules. `,
|
||||
|
|
@ -61,20 +61,20 @@ var authSecretAnnotations = parser.Annotation{
|
|||
Annotations: parser.AnnotationFields{
|
||||
AuthSecretAnnotation: AuthSecretConfig,
|
||||
authSecretTypeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*authSecretTypeRegex, true),
|
||||
Validator: parser.ValidateRegex(authSecretTypeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation what is the format of auth-secret value. Can be "auth-file" that defines the content of an htpasswd file, or "auth-map" where each key
|
||||
is a user and each value is the password.`,
|
||||
},
|
||||
authRealmAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.CharsWithSpace, false),
|
||||
Validator: parser.ValidateRegex(parser.CharsWithSpace, false),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
|
||||
Documentation: `This annotation defines the realm (message) that should be shown to user when authentication is requested.`,
|
||||
},
|
||||
authTypeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*authTypeRegex, true),
|
||||
Validator: parser.ValidateRegex(authTypeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation defines the basic authentication type. Should be "basic" or "digest"`,
|
||||
|
|
@ -167,14 +167,14 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
|
||||
s, err := parser.GetStringAnnotation(AuthSecretAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return nil, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("error reading secret name from annotation: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
sns, sname, err := cache.SplitMetaNamespaceKey(s)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return nil, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("error reading secret name from annotation: %w", err),
|
||||
}
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
secCfg := a.r.GetSecurityConfiguration()
|
||||
// We don't accept different namespaces for secrets.
|
||||
if !secCfg.AllowCrossNamespaceResources && sns != ing.Namespace {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return nil, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
|
||||
}
|
||||
}
|
||||
|
|
@ -193,7 +193,7 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
name := fmt.Sprintf("%v/%v", sns, sname)
|
||||
secret, err := a.r.GetSecret(name)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return nil, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("unexpected error reading secret %s: %w", name, err),
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return nil, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("invalid auth-secret-type in annotation, must be 'auth-file' or 'auth-map': %w", err),
|
||||
}
|
||||
}
|
||||
|
|
@ -238,14 +238,14 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
func dumpSecretAuthFile(filename string, secret *api.Secret) error {
|
||||
val, ok := secret.Data["auth"]
|
||||
if !ok {
|
||||
return ing_errors.LocationDenied{
|
||||
return ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("the secret %s does not contain a key with value auth", secret.Name),
|
||||
}
|
||||
}
|
||||
|
||||
err := os.WriteFile(filename, val, file.ReadWriteByUser)
|
||||
if err != nil {
|
||||
return ing_errors.LocationDenied{
|
||||
return ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("unexpected error creating password file: %w", err),
|
||||
}
|
||||
}
|
||||
|
|
@ -264,7 +264,7 @@ func dumpSecretAuthMap(filename string, secret *api.Secret) error {
|
|||
|
||||
err := os.WriteFile(filename, []byte(builder.String()), file.ReadWriteByUser)
|
||||
if err != nil {
|
||||
return ing_errors.LocationDenied{
|
||||
return ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("unexpected error creating password file: %w", err),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
//nolint:gosec // Ignore hardcoded credentials error in testing
|
||||
const (
|
||||
authType = "basic"
|
||||
authRealm = "-realm-"
|
||||
defaultDemoSecret = "default/demo-secret"
|
||||
othernsDemoSecret = "otherns/demo-secret"
|
||||
demoSecret = "demo-secret"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
|
|
@ -80,7 +89,7 @@ type mockSecret struct {
|
|||
}
|
||||
|
||||
func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
|
||||
if name != "default/demo-secret" && name != "otherns/demo-secret" {
|
||||
if name != defaultDemoSecret && name != othernsDemoSecret {
|
||||
return nil, fmt.Errorf("there is no secret with name %v", name)
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +101,7 @@ func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
|
|||
return &api.Secret{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
Name: "demo-secret",
|
||||
Name: demoSecret,
|
||||
},
|
||||
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
|
||||
}, nil
|
||||
|
|
@ -129,9 +138,9 @@ func TestIngressInvalidRealm(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "something weird ; location trying to { break }"
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
|
|
@ -148,14 +157,14 @@ func TestIngressInvalidDifferentNamespace(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "otherns/demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = othernsDemoSecret
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
expected := ing_errors.LocationDenied{
|
||||
expected := ing_errors.LocationDeniedError{
|
||||
Reason: errors.New("cross namespace usage of secrets is not allowed"),
|
||||
}
|
||||
_, err := NewParser(dir, &mockSecret{}).Parse(ing)
|
||||
|
|
@ -168,8 +177,8 @@ func TestIngressInvalidDifferentNamespaceAllowed(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "otherns/demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = othernsDemoSecret
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
|
|
@ -187,14 +196,14 @@ func TestIngressInvalidSecretName(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret;xpto"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
expected := ing_errors.LocationDenied{
|
||||
expected := ing_errors.LocationDeniedError{
|
||||
Reason: errors.New("error reading secret name from annotation: annotation nginx.ingress.kubernetes.io/auth-secret contains invalid value"),
|
||||
}
|
||||
_, err := NewParser(dir, &mockSecret{}).Parse(ing)
|
||||
|
|
@ -207,13 +216,13 @@ func TestInvalidIngressAuthNoSecret(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
expected := ing_errors.LocationDenied{
|
||||
expected := ing_errors.LocationDeniedError{
|
||||
Reason: errors.New("error reading secret name from annotation: ingress rule without annotations"),
|
||||
}
|
||||
_, err := NewParser(dir, &mockSecret{}).Parse(ing)
|
||||
|
|
@ -226,9 +235,9 @@ func TestIngressAuth(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
|
||||
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
|
|
@ -242,10 +251,10 @@ func TestIngressAuth(t *testing.T) {
|
|||
if !ok {
|
||||
t.Errorf("expected a BasicDigest type")
|
||||
}
|
||||
if auth.Type != "basic" {
|
||||
if auth.Type != authType {
|
||||
t.Errorf("Expected basic as auth type but returned %s", auth.Type)
|
||||
}
|
||||
if auth.Realm != "-realm-" {
|
||||
if auth.Realm != authRealm {
|
||||
t.Errorf("Expected -realm- as realm but returned %s", auth.Realm)
|
||||
}
|
||||
if !auth.Secured {
|
||||
|
|
@ -257,9 +266,9 @@ func TestIngressAuthWithoutSecret(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "invalid-secret"
|
||||
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-"
|
||||
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
|
|
@ -275,10 +284,10 @@ func TestIngressAuthInvalidSecretKey(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
|
||||
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
|
||||
data[parser.GetAnnotationWithPrefix(authSecretTypeAnnotation)] = "invalid-type"
|
||||
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-"
|
||||
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
|
|
@ -290,7 +299,7 @@ func TestIngressAuthInvalidSecretKey(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
||||
func dummySecretContent(t *testing.T) (fileName, dir string, s *api.Secret) {
|
||||
dir, err := os.MkdirTemp("", fmt.Sprintf("%v", time.Now().Unix()))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
@ -301,7 +310,10 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
|||
t.Error(err)
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
s, _ := mockSecret{}.GetSecret("default/demo-secret")
|
||||
s, err = mockSecret{}.GetSecret(defaultDemoSecret)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
return tmpfile.Name(), dir, s
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,25 +57,25 @@ var authReqAnnotations = parser.Annotation{
|
|||
Group: "authentication",
|
||||
Annotations: parser.AnnotationFields{
|
||||
authReqURLAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLWithNginxVariableRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLWithNginxVariableRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskHigh,
|
||||
Documentation: `This annotation allows to indicate the URL where the HTTP request should be sent`,
|
||||
},
|
||||
authReqMethodAnnotation: {
|
||||
Validator: parser.ValidateRegex(*methodsRegex, true),
|
||||
Validator: parser.ValidateRegex(methodsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation allows to specify the HTTP method to use`,
|
||||
},
|
||||
authReqSigninAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLWithNginxVariableRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLWithNginxVariableRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskHigh,
|
||||
Documentation: `This annotation allows to specify the location of the error page`,
|
||||
},
|
||||
authReqSigninRedirParamAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation allows to specify the URL parameter in the error page which should contain the original URL for a failed signin request`,
|
||||
|
|
@ -87,7 +87,7 @@ var authReqAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation allows to specify a custom snippet to use with external authentication`,
|
||||
},
|
||||
authReqCacheKeyAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.NGINXVariable, true),
|
||||
Validator: parser.ValidateRegex(parser.NGINXVariable, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation enables caching for auth requests.`,
|
||||
|
|
@ -117,26 +117,26 @@ var authReqAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation specifies a duration in seconds which an idle keepalive connection to an upstream server will stay open`,
|
||||
},
|
||||
authReqCacheDuration: {
|
||||
Validator: parser.ValidateRegex(*parser.ExtendedCharsRegex, false),
|
||||
Validator: parser.ValidateRegex(parser.ExtendedCharsRegex, false),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation allows to specify a caching time for auth responses based on their response codes, e.g. 200 202 30m`,
|
||||
},
|
||||
authReqResponseHeadersAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.HeadersVariable, true),
|
||||
Validator: parser.ValidateRegex(parser.HeadersVariable, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation sets the headers to pass to backend once authentication request completes. They should be separated by comma.`,
|
||||
},
|
||||
authReqProxySetHeadersAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation sets the name of a ConfigMap that specifies headers to pass to the authentication service.
|
||||
Only ConfigMaps on the same namespace are allowed`,
|
||||
},
|
||||
authReqRequestRedirectAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation allows to specify the X-Auth-Request-Redirect header value`,
|
||||
|
|
@ -249,8 +249,8 @@ func (e1 *Config) Equal(e2 *Config) bool {
|
|||
var (
|
||||
methodsRegex = regexp.MustCompile("(GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS|TRACE)")
|
||||
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
|
||||
statusCodeRegex = regexp.MustCompile(`^[\d]{3}$`)
|
||||
durationRegex = regexp.MustCompile(`^[\d]+(ms|s|m|h|d|w|M|y)$`) // see http://nginx.org/en/docs/syntax.html
|
||||
statusCodeRegex = regexp.MustCompile(`^\d{3}$`)
|
||||
durationRegex = regexp.MustCompile(`^\d+(ms|s|m|h|d|w|M|y)$`) // see http://nginx.org/en/docs/syntax.html
|
||||
)
|
||||
|
||||
// ValidMethod checks is the provided string a valid HTTP method
|
||||
|
|
@ -273,7 +273,7 @@ func ValidCacheDuration(duration string) bool {
|
|||
seenDuration := false
|
||||
|
||||
for _, element := range elements {
|
||||
if len(element) == 0 {
|
||||
if element == "" {
|
||||
continue
|
||||
}
|
||||
if statusCodeRegex.MatchString(element) {
|
||||
|
|
@ -304,6 +304,8 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to use an Config URL as source for authentication
|
||||
//
|
||||
//nolint:gocyclo // Ignore function complexity error
|
||||
func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
// Required Parameters
|
||||
urlString, err := parser.GetStringAnnotation(authReqURLAnnotation, ing, a.annotationConfig.Annotations)
|
||||
|
|
@ -313,7 +315,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
|
||||
authURL, err := parser.StringToURL(urlString)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{Reason: fmt.Errorf("could not parse auth-url annotation: %v", err)}
|
||||
return nil, ing_errors.LocationDeniedError{Reason: fmt.Errorf("could not parse auth-url annotation: %v", err)}
|
||||
}
|
||||
|
||||
authMethod, err := parser.GetStringAnnotation(authReqMethodAnnotation, ing, a.annotationConfig.Annotations)
|
||||
|
|
@ -410,7 +412,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
if err != nil && ing_errors.IsValidationError(err) {
|
||||
return nil, ing_errors.NewLocationDenied("validation error")
|
||||
}
|
||||
if len(hstr) != 0 {
|
||||
if hstr != "" {
|
||||
harr := strings.Split(hstr, ",")
|
||||
for _, header := range harr {
|
||||
header = strings.TrimSpace(header)
|
||||
|
|
@ -430,7 +432,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
|
||||
cns, _, err := cache.SplitMetaNamespaceKey(proxySetHeaderMap)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return nil, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("error reading configmap name %s from annotation: %w", proxySetHeaderMap, err),
|
||||
}
|
||||
}
|
||||
|
|
@ -442,7 +444,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
secCfg := a.r.GetSecurityConfiguration()
|
||||
// We don't accept different namespaces for secrets.
|
||||
if !secCfg.AllowCrossNamespaceResources && cns != ing.Namespace {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return nil, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
|
||||
}
|
||||
}
|
||||
|
|
@ -499,7 +501,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
// It will always return at least one duration (the default duration)
|
||||
func ParseStringToCacheDurations(input string) ([]string, error) {
|
||||
authCacheDuration := []string{}
|
||||
if len(input) != 0 {
|
||||
if input != "" {
|
||||
arr := strings.Split(input, ",")
|
||||
for _, duration := range arr {
|
||||
duration = strings.TrimSpace(duration)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package authreq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
|
@ -113,7 +112,7 @@ func TestAnnotations(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("auth-url")] = test.url
|
||||
data[parser.GetAnnotationWithPrefix("auth-signin")] = test.signinURL
|
||||
data[parser.GetAnnotationWithPrefix("auth-signin-redirect-param")] = test.signinURLRedirectParam
|
||||
data[parser.GetAnnotationWithPrefix("auth-method")] = fmt.Sprintf("%v", test.method)
|
||||
data[parser.GetAnnotationWithPrefix("auth-method")] = test.method
|
||||
data[parser.GetAnnotationWithPrefix("auth-request-redirect")] = test.requestRedirect
|
||||
data[parser.GetAnnotationWithPrefix("auth-snippet")] = test.authSnippet
|
||||
data[parser.GetAnnotationWithPrefix("auth-cache-key")] = test.authCacheKey
|
||||
|
|
@ -331,7 +330,6 @@ func TestKeepaliveAnnotations(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParseStringToCacheDurations(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
title string
|
||||
duration string
|
||||
|
|
@ -346,7 +344,6 @@ func TestParseStringToCacheDurations(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
dur, err := ParseStringToCacheDurations(test.duration)
|
||||
if test.expErr {
|
||||
if err == nil {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to enable or disable global external authentication
|
||||
func (a authReqGlobal) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
|
||||
enableGlobalAuth, err := parser.GetBoolAnnotation(enableGlobalAuthAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
enableGlobalAuth = true
|
||||
|
|
|
|||
|
|
@ -77,7 +77,10 @@ func TestAnnotation(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("enable-global-auth")] = "false"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
u, ok := i.(bool)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
|
|||
|
|
@ -18,11 +18,10 @@ package authtls
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
networking "k8s.io/api/networking/v1"
|
||||
|
||||
"regexp"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
|
|
@ -45,20 +44,20 @@ var (
|
|||
regexChars = regexp.QuoteMeta(`()|=`)
|
||||
authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`)
|
||||
commonNameRegex = regexp.MustCompile(`^CN=[/\-.\_\~a-zA-Z0-9` + regexChars + `]*$`)
|
||||
redirectRegex = regexp.MustCompile(`^((https?://)?[A-Za-z0-9\-\.]*(:[0-9]+)?/[A-Za-z0-9\-\.]*)?$`)
|
||||
redirectRegex = regexp.MustCompile(`^((https?://)?[A-Za-z0-9\-.]*(:\d+)?/[A-Za-z0-9\-.]*)?$`)
|
||||
)
|
||||
|
||||
var authTLSAnnotations = parser.Annotation{
|
||||
Group: "authentication",
|
||||
Annotations: parser.AnnotationFields{
|
||||
annotationAuthTLSSecret: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
|
||||
Documentation: `This annotation defines the secret that contains the certificate chain of allowed certs`,
|
||||
},
|
||||
annotationAuthTLSVerifyClient: {
|
||||
Validator: parser.ValidateRegex(*authVerifyClientRegex, true),
|
||||
Validator: parser.ValidateRegex(authVerifyClientRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
|
||||
Documentation: `This annotation enables verification of client certificates. Can be "on", "off", "optional" or "optional_no_ca"`,
|
||||
|
|
@ -70,7 +69,7 @@ var authTLSAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation defines validation depth between the provided client certificate and the Certification Authority chain.`,
|
||||
},
|
||||
annotationAuthTLSErrorPage: {
|
||||
Validator: parser.ValidateRegex(*redirectRegex, true),
|
||||
Validator: parser.ValidateRegex(redirectRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskHigh,
|
||||
Documentation: `This annotation defines the URL/Page that user should be redirected in case of a Certificate Authentication Error`,
|
||||
|
|
@ -82,7 +81,7 @@ var authTLSAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation defines if the received certificates should be passed or not to the upstream server in the header "ssl-client-cert"`,
|
||||
},
|
||||
annotationAuthTLSMatchCN: {
|
||||
Validator: parser.ValidateRegex(*commonNameRegex, true),
|
||||
Validator: parser.ValidateRegex(commonNameRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskHigh,
|
||||
Documentation: `This annotation adds a sanity check for the CN of the client certificate that is sent over using a string / regex starting with "CN="`,
|
||||
|
|
@ -130,9 +129,9 @@ func (assl1 *Config) Equal(assl2 *Config) bool {
|
|||
}
|
||||
|
||||
// NewParser creates a new TLS authentication annotation parser
|
||||
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return authTLS{
|
||||
r: resolver,
|
||||
r: r,
|
||||
annotationConfig: authTLSAnnotations,
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +168,7 @@ func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
authCert, err := a.r.GetAuthCertificate(tlsauthsecret)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("error obtaining certificate: %w", err)
|
||||
return &Config{}, ing_errors.LocationDenied{Reason: e}
|
||||
return &Config{}, ing_errors.LocationDeniedError{Reason: e}
|
||||
}
|
||||
config.AuthSSLCert = *authCert
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDemoSecret = "default/demo-secret"
|
||||
off = "off"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
|
|
@ -77,23 +82,22 @@ type mockSecret struct {
|
|||
|
||||
// GetAuthCertificate from mockSecret mocks the GetAuthCertificate for authTLS
|
||||
func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
|
||||
if name != "default/demo-secret" {
|
||||
if name != defaultDemoSecret {
|
||||
return nil, errors.Errorf("there is no secret with name %v", name)
|
||||
}
|
||||
|
||||
return &resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
Secret: defaultDemoSecret,
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
CASHA: "abc",
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
data := map[string]string{}
|
||||
|
||||
data[parser.GetAnnotationWithPrefix(annotationAuthTLSSecret)] = "default/demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix(annotationAuthTLSSecret)] = defaultDemoSecret
|
||||
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
|
|
@ -108,7 +112,7 @@ func TestAnnotations(t *testing.T) {
|
|||
t.Errorf("expected *Config but got %v", u)
|
||||
}
|
||||
|
||||
secret, err := fakeSecret.GetAuthCertificate("default/demo-secret")
|
||||
secret, err := fakeSecret.GetAuthCertificate(defaultDemoSecret)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error getting secret %v", err)
|
||||
}
|
||||
|
|
@ -132,7 +136,7 @@ func TestAnnotations(t *testing.T) {
|
|||
t.Errorf("expected empty string, but got %v", u.MatchCN)
|
||||
}
|
||||
|
||||
data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyClient)] = "off"
|
||||
data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyClient)] = off
|
||||
data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyDepth)] = "2"
|
||||
data[parser.GetAnnotationWithPrefix(annotationAuthTLSErrorPage)] = "ok.com/error"
|
||||
data[parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstream)] = "true"
|
||||
|
|
@ -153,8 +157,8 @@ func TestAnnotations(t *testing.T) {
|
|||
if u.AuthSSLCert.Secret != secret.Secret {
|
||||
t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
|
||||
}
|
||||
if u.VerifyClient != "off" {
|
||||
t.Errorf("expected %v but got %v", "off", u.VerifyClient)
|
||||
if u.VerifyClient != off {
|
||||
t.Errorf("expected %v but got %v", off, u.VerifyClient)
|
||||
}
|
||||
if u.ValidationDepth != 2 {
|
||||
t.Errorf("expected %v but got %v", 2, u.ValidationDepth)
|
||||
|
|
@ -262,28 +266,21 @@ func TestInvalidAnnotations(t *testing.T) {
|
|||
if u.MatchCN != "" {
|
||||
t.Errorf("expected empty string but got %v", u.MatchCN)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEquals(t *testing.T) {
|
||||
cfg1 := &Config{}
|
||||
cfg2 := &Config{}
|
||||
|
||||
// Same config
|
||||
result := cfg1.Equal(cfg1)
|
||||
if result != true {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
|
||||
// compare nil
|
||||
result = cfg1.Equal(nil)
|
||||
result := cfg1.Equal(nil)
|
||||
if result != false {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
|
||||
// Different Certs
|
||||
sslCert1 := resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
Secret: defaultDemoSecret,
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
CASHA: "abc",
|
||||
}
|
||||
|
|
@ -302,7 +299,7 @@ func TestEquals(t *testing.T) {
|
|||
|
||||
// Different Verify Client
|
||||
cfg1.VerifyClient = "on"
|
||||
cfg2.VerifyClient = "off"
|
||||
cfg2.VerifyClient = off
|
||||
result = cfg1.Equal(cfg2)
|
||||
if result != false {
|
||||
t.Errorf("Expected false")
|
||||
|
|
|
|||
|
|
@ -25,9 +25,7 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
var (
|
||||
validProtocols = []string{"auto_http", "http", "https", "grpc", "grpcs", "fcgi"}
|
||||
)
|
||||
var validProtocols = []string{"auto_http", "http", "https", "grpc", "grpcs", "fcgi"}
|
||||
|
||||
const (
|
||||
http = "HTTP"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ func buildIngress() *networking.Ingress {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInvalidAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ func TestParseInvalidAnnotations(t *testing.T) {
|
|||
if !ok {
|
||||
t.Errorf("expected a string type")
|
||||
}
|
||||
if val != "HTTP" {
|
||||
if val != http {
|
||||
t.Errorf("expected HTTPS but %v returned", val)
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +73,7 @@ func TestParseInvalidAnnotations(t *testing.T) {
|
|||
if !ok {
|
||||
t.Errorf("expected a string type")
|
||||
}
|
||||
if val != "HTTP" {
|
||||
if val != http {
|
||||
t.Errorf("expected HTTPS but %v returned", val)
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ func TestParseInvalidAnnotations(t *testing.T) {
|
|||
if !ok {
|
||||
t.Errorf("expected a string type")
|
||||
}
|
||||
if val != "HTTP" {
|
||||
if val != http {
|
||||
t.Errorf("expected HTTPS but %v returned", val)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ var CanaryAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation The total weight of traffic. If unspecified, it defaults to 100`,
|
||||
},
|
||||
canaryByHeaderAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the header that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress.
|
||||
|
|
@ -65,7 +65,7 @@ var CanaryAnnotations = parser.Annotation{
|
|||
For any other value, the header will be ignored and the request compared against the other canary rules by precedence`,
|
||||
},
|
||||
canaryByHeaderValueAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the header value to match for notifying the Ingress to route the request to the service specified in the Canary Ingress.
|
||||
|
|
@ -74,7 +74,7 @@ var CanaryAnnotations = parser.Annotation{
|
|||
It doesn't have any effect if the 'canary-by-header' annotation is not defined`,
|
||||
},
|
||||
canaryByHeaderPatternAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.IsValidRegex, false),
|
||||
Validator: parser.ValidateRegex(parser.IsValidRegex, false),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation works the same way as canary-by-header-value except it does PCRE Regex matching.
|
||||
|
|
@ -82,7 +82,7 @@ var CanaryAnnotations = parser.Annotation{
|
|||
When the given Regex causes error during request processing, the request will be considered as not matching.`,
|
||||
},
|
||||
canaryByCookieAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the cookie that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress.
|
||||
|
|
@ -189,7 +189,7 @@ func (c canary) GetDocumentation() parser.AnnotationFields {
|
|||
return c.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a canary) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (c canary) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, CanaryAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package canary
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
|
|
@ -24,8 +25,6 @@ import (
|
|||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
|
||||
"strconv"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
|
|
@ -93,7 +92,6 @@ func TestCanaryInvalid(t *testing.T) {
|
|||
if val.Weight != 0 {
|
||||
t.Errorf("Expected %v but got %v", 0, val.Weight)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
|
|
@ -133,10 +131,9 @@ func TestAnnotations(t *testing.T) {
|
|||
}
|
||||
|
||||
continue
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("%v: expected nil but returned error %v", test.title, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%v: expected nil but returned error %v", test.title, err)
|
||||
}
|
||||
|
||||
canaryConfig, ok := i.(*Config)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ var clientBodyBufferSizeConfig = parser.Annotation{
|
|||
Group: "backend",
|
||||
Annotations: parser.AnnotationFields{
|
||||
clientBodyBufferSizeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.SizeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||
Documentation: `Sets buffer size for reading client request body per location.
|
||||
|
|
@ -65,7 +65,7 @@ func (cbbs clientBodyBufferSize) Parse(ing *networking.Ingress) (interface{}, er
|
|||
return parser.GetStringAnnotation(clientBodyBufferSizeAnnotation, ing, cbbs.annotationConfig.Annotations)
|
||||
}
|
||||
|
||||
func (a clientBodyBufferSize) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (cbbs clientBodyBufferSize) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(cbbs.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, clientBodyBufferSizeConfig.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ func TestParse(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
|
||||
result, _ := ap.Parse(ing)
|
||||
if result != testCase.expected {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
|
|
|
|||
|
|
@ -29,15 +29,13 @@ const (
|
|||
connectionProxyHeaderAnnotation = "connection-proxy-header"
|
||||
)
|
||||
|
||||
var (
|
||||
validConnectionHeaderValue = regexp.MustCompile(`^(close|keep-alive)$`)
|
||||
)
|
||||
var validConnectionHeaderValue = regexp.MustCompile(`^(close|keep-alive)$`)
|
||||
|
||||
var connectionHeadersAnnotations = parser.Annotation{
|
||||
Group: "backend",
|
||||
Annotations: parser.AnnotationFields{
|
||||
connectionProxyHeaderAnnotation: {
|
||||
Validator: parser.ValidateRegex(*validConnectionHeaderValue, true),
|
||||
Validator: parser.ValidateRegex(validConnectionHeaderValue, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation allows setting a specific value for "proxy_set_header Connection" directive. Right now it is restricted to "close" or "keep-alive"`,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,5 @@ func TestParse(t *testing.T) {
|
|||
if !p.Equal(testCase.expected) {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, p, testCase.annotations)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ var (
|
|||
// * Sets a group that can be (https?://)?*?.something.com:port?
|
||||
// * Allows this to be repeated as much as possible, and separated by comma
|
||||
// Otherwise it should be '*'
|
||||
corsOriginRegexValidator = regexp.MustCompile(`^((((https?://)?(\*\.)?[A-Za-z0-9\-\.]*(:[0-9]+)?,?)+)|\*)?$`)
|
||||
corsOriginRegexValidator = regexp.MustCompile(`^((((https?://)?(\*\.)?[A-Za-z0-9\-.]*(:\d+)?,?)+)|\*)?$`)
|
||||
// corsOriginRegex defines the regex for validation inside Parse
|
||||
corsOriginRegex = regexp.MustCompile(`^(https?://(\*\.)?[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`)
|
||||
corsOriginRegex = regexp.MustCompile(`^(https?://(\*\.)?[A-Za-z0-9\-.]*(:\d+)?|\*)?$`)
|
||||
// Method must contain valid methods list (PUT, GET, POST, BLA)
|
||||
// May contain or not spaces between each verb
|
||||
corsMethodsRegex = regexp.MustCompile(`^([A-Za-z]+,?\s?)+$`)
|
||||
|
|
@ -74,7 +74,7 @@ var corsAnnotation = parser.Annotation{
|
|||
Documentation: `This annotation enables Cross-Origin Resource Sharing (CORS) in an Ingress rule`,
|
||||
},
|
||||
corsAllowOriginAnnotation: {
|
||||
Validator: parser.ValidateRegex(*corsOriginRegexValidator, true),
|
||||
Validator: parser.ValidateRegex(corsOriginRegexValidator, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation controls what's the accepted Origin for CORS.
|
||||
|
|
@ -82,14 +82,14 @@ var corsAnnotation = parser.Annotation{
|
|||
It also supports single level wildcard subdomains and follows this format: http(s)://*.foo.bar, http(s)://*.bar.foo:8080 or http(s)://*.abc.bar.foo:9000`,
|
||||
},
|
||||
corsAllowHeadersAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.HeadersVariable, true),
|
||||
Validator: parser.ValidateRegex(parser.HeadersVariable, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation controls which headers are accepted.
|
||||
This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -`,
|
||||
},
|
||||
corsAllowMethodsAnnotation: {
|
||||
Validator: parser.ValidateRegex(*corsMethodsRegex, true),
|
||||
Validator: parser.ValidateRegex(corsMethodsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation controls which methods are accepted.
|
||||
|
|
@ -102,7 +102,7 @@ var corsAnnotation = parser.Annotation{
|
|||
Documentation: `This annotation controls if credentials can be passed during CORS operations.`,
|
||||
},
|
||||
corsExposeHeadersAnnotation: {
|
||||
Validator: parser.ValidateRegex(*corsExposeHeadersRegex, true),
|
||||
Validator: parser.ValidateRegex(corsExposeHeadersRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation controls which headers are exposed to response.
|
||||
|
|
@ -260,7 +260,7 @@ func (c cors) GetDocumentation() parser.AnnotationFields {
|
|||
return c.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a cors) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (c cors) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, corsAnnotation.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,16 +31,14 @@ const (
|
|||
customHTTPErrorsAnnotation = "custom-http-errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// We accept anything between 400 and 599, on a comma separated.
|
||||
arrayOfHTTPErrors = regexp.MustCompile(`^(?:[4,5][0-9][0-9],?)*$`)
|
||||
)
|
||||
// We accept anything between 400 and 599, on a comma separated.
|
||||
var arrayOfHTTPErrors = regexp.MustCompile(`^(?:[4,5]\d{2},?)*$`)
|
||||
|
||||
var customHTTPErrorsAnnotations = parser.Annotation{
|
||||
Group: "backend",
|
||||
Annotations: parser.AnnotationFields{
|
||||
customHTTPErrorsAnnotation: {
|
||||
Validator: parser.ValidateRegex(*arrayOfHTTPErrors, true),
|
||||
Validator: parser.ValidateRegex(arrayOfHTTPErrors, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `If a default backend annotation is specified on the ingress, the errors code specified on this annotation
|
||||
|
|
@ -72,7 +70,7 @@ func (e customhttperrors) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
}
|
||||
|
||||
cSplit := strings.Split(c, ",")
|
||||
var codes []int
|
||||
codes := make([]int, 0, len(cSplit))
|
||||
for _, i := range cSplit {
|
||||
num, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
|
|
@ -88,7 +86,7 @@ func (e customhttperrors) GetDocumentation() parser.AnnotationFields {
|
|||
return e.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a customhttperrors) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (e customhttperrors) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(e.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, customHTTPErrorsAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,14 +57,14 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|||
|
||||
// Parse parses the annotations contained in the ingress to use
|
||||
// a custom default backend
|
||||
func (db backend) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
s, err := parser.GetStringAnnotation(defaultBackendAnnotation, ing, db.annotationConfig.Annotations)
|
||||
func (b backend) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
s, err := parser.GetStringAnnotation(defaultBackendAnnotation, ing, b.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
|
||||
svc, err := db.r.GetService(name)
|
||||
svc, err := b.r.GetService(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error reading service %s: %w", name, err)
|
||||
}
|
||||
|
|
@ -72,11 +72,11 @@ func (db backend) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
return svc, nil
|
||||
}
|
||||
|
||||
func (db backend) GetDocumentation() parser.AnnotationFields {
|
||||
return db.annotationConfig.Annotations
|
||||
func (b backend) GetDocumentation() parser.AnnotationFields {
|
||||
return b.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a backend) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (b backend) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(b.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, defaultBackendAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,22 +35,20 @@ const (
|
|||
fastCGIParamsAnnotation = "fastcgi-params-configmap"
|
||||
)
|
||||
|
||||
var (
|
||||
// fast-cgi valid parameters is just a single file name (like index.php)
|
||||
regexValidIndexAnnotationAndKey = regexp.MustCompile(`^[A-Za-z0-9\.\-\_]+$`)
|
||||
)
|
||||
// fast-cgi valid parameters is just a single file name (like index.php)
|
||||
var regexValidIndexAnnotationAndKey = regexp.MustCompile(`^[A-Za-z0-9.\-\_]+$`)
|
||||
|
||||
var fastCGIAnnotations = parser.Annotation{
|
||||
Group: "fastcgi",
|
||||
Annotations: parser.AnnotationFields{
|
||||
fastCGIIndexAnnotation: {
|
||||
Validator: parser.ValidateRegex(*regexValidIndexAnnotationAndKey, true),
|
||||
Validator: parser.ValidateRegex(regexValidIndexAnnotationAndKey, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation can be used to specify an index file`,
|
||||
},
|
||||
fastCGIParamsAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation can be used to specify a ConfigMap containing the fastcgi parameters as a key/value.
|
||||
|
|
@ -98,7 +96,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to indicate the fastcgiConfig.
|
||||
func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
|
||||
fcgiConfig := Config{}
|
||||
|
||||
if ing.GetAnnotations() == nil {
|
||||
|
|
@ -125,7 +122,7 @@ func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
|
||||
cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
|
||||
if err != nil {
|
||||
return fcgiConfig, ing_errors.LocationDenied{
|
||||
return fcgiConfig, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("error reading configmap name from annotation: %w", err),
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +136,7 @@ func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
cm = fmt.Sprintf("%v/%v", ing.Namespace, cmn)
|
||||
cmap, err := a.r.GetConfigMap(cm)
|
||||
if err != nil {
|
||||
return fcgiConfig, ing_errors.LocationDenied{
|
||||
return fcgiConfig, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("unexpected error reading configmap %s: %w", cm, err),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,6 @@ func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) {
|
|||
|
||||
invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"}
|
||||
for _, configmap := range invalidConfigMapList {
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap
|
||||
ing.SetAnnotations(data)
|
||||
|
|
@ -239,11 +238,9 @@ func TestParseFastCGIParamsConfigMapAnnotationWithDifferentNS(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Errorf("Different namespace configmap should return an error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConfigEquality(t *testing.T) {
|
||||
|
||||
var nilConfig *Config
|
||||
|
||||
config := Config{
|
||||
|
|
@ -297,7 +294,6 @@ func TestConfigEquality(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_fastcgi_Parse(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
index string
|
||||
|
|
@ -378,7 +374,6 @@ func Test_fastcgi_Parse(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
"k8s.io/ingress-nginx/internal/net"
|
||||
|
|
@ -57,7 +56,7 @@ var globalRateLimitAnnotationConfig = parser.Annotation{
|
|||
Documentation: `Configures a time window (i.e 1m) that the limit is applied`,
|
||||
},
|
||||
globalRateLimitKeyAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.NGINXVariable, true),
|
||||
Validator: parser.ValidateRegex(parser.NGINXVariable, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskHigh,
|
||||
Documentation: `This annotation Configures a key for counting the samples. Defaults to $remote_addr.
|
||||
|
|
@ -123,23 +122,23 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
config := &Config{}
|
||||
|
||||
limit, err := parser.GetIntAnnotation(globalRateLimitAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && errors.IsInvalidContent(err) {
|
||||
if err != nil && ing_errors.IsInvalidContent(err) {
|
||||
return nil, err
|
||||
}
|
||||
rawWindowSize, err := parser.GetStringAnnotation(globalRateLimitWindowAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && errors.IsValidationError(err) {
|
||||
return config, ing_errors.LocationDenied{
|
||||
if err != nil && ing_errors.IsValidationError(err) {
|
||||
return config, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
if limit == 0 || len(rawWindowSize) == 0 {
|
||||
if limit == 0 || rawWindowSize == "" {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
windowSize, err := time.ParseDuration(rawWindowSize)
|
||||
if err != nil {
|
||||
return config, ing_errors.LocationDenied{
|
||||
return config, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err),
|
||||
}
|
||||
}
|
||||
|
|
@ -148,12 +147,12 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
if err != nil {
|
||||
klog.Warningf("invalid %s, defaulting to %s", globalRateLimitKeyAnnotation, defaultKey)
|
||||
}
|
||||
if len(key) == 0 {
|
||||
if key == "" {
|
||||
key = defaultKey
|
||||
}
|
||||
|
||||
rawIgnoredCIDRs, err := parser.GetStringAnnotation(globalRateLimitIgnoredCidrsAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil && errors.IsInvalidContent(err) {
|
||||
if err != nil && ing_errors.IsInvalidContent(err) {
|
||||
return nil, err
|
||||
}
|
||||
ignoredCIDRs, err := net.ParseCIDRs(rawIgnoredCIDRs)
|
||||
|
|
@ -161,7 +160,7 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
config.Namespace = strings.Replace(string(ing.UID), "-", "", -1)
|
||||
config.Namespace = strings.ReplaceAll(string(ing.UID), "-", "")
|
||||
config.Limit = limit
|
||||
config.WindowSize = int(windowSize.Seconds())
|
||||
config.Key = key
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const UID = "31285d47-b150-4dcf-bd6f-12c46d769f6e"
|
||||
const expectedUID = "31285d47b1504dcfbd6f12c46d769f6e"
|
||||
const (
|
||||
UID = "31285d47-b150-4dcf-bd6f-12c46d769f6e"
|
||||
expectedUID = "31285d47b1504dcfbd6f12c46d769f6e"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
|
|
@ -190,10 +192,19 @@ func TestGlobalRateLimiting(t *testing.T) {
|
|||
t.Errorf("expected error '%v' but got '%v'", testCase.expectedErr, actualErr)
|
||||
}
|
||||
|
||||
actualConfig := i.(*Config)
|
||||
actualConfig, ok := i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected Config type but got %T", i)
|
||||
}
|
||||
if !testCase.expectedConfig.Equal(actualConfig) {
|
||||
expectedJSON, _ := json.Marshal(testCase.expectedConfig)
|
||||
actualJSON, _ := json.Marshal(actualConfig)
|
||||
expectedJSON, err := json.Marshal(testCase.expectedConfig)
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal expected config: %v", err)
|
||||
}
|
||||
actualJSON, err := json.Marshal(actualConfig)
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal actual config: %v", err)
|
||||
}
|
||||
t.Errorf("%v: expected config '%s' but got '%s'", testCase.title, expectedJSON, actualJSON)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (h2pp http2PushPreload) GetDocumentation() parser.AnnotationFields {
|
|||
return h2pp.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a http2PushPreload) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (h2pp http2PushPreload) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(h2pp.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, http2PushPreloadAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,16 +96,15 @@ func (a ipallowlist) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
return &SourceRange{CIDR: defaultAllowlistSourceRange}, nil
|
||||
}
|
||||
|
||||
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDenied{
|
||||
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDeniedError{
|
||||
Reason: err,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
values := strings.Split(val, ",")
|
||||
ipnets, ips, err := net.ParseIPNets(values...)
|
||||
if err != nil && len(ips) == 0 {
|
||||
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDenied{
|
||||
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("the annotation does not contain a valid IP address or network: %w", err),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,16 +93,15 @@ func (a ipdenylist) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
return &SourceRange{CIDR: defaultDenylistSourceRange}, nil
|
||||
}
|
||||
|
||||
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDenied{
|
||||
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDeniedError{
|
||||
Reason: err,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
values := strings.Split(val, ",")
|
||||
ipnets, ips, err := net.ParseIPNets(values...)
|
||||
if err != nil && len(ips) == 0 {
|
||||
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDenied{
|
||||
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDeniedError{
|
||||
Reason: fmt.Errorf("the annotation does not contain a valid IP address or network: %w", err),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
|
||||
result, _ := ap.Parse(ing)
|
||||
if result != testCase.expected {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ func (l log) GetDocumentation() parser.AnnotationFields {
|
|||
return l.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a log) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (l log) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(l.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, logAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,10 @@ func TestIngressAccessLogConfig(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(enableAccessLogAnnotation)] = "false"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
log, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
log, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
nginxLogs, ok := log.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
@ -94,7 +97,10 @@ func TestIngressRewriteLogConfig(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "true"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
log, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
log, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing annotations %v", err)
|
||||
}
|
||||
nginxLogs, ok := log.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
@ -112,7 +118,10 @@ func TestInvalidBoolConfig(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "blo"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
log, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
log, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
nginxLogs, ok := log.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
|
|||
|
|
@ -34,15 +34,13 @@ const (
|
|||
mirrorHostAnnotation = "mirror-host"
|
||||
)
|
||||
|
||||
var (
|
||||
OnOffRegex = regexp.MustCompile(`^(on|off)$`)
|
||||
)
|
||||
var OnOffRegex = regexp.MustCompile(`^(on|off)$`)
|
||||
|
||||
var mirrorAnnotation = parser.Annotation{
|
||||
Group: "mirror",
|
||||
Annotations: parser.AnnotationFields{
|
||||
mirrorRequestBodyAnnotation: {
|
||||
Validator: parser.ValidateRegex(*OnOffRegex, true),
|
||||
Validator: parser.ValidateRegex(OnOffRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation defines if the request-body should be sent to the mirror backend. Can be 'on' or 'off'`,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
const (
|
||||
modsecEnableAnnotation = "enable-modsecurity"
|
||||
modsecEnableOwaspCoreAnnotation = "enable-owasp-core-rules"
|
||||
modesecTransactionIdAnnotation = "modsecurity-transaction-id"
|
||||
modesecTransactionIDAnnotation = "modsecurity-transaction-id"
|
||||
modsecSnippetAnnotation = "modsecurity-snippet"
|
||||
)
|
||||
|
||||
|
|
@ -46,8 +46,8 @@ var modsecurityAnnotation = parser.Annotation{
|
|||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation enables the OWASP Core Rule Set`,
|
||||
},
|
||||
modesecTransactionIdAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.NGINXVariable, true),
|
||||
modesecTransactionIDAnnotation: {
|
||||
Validator: parser.ValidateRegex(parser.NGINXVariable, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskHigh,
|
||||
Documentation: `This annotation enables passing an NGINX variable to ModSecurity.`,
|
||||
|
|
@ -98,9 +98,9 @@ func (modsec1 *Config) Equal(modsec2 *Config) bool {
|
|||
}
|
||||
|
||||
// NewParser creates a new ModSecurity annotation parser
|
||||
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return modSecurity{
|
||||
r: resolver,
|
||||
r: r,
|
||||
annotationConfig: modsecurityAnnotation,
|
||||
}
|
||||
}
|
||||
|
|
@ -134,10 +134,10 @@ func (a modSecurity) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
config.OWASPRules = false
|
||||
}
|
||||
|
||||
config.TransactionID, err = parser.GetStringAnnotation(modesecTransactionIdAnnotation, ing, a.annotationConfig.Annotations)
|
||||
config.TransactionID, err = parser.GetStringAnnotation(modesecTransactionIDAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsInvalidContent(err) {
|
||||
klog.Warningf("annotation %s contains invalid directive, defaulting", modesecTransactionIdAnnotation)
|
||||
klog.Warningf("annotation %s contains invalid directive, defaulting", modesecTransactionIDAnnotation)
|
||||
}
|
||||
config.TransactionID = ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,14 @@ func TestParse(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
result, _ := ap.Parse(ing)
|
||||
config := result.(*Config)
|
||||
result, err := ap.Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
config, ok := result.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("unexpected type: %T", result)
|
||||
}
|
||||
if !config.Equal(&testCase.expected) {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ var otelAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation enables or disables using spans from incoming requests as parent for created ones`,
|
||||
},
|
||||
otelOperationNameAnnotation: {
|
||||
Validator: parser.ValidateRegex(*regexOperationName, true),
|
||||
Validator: parser.ValidateRegex(regexOperationName, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines what operation name should be added to the span`,
|
||||
|
|
@ -75,7 +75,6 @@ type Config struct {
|
|||
|
||||
// Equal tests for equality between two Config types
|
||||
func (bd1 *Config) Equal(bd2 *Config) bool {
|
||||
|
||||
if bd1.Set != bd2.Set {
|
||||
return false
|
||||
}
|
||||
|
|
@ -150,7 +149,7 @@ func (c opentelemetry) GetDocumentation() parser.AnnotationFields {
|
|||
return c.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a opentelemetry) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (c opentelemetry) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, otelAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const enableAnnotation = "true"
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
|
|
@ -73,10 +75,13 @@ func TestIngressAnnotationOpentelemetrySetTrue(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "true"
|
||||
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = enableAnnotation
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
openTelemetry, ok := val.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
@ -103,7 +108,10 @@ func TestIngressAnnotationOpentelemetrySetFalse(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "false"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
openTelemetry, ok := val.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
@ -123,8 +131,8 @@ func TestIngressAnnotationOpentelemetryTrustSetTrue(t *testing.T) {
|
|||
|
||||
data := map[string]string{}
|
||||
opName := "foo-op"
|
||||
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "true"
|
||||
data[parser.GetAnnotationWithPrefix(otelTrustSpanAnnotation)] = "true"
|
||||
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = enableAnnotation
|
||||
data[parser.GetAnnotationWithPrefix(otelTrustSpanAnnotation)] = enableAnnotation
|
||||
data[parser.GetAnnotationWithPrefix(otelOperationNameAnnotation)] = opName
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
|
|
@ -163,7 +171,7 @@ func TestIngressAnnotationOpentelemetryWithBadOpName(t *testing.T) {
|
|||
|
||||
data := map[string]string{}
|
||||
opName := "fooxpto_123$la;"
|
||||
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "true"
|
||||
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = enableAnnotation
|
||||
data[parser.GetAnnotationWithPrefix(otelOperationNameAnnotation)] = opName
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
|
|
@ -180,7 +188,10 @@ func TestIngressAnnotationOpentelemetryUnset(t *testing.T) {
|
|||
data := map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
_, ok := val.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
|
|||
|
|
@ -89,13 +89,13 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|||
}
|
||||
}
|
||||
|
||||
func (s opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
enabled, err := parser.GetBoolAnnotation(enableOpentracingAnnotation, ing, s.annotationConfig.Annotations)
|
||||
func (o opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
enabled, err := parser.GetBoolAnnotation(enableOpentracingAnnotation, ing, o.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
return &Config{}, nil
|
||||
}
|
||||
|
||||
trustSpan, err := parser.GetBoolAnnotation(opentracingTrustSpanAnnotation, ing, s.annotationConfig.Annotations)
|
||||
trustSpan, err := parser.GetBoolAnnotation(opentracingTrustSpanAnnotation, ing, o.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
return &Config{Set: true, Enabled: enabled}, nil
|
||||
}
|
||||
|
|
@ -103,11 +103,11 @@ func (s opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
return &Config{Set: true, Enabled: enabled, TrustSet: true, TrustEnabled: trustSpan}, nil
|
||||
}
|
||||
|
||||
func (s opentracing) GetDocumentation() parser.AnnotationFields {
|
||||
return s.annotationConfig.Annotations
|
||||
func (o opentracing) GetDocumentation() parser.AnnotationFields {
|
||||
return o.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a opentracing) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (o opentracing) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(o.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, opentracingAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const enableAnnotation = "true"
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
|
|
@ -73,10 +75,13 @@ func TestIngressAnnotationOpentracingSetTrue(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "true"
|
||||
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = enableAnnotation
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
openTracing, ok := val.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
@ -95,7 +100,10 @@ func TestIngressAnnotationOpentracingSetFalse(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "false"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
openTracing, ok := val.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
@ -110,11 +118,14 @@ func TestIngressAnnotationOpentracingTrustSetTrue(t *testing.T) {
|
|||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "true"
|
||||
data[parser.GetAnnotationWithPrefix(opentracingTrustSpanAnnotation)] = "true"
|
||||
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = enableAnnotation
|
||||
data[parser.GetAnnotationWithPrefix(opentracingTrustSpanAnnotation)] = enableAnnotation
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
openTracing, ok := val.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
@ -136,7 +147,11 @@ func TestIngressAnnotationOpentracingUnset(t *testing.T) {
|
|||
data := map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
_, ok := val.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ func (a ingAnnotations) parseString(name string) (string, error) {
|
|||
val, ok := a[name]
|
||||
if ok {
|
||||
s := normalizeString(val)
|
||||
if len(s) == 0 {
|
||||
if s == "" {
|
||||
return "", errors.NewInvalidAnnotationContent(name, val)
|
||||
}
|
||||
|
||||
|
|
@ -248,13 +248,14 @@ func StringToURL(input string) (*url.URL, error) {
|
|||
return nil, fmt.Errorf("%v is not a valid URL: %v", input, err)
|
||||
}
|
||||
|
||||
if parsedURL.Scheme == "" {
|
||||
switch {
|
||||
case parsedURL.Scheme == "":
|
||||
return nil, fmt.Errorf("url scheme is empty")
|
||||
} else if parsedURL.Host == "" {
|
||||
case parsedURL.Host == "":
|
||||
return nil, fmt.Errorf("url host is empty")
|
||||
} else if strings.Contains(parsedURL.Host, "..") {
|
||||
case strings.Contains(parsedURL.Host, ".."):
|
||||
return nil, fmt.Errorf("invalid url host")
|
||||
default:
|
||||
return parsedURL, nil
|
||||
}
|
||||
|
||||
return parsedURL, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,14 +93,16 @@ func TestGetStringAnnotation(t *testing.T) {
|
|||
{"valid - A", "string", "A ", "A", false},
|
||||
{"valid - B", "string", " B", "B", false},
|
||||
{"empty", "string", " ", "", true},
|
||||
{"valid multiline", "string", `
|
||||
{
|
||||
"valid multiline", "string", `
|
||||
rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break;
|
||||
rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
|
||||
`, `
|
||||
rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break;
|
||||
rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
|
||||
`,
|
||||
false},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
|
|
@ -213,8 +215,10 @@ func TestGetIntAnnotation(t *testing.T) {
|
|||
|
||||
func TestStringToURL(t *testing.T) {
|
||||
validURL := "http://bar.foo.com/external-auth"
|
||||
validParsedURL, _ := url.Parse(validURL)
|
||||
|
||||
validParsedURL, err := url.Parse(validURL)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
tests := []struct {
|
||||
title string
|
||||
url string
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ var (
|
|||
var IsValidRegex = regexp.MustCompile("^[/" + alphaNumericChars + regexEnabledChars + "]*$")
|
||||
|
||||
// SizeRegex validates sizes understood by NGINX, like 1000, 100k, 1000M
|
||||
var SizeRegex = regexp.MustCompile("^(?i)[0-9]+[bkmg]?$")
|
||||
var SizeRegex = regexp.MustCompile(`^(?i)\d+[bkmg]?$`)
|
||||
|
||||
// URLRegex is used to validate a URL but with only a specific set of characters:
|
||||
// It is alphanumericChar + ":", "?", "&"
|
||||
|
|
@ -103,7 +103,7 @@ func ValidateServerName(value string) error {
|
|||
// ValidateRegex receives a regex as an argument and uses it to validate
|
||||
// the value of the field.
|
||||
// Annotation can define if the spaces should be trimmed before validating the value
|
||||
func ValidateRegex(regex regexp.Regexp, removeSpace bool) AnnotationValidator {
|
||||
func ValidateRegex(regex *regexp.Regexp, removeSpace bool) AnnotationValidator {
|
||||
return func(s string) error {
|
||||
if removeSpace {
|
||||
s = strings.ReplaceAll(s, " ", "")
|
||||
|
|
@ -117,7 +117,7 @@ func ValidateRegex(regex regexp.Regexp, removeSpace bool) AnnotationValidator {
|
|||
|
||||
// ValidateOptions receives an array of valid options that can be the value of annotation.
|
||||
// If no valid option is found, it will return an error
|
||||
func ValidateOptions(options []string, caseSensitive bool, trimSpace bool) AnnotationValidator {
|
||||
func ValidateOptions(options []string, caseSensitive, trimSpace bool) AnnotationValidator {
|
||||
return func(s string) error {
|
||||
if trimSpace {
|
||||
s = strings.TrimSpace(s)
|
||||
|
|
@ -161,7 +161,7 @@ func ValidateDuration(value string) error {
|
|||
// ValidateNull always return null values and should not be widely used.
|
||||
// It is used on the "snippet" annotations, as it is up to the admin to allow its
|
||||
// usage, knowing it can be critical!
|
||||
func ValidateNull(value string) error {
|
||||
func ValidateNull(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -223,7 +223,6 @@ func Test_checkAnnotation(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCheckAnnotationRisk(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
annotations map[string]string
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ const (
|
|||
proxyMaxTempFileSizeAnnotation = "proxy-max-temp-file-size"
|
||||
)
|
||||
|
||||
var (
|
||||
validUpstreamAnnotation = regexp.MustCompile(`^((error|timeout|invalid_header|http_500|http_502|http_503|http_504|http_403|http_404|http_429|non_idempotent|off)\s?)+$`)
|
||||
)
|
||||
var validUpstreamAnnotation = regexp.MustCompile(`^((error|timeout|invalid_header|http_500|http_502|http_503|http_504|http_403|http_404|http_429|non_idempotent|off)\s?)+$`)
|
||||
|
||||
var proxyAnnotations = parser.Annotation{
|
||||
Group: "backend",
|
||||
|
|
@ -78,32 +76,32 @@ var proxyAnnotations = parser.Annotation{
|
|||
By default proxy buffers number is set as 4`,
|
||||
},
|
||||
proxyBufferSizeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.SizeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation sets the size of the buffer proxy_buffer_size used for reading the first part of the response received from the proxied server.
|
||||
By default proxy buffer size is set as "4k".`,
|
||||
},
|
||||
proxyCookiePathAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation sets a text that should be changed in the path attribute of the "Set-Cookie" header fields of a proxied server response.`,
|
||||
},
|
||||
proxyCookieDomainAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation ets a text that should be changed in the domain attribute of the "Set-Cookie" header fields of a proxied server response.`,
|
||||
},
|
||||
proxyBodySizeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.SizeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation allows setting the maximum allowed size of a client request body.`,
|
||||
},
|
||||
proxyNextUpstreamAnnotation: {
|
||||
Validator: parser.ValidateRegex(*validUpstreamAnnotation, false),
|
||||
Validator: parser.ValidateRegex(validUpstreamAnnotation, false),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines when the next upstream should be used.
|
||||
|
|
@ -129,13 +127,13 @@ var proxyAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation enables or disables buffering of a client request body.`,
|
||||
},
|
||||
proxyRedirectFromAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `The annotations proxy-redirect-from and proxy-redirect-to will set the first and second parameters of NGINX's proxy_redirect directive respectively`,
|
||||
},
|
||||
proxyRedirectToAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `The annotations proxy-redirect-from and proxy-redirect-to will set the first and second parameters of NGINX's proxy_redirect directive respectively`,
|
||||
|
|
@ -153,7 +151,7 @@ var proxyAnnotations = parser.Annotation{
|
|||
Documentation: `This annotations sets the HTTP protocol version for proxying. Can be "1.0" or "1.1".`,
|
||||
},
|
||||
proxyMaxTempFileSizeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.SizeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation defines the maximum size of a temporary file when buffering responses.`,
|
||||
|
|
@ -253,7 +251,8 @@ type proxy struct {
|
|||
|
||||
// NewParser creates a new reverse proxy configuration annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return proxy{r: r,
|
||||
return proxy{
|
||||
r: r,
|
||||
annotationConfig: proxyAnnotations,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
off = "off"
|
||||
proxyHTTPVersion = "1.0"
|
||||
proxyMaxTempFileSize = "128k"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
|
|
@ -87,7 +93,7 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|||
ProxyNextUpstreamTimeout: 0,
|
||||
ProxyNextUpstreamTries: 3,
|
||||
ProxyRequestBuffering: "on",
|
||||
ProxyBuffering: "off",
|
||||
ProxyBuffering: off,
|
||||
ProxyHTTPVersion: "1.1",
|
||||
ProxyMaxTempFileSize: "1024m",
|
||||
}
|
||||
|
|
@ -103,13 +109,13 @@ func TestProxy(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("proxy-buffers-number")] = "8"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-buffer-size")] = "1k"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-body-size")] = "2k"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-next-upstream")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-next-upstream")] = off
|
||||
data[parser.GetAnnotationWithPrefix("proxy-next-upstream-timeout")] = "5"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-next-upstream-tries")] = "3"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = off
|
||||
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = proxyHTTPVersion
|
||||
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = proxyMaxTempFileSize
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||
|
|
@ -138,7 +144,7 @@ func TestProxy(t *testing.T) {
|
|||
if p.BodySize != "2k" {
|
||||
t.Errorf("expected 2k as body-size but returned %v", p.BodySize)
|
||||
}
|
||||
if p.NextUpstream != "off" {
|
||||
if p.NextUpstream != off {
|
||||
t.Errorf("expected off as next-upstream but returned %v", p.NextUpstream)
|
||||
}
|
||||
if p.NextUpstreamTimeout != 5 {
|
||||
|
|
@ -147,16 +153,16 @@ func TestProxy(t *testing.T) {
|
|||
if p.NextUpstreamTries != 3 {
|
||||
t.Errorf("expected 3 as next-upstream-tries but returned %v", p.NextUpstreamTries)
|
||||
}
|
||||
if p.RequestBuffering != "off" {
|
||||
if p.RequestBuffering != off {
|
||||
t.Errorf("expected off as request-buffering but returned %v", p.RequestBuffering)
|
||||
}
|
||||
if p.ProxyBuffering != "on" {
|
||||
t.Errorf("expected on as proxy-buffering but returned %v", p.ProxyBuffering)
|
||||
}
|
||||
if p.ProxyHTTPVersion != "1.0" {
|
||||
if p.ProxyHTTPVersion != proxyHTTPVersion {
|
||||
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
|
||||
}
|
||||
if p.ProxyMaxTempFileSize != "128k" {
|
||||
if p.ProxyMaxTempFileSize != proxyMaxTempFileSize {
|
||||
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
|
||||
}
|
||||
}
|
||||
|
|
@ -176,8 +182,8 @@ func TestProxyComplex(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("proxy-next-upstream-tries")] = "3"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = proxyHTTPVersion
|
||||
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = proxyMaxTempFileSize
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||
|
|
@ -221,10 +227,10 @@ func TestProxyComplex(t *testing.T) {
|
|||
if p.ProxyBuffering != "on" {
|
||||
t.Errorf("expected on as proxy-buffering but returned %v", p.ProxyBuffering)
|
||||
}
|
||||
if p.ProxyHTTPVersion != "1.0" {
|
||||
if p.ProxyHTTPVersion != proxyHTTPVersion {
|
||||
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
|
||||
}
|
||||
if p.ProxyMaxTempFileSize != "128k" {
|
||||
if p.ProxyMaxTempFileSize != proxyMaxTempFileSize {
|
||||
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import (
|
|||
|
||||
networking "k8s.io/api/networking/v1"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
"k8s.io/ingress-nginx/internal/k8s"
|
||||
|
|
@ -42,7 +41,7 @@ const (
|
|||
var (
|
||||
proxySSLOnOffRegex = regexp.MustCompile(`^(on|off)$`)
|
||||
proxySSLProtocolRegex = regexp.MustCompile(`^(SSLv2|SSLv3|TLSv1|TLSv1\.1|TLSv1\.2|TLSv1\.3| )*$`)
|
||||
proxySSLCiphersRegex = regexp.MustCompile(`^[A-Za-z0-9\+\:\_\-\!]*$`)
|
||||
proxySSLCiphersRegex = regexp.MustCompile(`^[A-Za-z0-9\+:\_\-!]*$`)
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -59,7 +58,7 @@ var proxySSLAnnotation = parser.Annotation{
|
|||
Group: "proxy",
|
||||
Annotations: parser.AnnotationFields{
|
||||
proxySSLSecretAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation specifies a Secret with the certificate tls.crt, key tls.key in PEM format used for authentication to a proxied HTTPS server.
|
||||
|
|
@ -68,14 +67,14 @@ var proxySSLAnnotation = parser.Annotation{
|
|||
Just secrets on the same namespace of the ingress can be used.`,
|
||||
},
|
||||
proxySSLCiphersAnnotation: {
|
||||
Validator: parser.ValidateRegex(*proxySSLCiphersRegex, true),
|
||||
Validator: parser.ValidateRegex(proxySSLCiphersRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation Specifies the enabled ciphers for requests to a proxied HTTPS server.
|
||||
The ciphers are specified in the format understood by the OpenSSL library.`,
|
||||
},
|
||||
proxySSLProtocolsAnnotation: {
|
||||
Validator: parser.ValidateRegex(*proxySSLProtocolRegex, true),
|
||||
Validator: parser.ValidateRegex(proxySSLProtocolRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation enables the specified protocols for requests to a proxied HTTPS server.`,
|
||||
|
|
@ -88,7 +87,7 @@ var proxySSLAnnotation = parser.Annotation{
|
|||
This value is also passed through SNI when a connection is established to the proxied HTTPS server.`,
|
||||
},
|
||||
proxySSLVerifyAnnotation: {
|
||||
Validator: parser.ValidateRegex(*proxySSLOnOffRegex, true),
|
||||
Validator: parser.ValidateRegex(proxySSLOnOffRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation enables or disables verification of the proxied HTTPS server certificate. (default: off)`,
|
||||
|
|
@ -100,7 +99,7 @@ var proxySSLAnnotation = parser.Annotation{
|
|||
Documentation: `This annotation Sets the verification depth in the proxied HTTPS server certificates chain. (default: 1).`,
|
||||
},
|
||||
proxySSLServerNameAnnotation: {
|
||||
Validator: parser.ValidateRegex(*proxySSLOnOffRegex, true),
|
||||
Validator: parser.ValidateRegex(proxySSLOnOffRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation enables passing of the server name through TLS Server Name Indication extension (SNI, RFC 6066) when establishing a connection with the proxied HTTPS server.`,
|
||||
|
|
@ -150,10 +149,11 @@ func (pssl1 *Config) Equal(pssl2 *Config) bool {
|
|||
}
|
||||
|
||||
// NewParser creates a new TLS authentication annotation parser
|
||||
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return proxySSL{
|
||||
r: resolver,
|
||||
annotationConfig: proxySSLAnnotation}
|
||||
r: r,
|
||||
annotationConfig: proxySSLAnnotation,
|
||||
}
|
||||
}
|
||||
|
||||
type proxySSL struct {
|
||||
|
|
@ -208,13 +208,13 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
proxyCert, err := p.r.GetAuthCertificate(proxysslsecret)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("error obtaining certificate: %w", err)
|
||||
return &Config{}, ing_errors.LocationDenied{Reason: e}
|
||||
return &Config{}, ing_errors.LocationDeniedError{Reason: e}
|
||||
}
|
||||
config.AuthSSLCert = *proxyCert
|
||||
|
||||
config.Ciphers, err = parser.GetStringAnnotation(proxySSLCiphersAnnotation, ing, p.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
if ing_errors.IsValidationError(err) {
|
||||
klog.Warningf("invalid value passed to proxy-ssl-ciphers, defaulting to %s", defaultProxySSLCiphers)
|
||||
}
|
||||
config.Ciphers = defaultProxySSLCiphers
|
||||
|
|
@ -222,7 +222,7 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
|
||||
config.Protocols, err = parser.GetStringAnnotation(proxySSLProtocolsAnnotation, ing, p.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
if ing_errors.IsValidationError(err) {
|
||||
klog.Warningf("invalid value passed to proxy-ssl-protocols, defaulting to %s", defaultProxySSLProtocols)
|
||||
}
|
||||
config.Protocols = defaultProxySSLProtocols
|
||||
|
|
@ -232,7 +232,7 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
|
||||
config.ProxySSLName, err = parser.GetStringAnnotation(proxySSLNameAnnotation, ing, p.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if errors.IsValidationError(err) {
|
||||
if ing_errors.IsValidationError(err) {
|
||||
klog.Warningf("invalid value passed to proxy-ssl-name, defaulting to empty")
|
||||
}
|
||||
config.ProxySSLName = ""
|
||||
|
|
@ -260,7 +260,7 @@ func (p proxySSL) GetDocumentation() parser.AnnotationFields {
|
|||
return p.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a proxySSL) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (p proxySSL) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(p.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, proxySSLAnnotation.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ import (
|
|||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDemoSecret = "default/demo-secret"
|
||||
proxySslCiphers = "HIGH:-SHA"
|
||||
off = "off"
|
||||
sslServerName = "w00t"
|
||||
defaultProtocol = "SSLv2 TLSv1 TLSv1.2 TLSv1.3"
|
||||
)
|
||||
|
||||
func buildIngress() *networking.Ingress {
|
||||
defaultBackend := networking.IngressBackend{
|
||||
Service: &networking.IngressServiceBackend{
|
||||
|
|
@ -77,28 +85,27 @@ type mockSecret struct {
|
|||
|
||||
// GetAuthCertificate from mockSecret mocks the GetAuthCertificate for backend certificate authentication
|
||||
func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
|
||||
if name != "default/demo-secret" {
|
||||
if name != defaultDemoSecret {
|
||||
return nil, errors.Errorf("there is no secret with name %v", name)
|
||||
}
|
||||
|
||||
return &resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
Secret: defaultDemoSecret,
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
CASHA: "abc",
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
data := map[string]string{}
|
||||
|
||||
data[parser.GetAnnotationWithPrefix(proxySSLSecretAnnotation)] = "default/demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-ciphers")] = "HIGH:-SHA"
|
||||
data[parser.GetAnnotationWithPrefix(proxySSLSecretAnnotation)] = defaultDemoSecret
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-ciphers")] = proxySslCiphers
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-name")] = "$host"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv1.3 SSLv2 TLSv1 TLSv1.2"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "on"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = off
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "on"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "3"
|
||||
|
||||
|
|
@ -115,7 +122,7 @@ func TestAnnotations(t *testing.T) {
|
|||
t.Errorf("expected *Config but got %v", u)
|
||||
}
|
||||
|
||||
secret, err := fakeSecret.GetAuthCertificate("default/demo-secret")
|
||||
secret, err := fakeSecret.GetAuthCertificate(defaultDemoSecret)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error getting secret %v", err)
|
||||
}
|
||||
|
|
@ -123,11 +130,11 @@ func TestAnnotations(t *testing.T) {
|
|||
if u.AuthSSLCert.Secret != secret.Secret {
|
||||
t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
|
||||
}
|
||||
if u.Ciphers != "HIGH:-SHA" {
|
||||
t.Errorf("expected %v but got %v", "HIGH:-SHA", u.Ciphers)
|
||||
if u.Ciphers != proxySslCiphers {
|
||||
t.Errorf("expected %v but got %v", proxySslCiphers, u.Ciphers)
|
||||
}
|
||||
if u.Protocols != "SSLv2 TLSv1 TLSv1.2 TLSv1.3" {
|
||||
t.Errorf("expected %v but got %v", "SSLv2 TLSv1 TLSv1.2 TLSv1.3", u.Protocols)
|
||||
if u.Protocols != defaultProtocol {
|
||||
t.Errorf("expected %v but got %v", defaultProtocol, u.Protocols)
|
||||
}
|
||||
if u.Verify != "on" {
|
||||
t.Errorf("expected %v but got %v", "on", u.Verify)
|
||||
|
|
@ -141,7 +148,6 @@ func TestAnnotations(t *testing.T) {
|
|||
if u.ProxySSLServerName != "on" {
|
||||
t.Errorf("expected %v but got %v", "on", u.ProxySSLServerName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInvalidAnnotations(t *testing.T) {
|
||||
|
|
@ -172,11 +178,11 @@ func TestInvalidAnnotations(t *testing.T) {
|
|||
}
|
||||
|
||||
// Invalid optional Annotations
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = defaultDemoSecret
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv111 SSLv1"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "w00t"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "w00t"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "w00t"
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = sslServerName
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = sslServerName
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = sslServerName
|
||||
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "abcd"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
|
|
@ -207,21 +213,15 @@ func TestEquals(t *testing.T) {
|
|||
cfg1 := &Config{}
|
||||
cfg2 := &Config{}
|
||||
|
||||
// Same config
|
||||
result := cfg1.Equal(cfg1)
|
||||
if result != true {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
|
||||
// compare nil
|
||||
result = cfg1.Equal(nil)
|
||||
result := cfg1.Equal(nil)
|
||||
if result != false {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
|
||||
// Different Certs
|
||||
sslCert1 := resolver.AuthSSLCert{
|
||||
Secret: "default/demo-secret",
|
||||
Secret: defaultDemoSecret,
|
||||
CAFileName: "/ssl/ca.crt",
|
||||
CASHA: "abc",
|
||||
}
|
||||
|
|
@ -240,7 +240,7 @@ func TestEquals(t *testing.T) {
|
|||
|
||||
// Different Ciphers
|
||||
cfg1.Ciphers = "DEFAULT"
|
||||
cfg2.Ciphers = "HIGH:-SHA"
|
||||
cfg2.Ciphers = proxySslCiphers
|
||||
result = cfg1.Equal(cfg2)
|
||||
if result != false {
|
||||
t.Errorf("Expected false")
|
||||
|
|
@ -248,22 +248,22 @@ func TestEquals(t *testing.T) {
|
|||
cfg2.Ciphers = "DEFAULT"
|
||||
|
||||
// Different Protocols
|
||||
cfg1.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3"
|
||||
cfg1.Protocols = defaultProtocol
|
||||
cfg2.Protocols = "SSLv3 TLSv1 TLSv1.2 TLSv1.3"
|
||||
result = cfg1.Equal(cfg2)
|
||||
if result != false {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
cfg2.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3"
|
||||
cfg2.Protocols = defaultProtocol
|
||||
|
||||
// Different Verify
|
||||
cfg1.Verify = "off"
|
||||
cfg1.Verify = off
|
||||
cfg2.Verify = "on"
|
||||
result = cfg1.Equal(cfg2)
|
||||
if result != false {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
cfg2.Verify = "off"
|
||||
cfg2.Verify = off
|
||||
|
||||
// Different VerifyDepth
|
||||
cfg1.VerifyDepth = 1
|
||||
|
|
@ -275,13 +275,13 @@ func TestEquals(t *testing.T) {
|
|||
cfg2.VerifyDepth = 1
|
||||
|
||||
// Different ProxySSLServerName
|
||||
cfg1.ProxySSLServerName = "off"
|
||||
cfg1.ProxySSLServerName = off
|
||||
cfg2.ProxySSLServerName = "on"
|
||||
result = cfg1.Equal(cfg2)
|
||||
if result != false {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
cfg2.ProxySSLServerName = "off"
|
||||
cfg2.ProxySSLServerName = off
|
||||
|
||||
// Equal Configs
|
||||
result = cfg1.Equal(cfg2)
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ func (a ratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
|
||||
func encode(s string) string {
|
||||
str := base64.URLEncoding.EncodeToString([]byte(s))
|
||||
return strings.Replace(str, "=", "", -1)
|
||||
return strings.ReplaceAll(str, "=", "")
|
||||
}
|
||||
|
||||
func (a ratelimit) GetDocumentation() parser.AnnotationFields {
|
||||
|
|
|
|||
|
|
@ -54,14 +54,14 @@ var redirectAnnotations = parser.Annotation{
|
|||
Documentation: `In some scenarios is required to redirect from www.domain.com to domain.com or vice versa. To enable this feature use this annotation.`,
|
||||
},
|
||||
temporalRedirectAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, false),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, false),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium, as it allows arbitrary URLs that needs to be validated
|
||||
Documentation: `This annotation allows you to return a temporal redirect (Return Code 302) instead of sending data to the upstream.
|
||||
For example setting this annotation to https://www.google.com would redirect everything to Google with a Return Code of 302 (Moved Temporarily).`,
|
||||
},
|
||||
permanentRedirectAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, false),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, false),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium, as it allows arbitrary URLs that needs to be validated
|
||||
Documentation: `This annotation allows to return a permanent redirect (Return Code 301) instead of sending data to the upstream.
|
||||
|
|
@ -174,11 +174,11 @@ func isValidURL(s string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a redirect) GetDocumentation() parser.AnnotationFields {
|
||||
return a.annotationConfig.Annotations
|
||||
func (r redirect) GetDocumentation() parser.AnnotationFields {
|
||||
return r.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a redirect) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (r redirect) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(r.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, redirectAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@ func TestTemporalRedirect(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIsValidURL(t *testing.T) {
|
||||
|
||||
invalid := "ok.com"
|
||||
urlParse, err := url.Parse(invalid)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ var rewriteAnnotations = parser.Annotation{
|
|||
Group: "rewrite",
|
||||
Annotations: parser.AnnotationFields{
|
||||
rewriteTargetAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.RegexPathWithCapture, false),
|
||||
Validator: parser.ValidateRegex(parser.RegexPathWithCapture, false),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation allows to specify the target URI where the traffic must be redirected. It can contain regular characters and captured
|
||||
|
|
@ -72,7 +72,7 @@ var rewriteAnnotations = parser.Annotation{
|
|||
the pathType should also be defined as 'ImplementationSpecific'.`,
|
||||
},
|
||||
appRootAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.RegexPathWithCapture, false),
|
||||
Validator: parser.ValidateRegex(parser.RegexPathWithCapture, false),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the Application Root that the Controller must redirect if it's in / context`,
|
||||
|
|
|
|||
|
|
@ -120,7 +120,10 @@ func TestSSLRedirect(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("rewrite-target")] = defRoute
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ := NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
i, err := NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
redirect, ok := i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Redirect type")
|
||||
|
|
@ -132,7 +135,10 @@ func TestSSLRedirect(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("rewrite-target")] = "/xpto/$1/abc/$2"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ = NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
i, err = NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
redirect, ok = i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Redirect type")
|
||||
|
|
@ -144,7 +150,10 @@ func TestSSLRedirect(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("rewrite-target")] = "/xpto/xas{445}"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ = NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
i, err = NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
redirect, ok = i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Redirect type")
|
||||
|
|
@ -156,7 +165,10 @@ func TestSSLRedirect(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("ssl-redirect")] = "false"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ = NewParser(mockBackend{redirect: false}).Parse(ing)
|
||||
i, err = NewParser(mockBackend{redirect: false}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
redirect, ok = i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Redirect type")
|
||||
|
|
@ -173,7 +185,10 @@ func TestForceSSLRedirect(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("rewrite-target")] = defRoute
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ := NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
i, err := NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
redirect, ok := i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Redirect type")
|
||||
|
|
@ -185,7 +200,10 @@ func TestForceSSLRedirect(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("force-ssl-redirect")] = "true"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ = NewParser(mockBackend{redirect: false}).Parse(ing)
|
||||
i, err = NewParser(mockBackend{redirect: false}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
redirect, ok = i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Redirect type")
|
||||
|
|
@ -194,6 +212,7 @@ func TestForceSSLRedirect(t *testing.T) {
|
|||
t.Errorf("Expected true but returned false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppRoot(t *testing.T) {
|
||||
ap := NewParser(mockBackend{redirect: true})
|
||||
|
||||
|
|
@ -241,7 +260,10 @@ func TestUseRegex(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix("use-regex")] = "true"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, _ := NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
i, err := NewParser(mockBackend{redirect: true}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
redirect, ok := i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a App Context")
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (s satisfy) GetDocumentation() parser.AnnotationFields {
|
|||
return s.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a satisfy) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (s satisfy) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(s.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, satisfyAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
|
||||
result, _ := ap.Parse(ing)
|
||||
if result != testCase.expected {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (s serviceUpstream) GetDocumentation() parser.AnnotationFields {
|
|||
return s.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a serviceUpstream) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (s serviceUpstream) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(s.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, serviceUpstreamAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,10 @@ func TestIngressAnnotationServiceUpstreamEnabled(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "true"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
enabled, ok := val.(bool)
|
||||
if !ok {
|
||||
t.Errorf("expected a bool type")
|
||||
|
|
@ -96,7 +99,10 @@ func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
enabled, ok := val.(bool)
|
||||
if !ok {
|
||||
t.Errorf("expected a bool type")
|
||||
|
|
@ -110,7 +116,10 @@ func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
|
|||
data = map[string]string{}
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ = NewParser(&resolver.Mock{}).Parse(ing)
|
||||
val, err = NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
enabled, ok = val.(bool)
|
||||
if !ok {
|
||||
t.Errorf("expected a bool type")
|
||||
|
|
@ -137,7 +146,10 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|||
func TestParseAnnotationsWithDefaultConfig(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
val, _ := NewParser(mockBackend{}).Parse(ing)
|
||||
val, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
enabled, ok := val.(bool)
|
||||
|
||||
if !ok {
|
||||
|
|
@ -158,7 +170,10 @@ func TestParseAnnotationsOverridesDefaultConfig(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
val, _ := NewParser(mockBackend{}).Parse(ing)
|
||||
val, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
enabled, ok := val.(bool)
|
||||
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -63,13 +63,15 @@ const (
|
|||
|
||||
// This is used to control the cookie change after request failure
|
||||
annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
|
||||
|
||||
cookieAffinity = "cookie"
|
||||
)
|
||||
|
||||
var sessionAffinityAnnotations = parser.Annotation{
|
||||
Group: "affinity",
|
||||
Annotations: parser.AnnotationFields{
|
||||
annotationAffinityType: {
|
||||
Validator: parser.ValidateOptions([]string{"cookie"}, true, true),
|
||||
Validator: parser.ValidateOptions([]string{cookieAffinity}, true, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation enables and sets the affinity type in all Upstreams of an Ingress. This way, a request will always be directed to the same upstream server. The only affinity type available for NGINX is cookie`,
|
||||
|
|
@ -91,7 +93,7 @@ var sessionAffinityAnnotations = parser.Annotation{
|
|||
Setting this to legacy will restore original canary behavior, when session affinity was ignored.`,
|
||||
},
|
||||
annotationAffinityCookieName: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation allows to specify the name of the cookie that will be used to route the requests`,
|
||||
|
|
@ -103,25 +105,25 @@ var sessionAffinityAnnotations = parser.Annotation{
|
|||
Documentation: `This annotation set the cookie as secure regardless the protocol of the incoming request`,
|
||||
},
|
||||
annotationAffinityCookieExpires: {
|
||||
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, true),
|
||||
Validator: parser.ValidateRegex(affinityCookieExpiresRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation is a legacy version of "session-cookie-max-age" for compatibility with older browsers, generates an "Expires" cookie directive by adding the seconds to the current date`,
|
||||
},
|
||||
annotationAffinityCookieMaxAge: {
|
||||
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, false),
|
||||
Validator: parser.ValidateRegex(affinityCookieExpiresRegex, false),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation sets the time until the cookie expires`,
|
||||
},
|
||||
annotationAffinityCookiePath: {
|
||||
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the Path that will be set on the cookie (required if your Ingress paths use regular expressions)`,
|
||||
},
|
||||
annotationAffinityCookieDomain: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskMedium,
|
||||
Documentation: `This annotation defines the Domain attribute of the sticky cookie.`,
|
||||
|
|
@ -149,9 +151,7 @@ var sessionAffinityAnnotations = parser.Annotation{
|
|||
},
|
||||
}
|
||||
|
||||
var (
|
||||
affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
|
||||
)
|
||||
var affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
|
||||
|
||||
// Config describes the per ingress session affinity config
|
||||
type Config struct {
|
||||
|
|
@ -186,6 +186,11 @@ type Cookie struct {
|
|||
ConditionalSameSiteNone bool `json:"conditional-samesite-none"`
|
||||
}
|
||||
|
||||
type affinity struct {
|
||||
r resolver.Resolver
|
||||
annotationConfig parser.Annotation
|
||||
}
|
||||
|
||||
// cookieAffinityParse gets the annotation values related to Cookie Affinity
|
||||
// It also sets default values when no value or incorrect value is found
|
||||
func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie {
|
||||
|
|
@ -252,11 +257,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|||
}
|
||||
}
|
||||
|
||||
type affinity struct {
|
||||
r resolver.Resolver
|
||||
annotationConfig parser.Annotation
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to configure the affinity directives
|
||||
func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
|
|
@ -279,11 +279,10 @@ func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
}
|
||||
|
||||
switch at {
|
||||
case "cookie":
|
||||
case cookieAffinity:
|
||||
cookie = a.cookieAffinityParse(ing)
|
||||
default:
|
||||
klog.V(3).InfoS("No default affinity found", "ingress", ing.Name)
|
||||
|
||||
}
|
||||
|
||||
return &Config{
|
||||
|
|
|
|||
|
|
@ -83,7 +83,11 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
|
|||
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieSecure)] = "true"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
affin, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
affin, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error parsing annotations: %v", err)
|
||||
}
|
||||
|
||||
nginxAffinity, ok := affin.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected a Config type")
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
|
||||
result, _ := ap.Parse(ing)
|
||||
if result != testCase.expected {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
|
|
|
|||
|
|
@ -31,10 +31,8 @@ const (
|
|||
sslCipherAnnotation = "ssl-ciphers"
|
||||
)
|
||||
|
||||
var (
|
||||
// Should cover something like "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
|
||||
regexValidSSLCipher = regexp.MustCompile(`^[A-Za-z0-9!:+\-]*$`)
|
||||
)
|
||||
// Should cover something like "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
|
||||
var regexValidSSLCipher = regexp.MustCompile(`^[A-Za-z0-9!:+\-]*$`)
|
||||
|
||||
var sslCipherAnnotations = parser.Annotation{
|
||||
Group: "backend",
|
||||
|
|
@ -47,7 +45,7 @@ var sslCipherAnnotations = parser.Annotation{
|
|||
This configuration specifies that server ciphers should be preferred over client ciphers when using the SSLv3 and TLS protocols.`,
|
||||
},
|
||||
sslCipherAnnotation: {
|
||||
Validator: parser.ValidateRegex(*regexValidSSLCipher, true),
|
||||
Validator: parser.ValidateRegex(regexValidSSLCipher, true),
|
||||
Scope: parser.AnnotationScopeIngress,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `Using this annotation will set the ssl_ciphers directive at the server level. This configuration is active for all the paths in the host.`,
|
||||
|
|
@ -104,7 +102,7 @@ func (sc sslCipher) GetDocumentation() parser.AnnotationFields {
|
|||
return sc.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a sslCipher) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (sc sslCipher) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(sc.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, sslCipherAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,11 @@ func TestParse(t *testing.T) {
|
|||
expectErr bool
|
||||
}{
|
||||
{map[string]string{annotationSSLCiphers: "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"}, Config{"ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP", ""}, false},
|
||||
{map[string]string{annotationSSLCiphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"},
|
||||
Config{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256", ""}, false},
|
||||
{
|
||||
map[string]string{annotationSSLCiphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"},
|
||||
Config{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256", ""},
|
||||
false,
|
||||
},
|
||||
{map[string]string{annotationSSLCiphers: ""}, Config{"", ""}, false},
|
||||
{map[string]string{annotationSSLPreferServerCiphers: "true"}, Config{"", "on"}, false},
|
||||
{map[string]string{annotationSSLPreferServerCiphers: "false"}, Config{"", "off"}, false},
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ type sslpt struct {
|
|||
|
||||
// NewParser creates a new SSL passthrough annotation parser
|
||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
||||
return sslpt{r: r,
|
||||
return sslpt{
|
||||
r: r,
|
||||
annotationConfig: sslPassthroughAnnotations,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ func TestParse(t *testing.T) {
|
|||
annotations map[string]string
|
||||
expected string
|
||||
}{
|
||||
{map[string]string{annotation: "server { listen: 8000; proxy_pass 127.0.0.1:80}"},
|
||||
{
|
||||
map[string]string{annotation: "server { listen: 8000; proxy_pass 127.0.0.1:80}"},
|
||||
"server { listen: 8000; proxy_pass 127.0.0.1:80}",
|
||||
},
|
||||
{map[string]string{annotation: "false"}, "false"},
|
||||
|
|
@ -56,6 +57,7 @@ func TestParse(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
|
||||
result, _ := ap.Parse(ing)
|
||||
if result != testCase.expected {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ var upstreamHashByAnnotations = parser.Annotation{
|
|||
Group: "backend",
|
||||
Annotations: parser.AnnotationFields{
|
||||
upstreamHashByAnnotation: {
|
||||
Validator: parser.ValidateRegex(*hashByRegex, true),
|
||||
Validator: parser.ValidateRegex(hashByRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskHigh, // High, this annotation allows accessing NGINX variables
|
||||
Documentation: `This annotation defines the nginx variable, text value or any combination thereof to use for consistent hashing.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ var xForwardedForAnnotations = parser.Annotation{
|
|||
Group: "backend",
|
||||
Annotations: parser.AnnotationFields{
|
||||
xForwardedForPrefixAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow, // Low, as it allows regexes but on a very limited set
|
||||
Documentation: `This annotation can be used to add the non-standard X-Forwarded-Prefix header to the upstream request with a string value`,
|
||||
|
|
@ -54,15 +54,15 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|||
|
||||
// Parse parses the annotations contained in the ingress rule
|
||||
// used to add an x-forwarded-prefix header to the request
|
||||
func (cbbs xforwardedprefix) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
return parser.GetStringAnnotation(xForwardedForPrefixAnnotation, ing, cbbs.annotationConfig.Annotations)
|
||||
func (x xforwardedprefix) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
return parser.GetStringAnnotation(xForwardedForPrefixAnnotation, ing, x.annotationConfig.Annotations)
|
||||
}
|
||||
|
||||
func (cbbs xforwardedprefix) GetDocumentation() parser.AnnotationFields {
|
||||
return cbbs.annotationConfig.Annotations
|
||||
func (x xforwardedprefix) GetDocumentation() parser.AnnotationFields {
|
||||
return x.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a xforwardedprefix) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
func (x xforwardedprefix) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(x.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, xForwardedForAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
ing.SetAnnotations(testCase.annotations)
|
||||
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
|
||||
result, _ := ap.Parse(ing)
|
||||
if result != testCase.expected {
|
||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue