From a225e5938f0502a9b290913d62e2ae4849517d3a Mon Sep 17 00:00:00 2001 From: Morgan Gangwere <470584+indrora@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:24:54 -0800 Subject: [PATCH 1/4] chore: create 3.2 branch --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf89a4..0da8f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- 3.2.0 + - Fancy new features here - 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 From 85e3cf0c218dc489d809b929393c0ac9e61d8a45 Mon Sep 17 00:00:00 2001 From: Joe VanWanzeele <76071503+joevanwanzeeleKF@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:28:05 -0500 Subject: [PATCH 2/4] feat: release 3.2, Added entry parameter to indicate the private key should not be exportable from KeyVault Co-authored-by: Keyfactor --- AzureKeyVault/AzureClient.cs | 5 ++- AzureKeyVault/Constants.cs | 1 + AzureKeyVault/Jobs/Management.cs | 23 +++++------ CHANGELOG.md | 6 +-- README.md | 66 ++++++++++++++++++++++++++++++-- integration-manifest.json | 15 +++++++- 6 files changed, 95 insertions(+), 21 deletions(-) diff --git a/AzureKeyVault/AzureClient.cs b/AzureKeyVault/AzureClient.cs index 3925c99..e470341 100644 --- a/AzureKeyVault/AzureClient.cs +++ b/AzureKeyVault/AzureClient.cs @@ -199,7 +199,7 @@ public virtual async Task CreateVault() } } - public virtual async Task ImportCertificateAsync(string certName, string contents, string pfxPassword, Dictionary tags) + public virtual async Task ImportCertificateAsync(string certName, string contents, string pfxPassword, Dictionary tags, bool nonExportable) { try { @@ -221,6 +221,7 @@ public virtual async Task ImportCertificateAsync( logger.LogTrace($"calling ImportCertificateAsync on the KeyVault certificate client to import certificate {certName}"); var options = new ImportCertificateOptions(certName, p12bytes); + options.Policy = new CertificatePolicy { Exportable = !nonExportable, ContentType = CertificateContentType.Pkcs12 }; if (tags.Any()) { @@ -388,7 +389,7 @@ public virtual (List, List) GetVaults() var warning = $"Exception thrown performing discovery on tenantId {searchTenantId} and subscription ID {searchSubscription}. Exception message: {ex.Message}"; logger.LogWarning(warning); - warnings.Add(warning); + warnings.Add(warning); } return (vaultNames, warnings); diff --git a/AzureKeyVault/Constants.cs b/AzureKeyVault/Constants.cs index b245237..3ae1cf8 100644 --- a/AzureKeyVault/Constants.cs +++ b/AzureKeyVault/Constants.cs @@ -16,6 +16,7 @@ static class AzureKeyVaultConstants static class EntryParameters { public const string TAGS = "CertificateTags"; public const string PRESERVE_TAGS = "PreserveExistingTags"; + public const string NON_EXPORTABLE = "NonExportable"; } static class JobTypes diff --git a/AzureKeyVault/Jobs/Management.cs b/AzureKeyVault/Jobs/Management.cs index 6a54a7e..3884503 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 { @@ -46,11 +45,13 @@ public JobResult ProcessJob(ManagementJobConfiguration config) string tagsJSON; bool preserveTags; + bool nonExportable; logger.LogTrace("parsing entry parameters.. "); tagsJSON = config.JobProperties[EntryParameters.TAGS] as string ?? string.Empty; preserveTags = config.JobProperties[EntryParameters.PRESERVE_TAGS] as bool? ?? false; + nonExportable = config.JobProperties[EntryParameters.NON_EXPORTABLE] as bool? ?? false; switch (config.OperationType) { @@ -61,7 +62,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) case CertStoreOperationType.Add: logger.LogDebug($"Begin Management > Add..."); - complete = PerformAddition(config.JobCertificate.Alias, config.JobCertificate.PrivateKeyPassword, config.JobCertificate.Contents, tagsJSON, config.JobHistoryId, config.Overwrite, preserveTags); + complete = PerformAddition(config.JobCertificate.Alias, config.JobCertificate.PrivateKeyPassword, config.JobCertificate.Contents, tagsJSON, config.JobHistoryId, config.Overwrite, preserveTags, nonExportable); break; case CertStoreOperationType.Remove: logger.LogDebug($"Begin Management > Remove..."); @@ -103,7 +104,7 @@ protected async Task PerformCreateVault(long jobHistoryId) #endregion #region Add - protected virtual JobResult PerformAddition(string alias, string pfxPassword, string entryContents, string tagsJSON, long jobHistoryId, bool overwrite, bool preserveTags) + protected virtual JobResult PerformAddition(string alias, string pfxPassword, string entryContents, string tagsJSON, long jobHistoryId, bool overwrite, bool preserveTags, bool nonExportable) { var complete = new JobResult() { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = jobHistoryId }; @@ -138,16 +139,16 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st if (existing != null) { logger.LogTrace($"there is an existing cert.."); - } - existingTags = existing?.Properties.Tags as Dictionary ?? new Dictionary(); + existingTags = existing?.Properties.Tags as Dictionary ?? new Dictionary(); - logger.LogTrace("existing cert tags: "); - if (!existingTags.Any()) logger.LogTrace("(none)"); + logger.LogTrace("existing cert tags: "); + if (!existingTags.Any()) logger.LogTrace("(none)"); - foreach (var tag in existingTags) - { - logger.LogTrace(tag.Key + " : " + tag.Value); + foreach (var tag in existingTags) + { + logger.LogTrace(tag.Key + " : " + tag.Value); + } } // if overwrite is unchecked, check for an existing cert first @@ -173,7 +174,7 @@ protected virtual JobResult PerformAddition(string alias, string pfxPassword, st } } - var cert = AzClient.ImportCertificateAsync(alias, entryContents, pfxPassword, tagDict).Result; + var cert = AzClient.ImportCertificateAsync(alias, entryContents, pfxPassword, tagDict, nonExportable).Result; // Ensure the return object has a AKV version tag, and Thumbprint if (!string.IsNullOrEmpty(cert.Properties.Version) && diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da8f2e..8ca7d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ - 3.2.0 - - Fancy new features here + - Added an optional entry parameter to indicate whether the private key of the cert should be not exportable when stored in KeyVault + - Now specifying the pkcs12 format when wirting certs to Azure KeyVault. This should prevent the error when a PEM cert was added outside of Command and then we attempt to update without specifying the format (Azure assumes PEM and throws an error if not). + - 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 - 3.1.8 - Fixed bug where enrollment would fail if the CertificateTags field was not defined as an entry parameter @@ -13,7 +14,6 @@ - Added support for Azure KeyVault Certificate Metadata via Entry Parameters - Fixed issue where an error would be returned during Inventory if 0 certificates were found - Converted to BouncyCastle crypto libraries - - 3.1.6 - Preventing CertStore parameters from getting used if present but empty. diff --git a/README.md b/README.md index 92a8541..c7f7431 100644 --- a/README.md +++ b/README.md @@ -658,32 +658,90 @@ 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 | | ---- | ------------ | ---- | ------------- | ----------------------- | ---------------- | ----------------- | ------------------- | ----------- | | CertificateTags | 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'}' | string | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | | PreserveExistingTags | 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. | Bool | False | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | + | NonExportable | Non Exportable Private Key | If true, this will mark the certificate as having a non-exportable private key when importing into Azure KeyVault | Bool | False | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | The Entry Parameters tab should look like this: ![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) + + + ##### Non Exportable Private Key + If true, this will mark the certificate as having a non-exportable private key when importing into Azure KeyVault + + ![AKV Entry Parameter - NonExportable](docsource/images/AKV-entry-parameters-store-type-dialog-NonExportable.png) + + + ## Installation 1. **Download the latest Azure Key Vault Universal Orchestrator extension from GitHub.** - Navigate to the [Azure Key Vault Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/azurekeyvault-orchestrator/releases/latest). Refer to the compatibility matrix below to determine whether the `net6.0` or `net8.0` asset should be downloaded. Then, click the corresponding asset to download the zip archive. + Navigate to the [Azure Key Vault Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/azurekeyvault-orchestrator/releases/latest). Refer to the compatibility matrix below to determine the asset should be downloaded. Then, click the corresponding asset to download the zip archive. | Universal Orchestrator Version | Latest .NET version installed on the Universal Orchestrator server | `rollForward` condition in `Orchestrator.runtimeconfig.json` | `azurekeyvault-orchestrator` .NET version to download | | --------- | ----------- | ----------- | ----------- | | Older than `11.0.0` | | | `net6.0` | | Between `11.0.0` and `11.5.1` (inclusive) | `net6.0` | | `net6.0` | - | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `Disable` | `net6.0` | - | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `LatestMajor` | `net8.0` | - | `11.6` _and_ newer | `net8.0` | | `net8.0` | + | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `Disable` | `net6.0` || Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `LatestMajor` | `net8.0` | + | `11.6` _and_ newer | `net8.0` | | `net8.0` | Unzip the archive containing extension assemblies to a known location. diff --git a/integration-manifest.json b/integration-manifest.json index 1630b9b..11ceec8 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -46,7 +46,20 @@ "OnRemove": false, "OnReenrollment": false } - } + }, + { + "Name": "NonExportable", + "DisplayName": "Non Exportable Private Key", + "Description": "If true, this will mark the certificate as having a non-exportable private key when importing into Azure KeyVault", + "Type": "Bool", + "DefaultValue": "False", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + } + } ], "JobProperties": [], "LocalStore": false, From 119503cca456a204e966d08e4fe5d1e7a2c93b40 Mon Sep 17 00:00:00 2001 From: Joe VanWanzeele <76071503+joevanwanzeeleKF@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:42:42 -0400 Subject: [PATCH 3/4] release: 3.2.1 Added entry parameter to indicate the private key should not be exportable from KeyVault Co-authored-by: Keyfactor --------- Co-authored-by: Joe VanWanzeele <76071503+joevanwanzeeleKF@users.noreply.github.com> Co-authored-by: Keyfactor * cleaned up docs, split RBAC permissions into seperate file for brevity * Update generated docs * Updated changelog, nuget package references * Explicit update of Newtonsoft.Json.Bson from 1.0.2 (used by Microsoft.AspNet.WebApi.Client) to 1.0.3 to address vulnerability --------- Co-authored-by: Morgan Gangwere <470584+indrora@users.noreply.github.com> Co-authored-by: Keyfactor --- AzureKeyVault.sln | 1 + AzureKeyVault/AzureKeyVault.csproj | 31 +- CHANGELOG.md | 5 + README.md | 714 ++++++++--------------------- docsource/content.md | 372 +-------------- rbac.md | 261 +++++++++++ 6 files changed, 503 insertions(+), 881 deletions(-) create mode 100644 rbac.md diff --git a/AzureKeyVault.sln b/AzureKeyVault.sln index 3437469..ebd5192 100644 --- a/AzureKeyVault.sln +++ b/AzureKeyVault.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution docsource\content.md = docsource\content.md create_sp_azure.md = create_sp_azure.md integration-manifest.json = integration-manifest.json + rbac.md = rbac.md EndProjectSection EndProject Global diff --git a/AzureKeyVault/AzureKeyVault.csproj b/AzureKeyVault/AzureKeyVault.csproj index d23caba..85c44bd 100644 --- a/AzureKeyVault/AzureKeyVault.csproj +++ b/AzureKeyVault/AzureKeyVault.csproj @@ -17,26 +17,27 @@ - - - - - - - - - + + + + + + + + + - - + + - - - + + + + - + diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ca7d18..520816e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ + +- 3.2.1 + - Documentation updates and improvements + - Updated NuGet packages + - 3.2.0 - Added an optional entry parameter to indicate whether the private key of the cert should be not exportable when stored in KeyVault - Now specifying the pkcs12 format when wirting certs to Azure KeyVault. This should prevent the error when a PEM cert was added outside of Command and then we attempt to update without specifying the format (Azure assumes PEM and throws an error if not). diff --git a/README.md b/README.md index c7f7431..19a3dd6 100644 --- a/README.md +++ b/README.md @@ -66,83 +66,15 @@ Before installing the Azure Key Vault Universal Orchestrator extension, we recom The high level steps required to configure the Azure Keyvault Orchestrator extension are: -1) [Migrating from the Windows Orchestrator for Azure KeyVault](#migrating-from-the-windows-orchestrator-for-azure-keyvault) +1) [Configure client access; permissions and authentication](#configure-the-azure-keyvault-for-client-access) -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) 1) [Create the Certificate Store](#add-a-new-or-existing-azure-keyvault-certificate-store) -_Note that the certificate store type used by this Universal Orchestrator support for Azure Keyvault is not compatible -with the certificate store type used by with Windows Orchestrator version for Azure Keyvault. -If your Keyfactor instance has used the Windows Orchestrator for Azure Keyvault, a specific migration process is -required. -See [Migrating from the Windows Orchestrator for Azure KeyVault](#migrating-from-the-windows-orchestrator-for-azure-keyvault) -section below._ - -
- -

Migrating from the Windows Orchestrator for Azure KeyVault

-If you were previously using the Azure Keyvault extension for the **Windows** Orchestrator, it is necessary to remove the Store Type definition as well as any Certificate stores that use the previous store type. -This is because the store type parameters have changed in order to facilitate the Discovery and Create functionality. - -If you have an existing AKV store type that was created for use with the Windows Orchestrator, you will need to follow -the steps in one of the below sections in order to transfer the capability to the Universal Orchestrator. - -> :warning: -> Before removing the certificate stores, view their configuration details and copy the values. -> Copying the values in the store parameters will save time when re-creating the stores. - -Follow the below steps to remove the AKV capability from **each** active Windows Orchestrator that supports it: - -##### If the Windows Orchestrator should still manage other cert store types - -_If the Windows Orchestrator will still be used to manage some store types, we will remove only the Azure Keyvault -functionality._ - -1) On the Windows Orchestrator host machine, run the Keyfactor Agent Configuration Wizard -1) Proceed through the steps to "Select Features" -1) Expand "Cert Stores" and un-check "Azure Keyvault" -1) Click "Apply Configuration" - -1) Open the Keyfactor Platform and navigate to **Orchestrators > Management** -1) Confirm that "AKV" no longer appears under "Capabilities" -1) Navigate to **Orchestrators > Management**, select the orchestrator and click "DISAPPROVE" to disapprove it and - cancel pending jobs. -1) Navigate to **Locations > Certificate Stores** -1) Select any stores with the Category "Azure Keyvault" and click "DELETE" to remove them from Keyfactor. -1) Navigate to the Administrative menu (gear icon) and then **> Certificate Store Types** -1) Select Azure Keyvault, click "DELETE" and confirm. -1) Navigate to **Orchestrators > Management**, select the orchestrator and click "APPROVE" to re-approve it for use. - -1) Repeat these steps for any other Windows Orchestrators that support the AKV store type. - -##### If the Windows Orchestrator can be retired completely - -_If the Windows Orchestrator is being completely replaced with the Universal Orchestrator, we can remove all associated -stores and jobs._ - -1) Navigate to **Orchestrators > Management** and select the Windows Orchestrator from the list. -1) With the orchestrator selected, click the "RESET" button at the top of the list -1) Make sure the orchestrator is still selected, and click "DISAPPROVE". -1) Click "OK" to confirm that you will remove all jobs and certificate stores associated to this orchestrator. -1) Navigate to the Administrative (gear icon in the top right) and then **Certificate Store Types** -1) Select "Azure Keyvault", click "DELETE" and confirm. -1) Repeat these steps for any other Windows Orchestrators that support the AKV store type (if they can also be retired). - -Note: Any Azure Keyvault certificate stores removed can be re-added once the Universal Orchestrator is configured with -the AKV capability. - -#### Migrating from version 1.x or version 2.x of the Azure Keyvault Orchestrator Extension - -It is not necessary to re-create all of the certificate stores when migrating from a previous version of this extension, -though it is important to note that Azure KeyVaults found during a Discovery job -will return with latest store path format: `{subscription id}:{resource group name}:{new vault name}`. - -
+> :warning: If you are still using the (deprecated) Windows Orchestrator, you can find instructions for migrating by searching previous versions of this README. --- @@ -150,8 +82,8 @@ will return with latest store path format: `{subscription id}:{resource group na In order for this orchestrator extension to be able to interact with your instances of Azure Keyvault, it will need to authenticate with a identity that has sufficient permissions to perform the jobs. Microsoft Azure implements both Role -Based Access Control (RBAC) and the classic Access Policy method. RBAC is the preferred method, as it allows the -assignment of granular level, inheretable access control on both the contents of the KeyVaults, as well as higher-level +Based Access Control (RBAC) and the classic Access Policy method. RBAC is the preferred method, and currently the default used by Azure. +It allows the assignment of granular level, inheretable access control on both the contents of the KeyVaults, as well as higher-level management operations. For more information and a comparison of the two access control strategies, refer to [this article](learn.microsoft.com/en-us/azure/key-vault/general/rbac-access-policy). @@ -159,277 +91,12 @@ to [this article](learn.microsoft.com/en-us/azure/key-vault/general/rbac-access- Azure KeyVaults originally utilized access policies for permissions and since then, Microsoft has begun recommending Role Based Access Control (RBAC) as the preferred method of authorization. -As of this version, new KeyVaults created via this integration are created with Access Policy authorization. This will -change to RBAC in the next release. -The access control type the KeyVault implements can be changed in the KeyVault configuration within the Azure Portal. -New KeyVaults created via Keyfactor by way of this integration will be accessible for subsequent actions regardless of -the access control type. - -##### Configure Role Based Access Control (RBAC) - -In order to illustrate the minimum permissions that the authenticating entity (service principal or managed identity) -requires, -we have created 3 seperate custom role definitions that you can use as a reference when creating an RBAC role definition -in your Azure environment. - -The reason for 3 definitions is that certain orchestrator jobs, such as Create (new KeyVault) or Discovery require more -elevated permissions at a different scope than the basic certificate operations (Inventory, Add, Remove) performed -within a specific KeyVault. +New KeyVaults created via this integration are created with the default authorization method that is configured in the Azure environment. -If you know that you will utilize all of the capabilities of this integration; the last custom role definition contains -all necessary permissions for performing all of the Jobs (Discovery, Create KeyVault, Inventory/Add/Remove -certificates). - -##### Built-in vs. custom roles - -> :warning: The custom role definitions below are designed to contain the absolute minimum permissions required. They -> are not intended to be used verbatim without consulting your organization's security team and/or Azure Administrator. -> Keyfactor does not provide consulting on internal security practices. - -It is possible to use the built-in roles provided by Microsoft for these operations. The built-in roles may contain more -permissions than necessary. -Whether to create custom role definitions or use an existing or pre-built role will depend on your organization's -securuity requirements. -For each job type performed by this orchestrator, we've included the minimally sufficient built-in role name(s) along -with our custom role definitions that limit permissions to the specific actions and scopes necessary. - -
-

Create Vault permissions

- -In order to allow for the ability to create new Azure KeyVaults from within command, here is a role that defines the -necessary permissions to do so. If you will never be creating new Azure KeyVaults from within Command, then it is -unnecessary to provide the authenticating entity with these permissions. - -> :warning: When creating a new KeyVault, we grant the creating entity the built-in "Key Vault Certificates Officer" -> role in order to be able to perform subsequent actions on the contents of the -> KeyVault. [click here](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) -> to see the list of permissions included in the Key Vault Certificates Officer built-in role. - -- built-in roles (both are required): - - ["Key Vault Contributor"](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-contributor) - - ["Key Vault Access Administrator"](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-data-access-administrator) - -- lowest level scope required - a resource group that will contain the new KeyVault. - -- condition: - -```js -"((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" -``` - -the above condition limits the ability to assign roles to a single role only (Key Vault Certificates Officer). This is -more restrictive than the condition on the built-in role -of [Key Vault Access Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-data-access-administrator). - -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorVaultCreator", - "description": "This role contains all of the necessary permissions to perform Inventory, Add and Remove operations on certificates on All KeyVaults within a Resource Group. It also contains sufficient permissions to create a new KeyVault within the resource group.", - "assignableScopes": [ - "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all subscriptions under management group - ], - "permissions": [ - { - "actions": [ - "Microsoft.KeyVault/vaults/*", - "Microsoft.Authorization/*/read", - "Microsoft.KeyVault/register/action", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/vaults/accessPolicies/*", - "Microsoft.Resources/deployments/*", - "Microsoft.KeyVault/locations/*/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Management/managementGroups/read", - "Microsoft.Resources/subscriptions/read", - "Microsoft.Authorization/roleAssignments/*", - "Microsoft.KeyVault/operations/read" - ], - "notActions": [], - "dataActions": [], - "notDataActions": [], - "conditionVersion": "2.0", - "condition": "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" - } - ] - } -} -``` - -
-
-

Discover Vaults Permissions

- -If you would like this integration to search across your subscriptions to discover instances of existing Azure -KeyVaults, this role definition contains the necessary permissions for this. -If you are working with a smaller number of KeyVaults and/or do not plan on utilizing a Discovery job to retrieve all -KeyVaults across your subscriptions, the permissions defined in this role are not necessary. - -- built-in - role: ["Key Vault Reader"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-reader) -- lowest level scope - a resource group -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorVaultDiscovery", - "description": "This role contains all of the necessary permissions to search for KeyVaults across a subscription", - "assignableScopes": [ - "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all resources under management group - ], - "permissions": [ - { - "actions": [ - "Microsoft.Authorization/*/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/locations/*/read", - "Microsoft.KeyVault/vaults/read", - "Microsoft.KeyVault/operations/read" - ], - "notActions": [], - "dataActions": [ - ], - "notDataActions": [], - } - ] - } -} -``` - -
-
-

Inventory, Add, and Remove Certificate Permissions

- -This set of permissions is the minimum required to support the basic operations of performing an Inventory and -Add/Removal of certificates. - -- built-in - role: ["Key Vault Certificates Officer"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) -- lowest level scope - an individual keyvault -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorManageCerts", - "description": "This role contains all of the necessary permissions to perform Inventory, Add and Remove operations on certificates on All KeyVaults within the scope.", - "assignableScopes": [ - "/providers/Microsoft.Management/managementGroups/{groupId1}", // allow scope for all subscriptions under management group - "/subscriptions/{subscriptionId}", // allow to scoped to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{vaultName}", // allow scope to a specific vault - "/subscriptions/{subscriptionId2}/resourceGroups/{resourceGroupName2}/providers/Microsoft.KeyVault/vaults/{vaultName2}", // .. and another - ], - "permissions": [ - { - "actions": [ - "Microsoft.Authorization/*/read", - "Microsoft.Resources/deployments/*", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/locations/*/read", - "Microsoft.KeyVault/vaults/*/read", - "Microsoft.KeyVault/operations/read", - ], - "notActions": [], - "dataActions": [ - "Microsoft.KeyVault/vaults/certificates/*", - "Microsoft.KeyVault/vaults/certificatecas/*", - "Microsoft.KeyVault/vaults/keys/*", - "Microsoft.KeyVault/vaults/secrets/readMetadata/action" - ], - "notDataActions": [] - } - ], - } -} -``` - -
- -
-

Combined permissions for all operations (Create, Discovery, Inventory, Add and Remove certificates)

- -This section defines a single custom role that contains the necessary permissions to perform all operations allowed by -this integration. The minimum scope allowable is an individual resource group. If this custom role is associated with -the authenticating identity, it will be able to discover existing KeyVaults, Create new ones, and perform inventory as -well as adding and removing certificates within the KeyVault. - -- minimally sufficient built-in roles (all are required): - - ["Key Vault Certificates Officer"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) - - ["Key Vault Contributor"](learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-contributor) - - ["Key Vault Access Administrator"](learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/) -- lowest level scope - an individual resource group -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorKeyVaultOperations", - "description": "This role contains all of the necessary permissions to perform Discovery, Create, Inventory, Add and Remove operations on certificates on All KeyVaults within The scope.", - "assignableScopes": [ - "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all subscriptions under management group - ], - "permissions": [ - { - "actions": [ - "Microsoft.KeyVault/vaults/*", - "Microsoft.Authorization/*/read", - "Microsoft.KeyVault/register/action", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/vaults/accessPolicies/*", - "Microsoft.Resources/deployments/*", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Management/managementGroups/read", - "Microsoft.Resources/subscriptions/read", - "Microsoft.Authorization/roleAssignments/*", - "Microsoft.KeyVault/operations/read" - "Microsoft.KeyVault/locations/*/read", - "Microsoft.KeyVault/vaults/*/read", - ], - "notActions": [], - "dataActions": [ - "Microsoft.KeyVault/vaults/certificates/*", - "Microsoft.KeyVault/vaults/certificatecas/*", - "Microsoft.KeyVault/vaults/keys/*", - "Microsoft.KeyVault/vaults/secrets/*" - ], - "notDataActions": [], - "conditionVersion": "2.0", - "condition": "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" - } - ] - } -} -``` - -> :warning: You still may decide to split the capabilities into seperate roles in order to apply each of them to the -> lowest level scope -> required. We have tried to provide you with an absolute minimum set of required permissions necessary to perform each -> operation. Refer to -> your organization's security policies and/or consult with your information security team in order to determine which -> role combinations would -> be most appropriate for your needs. +The access control type the KeyVault implements can be changed in the KeyVault configuration within the Azure Portal. +New KeyVaults created via Keyfactor by way of this integration will use the default that is configured in your Azure environment (as of February 2026, if not specified, RBAC). -
+> :exclamation: Additional guidance regarding the minimum permissions needed for each job and sample RBAC policy definitions with descriptions can be found [here](rbac.md). #### Endpoint Access / Firewall @@ -460,7 +127,7 @@ The Azure KeyVault orchestrator plugin supports several authentication options: Steps for setting up each option are detailed below.
-

Authentication via Service Principal

+ Authentication via Service Principal For the Orchestrator to be able to interact with the instance of Azure Keyvault, we will need to create an entity in Azure that will encapsulate the permissions we would like to grant it. In Azure, these intermediate entities are @@ -503,7 +170,7 @@ We will store these values securely in Keyfactor in subsequent steps.
-

Authentication via User Assigned Managed Identity

+ Authentication via User Assigned Managed Identity Authentication has been somewhat simplified with the introduction of Azure Managed Identities. If the orchestrator is running on an Azure Virtual Machine, Managed identities allow an Azure administrator to @@ -529,13 +196,190 @@ Id field on the certificate store definition (the Client Secret can be left blan
-

Authentication via System Assigned Managed Identity

+Authentication via System Assigned Managed Identity In order to use a _System_ assigned managed identity, there is no need to enter the server credentials. If no server credentials are provided, the extension assumes authentication is via system assigned managed identity.
+### Running a Discovery Job + +Now that we have the extension registered on the Orchestrator, we can navigate back to the Keyfactor platform and finish +the setup. If there are existing Azure Key Vaults, complete the below steps to discover and add them. If there are no +existing key vaults to integrate and you will be creating a new one via the Keyfactor Platform, you can skip to the next +section. + +1) Navigate to Orchestrators > Management in the platform. + + ![Manage Orchestrators](/Images/orch-manage.png) + +1) Find the row corresponding to the orchestrator that we just installed the extension on. + +1) If the store type has been created and the integration installed on the orchestrator, you should see the _AKV_ + capability in the list. + + ![AKV Capability](/Images/akv-capability.png) + +1) Approve the orchestrator if necessary. + +##### Create the discovery job + +1) Navigate to "Locations > Certificate Stores" + + ![Locations Cert Stores](/Images/locations-certstores.png) + +1) Click the "Discover" tab, and then the "Schedule" button. + + ![Discovery Schedule](/Images/discover-schedule.png) + +1) You should see the form for creating the Discovery job. + + ![Discovery Form](/Images/discovery-form.png) + +##### Store the Server Credentials in Keyfactor + +> :warning: +> The steps for configuring discovery are different for each authentication type. + +- For System Assigned managed identity authentication this step can be skipped. No server credentials are necessary. The + store type should have been set up without "needs server" checked, so the form field should not be present. + +- For User assigned managed identity: + - `Client Machine` should be set to the GUID of the tenant ID of the instance of Azure Keyvault. + - `User` should be set to the Client ID of the managed identity. + - `Password` should be set to the value **"managed"**. + +- For Service principal authentication: + - `Client Machine` should be set to the GUID of the tenant ID of the instance of Azure Keyvault. **Note:** If using + a multi-tenant app registration, use the tenant ID of the Azure tenant where the key vault lives. + - `User` should be set to the service principal id + - `Password` should be set to the client secret. + +The first thing we'll need to do is store the server credentials that will be used by the extension. +The combination of fields required to interact with the Azure Keyvault are: + +- Tenant (or Directory) ID +- Application ID or user managed identity ID +- Client Secret (if using Service Principal Authentication) + +If not using system managed identity authentication, the integration expects the above values to be included in the +server credentials in the following way: + +- **Client Machine**: `` (GUID) + +- **User**: `` (if service principal authentication) `` (if user managed identity + authentication is used) + +- **Password**: `` (if service principal authentication), `managed` (if user managed identity + authentication is used) + +Follow these steps to store the values: + +1) Enter the _Tenant Id_ in the **Client Machine** field. + + ![Discovery Form](/Images/discovery-form-client-machine.png) + +1) Click "Change Credentials" to open up the Server Credentials form. + + ![Change Credentials](/Images/change-credentials-form.png) + +1) Click "UPDATE SERVER USERNAME" and Enter the appropriate values based on the authentication type. + + ![Set Username](/Images/server-creds-username.png) + +1) Enter again to confirm, and click save. + +1) Click "UPDATE SERVER PASSWORD" and update with the appropriate value (`` or `managed`) following the + same steps as above. + +1) Select a time to run the discovery job. + +1) Enter commma seperated list of tenant ID's in the "Directories to search" field.' + +> :warning: +> If nothing is entered here, the default Tenant ID included with the credentials will be used. For system managed +> identities, it is necessary to include the Tenant ID(s) in this field. + +1) Leave the remaining fields blank and click "SAVE". + +##### Approve the Certificate Store + +When the Discovery job runs successfully, it will list the existing Azure Keyvaults that are acessible by our service +principal. + +In this example, our job returned these Azure Keyvaults. + +![Discovery Results](/Images/discovery-result.png) + +The store path of each vault is the `::`: + +![Discovery Results](/Images/storepath.png) + +To add one of these results to Keyfactor as a certificate store: + +1) Double-click the row that corresponds to the Azure Keyvault in the discovery results (you can also select the row and + click "SAVE"). + +1) In the dialog window, enter values for any of the optional fields you have set up for your store type. + +1) Select a container to store the certificates for this cert store (optional) + +1) Select any value for SKU Type and Vault Region. These values are not used for existing KeyVaults. + +1) Click "SAVE". + +### Add an existing Azure Keyvault certificate store + +You can also add a certificate store that corresponds to an Azure Keyvault individually without the need to run the +discovery / approval workflow. +The steps to do this are: + +1) Navigate to "Locations > Certificate Stores" + +1) Click "ADD" + + ![Approve Cert Store](/Images/cert-store-add-button.png) + +1) Enter the values corresponding to the Azure Keyvault instance. + +- **Category**: Azure Keyvault +- **Container**: _optional_ +- **Client Machine**: If applicable; Tenant Id. + + - Note: These will only have to be entered once, even if adding multiple certificate stores. + - Follow the steps [here](#store-the-server-credentials-in-keyfactor) to enter them. + +- **Store Path**: This is the Subscription ID, Resource Group name, and Vault name in the following format: + `{subscription id}:{resource group name}:{new vault name}` + +- **SKU Type**: This field is only used when creating new vaults in Azure. If present, select any value, or leave blank. +- **Vault Region**: This field is also only used when creating new vaults. If present, select any value. + +If the vault already exists in azure the store path can be found by navigating to the existing Keyvault resource in +Azure and clicking "Properties" in the left menu. + +![Resource Id](/Images/resource-id.png) + +- Use these values to create the store path +- Save the certificate store +- If an inventory schedule was provided, a new inventory job should appear in _Orchestrators > Jobs_. + +### Create a new Azure Keyvault + +- Enter a value for the store path in the following format: `{subscription id}:{resource group name}:{new vault name}` +- Make sure that the "Create Certificate Store" box is checked. +- Optionally choose values for the **SKUtype**, **Vault Region**, **Azure Cloud** and **Private Endpoint** (as applicable). + - The **SKUType** and **Vault Region** fields are _only_ used when creating new KeyVaults. +- Save the certificate store +-Navigate to _Orchestrators > Jobs_; you should see the "Management" job that was generated in order to create the Keyvault. +- Once this job completes, a new Keyvault should have been created + +> :warning: The identity you are using for authentication will need to have sufficient Azure permissions to be able to +> create new Keyvaults. + +--- + ## AKV Certificate Store Type @@ -663,6 +507,7 @@ the Keyfactor Command Portal The ID of the primary Azure Tenant where the KeyVaults are hosted ![AKV Custom Field - TenantId](docsource/images/AKV-custom-field-TenantId-dialog.png) + ![AKV Custom Field - TenantId](docsource/images/AKV-custom-field-TenantId-validation-options-dialog.png) @@ -670,6 +515,7 @@ the Keyfactor Command Portal 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) + ![AKV Custom Field - SkuType](docsource/images/AKV-custom-field-SkuType-validation-options-dialog.png) @@ -677,6 +523,7 @@ the Keyfactor Command Portal 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) + ![AKV Custom Field - VaultRegion](docsource/images/AKV-custom-field-VaultRegion-validation-options-dialog.png) @@ -684,6 +531,7 @@ the Keyfactor Command Portal 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) + ![AKV Custom Field - AzureCloud](docsource/images/AKV-custom-field-AzureCloud-validation-options-dialog.png) @@ -691,6 +539,7 @@ the Keyfactor Command Portal 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) + ![AKV Custom Field - PrivateEndpoint](docsource/images/AKV-custom-field-PrivateEndpoint-validation-options-dialog.png) @@ -713,18 +562,21 @@ the Keyfactor Command Portal 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) + ![AKV Entry Parameter - CertificateTags](docsource/images/AKV-entry-parameters-store-type-dialog-CertificateTags-validation-options.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) + ![AKV Entry Parameter - PreserveExistingTags](docsource/images/AKV-entry-parameters-store-type-dialog-PreserveExistingTags-validation-options.png) ##### Non Exportable Private Key If true, this will mark the certificate as having a non-exportable private key when importing into Azure KeyVault ![AKV Entry Parameter - NonExportable](docsource/images/AKV-entry-parameters-store-type-dialog-NonExportable.png) + ![AKV Entry Parameter - NonExportable](docsource/images/AKV-entry-parameters-store-type-dialog-NonExportable-validation-options.png) @@ -864,178 +716,6 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov > The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). -## Discovering Certificate Stores with the Discovery Job -Now that we have the extension registered on the Orchestrator, we can navigate back to the Keyfactor platform and finish -the setup. If there are existing Azure Key Vaults, complete the below steps to discover and add them. If there are no -existing key vaults to integrate and you will be creating a new one via the Keyfactor Platform, you can skip to the next -section. - -1) Navigate to Orchestrators > Management in the platform. - - ![Manage Orchestrators](/Images/orch-manage.png) - -1) Find the row corresponding to the orchestrator that we just installed the extension on. - -1) If the store type has been created and the integration installed on the orchestrator, you should see the _AKV_ - capability in the list. - - ![AKV Capability](/Images/akv-capability.png) - -1) Approve the orchestrator if necessary. - -##### Create the discovery job - -1) Navigate to "Locations > Certificate Stores" - - ![Locations Cert Stores](/Images/locations-certstores.png) - -1) Click the "Discover" tab, and then the "Schedule" button. - - ![Discovery Schedule](/Images/discover-schedule.png) - -1) You should see the form for creating the Discovery job. - - ![Discovery Form](/Images/discovery-form.png) - -##### Store the Server Credentials in Keyfactor - -> :warning: -> The steps for configuring discovery are different for each authentication type. - -- For System Assigned managed identity authentication this step can be skipped. No server credentials are necessary. The - store type should have been set up without "needs server" checked, so the form field should not be present. - -- For User assigned managed identity: - - `Client Machine` should be set to the GUID of the tenant ID of the instance of Azure Keyvault. - - `User` should be set to the Client ID of the managed identity. - - `Password` should be set to the value **"managed"**. - -- For Service principal authentication: - - `Client Machine` should be set to the GUID of the tenant ID of the instance of Azure Keyvault. **Note:** If using - a multi-tenant app registration, use the tenant ID of the Azure tenant where the key vault lives. - - `User` should be set to the service principal id - - `Password` should be set to the client secret. - -The first thing we'll need to do is store the server credentials that will be used by the extension. -The combination of fields required to interact with the Azure Keyvault are: - -- Tenant (or Directory) ID -- Application ID or user managed identity ID -- Client Secret (if using Service Principal Authentication) - -If not using system managed identity authentication, the integration expects the above values to be included in the -server credentials in the following way: - -- **Client Machine**: `` (GUID) - -- **User**: `` (if service principal authentication) `` (if user managed identity - authentication is used) - -- **Password**: `` (if service principal authentication), `managed` (if user managed identity - authentication is used) - -Follow these steps to store the values: - -1) Enter the _Tenant Id_ in the **Client Machine** field. - - ![Discovery Form](/Images/discovery-form-client-machine.png) - -1) Click "Change Credentials" to open up the Server Credentials form. - - ![Change Credentials](/Images/change-credentials-form.png) - -1) Click "UPDATE SERVER USERNAME" and Enter the appropriate values based on the authentication type. - - ![Set Username](/Images/server-creds-username.png) - -1) Enter again to confirm, and click save. - -1) Click "UPDATE SERVER PASSWORD" and update with the appropriate value (`` or `managed`) following the - same steps as above. - -1) Select a time to run the discovery job. - -1) Enter commma seperated list of tenant ID's in the "Directories to search" field.' - -> :warning: -> If nothing is entered here, the default Tenant ID included with the credentials will be used. For system managed -> identities, it is necessary to include the Tenant ID(s) in this field. - -1) Leave the remaining fields blank and click "SAVE". - -##### Approve the Certificate Store - -When the Discovery job runs successfully, it will list the existing Azure Keyvaults that are acessible by our service -principal. - -In this example, our job returned these Azure Keyvaults. - -![Discovery Results](/Images/discovery-result.png) - -The store path of each vault is the `::`: - -![Discovery Results](/Images/storepath.png) - -To add one of these results to Keyfactor as a certificate store: - -1) Double-click the row that corresponds to the Azure Keyvault in the discovery results (you can also select the row and - click "SAVE"). - -1) In the dialog window, enter values for any of the optional fields you have set up for your store type. - -1) Select a container to store the certificates for this cert store (optional) - -1) Select any value for SKU Type and Vault Region. These values are not used for existing KeyVaults. - -1) Click "SAVE". - -#### Add a new or existing Azure Keyvault certificate store - -You can also add a certificate store that corresponds to an Azure Keyvault individually without the need to run the -discovery / approval workflow. -The steps to do this are: - -1) Navigate to "Locations > Certificate Stores" - -1) Click "ADD" - - ![Approve Cert Store](/Images/cert-store-add-button.png) - -1) Enter the values corresponding to the Azure Keyvault instance. - -- **Category**: Azure Keyvault -- **Container**: _optional_ -- **Client Machine**: If applicable; Tenant Id. - - - Note: These will only have to be entered once, even if adding multiple certificate stores. - - Follow the steps [here](#store-the-server-credentials-in-keyfactor) to enter them. - -- **Store Path**: This is the Subscription ID, Resource Group name, and Vault name in the following format: - `{subscription id}:{resource group name}:{new vault name}` - -- **SKU Type**: This field is only used when creating new vaults in Azure. If present, select any value, or leave blank. -- **Vault Region**: This field is also only used when creating new vaults. If present, select any value. - -If the vault already exists in azure the store path can be found by navigating to the existing Keyvault resource in -Azure and clicking "Properties" in the left menu. - -![Resource Id](/Images/resource-id.png) - -- Use these values to create the store path - -If the Keyvault does not exist in Azure, and you would like to create it: - -- Enter a value for the store path in the following format: `{subscription id}:{resource group name}:{new vault name}` - -- For a non-existing Keyvault that you would like to create in Azure, make sure you have the "Create Certificate Store" - box checked. - -> :warning: The identity you are using for authentication will need to have sufficient Azure permissions to be able to -> create new Keyvaults. - ---- - - diff --git a/docsource/content.md b/docsource/content.md index 0fcc7e2..d75faed 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -21,83 +21,15 @@ operations. The high level steps required to configure the Azure Keyvault Orchestrator extension are: -1) [Migrating from the Windows Orchestrator for Azure KeyVault](#migrating-from-the-windows-orchestrator-for-azure-keyvault) +1) [Configure client access; permissions and authentication](#configure-the-azure-keyvault-for-client-access) -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) 1) [Create the Certificate Store](#add-a-new-or-existing-azure-keyvault-certificate-store) -_Note that the certificate store type used by this Universal Orchestrator support for Azure Keyvault is not compatible -with the certificate store type used by with Windows Orchestrator version for Azure Keyvault. -If your Keyfactor instance has used the Windows Orchestrator for Azure Keyvault, a specific migration process is -required. -See [Migrating from the Windows Orchestrator for Azure KeyVault](#migrating-from-the-windows-orchestrator-for-azure-keyvault) -section below._ - -
- -

Migrating from the Windows Orchestrator for Azure KeyVault

-If you were previously using the Azure Keyvault extension for the **Windows** Orchestrator, it is necessary to remove the Store Type definition as well as any Certificate stores that use the previous store type. -This is because the store type parameters have changed in order to facilitate the Discovery and Create functionality. - -If you have an existing AKV store type that was created for use with the Windows Orchestrator, you will need to follow -the steps in one of the below sections in order to transfer the capability to the Universal Orchestrator. - -> :warning: -> Before removing the certificate stores, view their configuration details and copy the values. -> Copying the values in the store parameters will save time when re-creating the stores. - -Follow the below steps to remove the AKV capability from **each** active Windows Orchestrator that supports it: - -##### If the Windows Orchestrator should still manage other cert store types - -_If the Windows Orchestrator will still be used to manage some store types, we will remove only the Azure Keyvault -functionality._ - -1) On the Windows Orchestrator host machine, run the Keyfactor Agent Configuration Wizard -1) Proceed through the steps to "Select Features" -1) Expand "Cert Stores" and un-check "Azure Keyvault" -1) Click "Apply Configuration" - -1) Open the Keyfactor Platform and navigate to **Orchestrators > Management** -1) Confirm that "AKV" no longer appears under "Capabilities" -1) Navigate to **Orchestrators > Management**, select the orchestrator and click "DISAPPROVE" to disapprove it and - cancel pending jobs. -1) Navigate to **Locations > Certificate Stores** -1) Select any stores with the Category "Azure Keyvault" and click "DELETE" to remove them from Keyfactor. -1) Navigate to the Administrative menu (gear icon) and then **> Certificate Store Types** -1) Select Azure Keyvault, click "DELETE" and confirm. -1) Navigate to **Orchestrators > Management**, select the orchestrator and click "APPROVE" to re-approve it for use. - -1) Repeat these steps for any other Windows Orchestrators that support the AKV store type. - -##### If the Windows Orchestrator can be retired completely - -_If the Windows Orchestrator is being completely replaced with the Universal Orchestrator, we can remove all associated -stores and jobs._ - -1) Navigate to **Orchestrators > Management** and select the Windows Orchestrator from the list. -1) With the orchestrator selected, click the "RESET" button at the top of the list -1) Make sure the orchestrator is still selected, and click "DISAPPROVE". -1) Click "OK" to confirm that you will remove all jobs and certificate stores associated to this orchestrator. -1) Navigate to the Administrative (gear icon in the top right) and then **Certificate Store Types** -1) Select "Azure Keyvault", click "DELETE" and confirm. -1) Repeat these steps for any other Windows Orchestrators that support the AKV store type (if they can also be retired). - -Note: Any Azure Keyvault certificate stores removed can be re-added once the Universal Orchestrator is configured with -the AKV capability. - -#### Migrating from version 1.x or version 2.x of the Azure Keyvault Orchestrator Extension - -It is not necessary to re-create all of the certificate stores when migrating from a previous version of this extension, -though it is important to note that Azure KeyVaults found during a Discovery job -will return with latest store path format: `{subscription id}:{resource group name}:{new vault name}`. - -
+> :warning: If you are still using the (deprecated) Windows Orchestrator, you can find instructions for migrating by searching previous versions of this README. --- @@ -105,8 +37,8 @@ will return with latest store path format: `{subscription id}:{resource group na In order for this orchestrator extension to be able to interact with your instances of Azure Keyvault, it will need to authenticate with a identity that has sufficient permissions to perform the jobs. Microsoft Azure implements both Role -Based Access Control (RBAC) and the classic Access Policy method. RBAC is the preferred method, as it allows the -assignment of granular level, inheretable access control on both the contents of the KeyVaults, as well as higher-level +Based Access Control (RBAC) and the classic Access Policy method. RBAC is the preferred method, and currently the default used by Azure. +It allows the assignment of granular level, inheretable access control on both the contents of the KeyVaults, as well as higher-level management operations. For more information and a comparison of the two access control strategies, refer to [this article](learn.microsoft.com/en-us/azure/key-vault/general/rbac-access-policy). @@ -114,277 +46,14 @@ to [this article](learn.microsoft.com/en-us/azure/key-vault/general/rbac-access- Azure KeyVaults originally utilized access policies for permissions and since then, Microsoft has begun recommending Role Based Access Control (RBAC) as the preferred method of authorization. -As of this version, new KeyVaults created via this integration are created with Access Policy authorization. This will -change to RBAC in the next release. -The access control type the KeyVault implements can be changed in the KeyVault configuration within the Azure Portal. -New KeyVaults created via Keyfactor by way of this integration will be accessible for subsequent actions regardless of -the access control type. - -##### Configure Role Based Access Control (RBAC) - -In order to illustrate the minimum permissions that the authenticating entity (service principal or managed identity) -requires, -we have created 3 seperate custom role definitions that you can use as a reference when creating an RBAC role definition -in your Azure environment. - -The reason for 3 definitions is that certain orchestrator jobs, such as Create (new KeyVault) or Discovery require more -elevated permissions at a different scope than the basic certificate operations (Inventory, Add, Remove) performed -within a specific KeyVault. - -If you know that you will utilize all of the capabilities of this integration; the last custom role definition contains -all necessary permissions for performing all of the Jobs (Discovery, Create KeyVault, Inventory/Add/Remove -certificates). - -##### Built-in vs. custom roles - -> :warning: The custom role definitions below are designed to contain the absolute minimum permissions required. They -> are not intended to be used verbatim without consulting your organization's security team and/or Azure Administrator. -> Keyfactor does not provide consulting on internal security practices. - -It is possible to use the built-in roles provided by Microsoft for these operations. The built-in roles may contain more -permissions than necessary. -Whether to create custom role definitions or use an existing or pre-built role will depend on your organization's -securuity requirements. -For each job type performed by this orchestrator, we've included the minimally sufficient built-in role name(s) along -with our custom role definitions that limit permissions to the specific actions and scopes necessary. - -
-

Create Vault permissions

- -In order to allow for the ability to create new Azure KeyVaults from within command, here is a role that defines the -necessary permissions to do so. If you will never be creating new Azure KeyVaults from within Command, then it is -unnecessary to provide the authenticating entity with these permissions. - -> :warning: When creating a new KeyVault, we grant the creating entity the built-in "Key Vault Certificates Officer" -> role in order to be able to perform subsequent actions on the contents of the -> KeyVault. [click here](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) -> to see the list of permissions included in the Key Vault Certificates Officer built-in role. - -- built-in roles (both are required): - - ["Key Vault Contributor"](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-contributor) - - ["Key Vault Access Administrator"](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-data-access-administrator) - -- lowest level scope required - a resource group that will contain the new KeyVault. - -- condition: - -```js -"((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" -``` - -the above condition limits the ability to assign roles to a single role only (Key Vault Certificates Officer). This is -more restrictive than the condition on the built-in role -of [Key Vault Access Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-data-access-administrator). - -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorVaultCreator", - "description": "This role contains all of the necessary permissions to perform Inventory, Add and Remove operations on certificates on All KeyVaults within a Resource Group. It also contains sufficient permissions to create a new KeyVault within the resource group.", - "assignableScopes": [ - "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all subscriptions under management group - ], - "permissions": [ - { - "actions": [ - "Microsoft.KeyVault/vaults/*", - "Microsoft.Authorization/*/read", - "Microsoft.KeyVault/register/action", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/vaults/accessPolicies/*", - "Microsoft.Resources/deployments/*", - "Microsoft.KeyVault/locations/*/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Management/managementGroups/read", - "Microsoft.Resources/subscriptions/read", - "Microsoft.Authorization/roleAssignments/*", - "Microsoft.KeyVault/operations/read" - ], - "notActions": [], - "dataActions": [], - "notDataActions": [], - "conditionVersion": "2.0", - "condition": "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" - } - ] - } -} -``` +New KeyVaults created via this integration are created with the default authorization method that is configured in the Azure environment. -
-
-

Discover Vaults Permissions

- -If you would like this integration to search across your subscriptions to discover instances of existing Azure -KeyVaults, this role definition contains the necessary permissions for this. -If you are working with a smaller number of KeyVaults and/or do not plan on utilizing a Discovery job to retrieve all -KeyVaults across your subscriptions, the permissions defined in this role are not necessary. - -- built-in - role: ["Key Vault Reader"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-reader) -- lowest level scope - a resource group -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorVaultDiscovery", - "description": "This role contains all of the necessary permissions to search for KeyVaults across a subscription", - "assignableScopes": [ - "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all resources under management group - ], - "permissions": [ - { - "actions": [ - "Microsoft.Authorization/*/read", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/locations/*/read", - "Microsoft.KeyVault/vaults/read", - "Microsoft.KeyVault/operations/read" - ], - "notActions": [], - "dataActions": [ - ], - "notDataActions": [], - } - ] - } -} -``` - -
-
-

