diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md index 0232689..499ed41 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md @@ -1,6 +1,7 @@ # Enable Logging For Keyvault -This job enables Key Vault Logging. +This job enables Logging for Key Vault. It checks for the existence of Storage Account created by CHSS in the given resource group and region, if the Storage Account exists then it stores the Key Vault Logs in it. Else it creates a new Storage Account to store the Key Vault logs. +The Storage Account created by CHSS is prefixed with "chss" and contains tag `{"Created By" : "CHSS"}`. ### Applicable Rule @@ -15,9 +16,14 @@ Logging For Keyvault Enabled The provided Azure service principal must have the following permissions: `Microsoft.Storage/storageAccounts/read` `Microsoft.Storage/storageAccounts/write` +`Microsoft.Storage/storageAccounts/blobServices/write` +`Microsoft.Storage/storageAccounts/blobServices/read` `Microsoft.Insights/DiagnosticSettings/Write` `Microsoft.KeyVault/vaults/read` `Microsoft.KeyVault/vaults/write` +`Microsoft.KeyVault/vaults/keys/read` +`Microsoft.KeyVault/vaults/keys/write` +`Microsoft.KeyVault/vaults/accessPolicies/write` A sample role with requisite permissions can be found [here](minimum_permissions.json) @@ -38,13 +44,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision an instance by creating an Azure Virtual Machine to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker on newly provisioned Azure Virtual Machine instance.You can refer to the [docs here](https://docs.microsoft.com/en-us/previous-versions/azure/virtual-machines/linux/docker-machine) for more information. +Deploy the worker docker image by SSH into the Azure Virtual Machine instance and run the following commands: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py index cbd0d87..0dd72bd 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py @@ -16,52 +16,79 @@ import os import sys import logging +import datetime +from dateutil import parser as date_parse +from typing import List +from azure.core.paging import ItemPaged +from azure.keyvault.keys import KeyClient +from azure.mgmt.monitor import MonitorClient from azure.identity import ClientSecretCredential from azure.mgmt.keyvault import KeyVaultManagementClient -from azure.mgmt.monitor import MonitorClient from azure.mgmt.storage import StorageManagementClient +from azure.graphrbac import GraphRbacManagementClient +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.storage.models import Sku as sku_storage from azure.mgmt.storage.models import ( StorageAccountCreateParameters, NetworkRuleSet, - Sku, SkuName, SkuTier, DefaultAction, + Identity, + StorageAccountListResult, + BlobServiceProperties, + DeleteRetentionPolicy, + Encryption, + KeySource, + KeyVaultProperties, + StorageAccountUpdateParameters, ) from azure.mgmt.monitor.models import ( DiagnosticSettingsResource, LogSettings, RetentionPolicy, ) +from azure.mgmt.keyvault.models import ( + VaultCreateOrUpdateParameters, + VaultProperties, + Sku, + AccessPolicyEntry, + Permissions, + KeyPermissions, + VaultListResult, + AccessPolicyUpdateKind, + VaultAccessPolicyParameters, + VaultAccessPolicyProperties, +) logging.basicConfig(level=logging.INFO) - -def generate_name(prefix): - prefix = "".join(i for i in prefix if i.islower() or i.isdigit()) - if len(prefix) >= 12: - prefix = str(prefix[:11]) - result_str = prefix + "keyvaultlogs" - return result_str +MAX_COUNT_SUBSCRIPTION = 5 +MAX_COUNT_RESOURCE_NAME = 5 +MAX_COUNT_REGION = 6 +MAX_COUNT_COMPONENT = 4 -def create_storage_account( - resource_group_name, name, region, storage_client, -): - create_params = StorageAccountCreateParameters( - location=region, - sku=Sku(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), - kind="StorageV2", - enable_https_traffic_only=True, - network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), - ) - poller = storage_client.storage_accounts.begin_create( - resource_group_name=resource_group_name, - account_name=name, - parameters=create_params, - ) - return poller.result() +def generate_name(region, subscription_id, resource_group_name): + """Generates a name for the resource + :param region: location in which the resource exists + :param subscription_id: Azure Subscription Id + :param resource_group_name: Resource group name in which the resource exists + :type region: str + :type subscription_id: str + :type resource_group_name: str + :returns: resource name + :rtype: str + """ + random_str = "".join(i for i in subscription_id if i.islower() or i.isdigit()) + subscription_id = random_str[:MAX_COUNT_SUBSCRIPTION] + random_str = "".join(i for i in region if i.islower() or i.isdigit()) + region = random_str[-MAX_COUNT_REGION:] + random_str = "".join(i for i in resource_group_name if i.islower() or i.isdigit()) + resource_group_name = random_str[-MAX_COUNT_RESOURCE_NAME:] + result_str = "chss" + subscription_id + resource_group_name + region + "logs" + return result_str class EnableKeyVaultLogging(object): @@ -106,65 +133,494 @@ def parse(self, payload): "region": region, } + def create_storage_account( + self, resource_group_name, name, region, storage_client, + ): + """Creates a Storage Account + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + + create_params = StorageAccountCreateParameters( + location=region, + sku=sku_storage(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), + identity=Identity(type="SystemAssigned"), + kind="StorageV2", + enable_https_traffic_only=True, + network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), + tags={"Created By": "CHSS"}, + ) + stg_account = storage_client.storage_accounts.begin_create( + resource_group_name=resource_group_name, + account_name=name, + parameters=create_params, + ).result() + + storage_client.blob_services.set_service_properties( + resource_group_name=resource_group_name, + account_name=name, + parameters=BlobServiceProperties( + delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7) + ), + ) + return stg_account + + def update_storage_account_encryption( + self, storage_client, resource_group_name, stg_name, key_name, vault_uri + ): + """Updates Storage Account Encryption for a Storage Account. + :param storage_client: Instance of the Azure StorageManagementClient. + :param resource_group_name: The name of the resource group. + :param stg_name: The Storage Account name. + :param key_name: Name of the Key to encrypt the Storage Account with. + :param vault_uri: Key Vault uri in which the Key exists. + :type storage_client: object + :type resource_group_name: str + :type stg_name: str + :type key_name: str + :type vault_uri: str + :returns: None + :rtype: None + """ + logging.info(" Encrypting Storage Account with Customer Managed Key") + logging.info(" executing storage_client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={stg_name}") + logging.info(f" key_vault_uri={vault_uri}") + logging.info(f" key_name={key_name}") + storage_client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=stg_name, + parameters=StorageAccountUpdateParameters( + encryption=Encryption( + key_source=KeySource.MICROSOFT_KEYVAULT, + key_vault_properties=KeyVaultProperties( + key_name=key_name, key_vault_uri=vault_uri, + ), + ), + ), + ) + + def check_stg_account(self, storage_client, region, name, resource_group_name): + """Checks For the existence of the Storage Account created by CHSS + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + storage_accounts_paged: ItemPaged[ + StorageAccountListResult + ] = storage_client.storage_accounts.list() + storage_accounts_list: List[dict] = list(storage_accounts_paged) + storage_account = None + for stg_account in storage_accounts_list: + stg_id = stg_account.id + stg_components = stg_id.split("/") + if len(stg_components) > MAX_COUNT_COMPONENT: + resource_grp = stg_components[MAX_COUNT_COMPONENT] + if ( + stg_account.name == name + and stg_account.location == region + and resource_grp == resource_group_name + ): + storage_account = stg_account + break + return storage_account + + def check_key_vault(self, keyvault_client, region, name, resource_group_name): + """Checks for the existence of the Key Vault created by CHSS. + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param region: The location in which the Key Vault exists. + :param name: Key Vault name. + :param resource_group_name: The name of the resource group. + :type keyvault_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: Vault object + :rtype: object + """ + key_vault_paged: ItemPaged[ + VaultListResult + ] = keyvault_client.vaults.list_by_subscription() + key_vault_list: List[dict] = list(key_vault_paged) + chss_key_vault = None + for key_vault in key_vault_list: + key_vault_id = key_vault.id + key_vault_components = key_vault_id.split("/") + if len(key_vault_components) > MAX_COUNT_COMPONENT: + resource_grp = key_vault_components[MAX_COUNT_COMPONENT] + if ( + key_vault.name == name + and key_vault.location == region + and resource_grp == resource_group_name + ): + chss_key_vault = key_vault + break + return chss_key_vault + + def create_diagnostic_setting( + self, monitor_client, key_vault_id, key_vault_name, stg_account_id, log + ): + """Creates a diagnostic setting + :param monitor_client: Instance of the Azure StorageManagementClient. + :param key_vault_id: The resource Id of the Key Vault. + :param key_vault_name: Name of the Key Vault. + :param stg_account_id: The Storage Account resource Id. + :param log: Instance of Azure Monitor LogSettings + :type monitor_client: object + :type log: object + :type key_vault_id: str + :type key_vault_name: str + :type stg_account_id: str + :returns: None + :rtype: None + """ + logging.info(" Creating a Diagnostic setting for key vault logs") + logging.info( + " executing monitor_client.diagnostic_settings.create_or_update" + ) + logging.info(f" resource_uri={key_vault_id}") + logging.info(f" name={key_vault_name}") + monitor_client.diagnostic_settings.create_or_update( + resource_uri=key_vault_id, + name=key_vault_name, + parameters=DiagnosticSettingsResource( + storage_account_id=stg_account_id, logs=[log], + ), + ) + + def create_key_vault( + self, + keyvault_client, + resource_group_name, + key_vault_name, + region, + tenant_id, + app_object_id, + stg_principal_id, + ): + """Creates a Key Vault + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: location of the Key Vault. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: Vault object + :rtype: object + """ + access_policy_storage_account = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_principal_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + key_vault_properties = VaultCreateOrUpdateParameters( + location=region, + tags={"Created By": "CHSS"}, + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard",), + access_policies=[access_policy_storage_account, access_policy_app], + soft_delete_retention_in_days=7, + enabled_for_disk_encryption=False, + enabled_for_deployment=False, + enabled_for_template_deployment=False, + enable_soft_delete=True, + enable_purge_protection=True, + ), + ) + logging.info("creating a key vault") + logging.info("executing keyvault_client.vaults.begin_create_or_update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + vault = keyvault_client.vaults.begin_create_or_update( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + parameters=key_vault_properties, + ).result() + return vault + + def update_key_vault_access_policy( + self, + keyvault_client, + resource_group_name, + key_vault_name, + tenant_id, + app_object_id, + stg_object_id, + ): + """Updates Key Vault Access Policy + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: The location in which the Key Vault exists. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: None + :rtype: None + """ + access_policy_storage = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + access_policy = [access_policy_app, access_policy_storage] + + logging.info("Updating Key Vault Access Policy") + logging.info("executing keyvault_client.vaults.update_access_policy") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + keyvault_client.vaults.update_access_policy( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + operation_kind=AccessPolicyUpdateKind.ADD, + parameters=VaultAccessPolicyParameters( + properties=VaultAccessPolicyProperties(access_policies=access_policy), + ), + ) + + def create_key(self, credential, key_vault_name, suffix): + """Creates a Key within the given Key Vault + :param credential: Azure Credentials + :param key_vault_name: Name of the Key Vault. + :param suffix: suffix for Key name + :type key_vault_name: str + :type suffix: str + :returns: Azure Key object which was created + :rtype: object + """ + d = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() + date = datetime.datetime.strptime( + d[0:19], "%Y-%m-%dT%H:%M:%S" + ) + datetime.timedelta(days=180) + expires_on = date_parse.parse( + date.replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() + ) + key_client = KeyClient( + vault_url=f"https://{key_vault_name}.vault.azure.net/", + credential=credential, + ) + rsa_key_name = key_vault_name + "-" + suffix + logging.info("creating a key") + rsa_key = key_client.create_rsa_key( + rsa_key_name, size=2048, expires_on=expires_on, enabled=True + ) + return rsa_key + def remediate( self, + client_id, + tenant_id, keyvault_client, monitor_client, storage_client, + graph_client, + credentials, resource_group_name, key_vault_name, region, + subscription_id, ): """Enable key vault logging + :param client_id: Azure Client ID. + :param tenant_id: Azure Tenant ID. + :param graph_client: Instance of the AzureGraphRbacManagementClient. :param keyvault_client: Instance of the Azure KeyVaultManagementClient. :param storage_client: Instance of the Azure StorageManagementClient. :param monitor_client: Instance of the Azure MonitorClient. + :param credentials: Azure Credential object. :param resource_group_name: The name of the resource group to which the storage account belongs. :param key_vault_name: The name of the key vault. :param region: The region in which the key vault is present. + :param subscription_id: Azure Subscription Id + :type client_id: str + :type tenant_id: str + :type graph_client: object + :type keyvault_client: object + :type storage_client: object + :type monitor_client: object + :type credentials: object :type resource_group_name: str. :type key_vault_name: str. :type region: str. + :type subscription_id: str :returns: Integer signaling success or failure :rtype: int :raises: msrestazure.azure_exceptions.CloudError """ - key_vault = keyvault_client.vaults.get( - resource_group_name=resource_group_name, vault_name=key_vault_name, - ) try: - stg_name = generate_name(key_vault_name) - logging.info(" Creating a Storage Account") - logging.info(" executing client_storage.storage_accounts.begin_create") - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" account_name={stg_name}") - stg_account = create_storage_account( - resource_group_name, stg_name, region, storage_client + key_vault = keyvault_client.vaults.get( + resource_group_name=resource_group_name, vault_name=key_vault_name, ) log = LogSettings( category="AuditEvent", enabled=True, retention_policy=RetentionPolicy(enabled=True, days=180), ) - - logging.info(" Creating a Diagnostic setting for key vault logs") - logging.info( - " executing monitor_client.diagnostic_settings.create_or_update" + app_details = graph_client.applications.get_service_principals_id_by_app_id( + application_id=client_id ) - logging.info(f" resource_uri={key_vault.id}") - logging.info(f" name={key_vault_name}") - - monitor_client.diagnostic_settings.create_or_update( - resource_uri=key_vault.id, - name=key_vault_name, - parameters=DiagnosticSettingsResource( - storage_account_id=stg_account.id, logs=[log], - ), + # Check if the Storage Account Created by CHSS is available in the same region and resource group + stg_name = generate_name(region, subscription_id, resource_group_name) + stg_account = self.check_stg_account( + storage_client, region, stg_name, resource_group_name + ) + if stg_account is None: + # If the Storage Account does not exists, create a storage account to store logs + logging.info(" Creating a Storage Account") + logging.info( + " executing client_storage.storage_accounts.begin_create" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={stg_name}") + stg_account = self.create_storage_account( + resource_group_name, stg_name, region, storage_client + ) + # Check if the Key Vault created by CHSS exists in the given region and resource group + encryption_key_vault_name = generate_name( + region, subscription_id, resource_group_name + ) + encryption_key_vault = self.check_key_vault( + keyvault_client, + region, + encryption_key_vault_name, + resource_group_name, + ) + if encryption_key_vault is None: + # If the Key Vault does not exists, create Key Vault + encryption_key_vault = self.create_key_vault( + keyvault_client, + resource_group_name, + encryption_key_vault_name, + region, + tenant_id, + app_details.value, + stg_account.identity.principal_id, + ) + # Create a key to encrypt the Storage Account + key = self.create_key( + credentials, encryption_key_vault_name, stg_account.name + ) + # Encrypt the Storage Account using the above Key + self.update_storage_account_encryption( + storage_client, + resource_group_name, + stg_name, + key.name, + encryption_key_vault.properties.vault_uri, + ) + # Create a diagnostic setting to store Key Vault Logs + self.create_diagnostic_setting( + monitor_client, + encryption_key_vault.id, + encryption_key_vault.name, + stg_account.id, + log, + ) + else: + # Update the key vault access policy to give permissions for both app and storage account. + self.update_key_vault_access_policy( + keyvault_client, + resource_group_name, + encryption_key_vault.name, + tenant_id, + app_details.value, + stg_account.identity.principal_id, + ) + # Create a key to encrypt the Storage Account + key = self.create_key( + credentials, encryption_key_vault.name, stg_account.name + ) + # Encrypt the Storage Account using the above Key + self.update_storage_account_encryption( + storage_client, + resource_group_name, + stg_account.name, + key.name, + encryption_key_vault.properties.vault_uri, + ) + # Creating Diagnostic settings to store violated Key vault's logs + self.create_diagnostic_setting( + monitor_client, key_vault.id, key_vault_name, stg_account.id, log ) except Exception as e: logging.error(f"{str(e)}") raise - return 0 def run(self, args): @@ -174,26 +630,41 @@ def run(self, args): :returns: int """ params = self.parse(args[1]) + client_id = os.environ.get("AZURE_CLIENT_ID") + client_secret = os.environ.get("AZURE_CLIENT_SECRET") + tenant_id = os.environ.get("AZURE_TENANT_ID") - credentials = ClientSecretCredential( - client_id=os.environ.get("AZURE_CLIENT_ID"), - client_secret=os.environ.get("AZURE_CLIENT_SECRET"), - tenant_id=os.environ.get("AZURE_TENANT_ID"), + # credential for Storage Account and Key Vault management client + credential = ClientSecretCredential( + client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, ) - storage_client = StorageManagementClient( - credentials, params["subscription_id"] + + # credential for AzureGraphRbacManagementClient + credentials = ServicePrincipalCredentials( + client_id=client_id, + secret=client_secret, + tenant=tenant_id, + resource="https://graph.windows.net", ) + + storage_client = StorageManagementClient(credential, params["subscription_id"]) keyvault_client = KeyVaultManagementClient( - credentials, params["subscription_id"] + credential, params["subscription_id"] ) - monitor_client = MonitorClient(credentials, params["subscription_id"]) + graph_client = GraphRbacManagementClient(credentials, tenant_id, base_url=None) + monitor_client = MonitorClient(credential, params["subscription_id"]) return self.remediate( + client_id, + tenant_id, keyvault_client, monitor_client, storage_client, + graph_client, + credential, params["resource_group_name"], params["key_vault_name"], params["region"], + params["subscription_id"], ) diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt index b184eeb..0a3f16a 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt @@ -25,6 +25,12 @@ azure-mgmt-monitor==1.0.1 \ azure-mgmt-keyvault==8.0.0 \ --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +azure-graphrbac==0.61.1 \ + --hash=sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2 \ + --hash=sha256:7b4e0f05676acc912f2b33c71c328d9fb2e4dc8e70ebadc9d3de8ab08bf0b175 +azure-keyvault-keys==4.3.1 \ + --hash=sha256:25cf889bbcafd8dee0e068fd48d84e24e11143bf85e35c68d997a222e4a0f7fb \ + --hash=sha256:fbf67bca913ebf68b9075ee9d2e2b899dc3c7892cc40abfe1b08220a382f6ed9 certifi==2020.6.20 \ --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json index 16a9bb0..409cffd 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json @@ -9,9 +9,14 @@ "actions": [ "Microsoft.Storage/storageAccounts/read", "Microsoft.Storage/storageAccounts/write", + "Microsoft.Storage/storageAccounts/blobServices/write", + "Microsoft.Storage/storageAccounts/blobServices/read", "Microsoft.Insights/DiagnosticSettings/Write", "Microsoft.KeyVault/vaults/read", - "Microsoft.KeyVault/vaults/write" + "Microsoft.KeyVault/vaults/write", + "Microsoft.KeyVault/vaults/keys/read", + "Microsoft.KeyVault/vaults/keys/write", + "Microsoft.KeyVault/vaults/accessPolicies/write" ], "notActions": [], "dataActions": [], diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt index 759f2ca..4ee3a9a 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt @@ -1,6 +1,9 @@ -r requirements.txt -c constraints.txt +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a attrs==20.1.0 \ --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt index fbfcae4..2617d96 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt @@ -10,3 +10,9 @@ azure-mgmt-monitor==1.0.1 \ azure-mgmt-keyvault==8.0.0 \ --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +azure-graphrbac==0.61.1 \ + --hash=sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2 \ + --hash=sha256:7b4e0f05676acc912f2b33c71c328d9fb2e4dc8e70ebadc9d3de8ab08bf0b175 +azure-keyvault-keys==4.3.1 \ + --hash=sha256:25cf889bbcafd8dee0e068fd48d84e24e11143bf85e35c68d997a222e4a0f7fb \ + --hash=sha256:fbf67bca913ebf68b9075ee9d2e2b899dc3c7892cc40abfe1b08220a382f6ed9 diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/README.md b/remediation_worker/jobs/azure_sql_data_encryption_on/README.md index cf41263..06fd7b1 100644 --- a/remediation_worker/jobs/azure_sql_data_encryption_on/README.md +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/README.md @@ -35,13 +35,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision a Virtual Machine Create an Azure Virtual Machine instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned Azure Virtual Machine instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the Azure Virtual Machine instance and run the command below to deploy the worker image: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md index 7e84da9..f7f92d2 100644 --- a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md @@ -35,13 +35,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision a Virtual Machine Create an Azure Virtual Machine instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned Azure Virtual Machine instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the Azure Virtual Machine instance and run the command below to deploy the worker image: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py b/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py index 73901ef..7008b05 100644 --- a/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py +++ b/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py @@ -17,6 +17,18 @@ from remediation_worker.jobs.azure_key_vault_logging_for_keyvault_enabled.azure_key_vault_logging_for_keyvault_enabled import ( EnableKeyVaultLogging, ) +from azure.mgmt.storage.models import StorageAccount +from azure.mgmt.keyvault.models import ( + VaultProperties, + Vault, + Sku, +) +from azure.keyvault.keys import KeyVaultKey +from azure.mgmt.monitor.models import ( + LogSettings, + RetentionPolicy, + DiagnosticSettingsResource, +) @pytest.fixture @@ -45,28 +57,175 @@ def test_parse_payload(self, valid_payload): assert params["subscription_id"] == "subscription_id" assert params["region"] == "region" - def test_remediate_success(self): + def test_remediate_success_with_stg(self): + client_id = Mock() + tenant_id = Mock() + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + log = LogSettings( + category="AuditEvent", + enabled=True, + retention_policy=RetentionPolicy(enabled=True, days=180), + ) + action = EnableKeyVaultLogging() + action.check_stg_account = Mock() + action.create_diagnostic_setting = Mock() + """ + StorageAccountListResult = Mock() + storage_accounts_list = [] + storage_accounts_list.append(StorageAccountListResult) + action.check_stg_account.return_value = storage_accounts_list + """ + action.check_stg_account.return_value = StorageAccount( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/chss538f633keyvaultlogs", + name="chss538f633keyvaultlogs", + location="eastus", + ) + action.create_diagnostic_setting.return_value = DiagnosticSettingsResource( + storage_account_id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/chss538f633keyvaultlogs", + logs=[log], + ) + assert ( + action.remediate( + client_id, + tenant_id, + keyvault_client, + monitor_client, + storage_client, + graph_client, + credentials, + "resource_group", + "key_vault_name", + "region", + "subscription_id", + ) + == 0 + ) + assert action.create_diagnostic_setting.call_count == 1 + + def test_remediate_success_without_stg_without_keyvault(self): + client_id = Mock() + tenant_id = Mock() storage_client = Mock() keyvault_client = Mock() monitor_client = Mock() + graph_client = Mock() + credentials = Mock() action = EnableKeyVaultLogging() + action.check_stg_account = Mock() + action.check_key_vault = Mock() + action.create_key = Mock() + action.create_key_vault = Mock() + action.create_diagnostic_setting = Mock() + action.create_storage_account = Mock() + action.create_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + action.check_stg_account.return_value = None + action.check_key_vault.return_value = None assert ( action.remediate( + client_id, + tenant_id, keyvault_client, monitor_client, storage_client, + graph_client, + credentials, "resource_group", "key_vault_name", "region", + "subscription_id", ) == 0 ) - assert storage_client.storage_accounts.begin_create.call_count == 1 - assert monitor_client.diagnostic_settings.create_or_update.call_count == 1 + assert action.create_diagnostic_setting.call_count == 2 + assert action.create_storage_account.call_count == 1 + assert action.create_key_vault.call_count == 1 + assert action.create_key.call_count == 1 + + def test_remediate_success_without_stg_with_keyvault(self): + client_id = Mock() + tenant_id = Mock() + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + action = EnableKeyVaultLogging() + action.check_stg_account = Mock() + action.check_key_vault = Mock() + action.create_key = Mock() + action.create_key_vault = Mock() + action.create_diagnostic_setting = Mock() + action.create_storage_account = Mock() + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + action.check_stg_account.return_value = None + action.check_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + assert ( + action.remediate( + client_id, + tenant_id, + keyvault_client, + monitor_client, + storage_client, + graph_client, + credentials, + "resource_group", + "key_vault_name", + "region", + "subscription_id", + ) + == 0 + ) + assert action.create_diagnostic_setting.call_count == 1 + assert action.create_storage_account.call_count == 1 + assert action.create_key.call_count == 1 def test_remediate_with_exception(self): - client = Mock() - client.storage_accounts.update.side_effect = Exception + client_id = Mock() + tenant_id = Mock() + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + monitor_client.diagnostic_settings.create_or_update.side_effect = Exception action = EnableKeyVaultLogging() with pytest.raises(Exception): - assert action.remediate(client, "security_group_id", "resource_group") + assert action.remediate( + client_id, + tenant_id, + keyvault_client, + monitor_client, + storage_client, + graph_client, + credentials, + "resource_group", + "key_vault_name", + "region", + )