From cad2728ac222a9b316e5ff6bcfdf69f0caeb94b1 Mon Sep 17 00:00:00 2001 From: Stefan Nica Date: Wed, 3 Apr 2024 22:33:29 +0200 Subject: [PATCH 1/4] Add security headers to the ZenML server --- pyproject.toml | 2 ++ src/zenml/zen_server/zen_server_api.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index dadc1fe062..182d54e7e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ fastapi-utils = { version = "~0.2.1", optional = true } orjson = { version = "~3.8.3", optional = true } Jinja2 = { version = "*", optional = true } ipinfo = { version = ">=4.4.3", optional = true } +secure = { version = "~0.3.0", optional = true } # Optional dependencies for project templates copier = { version = ">=8.1.0", optional = true } @@ -180,6 +181,7 @@ server = [ "orjson", "Jinja2", "ipinfo", + "secure", ] templates = ["copier", "jinja2-time", "ruff"] terraform = ["python-terraform"] diff --git a/src/zenml/zen_server/zen_server_api.py b/src/zenml/zen_server/zen_server_api.py index b5f01f940d..2d455aeb3d 100644 --- a/src/zenml/zen_server/zen_server_api.py +++ b/src/zenml/zen_server/zen_server_api.py @@ -17,6 +17,7 @@ from asyncio.log import logger from typing import Any, List +import secure from fastapi import FastAPI, HTTPException, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import ORJSONResponse @@ -120,6 +121,29 @@ def validation_exception_handler( allow_headers=["*"], ) +secure_headers = secure.Secure( + # TODO: Add a nonce to the CSP header when ZenML supports it + # (see https://content-security-policy.com/examples/allow-inline-script/) + # csp=secure.ContentSecurityPolicy(), + permissions=secure.PermissionsPolicy() +) + + +@app.middleware("http") +async def set_secure_headers(request: Request, call_next: Any) -> Any: + """Middleware to set secure headers. + + Args: + request: The incoming request. + call_next: The next function to be called. + + Returns: + The response with secure headers set. + """ + response = await call_next(request) + secure_headers.framework.fastapi(response) + return response + @app.middleware("http") async def infer_source_context(request: Request, call_next: Any) -> Any: From bbaba84c1612e9b223c7699f7d0c683ac8a05d4b Mon Sep 17 00:00:00 2001 From: Stefan Nica Date: Thu, 4 Apr 2024 15:30:16 +0200 Subject: [PATCH 2/4] Make secure servers configurable and add sane default values --- src/zenml/config/server_config.py | 116 ++++++++++++++++++++++++- src/zenml/constants.py | 28 ++++++ src/zenml/zen_server/utils.py | 100 +++++++++++++++++++++ src/zenml/zen_server/zen_server_api.py | 13 +-- 4 files changed, 247 insertions(+), 10 deletions(-) diff --git a/src/zenml/config/server_config.py b/src/zenml/config/server_config.py index 2fef2890e0..7a3224e9b5 100644 --- a/src/zenml/config/server_config.py +++ b/src/zenml/config/server_config.py @@ -16,7 +16,7 @@ import json import os from secrets import token_hex -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from uuid import UUID from pydantic import BaseModel, Field, SecretStr, root_validator @@ -30,6 +30,14 @@ DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_MINUTE, DEFAULT_ZENML_SERVER_MAX_DEVICE_AUTH_ATTEMPTS, DEFAULT_ZENML_SERVER_PIPELINE_RUN_AUTH_WINDOW, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO, + DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP, ENV_ZENML_SERVER_PREFIX, ) from zenml.enums import AuthScheme @@ -128,6 +136,76 @@ class ServerConfiguration(BaseModel): server. login_rate_limit_minute: The number of login attempts allowed per minute. login_rate_limit_day: The number of login attempts allowed per day. + secure_headers_server: Custom value to be set in the `Server` HTTP + header to identify the server. If not specified, or if set to one of + the reserved values `enabled`, `yes`, `true`, `on`, the `Server` + header will be set to the default value (ZenML server ID). If set to + one of the reserved values `disabled`, `no`, `none`, `false`, `off` + or to an empty string, the `Server` header will not be included in + responses. + secure_headers_hsts: The server header value to be set in the HTTP + header `Strict-Transport-Security`. If not specified, or if set to + one of the reserved values `enabled`, `yes`, `true`, `on`, the + `Strict-Transport-Security` header will be set to the default value + (`max-age=63072000; includeSubdomains`). If set to one of + the reserved values `disabled`, `no`, `none`, `false`, `off` or to + an empty string, the `Strict-Transport-Security` header will not be + included in responses. + secure_headers_xfo: The server header value to be set in the HTTP + header `X-Frame-Options`. If not specified, or if set to one of the + reserved values `enabled`, `yes`, `true`, `on`, the `X-Frame-Options` + header will be set to the default value (`SAMEORIGIN`). If set to + one of the reserved values `disabled`, `no`, `none`, `false`, `off` + or to an empty string, the `X-Frame-Options` header will not be + included in responses. + secure_headers_xxp: The server header value to be set in the HTTP + header `X-XSS-Protection`. If not specified, or if set to one of the + reserved values `enabled`, `yes`, `true`, `on`, the `X-XSS-Protection` + header will be set to the default value (`0`). If set to one of the + reserved values `disabled`, `no`, `none`, `false`, `off` or + to an empty string, the `X-XSS-Protection` header will not be + included in responses. NOTE: this header is deprecated and should + always be set to `0`. The `Content-Security-Policy` header should be + used instead. + secure_headers_content: The server header value to be set in the HTTP + header `X-Content-Type-Options`. If not specified, or if set to one + of the reserved values `enabled`, `yes`, `true`, `on`, the + `X-Content-Type-Options` header will be set to the default value + (`nosniff`). If set to one of the reserved values `disabled`, `no`, + `none`, `false`, `off` or to an empty string, the + `X-Content-Type-Options` header will not be included in responses. + secure_headers_csp: The server header value to be set in the HTTP + header `Content-Security-Policy`. If not specified, or if set to one + of the reserved values `enabled`, `yes`, `true`, `on`, the + `Content-Security-Policy` header will be set to a default value + that is compatible with the ZenML dashboard. If set to one of the + reserved values `disabled`, `no`, `none`, `false`, `off` or to an + empty string, the `Content-Security-Policy` header will not be + included in responses. + secure_headers_referrer: The server header value to be set in the HTTP + header `Referrer-Policy`. If not specified, or if set to one of the + reserved values `enabled`, `yes`, `true`, `on`, the `Referrer-Policy` + header will be set to the default value + (`no-referrer-when-downgrade`). If set to one of the reserved values + `disabled`, `no`, `none`, `false`, `off` or to an empty string, the + `Referrer-Policy` header will not be included in responses. + secure_headers_cache: The server header value to be set in the HTTP + header `Cache-Control`. If not specified, or if set to one of the + reserved values `enabled`, `yes`, `true`, `on`, the `Cache-Control` + header will be set to the default value + (`no-store, no-cache, must-revalidate`). If set to one of the + reserved values `disabled`, `no`, `none`, `false`, `off` or to an + empty string, the `Cache-Control` header will not be included in + responses. + secure_headers_permissions: The server header value to be set in the + HTTP header `Permissions-Policy`. If not specified, or if set to one + of the reserved values `enabled`, `yes`, `true`, `on`, the + `Permissions-Policy` header will be set to the default value + (`accelerometer=(), camera=(), geolocation=(), gyroscope=(), + magnetometer=(), microphone=(), payment=(), usb=()`). If set to + one of the reserved values `disabled`, `no`, `none`, `false`, `off` + or to an empty string, the `Permissions-Policy` header will not be + included in responses. """ deployment_type: ServerDeploymentType = ServerDeploymentType.OTHER @@ -171,6 +249,32 @@ class ServerConfiguration(BaseModel): login_rate_limit_minute: int = DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_MINUTE login_rate_limit_day: int = DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_DAY + secure_headers_server: Union[bool, str] = True + secure_headers_hsts: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS + ) + secure_headers_xfo: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO + ) + secure_headers_xxp: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP + ) + secure_headers_content: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT + ) + secure_headers_csp: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP + ) + secure_headers_referrer: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER + ) + secure_headers_cache: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE + ) + secure_headers_permissions: Union[bool, str] = ( + DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS + ) + _deployment_id: Optional[UUID] = None @root_validator(pre=True) @@ -221,6 +325,16 @@ def _validate_config(cls, values: Dict[str, Any]) -> Dict[str, Any]: f"The server metadata is not a valid JSON string: {e}" ) + # if one of the secure headers options is set to a boolean value, set + # the corresponding value + for k, v in values.copy().items(): + if k.startswith("secure_headers_") and isinstance(v, str): + if v.lower() in ["disabled", "no", "none", "false", "off", ""]: + values[k] = False + if v.lower() in ["enabled", "yes", "true", "on"]: + # Revert to the default value if the header is enabled + del values[k] + return values @property diff --git a/src/zenml/constants.py b/src/zenml/constants.py index febf57ac3c..21f6cfe9ad 100644 --- a/src/zenml/constants.py +++ b/src/zenml/constants.py @@ -236,6 +236,34 @@ def handle_int_env_var(var: str, default: int = 0) -> int: DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_MINUTE = 5 DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_DAY = 1000 +DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS = ( + "max-age=63072000; includeSubdomains" +) +DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO = "SAMEORIGIN" +DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP = "0" +DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT = "nosniff" +DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP = ( + "default-src 'none'; " + "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + "connect-src 'self' https://sdkdocs.zenml.io https://hubapi.zenml.io; " + "img-src 'self' data: https://public-flavor-logos.s3.eu-central-1.amazonaws.com; " + "style-src 'self' 'unsafe-inline'; " + "base-uri 'self'; " + "form-action 'self'; " + "font-src 'self';" + "frame-src https://zenml.hellonext.co https://sdkdocs.zenml.io " +) +DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER = "no-referrer-when-downgrade" +DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE = ( + "no-store, no-cache, must-revalidate" +) +DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS = ( + "accelerometer=(), autoplay=(), camera=(), encrypted-media=(), " + "geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), " + "payment=(), sync-xhr=(), usb=()" +) +DEFAULT_ZENML_SERVER_SECURE_HEADERS_REPORT_TO = "default" + # Configurations to decide which resources report their usage and check for # entitlement in the case of a cloud deployment. Expected Format is this: # ENV_ZENML_REPORTABLE_RESOURCES='["Foo", "bar"]' diff --git a/src/zenml/zen_server/utils.py b/src/zenml/zen_server/utils.py index 2d6d9af132..2953621a1e 100644 --- a/src/zenml/zen_server/utils.py +++ b/src/zenml/zen_server/utils.py @@ -27,6 +27,7 @@ ) from urllib.parse import urlparse +import secure from pydantic import BaseModel, ValidationError from zenml.config.global_config import GlobalConfiguration @@ -59,6 +60,7 @@ _feature_gate: Optional[FeatureGateInterface] = None _workload_manager: Optional[WorkloadManagerInterface] = None _plugin_flavor_registry: Optional[PluginFlavorRegistry] = None +_secure_headers: Optional[secure.Secure] = None def zen_store() -> "SqlZenStore": @@ -214,6 +216,104 @@ def initialize_zen_store() -> None: _zen_store = zen_store_ +def secure_headers() -> secure.Secure: + """Return the secure headers component. + + Returns: + The secure headers component. + + Raises: + RuntimeError: If the secure headers component is not initialized. + """ + global _secure_headers + if _secure_headers is None: + raise RuntimeError("Secure headers component not initialized") + return _secure_headers + + +def initialize_secure_headers() -> None: + """Initialize the secure headers component.""" + global _secure_headers + + config = server_config() + + # For each of the secure headers supported by the `secure` library, we + # check if the corresponding configuration is set in the server + # configuration: + # + # - if set to `True`, we use the default value for the header + # - if set to a string, we use the string as the value for the header + # - if set to `False`, we don't set the header + + server: Optional[secure.Server] = None + if config.secure_headers_server: + server = secure.Server() + if isinstance(config.secure_headers_server, str): + server.set(config.secure_headers_server) + else: + server.set(str(config.deployment_id)) + + hsts: Optional[secure.StrictTransportSecurity] = None + if config.secure_headers_hsts: + hsts = secure.StrictTransportSecurity() + if isinstance(config.secure_headers_hsts, str): + hsts.set(config.secure_headers_hsts) + + xfo: Optional[secure.XFrameOptions] = None + if config.secure_headers_xfo: + xfo = secure.XFrameOptions() + if isinstance(config.secure_headers_xfo, str): + xfo.set(config.secure_headers_xfo) + + xxp: Optional[secure.XXSSProtection] = None + if config.secure_headers_xxp: + xxp = secure.XXSSProtection() + if isinstance(config.secure_headers_xxp, str): + xxp.set(config.secure_headers_xxp) + + csp: Optional[secure.ContentSecurityPolicy] = None + if config.secure_headers_csp: + csp = secure.ContentSecurityPolicy() + if isinstance(config.secure_headers_csp, str): + csp.set(config.secure_headers_csp) + + content: Optional[secure.XContentTypeOptions] = None + if config.secure_headers_content: + content = secure.XContentTypeOptions() + if isinstance(config.secure_headers_content, str): + content.set(config.secure_headers_content) + + referrer: Optional[secure.ReferrerPolicy] = None + if config.secure_headers_referrer: + referrer = secure.ReferrerPolicy() + if isinstance(config.secure_headers_referrer, str): + referrer.set(config.secure_headers_referrer) + + cache: Optional[secure.CacheControl] = None + if config.secure_headers_cache: + cache = secure.CacheControl() + if isinstance(config.secure_headers_cache, str): + cache.set(config.secure_headers_cache) + + permissions: Optional[secure.PermissionsPolicy] = None + if config.secure_headers_permissions: + permissions = secure.PermissionsPolicy() + if isinstance(config.secure_headers_permissions, str): + permissions.value = config.secure_headers_permissions + + _secure_headers = secure.Secure( + server=server, + hsts=hsts, + xfo=xfo, + xxp=xxp, + csp=csp, + content=content, + referrer=referrer, + cache=cache, + permissions=permissions, + ) + + _server_config: Optional[ServerConfiguration] = None diff --git a/src/zenml/zen_server/zen_server_api.py b/src/zenml/zen_server/zen_server_api.py index 2d455aeb3d..892dbc37e7 100644 --- a/src/zenml/zen_server/zen_server_api.py +++ b/src/zenml/zen_server/zen_server_api.py @@ -17,7 +17,6 @@ from asyncio.log import logger from typing import Any, List -import secure from fastapi import FastAPI, HTTPException, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import ORJSONResponse @@ -67,8 +66,10 @@ initialize_feature_gate, initialize_plugins, initialize_rbac, + initialize_secure_headers, initialize_workload_manager, initialize_zen_store, + secure_headers, server_config, ) @@ -121,13 +122,6 @@ def validation_exception_handler( allow_headers=["*"], ) -secure_headers = secure.Secure( - # TODO: Add a nonce to the CSP header when ZenML supports it - # (see https://content-security-policy.com/examples/allow-inline-script/) - # csp=secure.ContentSecurityPolicy(), - permissions=secure.PermissionsPolicy() -) - @app.middleware("http") async def set_secure_headers(request: Request, call_next: Any) -> Any: @@ -141,7 +135,7 @@ async def set_secure_headers(request: Request, call_next: Any) -> Any: The response with secure headers set. """ response = await call_next(request) - secure_headers.framework.fastapi(response) + secure_headers().framework.fastapi(response) return response @@ -187,6 +181,7 @@ def initialize() -> None: initialize_feature_gate() initialize_workload_manager() initialize_plugins() + initialize_secure_headers() app.mount( From 5f8ee3fa227e2d6701db508cd800d38032b03d52 Mon Sep 17 00:00:00 2001 From: Stefan Nica Date: Thu, 4 Apr 2024 17:07:44 +0200 Subject: [PATCH 3/4] Add secure headers options to helm chart and documentation --- .../zenml-self-hosted/deploy-with-docker.md | 18 ++++++++ .../deploy/helm/templates/_environment.tpl | 3 ++ src/zenml/zen_server/deploy/helm/values.yaml | 43 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md b/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md index d9103dfecf..04b471fe62 100644 --- a/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md +++ b/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md @@ -248,6 +248,24 @@ These configuration options are not required for most use cases, but can be usef openssl rand -hex 32 ``` +The environment variables starting with **ZENML\_SERVER\_SECURE*\_HEADERS\_** can be used to enable, disable or set custom values for security headers in the ZenML server's HTTP responses. The following values can be set for any of the supported secure headers configuration options: + +* `enabled`, `on`, `true` or `yes` - enables the secure header with the default value. +* `disabled`, `off`, `false`, `none` or `no` - disables the secure header entirely, so that it is not set in the ZenML server's HTTP responses. +* any other value - sets the secure header to the specified value. + +The following secure headers environment variables are supported: + +* **ZENML\_SERVER\_SECURE*\_HEADERS\_SERVER**: The `Server` HTTP header value used to identify the server. The default value is the ZenML server ID. +* **ZENML\_SERVER\_SECURE\_HEADERS\_HSTS**: The `Strict-Transport-Security` HTTP header value. The default value is `max-age=63072000; includeSubDomains`. +* **ZENML\_SERVER\_SECURE\_HEADERS\_XFO**: The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`. +* **ZENML\_SERVER\_SECURE\_HEADERS\_XXP**: The `X-XSS-Protection` HTTP header value. The default value is `0`. NOTE: this header is deprecated and should not be customized anymore. The `Content-Security-Policy` header should be used instead. +* **ZENML\_SERVER\_SECURE\_HEADERS\_CONTENT**: The `X-Content-Type-Options` HTTP header value. The default value is `nosniff`. +* **ZENML\_SERVER\_SECURE\_HEADERS\_CSP**: The `Content-Security-Policy` HTTP header value. This is by default set to a strict CSP policy that only allows content from the origins required by the ZenML dashboard. NOTE: customizing this header is discouraged, as it may cause the ZenML dashboard to malfunction. +* **ZENML\_SERVER\_SECURE\_HEADERS\_REFERRER**: The `Referrer-Policy` HTTP header value. The default value is `no-referrer-when-downgrade`. +* **ZENML\_SERVER\_SECURE\_HEADERS\_CACHE**: The `Cache-Control` HTTP header value. The default value is `no-store, no-cache, must-revalidate`. +* **ZENML\_SERVER\_SECURE\_HEADERS\_PERMISSIONS**: The `Permissions-Policy` HTTP header value. The default value is `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()`. + ## Run the ZenML server with Docker As previously mentioned, the ZenML server container image uses sensible defaults for most configuration options. This means that you can simply run the container with Docker without any additional configuration and it will work out of the box for most use cases: diff --git a/src/zenml/zen_server/deploy/helm/templates/_environment.tpl b/src/zenml/zen_server/deploy/helm/templates/_environment.tpl index 27a870e5fd..956e3621c2 100644 --- a/src/zenml/zen_server/deploy/helm/templates/_environment.tpl +++ b/src/zenml/zen_server/deploy/helm/templates/_environment.tpl @@ -88,6 +88,9 @@ base_url: {{ .ZenML.baseURL | quote }} {{- if .ZenML.auth.rbacImplementationSource }} rbac_implementation_source: {{ .ZenML.auth.rbacImplementationSource | quote }} {{- end }} +{{- range $key, $value := .ZenML.secure_headers }} +secure_headers_{{ $key }}: {{ $value | quote }} +{{- end }} {{- end }} diff --git a/src/zenml/zen_server/deploy/helm/values.yaml b/src/zenml/zen_server/deploy/helm/values.yaml index 0098ab64a9..f43cb6a104 100644 --- a/src/zenml/zen_server/deploy/helm/values.yaml +++ b/src/zenml/zen_server/deploy/helm/values.yaml @@ -787,6 +787,49 @@ zenml: # variable in the `zenml.secretEnvironment` variable. class_path: my.custom.secrets.store.MyCustomSecretsStore + # The ZenML server's secure headers configuration. This can be used to + # enable, disable or set custom values for security headers in the ZenML + # server's HTTP responses. The following values can be set for any of the + # supported secure headers configuration options: + # + # - `enabled`, `on`, `true` or `yes` - enables the secure header with the + # default value. + # - `disabled`, `off`, `false`, `none` or `no` - disables the secure header + # entirely, so that it is not set in the ZenML server's HTTP responses. + # - any other value - sets the secure header to the specified value. + secure_headers: + # The `Server` HTTP header value used to identify the server. The default + # value is the ZenML server ID. + server: enabled + # The `Strict-Transport-Security` HTTP header value. The default value is + # `max-age=63072000; includeSubDomains`. + hsts: enabled + # The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`. + xfo: enabled + # The `X-XSS-Protection` HTTP header value. The default value is `0`. + # NOTE: this header is deprecated and should not be customized anymore. The + # `Content-Security-Policy` header should be used instead. + xxp: enabled + # The `X-Content-Type-Options` HTTP header value. The default value is + # `nosniff`. + content: enabled + # The `Content-Security-Policy` HTTP header value. This is by default set + # to a strict CSP policy that only allows content from the origins required + # by the ZenML dashboard. + # NOTE: customizing this header is discouraged, as it may cause the ZenML + # dashboard to malfunction. + csp: enabled + # The `Referrer-Policy` HTTP header value. The default value is + # `no-referrer-when-downgrade`. + referrer: enabled + # The `Cache-Control` HTTP header value. The default value is + # `no-store, no-cache, must-revalidate`. + cache: enabled + # The `Permissions-Policy` HTTP header value. The default value is + # `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()`. + permissions: enabled + + # Extra environment variables to set in the ZenML server container. environment: {} From fa66f09ef436ec3b9fcad5e9da4188c05ed32c9f Mon Sep 17 00:00:00 2001 From: Stefan Nica Date: Thu, 4 Apr 2024 17:23:51 +0200 Subject: [PATCH 4/4] Disable uvicorn's server headers --- docker/base.Dockerfile | 2 +- docker/zenml-server-dev.Dockerfile | 2 +- docker/zenml-server-hf-spaces.Dockerfile | 2 +- src/zenml/zen_server/deploy/docker/docker_zen_server.py | 1 + src/zenml/zen_server/deploy/local/local_zen_server.py | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile index da53fbe8c6..6fdd6acaf7 100644 --- a/docker/base.Dockerfile +++ b/docker/base.Dockerfile @@ -37,5 +37,5 @@ RUN groupadd --gid $USER_GID $USERNAME \ RUN mkdir -p /zenml/.zenconfig/local_stores/default_zen_store && chown -R $USER_UID:$USER_GID /zenml ENV PATH="$PATH:/home/$USERNAME/.local/bin" -ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug"] +ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"] CMD ["--port", "8080", "--host", "0.0.0.0"] diff --git a/docker/zenml-server-dev.Dockerfile b/docker/zenml-server-dev.Dockerfile index 2627285385..37193f8ecd 100644 --- a/docker/zenml-server-dev.Dockerfile +++ b/docker/zenml-server-dev.Dockerfile @@ -46,5 +46,5 @@ ENV ZENML_CONFIG_PATH=/zenml/.zenconfig \ ZENML_DEBUG=true \ ZENML_ANALYTICS_OPT_IN=false -ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug"] +ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"] CMD ["--port", "8080", "--host", "0.0.0.0"] diff --git a/docker/zenml-server-hf-spaces.Dockerfile b/docker/zenml-server-hf-spaces.Dockerfile index 305589948b..886e8aa688 100644 --- a/docker/zenml-server-hf-spaces.Dockerfile +++ b/docker/zenml-server-hf-spaces.Dockerfile @@ -59,5 +59,5 @@ ENV ZENML_SERVER_DEPLOYMENT_TYPE="hf_spaces" # ENV ZENML_SECRETS_STORE_VAULT_NAMESPACE="" # ENV ZENML_SECRETS_STORE_MAX_VERSIONS="" -ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug"] +ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"] CMD ["--port", "8080", "--host", "0.0.0.0"] diff --git a/src/zenml/zen_server/deploy/docker/docker_zen_server.py b/src/zenml/zen_server/deploy/docker/docker_zen_server.py index 58c0216583..fdaad7c470 100644 --- a/src/zenml/zen_server/deploy/docker/docker_zen_server.py +++ b/src/zenml/zen_server/deploy/docker/docker_zen_server.py @@ -205,6 +205,7 @@ def run(self) -> None: host="0.0.0.0", # nosec port=self.endpoint.config.port or 8000, log_level="info", + server_header=False, ) except KeyboardInterrupt: logger.info("ZenML Server stopped. Resuming normal execution.") diff --git a/src/zenml/zen_server/deploy/local/local_zen_server.py b/src/zenml/zen_server/deploy/local/local_zen_server.py index 8f5041d9de..60bd0ff0bd 100644 --- a/src/zenml/zen_server/deploy/local/local_zen_server.py +++ b/src/zenml/zen_server/deploy/local/local_zen_server.py @@ -228,6 +228,7 @@ def run(self) -> None: host=self.endpoint.config.ip_address, port=self.endpoint.config.port or 8000, log_level="info", + server_header=False, ) except KeyboardInterrupt: logger.info("ZenML Server stopped. Resuming normal execution.")