Dashboard service (horizon) security

Use the Dashboard service (horizon) as a portal to provision your cloud resources. Horizon runs as a pod in the openstack namespace, exposed with a Red Hat OpenShift Container Platform (RHOCP) Route.

Apply configuration through customServiceConfig in the OpenStackControlPlane CR by using Python Django settings syntax, not ini-style configuration. There is no HAProxy on controller nodes, no local_settings.py on disk, and no httpd.conf managed by Puppet. Treat the security of the Dashboard with the same sensitivity as the OpenStack APIs. The Dashboard is a direct interface to the API. Vulnerabilities in the Dashboard can compromise your cloud.

Dashboard service debugging

In production environments, keep the default DEBUG setting of False. If DEBUG is True, Django outputs stack traces to browser users that contain sensitive server state, and the ALLOWED_HOSTS setting is disabled, which defeats host header validation.

To enable debug temporarily for investigation or in a test environment, inject the setting through customServiceConfig:

spec:
  horizon:
    template:
      customServiceConfig: |
        DEBUG = True
Important
Revert the DEBUG setting to False immediately after investigation. Never leave DEBUG = True enabled in a production environment. The operator reconciles the setting on the next pod restart.

Domain name selection

For security, deploy the Dashboard service (horizon) on a dedicated second-level domain, such as https://dashboard.example.com, and not a shared subdomain such as https://dashboard.public-cloud.example.com. The same-origin policy of a browser isolates cookies and security tokens by domain.

In Red Hat OpenStack Services on OpenShift (RHOSO) 18.0, the Dashboard URL is determined by the Red Hat OpenShift Container Platform (RHOCP) Route hostname. You can configure the Route hostname in the apiOverride section of the horizon configuration if you need a custom domain:

spec:
  horizon:
    apiOverride:
      route:
        spec:
          host: dashboard.example.com
Note
Deploying the Dashboard on a bare domain, such as https://example/, is not supported.

ALLOWED_HOSTS configuration

The Dashboard service (horizon) uses Django’s ALLOWED_HOSTS setting to reject HTTP requests with incorrect Host headers to prevent HTTP Host header attacks. The Dashboard service operator sets ALLOWED_HOSTS based on the Route hostname.

If you use a custom domain or need to add additional allowed hosts, you can set it through customServiceConfig:

spec:
  horizon:
    template:
      customServiceConfig: |
        ALLOWED_HOSTS = [get_pod_ip(), 'dashboard.example.com', 'horizon-openstack.apps.ocp.example.com']
Important
Never set the value of ALLOWED_HOSTS to the wildcard character, [*]. This disables host header validation and opens the Dashboard to Host header poisoning attacks.

Cross-site scripting protection

The Dashboard service (horizon) accepts the full Unicode character set in most fields. Malicious actors could exploit this to test for cross-site scripting (XSS) vulnerabilities. The Dashboard service includes Django’s built-in XSS protections.

When auditing custom dashboards or panels, pay attention to the following configurations:

  • Use of the mark_safe function
  • The is_safe flag on custom template tags
  • The safe template tag
  • Anywhere auto-escaping is disabled
  • Any JavaScript that evaluates improperly escaped data
Note
Django-level security concerns are consistent between Red Hat OpenStack Platform (RHOSP) 17.1 and Red Hat OpenStack Services on OpenShift (RHOSO) 18.0. The guidance applies to any custom panels or extensions deployed in the Dashboard service pod.

Cross-site request forgery protection

Audit Dashboard service (horizon) dashboards for Cross-site Request Forgery (CSRF) and check for inappropriate use of the @csrf_exempt decorator. Configure your web server or IngressController to send a restrictive Cross-Origin Resource Sharing (CORS) header with each response.

Allow only the Dashboard domain and protocol:

Access-Control-Allow-Origin: https://dashboard.example.com

Never allow the wildcard origin (*). Evaluate any dashboard that relaxes CORS restrictions before deploying it.

Iframe embedding configuration

