From aa3ec27c45640d169c4686e4f2defbb088dd7811 Mon Sep 17 00:00:00 2001 From: Paymaun Date: Tue, 17 Sep 2024 13:42:20 -0700 Subject: [PATCH] refactor: federation improvements + template increment. (#363) --- azext_edge/constants.py | 2 +- azext_edge/edge/_help.py | 2 - azext_edge/edge/commands_edge.py | 2 + azext_edge/edge/commands_secretsync.py | 2 + azext_edge/edge/params.py | 6 +++ .../orchestration/resources/instances.py | 50 ++++++++++++------- .../edge/providers/orchestration/template.py | 4 +- 7 files changed, 45 insertions(+), 23 deletions(-) diff --git a/azext_edge/constants.py b/azext_edge/constants.py index 4baa3b211..dbccd3f63 100644 --- a/azext_edge/constants.py +++ b/azext_edge/constants.py @@ -7,7 +7,7 @@ import os -VERSION = "0.7.0a8" +VERSION = "0.7.0a9" EXTENSION_NAME = "azure-iot-ops" EXTENSION_ROOT = os.path.dirname(os.path.abspath(__file__)) USER_AGENT = "IotOperationsCliExtension/{}".format(VERSION) diff --git a/azext_edge/edge/_help.py b/azext_edge/edge/_help.py index d8dce0def..972e3f5ab 100644 --- a/azext_edge/edge/_help.py +++ b/azext_edge/edge/_help.py @@ -629,8 +629,6 @@ def load_iotops_help(): ] = """ type: command short-summary: Remove a user-assigned managed identity from the instance. - long-summary: | - This operation includes removing federation of the identity. examples: - name: Remove the desired user-assigned managed identity from the instance. diff --git a/azext_edge/edge/commands_edge.py b/azext_edge/edge/commands_edge.py index f395ab9a8..1a606080d 100644 --- a/azext_edge/edge/commands_edge.py +++ b/azext_edge/edge/commands_edge.py @@ -272,6 +272,7 @@ def instance_identity_assign( mi_user_assigned: str, federated_credential_name: Optional[str] = None, usage_type: IdentityUsageType = IdentityUsageType.dataflow.value, + use_self_hosted_issuer: Optional[bool] = None, **kwargs, ) -> dict: return Instances(cmd).add_mi_user_assigned( @@ -279,6 +280,7 @@ def instance_identity_assign( resource_group_name=resource_group_name, mi_user_assigned=mi_user_assigned, federated_credential_name=federated_credential_name, + use_self_hosted_issuer=use_self_hosted_issuer, usage_type=usage_type, **kwargs, ) diff --git a/azext_edge/edge/commands_secretsync.py b/azext_edge/edge/commands_secretsync.py index 5b8ed57c3..ec3572139 100644 --- a/azext_edge/edge/commands_secretsync.py +++ b/azext_edge/edge/commands_secretsync.py @@ -21,6 +21,7 @@ def secretsync_enable( keyvault_resource_id: str, spc_name: Optional[str] = None, skip_role_assignments: Optional[bool] = None, + use_self_hosted_issuer: Optional[bool] = None, **kwargs, ) -> dict: return Instances(cmd).enable_secretsync( @@ -30,6 +31,7 @@ def secretsync_enable( keyvault_resource_id=keyvault_resource_id, spc_name=spc_name, skip_role_assignments=skip_role_assignments, + use_self_hosted_issuer=use_self_hosted_issuer, **kwargs, ) diff --git a/azext_edge/edge/params.py b/azext_edge/edge/params.py index 38838c271..4466b65d4 100644 --- a/azext_edge/edge/params.py +++ b/azext_edge/edge/params.py @@ -106,6 +106,12 @@ def load_iotops_arguments(self, _): options_list=["--fc"], help="The federated credential name.", ) + context.argument( + "use_self_hosted_issuer", + options_list=["--self-hosted-issuer"], + arg_type=get_three_state_flag(), + help="Use the self-hosted oidc issuer for federation.", + ) with self.argument_context("iot ops identity") as context: context.argument( diff --git a/azext_edge/edge/providers/orchestration/resources/instances.py b/azext_edge/edge/providers/orchestration/resources/instances.py index bb76d17e0..b9e0643ba 100644 --- a/azext_edge/edge/providers/orchestration/resources/instances.py +++ b/azext_edge/edge/providers/orchestration/resources/instances.py @@ -152,19 +152,22 @@ def remove_mi_user_assigned( mi_resource_id_container = parse_resource_id(mi_user_assigned) instance = self.show(name=name, resource_group_name=resource_group_name) - cluster_resource = self.get_resource_map(instance).connected_cluster.resource - custom_location = self._get_associated_cl(instance) - namespace = custom_location["properties"]["namespace"] - oidc_issuer = self._ensure_oidc_issuer(cluster_resource) - - cred_subject = get_cred_subject(namespace=namespace, service_account_name=SERVICE_ACCOUNT_DATAFLOW) - if not federated_credential_name: - federated_credential_name = get_fc_name( - cluster_name=cluster_resource["name"], - oidc_issuer=oidc_issuer, - subject=cred_subject, - ) - self.unfederate_msi(mi_resource_id_container, federated_credential_name) + # TODO - @digimaun + # cluster_resource = self.get_resource_map(instance).connected_cluster.resource + # custom_location = self._get_associated_cl(instance) + # namespace = custom_location["properties"]["namespace"] + # oidc_issuer = self._ensure_oidc_issuer(cluster_resource) + + # cred_subject = get_cred_subject(namespace=namespace, service_account_name=SERVICE_ACCOUNT_DATAFLOW) + # if not federated_credential_name: + # federated_credential_name = get_fc_name( + # cluster_name=cluster_resource["name"], + # oidc_issuer=oidc_issuer, + # subject=cred_subject, + # ) + # TODO - @digimaun + if federated_credential_name: + self.unfederate_msi(mi_resource_id_container, federated_credential_name) identity: dict = instance.get("identity", {}) if not identity: @@ -190,6 +193,7 @@ def add_mi_user_assigned( resource_group_name: str, mi_user_assigned: str, federated_credential_name: Optional[str] = None, + use_self_hosted_issuer: Optional[bool] = None, **kwargs, ): """ @@ -199,7 +203,7 @@ def add_mi_user_assigned( mi_resource_id_container = parse_resource_id(mi_user_assigned) instance = self.show(name=name, resource_group_name=resource_group_name) cluster_resource = self.get_resource_map(instance).connected_cluster.resource - oidc_issuer = self._ensure_oidc_issuer(cluster_resource) + oidc_issuer = self._ensure_oidc_issuer(cluster_resource, use_self_hosted_issuer) custom_location = self._get_associated_cl(instance) namespace = custom_location["properties"]["namespace"] cred_subject = get_cred_subject(namespace=namespace, service_account_name=SERVICE_ACCOUNT_DATAFLOW) @@ -234,6 +238,7 @@ def enable_secretsync( federated_credential_name: Optional[str] = None, spc_name: Optional[str] = None, skip_role_assignments: bool = False, + use_self_hosted_issuer: Optional[bool] = None, **kwargs, ): mi_resource_id_container = parse_resource_id(mi_user_assigned) @@ -242,6 +247,8 @@ def enable_secretsync( keyvault: dict = self.resource_client.resources.get_by_id( resource_id=keyvault_resource_id_container.resource_id, api_version=KEYVAULT_CLOUD_API_VERSION ) + # TODO - @digimaun + self.msi_mgmt_client._config.subscription_id = mi_resource_id_container.subscription_id mi_user_assigned: dict = self.msi_mgmt_client.user_assigned_identities.get( resource_group_name=mi_resource_id_container.resource_group_name, resource_name=mi_resource_id_container.resource_name, @@ -258,7 +265,7 @@ def enable_secretsync( custom_location = self._get_associated_cl(instance) namespace = custom_location["properties"]["namespace"] cred_subject = get_cred_subject(namespace=namespace, service_account_name=SERVICE_ACCOUNT_SECRETSYNC) - oidc_issuer = self._ensure_oidc_issuer(cluster_resource) + oidc_issuer = self._ensure_oidc_issuer(cluster_resource, use_self_hosted_issuer) cl_resources = resource_map.connected_cluster.get_aio_resources(custom_location_id=custom_location["id"]) secretsync_spc = self._find_existing_spc(cl_resources) @@ -371,7 +378,7 @@ def _attempt_keyvault_role_assignments(self, keyvault: dict, mi_user_assigned: d scope=keyvault["id"], ) - def _ensure_oidc_issuer(self, cluster_resource: dict) -> str: + def _ensure_oidc_issuer(self, cluster_resource: dict, use_self_hosted_issuer: Optional[bool] = None) -> str: enabled_oidc = cluster_resource["properties"].get("oidcIssuerProfile", {}).get("enabled", False) enabled_wlif = ( cluster_resource["properties"].get("securityProfile", {}).get("workloadIdentity", {}).get("enabled", False) @@ -396,9 +403,10 @@ def _ensure_oidc_issuer(self, cluster_resource: dict) -> str: raise ValidationError(error) oidc_issuer_profile: dict = cluster_resource["properties"]["oidcIssuerProfile"] - issuer_url = oidc_issuer_profile.get("issuerUrl") or oidc_issuer_profile.get("selfHostedIssuerUrl") + issuer_key = "selfHostedIssuerUrl" if use_self_hosted_issuer else "issuerUrl" + issuer_url = oidc_issuer_profile.get(issuer_key) if not issuer_url: - raise ValidationError("No issuer Url is available. Check cluster config.") + raise ValidationError(f"No {issuer_key} is available. Check cluster config.") return issuer_url def federate_msi( @@ -418,6 +426,8 @@ def federate_msi( "No new federated credential will be created." ) return + # TODO - @digimaun + self.msi_mgmt_client._config.subscription_id = mi_resource_id_container.subscription_id self.msi_mgmt_client.federated_identity_credentials.create_or_update( resource_group_name=mi_resource_id_container.resource_group_name, resource_name=mi_resource_id_container.resource_name, @@ -436,6 +446,8 @@ def unfederate_msi( mi_resource_id_container: ResourceIdContainer, federated_credential_name: str, ): + # TODO - @digimaun + self.msi_mgmt_client._config.subscription_id = mi_resource_id_container.subscription_id self.msi_mgmt_client.federated_identity_credentials.delete( resource_group_name=mi_resource_id_container.resource_group_name, resource_name=mi_resource_id_container.resource_name, @@ -445,6 +457,8 @@ def unfederate_msi( def _find_federated_cred( self, mi_resource_id_container: ResourceIdContainer, issuer_url: str, subject: str ) -> Optional[dict]: + # TODO - @digimaun + self.msi_mgmt_client._config.subscription_id = mi_resource_id_container.subscription_id cred_iteratable = self.msi_mgmt_client.federated_identity_credentials.list( resource_group_name=mi_resource_id_container.resource_group_name, resource_name=mi_resource_id_container.resource_name, diff --git a/azext_edge/edge/providers/orchestration/template.py b/azext_edge/edge/providers/orchestration/template.py index 1bf8cc25f..552ba5f0f 100644 --- a/azext_edge/edge/providers/orchestration/template.py +++ b/azext_edge/edge/providers/orchestration/template.py @@ -50,7 +50,7 @@ def copy(self) -> "TemplateBlueprint": IOT_OPERATIONS_VERSION_MONIKER = "v0.7.0-preview" M2_ENABLEMENT_TEMPLATE = TemplateBlueprint( - commit_id="0f5d0d234045e945ab7808c6aa2a44dc0ce77723", + commit_id="a3a65663793fc761928c736b5e7732c68c0591d6", content={ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "languageVersion": "2.0", @@ -218,7 +218,7 @@ def copy(self) -> "TemplateBlueprint": "AIO_EXTENSION_SUFFIX": "[take(uniqueString(resourceId('Microsoft.Kubernetes/connectedClusters', parameters('clusterName'))), 5)]", "VERSIONS": { "platform": "0.7.6", - "aio": "0.7.13", + "aio": "0.7.21", "secretSyncController": "0.6.4", "edgeStorageAccelerator": "2.1.1-preview", "openServiceMesh": "1.2.9",