From 8ad9fcaae29fea829ad158271c0b9f3dd8b81795 Mon Sep 17 00:00:00 2001 From: Adam Drew Date: Tue, 26 Nov 2024 14:18:37 -0500 Subject: [PATCH] Feature and test --- api/v1alpha1/frontendenvironment_types.go | 3 ++ api/v1alpha1/zz_generated.deepcopy.go | 7 +++ ...cloud.redhat.com_frontendenvironments.yaml | 7 +++ controllers/reconcile.go | 49 +++++++++++++++---- deploy.yml | 8 +++ .../modules/ROOT/pages/api_reference.adoc | 3 ++ .../e2e/http_headers/00-create-namespace.yaml | 8 +++ .../e2e/http_headers/01-create-resources.yaml | 38 ++++++++++++++ tests/e2e/http_headers/02-assert.yaml | 46 +++++++++++++++++ 9 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 tests/e2e/http_headers/00-create-namespace.yaml create mode 100644 tests/e2e/http_headers/01-create-resources.yaml create mode 100644 tests/e2e/http_headers/02-assert.yaml diff --git a/api/v1alpha1/frontendenvironment_types.go b/api/v1alpha1/frontendenvironment_types.go index c746ca0d..bcfb248f 100644 --- a/api/v1alpha1/frontendenvironment_types.go +++ b/api/v1alpha1/frontendenvironment_types.go @@ -97,6 +97,9 @@ type FrontendEnvironmentSpec struct { TargetNamespaces []string `json:"targetNamespaces,omitempty" yaml:"targetNamespaces,omitempty"` // For the ChromeUI to render additional global components ServiceCategories *[]FrontendServiceCategory `json:"serviceCategories,omitempty" yaml:"serviceCategories,omitempty"` + // Custom HTTP Headers + // These populate an ENV var that is then added into the caddy config as a header block + HTTPHeaders map[string]string `json:"httpHeaders,omitempty"` } type MonitoringConfig struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 9ff5aa8b..e5d43c6c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -496,6 +496,13 @@ func (in *FrontendEnvironmentSpec) DeepCopyInto(out *FrontendEnvironmentSpec) { } } } + if in.HTTPHeaders != nil { + in, out := &in.HTTPHeaders, &out.HTTPHeaders + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrontendEnvironmentSpec. diff --git a/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml b/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml index a6a6a5ba..ac046100 100644 --- a/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml +++ b/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml @@ -72,6 +72,13 @@ spec: hostname: description: Hostname type: string + httpHeaders: + additionalProperties: + type: string + description: |- + Custom HTTP Headers + These populate an ENV var that is then added into the caddy config as a header block + type: object ingressAnnotations: additionalProperties: type: string diff --git a/controllers/reconcile.go b/controllers/reconcile.go index 4071ee35..757a4491 100644 --- a/controllers/reconcile.go +++ b/controllers/reconcile.go @@ -363,23 +363,54 @@ func populateVolumes(d *apps.Deployment, frontend *crd.Frontend, frontendEnviron d.Spec.Template.Spec.Volumes = volumes } -// Add the SSL env vars if we SSL mode is set in the frontend environment +// Add the env vars if eny are set func (r *FrontendReconciliation) populateEnvVars(d *apps.Deployment, frontendEnvironment *crd.FrontendEnvironment) { - if !frontendEnvironment.Spec.SSL { - return - } - envVars := []v1.EnvVar{ - { + envVars := []v1.EnvVar{} + + if frontendEnvironment.Spec.SSL { + envVars = append(envVars, v1.EnvVar{ Name: "CADDY_TLS_MODE", Value: "https_port 8000", - }, - { + }) + envVars = append(envVars, v1.EnvVar{ Name: "CADDY_TLS_CERT", Value: "tls /opt/certs/tls.crt /opt/certs/tls.key", - }} + }) + } + + // Add the HTTP Headers to the env vars if the frontend environment has them + if frontendEnvironment.Spec.HTTPHeaders != nil { + envVars = append(envVars, v1.EnvVar{ + Name: "CADDY_HTTP_HEADERS", + Value: createCaddyfileHeaderBlock(frontendEnvironment.Spec.HTTPHeaders), + }) + } + d.Spec.Template.Spec.Containers[0].Env = envVars } +// Create a Caddyfile header block from the FrontenbdEnvironment httpHeaders +func createCaddyfileHeaderBlock(httpHeaders map[string]string) string { + if len(httpHeaders) == 0 { + return "" + } + var keys []string + for key := range httpHeaders { + keys = append(keys, key) + } + // Sort keys alphabetically + // We do this to ensure the order of the headers is consistent + // without this tests will be flaky + sort.Strings(keys) + + headerBlock := "header {\n" + for _, key := range keys { + headerBlock += fmt.Sprintf("%s %s\n", key, httpHeaders[key]) + } + headerBlock += "}" + return headerBlock +} + func (r *FrontendReconciliation) createFrontendDeployment(annotationHashes []map[string]string) error { // Create new empty struct diff --git a/deploy.yml b/deploy.yml index b4713810..79fc0561 100644 --- a/deploy.yml +++ b/deploy.yml @@ -276,6 +276,14 @@ objects: hostname: description: Hostname type: string + httpHeaders: + additionalProperties: + type: string + description: 'Custom HTTP Headers + + These populate an ENV var that is then added into the caddy config + as a header block' + type: object ingressAnnotations: additionalProperties: type: string diff --git a/docs/antora/modules/ROOT/pages/api_reference.adoc b/docs/antora/modules/ROOT/pages/api_reference.adoc index 5746dee5..2316ca25 100644 --- a/docs/antora/modules/ROOT/pages/api_reference.adoc +++ b/docs/antora/modules/ROOT/pages/api_reference.adoc @@ -463,6 +463,8 @@ do this in epehemeral environments but not in production + | | | *`targetNamespaces`* __string array__ | List of namespaces that should receive a copy of the frontend configuration as a config map + By configurations we mean the fed-modules.json, navigation files, etc. + | | | *`serviceCategories`* __xref:{anchor_prefix}-github-com-redhatinsights-frontend-operator-api-v1alpha1-frontendservicecategory[$$FrontendServiceCategory$$]__ | For the ChromeUI to render additional global components + | | +| *`httpHeaders`* __object (keys:string, values:string)__ | Custom HTTP Headers + +These populate an ENV var that is then added into the caddy config as a header block + | | |=== @@ -618,6 +620,7 @@ FrontendSpec defines the desired state of Frontend | *`serviceTiles`* __xref:{anchor_prefix}-github-com-redhatinsights-frontend-operator-api-v1alpha1-servicetile[$$ServiceTile$$] array__ | Data for the all services dropdown + | | | *`widgetRegistry`* __xref:{anchor_prefix}-github-com-redhatinsights-frontend-operator-api-v1alpha1-widgetentry[$$WidgetEntry$$] array__ | Data for the available widgets for the resource + | | | *`replicas`* __integer__ | | | +| *`feoConfigEnabled`* __boolean__ | Injects configuration from application when enabled + | | |=== diff --git a/tests/e2e/http_headers/00-create-namespace.yaml b/tests/e2e/http_headers/00-create-namespace.yaml new file mode 100644 index 00000000..ea4fe821 --- /dev/null +++ b/tests/e2e/http_headers/00-create-namespace.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: test-http-headers +spec: + finalizers: + - kubernetes diff --git a/tests/e2e/http_headers/01-create-resources.yaml b/tests/e2e/http_headers/01-create-resources.yaml new file mode 100644 index 00000000..35d0b5aa --- /dev/null +++ b/tests/e2e/http_headers/01-create-resources.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: FrontendEnvironment +metadata: + name: test-http-headers-environment +spec: + generateNavJSON: false + ssl: false + hostname: foo.redhat.com + sso: https://sso.foo.redhat.com + httpHeaders: + "X-Frame-Options": "Set" + "X-XSS-Protection": "1; mode=block;" + "X-Content-Type-Options": "nosniff" + "Content-Security-Policy": "default-src 'self'" + "Referrer-Policy": "no-referrer" +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: Frontend +metadata: + name: chrome + namespace: test-http-headers +spec: + API: + versions: + - v1 + frontend: + paths: + - / + deploymentRepo: https://github.com/RedHatInsights/insights-chrome + envName: test-http-headers-environment + image: quay.io/cloudservices/insights-chrome-frontend:720317c + module: + config: + ssoUrl: 'https://' + manifestLocation: /apps/chrome/js/fed-mods.json + title: Chrome + diff --git a/tests/e2e/http_headers/02-assert.yaml b/tests/e2e/http_headers/02-assert.yaml new file mode 100644 index 00000000..0b4365b4 --- /dev/null +++ b/tests/e2e/http_headers/02-assert.yaml @@ -0,0 +1,46 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: chrome-frontend + namespace: test-http-headers + labels: + frontend: chrome + ownerReferences: + - apiVersion: cloud.redhat.com/v1alpha1 + kind: Frontend + name: chrome +spec: + selector: + matchLabels: + frontend: chrome + template: + spec: + volumes: + - name: config + configMap: + name: test-http-headers-environment + defaultMode: 420 + containers: + - name: fe-image + image: 'quay.io/cloudservices/insights-chrome-frontend:720317c' + env: + - name: CADDY_HTTP_HEADERS + value: |- + header { + Content-Security-Policy default-src 'self' + Referrer-Policy no-referrer + X-Content-Type-Options nosniff + X-Frame-Options Set + X-XSS-Protection 1; mode=block; + } + ports: + - name: web + containerPort: 80 + protocol: TCP + - name: metrics + containerPort: 9000 + protocol: TCP + volumeMounts: + - name: config + mountPath: /opt/app-root/src/build/stable/operator-generated \ No newline at end of file