diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml index b7d1185..c77eeda 100644 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -45,9 +45,3 @@ jobs: secrets: token: ${{ secrets.SDK_SYNC_PAT }} - call-update-store-types-workflow: - needs: get-manifest-properties - if: needs.get-manifest-properties.outputs.integration_type == 'orchestrator' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') - uses: Keyfactor/actions/.github/workflows/update-store-types.yml@main - secrets: - token: ${{ secrets.UPDATE_STORE_TYPES }} diff --git a/AzureKeyVault.sln b/AzureKeyVault.sln index 75246d0..464c21d 100644 --- a/AzureKeyVault.sln +++ b/AzureKeyVault.sln @@ -7,6 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureKeyVault", "AzureKeyVa EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AB1BF579-FBD3-4F59-BBF2-7B973B9AD1DB}" ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md integration-manifest.json = integration-manifest.json readme_source.md = readme_source.md EndProjectSection diff --git a/AzureKeyVault/AkvProperties.cs b/AzureKeyVault/AkvProperties.cs index 1431051..031af3b 100644 --- a/AzureKeyVault/AkvProperties.cs +++ b/AzureKeyVault/AkvProperties.cs @@ -1,16 +1,11 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System.Collections.Generic; namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault { @@ -22,9 +17,10 @@ public class AkvProperties public string TenantId { get; set; } public string ResourceGroupName { get; set; } public string VaultName { get; set; } - public string StorePath { get; set; } + public string StorePath { get; set; } // format = :: public string VaultRegion { get; set; } public bool PremiumSKU { get; set; } + public List TenantIdsForDiscovery { get; set; } internal protected bool UseAzureManagedIdentity { get diff --git a/AzureKeyVault/AzureClient.cs b/AzureKeyVault/AzureClient.cs index 42a5c56..8ca5308 100644 --- a/AzureKeyVault/AzureClient.cs +++ b/AzureKeyVault/AzureClient.cs @@ -1,16 +1,9 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. using System; using System.Collections.Generic; @@ -36,14 +29,17 @@ public class AzureClient { internal protected virtual AkvProperties VaultProperties { get; set; } - private Uri AzureCloudEndpoint { - get { - switch (VaultProperties.AzureCloud.ToLower()) { + private Uri AzureCloudEndpoint + { + get + { + switch (VaultProperties.AzureCloud?.ToLower()) + { case "china": - return AzureAuthorityHosts.AzureChina; + return AzureAuthorityHosts.AzureChina; case "germany": - return AzureAuthorityHosts.AzureGermany; + return AzureAuthorityHosts.AzureGermany; case "government": return AzureAuthorityHosts.AzureGovernment; default: @@ -59,6 +55,7 @@ private protected virtual CertificateClient CertClient { if (_certClient != null) { + logger.LogTrace("getting previously initialized certificate client"); return _certClient; } @@ -68,17 +65,22 @@ private protected virtual CertificateClient CertClient if (this.VaultProperties.UseAzureManagedIdentity) { - var credentialOptions = new DefaultAzureCredentialOptions { AuthorityHost = AzureCloudEndpoint }; + logger.LogTrace("Entering the managed identity workflow"); + + var credentialOptions = new DefaultAzureCredentialOptions { AuthorityHost = AzureCloudEndpoint, AdditionallyAllowedTenants = { "*" } }; if (!string.IsNullOrEmpty(this.VaultProperties.ClientId)) // are they using a user assigned identity instead of a system assigned one (default)? { + logger.LogTrace("they provided client ID, so it is a user assigned managed identity (instead of system assigned)"); credentialOptions.ManagedIdentityClientId = VaultProperties.ClientId; } cred = new DefaultAzureCredential(credentialOptions); } else { - cred = new ClientSecretCredential(VaultProperties.TenantId, VaultProperties.ClientId, VaultProperties.ClientSecret); + logger.LogTrace("They are using a service principal to authenticate, generating the credentials"); + cred = new ClientSecretCredential(VaultProperties.TenantId, VaultProperties.ClientId, VaultProperties.ClientSecret, new ClientSecretCredentialOptions() { AuthorityHost = AzureCloudEndpoint, AdditionallyAllowedTenants = { "*" } }); + logger.LogTrace("generated credentials", cred); } _certClient = new CertificateClient(new Uri(VaultProperties.VaultURL), credential: cred); @@ -88,40 +90,47 @@ private protected virtual CertificateClient CertClient } protected CertificateClient _certClient { get; set; } - internal protected virtual ArmClient KvManagementClient + internal protected virtual ArmClient getArmClient(string tenantId) { - get + TokenCredential credential; + var credentialOptions = new DefaultAzureCredentialOptions { AuthorityHost = AzureCloudEndpoint, AdditionallyAllowedTenants = { "*" } }; + if (this.VaultProperties.UseAzureManagedIdentity) { - if (_mgmtClient != null) + logger.LogTrace("getting management client for a managed identity"); + if (!string.IsNullOrEmpty(tenantId)) credentialOptions.TenantId = tenantId; + + if (!string.IsNullOrEmpty(this.VaultProperties.ClientId)) // they have selected a managed identity and provided a client ID, so it is a user assigned identity { - return _mgmtClient; + logger.LogTrace("It is a user assigned managed identity"); + credentialOptions.ManagedIdentityClientId = VaultProperties.ClientId; } + credential = new DefaultAzureCredential(credentialOptions); + } + else + { + logger.LogTrace("getting credentials for a service principal identity"); + credential = new ClientSecretCredential(tenantId, VaultProperties.ClientId, VaultProperties.ClientSecret, credentialOptions); + logger.LogTrace("got credentials for service principal identity", credential); + } - // var subId = VaultProperties.SubscriptionId ?? VaultProperties.StorePath.Split("/")[2]; - // var creds = SdkContext.AzureCredentialsFactory.FromServicePrincipal(VaultProperties.ApplicationId, VaultProperties.ClientSecret, VaultProperties.TenantId, AzureEnvironment.AzureGlobalCloud); - //NOTE: creating a certificate store from the platform is currently only supported for Azure GlobalCloud customers. - - TokenCredential credential; - - if (this.VaultProperties.UseAzureManagedIdentity) - { - var credentialOptions = new DefaultAzureCredentialOptions { AuthorityHost = AzureCloudEndpoint }; + _mgmtClient = new ArmClient(credential); + logger.LogTrace("created management client", _mgmtClient); + return _mgmtClient; + } - if (!string.IsNullOrEmpty(this.VaultProperties.ClientId)) // they have selected a managed identity and provided a client ID, so it is a user assigned identity - { - credentialOptions.ManagedIdentityClientId = VaultProperties.ClientId; - } - credential = new DefaultAzureCredential(credentialOptions); - } - else + internal protected virtual ArmClient KvManagementClient + { + get + { + if (_mgmtClient != null) { - credential = new ClientSecretCredential(VaultProperties.TenantId, VaultProperties.ClientId, VaultProperties.ClientSecret); + logger.LogTrace("getting previously initialized management client"); + return _mgmtClient; } - - _mgmtClient = new ArmClient(credential); - return _mgmtClient; + return getArmClient(VaultProperties.TenantId); } } + protected virtual ArmClient _mgmtClient { get; set; } public AzureClient() { } @@ -134,6 +143,7 @@ public AzureClient(AkvProperties props) public virtual async Task DeleteCertificateAsync(string certName) { + logger.LogTrace("calling method to delete certificate"); return await CertClient.StartDeleteCertificateAsync(certName); } @@ -141,24 +151,41 @@ public virtual async Task CreateVault() { try { - logger.LogInformation("Begin create vault..."); + logger.LogInformation($"Begin create vault in Subscription {VaultProperties.SubscriptionId} with storepath = {VaultProperties.StorePath}"); - SubscriptionResource subscription = await KvManagementClient.GetDefaultSubscriptionAsync(); - ResourceGroupCollection resourceGroups = subscription.GetResourceGroups(); - ResourceGroupResource resourceGroup = await resourceGroups.GetAsync(this.VaultProperties.ResourceGroupName); - - var vaults = resourceGroup.GetKeyVaults(); + logger.LogTrace($"getting subscription info for provided subscription id {VaultProperties.SubscriptionId}"); + SubscriptionResource subscription = KvManagementClient.GetSubscriptionResource(SubscriptionResource.CreateResourceIdentifier(VaultProperties.SubscriptionId)); + ResourceGroupResource resourceGroup = subscription.GetResourceGroup(VaultProperties.ResourceGroupName); - //TODO: Create store type parameter for Azure Location. + AzureLocation loc; - var loc = new AzureLocation(VaultProperties.VaultRegion); // pass property instead of hardcoded value after testing + var vaults = resourceGroup.GetKeyVaults(); + if (string.IsNullOrEmpty(VaultProperties.VaultRegion)) + { + try + { + logger.LogTrace($"no Vault Region location specified for new Vault, Getting available regions for resource group {resourceGroup.Data.Name}."); + var locOptions = await resourceGroup.GetAvailableLocationsAsync(); + logger.LogTrace($"got location options for subscription {subscription.Data.SubscriptionId}", locOptions); + loc = locOptions.Value.FirstOrDefault(); + } + catch (Exception ex) + { + logger.LogError($"error retrieving default Azure Location: {ex.Message}", ex); + throw; + } + } + else + { + loc = new AzureLocation(VaultProperties.VaultRegion); + } var skuType = VaultProperties.PremiumSKU ? KeyVaultSkuName.Premium : KeyVaultSkuName.Standard; var content = new KeyVaultCreateOrUpdateContent(loc, new KeyVaultProperties(new Guid(VaultProperties.TenantId), new KeyVaultSku(KeyVaultSkuFamily.A, skuType))); - var newVault = await vaults.CreateOrUpdateAsync(WaitUntil.Completed, VaultProperties.VaultName, content); //this takes a bit of time to run + var newVault = await vaults.CreateOrUpdateAsync(WaitUntil.Completed, VaultProperties.VaultName, content); return newVault.Value; } @@ -174,15 +201,19 @@ public virtual async Task ImportCertificateAsync( { try { + logger.LogTrace("checking to see if the certificate exists and has been deleted"); + if (CertClient.GetDeletedCertificates().FirstOrDefault(i => i.Name == certName) != null) { + logger.LogTrace("certificate to import has been previously deleted, starting recovery operation."); RecoverDeletedCertificateOperation recovery = await CertClient.StartRecoverDeletedCertificateAsync(certName); recovery.WaitForCompletion(); } - + logger.LogTrace("begin creating x509 certificate from contents."); var bytes = Convert.FromBase64String(contents); var x509 = new X509Certificate2(bytes, pfxPassword, X509KeyStorageFlags.Exportable); var certWithKey = x509.Export(X509ContentType.Pkcs12); + logger.LogTrace($"importing created x509 certificate named {1}", certName); var cert = await CertClient.ImportCertificateAsync(new ImportCertificateOptions(certName, certWithKey)); return cert; } @@ -199,7 +230,9 @@ public virtual async Task> GetCertificatesAsyn Pageable inventory = null; try { + logger.LogTrace("calling GetPropertiesOfCertificates() on the Certificate Client", CertClient); inventory = CertClient.GetPropertiesOfCertificates(); + logger.LogTrace("got a response", inventory); var certQuantity = inventory.Count(); } catch (Exception ex) @@ -208,9 +241,14 @@ public virtual async Task> GetCertificatesAsyn throw; } + logger.LogTrace("retrieving each certificate from the response"); + foreach (var certificate in inventory) { + logger.LogTrace("getting details for the individual certificate", certificate); var cert = await CertClient.GetCertificateAsync(certificate.Name); + logger.LogTrace("got certificate response", cert); + inventoryItems.Add(new CurrentInventoryItem() { Alias = cert.Value.Name, @@ -225,21 +263,43 @@ public virtual async Task> GetCertificatesAsyn public virtual async Task> GetVaults() { - // discovery is currently only available for the Azure Public cloud. - // We need a way to pass the Azure Cloud parameter as part of a discovery job to support other cloud instances. + var vaultNames = new List(); try { - // trace log the resrouce groups available and checked - SubscriptionResource subscription = await KvManagementClient.GetDefaultSubscriptionAsync(); - ResourceGroupCollection resourceGroups = subscription.GetResourceGroups(); - var vaultNames = new List(); - - resourceGroups.ToList().ForEach(rg => // we go through all of the resource groups that the identity has access to + if (VaultProperties.TenantIdsForDiscovery == null || VaultProperties.TenantIdsForDiscovery.Count() < 1) + { + throw new Exception("no tenant ID's provided."); + } + VaultProperties.TenantIdsForDiscovery.ForEach(tenantId => { - var rgVaults = rg.GetKeyVaults().ToList(); - vaultNames.AddRange(rgVaults.Select(v => subscription.Id.SubscriptionId + ":" + v.Data.Name)); + logger.LogTrace($"getting ARM client for tenantId {tenantId}"); + + var mgmtClient = getArmClient(tenantId); + + logger.LogTrace($"getting all available subscriptions in tenant with ID {tenantId}"); + var allSubs = mgmtClient.GetSubscriptions(); + + logger.LogTrace($"got {allSubs.Count()} subscriptions"); + + foreach (var sub in allSubs) + { + logger.LogTrace($"searching for vaults in subscription with ID {sub.Data.SubscriptionId}"); + var vaults = sub.GetKeyVaults(); + logger.LogTrace($"found {vaults.Count()} vaults."); + + foreach (var vault in vaults) + { + var splitId = vault.Id.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries); + // example resource identifier: /subscriptions/b3114ff1-bb92-45b6-9bd6-e4a1eed8c91e/resourceGroups/azure_sentinel_evaluation/providers/Microsoft.KeyVault/vaults/jv2-vault + var subId = splitId[1]; + var resourceGroupName = splitId[3]; + var vaultName = splitId.Last(); + vaultNames.Add($"{subId}:{resourceGroupName}:{vaultName}"); + } + } }); + return vaultNames; } catch (Exception ex) diff --git a/AzureKeyVault/Constants.cs b/AzureKeyVault/Constants.cs index 991b9c9..c142ca5 100644 --- a/AzureKeyVault/Constants.cs +++ b/AzureKeyVault/Constants.cs @@ -1,16 +1,9 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault { diff --git a/AzureKeyVault/JobAttribute.cs b/AzureKeyVault/JobAttribute.cs index 2bf13cd..4b52863 100644 --- a/AzureKeyVault/JobAttribute.cs +++ b/AzureKeyVault/JobAttribute.cs @@ -1,16 +1,9 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. using System; diff --git a/AzureKeyVault/Jobs/AzureKeyVaultJob.cs b/AzureKeyVault/Jobs/AzureKeyVaultJob.cs index f49aa22..07d432e 100644 --- a/AzureKeyVault/Jobs/AzureKeyVaultJob.cs +++ b/AzureKeyVault/Jobs/AzureKeyVaultJob.cs @@ -1,18 +1,12 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. using System; +using System.Collections.Generic; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; @@ -35,7 +29,7 @@ public void InitializeStore(dynamic config) try { VaultProperties = new AkvProperties(); - if (config.GetType().GetProperty("ClientMachine") != null) + if (config.GetType().GetProperty("ClientMachine") != null) // Discovery job VaultProperties.TenantId = config.ClientMachine; // ClientId can be omitted for system assigned managed identities, required for user assigned or service principal auth @@ -44,25 +38,84 @@ public void InitializeStore(dynamic config) // ClientSecret can be omitted for managed identities, required for service principal auth VaultProperties.ClientSecret = PAMUtilities.ResolvePAMField(PamSecretResolver, logger, "Server Password", config.ServerPassword); - if (config.GetType().GetProperty("CertificateStoreDetails") != null) + if (config.GetType().GetProperty("CertificateStoreDetails") != null) // anything except a discovery job { VaultProperties.StorePath = config.CertificateStoreDetails?.StorePath; dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()); - VaultProperties.TenantId = config.CertificateStoreDetails?.ClientMachine != null ? config.CertificateStoreDetails?.ClientMachine : VaultProperties.TenantId; - VaultProperties.TenantId = VaultProperties.TenantId ?? properties.dirs; - VaultProperties.ResourceGroupName = properties.ResourceGroupName; - VaultProperties.VaultName = properties.VaultName; - VaultProperties.PremiumSKU = properties.SkuType == "premium"; - VaultProperties.VaultRegion = properties.VaultRegion ?? "eastus"; - VaultProperties.VaultRegion = VaultProperties.VaultRegion.ToLower(); + + // get the values from the storepath field. format is :: + var storePathFields = VaultProperties.StorePath.Split(":"); + + if (storePathFields.Length == 3) + { //using the latest (3 fields) + logger.LogTrace($"storepath split by `:` into 3 parts. {storePathFields}. Using Using {{subscription id}}:{{resource group name}}:{{vault name}} format."); + VaultProperties.SubscriptionId = storePathFields[0].Trim(); + VaultProperties.ResourceGroupName = storePathFields[1].Trim(); + VaultProperties.VaultName = storePathFields[2]?.Trim(); + } + + // support legacy store path : + if (storePathFields.Length == 2) + { // using previous version (2 fields) + logger.LogTrace($"storepath split by `:` into 2 parts. {storePathFields}. Using {{subscription id}}:{{vault name}} format."); + VaultProperties.SubscriptionId = storePathFields[0].Trim(); + VaultProperties.VaultName = storePathFields[0].Trim(); + VaultProperties.SubscriptionId = properties.SubscriptionId; + } + + // support legacy store path + // - example: /subscriptions/b3114ff1-bb92-45b6-9bd6-e4a1eed8c91e/resourceGroups/azure_sentinel_evaluation/providers/Microsoft.KeyVault/vaults/jv2-vault + if (storePathFields.Length == 1) + { + var legacyPathComponents = VaultProperties.StorePath.Split('/', StringSplitOptions.RemoveEmptyEntries); + if (legacyPathComponents.Length == 8) // they are using the full resource path + { + logger.LogTrace($"storepath split by `/`. {storePathFields}. Using {{subscription id}}:{{vault name}} format."); + VaultProperties.SubscriptionId = legacyPathComponents[1]; + VaultProperties.ResourceGroupName = legacyPathComponents[3]; + VaultProperties.VaultName = legacyPathComponents[7]; + } + } + + VaultProperties.SubscriptionId = properties.SubscriptionId ?? VaultProperties.SubscriptionId; + VaultProperties.ResourceGroupName = properties.ResourceGroupName ?? VaultProperties.ResourceGroupName; + VaultProperties.VaultName = properties.VaultName ?? VaultProperties.VaultName; // check the field in case of legacy paths. + VaultProperties.TenantId = VaultProperties.TenantId ?? config.CertificateStoreDetails?.ClientMachine; // Client Machine could be null in the case of managed identity. That's ok. + + string skuType = properties.SkuType; + VaultProperties.PremiumSKU = skuType?.ToLower() == "premium"; + VaultProperties.VaultRegion = properties.VaultRegion; + VaultProperties.VaultRegion = VaultProperties.VaultRegion?.ToLower(); } + else // discovery job : Discovery only works on the Global Public Azure cloud because we do not have a way to pass the Azure Cloud instance value during a discovery job. + { + logger.LogTrace("Discovery job - getting tenant ids from directories to search field."); + VaultProperties.TenantIdsForDiscovery = new List(); + var dirs = config.JobProperties?["dirs"] as string; + logger.LogTrace($"Directories to search: {dirs}"); + + if (!string.IsNullOrEmpty(dirs)) + { + // parse the list of tenant ids to perform discovery on + VaultProperties.TenantIdsForDiscovery.AddRange(dirs.Split(',')); + } + else + { + // if it is empty, we use the default provided Tenant Id only + VaultProperties.TenantIdsForDiscovery.Add(VaultProperties.TenantId); + } + + VaultProperties.TenantIdsForDiscovery.ForEach(tId => tId = tId.Trim()); + VaultProperties.TenantId = VaultProperties.TenantId ?? VaultProperties.TenantIdsForDiscovery[0]; + } AzClient ??= new AzureClient(VaultProperties); } - catch (Exception ex) { + catch (Exception ex) + { logger.LogError("Error initializing store", ex.Message); throw; } - } + } } } diff --git a/AzureKeyVault/Jobs/Discovery.cs b/AzureKeyVault/Jobs/Discovery.cs index acdcbd8..3550020 100644 --- a/AzureKeyVault/Jobs/Discovery.cs +++ b/AzureKeyVault/Jobs/Discovery.cs @@ -1,16 +1,9 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. using System; using System.Collections.Generic; diff --git a/AzureKeyVault/Jobs/Inventory.cs b/AzureKeyVault/Jobs/Inventory.cs index ebddc7c..f6e2352 100644 --- a/AzureKeyVault/Jobs/Inventory.cs +++ b/AzureKeyVault/Jobs/Inventory.cs @@ -1,16 +1,9 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. using System; using System.Collections.Generic; diff --git a/AzureKeyVault/Jobs/Management.cs b/AzureKeyVault/Jobs/Management.cs index 3615cc6..564f515 100644 --- a/AzureKeyVault/Jobs/Management.cs +++ b/AzureKeyVault/Jobs/Management.cs @@ -1,16 +1,9 @@ // Copyright 2023 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. using System; using System.Linq; diff --git a/AzureKeyVault/PamUtilities.cs b/AzureKeyVault/PamUtilities.cs index accc1b8..4b1e7c6 100644 --- a/AzureKeyVault/PamUtilities.cs +++ b/AzureKeyVault/PamUtilities.cs @@ -1,5 +1,4 @@ - -// Copyright 2023 Keyfactor +// Copyright 2023 Keyfactor // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6d290..6dd9797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ + +- 3.1 + - Updated the Discovery job to support multiple tenants and all accessible subscriptions they contain + - Added more detailed trace logging during the discovery process + - Changed store path to be `subscription id : resource group name : vault name` + - Removed redundant Vault Name and Resource Group Name fields + - Updated documentation to explain when optional fields can be omitted from the store type definition + - Added support for legacy store paths on existing stores + - 3.0 - Added support for Azure clouds other than US public. - Shortened Store path to `subscription id : vault name` @@ -10,6 +19,6 @@ - Added support for Azure Managed Identity authentication - Updated Azure client libraries - Removed ObjectId parameter from StoreType definition - - Added SkuType and VaultRegion parameters to support vault creation from the platform. + - Added SkuType and VaultRegion parameters to support vault creation from the platform - 1.05 diff --git a/Images/discovery-result.png b/Images/discovery-result.png index 6af90a7..10c0871 100644 Binary files a/Images/discovery-result.png and b/Images/discovery-result.png differ diff --git a/Images/resource-id.png b/Images/resource-id.png index 8f92755..937e9d4 100644 Binary files a/Images/resource-id.png and b/Images/resource-id.png differ diff --git a/Images/storepath.png b/Images/storepath.png index 9f93278..a0c38dd 100644 Binary files a/Images/storepath.png and b/Images/storepath.png differ diff --git a/README.md b/README.md index 10b2a41..2bd376c 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,11 @@ _If the Windows Orchestrator is being completely replaced with the Universal Orc 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}`. + --- ### Configure the Azure Keyvault for client access @@ -372,6 +377,8 @@ Now we can navigate to the Keyfactor platform and create the store type for Azur 1) The Azure Keyvault integration supports the following job types: _Inventory, Add, Remove, Create and Discovery_. Select from these the capabilities you would like to utilize. +> :warning: The store type definition needs to include the necessary fields to support Create functionality (SkuType and VaultRegion). Be sure to read through the _Custom Fields_ instructions below and set them up with the required fields if Creating new Azure Keyvaults from Keyfactor Command is desired. + 1) **If you are using a Service Principal or User assigned Managed Identity only** Make sure that "Needs Server" is checked. ![Cert Store Types Menu](/Images/cert-store-type.png) @@ -387,23 +394,39 @@ Now we can navigate to the Keyfactor platform and create the store type for Azur ![Cert Store Types Menu](/Images/store-type-fields-advanced.png) -1) Navigate to the _Custom Fields_ tab and add the following fields +1) Navigate to the _Custom Fields_ tab and add the custom fields for the store type. + +> :warning: If you are using the Global Public cloud (*.vault.azure.net) and creating new Azure +> Keyvaults from Keyfactor Command functionality is not necessary for your workflow, this section can +> be skipped entirely. + +- The below two fields are necessary if working with Keyvaults in Azure Cloud instances that are not the standard global public one (*.vault.azure.net) If your vault instance(s) have the base url of `.vault.azure.net` then the next two fields can be omitted from the store type definition and the default global public cloud will be assumed. +- - The "Azure Cloud" field refers to + +| Name | Display Name | Type | Required | +| ---- | ------------ | ---- | -------- | +| AzureCloud[^azurecloud] | Azure Cloud | MultipleChoice | false | +| PrivateEndpoint[^privateEndpoint] | Private Endpoint | String | false | + +[^azurecloud]: The Azure Cloud field, if necessary, should contain one of the following values: "china, germany, government". This is the Azure Cloud instance your organization uses. If using the standard "public" cloud, this field can be left blank or omitted entirely from the store type definition. - | Name | Display Name | Type | Required | - | ---- | ------------ | ---- | -------- | - | VaultName | Vault Name | String | true | - | ResourceGroupName | Resource Group Name | String | true | - | SkuType[^sku] | SKU Type | MultipleChoice | false | - | VaultRegion[^vaultregion] | Vault Region | MultipleChoice | false | - | TenantId | Tenant Id | String | True +[^privateEndpoint]: The Private Endpoint field should be used if you if have a custom url assigned to your keyvault resources and they are not accessible via the standard endpoint associated with the Azure Cloud instance (*.vault.azure.net, *.vault.azure.cn, etc.). This field should contain the base url for your vault instance(s), excluding the vault name. - [^sku]: The SkuType determines the service tier when creating a new instance of Azure KeyVault via the platform. Valid values include "premium" and "standard". - If either option should be available when creating a new KeyVault from the Command platform via creating a new certificate store, then the value to enter for the multiple choice options should be "standard,premium". - If your organization requires that one or the other option should always be used, you can limit the options to a single value ("premium" or "standard"). If not selected, "standard" is used when creating a new KeyVault. +- The following fields are _only_ necessary in order to support creating new Azure Keyvaults from the Keyfactor Command platform. If this functionality is not needed, there is no need to set up these fields. - [^vaultregion]: The Vault Region field is only important when creating a new Azure KeyVault from the Command Platform. This is the region that the newly created vault will be created in. When creating the cert store type, - you can limit the options to those that should be applicable to your organization. Refer to the [Azure Documentation](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnethttps://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) for a list of valid region names. - If no value is selected, "eastus" is used by default. +| Name | Display Name | Type | Required | +| ---- | ------------ | ---- | -------- | +| TenantId | Tenant Id | String | false | +| SkuType[^sku] | SKU Type | MultipleChoice | false | +| VaultRegion[^vaultregion] | Vault Region | MultipleChoice | false | + +[^sku]: The SkuType determines the service tier when creating a new instance of Azure KeyVault via the platform. Valid values include "premium" and "standard". + If either option should be available when creating a new KeyVault from the Command platform via creating a new certificate store, then the value to enter for the multiple choice options should be "standard,premium". + If your organization requires that one or the other option should always be used, you can limit the options to a single value ("premium" or "standard"). If not selected, "standard" is used when creating a new KeyVault. + +[^vaultregion]: The Vault Region field is only important when creating a new Azure KeyVault from the Command Platform. This is the region that the newly created vault will be created in. When creating the cert store type, + you can limit the options to those that should be applicable to your organization. Refer to the [Azure Documentation](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnethttps://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) for a list of valid region names. + If no value is selected, "eastus" is used by default. ### Install the Extension on the Orchestrator @@ -460,7 +483,7 @@ Now that we have the extension registered on the Orchestrator, we can navigate b - 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 managed user ID. + - `User` should be set to the Client ID of the managed identity. - `Password` should be set to the value **"managed"**. - For Service principal authentication: @@ -503,8 +526,10 @@ Follow these steps to store the values: 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 you are using a system assigned managed identity, you will need to enter the **Tenant Id** value into the "Directories to Search" field. +> 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". @@ -512,21 +537,19 @@ Follow these steps to store the values: 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 four Azure Keyvaults. +In this example, our job returned these Azure Keyvaults. ![Discovery Results](/Images/discovery-result.png) -The store path of each vault is the Azure Resource Identifier, and contains the following information: +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 "approve"). - -1) In the dialog window, enter the Vault Name from the store path value above, as well as the resource group name for the vault (found in the Azure portal). +1) Double-click the row that corresponds to the Azure Keyvault in the discovery results (you can also select the row and click "SAVE"). - ![Approve Cert Store](/Images/approve-cert-store.png) +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) @@ -534,7 +557,7 @@ To add one of these results to Keyfactor as a certificate store: 1) Click "SAVE". -### Add an individual Azure Keyvault certificate store +### 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: @@ -554,26 +577,24 @@ The steps to do this are: - 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 Azure Resource Identifier for the Keyvault. Copied from Azure, or created a new Keyvault (see below). -- **VaultName**: This is the name of the new or existing Azure Keyvault. -- **ResourceGroupName**: The name of the Azure Resource Group that contains the Keyvault. -- **SKU Type**: This field is only used when creating new vaults in Azure. Select any value, or leave blank. -- **Vault Region**: This field is also only used when creating new vaults. Select any value. +- **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}` -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. +- **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) -If the Keyvault does not exist in Azure, and you would like to create it: +- Use these values to create the store path -- Enter a value for the store path in the following format: +If the Keyvault does not exist in Azure, and you would like to create it: -`{subscription id}:{new vault name}` +- 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. -![Add Vault](/Images/add-vault.png) +> :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/integration-manifest.json b/integration-manifest.json index 824f26d..745f20f 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -44,20 +44,12 @@ }, "Properties": [ { - "Name": "VaultName", - "DisplayName": "VaultName", + "Name": "TenantId", + "DisplayName": "Tenant Id", "Type": "String", "DependsOn": "", "DefaultValue": null, - "Required": true - }, - { - "Name": "ResourceGroupName", - "DisplayName": "ResourceGroupName", - "Type": "String", - "DependsOn": "", - "DefaultValue": null, - "Required": true + "Required": false }, { "Name": "SkuType", @@ -72,7 +64,7 @@ "DisplayName": "Vault Region", "Type": "MultipleChoice", "DependsOn": "", - "DefaultValue": "eastus,eastus2,southcentralus,westus2,westus3,australiaeast,northeurope,swedencentral,uksouth,westeurope,centralus,southafricanorth,centralindia,eastasia,japaneast,koreacentral,canadacentral,francecentral,germanywestcentral,norwayeast,switzerlandnorth,uaenorth,brazilsouth,centraluseuap,eastus2euap,qatarcentral,centralusstage,eastusstage,eastus2stage,northcentralusstage,westusstage,asia,asiapacific,australia,brazil,canada,europe,france,germany,global,india,japan,korea,norway,singapore,southafrica,switzerland,uae,uk,unitedstates,unitedstatesuap,eastasiastage,southeastasiastage,brazilus,eastusstg,northcentralus,westus,jioindiawest,devfabric,westcentralus,southafricawest,australiacentral,australiacentral2,australiasoutheast,japanwest,jioindiacentral,koreasouth,southindia,westindia,canadaeast,francesouth,germanynorth,norwaywest,switzerlandwest,ukwest,uaecentral,brazilsoutheast", + "DefaultValue": "eastus,eastus2,westus2,westus3,westus", "Required": false }, { diff --git a/readme_source.md b/readme_source.md index 556aadd..cfc1d3a 100644 --- a/readme_source.md +++ b/readme_source.md @@ -65,6 +65,11 @@ _If the Windows Orchestrator is being completely replaced with the Universal Orc 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}`. + --- ### Configure the Azure Keyvault for client access @@ -272,6 +277,8 @@ Now we can navigate to the Keyfactor platform and create the store type for Azur 1) The Azure Keyvault integration supports the following job types: _Inventory, Add, Remove, Create and Discovery_. Select from these the capabilities you would like to utilize. +> :warning: The store type definition needs to include the necessary fields to support Create functionality (SkuType and VaultRegion). Be sure to read through the _Custom Fields_ instructions below and set them up with the required fields if Creating new Azure Keyvaults from Keyfactor Command is desired. + 1) **If you are using a Service Principal or User assigned Managed Identity only** Make sure that "Needs Server" is checked. ![Cert Store Types Menu](/Images/cert-store-type.png) @@ -287,23 +294,39 @@ Now we can navigate to the Keyfactor platform and create the store type for Azur ![Cert Store Types Menu](/Images/store-type-fields-advanced.png) -1) Navigate to the _Custom Fields_ tab and add the following fields +1) Navigate to the _Custom Fields_ tab and add the custom fields for the store type. + +> :warning: If you are using the Global Public cloud (*.vault.azure.net) and creating new Azure +> Keyvaults from Keyfactor Command functionality is not necessary for your workflow, this section can +> be skipped entirely. + +- The below two fields are necessary if working with Keyvaults in Azure Cloud instances that are not the standard global public one (*.vault.azure.net) If your vault instance(s) have the base url of `.vault.azure.net` then the next two fields can be omitted from the store type definition and the default global public cloud will be assumed. +- - The "Azure Cloud" field refers to + +| Name | Display Name | Type | Required | +| ---- | ------------ | ---- | -------- | +| AzureCloud[^azurecloud] | Azure Cloud | MultipleChoice | false | +| PrivateEndpoint[^privateEndpoint] | Private Endpoint | String | false | + +[^azurecloud]: The Azure Cloud field, if necessary, should contain one of the following values: "china, germany, government". This is the Azure Cloud instance your organization uses. If using the standard "public" cloud, this field can be left blank or omitted entirely from the store type definition. - | Name | Display Name | Type | Required | - | ---- | ------------ | ---- | -------- | - | VaultName | Vault Name | String | true | - | ResourceGroupName | Resource Group Name | String | true | - | SkuType[^sku] | SKU Type | MultipleChoice | false | - | VaultRegion[^vaultregion] | Vault Region | MultipleChoice | false | - | TenantId | Tenant Id | String | True +[^privateEndpoint]: The Private Endpoint field should be used if you if have a custom url assigned to your keyvault resources and they are not accessible via the standard endpoint associated with the Azure Cloud instance (*.vault.azure.net, *.vault.azure.cn, etc.). This field should contain the base url for your vault instance(s), excluding the vault name. - [^sku]: The SkuType determines the service tier when creating a new instance of Azure KeyVault via the platform. Valid values include "premium" and "standard". - If either option should be available when creating a new KeyVault from the Command platform via creating a new certificate store, then the value to enter for the multiple choice options should be "standard,premium". - If your organization requires that one or the other option should always be used, you can limit the options to a single value ("premium" or "standard"). If not selected, "standard" is used when creating a new KeyVault. +- The following fields are _only_ necessary in order to support creating new Azure Keyvaults from the Keyfactor Command platform. If this functionality is not needed, there is no need to set up these fields. - [^vaultregion]: The Vault Region field is only important when creating a new Azure KeyVault from the Command Platform. This is the region that the newly created vault will be created in. When creating the cert store type, - you can limit the options to those that should be applicable to your organization. Refer to the [Azure Documentation](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnethttps://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) for a list of valid region names. - If no value is selected, "eastus" is used by default. +| Name | Display Name | Type | Required | +| ---- | ------------ | ---- | -------- | +| TenantId | Tenant Id | String | false | +| SkuType[^sku] | SKU Type | MultipleChoice | false | +| VaultRegion[^vaultregion] | Vault Region | MultipleChoice | false | + +[^sku]: The SkuType determines the service tier when creating a new instance of Azure KeyVault via the platform. Valid values include "premium" and "standard". + If either option should be available when creating a new KeyVault from the Command platform via creating a new certificate store, then the value to enter for the multiple choice options should be "standard,premium". + If your organization requires that one or the other option should always be used, you can limit the options to a single value ("premium" or "standard"). If not selected, "standard" is used when creating a new KeyVault. + +[^vaultregion]: The Vault Region field is only important when creating a new Azure KeyVault from the Command Platform. This is the region that the newly created vault will be created in. When creating the cert store type, + you can limit the options to those that should be applicable to your organization. Refer to the [Azure Documentation](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnethttps://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) for a list of valid region names. + If no value is selected, "eastus" is used by default. ### Install the Extension on the Orchestrator @@ -360,7 +383,7 @@ Now that we have the extension registered on the Orchestrator, we can navigate b - 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 managed user ID. + - `User` should be set to the Client ID of the managed identity. - `Password` should be set to the value **"managed"**. - For Service principal authentication: @@ -403,8 +426,10 @@ Follow these steps to store the values: 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 you are using a system assigned managed identity, you will need to enter the **Tenant Id** value into the "Directories to Search" field. +> 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". @@ -412,21 +437,19 @@ Follow these steps to store the values: 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 four Azure Keyvaults. +In this example, our job returned these Azure Keyvaults. ![Discovery Results](/Images/discovery-result.png) -The store path of each vault is the Azure Resource Identifier, and contains the following information: +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 "approve"). - -1) In the dialog window, enter the Vault Name from the store path value above, as well as the resource group name for the vault (found in the Azure portal). +1) Double-click the row that corresponds to the Azure Keyvault in the discovery results (you can also select the row and click "SAVE"). - ![Approve Cert Store](/Images/approve-cert-store.png) +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) @@ -434,7 +457,7 @@ To add one of these results to Keyfactor as a certificate store: 1) Click "SAVE". -### Add an individual Azure Keyvault certificate store +### 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: @@ -454,26 +477,24 @@ The steps to do this are: - 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 Azure Resource Identifier for the Keyvault. Copied from Azure, or created a new Keyvault (see below). -- **VaultName**: This is the name of the new or existing Azure Keyvault. -- **ResourceGroupName**: The name of the Azure Resource Group that contains the Keyvault. -- **SKU Type**: This field is only used when creating new vaults in Azure. Select any value, or leave blank. -- **Vault Region**: This field is also only used when creating new vaults. Select any value. +- **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}` -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. +- **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) -If the Keyvault does not exist in Azure, and you would like to create it: +- Use these values to create the store path -- Enter a value for the store path in the following format: +If the Keyvault does not exist in Azure, and you would like to create it: -`{subscription id}:{new vault name}` +- 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. -![Add Vault](/Images/add-vault.png) +> :warning: The identity you are using for authentication will need to have sufficient Azure permissions to be able to create new Keyvaults. ---