The DISALLOW_IFRAME_EMBED setting prevents the Dashboard service (horizon) dashboard from iframe embedding and mitigates cross-frame scripting (XFS) attacks. The DISALLOW_IFRAME_EMBED setting defaults to True.

Important
Only set DISALLOW_IFRAME_EMBED to False if you have a specific requirement for iframe embedding and fully understand the XFS risks.

HTTP Strict Transport Security

Prevent browser fallback to insecure connections after an initial secure connection. Configure HSTS when you deploy the Dashboard service (horizon) on a public or untrusted zone.

In Red Hat OpenStack Platform (RHOSP) 17.1, you configured HSTS through a Puppet or HAProxy template such as tripleo::haproxy::horizon::options. In Red Hat OpenStack Services on OpenShift (RHOSO) 18.0, the dashboard is behind a Red Hat OpenShift Container Platform (RHOCP) Route. In RHOSO you can configure HSTS on the RHOCP IngressController or by using Route annotations.

To enable HSTS globally for all Routes including horizon, configure the httpHeaders policy for the IngressController:

apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
  name: default
  namespace: openshift-ingress-operator
spec:
  httpHeaders:
    actions:
      response:
        - name: Strict-Transport-Security
          action:
            type: Set
            set:
              value: "max-age=31536000; includeSubDomains"

You can verify HSTS with the following command:

$ curl -s -D- https://dashboard.example.com | grep -i strict

The output resembles the following:

strict-transport-security: max-age=31536000; includeSubDomains

Front-end caching

Do not use front-end caching tools, such as Varnish, with the Dashboard service (horizon). The Dashboard service renders dynamic content directly from OpenStack API requests. A caching layer can serve stale or incorrect content. Django already uses standard HTTP caching headers for static media.

Session back end

The default session back end for the Dashboard service (horizon) is django.contrib.sessions.backends.cache, backed by memcached. This configuration supports horizontal scaling and avoids local-memory session stores.

You can enable memcached authentication in the OpenStackControlPlane CR to restrict access to cached session data. By default, memcached does not require authentication.

Note
When pod-level TLS is enabled, memcached connections from the Dashboard service are encrypted automatically. Unlike the MemcachedTLS heat parameter from Red Hat OpenStack Platform (RHOSP) 17.1, you do not need to configure separate memcached TLS.

Dashboard SECRET_KEY management and rotation

The Dashboard service (horizon) depends on a shared SECRET_KEY for cryptographic signing of sessions and Cross-site Request Forgery (CSRF) tokens. Compromise of this key could allow remote code execution.

In Red Hat OpenStack Services on OpenShift (RHOSO) 18.0, the horizon-operator automatically generates the SECRET_KEY and stores it in a dedicated Kubernetes Secret named horizon in the openstack namespace, under the key horizon-secret. This secret is mounted into all horizon pod replicas and read by Django at startup. It is separate from the osp-secret referenced in the Horizon CR’s secret field, which serves only as a dependency gate and change-detection input.

The generated key is 10 random alphanumeric characters. For production environments, set the key value to at least 64 randomly generated characters. Editing the key invalidates all existing user sessions and cached data. Never commit the key to version control or store it in the OpenStackControlPlane CR spec in plain text. To rotate the secret key, update the value in the Kubernetes Secret and restart the horizon pods:

$ oc patch secret horizon -n openstack
-p '{"data":{"horizon-secret":"'$(openssl rand -base64 64 | tr -d '\n' | base64 -w 0)'"}}'
--type merge
$ oc rollout restart deployment/horizon -n openstack
Important
The operator will not overwrite a key that is set manually. The operator only generates a new value if the horizon secret does not exist or the horizon-secret key is empty.

Session cookie security settings

Protect session cookies from JavaScript access. When the Dashboard service (horizon) endpoint uses HTTPS, those settings are enabled by default.

You can configure the settings by using customServiceConfig:

spec:
  horizon:
    template:
      customServiceConfig: |
        SESSION_COOKIE_SECURE = True
        CSRF_COOKIE_SECURE = True
        SESSION_COOKIE_HTTPONLY = True

SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE ensure cookies are only sent over HTTPS. SESSION_COOKIE_HTTPONLY prevents JavaScript from reading the session cookie, mitigating session theft from Cross-Site Scripting (XSS).

Important
Never configure Cross-site Request Forgery (CSRF) or session cookies with a wildcard domain denoted by a leading dot. This would allow any subdomain to read the cookies.

Dashboard password security parameters

The ENFORCE_PASSWORD_CHECK setting displays an Admin Password field on the Change Password form to verify that an administrator initiated the change. This is set to True by default.

Note
Do not disable the ENFORCE_PASSWORD_CHECK setting as there are security implications. Without ENFORCE_PASSWORD_CHECK = True, any user who gains access to the Change Password form could reset passwords without needing to prove that they know the current admin password.

The DISABLE_PASSWORD_REVEAL setting removes the password reveal button from the login and password fields. The default value of DISABLE_PASSWORD_REVEAL is True.

Logon banner for regulatory compliance

Regulated industries such as Health Insurance Portability and Accountability Act (HIPAA), Payment Card Industry Data Security Standard (PCI-DSS), or government might require a user logon banner. Configure this with a custom theme and modified login template with extraMounts.

File upload size limits

In Red Hat OpenStack Platform (RHOSP) 17.1, you edited LimitRequestBody in httpd.conf files to configure upload size limits. In Red Hat OpenStack Services on OpenShift (RHOSO) 18.0, you configure the httpd timeout with extraMounts to load a custom httpd.conf into the horizon pod.

You can set the FILE_UPLOAD_MAX_MEMORY_SIZE and DATA_UPLOAD_MAX_MEMORY_SIZE Django settings through customServiceConfig:

spec:
  horizon:
    template:
      customServiceConfig: |
        FILE_UPLOAD_MAX_MEMORY_SIZE = 10737418240
        DATA_UPLOAD_MAX_MEMORY_SIZE = 10737418240

Note that both settings work at the same time. If you increase the Django httpd settings above the default value, then you must also increase the settings in the LimitRequestBody httpd configuration.

Important
Setting upload limits that are too low causes image and file upload failure. Test the limits against your largest expected upload, such as Image service (glance) images, before deploying to production.

RHOSP 17.1 to RHOSO 18.0 Dashboard service hardening comparison

Review the Dashboard service (horizon) hardening differences between Red Hat OpenStack Platform (RHOSP) 17.1 and Red Hat OpenStack Services on OpenShift (RHOSO) 18.0.

Table 1. Comparison between dashboards in RHOSP 17.1 and RHOSO 18.0
RHOSP 17.1 mechanism RHOSO 18.0 equivalent Notes

The heat parameter HorizonAllowedHosts.

ALLOWED_HOSTS in customServiceConfig.

Python list syntax in Django settings.

The Puppet parameters ControllerExtraConfig and horizon::*.

customServiceConfig in spec.horizon.template.

All Puppet parameters are replaced by Django settings.

tripleo::haproxy::horizon::options (HSTS).

RHOCP IngressControllerhttpHeaders.

HSTS is set at the ingress layer, not HAProxy.

The heat parameter HorizonSecret.

Kubernetes Secret osp-secretHorizonSecret key.

Managed as a Kubernetes Secret and rotated with oc patch.

The heat parameter HorizonSecureCookies.

SESSION_COOKIE_SECURE / CSRF_COOKIE_SECURE in customServiceConfig.

Set as Django settings.

The heat parameters HorizonPasswordValidator / HorizonPasswordValidatorHelp.

HORIZON_CONFIG, 'password_validator' in customServiceConfig.

LimitRequestBody in httpd.conf managed by Puppet.

FILE_UPLOAD_MAX_MEMORY_SIZE in customServiceConfig, or extraMounts for httpd.conf.

No Puppet, persistent and auditable.

Unmanaged editing login.html in container.

LOGIN_SPLASH_TEXT in customServiceConfig, or custom theme with extraMounts.

Survives redeployment.

The Puppet parameters horizon::cache_backend and django_session_engine.

memcachedInstance in horizon spec that is auto-configured.

Memcached auth is optionally available.