From 7027caf878c092ee0228c82d9c930c6bfc72e7a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:06:16 +0000 Subject: [PATCH 1/7] Initial plan From 74504fed6111700f8a1667dda1323891d65aff90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:10:13 +0000 Subject: [PATCH 2/7] Add logic to default resource_port to 443 for managed clusters Co-authored-by: aavalang <56377848+aavalang@users.noreply.github.com> --- src/bastion/azext_bastion/_help.py | 3 +++ src/bastion/azext_bastion/_params.py | 4 ++-- src/bastion/azext_bastion/custom.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/bastion/azext_bastion/_help.py b/src/bastion/azext_bastion/_help.py index fe1380a6981..72a29681fbf 100644 --- a/src/bastion/azext_bastion/_help.py +++ b/src/bastion/azext_bastion/_help.py @@ -51,4 +51,7 @@ - name: Open a tunnel through Azure Bastion to a target virtual machine using its IP address. text: | az network bastion tunnel --name MyBastionHost --resource-group MyResourceGroup --target-ip-address 10.0.0.1 --resource-port 22 --port 50022 + - name: Open a tunnel through Azure Bastion to a managed cluster (resource-port defaults to 443). + text: | + az network bastion tunnel --name MyBastionHost --resource-group MyResourceGroup --target-resource-id managedClusterResourceId --port 50443 """ diff --git a/src/bastion/azext_bastion/_params.py b/src/bastion/azext_bastion/_params.py index faa2379860b..586eb3e9f3c 100644 --- a/src/bastion/azext_bastion/_params.py +++ b/src/bastion/azext_bastion/_params.py @@ -23,8 +23,6 @@ def load_arguments(self, _): # pylint: disable=unused-argument with self.argument_context("network bastion") as c: c.argument("bastion_host_name", bastion_host_name_type, options_list=["--name", "-n"]) - c.argument("resource_port", help="Resource port of the target VM to which the bastion will connect.", - options_list=["--resource-port"]) c.argument("target_resource_id", help="ResourceId of the target Virtual Machine.", required=False, options_list=["--target-resource-id"]) c.argument("target_ip_address", help="IP address of target Virtual Machine.", required=False, @@ -46,5 +44,7 @@ def load_arguments(self, _): # pylint: disable=unused-argument "Available on Windows 10 20H2+, Windows 11 21H2+, WS 2022.", arg_type=get_three_state_flag()) with self.argument_context("network bastion tunnel") as c: + c.argument("resource_port", help="Resource port of the target resource to which the bastion will connect. Defaults to 443 for managed clusters.", + options_list=["--resource-port"], required=False) c.argument("port", help="Local port to use for the tunneling.", options_list=["--port"]) c.argument("timeout", help="Timeout for connection to bastion host tunnel.", options_list=["--timeout"]) diff --git a/src/bastion/azext_bastion/custom.py b/src/bastion/azext_bastion/custom.py index 82a5fd3520e..240bf0bcb95 100644 --- a/src/bastion/azext_bastion/custom.py +++ b/src/bastion/azext_bastion/custom.py @@ -413,6 +413,13 @@ def _validate_resourceid(target_resource_id): raise InvalidArgumentValueError(err_msg) +def _is_managed_cluster(target_resource_id): + """Check if the target resource is a managed cluster (AKS).""" + if not target_resource_id: + return False + return "Microsoft.ContainerService/managedClusters" in target_resource_id.lower() + + def _get_bastion_endpoint(cmd, bastion, resource_port, target_resource_id): if bastion['sku']['name'] == BastionSku.QuickConnect.value or bastion['sku']['name'] == BastionSku.Developer.value: from .developer_sku_helper import (_get_data_pod) @@ -467,6 +474,14 @@ def create_bastion_tunnel(cmd, target_resource_id, target_ip_address, resource_g target_resource_id = f"/subscriptions/{get_subscription_id(cmd.cli_ctx)}/resourceGroups/" \ f"{resource_group_name}/providers/Microsoft.Network/bh-hostConnect/{target_ip_address}" + # Default resource_port to 443 for managed clusters if not provided + if not resource_port and _is_managed_cluster(target_resource_id): + resource_port = 443 + + # Validate that resource_port is provided for non-managed cluster targets + if not resource_port: + raise RequiredArgumentMissingError("--resource-port is required for non-managed cluster targets.") + if ip_connect and int(resource_port) not in [22, 3389]: raise UnrecognizedArgumentError("Custom ports are not allowed. Allowed ports for Tunnel with IP connect is \ 22, 3389.") From 0bdcda4bc71f9a12a6c9c0e03214369320b2a7a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:12:23 +0000 Subject: [PATCH 3/7] Fix _is_managed_cluster logic to use lowercase comparison Co-authored-by: aavalang <56377848+aavalang@users.noreply.github.com> --- src/bastion/azext_bastion/custom.py | 2 +- .../tests/latest/test_bastion.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/bastion/azext_bastion/custom.py b/src/bastion/azext_bastion/custom.py index 240bf0bcb95..a4ca106c745 100644 --- a/src/bastion/azext_bastion/custom.py +++ b/src/bastion/azext_bastion/custom.py @@ -417,7 +417,7 @@ def _is_managed_cluster(target_resource_id): """Check if the target resource is a managed cluster (AKS).""" if not target_resource_id: return False - return "Microsoft.ContainerService/managedClusters" in target_resource_id.lower() + return "microsoft.containerservice/managedclusters" in target_resource_id.lower() def _get_bastion_endpoint(cmd, bastion, resource_port, target_resource_id): diff --git a/src/bastion/azext_bastion/tests/latest/test_bastion.py b/src/bastion/azext_bastion/tests/latest/test_bastion.py index fb0f1829118..69f13a9c8e7 100644 --- a/src/bastion/azext_bastion/tests/latest/test_bastion.py +++ b/src/bastion/azext_bastion/tests/latest/test_bastion.py @@ -7,10 +7,31 @@ # pylint: disable=line-too-long +import unittest from azure.cli.testsdk import * from azure.cli.testsdk.scenario_tests import AllowLargeResponse +class BastionUnitTests(unittest.TestCase): + def test_is_managed_cluster(self): + """Test the _is_managed_cluster helper function""" + from azext_bastion.custom import _is_managed_cluster + + # Test managed cluster resource ID + managed_cluster_id = "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/myRG/providers/Microsoft.ContainerService/managedClusters/myAKS" + self.assertTrue(_is_managed_cluster(managed_cluster_id)) + + # Test VM resource ID + vm_id = "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM" + self.assertFalse(_is_managed_cluster(vm_id)) + + # Test None + self.assertFalse(_is_managed_cluster(None)) + + # Test empty string + self.assertFalse(_is_managed_cluster("")) + + class BastionScenario(ScenarioTest): @AllowLargeResponse(size_kb=9999) @ResourceGroupPreparer(name_prefix="cli_test_bastion_host_", location="eastus") From 95769e450dd399b085c829d2871fbd3947991eb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:14:01 +0000 Subject: [PATCH 4/7] Improve logic order and add validation for resource_port Co-authored-by: aavalang <56377848+aavalang@users.noreply.github.com> --- src/bastion/azext_bastion/custom.py | 33 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/bastion/azext_bastion/custom.py b/src/bastion/azext_bastion/custom.py index a4ca106c745..9354bb6aaa1 100644 --- a/src/bastion/azext_bastion/custom.py +++ b/src/bastion/azext_bastion/custom.py @@ -473,18 +473,27 @@ def create_bastion_tunnel(cmd, target_resource_id, target_ip_address, resource_g if ip_connect: target_resource_id = f"/subscriptions/{get_subscription_id(cmd.cli_ctx)}/resourceGroups/" \ f"{resource_group_name}/providers/Microsoft.Network/bh-hostConnect/{target_ip_address}" - - # Default resource_port to 443 for managed clusters if not provided - if not resource_port and _is_managed_cluster(target_resource_id): - resource_port = 443 - - # Validate that resource_port is provided for non-managed cluster targets - if not resource_port: - raise RequiredArgumentMissingError("--resource-port is required for non-managed cluster targets.") - - if ip_connect and int(resource_port) not in [22, 3389]: - raise UnrecognizedArgumentError("Custom ports are not allowed. Allowed ports for Tunnel with IP connect is \ - 22, 3389.") + + # For IP connect, validate resource_port is provided and is valid + if not resource_port: + raise RequiredArgumentMissingError("--resource-port is required for IP connect.") + + try: + port_int = int(resource_port) + except (TypeError, ValueError): + raise InvalidArgumentValueError(f"Invalid resource port: {resource_port}. Must be a valid integer.") + + if port_int not in [22, 3389]: + raise UnrecognizedArgumentError("Custom ports are not allowed. Allowed ports for Tunnel with IP connect is \ + 22, 3389.") + else: + # Default resource_port to 443 for managed clusters if not provided + if not resource_port and _is_managed_cluster(target_resource_id): + resource_port = 443 + + # Validate that resource_port is provided for non-managed cluster targets + if not resource_port: + raise RequiredArgumentMissingError("--resource-port is required for non-managed cluster targets.") _validate_resourceid(target_resource_id) bastion_endpoint = _get_bastion_endpoint(cmd, bastion, resource_port, target_resource_id) From 9273f399d1471323d3bc7dcceec88c74c875b8bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:15:16 +0000 Subject: [PATCH 5/7] Add constant for default port and fix grammar in error message Co-authored-by: aavalang <56377848+aavalang@users.noreply.github.com> --- src/bastion/azext_bastion/custom.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bastion/azext_bastion/custom.py b/src/bastion/azext_bastion/custom.py index 9354bb6aaa1..1837dc80482 100644 --- a/src/bastion/azext_bastion/custom.py +++ b/src/bastion/azext_bastion/custom.py @@ -31,6 +31,9 @@ logger = get_logger(__name__) +# Default port for managed cluster tunnel connections +DEFAULT_MANAGED_CLUSTER_PORT = 443 + class BastionCreate(_BastionCreate): @classmethod @@ -484,12 +487,12 @@ def create_bastion_tunnel(cmd, target_resource_id, target_ip_address, resource_g raise InvalidArgumentValueError(f"Invalid resource port: {resource_port}. Must be a valid integer.") if port_int not in [22, 3389]: - raise UnrecognizedArgumentError("Custom ports are not allowed. Allowed ports for Tunnel with IP connect is \ + raise UnrecognizedArgumentError("Custom ports are not allowed. Allowed ports for Tunnel with IP connect are \ 22, 3389.") else: - # Default resource_port to 443 for managed clusters if not provided + # Default resource_port to DEFAULT_MANAGED_CLUSTER_PORT for managed clusters if not provided if not resource_port and _is_managed_cluster(target_resource_id): - resource_port = 443 + resource_port = DEFAULT_MANAGED_CLUSTER_PORT # Validate that resource_port is provided for non-managed cluster targets if not resource_port: From 870e81cb9ca97630c1ef26020ffcf508dd2d4f3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:16:08 +0000 Subject: [PATCH 6/7] Use parentheses for string continuation instead of backslash Co-authored-by: aavalang <56377848+aavalang@users.noreply.github.com> --- src/bastion/azext_bastion/custom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bastion/azext_bastion/custom.py b/src/bastion/azext_bastion/custom.py index 1837dc80482..764a7464af6 100644 --- a/src/bastion/azext_bastion/custom.py +++ b/src/bastion/azext_bastion/custom.py @@ -487,8 +487,8 @@ def create_bastion_tunnel(cmd, target_resource_id, target_ip_address, resource_g raise InvalidArgumentValueError(f"Invalid resource port: {resource_port}. Must be a valid integer.") if port_int not in [22, 3389]: - raise UnrecognizedArgumentError("Custom ports are not allowed. Allowed ports for Tunnel with IP connect are \ - 22, 3389.") + raise UnrecognizedArgumentError( + "Custom ports are not allowed. Allowed ports for Tunnel with IP connect are 22, 3389.") else: # Default resource_port to DEFAULT_MANAGED_CLUSTER_PORT for managed clusters if not provided if not resource_port and _is_managed_cluster(target_resource_id): From bdadddd7c707fb7a4b81f3417a9e04aa83709c0d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:17:19 +0000 Subject: [PATCH 7/7] Add integer validation for resource_port in non-IP connect scenarios Co-authored-by: aavalang <56377848+aavalang@users.noreply.github.com> --- src/bastion/azext_bastion/custom.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bastion/azext_bastion/custom.py b/src/bastion/azext_bastion/custom.py index 764a7464af6..33b38376db7 100644 --- a/src/bastion/azext_bastion/custom.py +++ b/src/bastion/azext_bastion/custom.py @@ -497,6 +497,12 @@ def create_bastion_tunnel(cmd, target_resource_id, target_ip_address, resource_g # Validate that resource_port is provided for non-managed cluster targets if not resource_port: raise RequiredArgumentMissingError("--resource-port is required for non-managed cluster targets.") + + # Validate that resource_port is a valid integer + try: + int(resource_port) + except (TypeError, ValueError): + raise InvalidArgumentValueError(f"Invalid resource port: {resource_port}. Must be a valid integer.") _validate_resourceid(target_resource_id) bastion_endpoint = _get_bastion_endpoint(cmd, bastion, resource_port, target_resource_id)