diff --git a/src/azure-cli/azure/cli/command_modules/postgresql/_help.py b/src/azure-cli/azure/cli/command_modules/postgresql/_help.py index f5819398adb..e47e5eab21d 100644 --- a/src/azure-cli/azure/cli/command_modules/postgresql/_help.py +++ b/src/azure-cli/azure/cli/command_modules/postgresql/_help.py @@ -217,6 +217,25 @@ az postgres flexible-server create -g testgroup -n testserver --location testlocation --geo-redundant-backup Enabled \\ --key $keyIdentifier --identity testidentity --backup-key $geoKeyIdentifier --backup-identity geoidentity + - name: > + Create server with Azure Key Vault from a different Microsoft Entra tenant using multi-tenant application registration. + text: > + # create multi-tenant application registration for accessing key vault from different tenant + + testfederatedclientid=$(az ad app create --display-name testmultitenantapp --query appId -o tsv) + + + # get key identifier from keyvault in different tenant + + keyIdentifier=$(az keyvault key show --vault-name testVault --name testKey \\ + --query key.kid -o tsv) + + + # create flexible server with key from different tenant using multi-tenant app + + az postgres flexible-server create -g testgroup -n testserver --location testlocation \\ + --key $keyIdentifier --identity testidentity --federated-client-id $testfederatedclientid + - name: > Create flexible server with custom storage performance tier. Accepted values "P4", "P6", "P10", "P15", "P20", "P30", \\ "P40", "P50", "P60", "P70", "P80". Actual allowed values depend on the --storage-size selection for flexible server creation. \\ diff --git a/src/azure-cli/azure/cli/command_modules/postgresql/_params.py b/src/azure-cli/azure/cli/command_modules/postgresql/_params.py index a0d7a6678bf..f75a9b269cf 100644 --- a/src/azure-cli/azure/cli/command_modules/postgresql/_params.py +++ b/src/azure-cli/azure/cli/command_modules/postgresql/_params.py @@ -323,6 +323,16 @@ def _flexible_server_params(command_group): validator=validate_identities ) + federated_client_id_arg_type = CLIArgumentType( + options_list=['--federated-client-id'], + help='The client ID of the federated identity.' + ) + + backup_federated_client_id_arg_type = CLIArgumentType( + options_list=['--backup-federated-client-id', '-f'], + help='The client ID of the geo backup federated identity.' + ) + microsoft_entra_auth_arg_type = CLIArgumentType( options_list=['--microsoft-entra-auth'], arg_type=get_enum_type(['Enabled', 'Disabled']), @@ -413,6 +423,8 @@ def _flexible_server_params(command_group): c.argument('zone', zone_arg_type) c.argument('tags', tags_type) c.argument('standby_availability_zone', arg_type=standby_availability_zone_arg_type) + c.argument('backup_federated_client_id', arg_type=backup_federated_client_id_arg_type) + c.argument('federated_client_id', arg_type=federated_client_id_arg_type) c.argument('yes', arg_type=yes_arg_type) with self.argument_context('{} flexible-server list'.format(command_group)) as c: @@ -430,6 +442,8 @@ def _flexible_server_params(command_group): c.argument('private_dns_zone_arguments', private_dns_zone_arguments_arg_type) c.argument('zone', arg_type=zone_arg_type) c.argument('yes', arg_type=yes_arg_type) + c.argument('backup_federated_client_id', arg_type=backup_federated_client_id_arg_type) + c.argument('federated_client_id', arg_type=federated_client_id_arg_type) c.argument('byok_key', arg_type=key_arg_type) c.argument('byok_identity', arg_type=identity_arg_type) c.argument('geo_redundant_backup', default='Disabled', arg_type=geo_redundant_backup_arg_type) @@ -452,6 +466,8 @@ def _flexible_server_params(command_group): c.argument('byok_identity', arg_type=identity_arg_type) c.argument('backup_byok_identity', arg_type=backup_identity_arg_type) c.argument('backup_byok_key', arg_type=backup_key_arg_type) + c.argument('backup_federated_client_id', arg_type=backup_federated_client_id_arg_type) + c.argument('federated_client_id', arg_type=federated_client_id_arg_type) with self.argument_context('{} flexible-server revive-dropped'. format(command_group)) as c: c.argument('location', arg_type=get_location_type(self.cli_ctx), required=True) @@ -481,6 +497,8 @@ def _flexible_server_params(command_group): c.argument('byok_identity', arg_type=identity_arg_type) c.argument('backup_byok_identity', arg_type=backup_identity_arg_type) c.argument('backup_byok_key', arg_type=backup_key_arg_type) + c.argument('backup_federated_client_id', arg_type=backup_federated_client_id_arg_type) + c.argument('federated_client_id', arg_type=federated_client_id_arg_type) c.argument('public_access', arg_type=public_access_update_arg_type) c.argument('auto_grow', arg_type=auto_grow_arg_type) c.argument('performance_tier', default=None, arg_type=performance_tier_arg_type) @@ -592,6 +610,8 @@ def _flexible_server_params(command_group): c.argument('private_dns_zone_arguments', private_dns_zone_arguments_arg_type) c.argument('byok_key', arg_type=key_arg_type) c.argument('byok_identity', arg_type=identity_arg_type) + c.argument('backup_federated_client_id', arg_type=backup_federated_client_id_arg_type) + c.argument('federated_client_id', arg_type=federated_client_id_arg_type) c.argument('tier', arg_type=tier_arg_type) c.argument('sku_name', arg_type=sku_name_arg_type) c.argument('storage_gb', arg_type=storage_gb_arg_type) diff --git a/src/azure-cli/azure/cli/command_modules/postgresql/commands/custom_commands.py b/src/azure-cli/azure/cli/command_modules/postgresql/commands/custom_commands.py index f4e94cd7118..74d4b7ccc60 100644 --- a/src/azure-cli/azure/cli/command_modules/postgresql/commands/custom_commands.py +++ b/src/azure-cli/azure/cli/command_modules/postgresql/commands/custom_commands.py @@ -76,6 +76,7 @@ def flexible_server_create(cmd, client, zonal_resiliency=None, allow_same_zone=False, zone=None, standby_availability_zone=None, geo_redundant_backup=None, byok_identity=None, byok_key=None, backup_byok_identity=None, backup_byok_key=None, + federated_client_id=None, geo_backup_federated_client_id=None, auto_grow=None, performance_tier=None, storage_type=None, iops=None, throughput=None, cluster_size=None, database_name=None, yes=False): @@ -138,6 +139,8 @@ def flexible_server_create(cmd, client, byok_key=byok_key, backup_byok_identity=backup_byok_identity, backup_byok_key=backup_byok_key, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id, performance_tier=performance_tier, password_auth=password_auth, microsoft_entra_auth=microsoft_entra_auth, admin_name=admin_name, admin_id=admin_id, admin_type=admin_type, database_name=database_name) @@ -179,7 +182,9 @@ def flexible_server_create(cmd, client, byok_identity=byok_identity, byok_key=byok_key, backup_byok_identity=backup_byok_identity, - backup_byok_key=backup_byok_key) + backup_byok_key=backup_byok_key, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id) auth_config = postgresql_flexibleservers.models.AuthConfig(active_directory_auth='Enabled' if is_microsoft_entra_auth_enabled else 'Disabled', password_auth=password_auth) @@ -326,7 +331,9 @@ def flexible_server_restore(cmd, client, source_server, restore_point_in_time=None, zone=None, no_wait=False, subnet=None, vnet=None, private_dns_zone_arguments=None, geo_redundant_backup=None, - byok_identity=None, byok_key=None, backup_byok_identity=None, backup_byok_key=None, storage_type=None, yes=False): + byok_identity=None, byok_key=None, backup_byok_identity=None, backup_byok_key=None, + federated_client_id=None, geo_backup_federated_client_id=None, + storage_type=None, yes=False): server_name = server_name.lower() @@ -362,7 +369,9 @@ def flexible_server_restore(cmd, client, logging_name='PostgreSQL', command_group='postgres', server_client=client, location=location) validate_server_name(db_context, server_name, 'Microsoft.DBforPostgreSQL/flexibleServers') - pg_byok_validator(byok_identity, byok_key, backup_byok_identity, backup_byok_key, geo_redundant_backup) + pg_byok_validator(byok_identity, byok_key, backup_byok_identity, backup_byok_key, geo_redundant_backup, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id) pg_restore_validator(source_server_object.sku.tier, storage_type=storage_type) storage = postgresql_flexibleservers.models.Storage(type=storage_type if source_server_object.storage.type != "PremiumV2_LRS" else None) @@ -397,7 +406,9 @@ def flexible_server_restore(cmd, client, byok_identity=byok_identity, byok_key=byok_key, backup_byok_identity=backup_byok_identity, - backup_byok_key=backup_byok_key) + backup_byok_key=backup_byok_key, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id) except Exception as e: raise ResourceNotFoundError(e) @@ -417,6 +428,7 @@ def flexible_server_update_custom_func(cmd, client, instance, maintenance_window=None, byok_identity=None, byok_key=None, backup_byok_identity=None, backup_byok_key=None, + federated_client_id=None, geo_backup_federated_client_id=None, microsoft_entra_auth=None, password_auth=None, private_dns_zone_arguments=None, public_access=None, @@ -450,6 +462,8 @@ def flexible_server_update_custom_func(cmd, client, instance, byok_key=byok_key, backup_byok_identity=backup_byok_identity, backup_byok_key=backup_byok_key, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id, performance_tier=performance_tier, cluster_size=cluster_size, instance=instance) @@ -526,6 +540,8 @@ def flexible_server_update_custom_func(cmd, client, instance, byok_key=byok_key, backup_byok_identity=backup_byok_identity, backup_byok_key=backup_byok_key, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id, instance=instance) auth_config = instance.auth_config @@ -678,7 +694,9 @@ def flexible_list_skus(cmd, client, location): def flexible_server_georestore(cmd, client, resource_group_name, server_name, source_server, location, zone=None, vnet=None, subnet=None, private_dns_zone_arguments=None, geo_redundant_backup=None, no_wait=False, yes=False, - byok_identity=None, byok_key=None, backup_byok_identity=None, backup_byok_key=None, restore_point_in_time=None): + byok_identity=None, byok_key=None, backup_byok_identity=None, backup_byok_key=None, + federated_client_id=None, geo_backup_federated_client_id=None, + restore_point_in_time=None): validate_resource_group(resource_group_name) server_name = server_name.lower() @@ -716,7 +734,9 @@ def flexible_server_georestore(cmd, client, resource_group_name, server_name, so if source_server_object.network.delegated_subnet_resource_id is not None: validate_georestore_network(source_server_object, None, vnet, subnet, 'postgres') - pg_byok_validator(byok_identity, byok_key, backup_byok_identity, backup_byok_key, geo_redundant_backup) + pg_byok_validator(byok_identity, byok_key, backup_byok_identity, backup_byok_key, geo_redundant_backup, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id) storage = postgresql_flexibleservers.models.Storage(type=None) @@ -748,7 +768,9 @@ def flexible_server_georestore(cmd, client, resource_group_name, server_name, so byok_identity=byok_identity, byok_key=byok_key, backup_byok_identity=backup_byok_identity, - backup_byok_key=backup_byok_key) + backup_byok_key=backup_byok_key, + federated_client_id=federated_client_id, + geo_backup_federated_client_id=geo_backup_federated_client_id) return sdk_no_wait(no_wait, client.begin_create_or_update, resource_group_name, server_name, parameters) diff --git a/src/azure-cli/azure/cli/command_modules/postgresql/commands/replica_commands.py b/src/azure-cli/azure/cli/command_modules/postgresql/commands/replica_commands.py index 3fe246a1a19..11780f915e1 100644 --- a/src/azure-cli/azure/cli/command_modules/postgresql/commands/replica_commands.py +++ b/src/azure-cli/azure/cli/command_modules/postgresql/commands/replica_commands.py @@ -33,6 +33,7 @@ def flexible_replica_create(cmd, client, resource_group_name, source_server, nam location=None, vnet=None, subnet=None, private_dns_zone_arguments=None, no_wait=False, byok_identity=None, byok_key=None, + federated_client_id=None, backup_federated_client_id=None, sku_name=None, tier=None, storage_type=None, storage_gb=None, performance_tier=None, yes=False, tags=None): validate_resource_group(resource_group_name) @@ -82,7 +83,9 @@ def flexible_replica_create(cmd, client, resource_group_name, source_server, nam logging_name='PostgreSQL', command_group='postgres', server_client=client, location=location) validate_server_name(db_context, name, 'Microsoft.DBforPostgreSQL/flexibleServers') - pg_byok_validator(byok_identity, byok_key) + pg_byok_validator(byok_identity, byok_key, + federated_client_id=federated_client_id, + backup_federated_client_id=backup_federated_client_id) parameters = postgresql_flexibleservers.models.Server( tags=tags, @@ -108,7 +111,9 @@ def flexible_replica_create(cmd, client, resource_group_name, source_server, nam parameters.identity, parameters.data_encryption = build_identity_and_data_encryption(db_engine='postgres', byok_identity=byok_identity, - byok_key=byok_key) + byok_key=byok_key, + federated_client_id=federated_client_id, + backup_federated_client_id=backup_federated_client_id) parameters.sku = postgresql_flexibleservers.models.Sku(name=sku_name, tier=tier) diff --git a/src/azure-cli/azure/cli/command_modules/postgresql/utils/_flexible_server_util.py b/src/azure-cli/azure/cli/command_modules/postgresql/utils/_flexible_server_util.py index 15366ae430d..d21de0d394e 100644 --- a/src/azure-cli/azure/cli/command_modules/postgresql/utils/_flexible_server_util.py +++ b/src/azure-cli/azure/cli/command_modules/postgresql/utils/_flexible_server_util.py @@ -372,7 +372,9 @@ def _is_resource_name(resource): def build_identity_and_data_encryption(db_engine, byok_identity=None, backup_byok_identity=None, - byok_key=None, backup_byok_key=None, instance=None): + byok_key=None, backup_byok_key=None, + federated_client_id=None, backup_federated_client_id=None, + instance=None): identity, data_encryption = None, None primary_user_assigned_identity_id = byok_identity @@ -397,6 +399,8 @@ def build_identity_and_data_encryption(db_engine, byok_identity=None, backup_byo primary_key_uri=primary_key_uri, geo_backup_user_assigned_identity_id=geo_backup_user_assigned_identity_id, geo_backup_key_uri=geo_backup_key_uri, + primary_federated_identity_client_id=federated_client_id, + geo_backup_federated_identity_client_id=backup_federated_client_id, type="AzureKeyVault") return identity, data_encryption diff --git a/src/azure-cli/azure/cli/command_modules/postgresql/utils/validators.py b/src/azure-cli/azure/cli/command_modules/postgresql/utils/validators.py index e0f98fcc5a6..56988dc4423 100644 --- a/src/azure-cli/azure/cli/command_modules/postgresql/utils/validators.py +++ b/src/azure-cli/azure/cli/command_modules/postgresql/utils/validators.py @@ -156,6 +156,7 @@ def pg_arguments_validator(db_context, location, tier, sku_name, storage_gb, ser zonal_resiliency=None, allow_same_zone=False, subnet=None, public_access=None, version=None, instance=None, geo_redundant_backup=None, byok_identity=None, byok_key=None, backup_byok_identity=None, backup_byok_key=None, + federated_client_id=None, backup_federated_client_id=None, auto_grow=None, performance_tier=None, storage_type=None, iops=None, throughput=None, cluster_size=None, password_auth=None, microsoft_entra_auth=None, @@ -199,7 +200,10 @@ def pg_arguments_validator(db_context, location, tier, sku_name, storage_gb, ser _pg_zonal_resiliency_validator(zonal_resiliency, allow_same_zone, standby_availability_zone, zone, tier, single_az, instance) pg_version_validator(version, list_location_capability_info['server_versions']) - pg_byok_validator(byok_identity, byok_key, backup_byok_identity, backup_byok_key, geo_redundant_backup, instance) + pg_byok_validator(byok_identity, byok_key, backup_byok_identity, backup_byok_key, + geo_redundant_backup, instance, + federated_client_id=federated_client_id, + backup_federated_client_id=backup_federated_client_id) is_microsoft_entra_auth = bool(microsoft_entra_auth is not None and microsoft_entra_auth.lower() == 'enabled') _pg_authentication_validator(password_auth, is_microsoft_entra_auth, admin_name, admin_id, admin_type, instance) @@ -377,7 +381,9 @@ def _pg_georedundant_backup_validator(geo_redundant_backup, geo_backup_supported def pg_byok_validator(byok_identity, byok_key, backup_byok_identity=None, backup_byok_key=None, - geo_redundant_backup=None, instance=None): + geo_redundant_backup=None, instance=None, + federated_client_id=None, backup_federated_client_id=None): + if bool(byok_identity is None) ^ bool(byok_key is None): raise ArgumentUsageError('A user-assigned identity and Key Vault key must be provided together. ' 'Provide --identity and --key together.') @@ -391,6 +397,20 @@ def pg_byok_validator(byok_identity, byok_key, backup_byok_identity=None, backup raise ArgumentUsageError('The primary user-assigned identity and backup identity cannot be the same. ' 'Provide different identities for --identity and --backup-identity.') + if (federated_client_id or backup_federated_client_id) and byok_identity is None: + if instance is None: + raise ArgumentUsageError('To use --federated-client-id or --geo-backup-federated-client-id, ' + 'provide --identity and --key together.') + if not (instance.data_encryption and instance.data_encryption.type == 'AzureKeyVault'): + logger.warning('You cannot update data encryption properties on a server ' + 'that was not created with data encryption..') + + if bool(federated_client_id is not None) and bool(backup_federated_client_id is not None) and \ + federated_client_id.lower() == backup_federated_client_id.lower(): + raise ArgumentUsageError('The primary federated client ID and backup federated client ID cannot be the same. ' + 'Provide different IDs for --federated-client-id and ' + '--geo-backup-federated-client-id.') + if (instance is not None) and \ not (instance.data_encryption and instance.data_encryption.type == 'AzureKeyVault') and \ (byok_key or backup_byok_key):