From 3e992eea99411a75aeb6eb41fe60100b0e6afb59 Mon Sep 17 00:00:00 2001 From: "J.C. Zhong" Date: Wed, 13 Mar 2024 15:55:16 -0700 Subject: [PATCH] fix: enable websocket cors for production (#1425) * fix: enable websocket cors for production * fix linter * add breaking change --- .../docs/changelog/breaking_change.mdx | 4 ++ .../docs/configurations/infra_config.mdx | 58 +++++++++++-------- package.json | 2 +- .../config/querybook_default_config.yaml | 4 ++ querybook/server/app/flask_app.py | 6 +- querybook/server/env.py | 1 + 6 files changed, 48 insertions(+), 27 deletions(-) diff --git a/docs_website/docs/changelog/breaking_change.mdx b/docs_website/docs/changelog/breaking_change.mdx index 4ce0f97ae..1c5037d61 100644 --- a/docs_website/docs/changelog/breaking_change.mdx +++ b/docs_website/docs/changelog/breaking_change.mdx @@ -7,6 +7,10 @@ slug: /changelog Here are the list of breaking changes that you should be aware of when updating Querybook: +## v3.32.0 + +Added config `WS_CORS_ALLOWED_ORIGINS` to configure allowed CORS origins for WebSocket connection. This is required for Prod environment. + ## v3.31.0 Upgraded langchain to [0.1.6](https://blog.langchain.dev/langchain-v0-1-0/). diff --git a/docs_website/docs/configurations/infra_config.mdx b/docs_website/docs/configurations/infra_config.mdx index 38528c2f9..490dbb349 100644 --- a/docs_website/docs/configurations/infra_config.mdx +++ b/docs_website/docs/configurations/infra_config.mdx @@ -34,6 +34,10 @@ Otherwise you can also pass the environment variable directly when launching the `FLASK_CACHE_CONFIG` (optional): This can be used to provide caching for API endpoints and internal logic. Follow https://pythonhosted.org/Flask-Cache/ for more details. You should provide a serialized JSON dictionary to be passed into the config. +### WebSocket + +`WS_CORS_ALLOWED_ORIGINS`: (**required for production**): This is the allowed list of origins for CORS. For dev environment, all origins will be allowed. + ### Database `DATABASE_CONN` (**required**): A sqlalchemy connection string to the database. Please check here https://docs.sqlalchemy.org/en/13/core/engines.html for formatting. @@ -100,6 +104,7 @@ You can also add addtional loggers in the event logger plugin. See [Add Event Lo - console: This will print the stats logs to the console. Could be used for debugging purpose. You need to add your own stats logger plugin to use it. See [Add Stats Logger guide](../integrations/add_stats_logger.mdx) for more details. + ## Authentication `AUTH_BACKEND` (optional, defaults to **app.auth.password_auth**): Python path to the authentication file. By default Querybook provides: @@ -119,31 +124,34 @@ the next few configurations are only relevant if you are using OAuth based authe for LDAP authentication: -- `LDAP_CONN`(**required**) -- `LDAP_USE_TLS` (optional, defaults to `False`) -- `LDAP_USE_BIND_USER` (optional, defaults to `False`) - - If `False`: Direct LDAP login - - Additional configuration: - - `LDAP_USER_DN` (**required**) DN with {} for username/etc (ex. `uid={},dc=example,dc=com`) - - Login flow: - - Direct login using formatted `LDAP_USER_DN` + password - - If `True`: Advanced LDAP login using _bind user_ - - Additional configuration: - - `LDAP_BIND_USER` (**required**) Name of a _bind user_ - - `LDAP_BIND_PASSWORD` (**required**) Password of a _bind user_ - - `LDAP_SEARCH` (**required**) LDAP search base (ex. `ou=people,dc=example,dc=com`) - - `LDAP_FILTER` (optional) LDAP filter condition (ex. `(departmentNumber=01000)`) - - `LDAP_UID_FIELD` (optional) Field that matches the username when searching for the account to bind to (defaults to `uid`) - - `LDAP_EMAIL_FIELD`: (optional) Field that matches the user email (default to `mail`) - - `LDAP_LASTNAME_FIELD`: (optional) Field that matches the user surname (default to `sn`) - - `LDAP_FIRSTNAME_FIELD`: (optional) Field that matches the user given name (default to `givenName`) - - `LDAP_FULLNAME_FIELD`: (optional) Field that matches the user full/common name (default to `cn`) - - - Login flow: - 1) Initialized connection for the _bind user_. - 2) Searching the _login user_ using the _bind user_ in LDAP dictionary based on `LDAP_SEARCH` and `LDAP_FILTER`. - 3) The _login user_ credentials are tested in direct login. - 4) If the previous steps were OK, the user is passed on. +- `LDAP_CONN`(**required**) +- `LDAP_USE_TLS` (optional, defaults to `False`) +- `LDAP_USE_BIND_USER` (optional, defaults to `False`) + + - If `False`: Direct LDAP login + - Additional configuration: + - `LDAP_USER_DN` (**required**) DN with {} for username/etc (ex. `uid={},dc=example,dc=com`) + - Login flow: + - Direct login using formatted `LDAP_USER_DN` + password + - If `True`: Advanced LDAP login using _bind user_ + + - Additional configuration: + + - `LDAP_BIND_USER` (**required**) Name of a _bind user_ + - `LDAP_BIND_PASSWORD` (**required**) Password of a _bind user_ + - `LDAP_SEARCH` (**required**) LDAP search base (ex. `ou=people,dc=example,dc=com`) + - `LDAP_FILTER` (optional) LDAP filter condition (ex. `(departmentNumber=01000)`) + - `LDAP_UID_FIELD` (optional) Field that matches the username when searching for the account to bind to (defaults to `uid`) + - `LDAP_EMAIL_FIELD`: (optional) Field that matches the user email (default to `mail`) + - `LDAP_LASTNAME_FIELD`: (optional) Field that matches the user surname (default to `sn`) + - `LDAP_FIRSTNAME_FIELD`: (optional) Field that matches the user given name (default to `givenName`) + - `LDAP_FULLNAME_FIELD`: (optional) Field that matches the user full/common name (default to `cn`) + + - Login flow: + 1. Initialized connection for the _bind user_. + 2. Searching the _login user_ using the _bind user_ in LDAP dictionary based on `LDAP_SEARCH` and `LDAP_FILTER`. + 3. The _login user_ credentials are tested in direct login. + 4. If the previous steps were OK, the user is passed on. If you want to force the user to login again after a certain time, you can the following variable: diff --git a/package.json b/package.json index b36beb417..ca11426e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "querybook", - "version": "3.31.2", + "version": "3.32.0", "description": "A Big Data Webapp", "private": true, "scripts": { diff --git a/querybook/config/querybook_default_config.yaml b/querybook/config/querybook_default_config.yaml index 30e5a3f65..42bd611c2 100644 --- a/querybook/config/querybook_default_config.yaml +++ b/querybook/config/querybook_default_config.yaml @@ -54,6 +54,10 @@ LDAP_LASTNAME_FIELD: sn LDAP_FIRSTNAME_FIELD: givenName LDAP_FULLNAME_FIELD: cn +# Websocket CORS allowed origins +WS_CORS_ALLOWED_ORIGINS: + - http://localhost:10001 + # --------------- Result Store --------------- RESULT_STORE_TYPE: db diff --git a/querybook/server/app/flask_app.py b/querybook/server/app/flask_app.py index f675a2282..7041f489a 100644 --- a/querybook/server/app/flask_app.py +++ b/querybook/server/app/flask_app.py @@ -131,7 +131,11 @@ def make_socketio(app): path="-/socket.io", message_queue=QuerybookSettings.REDIS_URL, json=flask_json, - cors_allowed_origins="*", + cors_allowed_origins=( + QuerybookSettings.WS_CORS_ALLOWED_ORIGINS + if QuerybookSettings.PRODUCTION + else "*" + ), ) return socketio diff --git a/querybook/server/env.py b/querybook/server/env.py index 7e59833ab..c27086f23 100644 --- a/querybook/server/env.py +++ b/querybook/server/env.py @@ -55,6 +55,7 @@ class QuerybookSettings(object): PUBLIC_URL = get_env_config("PUBLIC_URL") FLASK_SECRET_KEY = get_env_config("FLASK_SECRET_KEY", optional=False) FLASK_CACHE_CONFIG = get_env_config("FLASK_CACHE_CONFIG") + WS_CORS_ALLOWED_ORIGINS = get_env_config("WS_CORS_ALLOWED_ORIGINS", optional=False) # Celery REDIS_URL = get_env_config("REDIS_URL", optional=False)