diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..671250d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# See GitHub's documentation for more information on this file: +# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/keyfactor-release-workflow.yml b/.github/workflows/keyfactor-release-workflow.yml index 3e270cf..bd5f384 100644 --- a/.github/workflows/keyfactor-release-workflow.yml +++ b/.github/workflows/keyfactor-release-workflow.yml @@ -1,4 +1,4 @@ -name: Keyfactor Release Workflow +name: Keyfactor Bootstrap Workflow on: workflow_dispatch: @@ -11,10 +11,17 @@ on: jobs: call-starter-workflow: - uses: keyfactor/actions/.github/workflows/starter.yml@v3.1.2 + uses: keyfactor/actions/.github/workflows/starter.yml@v4 + with: + command_token_url: ${{ vars.COMMAND_TOKEN_URL }} + command_hostname: ${{ vars.COMMAND_HOSTNAME }} + command_base_api_path: ${{ vars.COMMAND_API_PATH }} secrets: token: ${{ secrets.V2BUILDTOKEN}} - APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} - + scan_token: ${{ secrets.SAST_TOKEN }} + entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }} + entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }} + command_client_id: ${{ secrets.COMMAND_CLIENT_ID }} + command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }} \ No newline at end of file diff --git a/AzureKeyVault/AzureClient.cs b/AzureKeyVault/AzureClient.cs index 3925c99..e11fbe3 100644 --- a/AzureKeyVault/AzureClient.cs +++ b/AzureKeyVault/AzureClient.cs @@ -158,8 +158,17 @@ public virtual async Task CreateVault() logger.LogTrace($"getting subscription info for provided subscription id {VaultProperties.SubscriptionId}"); - SubscriptionResource subscription = KvManagementClient.GetSubscriptionResource(SubscriptionResource.CreateResourceIdentifier(VaultProperties.SubscriptionId)); - ResourceGroupResource resourceGroup = subscription.GetResourceGroup(VaultProperties.ResourceGroupName); + var subscription = KvManagementClient.GetSubscriptionResource(SubscriptionResource.CreateResourceIdentifier(VaultProperties.SubscriptionId)); + + var resourceGroups = subscription.GetResourceGroups(); + ResourceGroupResource resourceGroup = await resourceGroups.GetAsync(VaultProperties.ResourceGroupName); + logger.LogTrace("calling getAsync on resourcegroup..."); + await resourceGroup.GetAsync(); + logger.LogTrace("completed getAsync on resource group..."); + + var s = resourceGroup.HasData.ToString(); + + logger.LogTrace($"resource group has data?: {s}"); AzureLocation loc; @@ -170,7 +179,7 @@ public virtual async Task CreateVault() { logger.LogTrace($"no Vault Region location specified for new Vault, Getting available regions for resource group {resourceGroup.Data.Name}."); var locOptions = await resourceGroup.GetAvailableLocationsAsync(); - logger.LogTrace($"got location options for subscription {subscription.Data.SubscriptionId}", locOptions); + logger.LogTrace($"got location options for subscription {VaultProperties.SubscriptionId}", locOptions); loc = locOptions.Value.FirstOrDefault(); } catch (Exception ex) @@ -269,33 +278,46 @@ public virtual async Task> GetCertificatesAsyn { List inventoryItems = new List(); AsyncPageable inventory = null; + var fullInventoryList = new List(); + var failedCount = 0; + Exception innerException = null; + try { logger.LogTrace("calling GetPropertiesOfCertificates() on the Certificate Client"); inventory = CertClient.GetPropertiesOfCertificatesAsync(); + + logger.LogTrace($"created pageable request"); + + logger.LogTrace("iterating over result pages for complete list.."); - logger.LogTrace($"got a pageable response"); + await foreach (var cert in inventory) + { + logger.LogTrace($"adding cert with ID: {cert.Id} to the list."); + fullInventoryList.Add(cert); // convert to list from pages + } + + logger.LogTrace($"compiled full inventory list of {fullInventoryList.Count()} certificate(s)"); } - catch (Exception ex) + catch (AuthenticationFailedException ex) { - logger.LogError($"Error performing inventory. {ex.Message}", ex); + logger.LogError($"Authentication failed: {ex.Message}"); + logger.LogError(LogHandler.FlattenException(ex)); throw; } - - logger.LogTrace("iterating over result pages for complete list.."); - - var fullInventoryList = new List(); - var failedCount = 0; - Exception innerException = null; - - await foreach (var cert in inventory) + catch (RequestFailedException ex) // Catch other potential Azure-specific errors + { + logger.LogError($"Azure Key Vault operation failed: {ex.Status} - {ex.Message}"); + logger.LogError(LogHandler.FlattenException(ex)); + throw; + } + catch (Exception ex) { - logger.LogTrace($"adding cert with ID: {cert.Id} to the list."); - fullInventoryList.Add(cert); // convert to list from pages + logger.LogError($"Error performing inventory. {ex.Message}", ex); + logger.LogError(LogHandler.FlattenException(ex)); + throw; } - logger.LogTrace($"compiled full inventory list of {fullInventoryList.Count()} certificate(s)"); - foreach (var certificate in fullInventoryList) { logger.LogTrace($"getting details for the individual certificate with id: {certificate.Id} and name: {certificate.Name}"); @@ -317,8 +339,7 @@ public virtual async Task> GetCertificatesAsyn catch (Exception ex) { failedCount++; - innerException = ex; - logger.LogError($"Failed to retreive details for certificate {certificate.Name}. Exception: {ex.Message}"); + logger.LogError($"Failed to retreive details for certificate {certificate.Name}. Exception: {LogHandler.FlattenException(ex)}"); // continuing with inventory instead of throwing, in case there's an issue with a single certificate } } diff --git a/AzureKeyVault/Jobs/AzureKeyVaultJob.cs b/AzureKeyVault/Jobs/AzureKeyVaultJob.cs index 3279932..911be72 100644 --- a/AzureKeyVault/Jobs/AzureKeyVaultJob.cs +++ b/AzureKeyVault/Jobs/AzureKeyVaultJob.cs @@ -40,7 +40,7 @@ public void InitializeStore(dynamic config) // ClientSecret can be omitted for managed identities, required for service principal auth VaultProperties.ClientSecret = PAMUtilities.ResolvePAMField(PamSecretResolver, logger, "Server Password", config.ServerPassword); - if (VaultProperties.ClientSecret == null) + if (string.IsNullOrEmpty(VaultProperties.ClientSecret)) { logger.LogTrace("No client secret provided, assuming Managed Identity authentication"); } diff --git a/AzureKeyVault/Jobs/Management.cs b/AzureKeyVault/Jobs/Management.cs index 6a54a7e..8c45f1f 100644 --- a/AzureKeyVault/Jobs/Management.cs +++ b/AzureKeyVault/Jobs/Management.cs @@ -17,7 +17,6 @@ using Keyfactor.Orchestrators.Extensions.Interfaces; using System.Collections.Generic; using Newtonsoft.Json; -using System.Security.AccessControl; namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault { @@ -42,15 +41,18 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { Result = OrchestratorJobStatusJobResult.Failure, FailureMessage = "Invalid Management Operation" - }; - - string tagsJSON; - bool preserveTags; + }; logger.LogTrace("parsing entry parameters.. "); - - tagsJSON = config.JobProperties[EntryParameters.TAGS] as string ?? string.Empty; - preserveTags = config.JobProperties[EntryParameters.PRESERVE_TAGS] as bool? ?? false; + string tagsJSON = string.Empty; + bool preserveTags = false; + if (config.JobProperties != null) + { + config.JobProperties.TryGetValue(EntryParameters.TAGS, out object tagsJSONObj); + config.JobProperties.TryGetValue(EntryParameters.PRESERVE_TAGS, out object preserveTagsObj); + tagsJSON = tagsJSONObj == null ? string.Empty : tagsJSONObj.ToString(); + preserveTags = preserveTagsObj == null ? false : Boolean.Parse(preserveTagsObj.ToString()); + } switch (config.OperationType) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf89a4..e9c736d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +- 3.1.11 + - bug fix for error when creating new Azure Keyvaults + - documentation updates + +- 3.1.10 + - bug fix for government cloud host name resolution + - 3.1.9 - Added optional entry parameter to indicate that existing tags should be preserved if certificate is replaced - bug fix for government cloud host name resolution diff --git a/README.md b/README.md index 92a8541..4eb5a79 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ The high level steps required to configure the Azure Keyvault Orchestrator exten 1) [Configure the Azure Keyvault for client access](#configure-the-azure-keyvault-for-client-access) -1) [Create the Store Type in Keyfactor](#create-the-akv-certificate-store-type) +1) [Create the Store Type in Keyfactor](#akv-certificate-store-type) 1) [Install the Extension on the Orchestrator](#installation) @@ -544,7 +544,7 @@ To use the Azure Key Vault Universal Orchestrator extension, you **must** create The Azure Keyvault Certificate Store Type is designed to integrate with Microsoft Azure Key Vault, enabling users to -manage and automate the lifecycle of cryptographic certificates stored in Azure Key Vault through Keyfactor Command. +manage and automate the lifecycle of cryptographic certificates stored in Azure Keyvault through Keyfactor Command. This Certificate Store Type represents the connection and configuration necessary to interact with specific instances of Azure Key Vault, allowing for operations such as inventory, addition, removal, and discovery of certificates and certificate stores. @@ -565,6 +565,11 @@ However, ensuring that the orchestrator has network access to Azure endpoints is mindful of these caveats and limitations will help ensure successful deployment and use of the Azure Keyvault Certificate Store Type within your organization’s security framework. +> :warning: +> The alias you provide when enrolling a certificate will be used as the certificate name in Azure Keyvault. +> Consequently; [it must _only_ contain alphanumeric characters and hyphens](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftkeyvault). +> If you encounter the error "The request URI contains an invalid name" when attempting to perform an enrollment, it is likely due to the use of disallowed characters in the alias. + @@ -633,7 +638,7 @@ the Keyfactor Command Portal ##### Advanced Tab | Attribute | Value | Description | | --------- | ----- | ----- | - | Supports Custom Alias | Optional | Determines if an individual entry within a store can have a custom Alias. | + | Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. | | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. | | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) | @@ -658,6 +663,44 @@ the Keyfactor Command Portal ![AKV Custom Fields Tab](docsource/images/AKV-custom-fields-store-type-dialog.png) + + ###### Tenant Id + The ID of the primary Azure Tenant where the KeyVaults are hosted + + ![AKV Custom Field - TenantId](docsource/images/AKV-custom-field-TenantId-dialog.png) + + + + ###### SKU Type + The SKU type for newly created KeyVaults (only needed if needing to create new KeyVaults in your Azure subscription via Command) + + ![AKV Custom Field - SkuType](docsource/images/AKV-custom-field-SkuType-dialog.png) + + + + ###### Vault Region + The Azure Region to put newly created KeyVaults (only needed if needing to create new KeyVaults in your Azure subscription via Command) + + ![AKV Custom Field - VaultRegion](docsource/images/AKV-custom-field-VaultRegion-dialog.png) + + + + ###### Azure Cloud + The Azure Cloud where the KeyVaults are located (only necessary if not using the standard Azure Public cloud) + + ![AKV Custom Field - AzureCloud](docsource/images/AKV-custom-field-AzureCloud-dialog.png) + + + + ###### Private KeyVault Endpoint + The private endpoint of your vault instance (if a private endpoint is configured in Azure) + + ![AKV Custom Field - PrivateEndpoint](docsource/images/AKV-custom-field-PrivateEndpoint-dialog.png) + + + + + ##### Entry Parameters Tab | Name | Display Name | Description | Type | Default Value | Entry has a private key | Adding an entry | Removing an entry | Reenrolling an entry | @@ -669,6 +712,20 @@ the Keyfactor Command Portal ![AKV Entry Parameters Tab](docsource/images/AKV-entry-parameters-store-type-dialog.png) + + ##### Certificate Tags + If desired, tags can be applied to the KeyVault entries. Provide them as a JSON string of key-value pairs ie: '{'tag-name': 'tag-content', 'other-tag-name': 'other-tag-content'}' + + ![AKV Entry Parameter - CertificateTags](docsource/images/AKV-entry-parameters-store-type-dialog-CertificateTags.png) + + + ##### Preserve Existing Tags + If true, this will perform a union of any tags provided with enrollment with the tags on the existing cert with the same alias and apply the result to the new certificate. + + ![AKV Entry Parameter - PreserveExistingTags](docsource/images/AKV-entry-parameters-store-type-dialog-PreserveExistingTags.png) + + + ## Installation diff --git a/docsource/akv.md b/docsource/akv.md index f6110cf..b1539de 100644 --- a/docsource/akv.md +++ b/docsource/akv.md @@ -1,7 +1,7 @@ ## Overview The Azure Keyvault Certificate Store Type is designed to integrate with Microsoft Azure Key Vault, enabling users to -manage and automate the lifecycle of cryptographic certificates stored in Azure Key Vault through Keyfactor Command. +manage and automate the lifecycle of cryptographic certificates stored in Azure Keyvault through Keyfactor Command. This Certificate Store Type represents the connection and configuration necessary to interact with specific instances of Azure Key Vault, allowing for operations such as inventory, addition, removal, and discovery of certificates and certificate stores. @@ -22,3 +22,8 @@ However, ensuring that the orchestrator has network access to Azure endpoints is mindful of these caveats and limitations will help ensure successful deployment and use of the Azure Keyvault Certificate Store Type within your organization’s security framework. +> :warning: +> The alias you provide when enrolling a certificate will be used as the certificate name in Azure Keyvault. +> Consequently; [it must _only_ contain alphanumeric characters and hyphens](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftkeyvault). +> If you encounter the error "The request URI contains an invalid name" when attempting to perform an enrollment, it is likely due to the use of disallowed characters in the alias. + diff --git a/docsource/content.md b/docsource/content.md index 0fcc7e2..5c3bcef 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -25,7 +25,7 @@ The high level steps required to configure the Azure Keyvault Orchestrator exten 1) [Configure the Azure Keyvault for client access](#configure-the-azure-keyvault-for-client-access) -1) [Create the Store Type in Keyfactor](#create-the-akv-certificate-store-type) +1) [Create the Store Type in Keyfactor](#akv-certificate-store-type) 1) [Install the Extension on the Orchestrator](#installation) diff --git a/docsource/images/AKV-advanced-store-type-dialog.png b/docsource/images/AKV-advanced-store-type-dialog.png index 7a4ebec..4827627 100644 Binary files a/docsource/images/AKV-advanced-store-type-dialog.png and b/docsource/images/AKV-advanced-store-type-dialog.png differ diff --git a/docsource/images/AKV-basic-store-type-dialog.png b/docsource/images/AKV-basic-store-type-dialog.png index 08140cc..658f177 100644 Binary files a/docsource/images/AKV-basic-store-type-dialog.png and b/docsource/images/AKV-basic-store-type-dialog.png differ diff --git a/docsource/images/AKV-custom-field-AzureCloud-dialog.png b/docsource/images/AKV-custom-field-AzureCloud-dialog.png new file mode 100644 index 0000000..e3b2d49 Binary files /dev/null and b/docsource/images/AKV-custom-field-AzureCloud-dialog.png differ diff --git a/docsource/images/AKV-custom-field-PrivateEndpoint-dialog.png b/docsource/images/AKV-custom-field-PrivateEndpoint-dialog.png new file mode 100644 index 0000000..f6b7a36 Binary files /dev/null and b/docsource/images/AKV-custom-field-PrivateEndpoint-dialog.png differ diff --git a/docsource/images/AKV-custom-field-SkuType-dialog.png b/docsource/images/AKV-custom-field-SkuType-dialog.png new file mode 100644 index 0000000..df1a07b Binary files /dev/null and b/docsource/images/AKV-custom-field-SkuType-dialog.png differ diff --git a/docsource/images/AKV-custom-field-TenantId-dialog.png b/docsource/images/AKV-custom-field-TenantId-dialog.png new file mode 100644 index 0000000..b4c5643 Binary files /dev/null and b/docsource/images/AKV-custom-field-TenantId-dialog.png differ diff --git a/docsource/images/AKV-custom-field-VaultRegion-dialog.png b/docsource/images/AKV-custom-field-VaultRegion-dialog.png new file mode 100644 index 0000000..6f1724f Binary files /dev/null and b/docsource/images/AKV-custom-field-VaultRegion-dialog.png differ diff --git a/docsource/images/AKV-custom-fields-store-type-dialog.png b/docsource/images/AKV-custom-fields-store-type-dialog.png index ecae472..54be8dc 100644 Binary files a/docsource/images/AKV-custom-fields-store-type-dialog.png and b/docsource/images/AKV-custom-fields-store-type-dialog.png differ diff --git a/docsource/images/AKV-entry-parameters-store-type-dialog-CertificateTags.png b/docsource/images/AKV-entry-parameters-store-type-dialog-CertificateTags.png new file mode 100644 index 0000000..ead5735 Binary files /dev/null and b/docsource/images/AKV-entry-parameters-store-type-dialog-CertificateTags.png differ diff --git a/docsource/images/AKV-entry-parameters-store-type-dialog-PreserveExistingTags.png b/docsource/images/AKV-entry-parameters-store-type-dialog-PreserveExistingTags.png new file mode 100644 index 0000000..d95e196 Binary files /dev/null and b/docsource/images/AKV-entry-parameters-store-type-dialog-PreserveExistingTags.png differ diff --git a/docsource/images/AKV-entry-parameters-store-type-dialog.png b/docsource/images/AKV-entry-parameters-store-type-dialog.png index 25d2c06..7d08db9 100644 Binary files a/docsource/images/AKV-entry-parameters-store-type-dialog.png and b/docsource/images/AKV-entry-parameters-store-type-dialog.png differ diff --git a/integration-manifest.json b/integration-manifest.json index 1630b9b..9eafe71 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -19,7 +19,7 @@ "BlueprintAllowed": false, "Capability": "AKV", "ClientMachineDescription": "The GUID of the tenant ID of the Azure Keyvault instance; for example, '12345678-1234-1234-1234-123456789abc'.", - "CustomAliasAllowed": "Optional", + "CustomAliasAllowed": "Required", "EntryParameters": [ { "Name": "CertificateTags",