From 9637412e2e0dfa65618ab494b4a1c57a6c8e0fb9 Mon Sep 17 00:00:00 2001 From: Christoffer Reijer Date: Tue, 31 Oct 2023 16:48:31 +0100 Subject: [PATCH 1/3] Add encryption to azure_rm_galleryimageversion Add the parameter `encryption` to `target_regions` of the `azure_rm_galleryimageversion` module, making it possible to create encrypted images. Fixes #1290 --- .../modules/azure_rm_galleryimageversion.py | 112 +++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/plugins/modules/azure_rm_galleryimageversion.py b/plugins/modules/azure_rm_galleryimageversion.py index dbe823583..763d39935 100644 --- a/plugins/modules/azure_rm_galleryimageversion.py +++ b/plugins/modules/azure_rm_galleryimageversion.py @@ -125,6 +125,60 @@ description: - Storage account type. type: str + encryption: + description: + - Allows users to provide customer managed keys for encrypting the OS and data disks in the gallery artifact. + type: dict + suboptions: + data_disk_images: + description: + - A list of encryption specifications for data disk images. + type: list + suboptions: + disk_encryption_set_id: + description: + - A relative URI containing the resource ID of the disk encryption set. + type: str + lun: + description: + - This property specifies the logical unit number of the data disk. + - This value is used to identify data disks within the Virtual Machine and + therefore must be unique for each data disk attached to the Virtual Machine. + os_disk_image: + description: + - Contains encryption settings for an OS disk image. + type: dict + suboptions: + disk_encryption_set_id: + description: + - A relative URI containing the resource ID of the disk encryption set. + type: str + security_profile: + description: + - This property specifies the security profile of an OS disk image. + type: dict + suboptions: + confidential_vm_encryption_type: + description: + - Confidential VM encryption types. + type: dict + suboptions: + encrypted_vm_guest_state_only_with_pmk: + description: + - VM Guest State Only with PMK. + type: str + encrypted_with_cmk: + description: + - Encrypted with CMK. + type: str + encrypted_with_pmk: + description: + - Encrypted with PMK. + type: str + secure_vm_disk_encryption_set_id: + description: + - Secure VM disk encryption set id. + type: str managed_image: description: - Managed image reference, could be resource ID, or dictionary containing I(resource_group) and I(name) @@ -394,6 +448,62 @@ def __init__(self): storage_account_type=dict( type='str', disposition='storageAccountType' + ), + encryption=dict( + type='dict', + options=dict( + data_disk_images=dict( + type='list', + disposition='dataDiskImages', + options=dict( + disk_encryption_set_id=dict( + type='str', + disposition='diskEncryptionSetId' + ), + lun=dict( + type='int' + ) + ) + ), + os_disk_image=dict( + type='dict', + disposition='osDiskImage', + options=dict( + disk_encryption_set_id=dict( + type='str', + disposition='diskEncryptionSetId' + ), + securityProfile=dict( + type='dict', + disposition='security_profile', + options=dict( + confidential_vm_encryption_type=dict( + type='dict', + disposition='confidentialVMEncryptionType', + options=dict( + encrypted_vm_guest_state_only_with_pmk=dict( + type='dict', + disposition='EncryptedVMGuestStateOnlyWithPmk' + ), + encrypted_with_cmk=dict( + type='dict', + disposition='EncryptedWithCmk' + ), + encrypted_with_pmk=dict( + type='dict', + disposition='EncryptedWithPmk' + ) + ) + ), + secure_vm_disk_encryption_set_id=dict( + type='str', + disposition='secureVMDiskEncryptionSetId' + ) + ) + ) + ) + ) + ) ) ) ), @@ -455,7 +565,7 @@ def __init__(self): required_if = [('state', 'present', ['storage_profile'])] self.body = {} self.query_parameters = {} - self.query_parameters['api-version'] = '2019-07-01' + self.query_parameters['api-version'] = '2022-03-03' self.header_parameters = {} self.header_parameters['Content-Type'] = 'application/json; charset=utf-8' From 12c4896847d7bad169f9c0d15b3f5b6df3147ab7 Mon Sep 17 00:00:00 2001 From: Christoffer Reijer Date: Fri, 3 Nov 2023 09:55:10 +0100 Subject: [PATCH 2/3] Fix documentation --- plugins/modules/azure_rm_galleryimageversion.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/modules/azure_rm_galleryimageversion.py b/plugins/modules/azure_rm_galleryimageversion.py index 763d39935..57e4ddb68 100644 --- a/plugins/modules/azure_rm_galleryimageversion.py +++ b/plugins/modules/azure_rm_galleryimageversion.py @@ -134,6 +134,7 @@ description: - A list of encryption specifications for data disk images. type: list + elements: dict suboptions: disk_encryption_set_id: description: @@ -144,6 +145,7 @@ - This property specifies the logical unit number of the data disk. - This value is used to identify data disks within the Virtual Machine and therefore must be unique for each data disk attached to the Virtual Machine. + type: int os_disk_image: description: - Contains encryption settings for an OS disk image. @@ -455,6 +457,7 @@ def __init__(self): data_disk_images=dict( type='list', disposition='dataDiskImages', + elements='dict', options=dict( disk_encryption_set_id=dict( type='str', @@ -473,24 +476,24 @@ def __init__(self): type='str', disposition='diskEncryptionSetId' ), - securityProfile=dict( + security_profile=dict( type='dict', - disposition='security_profile', + disposition='securityProfile', options=dict( confidential_vm_encryption_type=dict( type='dict', disposition='confidentialVMEncryptionType', options=dict( encrypted_vm_guest_state_only_with_pmk=dict( - type='dict', + type='str', disposition='EncryptedVMGuestStateOnlyWithPmk' ), encrypted_with_cmk=dict( - type='dict', + type='str', disposition='EncryptedWithCmk' ), encrypted_with_pmk=dict( - type='dict', + type='str', disposition='EncryptedWithPmk' ) ) From b09d0ffc07d0002370e03efb746e5cebb73ef84e Mon Sep 17 00:00:00 2001 From: Christoffer Reijer Date: Fri, 3 Nov 2023 13:27:37 +0100 Subject: [PATCH 3/3] Add disk encryption to gallery image version tests Add the usage of disk encryption to all regions in the tests of the module azure_rm_galleryimageversion. This required a fix, moving the deprecated managed_image to storage_profile.source_image instead. --- .../azure_service_principal_attribute.py | 92 ++++++++++++++++++ .../targets/azure_rm_gallery/tasks/main.yml | 97 ++++++++++++++++++- 2 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 tests/integration/targets/azure_rm_gallery/lookup_plugins/azure_service_principal_attribute.py diff --git a/tests/integration/targets/azure_rm_gallery/lookup_plugins/azure_service_principal_attribute.py b/tests/integration/targets/azure_rm_gallery/lookup_plugins/azure_service_principal_attribute.py new file mode 100644 index 000000000..c6f488f13 --- /dev/null +++ b/tests/integration/targets/azure_rm_gallery/lookup_plugins/azure_service_principal_attribute.py @@ -0,0 +1,92 @@ +# (c) 2018 Yunge Zhu, +# (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ +lookup: azure_service_principal_attribute + +requirements: + - azure-graphrbac + +author: + - Yunge Zhu + +version_added: "2.7" + +short_description: Look up Azure service principal attributes. + +description: + - Describes object id of your Azure service principal account. +options: + azure_client_id: + description: azure service principal client id. + azure_secret: + description: azure service principal secret + azure_tenant: + description: azure tenant + azure_cloud_environment: + description: azure cloud environment +""" + +EXAMPLES = """ +set_fact: + object_id: "{{ lookup('azure_service_principal_attribute', + azure_client_id=azure_client_id, + azure_secret=azure_secret, + azure_tenant=azure_secret) }}" +""" + +RETURN = """ +_raw: + description: + Returns object id of service principal. +""" + +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from ansible.module_utils._text import to_native + +try: + from azure.common.credentials import ServicePrincipalCredentials + from azure.graphrbac import GraphRbacManagementClient + from azure.cli.core import cloud as azure_cloud +except ImportError: + raise AnsibleError( + "The lookup azure_service_principal_attribute requires azure.graphrbac, msrest") + + +class LookupModule(LookupBase): + def run(self, terms, variables, **kwargs): + + self.set_options(direct=kwargs) + + credentials = {} + credentials['azure_client_id'] = self.get_option('azure_client_id', None) + credentials['azure_secret'] = self.get_option('azure_secret', None) + credentials['azure_tenant'] = self.get_option('azure_tenant', 'common') + + if credentials['azure_client_id'] is None or credentials['azure_secret'] is None: + raise AnsibleError("Must specify azure_client_id and azure_secret") + + _cloud_environment = azure_cloud.AZURE_PUBLIC_CLOUD + if self.get_option('azure_cloud_environment', None) is not None: + cloud_environment = azure_cloud.get_cloud_from_metadata_endpoint(credentials['azure_cloud_environment']) + + try: + azure_credentials = ServicePrincipalCredentials(client_id=credentials['azure_client_id'], + secret=credentials['azure_secret'], + tenant=credentials['azure_tenant'], + resource=_cloud_environment.endpoints.active_directory_graph_resource_id) + + client = GraphRbacManagementClient(azure_credentials, credentials['azure_tenant'], + base_url=_cloud_environment.endpoints.active_directory_graph_resource_id) + + response = list(client.service_principals.list(filter="appId eq '{0}'".format(credentials['azure_client_id']))) + sp = response[0] + + return sp.object_id.split(',') + except Exception as ex: + raise AnsibleError("Failed to get service principal object id: %s" % to_native(ex)) + return False diff --git a/tests/integration/targets/azure_rm_gallery/tasks/main.yml b/tests/integration/targets/azure_rm_gallery/tasks/main.yml index b9705dc34..c080cb49c 100644 --- a/tests/integration/targets/azure_rm_gallery/tasks/main.yml +++ b/tests/integration/targets/azure_rm_gallery/tasks/main.yml @@ -3,6 +3,64 @@ rpfx: "{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" run_once: true +- name: Lookup service principal object id + ansible.builtin.set_fact: + object_id: "{{ lookup('azure_service_principal_attribute', + azure_client_id=azure_client_id, + azure_secret=azure_secret, + azure_tenant=azure_tenant) }}" + register: object_id_facts + +- name: Create a key vault + azure_rm_keyvault: + resource_group: "{{ resource_group }}" + vault_name: "myvault{{ rpfx }}" + enabled_for_disk_encryption: true + vault_tenant: "{{ azure_tenant }}" + sku: + name: standard + family: A + access_policies: + - tenant_id: "{{ azure_tenant }}" + object_id: "{{ object_id }}" + keys: + - get + - list + - wrapkey + - unwrapkey + - create + - update + - import + - delete + - backup + - restore + - recover + - purge + +- name: Create a key in key vault + azure_rm_keyvaultkey: + key_name: testkey + keyvault_uri: https://myvault{{ rpfx }}.vault.azure.net + +- name: Get latest version of key + azure_rm_keyvaultkey_info: + vault_uri: https://myvault{{ rpfx }}.vault.azure.net + name: testkey + register: results + +- name: Assert the key vault facts + ansible.builtin.set_fact: + key_url: "{{ results['keys'][0]['kid'] }}" + +- name: Create disk encryption set + azure_rm_diskencryptionset: + resource_group: "{{ resource_group }}" + name: "des{{ rpfx }}" + source_vault: "myvault{{ rpfx }}" + key_url: "{{ key_url }}" + state: present + register: des_results + - name: Create virtual network azure_rm_virtualnetwork: resource_group: "{{ resource_group }}" @@ -254,10 +312,21 @@ target_regions: - name: eastus regional_replica_count: 1 + encryption: + data_disk_images: + - disk_encryption_set_id: "{{ des_results.state.id }}" + os_disk_image: + disk_encryption_set_id: "{{ des_results.state.id }}" - name: westus regional_replica_count: 2 + encryption: + data_disk_images: + - disk_encryption_set_id: "{{ des_results.state.id }}" + os_disk_image: + disk_encryption_set_id: "{{ des_results.state.id }}" storage_account_type: Standard_ZRS - managed_image: + storage_profile: + source_image: name: testimagea resource_group: "{{ resource_group }}" register: output @@ -282,10 +351,21 @@ target_regions: - name: eastus regional_replica_count: 1 + encryption: + data_disk_images: + - disk_encryption_set_id: "{{ des_results.state.id }}" + os_disk_image: + disk_encryption_set_id: "{{ des_results.state.id }}" - name: westus regional_replica_count: 2 + encryption: + data_disk_images: + - disk_encryption_set_id: "{{ des_results.state.id }}" + os_disk_image: + disk_encryption_set_id: "{{ des_results.state.id }}" storage_account_type: Standard_ZRS - managed_image: + storage_profile: + source_image: name: testimagea resource_group: "{{ resource_group }}" register: output @@ -310,10 +390,21 @@ target_regions: - name: eastus regional_replica_count: 1 + encryption: + data_disk_images: + - disk_encryption_set_id: "{{ des_results.state.id }}" + os_disk_image: + disk_encryption_set_id: "{{ des_results.state.id }}" - name: westus regional_replica_count: 2 + encryption: + data_disk_images: + - disk_encryption_set_id: "{{ des_results.state.id }}" + os_disk_image: + disk_encryption_set_id: "{{ des_results.state.id }}" storage_account_type: Standard_ZRS - managed_image: + storage_profile: + source_image: name: testimagea resource_group: "{{ resource_group }}" register: output