From 433cfcb0dc6f60034abce900714fafdac2aff0cc Mon Sep 17 00:00:00 2001 From: Scott Leggett Date: Mon, 1 Nov 2021 15:21:56 +0800 Subject: [PATCH 1/2] feat: also lint active/standby routes --- internal/lagoonyml/lagoon.go | 9 +- internal/lagoonyml/lint_test.go | 12 +++ internal/lagoonyml/routeannotation.go | 89 ++++++++++++------- .../lagoonyml/testdata/invalid.2.lagoon.yml | 31 +++++++ .../lagoonyml/testdata/invalid.3.lagoon.yml | 31 +++++++ .../lagoonyml/testdata/valid.6.lagoon.yml | 25 ++++++ 6 files changed, 162 insertions(+), 35 deletions(-) create mode 100644 internal/lagoonyml/testdata/invalid.2.lagoon.yml create mode 100644 internal/lagoonyml/testdata/invalid.3.lagoon.yml create mode 100644 internal/lagoonyml/testdata/valid.6.lagoon.yml diff --git a/internal/lagoonyml/lagoon.go b/internal/lagoonyml/lagoon.go index 2b4dfcf..33a79b9 100644 --- a/internal/lagoonyml/lagoon.go +++ b/internal/lagoonyml/lagoon.go @@ -27,7 +27,14 @@ type Environment struct { Routes []map[string][]LagoonRoute `json:"routes"` } +// ProductionRoutes represents an active/standby configuration. +type ProductionRoutes struct { + Active *Environment `json:"active"` + Standby *Environment `json:"standby"` +} + // Lagoon represents the .lagoon.yml file. type Lagoon struct { - Environments map[string]Environment `json:"environments"` + Environments map[string]Environment `json:"environments"` + ProductionRoutes *ProductionRoutes `json:"production_routes"` } diff --git a/internal/lagoonyml/lint_test.go b/internal/lagoonyml/lint_test.go index be074b8..2c79eb6 100644 --- a/internal/lagoonyml/lint_test.go +++ b/internal/lagoonyml/lint_test.go @@ -35,6 +35,10 @@ func TestLint(t *testing.T) { input: "testdata/valid.5.lagoon.yml", valid: true, }, + "standby route valid annotation": { + input: "testdata/valid.6.lagoon.yml", + valid: true, + }, "invalid.0.lagoon.yml": { input: "testdata/invalid.0.lagoon.yml", valid: false, @@ -43,6 +47,14 @@ func TestLint(t *testing.T) { input: "testdata/invalid.1.lagoon.yml", valid: false, }, + "standby route invalid annotation": { + input: "testdata/invalid.2.lagoon.yml", + valid: false, + }, + "active route invalid annotation": { + input: "testdata/invalid.3.lagoon.yml", + valid: false, + }, } for name, tc := range testCases { t.Run(name, func(tt *testing.T) { diff --git a/internal/lagoonyml/routeannotation.go b/internal/lagoonyml/routeannotation.go index 69799f5..30916b1 100644 --- a/internal/lagoonyml/routeannotation.go +++ b/internal/lagoonyml/routeannotation.go @@ -37,44 +37,65 @@ func validate(annotations map[string]string, r *regexp.Regexp, return "", true } +// validateEnvironment returns an error if the annotations on the environment +// are invalid, and nil otherwise. +func validateEnvironment(e *Environment) error { + for _, routeMap := range e.Routes { + for rName, lagoonRoutes := range routeMap { + for _, lagoonRoute := range lagoonRoutes { + for iName, ingress := range lagoonRoute.Ingresses { + // auth-snippet + if _, ok := ingress.Annotations[authSnippet]; ok { + return fmt.Errorf( + "invalid %s annotation on route %s, ingress %s: %s", + authSnippet, rName, iName, + "this annotation is restricted") + } + // configuration-snippet + if annotation, ok := validate(ingress.Annotations, validSnippets, + configurationSnippet); !ok { + return fmt.Errorf( + "invalid %s annotation on route %s, ingress %s: %s", + configurationSnippet, rName, iName, annotation) + } + // modsecurity-snippet + if _, ok := ingress.Annotations[modsecuritySnippet]; ok { + return fmt.Errorf( + "invalid %s annotation on route %s, ingress %s: %s", + modsecuritySnippet, rName, iName, + "this annotation is restricted") + } + // server-snippet + if annotation, ok := validate(ingress.Annotations, validSnippets, + serverSnippet); !ok { + return fmt.Errorf( + "invalid %s annotation on route %s, ingress %s: %s", + serverSnippet, rName, iName, annotation) + } + } + } + } + } + return nil +} + // RouteAnnotation checks for valid annotations on defined routes. func RouteAnnotation() Linter { return func(l *Lagoon) error { for eName, e := range l.Environments { - for _, routeMap := range e.Routes { - for rName, lagoonRoutes := range routeMap { - for _, lagoonRoute := range lagoonRoutes { - for iName, ingress := range lagoonRoute.Ingresses { - // auth-snippet - if _, ok := ingress.Annotations[authSnippet]; ok { - return fmt.Errorf( - "invalid %s annotation on environment %s, route %s, ingress %s: %s", - authSnippet, eName, rName, iName, - "this annotation is restricted") - } - // configuration-snippet - if annotation, ok := validate(ingress.Annotations, validSnippets, - configurationSnippet); !ok { - return fmt.Errorf( - "invalid %s annotation on environment %s, route %s, ingress %s: %s", - configurationSnippet, eName, rName, iName, annotation) - } - // modsecurity-snippet - if _, ok := ingress.Annotations[modsecuritySnippet]; ok { - return fmt.Errorf( - "invalid %s annotation on environment %s, route %s, ingress %s: %s", - modsecuritySnippet, eName, rName, iName, - "this annotation is restricted") - } - // server-snippet - if annotation, ok := validate(ingress.Annotations, validSnippets, - serverSnippet); !ok { - return fmt.Errorf( - "invalid %s annotation on environment %s, route %s, ingress %s: %s", - serverSnippet, eName, rName, iName, annotation) - } - } - } + if err := validateEnvironment(&e); err != nil { + return fmt.Errorf("environment %s: %v", eName, err) + } + } + if l.ProductionRoutes != nil { + if l.ProductionRoutes.Active != nil { + if err := validateEnvironment(l.ProductionRoutes.Active); err != nil { + return fmt.Errorf("active environment: %v", err) + } + } + if l.ProductionRoutes.Standby != nil { + if err := validateEnvironment(l.ProductionRoutes.Standby); err != nil { + return fmt.Errorf("standby environment: %v", err) } } } diff --git a/internal/lagoonyml/testdata/invalid.2.lagoon.yml b/internal/lagoonyml/testdata/invalid.2.lagoon.yml new file mode 100644 index 0000000..3a964fb --- /dev/null +++ b/internal/lagoonyml/testdata/invalid.2.lagoon.yml @@ -0,0 +1,31 @@ +production_routes: + active: + routes: + - nginx: + - "www.example.com": + tls-acme: true + insecure: Redirect + - "de.example.com": + tls-acme: "true" + insecure: Redirect + + standby: + routes: + - nginx: + - "www.standby.example.com": + tls-acme: "false" + insecure: Redirect + - "de.standby.example.com": + tls-acme: "false" + insecure: Redirect + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + set $agentflag 0; + + if ($http_user_agent ~* "(Mobile)" ){ + set $agentflag 1; + } + + if ( $agentflag = 1 ) { + return 301 https://m.example.com; + } diff --git a/internal/lagoonyml/testdata/invalid.3.lagoon.yml b/internal/lagoonyml/testdata/invalid.3.lagoon.yml new file mode 100644 index 0000000..ac506ff --- /dev/null +++ b/internal/lagoonyml/testdata/invalid.3.lagoon.yml @@ -0,0 +1,31 @@ +production_routes: + active: + routes: + - nginx: + - "www.example.com": + tls-acme: true + insecure: Redirect + - "de.example.com": + tls-acme: "true" + insecure: Redirect + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + set $agentflag 0; + + if ($http_user_agent ~* "(Mobile)" ){ + set $agentflag 1; + } + + if ( $agentflag = 1 ) { + return 301 https://m.example.com; + } + + standby: + routes: + - nginx: + - "www.standby.example.com": + tls-acme: "false" + insecure: Redirect + - "de.standby.example.com": + tls-acme: "false" + insecure: Redirect diff --git a/internal/lagoonyml/testdata/valid.6.lagoon.yml b/internal/lagoonyml/testdata/valid.6.lagoon.yml new file mode 100644 index 0000000..717aa3e --- /dev/null +++ b/internal/lagoonyml/testdata/valid.6.lagoon.yml @@ -0,0 +1,25 @@ +production_routes: + active: + routes: + - nginx: + - "www.example.com": + tls-acme: true + insecure: Redirect + - "de.example.com": + tls-acme: "true" + insecure: Redirect + + standby: + routes: + - nginx: + - "www.standby.example.com": + tls-acme: "false" + insecure: Redirect + - "de.standby.example.com": + tls-acme: "false" + insecure: Redirect + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + set_real_ip_from 1.2.3.4/32; + + add_header Content-type text/plain; From e59a85edad7b0c9c8f7140c73587c94bcce06b58 Mon Sep 17 00:00:00 2001 From: Scott Leggett Date: Mon, 1 Nov 2021 15:22:19 +0800 Subject: [PATCH 2/2] chore: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57823a5..e21d0fa 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ Currently implemented linters. | Name | Description | | --- | --- | -| RouteAnnotation | Validates the `server-snippets` Lagoon route / kubernetes Ingress annotation against an allow-list. | +| RouteAnnotation | Validates Lagoon Route / Kubernetes Ingress annotations. |