From 472e2a5d11ad4be0f7248c1616674766b0278ac3 Mon Sep 17 00:00:00 2001 From: Sylvain Leclerc Date: Thu, 12 Sep 2024 09:50:02 +0200 Subject: [PATCH] fix(security): let the user decide to disable SSL verification Signed-off-by: Sylvain Leclerc --- antarest/tools/cli.py | 15 ++++++- antarest/tools/lib.py | 40 +++++++++---------- .../test_integration_variantmanager_tool.py | 6 ++- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/antarest/tools/cli.py b/antarest/tools/cli.py index d1b91d522b..85eef22128 100644 --- a/antarest/tools/cli.py +++ b/antarest/tools/cli.py @@ -15,10 +15,11 @@ from typing import Optional import click +from httpx import Client from antarest.study.model import NEW_DEFAULT_STUDY_VERSION from antarest.study.storage.study_upgrader import StudyUpgrader -from antarest.tools.lib import extract_commands, generate_diff, generate_study +from antarest.tools.lib import create_http_client, extract_commands, generate_diff, generate_study @click.group(context_settings={"max_content_width": 120}) @@ -43,6 +44,12 @@ def commands() -> None: type=str, help="Authentication token if server needs one", ) +@click.option( + "--no-verify", + is_flag=True, + default=False, + help="Disables SSL certificate verification", +) @click.option( "--output", "-o", @@ -82,6 +89,7 @@ def cli_apply_script( output: Optional[str], host: Optional[str], auth_token: Optional[str], + no_verify: bool, version: str, ) -> None: """Apply a variant script onto an AntaresWeb study variant""" @@ -95,7 +103,10 @@ def cli_apply_script( print("--study_id must be set") exit(1) - res = generate_study(Path(input), study_id, output, host, auth_token, version) + client = None + if host: + client = create_http_client(verify=not no_verify, auth_token=auth_token) + res = generate_study(Path(input), study_id, output, host, client, version) print(res) diff --git a/antarest/tools/lib.py b/antarest/tools/lib.py index e2e876f723..6bfd3df997 100644 --- a/antarest/tools/lib.py +++ b/antarest/tools/lib.py @@ -51,29 +51,28 @@ def apply_commands(self, commands: List[CommandDTO], matrices_dir: Path) -> Gene raise NotImplementedError() +def set_auth_token(client: Client, auth_token: Optional[str] = None) -> Client: + if auth_token is not None: + client.headers.update({"Authorization": f"Bearer {auth_token}"}) + return client + + +def create_http_client(verify: bool, auth_token: Optional[str] = None) -> Client: + client = Client(verify=verify) + set_auth_token(client, auth_token) + return client + + class RemoteVariantGenerator(IVariantGenerator): def __init__( self, study_id: str, - host: Optional[str] = None, - token: Optional[str] = None, - session: Optional[Client] = None, + host: str, + session: Client, ): self.study_id = study_id - - # todo: find the correct way to handle certificates. - # By default, Httpx verifies SSL certificates for HTTPS requests. - # When verify is set to `False`, requests will accept any TLS certificate presented - # by the server, and will ignore hostname mismatches and/or expired certificates, - # which will make your application vulnerable to man-in-the-middle (MitM) attacks. - # Setting verify to `False` may be useful during local development or testing. - self.session = session or Client(verify=False) - + self.session = session self.host = host - if session is None and host is None: - raise ValueError("Missing either session or host") - if token is not None: - self.session.headers.update({"Authorization": f"Bearer {token}"}) def apply_commands( self, @@ -333,7 +332,7 @@ def generate_study( study_id: Optional[str], output: Optional[str] = None, host: Optional[str] = None, - token: Optional[str] = None, + session: Optional[Client] = None, study_version: str = NEW_DEFAULT_STUDY_VERSION, ) -> GenerationResultInfoDTO: """ @@ -347,7 +346,7 @@ def generate_study( If `study_id` and `host` are not provided, this must be specified. host: The URL of the Antares server to use for generating the new study. If `study_id` is not provided, this is ignored. - token: The authentication token to use when connecting to the Antares server. + session: The session to use when connecting to the Antares server. If `host` is not provided, this is ignored. study_version: The target version of the generated study. @@ -355,8 +354,9 @@ def generate_study( GenerationResultInfoDTO: A data transfer object containing information about the generation result. """ generator: Union[RemoteVariantGenerator, LocalVariantGenerator] - if study_id is not None and host is not None: - generator = RemoteVariantGenerator(study_id, host, token) + + if study_id is not None and host is not None and session is not None: + generator = RemoteVariantGenerator(study_id, host, session) elif output is None: raise TypeError("'output' must be set") else: diff --git a/tests/integration/test_integration_variantmanager_tool.py b/tests/integration/test_integration_variantmanager_tool.py index 605c2659f5..85783fe789 100644 --- a/tests/integration/test_integration_variantmanager_tool.py +++ b/tests/integration/test_integration_variantmanager_tool.py @@ -28,10 +28,12 @@ COMMAND_FILE, MATRIX_STORE_DIR, RemoteVariantGenerator, + create_http_client, extract_commands, generate_diff, generate_study, parse_commands, + set_auth_token, ) from tests.integration.assets import ASSETS_DIR @@ -62,7 +64,9 @@ def generate_study_with_server( ) assert res.status_code == 200, res.json() variant_id = res.json() - generator = RemoteVariantGenerator(variant_id, session=client, token=admin_credentials["access_token"]) + + set_auth_token(client, admin_credentials["access_token"]) + generator = RemoteVariantGenerator(variant_id, host="", session=client) return generator.apply_commands(commands, matrices_dir), variant_id