Inventory, Add, and Remove Certificate Permissions

- -This set of permissions is the minimum required to support the basic operations of performing an Inventory and -Add/Removal of certificates. - -- built-in - role: ["Key Vault Certificates Officer"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) -- lowest level scope - an individual keyvault -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorManageCerts", - "description": "This role contains all of the necessary permissions to perform Inventory, Add and Remove operations on certificates on All KeyVaults within the scope.", - "assignableScopes": [ - "/providers/Microsoft.Management/managementGroups/{groupId1}", // allow scope for all subscriptions under management group - "/subscriptions/{subscriptionId}", // allow to scoped to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{vaultName}", // allow scope to a specific vault - "/subscriptions/{subscriptionId2}/resourceGroups/{resourceGroupName2}/providers/Microsoft.KeyVault/vaults/{vaultName2}", // .. and another - ], - "permissions": [ - { - "actions": [ - "Microsoft.Authorization/*/read", - "Microsoft.Resources/deployments/*", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/locations/*/read", - "Microsoft.KeyVault/vaults/*/read", - "Microsoft.KeyVault/operations/read", - ], - "notActions": [], - "dataActions": [ - "Microsoft.KeyVault/vaults/certificates/*", - "Microsoft.KeyVault/vaults/certificatecas/*", - "Microsoft.KeyVault/vaults/keys/*", - "Microsoft.KeyVault/vaults/secrets/readMetadata/action" - ], - "notDataActions": [] - } - ], - } -} -``` +The access control type the KeyVault implements can be changed in the KeyVault configuration within the Azure Portal. +New KeyVaults created via Keyfactor by way of this integration will use the default that is configured in your Azure environment (as of February 2026, if not specified, RBAC). -
+> :exclamation: Additional guidance regarding the minimum permissions needed for each job and sample RBAC policy definitions with descriptions can be found [here](rbac.md). -
-

