From 0a8ea9548047b36f84e9efed0b9fc2f04596aab4 Mon Sep 17 00:00:00 2001 From: Joe VanWanzeele Date: Wed, 10 Jun 2026 14:17:04 -0400 Subject: [PATCH 1/3] ignoring license header file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dfcfd56..07a4825 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,4 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ +/aws-acm-orchestrator.sln.licenseheader From e1b94195d744c258d0def69642d0f270fb38c04c Mon Sep 17 00:00:00 2001 From: Joe VanWanzeele Date: Wed, 10 Jun 2026 14:17:09 -0400 Subject: [PATCH 2/3] added the .skip(1) on the chain results from BC to address issue where leaf was showing up twice in AWS. Added unit tests and improved determination of whether a string is a cert ARN. --- CHANGELOG.md | 3 + aws-acm-orchestrator.Tests/BcCertFactory.cs | 88 +++++++++++++++ aws-acm-orchestrator.Tests/GetChainTests.cs | 106 ++++++++++++++++++ .../IsAcmCertificateArnTests.cs | 46 ++++++++ .../aws-acm-orchestrator.Tests.csproj | 29 +++++ aws-acm-orchestrator.sln | 6 + aws-acm-orchestrator/Jobs/Inventory.cs | 20 ++-- aws-acm-orchestrator/Jobs/Management.cs | 60 ++++++---- .../aws-acm-orchestrator.csproj | 23 ++-- 9 files changed, 340 insertions(+), 41 deletions(-) create mode 100644 aws-acm-orchestrator.Tests/BcCertFactory.cs create mode 100644 aws-acm-orchestrator.Tests/GetChainTests.cs create mode 100644 aws-acm-orchestrator.Tests/IsAcmCertificateArnTests.cs create mode 100644 aws-acm-orchestrator.Tests/aws-acm-orchestrator.Tests.csproj diff --git a/CHANGELOG.md b/CHANGELOG.md index cb66b06..965a14c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +3.0.3 +* Bug Fix - On Management Add/renewal jobs, the leaf certificate is no longer included in the `CertificateChain` sent to ACM. BouncyCastle's `GetCertificateChain` returns the leaf as the first element, and it was already sent separately as the certificate body, causing the leaf to appear twice within the published certificate's chain. When the certificate has no intermediates, the chain is now omitted entirely rather than sent empty. + 3.0.2 * Bug Fix - On Management jobs, do not send ACM tags if the certificate is being renewed/replaced diff --git a/aws-acm-orchestrator.Tests/BcCertFactory.cs b/aws-acm-orchestrator.Tests/BcCertFactory.cs new file mode 100644 index 0000000..7c14f3f --- /dev/null +++ b/aws-acm-orchestrator.Tests/BcCertFactory.cs @@ -0,0 +1,88 @@ + +// Copyright 2026 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. + +using System; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace Keyfactor.Extensions.Orchestrator.Aws.Acm.Tests +{ + /// + /// Builds RSA key pairs, X.509 certificates, and PKCS#12 stores entirely with BouncyCastle + /// (no .NET CNG interop), so tests run deterministically on Windows without hitting the + /// private-key export restrictions that affect keys imported through the .NET certificate APIs. + /// Chains are set explicitly on the key entry so Pkcs12Store.GetCertificateChain returns + /// exactly the certificates we provide, in the order we provide them (leaf first). + /// + internal static class BcCertFactory + { + private const string SignatureAlgorithm = "SHA256WithRSA"; + + public static AsymmetricCipherKeyPair GenerateRsaKeyPair() + { + var generator = new RsaKeyPairGenerator(); + generator.Init(new KeyGenerationParameters(new SecureRandom(), 2048)); + return generator.GenerateKeyPair(); + } + + /// + /// Creates a certificate for signed by the holder of + /// . For a self-signed cert, pass the same common name for + /// subject and issuer and the subject's own key pair for public/private. + /// + public static X509Certificate CreateCertificate( + string subjectCommonName, + string issuerCommonName, + AsymmetricKeyParameter subjectPublicKey, + AsymmetricKeyParameter issuerPrivateKey) + { + var generator = new X509V3CertificateGenerator(); + generator.SetSerialNumber(BigInteger.ProbablePrime(120, new SecureRandom())); + generator.SetIssuerDN(new X509Name($"CN={issuerCommonName}")); + generator.SetSubjectDN(new X509Name($"CN={subjectCommonName}")); + generator.SetNotBefore(DateTime.UtcNow.AddDays(-1)); + generator.SetNotAfter(DateTime.UtcNow.AddYears(1)); + generator.SetPublicKey(subjectPublicKey); + + var signatureFactory = new Asn1SignatureFactory(SignatureAlgorithm, issuerPrivateKey, new SecureRandom()); + return generator.Generate(signatureFactory); + } + + /// Creates a self-signed certificate (issuer == subject, signed by its own key). + public static (X509Certificate Certificate, AsymmetricCipherKeyPair KeyPair) CreateSelfSigned(string commonName) + { + var keyPair = GenerateRsaKeyPair(); + var certificate = CreateCertificate(commonName, commonName, keyPair.Public, keyPair.Private); + return (certificate, keyPair); + } + + /// + /// Builds a PKCS#12 store containing a single key entry whose certificate chain is exactly + /// , in the order supplied (leaf first, then intermediates / root). + /// + public static Pkcs12Store BuildStore(string alias, AsymmetricKeyParameter privateKey, params X509Certificate[] chain) + { + var store = new Pkcs12StoreBuilder().Build(); + + var certificateEntries = new X509CertificateEntry[chain.Length]; + for (int i = 0; i < chain.Length; i++) + { + certificateEntries[i] = new X509CertificateEntry(chain[i]); + } + + store.SetKeyEntry(alias, new AsymmetricKeyEntry(privateKey), certificateEntries); + return store; + } + } +} diff --git a/aws-acm-orchestrator.Tests/GetChainTests.cs b/aws-acm-orchestrator.Tests/GetChainTests.cs new file mode 100644 index 0000000..706f4c1 --- /dev/null +++ b/aws-acm-orchestrator.Tests/GetChainTests.cs @@ -0,0 +1,106 @@ + +// Copyright 2026 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. + +using System.Collections.Generic; +using System.IO; +using System.Text; +using FluentAssertions; +using Keyfactor.Extensions.Orchestrator.Aws.Acm.Jobs; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.X509; +using Xunit; + +namespace Keyfactor.Extensions.Orchestrator.Aws.Acm.Tests +{ + /// + /// Covers . The leaf/end-entity certificate is sent to ACM + /// separately as the Certificate body of the ImportCertificateRequest, so it must NOT appear in + /// the CertificateChain. Including it caused the leaf to show up twice within a published + /// certificate's chain - the bug these tests guard against. + /// + public class GetChainTests + { + private const string KeyAlias = "leaf-entry"; + + [Fact] + public void GetChain_LeafAndRoot_ReturnsOnlyTheRoot_NotTheLeaf() + { + // Root signs the leaf directly; the PKCS#12 chain is [leaf, root]. + var (root, rootKeyPair) = BcCertFactory.CreateSelfSigned("Test Root CA"); + var leafKeyPair = BcCertFactory.GenerateRsaKeyPair(); + var leaf = BcCertFactory.CreateCertificate("Test Leaf", "Test Root CA", leafKeyPair.Public, rootKeyPair.Private); + + var store = BcCertFactory.BuildStore(KeyAlias, leafKeyPair.Private, leaf, root); + + var subjects = ReadChainSubjects(store, KeyAlias); + + subjects.Should().ContainSingle().Which.Should().Be("CN=Test Root CA"); + subjects.Should().NotContain("CN=Test Leaf"); + } + + [Fact] + public void GetChain_LeafIntermediateAndRoot_ReturnsIntermediateAndRoot_InOrder_WithoutLeaf() + { + // Root -> Intermediate -> Leaf; the PKCS#12 chain is [leaf, intermediate, root]. + var (root, rootKeyPair) = BcCertFactory.CreateSelfSigned("Test Root CA"); + var intermediateKeyPair = BcCertFactory.GenerateRsaKeyPair(); + var intermediate = BcCertFactory.CreateCertificate("Test Intermediate CA", "Test Root CA", intermediateKeyPair.Public, rootKeyPair.Private); + var leafKeyPair = BcCertFactory.GenerateRsaKeyPair(); + var leaf = BcCertFactory.CreateCertificate("Test Leaf", "Test Intermediate CA", leafKeyPair.Public, intermediateKeyPair.Private); + + var store = BcCertFactory.BuildStore(KeyAlias, leafKeyPair.Private, leaf, intermediate, root); + + var subjects = ReadChainSubjects(store, KeyAlias); + + subjects.Should().Equal("CN=Test Intermediate CA", "CN=Test Root CA"); + subjects.Should().NotContain("CN=Test Leaf"); + } + + [Fact] + public void GetChain_LeafOnly_ReturnsNull() + { + // A self-signed leaf with no issuers above it: nothing to send as a chain. + var leafKeyPair = BcCertFactory.GenerateRsaKeyPair(); + var leaf = BcCertFactory.CreateCertificate("Test Leaf", "Test Leaf", leafKeyPair.Public, leafKeyPair.Private); + + var store = BcCertFactory.BuildStore(KeyAlias, leafKeyPair.Private, leaf); + + MemoryStream chainStream = Management.GetChain(store, KeyAlias); + + chainStream.Should().BeNull("a leaf-only PFX has no intermediates, so the chain must be omitted rather than sent empty"); + } + + // Invokes the production GetChain and parses the PEM it produces back into subject DNs. + private static List ReadChainSubjects(Pkcs12Store store, string alias) + { + using (MemoryStream chainStream = Management.GetChain(store, alias)) + { + chainStream.Should().NotBeNull("a chain containing intermediates should produce PEM output"); + + string pem = Encoding.ASCII.GetString(chainStream.ToArray()); + + var subjects = new List(); + using (var reader = new StringReader(pem)) + { + var pemReader = new PemReader(reader); + object parsed; + while ((parsed = pemReader.ReadObject()) != null) + { + if (parsed is X509Certificate certificate) + { + subjects.Add(certificate.SubjectDN.ToString()); + } + } + } + + return subjects; + } + } + } +} diff --git a/aws-acm-orchestrator.Tests/IsAcmCertificateArnTests.cs b/aws-acm-orchestrator.Tests/IsAcmCertificateArnTests.cs new file mode 100644 index 0000000..b79e242 --- /dev/null +++ b/aws-acm-orchestrator.Tests/IsAcmCertificateArnTests.cs @@ -0,0 +1,46 @@ + +// Copyright 2026 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. + +using FluentAssertions; +using Keyfactor.Extensions.Orchestrator.Aws.Acm.Jobs; +using Xunit; + +namespace Keyfactor.Extensions.Orchestrator.Aws.Acm.Tests +{ + /// + /// Covers , which decides whether an Add job is a + /// replace/renewal of an existing ACM certificate (alias is an ACM ARN) or a brand-new import. + /// This replaced a brittle "alias length >= 20" heuristic that could misclassify a long + /// friendly alias as an ARN. + /// + public class IsAcmCertificateArnTests + { + [Theory] + [InlineData("arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012")] + [InlineData("arn:aws:acm:eu-west-2:000000000000:certificate/abcdef")] + [InlineData(" arn:aws:acm:us-east-1:123456789012:certificate/abc ")] // surrounding whitespace is tolerated + [InlineData("ARN:AWS:ACM:us-east-1:123456789012:certificate/abc")] // prefix match is case-insensitive + public void IsAcmCertificateArn_AcmCertificateArns_ReturnTrue(string alias) + { + Management.IsAcmCertificateArn(alias).Should().BeTrue(); + } + + [Theory] + [InlineData("prod-web-2025")] + [InlineData("my-friendly-cert-alias-2025")] // 27 chars: WOULD have passed the old "length >= 20" heuristic + [InlineData("")] + [InlineData(null)] + [InlineData(" ")] + [InlineData("arn:aws:iam::123456789012:role/MyRole")] // an ARN, but not for ACM + [InlineData("arn:aws:acm:us-east-1:123456789012:certificate-authority/abc")] // ACM PCA-style, not a certificate + public void IsAcmCertificateArn_NonAcmCertificateAliases_ReturnFalse(string alias) + { + Management.IsAcmCertificateArn(alias).Should().BeFalse(); + } + } +} diff --git a/aws-acm-orchestrator.Tests/aws-acm-orchestrator.Tests.csproj b/aws-acm-orchestrator.Tests/aws-acm-orchestrator.Tests.csproj new file mode 100644 index 0000000..a0f2754 --- /dev/null +++ b/aws-acm-orchestrator.Tests/aws-acm-orchestrator.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + disable + disable + false + Keyfactor.Extensions.Orchestrator.Aws.Acm.Tests + Keyfactor.Extensions.Orchestrator.Aws.Acm.Tests + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/aws-acm-orchestrator.sln b/aws-acm-orchestrator.sln index 5cc928c..bc18a90 100644 --- a/aws-acm-orchestrator.sln +++ b/aws-acm-orchestrator.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.30717.126 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aws-acm-orchestrator", "aws-acm-orchestrator\aws-acm-orchestrator.csproj", "{9BEDC094-06DC-40C6-B5DF-88B4960D4DCF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aws-acm-orchestrator.Tests", "aws-acm-orchestrator.Tests\aws-acm-orchestrator.Tests.csproj", "{A1B2C3D4-E5F6-47A8-9B0C-1D2E3F4A5B6C}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{515E06CB-9355-43EA-AF70-27F066A5C3AF}" ProjectSection(SolutionItems) = preProject integration-manifest.json = integration-manifest.json @@ -45,6 +47,10 @@ Global {9BEDC094-06DC-40C6-B5DF-88B4960D4DCF}.Debug|Any CPU.Build.0 = Debug|Any CPU {9BEDC094-06DC-40C6-B5DF-88B4960D4DCF}.Release|Any CPU.ActiveCfg = Release|Any CPU {9BEDC094-06DC-40C6-B5DF-88B4960D4DCF}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F4A5B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F4A5B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F4A5B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F4A5B6C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/aws-acm-orchestrator/Jobs/Inventory.cs b/aws-acm-orchestrator/Jobs/Inventory.cs index 90467a4..2266bcb 100644 --- a/aws-acm-orchestrator/Jobs/Inventory.cs +++ b/aws-acm-orchestrator/Jobs/Inventory.cs @@ -1,16 +1,10 @@ -// Copyright 2025 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. + +// Copyright 2026 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. using Amazon.CertificateManager; using Amazon.CertificateManager.Model; diff --git a/aws-acm-orchestrator/Jobs/Management.cs b/aws-acm-orchestrator/Jobs/Management.cs index deab124..a577685 100644 --- a/aws-acm-orchestrator/Jobs/Management.cs +++ b/aws-acm-orchestrator/Jobs/Management.cs @@ -1,16 +1,10 @@ -// Copyright 2025 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. + +// Copyright 2026 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. using Amazon.CertificateManager; using Amazon.CertificateManager.Model; @@ -134,10 +128,10 @@ internal JobResult PerformAddition(AwsExtensionCredential awsCredentials, Manage if (!string.IsNullOrWhiteSpace(config.JobCertificate.PrivateKeyPassword)) // This is a PFX Entry { Logger.LogTrace($"Found Private Key password."); - if (!string.IsNullOrWhiteSpace(config.JobCertificate.Alias)) + if (IsAcmCertificateArn(config.JobCertificate.Alias)) { - // Alias is specified, this is a replace / renewal - Logger.LogDebug($"Alias specified, validating existing cert can be renewed / replaced: {config.JobCertificate.Alias}"); + // Alias is an ACM certificate ARN, so this is a replace / renewal of an existing cert + Logger.LogDebug($"ACM ARN supplied as alias, validating existing cert can be renewed / replaced: {config.JobCertificate.Alias}"); // ARN Provided, Verify It is Not A PCA/Amazon Issued Cert DescribeCertificateResponse DescribeCertificateResponse = AsyncHelpers.RunSync(() => AcmClient.DescribeCertificateAsync(config.JobCertificate.Alias)); Logger.LogTrace($"DescribeCertificateResponse JSON: {JsonConvert.SerializeObject(DescribeCertificateResponse)}"); @@ -213,7 +207,7 @@ internal JobResult PerformAddition(AwsExtensionCredential awsCredentials, Manage CertificateChain = chainStream }; - icr.CertificateArn = config.JobCertificate.Alias?.Length >= 20 ? config.JobCertificate.Alias.Trim() : null; //If an arn is provided, use it, this will perform a renewal/replace + icr.CertificateArn = IsAcmCertificateArn(config.JobCertificate.Alias) ? config.JobCertificate.Alias.Trim() : null; //If an ACM certificate ARN is provided, reimport in place (renewal/replace); otherwise import as a new certificate Logger.LogTrace($"Certificate arn {icr.CertificateArn}"); if (icr.CertificateArn == null && acmTags != null && acmTags.Count > 0) @@ -352,13 +346,25 @@ internal JobResult PerformRemoval(AwsExtensionCredential awsCredentials, Managem } } - private static MemoryStream GetChain(Pkcs12Store store, string alias) + internal static MemoryStream GetChain(Pkcs12Store store, string alias) { string ccs = ""; X509CertificateEntry[] chain = store.GetCertificateChain(alias); - foreach (X509CertificateEntry chainEntry in chain) + // BouncyCastle returns the chain with the leaf/end-entity certificate as element [0], + // followed by any intermediates (and root). The leaf is already sent separately as the + // Certificate body of the ImportCertificateRequest, so it must NOT be repeated here; + // ACM's CertificateChain is expected to contain only the intermediate (and root) certs. + // Including the leaf caused it to appear twice within the published certificate's chain. + if (chain == null || chain.Length <= 1) + { + // Only the leaf is present (no intermediates) - omit the chain entirely rather than + // sending an empty value, which ACM may reject as an unparseable certificate chain. + return null; + } + + foreach (X509CertificateEntry chainEntry in chain.Skip(1)) { ccs += certStart + _pemify(Convert.ToBase64String(chainEntry.Certificate.GetEncoded())) + certEnd + "\n"; } @@ -374,6 +380,22 @@ private static MemoryStream CertStringToStream(string certString) return new MemoryStream(certBytes); } + /// + /// Determines whether the supplied alias is an AWS Certificate Manager certificate ARN + /// (e.g. arn:aws:acm:<region>:<account>:certificate/<id>). When it is, the certificate + /// already exists in ACM and an Add job should reimport in place (renewal/replace); + /// otherwise the certificate is imported as a new one and ACM assigns a fresh ARN. + /// Replaces an earlier alias-length heuristic that could misclassify a long friendly alias. + /// + internal static bool IsAcmCertificateArn(string alias) + { + if (string.IsNullOrWhiteSpace(alias)) return false; + + string trimmed = alias.Trim(); + return trimmed.StartsWith("arn:aws:acm:", StringComparison.OrdinalIgnoreCase) + && trimmed.Contains(":certificate/"); + } + private List ParseACMTags(Dictionary jobProperties) { List acmTags = new List(); diff --git a/aws-acm-orchestrator/aws-acm-orchestrator.csproj b/aws-acm-orchestrator/aws-acm-orchestrator.csproj index 5ebfe7f..8869e49 100644 --- a/aws-acm-orchestrator/aws-acm-orchestrator.csproj +++ b/aws-acm-orchestrator/aws-acm-orchestrator.csproj @@ -10,20 +10,25 @@ - - - - - - + + + + + + + + + + + - + - + - + Always From 236bd0f8846db5db40144f8b194740771c2977f1 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 10 Jun 2026 18:22:44 +0000 Subject: [PATCH 3/3] Update generated docs --- README.md | 115 ++++++++- .../bash/curl_create_store_types.sh | 225 ++++++++++++++++++ .../bash/kfutil_create_store_types.sh | 28 +++ .../powershell/kfutil_create_store_types.ps1 | 29 +++ .../restmethod_create_store_types.ps1 | 219 +++++++++++++++++ 5 files changed, 612 insertions(+), 4 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/README.md b/README.md index ab4f09f..8f3a2fe 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,105 @@ the Keyfactor Command Portal ![AWS-ACM-v3 Custom Fields Tab](docsource/images/AWS-ACM-v3-custom-fields-store-type-dialog.png) + + ###### Use Default SDK Auth + A switch to enable the store to use Default SDK credentials + + ![AWS-ACM-v3 Custom Field - UseDefaultSdkAuth](docsource/images/AWS-ACM-v3-custom-field-UseDefaultSdkAuth-dialog.png) + ![AWS-ACM-v3 Custom Field - UseDefaultSdkAuth](docsource/images/AWS-ACM-v3-custom-field-UseDefaultSdkAuth-validation-options-dialog.png) + + + + ###### Assume new Role using Default SDK Auth + A switch to enable the store to assume a new Role when using Default SDK credentials + + ![AWS-ACM-v3 Custom Field - DefaultSdkAssumeRole](docsource/images/AWS-ACM-v3-custom-field-DefaultSdkAssumeRole-dialog.png) + ![AWS-ACM-v3 Custom Field - DefaultSdkAssumeRole](docsource/images/AWS-ACM-v3-custom-field-DefaultSdkAssumeRole-validation-options-dialog.png) + + + + ###### Use OAuth 2.0 Provider + A switch to enable the store to use an OAuth provider workflow to authenticate with AWS + + ![AWS-ACM-v3 Custom Field - UseOAuth](docsource/images/AWS-ACM-v3-custom-field-UseOAuth-dialog.png) + ![AWS-ACM-v3 Custom Field - UseOAuth](docsource/images/AWS-ACM-v3-custom-field-UseOAuth-validation-options-dialog.png) + + + + ###### OAuth Scope + This is the OAuth Scope needed for Okta OAuth, defined in Okta + + ![AWS-ACM-v3 Custom Field - OAuthScope](docsource/images/AWS-ACM-v3-custom-field-OAuthScope-dialog.png) + ![AWS-ACM-v3 Custom Field - OAuthScope](docsource/images/AWS-ACM-v3-custom-field-OAuthScope-validation-options-dialog.png) + + + + ###### OAuth Grant Type + In OAuth 2.0, the term 'grant type' refers to the way an application gets an access token. In Okta this is `client_credentials` + + ![AWS-ACM-v3 Custom Field - OAuthGrantType](docsource/images/AWS-ACM-v3-custom-field-OAuthGrantType-dialog.png) + ![AWS-ACM-v3 Custom Field - OAuthGrantType](docsource/images/AWS-ACM-v3-custom-field-OAuthGrantType-validation-options-dialog.png) + + + + ###### OAuth Url + An optional parameter sts:ExternalId to pass with Assume Role calls + + ![AWS-ACM-v3 Custom Field - OAuthUrl](docsource/images/AWS-ACM-v3-custom-field-OAuthUrl-dialog.png) + ![AWS-ACM-v3 Custom Field - OAuthUrl](docsource/images/AWS-ACM-v3-custom-field-OAuthUrl-validation-options-dialog.png) + + + + ###### OAuth Client ID + The Client ID for OAuth. + + ![AWS-ACM-v3 Custom Field - OAuthClientId](docsource/images/AWS-ACM-v3-custom-field-OAuthClientId-dialog.png) + ![AWS-ACM-v3 Custom Field - OAuthClientId](docsource/images/AWS-ACM-v3-custom-field-OAuthClientId-validation-options-dialog.png) + + + + ###### OAuth Client Secret + The Client Secret for OAuth. + + ![AWS-ACM-v3 Custom Field - OAuthClientSecret](docsource/images/AWS-ACM-v3-custom-field-OAuthClientSecret-dialog.png) + ![AWS-ACM-v3 Custom Field - OAuthClientSecret](docsource/images/AWS-ACM-v3-custom-field-OAuthClientSecret-validation-options-dialog.png) + + + + ###### Use IAM User Auth + A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS + + ![AWS-ACM-v3 Custom Field - UseIAM](docsource/images/AWS-ACM-v3-custom-field-UseIAM-dialog.png) + ![AWS-ACM-v3 Custom Field - UseIAM](docsource/images/AWS-ACM-v3-custom-field-UseIAM-validation-options-dialog.png) + + + + ###### IAM User Access Key + The AWS Access Key for an IAM User + + ![AWS-ACM-v3 Custom Field - IAMUserAccessKey](docsource/images/AWS-ACM-v3-custom-field-IAMUserAccessKey-dialog.png) + ![AWS-ACM-v3 Custom Field - IAMUserAccessKey](docsource/images/AWS-ACM-v3-custom-field-IAMUserAccessKey-validation-options-dialog.png) + + + + ###### IAM User Access Secret + The AWS Access Secret for an IAM User. + + ![AWS-ACM-v3 Custom Field - IAMUserAccessSecret](docsource/images/AWS-ACM-v3-custom-field-IAMUserAccessSecret-dialog.png) + ![AWS-ACM-v3 Custom Field - IAMUserAccessSecret](docsource/images/AWS-ACM-v3-custom-field-IAMUserAccessSecret-validation-options-dialog.png) + + + + ###### sts:ExternalId + An optional parameter sts:ExternalId to pass with Assume Role calls + + ![AWS-ACM-v3 Custom Field - ExternalId](docsource/images/AWS-ACM-v3-custom-field-ExternalId-dialog.png) + ![AWS-ACM-v3 Custom Field - ExternalId](docsource/images/AWS-ACM-v3-custom-field-ExternalId-validation-options-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 | @@ -296,21 +395,29 @@ the Keyfactor Command Portal ![AWS-ACM-v3 Entry Parameters Tab](docsource/images/AWS-ACM-v3-entry-parameters-store-type-dialog.png) + + ##### ACM Tags + The optional ACM tags that should be assigned to the certificate. Multiple name/value pairs may be entered in the format of `Name1=Value1,Name2=Value2,...,NameN=ValueN` + + ![AWS-ACM-v3 Entry Parameter - ACM Tags](docsource/images/AWS-ACM-v3-entry-parameters-store-type-dialog-ACM Tags.png) + ![AWS-ACM-v3 Entry Parameter - ACM Tags](docsource/images/AWS-ACM-v3-entry-parameters-store-type-dialog-ACM Tags-validation-options.png) + + + ## Installation 1. **Download the latest AWS Certificate Manager (ACM) Universal Orchestrator extension from GitHub.** - Navigate to the [AWS Certificate Manager (ACM) Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/aws-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 [AWS Certificate Manager (ACM) Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/aws-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` | `aws-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/scripts/store_types/bash/curl_create_store_types.sh b/scripts/store_types/bash/curl_create_store_types.sh new file mode 100755 index 0000000..3bf2861 --- /dev/null +++ b/scripts/store_types/bash/curl_create_store_types.sh @@ -0,0 +1,225 @@ +#!/usr/bin/env bash + +# Creates all 1 store types via the Keyfactor Command REST API using curl. +# +# 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 [ -z "${KEYFACTOR_HOSTNAME}" ]; then + echo "ERROR: KEYFACTOR_HOSTNAME is required" + exit 1 +fi + +BASE_URL="https://${KEYFACTOR_HOSTNAME}/keyfactorapi" + +# --------------------------------------------------------------------------- +# Resolve auth +# --------------------------------------------------------------------------- +if [ -n "${KEYFACTOR_AUTH_ACCESS_TOKEN}" ]; then + BEARER_TOKEN="${KEYFACTOR_AUTH_ACCESS_TOKEN}" +elif [ -n "${KEYFACTOR_AUTH_CLIENT_ID}" ] && [ -n "${KEYFACTOR_AUTH_CLIENT_SECRET}" ] && [ -n "${KEYFACTOR_AUTH_TOKEN_URL}" ]; then + echo "Fetching OAuth token..." + BEARER_TOKEN=$(curl -s -X POST "${KEYFACTOR_AUTH_TOKEN_URL}" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "client_id=${KEYFACTOR_AUTH_CLIENT_ID}" \ + --data-urlencode "client_secret=${KEYFACTOR_AUTH_CLIENT_SECRET}" | jq -r '.access_token') + if [ -z "${BEARER_TOKEN}" ] || [ "${BEARER_TOKEN}" = "null" ]; then + echo "ERROR: Failed to fetch OAuth token from ${KEYFACTOR_AUTH_TOKEN_URL}" + exit 1 + fi +elif [ -n "${KEYFACTOR_USERNAME}" ] && [ -n "${KEYFACTOR_PASSWORD}" ] && [ -n "${KEYFACTOR_DOMAIN}" ]; then + BEARER_TOKEN="" +else + echo "ERROR: Authentication required. Set one of:" + echo " KEYFACTOR_AUTH_ACCESS_TOKEN" + echo " KEYFACTOR_AUTH_CLIENT_ID + KEYFACTOR_AUTH_CLIENT_SECRET + KEYFACTOR_AUTH_TOKEN_URL" + echo " KEYFACTOR_USERNAME + KEYFACTOR_PASSWORD + KEYFACTOR_DOMAIN" + exit 1 +fi + +if [ -n "${BEARER_TOKEN}" ]; then + CURL_AUTH=("-H" "Authorization: Bearer ${BEARER_TOKEN}") +else + CURL_AUTH=("-u" "${KEYFACTOR_USERNAME}@${KEYFACTOR_DOMAIN}:${KEYFACTOR_PASSWORD}") +fi + +create_store_type() { + local name="$1" + local body="$2" + echo "Creating ${name} store type..." + response=$(curl -s -o /dev/null -w "%{http_code}" \ + -X POST "${BASE_URL}/certificatestoretypes" \ + -H "Content-Type: application/json" \ + -H "x-keyfactor-requested-with: APIClient" \ + "${CURL_AUTH[@]}" \ + -d "${body}") + if [ "$response" = "200" ] || [ "$response" = "201" ]; then + echo " OK (HTTP ${response})" + else + echo " FAILED (HTTP ${response})" + fi +} + +# --------------------------------------------------------------------------- +# AWS-ACM-v3 — This is a full AWS ARN specifying a Role. This is the Role that will be assumed in any Auth scenario performing Assume Role. This will dictate what certificates are usable by the orchestrator. A preceding [profile] name should be included if a Credential Profile is to be used in Default Sdk Auth. +# --------------------------------------------------------------------------- +create_store_type "AWS-ACM-v3" '{ + "Name": "AWS Certificate Manager v3", + "ShortName": "AWS-ACM-v3", + "Capability": "AWS-ACM-v3", + "LocalStore": false, + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "Name": "UseDefaultSdkAuth", + "DisplayName": "Use Default SDK Auth", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true, + "IsPAMEligible": false + }, + { + "Name": "DefaultSdkAssumeRole", + "DisplayName": "Assume new Role using Default SDK Auth", + "Type": "Bool", + "DependsOn": "UseDefaultSdkAuth", + "DefaultValue": "false", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "UseOAuth", + "DisplayName": "Use OAuth 2.0 Provider", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true, + "IsPAMEligible": false + }, + { + "Name": "OAuthScope", + "DisplayName": "OAuth Scope", + "Type": "String", + "DependsOn": "UseOAuth", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "OAuthGrantType", + "DisplayName": "OAuth Grant Type", + "Type": "String", + "DependsOn": "UseOAuth", + "DefaultValue": "client_credentials", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "OAuthUrl", + "DisplayName": "OAuth Url", + "Type": "String", + "DependsOn": "UseOAuth", + "DefaultValue": "https://***/oauth2/default/v1/token", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "OAuthClientId", + "DisplayName": "OAuth Client ID", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "OAuthClientSecret", + "DisplayName": "OAuth Client Secret", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "UseIAM", + "DisplayName": "Use IAM User Auth", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true, + "IsPAMEligible": false + }, + { + "Name": "IAMUserAccessKey", + "DisplayName": "IAM User Access Key", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "IAMUserAccessSecret", + "DisplayName": "IAM User Access Secret", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "ExternalId", + "DisplayName": "sts:ExternalId", + "Type": "String", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": false + } + ], + "EntryParameters": [ + { + "Name": "ACM Tags", + "DisplayName": "ACM Tags", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + }, + "Description": "The optional ACM tags that should be assigned to the certificate. Multiple name/value pairs may be entered in the format of `Name1=Value1,Name2=Value2,...,NameN=ValueN`" + } + ], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "PrivateKeyAllowed": "Required", + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Optional", + "StorePathDescription": "A single specified AWS Region the store will operate in. Additional regions should get their own store defined." +}' + + +echo "Completed." diff --git a/scripts/store_types/bash/kfutil_create_store_types.sh b/scripts/store_types/bash/kfutil_create_store_types.sh new file mode 100755 index 0000000..9624215 --- /dev/null +++ b/scripts/store_types/bash/kfutil_create_store_types.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# 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. + +if ! command -v kfutil &> /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 "AWS-ACM-v3" + +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..cde2f4b --- /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 "AWS-ACM-v3" + +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..1987078 --- /dev/null +++ b/scripts/store_types/powershell/restmethod_create_store_types.ps1 @@ -0,0 +1,219 @@ +# 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)" + } +} + +# --------------------------------------------------------------------------- +# AWS-ACM-v3 — This is a full AWS ARN specifying a Role. This is the Role that will be assumed in any Auth scenario performing Assume Role. This will dictate what certificates are usable by the orchestrator. A preceding [profile] name should be included if a Credential Profile is to be used in Default Sdk Auth. +# --------------------------------------------------------------------------- +New-StoreType "AWS-ACM-v3" @' +{ + "Name": "AWS Certificate Manager v3", + "ShortName": "AWS-ACM-v3", + "Capability": "AWS-ACM-v3", + "LocalStore": false, + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "Name": "UseDefaultSdkAuth", + "DisplayName": "Use Default SDK Auth", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true, + "IsPAMEligible": false + }, + { + "Name": "DefaultSdkAssumeRole", + "DisplayName": "Assume new Role using Default SDK Auth", + "Type": "Bool", + "DependsOn": "UseDefaultSdkAuth", + "DefaultValue": "false", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "UseOAuth", + "DisplayName": "Use OAuth 2.0 Provider", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true, + "IsPAMEligible": false + }, + { + "Name": "OAuthScope", + "DisplayName": "OAuth Scope", + "Type": "String", + "DependsOn": "UseOAuth", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "OAuthGrantType", + "DisplayName": "OAuth Grant Type", + "Type": "String", + "DependsOn": "UseOAuth", + "DefaultValue": "client_credentials", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "OAuthUrl", + "DisplayName": "OAuth Url", + "Type": "String", + "DependsOn": "UseOAuth", + "DefaultValue": "https://***/oauth2/default/v1/token", + "Required": false, + "IsPAMEligible": false + }, + { + "Name": "OAuthClientId", + "DisplayName": "OAuth Client ID", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "OAuthClientSecret", + "DisplayName": "OAuth Client Secret", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "UseIAM", + "DisplayName": "Use IAM User Auth", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true, + "IsPAMEligible": false + }, + { + "Name": "IAMUserAccessKey", + "DisplayName": "IAM User Access Key", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "IAMUserAccessSecret", + "DisplayName": "IAM User Access Secret", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": true + }, + { + "Name": "ExternalId", + "DisplayName": "sts:ExternalId", + "Type": "String", + "DependsOn": "", + "DefaultValue": "", + "Required": false, + "IsPAMEligible": false + } + ], + "EntryParameters": [ + { + "Name": "ACM Tags", + "DisplayName": "ACM Tags", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + }, + "Description": "The optional ACM tags that should be assigned to the certificate. Multiple name/value pairs may be entered in the format of `Name1=Value1,Name2=Value2,...,NameN=ValueN`" + } + ], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "PrivateKeyAllowed": "Required", + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Optional", + "StorePathDescription": "A single specified AWS Region the store will operate in. Additional regions should get their own store defined." +} +'@ + + +Write-Host "Completed."