-
Notifications
You must be signed in to change notification settings - Fork 0
Improve security when one tenant is comporomised #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| auth: | ||
| allowed_apis: | ||
| - url: "https://your-dsw-instance.example.com/wizard-api" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean that every tenant must now be explicitly listed in the configuration? |
||
| tenant_uuid: "123e4567-e89b-12d3-a456-426614174000" | ||
|
|
||
| logging: | ||
| level: "INFO" | ||
|
|
||
| database: | ||
| host: "localhost" | ||
| port: "5432" | ||
| name: "ai_document_plugin" | ||
| user: "ai_document_plugin" | ||
| password: "ai_document_plugin" | ||
| schema: "public" | ||
|
|
||
| files: | ||
| prompts_path: "prompts.yaml" | ||
|
|
||
| max_parallel_executions: 2 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,17 @@ | ||
| import logging | ||
| import os | ||
| import pathlib | ||
| from dataclasses import dataclass | ||
| from typing import Any | ||
| from uuid import UUID | ||
|
|
||
| import yaml | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| DEFAULT_CONFIG_PATH = 'config.yaml' | ||
| CONFIG_PATH_ENV_VAR = 'AI_DOCUMENT_PLUGIN_CONFIG_PATH' | ||
| WILDCARD = '*' | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
|
|
@@ -29,6 +34,12 @@ class FilePaths: | |
| prompts_path: str | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class AllowedApi: | ||
| url: str | ||
| tenant_uuid: str | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class DatabaseConfig: | ||
| host: str | ||
|
|
@@ -41,7 +52,7 @@ class DatabaseConfig: | |
|
|
||
| @dataclass(frozen=True) | ||
| class Config: | ||
| allowed_project_urls: tuple[str, ...] | ||
| allowed_apis: tuple[AllowedApi, ...] | ||
| log_level: str | ||
| database: DatabaseConfig | ||
| files: FilePaths | ||
|
|
@@ -114,20 +125,49 @@ def normalize_project_url(url: str) -> str: | |
| return url.strip().rstrip('/') | ||
|
|
||
|
|
||
| def _get_allowed_project_urls(config: dict[str, Any]) -> tuple[str, ...]: | ||
| raw_urls = _get(config, 'auth', 'allowed_project_urls') | ||
| if not isinstance(raw_urls, list) or not raw_urls: | ||
| msg = "Invalid config value: 'auth.allowed_project_urls' must be a non-empty list" | ||
| raise ValueError(msg) | ||
| def _get_allowed_apis(config: dict[str, Any]) -> tuple[AllowedApi, ...]: | ||
| raw_apis = _get(config, 'auth', 'allowed_apis') | ||
| if not isinstance(raw_apis, list) or not raw_apis: | ||
| msg = "Invalid config value: 'auth.allowed_apis' must be a non-empty list" | ||
| raise TypeError(msg) | ||
|
|
||
| normalized: list[str] = [] | ||
| for index, entry in enumerate(raw_urls): | ||
| if not isinstance(entry, str) or not entry.strip(): | ||
| msg = f"Invalid config value: 'auth.allowed_project_urls[{index}]' must be a non-empty string" | ||
| allowed_apis: list[AllowedApi] = [] | ||
| for index, entry in enumerate(raw_apis): | ||
| if not isinstance(entry, dict): | ||
| msg = f"Invalid config value: 'auth.allowed_apis[{index}]' must be a mapping" | ||
| raise TypeError(msg) | ||
|
|
||
| raw_url = entry.get('url') | ||
| raw_tenant_uuid = entry.get('tenant_uuid') | ||
| if not isinstance(raw_url, str) or not raw_url.strip(): | ||
| msg = f"Invalid config value: 'auth.allowed_apis[{index}].url' must be a non-empty string" | ||
| raise ValueError(msg) | ||
| if not isinstance(raw_tenant_uuid, str) or not raw_tenant_uuid.strip(): | ||
| msg = f"Invalid config value: 'auth.allowed_apis[{index}].tenant_uuid' must be a non-empty string" | ||
| raise ValueError(msg) | ||
| normalized.append(normalize_project_url(entry)) | ||
|
|
||
| return tuple(normalized) | ||
| url = raw_url.strip() | ||
| tenant_uuid = raw_tenant_uuid.strip() | ||
| normalized_url = url if url == WILDCARD else normalize_project_url(url) | ||
| if tenant_uuid != WILDCARD: | ||
| try: | ||
| tenant_uuid = str(UUID(tenant_uuid)) | ||
| except ValueError as error: | ||
| msg = f"Invalid config value: 'auth.allowed_apis[{index}].tenant_uuid' must be a UUID or '*'" | ||
| raise ValueError(msg) from error | ||
|
|
||
| if WILDCARD in {normalized_url, tenant_uuid}: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be useful to introduce separate production and development modes in the future. But maybe it is too much for this project, just an idea. |
||
| logger.warning( | ||
| "Do not use in production! Config 'auth.allowed_apis[%d]' uses a wildcard (url=%s, tenant_uuid=%s) " | ||
| 'which allows anyone to use this API.', | ||
| index, | ||
| normalized_url, | ||
| tenant_uuid, | ||
| ) | ||
|
|
||
| allowed_apis.append(AllowedApi(url=normalized_url, tenant_uuid=tenant_uuid)) | ||
|
|
||
| return tuple(allowed_apis) | ||
|
|
||
|
|
||
| def _resolve_existing_path(path: str, *, base_dir: pathlib.Path | None = None) -> str: | ||
|
|
@@ -182,7 +222,7 @@ def load_config(config_path: str | None = None) -> Config: | |
| raise TypeError(msg) | ||
|
|
||
| return Config( | ||
| allowed_project_urls=_get_allowed_project_urls(config), | ||
| allowed_apis=_get_allowed_apis(config), | ||
| log_level=_get_log_level(config), | ||
| database=DatabaseConfig( | ||
| host=_expand_env_vars(_get(config, 'database', 'host')), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"The original approach to authentication where if a single tenant was compromised, attacker could issue uuids with any tenant uuid they want, which would compromise all other tenants as well."
What uuids do you mean? Like new tenant uuis?