Combined permissions for all operations (Create, Discovery, Inventory, Add and Remove certificates)

- -This section defines a single custom role that contains the necessary permissions to perform all operations allowed by -this integration. The minimum scope allowable is an individual resource group. If this custom role is associated with -the authenticating identity, it will be able to discover existing KeyVaults, Create new ones, and perform inventory as -well as adding and removing certificates within the KeyVault. - -- minimally sufficient built-in roles (all are required): - - ["Key Vault Certificates Officer"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) - - ["Key Vault Contributor"](learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-contributor) - - ["Key Vault Access Administrator"](learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/) -- lowest level scope - an individual resource group -- custom role definition: - -```js -{ - "properties": { - "roleName": "KeyfactorKeyVaultOperations", - "description": "This role contains all of the necessary permissions to perform Discovery, Create, Inventory, Add and Remove operations on certificates on All KeyVaults within The scope.", - "assignableScopes": [ - "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription - "/subscriptions/{subscriptionId2}", // and another.. etc. - "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group - "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. - "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all subscriptions under management group - ], - "permissions": [ - { - "actions": [ - "Microsoft.KeyVault/vaults/*", - "Microsoft.Authorization/*/read", - "Microsoft.KeyVault/register/action", - "Microsoft.KeyVault/checkNameAvailability/read", - "Microsoft.KeyVault/vaults/accessPolicies/*", - "Microsoft.Resources/deployments/*", - "Microsoft.Resources/subscriptions/resourceGroups/read", - "Microsoft.Management/managementGroups/read", - "Microsoft.Resources/subscriptions/read", - "Microsoft.Authorization/roleAssignments/*", - "Microsoft.KeyVault/operations/read" - "Microsoft.KeyVault/locations/*/read", - "Microsoft.KeyVault/vaults/*/read", - ], - "notActions": [], - "dataActions": [ - "Microsoft.KeyVault/vaults/certificates/*", - "Microsoft.KeyVault/vaults/certificatecas/*", - "Microsoft.KeyVault/vaults/keys/*", - "Microsoft.KeyVault/vaults/secrets/*" - ], - "notDataActions": [], - "conditionVersion": "2.0", - "condition": "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" - } - ] - } -} -``` - -> :warning: You still may decide to split the capabilities into seperate roles in order to apply each of them to the -> lowest level scope -> required. We have tried to provide you with an absolute minimum set of required permissions necessary to perform each -> operation. Refer to -> your organization's security policies and/or consult with your information security team in order to determine which -> role combinations would -> be most appropriate for your needs. -
#### Endpoint Access / Firewall @@ -415,7 +84,7 @@ The Azure KeyVault orchestrator plugin supports several authentication options: Steps for setting up each option are detailed below.
-

Authentication via Service Principal

+ Authentication via Service Principal For the Orchestrator to be able to interact with the instance of Azure Keyvault, we will need to create an entity in Azure that will encapsulate the permissions we would like to grant it. In Azure, these intermediate entities are @@ -458,7 +127,7 @@ We will store these values securely in Keyfactor in subsequent steps.
-

Authentication via User Assigned Managed Identity

+ Authentication via User Assigned Managed Identity Authentication has been somewhat simplified with the introduction of Azure Managed Identities. If the orchestrator is running on an Azure Virtual Machine, Managed identities allow an Azure administrator to @@ -484,7 +153,7 @@ Id field on the certificate store definition (the Client Secret can be left blan
-

Authentication via System Assigned Managed Identity

+Authentication via System Assigned Managed Identity In order to use a _System_ assigned managed identity, there is no need to enter the server credentials. If no server credentials are provided, the extension assumes authentication is via system assigned managed identity. @@ -492,7 +161,7 @@ credentials are provided, the extension assumes authentication is via system ass
-## Discovery +### Running a Discovery Job Now that we have the extension registered on the Orchestrator, we can navigate back to the Keyfactor platform and finish the setup. If there are existing Azure Key Vaults, complete the below steps to discover and add them. If there are no @@ -618,7 +287,7 @@ To add one of these results to Keyfactor as a certificate store: 1) Click "SAVE". -#### Add a new or existing Azure Keyvault certificate store +### Add an existing Azure Keyvault certificate store You can also add a certificate store that corresponds to an Azure Keyvault individually without the need to run the discovery / approval workflow. @@ -651,13 +320,18 @@ Azure and clicking "Properties" in the left menu. ![Resource Id](/Images/resource-id.png) - Use these values to create the store path +- Save the certificate store +- If an inventory schedule was provided, a new inventory job should appear in _Orchestrators > Jobs_. -If the Keyvault does not exist in Azure, and you would like to create it: +### Create a new Azure Keyvault - Enter a value for the store path in the following format: `{subscription id}:{resource group name}:{new vault name}` - -- For a non-existing Keyvault that you would like to create in Azure, make sure you have the "Create Certificate Store" - box checked. +- Make sure that the "Create Certificate Store" box is checked. +- Optionally choose values for the **SKUtype**, **Vault Region**, **Azure Cloud** and **Private Endpoint** (as applicable). + - The **SKUType** and **Vault Region** fields are _only_ used when creating new KeyVaults. +- Save the certificate store +-Navigate to _Orchestrators > Jobs_; you should see the "Management" job that was generated in order to create the Keyvault. +- Once this job completes, a new Keyvault should have been created > :warning: The identity you are using for authentication will need to have sufficient Azure permissions to be able to > create new Keyvaults. diff --git a/rbac.md b/rbac.md new file mode 100644 index 0000000..9b58cd4 --- /dev/null +++ b/rbac.md @@ -0,0 +1,261 @@ + +### Configure Role Based Access Control (RBAC) + +In order to illustrate the minimum permissions that the authenticating entity (service principal or managed identity) +requires we have created 3 seperate custom role definitions that you can use as a reference when creating an RBAC role definition +in your Azure environment. + +The reason for 3 definitions is that certain orchestrator jobs, such as Create (new KeyVault) or Discovery require more +elevated permissions at a different scope than the basic certificate operations (Inventory, Add, Remove) performed +within a specific KeyVault. + +If you know that you will utilize all of the capabilities of this integration; the last custom role definition contains +all necessary permissions for performing all of the Jobs (Discovery, Create KeyVault, Inventory/Add/Remove +certificates). + +#### Built-in vs. custom roles + +> :warning: The custom role definitions below are designed to contain the absolute minimum permissions required. They +> are not intended to be used verbatim without consulting your organization's security team and/or Azure Administrator. +> Keyfactor does not provide consulting on internal security practices. + +It is possible to use the built-in roles provided by Microsoft for these operations. The built-in roles may contain more +permissions than necessary. +Whether to create custom role definitions or use an existing or pre-built role will depend on your organization's +security requirements. +For each job type performed by this orchestrator, we've included the minimally sufficient built-in role name(s) along +with our custom role definitions that limit permissions to the specific actions and scopes necessary. + +
+ Create Vault permissions + +In order to allow for the ability to create new Azure KeyVaults from within command, here is a role that defines the +necessary permissions to do so. If you will never be creating new Azure KeyVaults from within Command, then it is +unnecessary to provide the authenticating entity with these permissions. + +- built-in roles (both are required): + - ["Key Vault Contributor"](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-contributor) + - ["Key Vault Access Administrator"](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-data-access-administrator) + +- lowest level scope required - a resource group that will contain the new KeyVault. + +- condition: + +```js +"((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" +``` + +the above condition limits the ability to assign roles to a single role only (Key Vault Certificates Officer). This is +more restrictive than the condition on the built-in role +of [Key Vault Access Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-data-access-administrator). + +- custom role definition: + +```js +{ + "properties": { + "roleName": "KeyfactorVaultCreator", + "description": "This role contains all of the necessary permissions to perform Inventory, Add and Remove operations on certificates on All KeyVaults within a Resource Group. It also contains sufficient permissions to create a new KeyVault within the resource group.", + "assignableScopes": [ + "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription + "/subscriptions/{subscriptionId2}", // and another.. etc. + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group + "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. + "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all subscriptions under management group + ], + "permissions": [ + { + "actions": [ + "Microsoft.KeyVault/vaults/*", + "Microsoft.Authorization/*/read", + "Microsoft.KeyVault/register/action", + "Microsoft.KeyVault/checkNameAvailability/read", + "Microsoft.KeyVault/vaults/accessPolicies/*", + "Microsoft.Resources/deployments/*", + "Microsoft.KeyVault/locations/*/read", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Management/managementGroups/read", + "Microsoft.Resources/subscriptions/read", + "Microsoft.Authorization/roleAssignments/*", + "Microsoft.KeyVault/operations/read" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [], + "conditionVersion": "2.0", + "condition": "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" + } + ] + } +} +``` + +
+ +
+ Discover Vaults Permissions + +If you would like this integration to search across your subscriptions to discover instances of existing Azure +KeyVaults, this role definition contains the necessary permissions for this. +If you are working with a smaller number of KeyVaults and/or do not plan on utilizing a Discovery job to retrieve all +KeyVaults across your subscriptions, the permissions defined in this role are not necessary. + +- built-in + role: ["Key Vault Reader"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-reader) +- lowest level scope - a resource group +- custom role definition: + +```js +{ + "properties": { + "roleName": "KeyfactorVaultDiscovery", + "description": "This role contains all of the necessary permissions to search for KeyVaults across a subscription", + "assignableScopes": [ + "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription + "/subscriptions/{subscriptionId2}", // and another.. etc. + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group + "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. + "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all resources under management group + ], + "permissions": [ + { + "actions": [ + "Microsoft.Authorization/*/read", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.KeyVault/checkNameAvailability/read", + "Microsoft.KeyVault/locations/*/read", + "Microsoft.KeyVault/vaults/read", + "Microsoft.KeyVault/operations/read" + ], + "notActions": [], + "dataActions": [ + ], + "notDataActions": [], + } + ] + } +} +``` + +
+ +
+ Inventory, Add, and Remove Certificate Permissions + +This set of permissions is the minimum required to support the basic operations of performing an Inventory and +Add/Removal of certificates. + +- built-in + role: ["Key Vault Certificates Officer"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) +- lowest level scope - an individual keyvault +- custom role definition: + +```js +{ + "properties": { + "roleName": "KeyfactorManageCerts", + "description": "This role contains all of the necessary permissions to perform Inventory, Add and Remove operations on certificates on All KeyVaults within the scope.", + "assignableScopes": [ + "/providers/Microsoft.Management/managementGroups/{groupId1}", // allow scope for all subscriptions under management group + "/subscriptions/{subscriptionId}", // allow to scoped to a specific subscription + "/subscriptions/{subscriptionId2}", // and another.. etc. + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group + "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{vaultName}", // allow scope to a specific vault + "/subscriptions/{subscriptionId2}/resourceGroups/{resourceGroupName2}/providers/Microsoft.KeyVault/vaults/{vaultName2}", // .. and another + ], + "permissions": [ + { + "actions": [ + "Microsoft.Authorization/*/read", + "Microsoft.Resources/deployments/*", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.KeyVault/checkNameAvailability/read", + "Microsoft.KeyVault/locations/*/read", + "Microsoft.KeyVault/vaults/*/read", + "Microsoft.KeyVault/operations/read", + ], + "notActions": [], + "dataActions": [ + "Microsoft.KeyVault/vaults/certificates/*", + "Microsoft.KeyVault/vaults/certificatecas/*", + "Microsoft.KeyVault/vaults/keys/*", + "Microsoft.KeyVault/vaults/secrets/readMetadata/action" + ], + "notDataActions": [] + } + ], + } +} +``` + +
+ +
+ Combined permissions for all operations (Create, Discovery, Inventory, Add and Remove certificates) + +This section defines a single custom role that contains the necessary permissions to perform all operations allowed by +this integration. The minimum scope allowable is an individual resource group. If this custom role is associated with +the authenticating identity, it will be able to discover existing KeyVaults, Create new ones, and perform inventory as +well as adding and removing certificates within the KeyVault. + +- minimally sufficient built-in roles (all are required): + - ["Key Vault Certificates Officer"](github.com/MicrosoftDocs/azure-docs/blob/main/articles/role-based-access-control/built-in-roles/security.md#key-vault-certificates-officer) + - ["Key Vault Contributor"](learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/security#key-vault-contributor) + - ["Key Vault Access Administrator"](learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/) +- lowest level scope - an individual resource group +- custom role definition: + +```js +{ + "properties": { + "roleName": "KeyfactorKeyVaultOperations", + "description": "This role contains all of the necessary permissions to perform Discovery, Create, Inventory, Add and Remove operations on certificates on All KeyVaults within The scope.", + "assignableScopes": [ + "/subscriptions/{subscriptionId1}", // allow to be applied to a specific subscription + "/subscriptions/{subscriptionId2}", // and another.. etc. + "/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}", // allow to be scoped to a specific resource group + "/subscriptions/{subscriptionId2}/resourcegroups/{resourceGroupName2}", // and another.. + "/providers/Microsoft.Management/managementGroups/{groupId1}" // allow to be applied for all subscriptions under management group + ], + "permissions": [ + { + "actions": [ + "Microsoft.KeyVault/vaults/*", + "Microsoft.Authorization/*/read", + "Microsoft.KeyVault/register/action", + "Microsoft.KeyVault/checkNameAvailability/read", + "Microsoft.KeyVault/vaults/accessPolicies/*", + "Microsoft.Resources/deployments/*", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Management/managementGroups/read", + "Microsoft.Resources/subscriptions/read", + "Microsoft.Authorization/roleAssignments/*", + "Microsoft.KeyVault/operations/read" + "Microsoft.KeyVault/locations/*/read", + "Microsoft.KeyVault/vaults/*/read", + ], + "notActions": [], + "dataActions": [ + "Microsoft.KeyVault/vaults/certificates/*", + "Microsoft.KeyVault/vaults/certificatecas/*", + "Microsoft.KeyVault/vaults/keys/*", + "Microsoft.KeyVault/vaults/secrets/*" + ], + "notDataActions": [], + "conditionVersion": "2.0", + "condition": "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985})) AND ((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals{a4417e6f-fecd-4de8-b567-7b0420556985}))" + } + ] + } +} +``` +
+ +> :warning: You still may decide to split the capabilities into seperate roles in order to apply each of them to the +> lowest level scope +> required. We have tried to provide you with an absolute minimum set of required permissions necessary to perform each +> operation. Refer to +> your organization's security policies and/or consult with your information security team in order to determine which +> role combinations would +> be most appropriate for your needs. \ No newline at end of file From 5589d336297abdda47f237d98944f36f4e6c6d53 Mon Sep 17 00:00:00 2001 From: Joe VanWanzeele <76071503+joevanwanzeeleKF@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:54:53 -0400 Subject: [PATCH 4/4] Returning entry parameters, documentation updates (#77) * Merge 3.2.0 to main (#74) * chore: create 3.2 branch * feat: release 3.2, Added entry parameter to indicate the private key should not be exportable from KeyVault Co-authored-by: Keyfactor --------- Co-authored-by: Joe VanWanzeele <76071503+joevanwanzeeleKF@users.noreply.github.com> Co-authored-by: Keyfactor * cleaned up docs, split RBAC permissions into seperate file for brevity * Update generated docs * Updated changelog, nuget package references * Explicit update of Newtonsoft.Json.Bson from 1.0.2 (used by Microsoft.AspNet.WebApi.Client) to 1.0.3 to address vulnerability * now returning the serialized certificate tags, as well as the exportable flag. Updated README image * Update generated docs --------- Co-authored-by: Morgan Gangwere <470584+indrora@users.noreply.github.com> Co-authored-by: Keyfactor --- AzureKeyVault/AzureClient.cs | 22 ++- AzureKeyVault/Jobs/Management.cs | 2 +- CHANGELOG.md | 5 +- ...AKV-entry-parameters-store-type-dialog.png | Bin 24374 -> 20816 bytes .../bash/curl_create_store_types.sh | 185 ++++++++++++++++++ .../bash/kfutil_create_store_types.sh | 28 +++ .../powershell/kfutil_create_store_types.ps1 | 29 +++ .../restmethod_create_store_types.ps1 | 179 +++++++++++++++++ 8 files changed, 445 insertions(+), 5 deletions(-) create mode 100755 scripts/store_types/bash/curl_create_store_types.sh create mode 100755 scripts/store_types/bash/kfutil_create_store_types.sh create mode 100644 scripts/store_types/powershell/kfutil_create_store_types.ps1 create mode 100644 scripts/store_types/powershell/restmethod_create_store_types.ps1 diff --git a/AzureKeyVault/AzureClient.cs b/AzureKeyVault/AzureClient.cs index e470341..1647001 100644 --- a/AzureKeyVault/AzureClient.cs +++ b/AzureKeyVault/AzureClient.cs @@ -22,6 +22,8 @@ using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.VisualBasic; +using Newtonsoft.Json; namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault { @@ -38,8 +40,6 @@ private Uri AzureCloudEndpoint case "china": logger.LogTrace(AzureAuthorityHosts.AzureChina.ToString()); return AzureAuthorityHosts.AzureChina; - //case "germany": - // return AzureAuthorityHosts.AzureGermany; // germany is no longer a valid azure authority host as of 2021 case "government": logger.LogTrace(AzureAuthorityHosts.AzureGovernment.ToString()); return AzureAuthorityHosts.AzureGovernment; @@ -304,7 +304,23 @@ public virtual async Task> GetCertificatesAsyn { var cert = await CertClient.GetCertificateAsync(certificate.Name); logger.LogTrace($"got certificate details"); + logger.LogTrace($"cert properties: {JsonConvert.SerializeObject(cert.Value?.Properties)}"); + var itemEntryParams = new Dictionary(); + if (cert.Value?.Properties?.Tags != null && cert.Value.Properties.Tags.Count > 0) { // set tags entry parameter to value + itemEntryParams.Add(EntryParameters.TAGS, JsonConvert.SerializeObject(cert.Value.Properties.Tags)); + } + + if (cert.Value.Policy != null) // set nonexportable entry parameter to value + { + var exportable = cert.Value.Policy?.Exportable; + itemEntryParams.Add(EntryParameters.NON_EXPORTABLE, !exportable); + } + + itemEntryParams.Add(EntryParameters.PRESERVE_TAGS, null); // we can never know this; it's only evaluated on enrollment; set to null + + logger.LogTrace($"evaluated entry parameters to be returned: {JsonConvert.SerializeObject(itemEntryParams)}"); + inventoryItems.Add(new CurrentInventoryItem() { Alias = cert.Value.Name, @@ -312,7 +328,7 @@ public virtual async Task> GetCertificatesAsyn ItemStatus = OrchestratorInventoryItemStatus.Unknown, UseChainLevel = true, Certificates = new List() { Convert.ToBase64String(cert.Value.Cer) }, - Parameters = cert.Value.Properties.Tags as Dictionary + Parameters = itemEntryParams }); } catch (Exception ex) diff --git a/AzureKeyVault/Jobs/Management.cs b/AzureKeyVault/Jobs/Management.cs index 3884503..ed0d055 100644 --- a/AzureKeyVault/Jobs/Management.cs +++ b/AzureKeyVault/Jobs/Management.cs @@ -50,7 +50,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) logger.LogTrace("parsing entry parameters.. "); tagsJSON = config.JobProperties[EntryParameters.TAGS] as string ?? string.Empty; - preserveTags = config.JobProperties[EntryParameters.PRESERVE_TAGS] as bool? ?? false; + preserveTags = config.JobProperties[EntryParameters.PRESERVE_TAGS] as bool? ?? true; nonExportable = config.JobProperties[EntryParameters.NON_EXPORTABLE] as bool? ?? false; switch (config.OperationType) diff --git a/CHANGELOG.md b/CHANGELOG.md index 520816e..58cab0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ - +- 3.2.2 + - Updated screenshots in README + - Returning entry parameters along with inventory + - 3.2.1 - Documentation updates and improvements - Updated NuGet packages diff --git a/docsource/images/AKV-entry-parameters-store-type-dialog.png b/docsource/images/AKV-entry-parameters-store-type-dialog.png index 25d2c068d5ea9c1e7b2111f22166c449fa4dc867..4051ad693554f9cb5b633d1b4cd10db41dacb4ff 100644 GIT binary patch literal 20816 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 ADF6Tf literal 24374 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 /dev/null; then + echo "kfutil could not be found. Please install kfutil" + echo "See https://github.com/Keyfactor/kfutil#quickstart" + exit 1 +fi + +if [ -z "$KEYFACTOR_HOSTNAME" ]; then + echo "KEYFACTOR_HOSTNAME not set — launching kfutil login" + kfutil login +fi + +kfutil store-types create --name "AKV" + +echo "Done. All store types created." diff --git a/scripts/store_types/powershell/kfutil_create_store_types.ps1 b/scripts/store_types/powershell/kfutil_create_store_types.ps1 new file mode 100644 index 0000000..51a3153 --- /dev/null +++ b/scripts/store_types/powershell/kfutil_create_store_types.ps1 @@ -0,0 +1,29 @@ +# Creates all 1 store types using kfutil. +# kfutil reads definitions from the Keyfactor integration catalog. +# +# Auth environment variables (first matching method is used): +# OAuth access token: KEYFACTOR_AUTH_ACCESS_TOKEN +# OAuth client creds: KEYFACTOR_AUTH_CLIENT_ID + KEYFACTOR_AUTH_CLIENT_SECRET +# + KEYFACTOR_AUTH_TOKEN_URL +# Basic auth (AD): KEYFACTOR_HOSTNAME + KEYFACTOR_USERNAME + KEYFACTOR_PASSWORD +# + KEYFACTOR_DOMAIN +# +# Auto-generated by doctool generate-store-type-scripts — do not edit by hand. + +# Uncomment if kfutil is not in your PATH +# Set-Alias -Name kfutil -Value 'C:\Program Files\Keyfactor\kfutil\kfutil.exe' + +if ($null -eq (Get-Command "kfutil" -ErrorAction SilentlyContinue)) { + Write-Host "kfutil could not be found. Please install kfutil" + Write-Host "See https://github.com/Keyfactor/kfutil#quickstart" + exit 1 +} + +if (-not $env:KEYFACTOR_HOSTNAME) { + Write-Host "KEYFACTOR_HOSTNAME not set — launching kfutil login" + & kfutil login +} + +& kfutil store-types create --name "AKV" + +Write-Host "Done. All store types created." diff --git a/scripts/store_types/powershell/restmethod_create_store_types.ps1 b/scripts/store_types/powershell/restmethod_create_store_types.ps1 new file mode 100644 index 0000000..2eeb0b3 --- /dev/null +++ b/scripts/store_types/powershell/restmethod_create_store_types.ps1 @@ -0,0 +1,179 @@ +# Creates all 1 store types via the Keyfactor Command REST API +# using PowerShell Invoke-RestMethod. +# +# Authentication (first matching method is used): +# OAuth access token: KEYFACTOR_AUTH_ACCESS_TOKEN +# OAuth client creds: KEYFACTOR_AUTH_CLIENT_ID + KEYFACTOR_AUTH_CLIENT_SECRET +# + KEYFACTOR_AUTH_TOKEN_URL +# Basic auth (AD): KEYFACTOR_USERNAME + KEYFACTOR_PASSWORD + KEYFACTOR_DOMAIN +# +# Always required: +# KEYFACTOR_HOSTNAME Command hostname (e.g. my-command.example.com) +# +# Auto-generated by doctool generate-store-type-scripts — do not edit by hand. + +if (-not $env:KEYFACTOR_HOSTNAME) { + Write-Error "KEYFACTOR_HOSTNAME is required" + exit 1 +} + +$uri = "https://$($env:KEYFACTOR_HOSTNAME)/keyfactorapi/certificatestoretypes" +$headers = @{ + 'Content-Type' = "application/json" + 'x-keyfactor-requested-with' = "APIClient" +} + +# --------------------------------------------------------------------------- +# Resolve auth +# --------------------------------------------------------------------------- +if ($env:KEYFACTOR_AUTH_ACCESS_TOKEN) { + $headers['Authorization'] = "Bearer $($env:KEYFACTOR_AUTH_ACCESS_TOKEN)" +} elseif ($env:KEYFACTOR_AUTH_CLIENT_ID -and $env:KEYFACTOR_AUTH_CLIENT_SECRET -and $env:KEYFACTOR_AUTH_TOKEN_URL) { + Write-Host "Fetching OAuth token..." + $tokenBody = @{ + grant_type = 'client_credentials' + client_id = $env:KEYFACTOR_AUTH_CLIENT_ID + client_secret = $env:KEYFACTOR_AUTH_CLIENT_SECRET + } + $tokenResp = Invoke-RestMethod -Method Post -Uri $env:KEYFACTOR_AUTH_TOKEN_URL -Body $tokenBody + $headers['Authorization'] = "Bearer $($tokenResp.access_token)" +} elseif ($env:KEYFACTOR_USERNAME -and $env:KEYFACTOR_PASSWORD -and $env:KEYFACTOR_DOMAIN) { + $cred = [System.Convert]::ToBase64String( + [System.Text.Encoding]::ASCII.GetBytes( + "$($env:KEYFACTOR_USERNAME)@$($env:KEYFACTOR_DOMAIN):$($env:KEYFACTOR_PASSWORD)")) + $headers['Authorization'] = "Basic $cred" +} else { + Write-Error ("Authentication required. Set one of:`n" + + " KEYFACTOR_AUTH_ACCESS_TOKEN`n" + + " KEYFACTOR_AUTH_CLIENT_ID + KEYFACTOR_AUTH_CLIENT_SECRET + KEYFACTOR_AUTH_TOKEN_URL`n" + + " KEYFACTOR_USERNAME + KEYFACTOR_PASSWORD + KEYFACTOR_DOMAIN") + exit 1 +} + +function New-StoreType { + param([string]$Name, [string]$Body) + Write-Host "Creating $Name store type..." + try { + Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -Body $Body -ContentType "application/json" | Out-Null + Write-Host " OK" + } catch { + Write-Warning " FAILED: $($_.Exception.Message)" + } +} + +# --------------------------------------------------------------------------- +# AKV — The GUID of the tenant ID of the Azure Keyvault instance; for example, '12345678-1234-1234-1234-123456789abc'. +# --------------------------------------------------------------------------- +New-StoreType "AKV" @' +{ + "BlueprintAllowed": false, + "Capability": "AKV", + "CustomAliasAllowed": "Optional", + "EntryParameters": [ + { + "Name": "CertificateTags", + "DisplayName": "Certificate Tags", + "Description": "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'}'", + "Type": "string", + "DefaultValue": "", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + } + }, + { + "Name": "PreserveExistingTags", + "DisplayName": "Preserve Existing Tags", + "Description": "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.", + "Type": "Bool", + "DefaultValue": "False", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + } + }, + { + "Name": "NonExportable", + "DisplayName": "Non Exportable Private Key", + "Description": "If true, this will mark the certificate as having a non-exportable private key when importing into Azure KeyVault", + "Type": "Bool", + "DefaultValue": "False", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + } + } + ], + "JobProperties": [], + "LocalStore": false, + "Name": "Azure Keyvault", + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "PowerShell": false, + "PrivateKeyAllowed": "Optional", + "Properties": [ + { + "Name": "TenantId", + "DisplayName": "Tenant Id", + "Type": "String", + "DependsOn": "", + "Required": false + }, + { + "Name": "SkuType", + "DisplayName": "SKU Type", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "standard,premium", + "Required": false + }, + { + "Name": "VaultRegion", + "DisplayName": "Vault Region", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "eastus,eastus2,westus2,westus3,westus", + "Required": false + }, + { + "Name": "AzureCloud", + "DisplayName": "Azure Cloud", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "public,china,government", + "Required": false + }, + { + "Name": "PrivateEndpoint", + "DisplayName": "Private KeyVault Endpoint", + "Type": "String", + "DependsOn": "", + "Required": false + } + ], + "ServerRequired": true, + "ShortName": "AKV", + "StorePathDescription": "A string formatted as '{subscription id}:{resource group name}:{vault name}'; for example, '12345678-1234-1234-1234-123456789abc:myResourceGroup:myVault'.", + "StorePathType": "", + "StorePathValue": "", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": false, + "Remove": true + } +} +'@ + + +Write-Host "Completed."