diff --git a/.github/workflows/keyfactor-release-workflow.yml b/.github/workflows/keyfactor-release-workflow.yml
index 64919a4..45eec87 100644
--- a/.github/workflows/keyfactor-release-workflow.yml
+++ b/.github/workflows/keyfactor-release-workflow.yml
@@ -11,10 +11,9 @@ on:
jobs:
call-starter-workflow:
- uses: keyfactor/actions/.github/workflows/starter.yml@v3
+ uses: keyfactor/actions/.github/workflows/starter.yml@v5
secrets:
token: ${{ secrets.V2BUILDTOKEN}}
- APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
scan_token: ${{ secrets.SAST_TOKEN }}
diff --git a/AwsSecretsManager.Tests/CertUtilitiesConvertPfxToPemTests.cs b/AwsSecretsManager.Tests/CertUtilitiesConvertPfxToPemTests.cs
index 2b644a7..30cf1b9 100644
--- a/AwsSecretsManager.Tests/CertUtilitiesConvertPfxToPemTests.cs
+++ b/AwsSecretsManager.Tests/CertUtilitiesConvertPfxToPemTests.cs
@@ -72,6 +72,66 @@ public void ConvertPfxToPem_ChainPfx_ReturnsLeafOnly()
certs[0].Subject.Should().Be(leafSubject);
}
+ [Fact]
+ public void ConvertPfxToPem_IncludeChainFalse_ReturnsLeafOnly()
+ {
+ var pfx = TestCertFactory.CreateChainPfxBase64(out var leafSubject, out _);
+
+ var pem = CertUtilities.ConvertPfxToPem(pfx, TestCertFactory.Password, includeChain: false);
+
+ var certs = new X509Certificate2Collection();
+ certs.ImportFromPem(pem);
+
+ certs.Count.Should().Be(1);
+ certs[0].Subject.Should().Be(leafSubject);
+ }
+
+ [Fact]
+ public void ConvertPfxToPem_IncludeChain_ReturnsLeafThenChainThenKey()
+ {
+ var pfx = TestCertFactory.CreateChainPfxBase64(out var leafSubject, out var rootSubject);
+
+ var pem = CertUtilities.ConvertPfxToPem(pfx, TestCertFactory.Password, includeChain: true);
+
+ var certs = new X509Certificate2Collection();
+ certs.ImportFromPem(pem);
+
+ certs.Count.Should().Be(2, "the leaf and its issuer are both included");
+ certs[0].Subject.Should().Be(leafSubject, "the leaf comes first");
+ certs[1].Subject.Should().Be(rootSubject);
+
+ pem.IndexOf("-----BEGIN PRIVATE KEY-----", StringComparison.Ordinal).Should().BeGreaterThan(
+ pem.LastIndexOf("-----END CERTIFICATE-----", StringComparison.Ordinal),
+ "the private key follows the full chain");
+ }
+
+ [Fact]
+ public void ConvertPfxToPem_IncludeChain_KeyMatchesLeaf()
+ {
+ var pfx = TestCertFactory.CreateChainPfxBase64(out var leafSubject, out _);
+
+ var pem = CertUtilities.ConvertPfxToPem(pfx, TestCertFactory.Password, includeChain: true);
+
+ // CreateFromPem loads the first certificate and throws if the key does not match it.
+ using var rebuilt = X509Certificate2.CreateFromPem(pem, pem);
+ rebuilt.HasPrivateKey.Should().BeTrue();
+ rebuilt.Subject.Should().Be(leafSubject);
+ }
+
+ [Fact]
+ public void ConvertPfxToPem_IncludeChain_SelfSigned_ReturnsSingleCert()
+ {
+ var pfx = TestCertFactory.CreateSelfSignedRsaPfxBase64("CN=no-chain");
+
+ var pem = CertUtilities.ConvertPfxToPem(pfx, TestCertFactory.Password, includeChain: true);
+
+ var certs = new X509Certificate2Collection();
+ certs.ImportFromPem(pem);
+
+ certs.Count.Should().Be(1, "a PFX with no issuer certs has nothing to add");
+ pem.Should().Contain("-----BEGIN PRIVATE KEY-----");
+ }
+
[Fact]
public void ConvertPfxToPem_InvalidBase64_Throws()
{
diff --git a/AwsSecretsManager.Tests/ClientPemIncludeChainWriteTests.cs b/AwsSecretsManager.Tests/ClientPemIncludeChainWriteTests.cs
new file mode 100644
index 0000000..90a4314
--- /dev/null
+++ b/AwsSecretsManager.Tests/ClientPemIncludeChainWriteTests.cs
@@ -0,0 +1,82 @@
+// 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
+
+using System.Reflection;
+using System.Security.Cryptography.X509Certificates;
+using Amazon.SecretsManager.Model;
+using FluentAssertions;
+using Xunit;
+
+namespace Keyfactor.Extensions.Orchestrators.AwsSecretsManager.Tests
+{
+ ///
+ /// Exercises the client's single-PEM write generators (AWSSMPEM without SeparatePrivateKey)
+ /// to verify the IncludeChain store property controls whether the issuer chain is written.
+ /// The generate methods are private and AWS-free, so they are invoked via reflection.
+ ///
+ public class ClientPemIncludeChainWriteTests
+ {
+ [Theory]
+ [InlineData("GenerateAddSecretPemRequest")]
+ [InlineData("GenerateUpdateSecretPemRequest")]
+ public void PemRequest_IncludeChainFalse_WritesLeafOnly(string methodName)
+ {
+ var jp = BuildJobParameters(includeChain: false, out var leafSubject, out _);
+
+ var secretString = GetSecretString(methodName, jp);
+
+ var certs = new X509Certificate2Collection();
+ certs.ImportFromPem(secretString);
+ certs.Count.Should().Be(1, "the default format is unchanged: leaf and key only");
+ certs[0].Subject.Should().Be(leafSubject);
+ secretString.Should().Contain("-----BEGIN PRIVATE KEY-----");
+ }
+
+ [Theory]
+ [InlineData("GenerateAddSecretPemRequest")]
+ [InlineData("GenerateUpdateSecretPemRequest")]
+ public void PemRequest_IncludeChainTrue_WritesLeafAndChain(string methodName)
+ {
+ var jp = BuildJobParameters(includeChain: true, out var leafSubject, out var rootSubject);
+
+ var secretString = GetSecretString(methodName, jp);
+
+ var certs = new X509Certificate2Collection();
+ certs.ImportFromPem(secretString);
+ certs.Count.Should().Be(2);
+ certs[0].Subject.Should().Be(leafSubject);
+ certs[1].Subject.Should().Be(rootSubject);
+ secretString.Should().Contain("-----BEGIN PRIVATE KEY-----");
+ }
+
+ // ── helpers ────────────────────────────────────────────────────────
+
+ private static AwsSecretsManagerJobParameters BuildJobParameters(bool includeChain, out string leafSubject, out string rootSubject)
+ {
+ var jp = new AwsSecretsManagerJobParameters { StoreType = "AWSSMPEM" };
+ jp.StoreProperties.IncludeChain = includeChain;
+ jp.CertProperties.Alias = "chain-write-test";
+ jp.CertProperties.Contents = TestCertFactory.CreateChainPfxBase64(out leafSubject, out rootSubject);
+ jp.CertProperties.PrivateKeyPassword = TestCertFactory.Password;
+ return jp;
+ }
+
+ private static string GetSecretString(string methodName, AwsSecretsManagerJobParameters jp)
+ {
+ var client = new AwsSecretsManagerClient();
+ var m = typeof(AwsSecretsManagerClient).GetMethod(
+ methodName, BindingFlags.NonPublic | BindingFlags.Instance);
+ m.Should().NotBeNull($"{methodName} must be reachable via reflection");
+
+ var result = m!.Invoke(client, new object[] { jp });
+ return result switch
+ {
+ CreateSecretRequest c => c.SecretString,
+ UpdateSecretRequest u => u.SecretString,
+ _ => throw new System.InvalidOperationException($"unexpected return type from {methodName}")
+ };
+ }
+ }
+}
diff --git a/AwsSecretsManager.Tests/ClientUpdateSecretTagsTests.cs b/AwsSecretsManager.Tests/ClientUpdateSecretTagsTests.cs
new file mode 100644
index 0000000..576ce97
--- /dev/null
+++ b/AwsSecretsManager.Tests/ClientUpdateSecretTagsTests.cs
@@ -0,0 +1,115 @@
+// 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
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Amazon.SecretsManager;
+using Amazon.SecretsManager.Model;
+using FluentAssertions;
+using Moq;
+using Xunit;
+
+namespace Keyfactor.Extensions.Orchestrators.AwsSecretsManager.Tests
+{
+ ///
+ /// Verifies UpdateSecretTagsAsync merges tags: keys provided by Command replace existing
+ /// values, while tags not provided (e.g. added by other tooling) are left on the secret.
+ ///
+ public class ClientUpdateSecretTagsTests
+ {
+ [Fact]
+ public async Task UpdateSecretTags_OverlappingKey_UntagsOnlyOverlappingKeys()
+ {
+ var awsMock = MockAwsWithCurrentTags(
+ new Tag { Key = "Environment", Value = "Dev" },
+ new Tag { Key = "CostCenter", Value = "1234" }); // not managed by Command
+
+ await InvokeUpdateSecretTags(awsMock.Object, "my-cert",
+ new List { new Tag { Key = "Environment", Value = "Prod" } });
+
+ awsMock.Verify(c => c.UntagResourceAsync(
+ It.Is(r =>
+ r.SecretId == "my-cert" &&
+ r.TagKeys.Count == 1 &&
+ r.TagKeys[0] == "Environment"),
+ It.IsAny()), Times.Once);
+
+ awsMock.Verify(c => c.TagResourceAsync(
+ It.Is(r =>
+ r.Tags.Count == 1 &&
+ r.Tags[0].Key == "Environment" &&
+ r.Tags[0].Value == "Prod"),
+ It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task UpdateSecretTags_NoOverlap_DoesNotUntag()
+ {
+ var awsMock = MockAwsWithCurrentTags(new Tag { Key = "CostCenter", Value = "1234" });
+
+ await InvokeUpdateSecretTags(awsMock.Object, "my-cert",
+ new List { new Tag { Key = "Environment", Value = "Prod" } });
+
+ awsMock.Verify(c => c.UntagResourceAsync(
+ It.IsAny(), It.IsAny()), Times.Never);
+ awsMock.Verify(c => c.TagResourceAsync(
+ It.IsAny(), It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task UpdateSecretTags_NeverUntagsKeysNotProvided()
+ {
+ var awsMock = MockAwsWithCurrentTags(
+ new Tag { Key = "managedBy", Value = "Keyfactor" },
+ new Tag { Key = "Environment", Value = "Dev" },
+ new Tag { Key = "Owner", Value = "team-a" },
+ new Tag { Key = "aws:cloudformation:stack-name", Value = "stack" });
+
+ await InvokeUpdateSecretTags(awsMock.Object, "my-cert", new List
+ {
+ new Tag { Key = "managedBy", Value = "Keyfactor" },
+ new Tag { Key = "Environment", Value = "Prod" }
+ });
+
+ awsMock.Verify(c => c.UntagResourceAsync(
+ It.Is(r =>
+ r.TagKeys.OrderBy(k => k).SequenceEqual(new[] { "Environment", "managedBy" })),
+ It.IsAny()), Times.Once);
+ }
+
+ // ── helpers ────────────────────────────────────────────────────────
+
+ private static Mock MockAwsWithCurrentTags(params Tag[] currentTags)
+ {
+ var awsMock = new Mock();
+ awsMock.Setup(c => c.DescribeSecretAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(new DescribeSecretResponse { Tags = currentTags.ToList() });
+ awsMock.Setup(c => c.UntagResourceAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(new UntagResourceResponse());
+ awsMock.Setup(c => c.TagResourceAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(new TagResourceResponse());
+ return awsMock;
+ }
+
+ private static Task InvokeUpdateSecretTags(IAmazonSecretsManager aws, string secretName, List newTags)
+ {
+ var client = new AwsSecretsManagerClient();
+
+ var prop = typeof(AwsSecretsManagerClient).GetProperty(
+ "_secretsManagerClient", BindingFlags.NonPublic | BindingFlags.Instance);
+ prop.Should().NotBeNull("the AWS client must be injectable via reflection");
+ prop!.SetValue(client, aws);
+
+ var m = typeof(AwsSecretsManagerClient).GetMethod(
+ "UpdateSecretTagsAsync", BindingFlags.NonPublic | BindingFlags.Instance);
+ m.Should().NotBeNull("UpdateSecretTagsAsync must be reachable via reflection");
+
+ return (Task)m!.Invoke(client, new object[] { secretName, newTags })!;
+ }
+ }
+}
diff --git a/AwsSecretsManager.Tests/InventorySeparateKeyTests.cs b/AwsSecretsManager.Tests/InventorySeparateKeyTests.cs
index 9bc2ff8..d57c35a 100644
--- a/AwsSecretsManager.Tests/InventorySeparateKeyTests.cs
+++ b/AwsSecretsManager.Tests/InventorySeparateKeyTests.cs
@@ -87,6 +87,26 @@ public void ConvertSecretsPem_JsonSplitWithTags_SerializesTagsAsJsonString()
tags!["Environment"].Should().Be("Prod");
}
+ [Fact]
+ public void ConvertSecretsPem_PlainPemWithChain_InventoriesFullChain()
+ {
+ // The IncludeChain format: leaf, then issuer chain, then key in a single PEM string.
+ var pfx = TestCertFactory.CreateChainPfxBase64(out _, out _);
+ var secret = new AWSSecret
+ {
+ Name = "chained-pem",
+ SecretString = CertUtilities.ConvertPfxToPem(pfx, TestCertFactory.Password, includeChain: true),
+ Tags = new List()
+ };
+
+ var item = InvokeConvertPem(secret).Single();
+
+ item.Alias.Should().Be("chained-pem");
+ item.Certificates.Should().HaveCount(2);
+ item.UseChainLevel.Should().BeTrue();
+ item.PrivateKeyEntry.Should().BeTrue();
+ }
+
// ── helpers ────────────────────────────────────────────────────────
private static string BuildSeparateKeyJsonSecret(string subject)
diff --git a/AwsSecretsManager.Tests/JobBaseSeparatePrivateKeyTests.cs b/AwsSecretsManager.Tests/JobBaseSeparatePrivateKeyTests.cs
index 2397902..0edf39b 100644
--- a/AwsSecretsManager.Tests/JobBaseSeparatePrivateKeyTests.cs
+++ b/AwsSecretsManager.Tests/JobBaseSeparatePrivateKeyTests.cs
@@ -14,9 +14,9 @@
namespace Keyfactor.Extensions.Orchestrators.AwsSecretsManager.Tests
{
///
- /// Verifies that the SeparatePrivateKey store-type custom field is read out of the
- /// serialized store Properties JSON, tolerant of the various shapes Command may use
- /// to serialize a boolean custom field.
+ /// Verifies that the SeparatePrivateKey and IncludeChain store-type custom fields are read
+ /// out of the serialized store Properties JSON, tolerant of the various shapes Command may
+ /// use to serialize a boolean custom field.
///
public class JobBaseSeparatePrivateKeyTests
{
@@ -75,5 +75,46 @@ public void SeparatePrivateKey_ParsesFalse(string properties)
{
InvokeSetStoreProperties(properties).SeparatePrivateKey.Should().BeFalse();
}
+
+ // IncludeChain uses the same parsing path; an absent field must read as false so that
+ // stores created before the field existed keep the leaf-only format after upgrade.
+
+ [Fact]
+ public void IncludeChain_DefaultsToFalse_WhenAbsent()
+ {
+ InvokeSetStoreProperties("{\"SeparatePrivateKey\":\"false\"}").IncludeChain.Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData("{\"IncludeChain\":true}")]
+ [InlineData("{\"IncludeChain\":\"true\"}")]
+ [InlineData("{\"IncludeChain\":\"True\"}")]
+ [InlineData("{\"IncludeChain\":{\"value\":\"true\"}}")]
+ [InlineData("{\"includechain\":\"true\"}")] // case-insensitive name match
+ public void IncludeChain_ParsesTrue(string properties)
+ {
+ InvokeSetStoreProperties(properties).IncludeChain.Should().BeTrue();
+ }
+
+ [Theory]
+ [InlineData("{\"IncludeChain\":false}")]
+ [InlineData("{\"IncludeChain\":\"false\"}")]
+ [InlineData("{\"IncludeChain\":\"\"}")]
+ [InlineData("{\"IncludeChain\":null}")]
+ [InlineData("")]
+ [InlineData("not valid json")]
+ public void IncludeChain_ParsesFalse(string properties)
+ {
+ InvokeSetStoreProperties(properties).IncludeChain.Should().BeFalse();
+ }
+
+ [Fact]
+ public void IncludeChain_AndSeparatePrivateKey_AreParsedIndependently()
+ {
+ var props = InvokeSetStoreProperties("{\"SeparatePrivateKey\":\"true\",\"IncludeChain\":\"true\"}");
+
+ props.SeparatePrivateKey.Should().BeTrue();
+ props.IncludeChain.Should().BeTrue();
+ }
}
}
diff --git a/AwsSecretsManager.Tests/SecretLoggingTests.cs b/AwsSecretsManager.Tests/SecretLoggingTests.cs
new file mode 100644
index 0000000..d256339
--- /dev/null
+++ b/AwsSecretsManager.Tests/SecretLoggingTests.cs
@@ -0,0 +1,130 @@
+// 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
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Amazon.SecretsManager.Model;
+using FluentAssertions;
+using Keyfactor.Extensions.Orchestrators.AwsSecretsManager.Jobs;
+using Keyfactor.Extensions.Orchestrators.AwsSecretsManager.models;
+using Keyfactor.Orchestrators.Extensions;
+using Keyfactor.Orchestrators.Extensions.Interfaces;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Keyfactor.Extensions.Orchestrators.AwsSecretsManager.Tests
+{
+ ///
+ /// Guards against secret material (private keys, credentials) being written to the
+ /// orchestrator log.
+ ///
+ public class SecretLoggingTests
+ {
+ private const string SecretMarker = "SUPER-SECRET-KEY-MATERIAL";
+
+ [Fact]
+ public void ConvertSecretsPem_UnparseableSecret_DoesNotLogContents()
+ {
+ var logger = new RecordingLogger();
+ var secret = new AWSSecret
+ {
+ Name = "broken-cert",
+ SecretString = $"-----BEGIN PRIVATE KEY-----\n{SecretMarker}\n-----END PRIVATE KEY-----",
+ Tags = new List()
+ };
+
+ var (items, warnings) = InvokeConvertPem(logger, secret);
+
+ items.Should().BeEmpty();
+ warnings.Should().ContainSingle().Which.Should().Contain("broken-cert");
+ warnings.Single().Should().NotContain(SecretMarker);
+
+ logger.Messages.Should().Contain(m => m.Contains("broken-cert"),
+ "the warning should still identify which secret failed");
+ logger.Messages.Should().NotContain(m => m.Contains(SecretMarker),
+ "secret contents must never be logged");
+ }
+
+ [Fact]
+ public void RedactSecretStoreProperties_RedactsSecretFields()
+ {
+ var json = "{\"UseIAM\":\"true\",\"IAMUserAccessKey\":\"AKIA-" + SecretMarker + "\"," +
+ "\"IAMUserAccessSecret\":\"" + SecretMarker + "\"," +
+ "\"OAuthClientId\":{\"value\":\"" + SecretMarker + "\"}," +
+ "\"oauthclientsecret\":\"" + SecretMarker + "\"," +
+ "\"ExternalId\":\"ext-123\"}";
+
+ var redacted = InvokeRedact(json);
+
+ redacted.Should().NotContain(SecretMarker);
+ redacted.Should().Contain("\"UseIAM\":\"true\"", "non-secret fields are kept for troubleshooting");
+ redacted.Should().Contain("\"ExternalId\":\"ext-123\"");
+ }
+
+ [Theory]
+ [InlineData("not valid json " + SecretMarker)]
+ [InlineData("{\"IAMUserAccessSecret\":\"" + SecretMarker + "\"")] // truncated JSON
+ public void RedactSecretStoreProperties_UnparseableInput_ReturnsNothingFromInput(string input)
+ {
+ InvokeRedact(input).Should().NotContain(SecretMarker);
+ }
+
+ // ── helpers ────────────────────────────────────────────────────────
+
+ private static string InvokeRedact(string json)
+ {
+ var m = typeof(JobBase).GetMethod(
+ "RedactSecretStoreProperties", BindingFlags.NonPublic | BindingFlags.Static);
+ m.Should().NotBeNull("RedactSecretStoreProperties must be reachable via reflection");
+ return (string)m!.Invoke(null, new object[] { json })!;
+ }
+
+ private static (List, List) InvokeConvertPem(ILogger logger, params AWSSecret[] secrets)
+ {
+ var resolverMock = new Mock();
+ resolverMock.Setup(r => r.Resolve(It.IsAny())).Returns(s => s);
+
+ var inv = new TestableInventory(resolverMock.Object)
+ {
+ PublicJobParameters = new AwsSecretsManagerJobParameters { StoreType = "AWSSMPEM" }
+ };
+
+ var loggerProp = typeof(JobBase).GetProperty(
+ "_logger", BindingFlags.NonPublic | BindingFlags.Instance);
+ loggerProp.Should().NotBeNull("the job logger must be injectable via reflection");
+ loggerProp!.SetValue(inv, logger);
+
+ var m = typeof(Inventory).GetMethod(
+ "ConvertSecretsPem", BindingFlags.NonPublic | BindingFlags.Instance);
+ m.Should().NotBeNull("ConvertSecretsPem must be reachable via reflection");
+
+ var tuple = (System.Runtime.CompilerServices.ITuple)m!.Invoke(inv, new object[] { secrets.ToList() })!;
+ return ((List)tuple[0]!, (List)tuple[1]!);
+ }
+
+ private sealed class RecordingLogger : ILogger
+ {
+ public List Messages { get; } = new List();
+
+ public IDisposable BeginScope(TState state) where TState : notnull => NullScope.Instance;
+ public bool IsEnabled(LogLevel logLevel) => true;
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
+ Func formatter)
+ {
+ Messages.Add(formatter(state, exception));
+ }
+
+ private sealed class NullScope : IDisposable
+ {
+ public static readonly NullScope Instance = new NullScope();
+ public void Dispose() { }
+ }
+ }
+ }
+}
diff --git a/AwsSecretsManager/AwsSecretsManager.csproj b/AwsSecretsManager/AwsSecretsManager.csproj
index 979cc06..4f9587d 100644
--- a/AwsSecretsManager/AwsSecretsManager.csproj
+++ b/AwsSecretsManager/AwsSecretsManager.csproj
@@ -1,7 +1,7 @@
- net6.0;net8.0
+ net6.0;net8.0;net10.0disabletruetrue
diff --git a/AwsSecretsManager/AwsSecretsManagerClient.cs b/AwsSecretsManager/AwsSecretsManagerClient.cs
index cfdc286..c438ef8 100644
--- a/AwsSecretsManager/AwsSecretsManagerClient.cs
+++ b/AwsSecretsManager/AwsSecretsManagerClient.cs
@@ -438,7 +438,7 @@ private CreateSecretRequest GenerateAddSecretPemRequest(AwsSecretsManagerJobPara
try
{
- pemCert = CertUtilities.ConvertPfxToPem(jobParameters.CertProperties.Contents, jobParameters.CertProperties.PrivateKeyPassword);
+ pemCert = CertUtilities.ConvertPfxToPem(jobParameters.CertProperties.Contents, jobParameters.CertProperties.PrivateKeyPassword, jobParameters.StoreProperties.IncludeChain);
}
catch (Exception ex)
{
@@ -581,7 +581,7 @@ private async Task UpdateSecret(AwsSecretsManagerJobParameters jobParame
if (tags.Any())
{
- _logger.LogTrace($"tags are included, replacing existing tags with the {tags.Count} provided.");
+ _logger.LogTrace($"tags are included, applying the {tags.Count} provided (existing tags with the same keys are replaced; other tags are kept).");
await UpdateSecretTagsAsync(jobParameters.SecretName, tags);
}
@@ -659,7 +659,7 @@ private UpdateSecretRequest GenerateUpdateSecretPemRequest(AwsSecretsManagerJobP
string pemCert;
try
{
- pemCert = CertUtilities.ConvertPfxToPem(jobParameters.CertProperties.Contents, jobParameters.CertProperties.PrivateKeyPassword);
+ pemCert = CertUtilities.ConvertPfxToPem(jobParameters.CertProperties.Contents, jobParameters.CertProperties.PrivateKeyPassword, jobParameters.StoreProperties.IncludeChain);
}
catch (Exception ex)
{
@@ -767,7 +767,8 @@ private async Task UpdateSecretTagsAsync(string secretName, List newTags)
var describeResponse = await _secretsManagerClient.DescribeSecretAsync(describeRequest);
var currentTags = describeResponse.Tags ?? new List();
- // Remove any existing tags with the same name
+ // Remove only the existing tags whose keys are being replaced; tags not provided
+ // by Command (e.g. added by other tooling) are left on the secret.
var newTagKeys = newTags.Select(t => t.Key).ToList();
var toReplace = currentTags.Where(t => newTagKeys.Any(key => key == t.Key)).Select(t => t.Key).ToList();
@@ -777,7 +778,7 @@ private async Task UpdateSecretTagsAsync(string secretName, List newTags)
var untagRequest = new UntagResourceRequest
{
SecretId = secretName,
- TagKeys = currentTags.Select(tag => tag.Key).ToList()
+ TagKeys = toReplace
};
await _secretsManagerClient.UntagResourceAsync(untagRequest);
diff --git a/AwsSecretsManager/AwsSecretsManagerJobParameters.cs b/AwsSecretsManager/AwsSecretsManagerJobParameters.cs
index d30334f..541e597 100644
--- a/AwsSecretsManager/AwsSecretsManagerJobParameters.cs
+++ b/AwsSecretsManager/AwsSecretsManagerJobParameters.cs
@@ -49,6 +49,10 @@ public class CertStoreProperties
// When true (AWSSMPEM only), the secret value is written as a JSON document with
// separate "certificate" (PEM cert + chain, leaf first) and "private_key" (PEM) properties.
public bool SeparatePrivateKey { get; set; }
+
+ // When true (AWSSMPEM only, and only when SeparatePrivateKey is false), the single PEM
+ // secret contains the leaf certificate, the issuer chain (leaf first), then the private key.
+ public bool IncludeChain { get; set; }
}
public class CertProperties
diff --git a/AwsSecretsManager/CertUtilities.cs b/AwsSecretsManager/CertUtilities.cs
index 7f402f5..6f5c78b 100644
--- a/AwsSecretsManager/CertUtilities.cs
+++ b/AwsSecretsManager/CertUtilities.cs
@@ -76,7 +76,12 @@ public static bool IsValidJks(byte[] data, string password = null)
}
}
- public static string ConvertPfxToPem(string base64Pfx, string password)
+ ///
+ /// Converts a base64-encoded PFX into a single concatenated PEM string: the leaf
+ /// certificate, optionally followed by the issuer chain (leaf first), then the
+ /// private key in PKCS#8 form.
+ ///
+ public static string ConvertPfxToPem(string base64Pfx, string password, bool includeChain = false)
{
if (string.IsNullOrEmpty(base64Pfx))
throw new ArgumentException("Base64 PFX string cannot be null or empty", nameof(base64Pfx));
@@ -93,12 +98,13 @@ public static string ConvertPfxToPem(string base64Pfx, string password)
try
{
- // Legacy PEM format: the leaf certificate followed by its private key (no chain).
+ // Default (legacy) PEM format: the leaf certificate followed by its private key.
+ // With includeChain, the issuer chain is placed between the leaf and the key.
// Certificates are read via .NET (public data only, no key export); the private
// key is exported via BouncyCastle to avoid the .NET/CNG export failure on Windows.
- var leafPem = BuildCertPemFromPfx(pfxBytes, password, leafOnly: true);
+ var certPem = BuildCertPemFromPfx(pfxBytes, password, leafOnly: !includeChain);
var keyPem = GetPrivateKeyPem(pfxBytes, password);
- return leafPem + "\n" + keyPem;
+ return certPem + "\n" + keyPem;
}
catch (InvalidOperationException)
{
diff --git a/AwsSecretsManager/Constants.cs b/AwsSecretsManager/Constants.cs
index 0efe34b..048114c 100644
--- a/AwsSecretsManager/Constants.cs
+++ b/AwsSecretsManager/Constants.cs
@@ -20,6 +20,21 @@ public static class StorePropertyNames
// Boolean store-type custom field (AWSSMPEM) that switches the secret value to a
// JSON document with separate "certificate" and "private_key" PEM properties.
public const string SEPARATE_PRIVATE_KEY = "SeparatePrivateKey";
+
+ // Boolean store-type custom field (AWSSMPEM) that includes the issuer chain in the
+ // single concatenated PEM secret. Ignored when SeparatePrivateKey is enabled, since
+ // the JSON format always includes the chain.
+ public const string INCLUDE_CHAIN = "IncludeChain";
+
+ // Store-type custom fields of type "Secret"; their values are redacted before the
+ // store properties are logged.
+ public static readonly string[] SECRET_FIELDS =
+ {
+ "OAuthClientId",
+ "OAuthClientSecret",
+ "IAMUserAccessKey",
+ "IAMUserAccessSecret"
+ };
}
public static class KeyfactorJobType
{
diff --git a/AwsSecretsManager/Jobs/Inventory.cs b/AwsSecretsManager/Jobs/Inventory.cs
index be0ff7d..fa186ca 100644
--- a/AwsSecretsManager/Jobs/Inventory.cs
+++ b/AwsSecretsManager/Jobs/Inventory.cs
@@ -442,16 +442,11 @@ private List GetSecretFilters()
}
catch (Exception ex)
{
- // it failed; log a warning and continue.
- var msg =
- $@"Unable to perform PEM to DER conversion on secret named {potentialCert.Name}.
-cert contents:
-{{potentialCert.SecretString}}
-""Exception: {{ex.Message}}";
-
- _logger.LogWarning("cert contents:");
- _logger.LogWarning($"\n{potentialCert.SecretString}\n");
- _logger.LogWarning($"Exception: {ex.Message}");
+ // it failed; log a warning and continue. The secret contents are never
+ // logged, since for this store type they contain the unencrypted private key.
+ var msg = $"Unable to parse the secret named {potentialCert.Name} as a PEM certificate: {ex.Message}";
+
+ _logger.LogWarning($"{msg} (secret string length: {potentialCert.SecretString?.Length ?? 0}; contents not logged)");
warnings.Add(msg);
continue;
}
diff --git a/AwsSecretsManager/Jobs/JobBase.cs b/AwsSecretsManager/Jobs/JobBase.cs
index 5132c1c..2f333fd 100644
--- a/AwsSecretsManager/Jobs/JobBase.cs
+++ b/AwsSecretsManager/Jobs/JobBase.cs
@@ -143,6 +143,16 @@ private protected void SetStoreProperties(CertificateStore storeProps)
JobParameters.StoreProperties.SeparatePrivateKey =
ReadBoolStoreProperty(storeProps.Properties, StorePropertyNames.SEPARATE_PRIVATE_KEY);
_logger.LogTrace($"SeparatePrivateKey = {JobParameters.StoreProperties.SeparatePrivateKey}");
+
+ // read the IncludeChain custom field (AWSSMPEM single-PEM format only)
+ JobParameters.StoreProperties.IncludeChain =
+ ReadBoolStoreProperty(storeProps.Properties, StorePropertyNames.INCLUDE_CHAIN);
+ _logger.LogTrace($"IncludeChain = {JobParameters.StoreProperties.IncludeChain}");
+
+ if (JobParameters.StoreProperties.SeparatePrivateKey && JobParameters.StoreProperties.IncludeChain)
+ {
+ _logger.LogTrace("IncludeChain is ignored because SeparatePrivateKey is enabled; the JSON format always includes the chain.");
+ }
}
///
@@ -178,11 +188,39 @@ private bool ReadBoolStoreProperty(string propertiesJson, string name)
}
}
+ ///
+ /// Returns the serialized store Properties with the values of Secret-type fields replaced,
+ /// so the properties can be logged without exposing credentials. If the JSON cannot be
+ /// parsed, nothing from it is returned.
+ ///
+ internal static string RedactSecretStoreProperties(string propertiesJson)
+ {
+ if (string.IsNullOrWhiteSpace(propertiesJson)) return propertiesJson;
+
+ try
+ {
+ var jObj = JObject.Parse(propertiesJson);
+ foreach (var prop in jObj.Properties())
+ {
+ if (StorePropertyNames.SECRET_FIELDS.Any(f => string.Equals(f, prop.Name, StringComparison.OrdinalIgnoreCase))
+ && prop.Value.Type != JTokenType.Null)
+ {
+ prop.Value = "REDACTED";
+ }
+ }
+ return jObj.ToString(Formatting.None);
+ }
+ catch (Exception)
+ {
+ return "(unable to parse store properties; not logged)";
+ }
+ }
+
private void InitializeAwsClient(CertificateStore storeProps)
{
_logger.MethodEntry();
_logger.LogTrace("deserializing store properties..");
- _logger.LogTrace($"raw value: {storeProps.Properties}");
+ _logger.LogTrace($"raw value (secret fields redacted): {RedactSecretStoreProperties(storeProps.Properties)}");
AuthCustomFieldParameters customFields;
try
{
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d18040..0735ba6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 1.2.0
+* AWSSMPEM - New optional store type parameter `IncludeChain` (default false) to include the issuer chain in the single concatenated PEM secret (leaf, then chain, then private key). Ignored when `SeparatePrivateKey` is enabled, as the JSON format already includes the chain. Stores without the parameter behave as before.
+* Added .NET 10 as a target framework.
+* Security - AWSSMPEM inventory no longer writes the contents of a secret that cannot be parsed to the orchestrator log. Previously the full secret value, including the unencrypted private key, was logged at the Warning level. The warning now includes only the secret name and the parse error.
+* Security - Secret-type store properties (OAuth client ID/secret, IAM user access key/secret) are now redacted when the store properties are written to the Trace log.
+* Fixed tag handling when a certificate is renewed or overwritten: previously, if any provided tag key already existed on the secret, all existing tags were removed (including tags not managed by Keyfactor). Now only the tags with matching keys are replaced; other tags on the secret are left intact.
+* Documentation - added the `secretsmanager:BatchGetSecretValue` and `secretsmanager:DescribeSecret` IAM actions to the list of required permissions.
+* Documentation - added the conditionally required `secretsmanager:UntagResource`, `secretsmanager:ReplicateSecretToRegions`, and `secretsmanager:RemoveRegionsFromReplication` IAM actions, and corrected the formatting of the listed action names (`secretsmanager:`) so they can be used directly in an IAM policy.
+
## 1.1.0
* AWSSMPEM - New optional store type parameter to indicate that the secret containing the cert and private key should use the JSON format with seperate properties for certificate and private key.
* now support placeholders in CertificateTags for 3 certificate metadata fields:
diff --git a/README.md b/README.md
index 4e23582..279d95a 100644
--- a/README.md
+++ b/README.md
@@ -44,19 +44,16 @@ It can read and write secrets containing certificates stored in the following fo
For each format there is a corresponding certificate store type ([AWSSMPEM](#AWSSMPEM), [AWSSMPFX](#AWSSMPFX), [AWSSMJKS](#AWSSMJKS)).
The AWS Secrets Manager Universal Orchestrator extension implements 3 Certificate Store Types. Depending on your use case, you may elect to use one, or all of these Certificate Store Types. Descriptions of each are provided below.
-
- [AwsSecretsManager PEM](#AWSSMPEM)
-
- [AwsSecretsManager PFX](#AWSSMPFX)
-
- [AwsSecretsManager JKS](#AWSSMJKS)
-
## Compatibility
This integration is compatible with Keyfactor Universal Orchestrator version 10.1 and later.
## Support
+
The AWS Secrets Manager Universal Orchestrator extension is community open source and there is **no SLA**. Keyfactor will address issues as resources become available.
> To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute bug fixes or additional enhancements, use the **[Pull requests](../../pulls)** tab.
@@ -65,7 +62,6 @@ The AWS Secrets Manager Universal Orchestrator extension is community open sourc
Before installing the AWS Secrets Manager Universal Orchestrator extension, we recommend that you install [kfutil](https://github.com/Keyfactor/kfutil). Kfutil is a command-line tool that simplifies the process of creating store types, installing extensions, and instantiating certificate stores in Keyfactor Command.
-
In order to use this integration, you should have..
- An instance of Keyfactor Command v11.0+
- An instance of the Keyfactor Universal Orchestrator v10.4+
@@ -82,12 +78,17 @@ the authentication credentials has access to.
Here is a list of the IAM actions that the authenticating identity should have in order to perform all Jobs supported by this extension:
-- `secretsmanager.ListSecrets`
-- `secretsManager.GetSecretValue`
+- `secretsmanager:ListSecrets`
+- `secretsmanager:GetSecretValue`
+- `secretsmanager:BatchGetSecretValue`
+- `secretsmanager:DescribeSecret`
- `secretsmanager:CreateSecret`
-- `secretsmanager.DeleteSecret`
-- `secretsmanager.UpdateSecret`
-- `secretsmanager.TagResource` _if using tags for filtering or to add tags via entry parameters._
+- `secretsmanager:DeleteSecret`
+- `secretsmanager:UpdateSecret`
+- `secretsmanager:TagResource` _if using tags for filtering or to add tags via entry parameters._
+- `secretsmanager:UntagResource` _if using tags for filtering or to add tags via entry parameters; used to replace existing tags when a certificate is renewed or overwritten._
+- `secretsmanager:ReplicateSecretToRegions` _if using the ReplicaRegions entry parameter._
+- `secretsmanager:RemoveRegionsFromReplication` _if using the ReplicaRegions entry parameter; used to remove regions no longer listed when a certificate is renewed or overwritten._
For more information on these permission actions, refer to the [AWS Documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/list_awssecretsmanager.html).
@@ -134,7 +135,6 @@ would write two tags on the certificate secret: `serial` containing the certific
If the `CertificateTags` value is not valid JSON, the enrollment job fails with an error identifying the `CertificateTags` parameter, rather than a generic parse error.
-
## Certificate Store Types
To use the AWS Secrets Manager Universal Orchestrator extension, you **must** create the Certificate Store Types required for your use-case. This only needs to happen _once_ per Keyfactor Command instance.
@@ -145,13 +145,9 @@ The AWS Secrets Manager Universal Orchestrator extension implements 3 Certificat
Click to expand details
-
The AWSSMPEM certificate store type provided by this integration is the one to use for managing certificates stored in AWS Secrets Manager in the PEM format.
Certificates managed by this certificate store are expected to have the PEM formatted certificate stored as a SecretString in AWS Secrets Manager.
-
-
-
#### AwsSecretsManager PEM Requirements
1. [Certificate Format](#certificate-format)
@@ -169,6 +165,29 @@ For this certificate store type, the certificates are expected to be stored as a
When enrolling a certificate from Keyfactor Command into the Certificate Store with this type (AWSSMPEM), it will be stored as a PEM
formatted string, including the private key, with no seperate password.
+###### Including the certificate chain in the PEM secret
+
+By default, the concatenated PEM secret contains only the leaf certificate followed by its private key. The AWSSMPEM store type includes an optional `IncludeChain` custom field. When it is enabled, the secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key:
+
+```
+-----BEGIN CERTIFICATE-----
+
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+
+-----END CERTIFICATE-----
+...
+-----BEGIN PRIVATE KEY-----
+
+-----END PRIVATE KEY-----
+```
+
+A few things to note:
+- `IncludeChain` only applies when `SeparatePrivateKey` is disabled. The JSON format described below always includes the chain in its `certificate` property, so `IncludeChain` is ignored when `SeparatePrivateKey` is enabled.
+- The chain written is the chain delivered by Keyfactor Command with the certificate, which may include the root CA certificate.
+- The setting applies when a certificate is added or renewed. Existing secrets are not rewritten when the option is changed; they pick up the new format the next time they are added or renewed. Inventory reads both formats.
+- Existing AWSSMPEM store types created before this option was introduced do not include the field. Stores without the field behave exactly as before (leaf and key only). To use the option, add the `IncludeChain` property to the store type definition.
+
###### Storing the certificate and private key as separate JSON properties
The AWSSMPEM store type includes an optional `SeparatePrivateKey` custom field. When it is enabled, certificates added to the store are written not as a single concatenated PEM string, but as a JSON document with two properties:
@@ -207,23 +226,22 @@ In summary: supplying a name prefix or tag name and value as part of a certifica
- Inventory Jobs will only return certificates where the name begins with the prefix, and/or the tagName exists on the secret and contains the provided tagValue.
- Enrollment into these stores will apply the same convention to newly added certificate secrets; appending the prefix to the name and/or associating the tag name and value.
-
-
#### Supported Operations
-| Operation | Is Supported |
-|--------------|------------------------------------------------------------------------------------------------------------------------|
-| Add | ✅ Checked |
-| Remove | ✅ Checked |
-| Discovery | 🔲 Unchecked |
+| Operation | Is Supported |
+|--------------|--------------|
+| Add | ✅ Checked |
+| Remove | ✅ Checked |
+| Discovery | 🔲 Unchecked |
| Reenrollment | 🔲 Unchecked |
-| Create | 🔲 Unchecked |
+| Create | 🔲 Unchecked |
#### Store Type Creation
##### Using kfutil:
`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types.
For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart)
+
Click to expand AWSSMPEM kfutil details
##### Using online definition from GitHub:
@@ -242,10 +260,10 @@ For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out
```
-
#### Manual Creation
Below are instructions on how to create the AWSSMPEM store type manually in
the Keyfactor Command Portal
+
Click to expand manual AWSSMPEM details
Create a store type called `AWSSMPEM` with the attributes in the tables below:
@@ -256,11 +274,11 @@ the Keyfactor Command Portal
| Name | AwsSecretsManager PEM | Display name for the store type (may be customized) |
| Short Name | AWSSMPEM | Short display name for the store type |
| Capability | AWSSMPEM | Store type name orchestrator will register with. Check the box to allow entry of value |
- | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add |
- | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove |
- | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery |
- | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment |
- | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation |
+ | Supports Add | ✅ Checked | Indicates that the Store Type supports Management Add |
+ | Supports Remove | ✅ Checked | Indicates that the Store Type supports Management Remove |
+ | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery |
+ | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment |
+ | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation |
| Needs Server | 🔲 Unchecked | Determines if a target server name is required when creating store |
| Blueprint Allowed | 🔲 Unchecked | Determines if store type may be included in an Orchestrator blueprint |
| Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
@@ -269,18 +287,18 @@ the Keyfactor Command Portal
The Basic tab should look like this:
- 
+ 
##### Advanced Tab
| Attribute | Value | Description |
| --------- | ----- | ----- |
| Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. |
- | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. |
| PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
The Advanced tab should look like this:
- 
+ 
> For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX.
@@ -290,6 +308,7 @@ the Keyfactor Command Portal
| Name | Display Name | Description | Type | Default Value/Options | Required |
| ---- | ------------ | ---- | --------------------- | -------- | ----------- |
| SeparatePrivateKey | Store as JSON with separate private key | When enabled, the certificate is stored as a JSON document with separate 'certificate' (PEM certificate and chain, leaf first) and 'private_key' (PEM) properties, rather than a single concatenated PEM string. | Bool | false | 🔲 Unchecked |
+ | IncludeChain | Include certificate chain in PEM | When enabled, the single concatenated PEM secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key. Only applies when 'Store as JSON with separate private key' is disabled; the JSON format always includes the chain. | Bool | false | 🔲 Unchecked |
| UseDefaultSdkAuth | Use Default SDK Auth | A switch to enable the store to use Default SDK credentials | Bool | false | ✅ Checked |
| DefaultSdkAssumeRole | Assume new Role using Default SDK Auth | A switch to enable the store to assume a new Role when using Default SDK credentials | Bool | false | 🔲 Unchecked |
| UseOAuth | Use OAuth 2.0 Provider | A switch to enable the store to use an OAuth provider workflow to authenticate with AWS | Bool | false | ✅ Checked |
@@ -305,113 +324,90 @@ the Keyfactor Command Portal
The Custom Fields tab should look like this:
- 
-
+ 
###### Store as JSON with separate private key
When enabled, the certificate is stored as a JSON document with separate 'certificate' (PEM certificate and chain, leaf first) and 'private_key' (PEM) properties, rather than a single concatenated PEM string.
- 
- 
+ 
+
+ ###### Include certificate chain in PEM
+ When enabled, the single concatenated PEM secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key. Only applies when 'Store as JSON with separate private key' is disabled; the JSON format always includes the chain.
+
+ 
###### Use Default SDK Auth
A switch to enable the store to use Default SDK credentials
- 
- 
-
+ 
###### Assume new Role using Default SDK Auth
A switch to enable the store to assume a new Role when using Default SDK credentials
- 
- 
-
+ 
###### Use OAuth 2.0 Provider
A switch to enable the store to use an OAuth provider workflow to authenticate with AWS
- 
- 
-
+ 
###### OAuth Scope
This is the OAuth Scope needed for Okta OAuth, defined in Okta
- 
- 
-
+ 
###### 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`
- 
- 
-
+ 
###### OAuth Url
The token endpoint for the OAuth 2.0 provider
- 
- 
-
+ 
###### OAuth Client ID
The Client ID for OAuth.
- 
- 
-
+ 
###### OAuth Client Secret
The Client Secret for OAuth.
- 
- 
-
+ 
###### Use IAM User Auth
A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS
- 
- 
-
+ 
###### IAM User Access Key
The AWS Access Key for an IAM User
- 
- 
-
+ 
###### IAM User Access Secret
The AWS Access Secret for an IAM User.
- 
- 
-
+ 
###### sts:ExternalId
An optional parameter sts:ExternalId to pass with Assume Role calls
- 
- 
-
-
-
+ 
##### Entry Parameters Tab
@@ -423,22 +419,17 @@ the Keyfactor Command Portal
The Entry Parameters tab should look like this:
- 
-
-
+ 
##### Certificate Tags
If desired, tags can be applied to the certificate entries in AWS Secrets Manager. Provide them as a JSON string of key-value pairs ie: '{'tag-name': 'tag-content', 'other-tag-name': 'other-tag-content'}'. Tag values may contain the placeholder tokens %SERIAL_NUMBER%, %NOT_BEFORE%, and %NOT_AFTER%, which are replaced with the certificate's serial number (hexadecimal) and validity dates (ISO-8601 UTC) before the tag is written.
- 
- 
+ 
##### Replica Regions
To replicate secrets to other regions, you can provide them here as a JSON array in the format: [{ 'KmsKeyId': ''}, {...}]
- 
- 
-
+ 
@@ -448,14 +439,10 @@ the Keyfactor Command Portal
Click to expand details
-
The AWSSMPFX certificate store type allows managing certificates stored in AWS Secrets Manager in the PFX format via Keyfactor Command.
Since AWS Secrets Manager is designed to store arbitrary secrets of any type, it is necessary that we implement a convention for identifying and writing these certificates as
AWS Secrets Manager secrets.
-
-
-
#### AwsSecretsManager PFX Requirements
1. [Certificate Format](#certificate-format)
@@ -532,23 +519,22 @@ Enrolling a certificate with the alias "mycert" into a AWSSMPFX certificate stor
---
-
-
#### Supported Operations
-| Operation | Is Supported |
-|--------------|------------------------------------------------------------------------------------------------------------------------|
-| Add | ✅ Checked |
-| Remove | ✅ Checked |
-| Discovery | 🔲 Unchecked |
+| Operation | Is Supported |
+|--------------|--------------|
+| Add | ✅ Checked |
+| Remove | ✅ Checked |
+| Discovery | 🔲 Unchecked |
| Reenrollment | 🔲 Unchecked |
-| Create | 🔲 Unchecked |
+| Create | 🔲 Unchecked |
#### Store Type Creation
##### Using kfutil:
`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types.
For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart)
+
Click to expand AWSSMPFX kfutil details
##### Using online definition from GitHub:
@@ -567,10 +553,10 @@ For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out
```
-
#### Manual Creation
Below are instructions on how to create the AWSSMPFX store type manually in
the Keyfactor Command Portal
+
Click to expand manual AWSSMPFX details
Create a store type called `AWSSMPFX` with the attributes in the tables below:
@@ -581,11 +567,11 @@ the Keyfactor Command Portal
| Name | AwsSecretsManager PFX | Display name for the store type (may be customized) |
| Short Name | AWSSMPFX | Short display name for the store type |
| Capability | AWSSMPFX | Store type name orchestrator will register with. Check the box to allow entry of value |
- | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add |
- | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove |
- | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery |
- | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment |
- | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation |
+ | Supports Add | ✅ Checked | Indicates that the Store Type supports Management Add |
+ | Supports Remove | ✅ Checked | Indicates that the Store Type supports Management Remove |
+ | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery |
+ | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment |
+ | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation |
| Needs Server | 🔲 Unchecked | Determines if a target server name is required when creating store |
| Blueprint Allowed | 🔲 Unchecked | Determines if store type may be included in an Orchestrator blueprint |
| Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
@@ -594,18 +580,18 @@ the Keyfactor Command Portal
The Basic tab should look like this:
- 
+ 
##### Advanced Tab
| Attribute | Value | Description |
| --------- | ----- | ----- |
| Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. |
- | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. |
| PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
The Advanced tab should look like this:
- 
+ 
> For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX.
@@ -629,105 +615,78 @@ the Keyfactor Command Portal
The Custom Fields tab should look like this:
- 
-
+ 
###### Use Default SDK Auth
A switch to enable the store to use Default SDK credentials
- 
- 
-
+ 
###### Assume new Role using Default SDK Auth
A switch to enable the store to assume a new Role when using Default SDK credentials
- 
- 
-
+ 
###### Use OAuth 2.0 Provider
A switch to enable the store to use an OAuth provider workflow to authenticate with AWS
- 
- 
-
+ 
###### OAuth Scope
This is the OAuth Scope needed for Okta OAuth, defined in Okta
- 
- 
-
+ 
###### 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`
- 
- 
-
+ 
###### OAuth Url
The token endpoint for the OAuth 2.0 provider
- 
- 
-
+ 
###### OAuth Client ID
The Client ID for OAuth.
- 
- 
-
+ 
###### OAuth Client Secret
The Client Secret for OAuth.
- 
- 
-
+ 
###### Use IAM User Auth
A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS
- 
- 
-
+ 
###### IAM User Access Key
The AWS Access Key for an IAM User
- 
- 
-
+ 
###### IAM User Access Secret
The AWS Access Secret for an IAM User.
- 
- 
-
+ 
###### sts:ExternalId
An optional parameter sts:ExternalId to pass with Assume Role calls
- 
- 
-
-
-
+ 
##### Entry Parameters Tab
@@ -739,22 +698,17 @@ the Keyfactor Command Portal
The Entry Parameters tab should look like this:
- 
-
-
+ 
##### Certificate Tags
If desired, tags can be applied to the certificate entries in AWS Secrets Manager. Provide them as a JSON string of key-value pairs ie: '{'tag-name': 'tag-content', 'other-tag-name': 'other-tag-content'}'. Tag values may contain the placeholder tokens %SERIAL_NUMBER%, %NOT_BEFORE%, and %NOT_AFTER%, which are replaced with the certificate's serial number (hexadecimal) and validity dates (ISO-8601 UTC) before the tag is written.
- 
- 
+ 
##### Replica Regions
To replicate secrets to other regions, you can provide them here as a JSON array in the format: [{ 'KmsKeyId': ''}, {...}]
- 
- 
-
+ 
@@ -764,14 +718,10 @@ the Keyfactor Command Portal
Click to expand details
-
The AWSSMJKS certificate store type allows managing certificates stored in AWS Secrets Manager in the JKS (Java Keystore) format via Keyfactor Command.
Since AWS Secrets Manager is designed to store arbitrary secrets of any type, it is necessary that we implement a convention for identifying and writing these certificates as
AWS Secrets Manager secrets.
-
-
-
#### AwsSecretsManager JKS Requirements
1. [Certificate Format](#certificate-format)
@@ -848,23 +798,22 @@ Enrolling a certificate with the alias "mycert" into a AWSSMJKS certificate stor
---
-
-
#### Supported Operations
-| Operation | Is Supported |
-|--------------|------------------------------------------------------------------------------------------------------------------------|
-| Add | ✅ Checked |
-| Remove | ✅ Checked |
-| Discovery | 🔲 Unchecked |
+| Operation | Is Supported |
+|--------------|--------------|
+| Add | ✅ Checked |
+| Remove | ✅ Checked |
+| Discovery | 🔲 Unchecked |
| Reenrollment | 🔲 Unchecked |
-| Create | 🔲 Unchecked |
+| Create | 🔲 Unchecked |
#### Store Type Creation
##### Using kfutil:
`kfutil` is a custom CLI for the Keyfactor Command API and can be used to create certificate store types.
For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart)
+
Click to expand AWSSMJKS kfutil details
##### Using online definition from GitHub:
@@ -883,10 +832,10 @@ For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out
```
-
#### Manual Creation
Below are instructions on how to create the AWSSMJKS store type manually in
the Keyfactor Command Portal
+
Click to expand manual AWSSMJKS details
Create a store type called `AWSSMJKS` with the attributes in the tables below:
@@ -897,11 +846,11 @@ the Keyfactor Command Portal
| Name | AwsSecretsManager JKS | Display name for the store type (may be customized) |
| Short Name | AWSSMJKS | Short display name for the store type |
| Capability | AWSSMJKS | Store type name orchestrator will register with. Check the box to allow entry of value |
- | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add |
- | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove |
- | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery |
- | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment |
- | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation |
+ | Supports Add | ✅ Checked | Indicates that the Store Type supports Management Add |
+ | Supports Remove | ✅ Checked | Indicates that the Store Type supports Management Remove |
+ | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery |
+ | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment |
+ | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation |
| Needs Server | 🔲 Unchecked | Determines if a target server name is required when creating store |
| Blueprint Allowed | 🔲 Unchecked | Determines if store type may be included in an Orchestrator blueprint |
| Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell |
@@ -910,18 +859,18 @@ the Keyfactor Command Portal
The Basic tab should look like this:
- 
+ 
##### Advanced Tab
| Attribute | Value | Description |
| --------- | ----- | ----- |
| Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. |
- | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. |
+ | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. |
| PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) |
The Advanced tab should look like this:
- 
+ 
> For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX.
@@ -945,105 +894,78 @@ the Keyfactor Command Portal
The Custom Fields tab should look like this:
- 
-
+ 
###### Use Default SDK Auth
A switch to enable the store to use Default SDK credentials
- 
- 
-
+ 
###### Assume new Role using Default SDK Auth
A switch to enable the store to assume a new Role when using Default SDK credentials
- 
- 
-
+ 
###### Use OAuth 2.0 Provider
A switch to enable the store to use an OAuth provider workflow to authenticate with AWS
- 
- 
-
+ 
###### OAuth Scope
This is the OAuth Scope needed for Okta OAuth, defined in Okta
- 
- 
-
+ 
###### 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`
- 
- 
-
+ 
###### OAuth Url
The token endpoint for the OAuth 2.0 provider
- 
- 
-
+ 
###### OAuth Client ID
The Client ID for OAuth.
- 
- 
-
+ 
###### OAuth Client Secret
The Client Secret for OAuth.
- 
- 
-
+ 
###### Use IAM User Auth
A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS
- 
- 
-
+ 
###### IAM User Access Key
The AWS Access Key for an IAM User
- 
- 
-
+ 
###### IAM User Access Secret
The AWS Access Secret for an IAM User.
- 
- 
-
+ 
###### sts:ExternalId
An optional parameter sts:ExternalId to pass with Assume Role calls
- 
- 
-
-
-
+ 
##### Entry Parameters Tab
@@ -1055,22 +977,17 @@ the Keyfactor Command Portal
The Entry Parameters tab should look like this:
- 
-
-
+ 
##### Certificate Tags
If desired, tags can be applied to the certificate entries in AWS Secrets Manager. Provide them as a JSON string of key-value pairs ie: '{'tag-name': 'tag-content', 'other-tag-name': 'other-tag-content'}'. Tag values may contain the placeholder tokens %SERIAL_NUMBER%, %NOT_BEFORE%, and %NOT_AFTER%, which are replaced with the certificate's serial number (hexadecimal) and validity dates (ISO-8601 UTC) before the tag is written.
- 
- 
+ 
##### Replica Regions
To replicate secrets to other regions, you can provide them here as a JSON array in the format: [{ 'KmsKeyId': ''}, {...}]
- 
- 
-
+ 
@@ -1081,18 +998,20 @@ the Keyfactor Command Portal
1. **Download the latest AWS Secrets Manager Universal Orchestrator extension from GitHub.**
- Navigate to the [AWS Secrets Manager Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/aws-secretsmanager-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.
+ Navigate to the [AWS Secrets Manager Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/aws-secretsmanager-orchestrator/releases/latest). Refer to the compatibility matrix below to determine which 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-secretsmanager-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` |
+ | `25.5` _and_ newer | `net10.0` | | `net10.0` |
Unzip the archive containing extension assemblies to a known location.
- > **Note** If you don't see an asset with a corresponding .NET version, you should always assume that it was compiled for `net6.0`.
+ > **Note** If you don't see an asset with a corresponding .NET version, you should always assume that it was compiled for `net10.0`.
2. **Locate the Universal Orchestrator extensions directory.**
@@ -1110,25 +1029,20 @@ the Keyfactor Command Portal
Refer to [Starting/Restarting the Universal Orchestrator service](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/StarttheService.htm).
-
6. **(optional) PAM Integration**
The AWS Secrets Manager Universal Orchestrator extension is compatible with all supported Keyfactor PAM extensions to resolve PAM-eligible secrets. PAM extensions running on Universal Orchestrators enable secure retrieval of secrets from a connected PAM provider.
To configure a PAM provider, [reference the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam) to select an extension and follow the associated instructions to install it on the Universal Orchestrator (remote).
-
> The above installation steps can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/CustomExtensions.htm?Highlight=extensions).
-
-
## Defining Certificate Stores
The AWS Secrets Manager Universal Orchestrator extension implements 3 Certificate Store Types, each of which implements different functionality. Refer to the individual instructions below for each Certificate Store Type that you deemed necessary for your use case from the installation section.
AwsSecretsManager PEM (AWSSMPEM)
-
### Store Creation
#### Manually with the Command UI
@@ -1143,14 +1057,15 @@ The AWS Secrets Manager Universal Orchestrator extension implements 3 Certificat
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- |---------------------------------------------------------|
+ | Attribute | Description |
+ | --------- | ----------- |
| Category | Select "AwsSecretsManager PEM" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | |
| Store Path | The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' |
| Orchestrator | Select an approved orchestrator capable of managing `AWSSMPEM` certificates. Specifically, one with the `AWSSMPEM` capability. |
| SeparatePrivateKey | When enabled, the certificate is stored as a JSON document with separate 'certificate' (PEM certificate and chain, leaf first) and 'private_key' (PEM) properties, rather than a single concatenated PEM string. |
+ | IncludeChain | When enabled, the single concatenated PEM secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key. Only applies when 'Store as JSON with separate private key' is disabled; the JSON format always includes the chain. |
| UseDefaultSdkAuth | A switch to enable the store to use Default SDK credentials |
| DefaultSdkAssumeRole | A switch to enable the store to assume a new Role when using Default SDK credentials |
| UseOAuth | A switch to enable the store to use an OAuth provider workflow to authenticate with AWS |
@@ -1166,8 +1081,6 @@ The AWS Secrets Manager Universal Orchestrator extension implements 3 Certificat
-
-
#### Using kfutil CLI
Click to expand details
@@ -1189,6 +1102,7 @@ The AWS Secrets Manager Universal Orchestrator extension implements 3 Certificat
| Store Path | The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' |
| Orchestrator | Select an approved orchestrator capable of managing `AWSSMPEM` certificates. Specifically, one with the `AWSSMPEM` capability. |
| Properties.SeparatePrivateKey | When enabled, the certificate is stored as a JSON document with separate 'certificate' (PEM certificate and chain, leaf first) and 'private_key' (PEM) properties, rather than a single concatenated PEM string. |
+ | Properties.IncludeChain | When enabled, the single concatenated PEM secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key. Only applies when 'Store as JSON with separate private key' is disabled; the JSON format always includes the chain. |
| Properties.UseDefaultSdkAuth | A switch to enable the store to use Default SDK credentials |
| Properties.DefaultSdkAssumeRole | A switch to enable the store to assume a new Role when using Default SDK credentials |
| Properties.UseOAuth | A switch to enable the store to use an OAuth provider workflow to authenticate with AWS |
@@ -1210,7 +1124,6 @@ The AWS Secrets Manager Universal Orchestrator extension implements 3 Certificat
-
#### PAM Provider Eligible Fields
Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
@@ -1228,15 +1141,12 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
-
> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
-
AwsSecretsManager PFX (AWSSMPFX)
-
### Store Creation
#### Manually with the Command UI
@@ -1251,8 +1161,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- |---------------------------------------------------------|
+ | Attribute | Description |
+ | --------- | ----------- |
| Category | Select "AwsSecretsManager PFX" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | |
@@ -1273,8 +1183,6 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
-
-
#### Using kfutil CLI
Click to expand details
@@ -1316,7 +1224,6 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
-
#### PAM Provider Eligible Fields
Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
@@ -1334,15 +1241,12 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
-
> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
-
AwsSecretsManager JKS (AWSSMJKS)
-
### Store Creation
#### Manually with the Command UI
@@ -1357,8 +1261,8 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form.
- | Attribute | Description |
- | --------- |---------------------------------------------------------|
+ | Attribute | Description |
+ | --------- | ----------- |
| Category | Select "AwsSecretsManager JKS" or the customized certificate store name from the previous step. |
| Container | Optional container to associate certificate store with. |
| Client Machine | |
@@ -1379,8 +1283,6 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
-
-
#### Using kfutil CLI
Click to expand details
@@ -1422,7 +1324,6 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
-
#### PAM Provider Eligible Fields
Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator
@@ -1440,19 +1341,15 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov
-
> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store).
-
-
-
## License
Apache License 2.0, see [LICENSE](LICENSE).
## Related Integrations
-See all [Keyfactor Universal Orchestrator extensions](https://github.com/orgs/Keyfactor/repositories?q=orchestrator).
\ No newline at end of file
+See all [Keyfactor Universal Orchestrator extensions](https://github.com/orgs/Keyfactor/repositories?q=orchestrator).
diff --git a/docsource/awssmpem.md b/docsource/awssmpem.md
index 9d546b5..73c0e18 100644
--- a/docsource/awssmpem.md
+++ b/docsource/awssmpem.md
@@ -20,6 +20,29 @@ For this certificate store type, the certificates are expected to be stored as a
When enrolling a certificate from Keyfactor Command into the Certificate Store with this type (AWSSMPEM), it will be stored as a PEM
formatted string, including the private key, with no seperate password.
+##### Including the certificate chain in the PEM secret
+
+By default, the concatenated PEM secret contains only the leaf certificate followed by its private key. The AWSSMPEM store type includes an optional `IncludeChain` custom field. When it is enabled, the secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key:
+
+```
+-----BEGIN CERTIFICATE-----
+
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+
+-----END CERTIFICATE-----
+...
+-----BEGIN PRIVATE KEY-----
+
+-----END PRIVATE KEY-----
+```
+
+A few things to note:
+- `IncludeChain` only applies when `SeparatePrivateKey` is disabled. The JSON format described below always includes the chain in its `certificate` property, so `IncludeChain` is ignored when `SeparatePrivateKey` is enabled.
+- The chain written is the chain delivered by Keyfactor Command with the certificate, which may include the root CA certificate.
+- The setting applies when a certificate is added or renewed. Existing secrets are not rewritten when the option is changed; they pick up the new format the next time they are added or renewed. Inventory reads both formats.
+- Existing AWSSMPEM store types created before this option was introduced do not include the field. Stores without the field behave exactly as before (leaf and key only). To use the option, add the `IncludeChain` property to the store type definition.
+
##### Storing the certificate and private key as separate JSON properties
The AWSSMPEM store type includes an optional `SeparatePrivateKey` custom field. When it is enabled, certificates added to the store are written not as a single concatenated PEM string, but as a JSON document with two properties:
diff --git a/docsource/content.md b/docsource/content.md
index 6c0a713..355d440 100644
--- a/docsource/content.md
+++ b/docsource/content.md
@@ -29,12 +29,17 @@ the authentication credentials has access to.
Here is a list of the IAM actions that the authenticating identity should have in order to perform all Jobs supported by this extension:
-- `secretsmanager.ListSecrets`
-- `secretsManager.GetSecretValue`
+- `secretsmanager:ListSecrets`
+- `secretsmanager:GetSecretValue`
+- `secretsmanager:BatchGetSecretValue`
+- `secretsmanager:DescribeSecret`
- `secretsmanager:CreateSecret`
-- `secretsmanager.DeleteSecret`
-- `secretsmanager.UpdateSecret`
-- `secretsmanager.TagResource` _if using tags for filtering or to add tags via entry parameters._
+- `secretsmanager:DeleteSecret`
+- `secretsmanager:UpdateSecret`
+- `secretsmanager:TagResource` _if using tags for filtering or to add tags via entry parameters._
+- `secretsmanager:UntagResource` _if using tags for filtering or to add tags via entry parameters; used to replace existing tags when a certificate is renewed or overwritten._
+- `secretsmanager:ReplicateSecretToRegions` _if using the ReplicaRegions entry parameter._
+- `secretsmanager:RemoveRegionsFromReplication` _if using the ReplicaRegions entry parameter; used to remove regions no longer listed when a certificate is renewed or overwritten._
For more information on these permission actions, refer to the [AWS Documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/list_awssecretsmanager.html).
diff --git a/docsource/images/AWSSMJKS-advanced-store-type-dialog.svg b/docsource/images/AWSSMJKS-advanced-store-type-dialog.svg
new file mode 100644
index 0000000..4bd468b
--- /dev/null
+++ b/docsource/images/AWSSMJKS-advanced-store-type-dialog.svg
@@ -0,0 +1,67 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-basic-store-type-dialog.svg b/docsource/images/AWSSMJKS-basic-store-type-dialog.svg
new file mode 100644
index 0000000..6a417bc
--- /dev/null
+++ b/docsource/images/AWSSMJKS-basic-store-type-dialog.svg
@@ -0,0 +1,82 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-DefaultSdkAssumeRole-dialog.svg b/docsource/images/AWSSMJKS-custom-field-DefaultSdkAssumeRole-dialog.svg
new file mode 100644
index 0000000..efa7a36
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-DefaultSdkAssumeRole-dialog.svg
@@ -0,0 +1,55 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-ExternalId-dialog.svg b/docsource/images/AWSSMJKS-custom-field-ExternalId-dialog.svg
new file mode 100644
index 0000000..f4430a9
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-ExternalId-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-ExternalId-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-ExternalId-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-ExternalId-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-IAMUserAccessKey-dialog.svg b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessKey-dialog.svg
new file mode 100644
index 0000000..2c7e9b1
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessKey-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-IAMUserAccessKey-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessKey-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessKey-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-IAMUserAccessSecret-dialog.svg b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessSecret-dialog.svg
new file mode 100644
index 0000000..95d1ebe
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessSecret-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-IAMUserAccessSecret-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessSecret-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-IAMUserAccessSecret-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthClientId-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthClientId-dialog.svg
new file mode 100644
index 0000000..9b2d27b
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthClientId-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthClientId-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthClientId-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthClientId-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthClientSecret-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthClientSecret-dialog.svg
new file mode 100644
index 0000000..58727c8
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthClientSecret-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthClientSecret-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthClientSecret-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthClientSecret-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthGrantType-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthGrantType-dialog.svg
new file mode 100644
index 0000000..9b8aa94
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthGrantType-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthGrantType-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthGrantType-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthGrantType-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthScope-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthScope-dialog.svg
new file mode 100644
index 0000000..71d8a33
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthScope-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthScope-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthScope-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthScope-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthUrl-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthUrl-dialog.svg
new file mode 100644
index 0000000..9b81e1e
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthUrl-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-OAuthUrl-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-OAuthUrl-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-OAuthUrl-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-UseDefaultSdkAuth-dialog.svg b/docsource/images/AWSSMJKS-custom-field-UseDefaultSdkAuth-dialog.svg
new file mode 100644
index 0000000..d95def0
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-UseDefaultSdkAuth-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-UseIAM-dialog.svg b/docsource/images/AWSSMJKS-custom-field-UseIAM-dialog.svg
new file mode 100644
index 0000000..6628f5f
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-UseIAM-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-UseIAM-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-UseIAM-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-UseIAM-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-UseOAuth-dialog.svg b/docsource/images/AWSSMJKS-custom-field-UseOAuth-dialog.svg
new file mode 100644
index 0000000..84d8b48
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-UseOAuth-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-field-UseOAuth-validation-options-dialog.svg b/docsource/images/AWSSMJKS-custom-field-UseOAuth-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-field-UseOAuth-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-custom-fields-store-type-dialog.svg b/docsource/images/AWSSMJKS-custom-fields-store-type-dialog.svg
new file mode 100644
index 0000000..98608d8
--- /dev/null
+++ b/docsource/images/AWSSMJKS-custom-fields-store-type-dialog.svg
@@ -0,0 +1,148 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg
new file mode 100644
index 0000000..ddaa9ab
--- /dev/null
+++ b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg
@@ -0,0 +1,68 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-CertificateTags.svg b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-CertificateTags.svg
new file mode 100644
index 0000000..a898262
--- /dev/null
+++ b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-CertificateTags.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg
new file mode 100644
index 0000000..ddaa9ab
--- /dev/null
+++ b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg
@@ -0,0 +1,68 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-ReplicaRegions.svg b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-ReplicaRegions.svg
new file mode 100644
index 0000000..8003286
--- /dev/null
+++ b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog-ReplicaRegions.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog.svg b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog.svg
new file mode 100644
index 0000000..ac80c13
--- /dev/null
+++ b/docsource/images/AWSSMJKS-entry-parameters-store-type-dialog.svg
@@ -0,0 +1,62 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-advanced-store-type-dialog.svg b/docsource/images/AWSSMPEM-advanced-store-type-dialog.svg
new file mode 100644
index 0000000..4bd468b
--- /dev/null
+++ b/docsource/images/AWSSMPEM-advanced-store-type-dialog.svg
@@ -0,0 +1,67 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-basic-store-type-dialog.svg b/docsource/images/AWSSMPEM-basic-store-type-dialog.svg
new file mode 100644
index 0000000..e618a0b
--- /dev/null
+++ b/docsource/images/AWSSMPEM-basic-store-type-dialog.svg
@@ -0,0 +1,82 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-DefaultSdkAssumeRole-dialog.svg b/docsource/images/AWSSMPEM-custom-field-DefaultSdkAssumeRole-dialog.svg
new file mode 100644
index 0000000..efa7a36
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-DefaultSdkAssumeRole-dialog.svg
@@ -0,0 +1,55 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-ExternalId-dialog.svg b/docsource/images/AWSSMPEM-custom-field-ExternalId-dialog.svg
new file mode 100644
index 0000000..8989eee
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-ExternalId-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-ExternalId-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-ExternalId-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-ExternalId-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-IAMUserAccessKey-dialog.svg b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessKey-dialog.svg
new file mode 100644
index 0000000..3a0525c
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessKey-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-IAMUserAccessKey-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessKey-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessKey-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-IAMUserAccessSecret-dialog.svg b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessSecret-dialog.svg
new file mode 100644
index 0000000..9b9dcf5
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessSecret-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-IAMUserAccessSecret-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessSecret-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-IAMUserAccessSecret-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-IncludeChain-dialog.svg b/docsource/images/AWSSMPEM-custom-field-IncludeChain-dialog.svg
new file mode 100644
index 0000000..7a20733
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-IncludeChain-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-IncludeChain-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-IncludeChain-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-IncludeChain-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthClientId-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthClientId-dialog.svg
new file mode 100644
index 0000000..100cbd7
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthClientId-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthClientId-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthClientId-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthClientId-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthClientSecret-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthClientSecret-dialog.svg
new file mode 100644
index 0000000..96ab072
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthClientSecret-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthClientSecret-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthClientSecret-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthClientSecret-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthGrantType-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthGrantType-dialog.svg
new file mode 100644
index 0000000..9b8aa94
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthGrantType-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthGrantType-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthGrantType-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthGrantType-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthScope-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthScope-dialog.svg
new file mode 100644
index 0000000..71d8a33
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthScope-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthScope-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthScope-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthScope-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthUrl-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthUrl-dialog.svg
new file mode 100644
index 0000000..9b81e1e
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthUrl-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-OAuthUrl-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-OAuthUrl-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-OAuthUrl-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-SeparatePrivateKey-dialog.svg b/docsource/images/AWSSMPEM-custom-field-SeparatePrivateKey-dialog.svg
new file mode 100644
index 0000000..7f31d97
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-SeparatePrivateKey-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-SeparatePrivateKey-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-SeparatePrivateKey-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-SeparatePrivateKey-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-UseDefaultSdkAuth-dialog.svg b/docsource/images/AWSSMPEM-custom-field-UseDefaultSdkAuth-dialog.svg
new file mode 100644
index 0000000..0d6e3d6
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-UseDefaultSdkAuth-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-UseIAM-dialog.svg b/docsource/images/AWSSMPEM-custom-field-UseIAM-dialog.svg
new file mode 100644
index 0000000..8cae692
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-UseIAM-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-UseIAM-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-UseIAM-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-UseIAM-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-UseOAuth-dialog.svg b/docsource/images/AWSSMPEM-custom-field-UseOAuth-dialog.svg
new file mode 100644
index 0000000..a452767
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-UseOAuth-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-field-UseOAuth-validation-options-dialog.svg b/docsource/images/AWSSMPEM-custom-field-UseOAuth-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-field-UseOAuth-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-custom-fields-store-type-dialog.svg b/docsource/images/AWSSMPEM-custom-fields-store-type-dialog.svg
new file mode 100644
index 0000000..5be7aad
--- /dev/null
+++ b/docsource/images/AWSSMPEM-custom-fields-store-type-dialog.svg
@@ -0,0 +1,166 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg
new file mode 100644
index 0000000..ddaa9ab
--- /dev/null
+++ b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg
@@ -0,0 +1,68 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-CertificateTags.svg b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-CertificateTags.svg
new file mode 100644
index 0000000..a898262
--- /dev/null
+++ b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-CertificateTags.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg
new file mode 100644
index 0000000..ddaa9ab
--- /dev/null
+++ b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg
@@ -0,0 +1,68 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-ReplicaRegions.svg b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-ReplicaRegions.svg
new file mode 100644
index 0000000..8003286
--- /dev/null
+++ b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog-ReplicaRegions.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog.svg b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog.svg
new file mode 100644
index 0000000..ac80c13
--- /dev/null
+++ b/docsource/images/AWSSMPEM-entry-parameters-store-type-dialog.svg
@@ -0,0 +1,62 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-advanced-store-type-dialog.svg b/docsource/images/AWSSMPFX-advanced-store-type-dialog.svg
new file mode 100644
index 0000000..4bd468b
--- /dev/null
+++ b/docsource/images/AWSSMPFX-advanced-store-type-dialog.svg
@@ -0,0 +1,67 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-basic-store-type-dialog.svg b/docsource/images/AWSSMPFX-basic-store-type-dialog.svg
new file mode 100644
index 0000000..9b90e2e
--- /dev/null
+++ b/docsource/images/AWSSMPFX-basic-store-type-dialog.svg
@@ -0,0 +1,82 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-DefaultSdkAssumeRole-dialog.svg b/docsource/images/AWSSMPFX-custom-field-DefaultSdkAssumeRole-dialog.svg
new file mode 100644
index 0000000..efa7a36
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-DefaultSdkAssumeRole-dialog.svg
@@ -0,0 +1,55 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-DefaultSdkAssumeRole-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-ExternalId-dialog.svg b/docsource/images/AWSSMPFX-custom-field-ExternalId-dialog.svg
new file mode 100644
index 0000000..f4430a9
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-ExternalId-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-ExternalId-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-ExternalId-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-ExternalId-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-IAMUserAccessKey-dialog.svg b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessKey-dialog.svg
new file mode 100644
index 0000000..2c7e9b1
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessKey-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-IAMUserAccessKey-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessKey-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessKey-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-IAMUserAccessSecret-dialog.svg b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessSecret-dialog.svg
new file mode 100644
index 0000000..95d1ebe
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessSecret-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-IAMUserAccessSecret-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessSecret-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-IAMUserAccessSecret-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthClientId-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthClientId-dialog.svg
new file mode 100644
index 0000000..9b2d27b
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthClientId-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthClientId-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthClientId-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthClientId-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthClientSecret-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthClientSecret-dialog.svg
new file mode 100644
index 0000000..58727c8
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthClientSecret-dialog.svg
@@ -0,0 +1,49 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthClientSecret-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthClientSecret-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthClientSecret-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthGrantType-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthGrantType-dialog.svg
new file mode 100644
index 0000000..9b8aa94
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthGrantType-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthGrantType-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthGrantType-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthGrantType-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthScope-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthScope-dialog.svg
new file mode 100644
index 0000000..71d8a33
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthScope-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthScope-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthScope-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthScope-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthUrl-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthUrl-dialog.svg
new file mode 100644
index 0000000..9b81e1e
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthUrl-dialog.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-OAuthUrl-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-OAuthUrl-validation-options-dialog.svg
new file mode 100644
index 0000000..22f8bbd
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-OAuthUrl-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-UseDefaultSdkAuth-dialog.svg b/docsource/images/AWSSMPFX-custom-field-UseDefaultSdkAuth-dialog.svg
new file mode 100644
index 0000000..d95def0
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-UseDefaultSdkAuth-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-UseDefaultSdkAuth-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-UseIAM-dialog.svg b/docsource/images/AWSSMPFX-custom-field-UseIAM-dialog.svg
new file mode 100644
index 0000000..6628f5f
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-UseIAM-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-UseIAM-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-UseIAM-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-UseIAM-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-UseOAuth-dialog.svg b/docsource/images/AWSSMPFX-custom-field-UseOAuth-dialog.svg
new file mode 100644
index 0000000..84d8b48
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-UseOAuth-dialog.svg
@@ -0,0 +1,54 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-field-UseOAuth-validation-options-dialog.svg b/docsource/images/AWSSMPFX-custom-field-UseOAuth-validation-options-dialog.svg
new file mode 100644
index 0000000..7993c23
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-field-UseOAuth-validation-options-dialog.svg
@@ -0,0 +1,39 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-custom-fields-store-type-dialog.svg b/docsource/images/AWSSMPFX-custom-fields-store-type-dialog.svg
new file mode 100644
index 0000000..98608d8
--- /dev/null
+++ b/docsource/images/AWSSMPFX-custom-fields-store-type-dialog.svg
@@ -0,0 +1,148 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg
new file mode 100644
index 0000000..ddaa9ab
--- /dev/null
+++ b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-CertificateTags-validation-options.svg
@@ -0,0 +1,68 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-CertificateTags.svg b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-CertificateTags.svg
new file mode 100644
index 0000000..a898262
--- /dev/null
+++ b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-CertificateTags.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg
new file mode 100644
index 0000000..ddaa9ab
--- /dev/null
+++ b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-ReplicaRegions-validation-options.svg
@@ -0,0 +1,68 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-ReplicaRegions.svg b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-ReplicaRegions.svg
new file mode 100644
index 0000000..8003286
--- /dev/null
+++ b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog-ReplicaRegions.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog.svg b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog.svg
new file mode 100644
index 0000000..ac80c13
--- /dev/null
+++ b/docsource/images/AWSSMPFX-entry-parameters-store-type-dialog.svg
@@ -0,0 +1,62 @@
+
+
\ No newline at end of file
diff --git a/integration-manifest.json b/integration-manifest.json
index b17b0ea..366c6d1 100644
--- a/integration-manifest.json
+++ b/integration-manifest.json
@@ -40,6 +40,16 @@
"IsPAMEligible": false,
"Description": "When enabled, the certificate is stored as a JSON document with separate 'certificate' (PEM certificate and chain, leaf first) and 'private_key' (PEM) properties, rather than a single concatenated PEM string."
},
+ {
+ "Name": "IncludeChain",
+ "DisplayName": "Include certificate chain in PEM",
+ "Type": "Bool",
+ "DependsOn": "",
+ "DefaultValue": "false",
+ "Required": false,
+ "IsPAMEligible": false,
+ "Description": "When enabled, the single concatenated PEM secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key. Only applies when 'Store as JSON with separate private key' is disabled; the JSON format always includes the chain."
+ },
{
"Name": "UseDefaultSdkAuth",
"DisplayName": "Use Default SDK Auth",
diff --git a/scripts/store_types/bash/curl_create_store_types.sh b/scripts/store_types/bash/curl_create_store_types.sh
index 3dc9854..edf39ac 100755
--- a/scripts/store_types/bash/curl_create_store_types.sh
+++ b/scripts/store_types/bash/curl_create_store_types.sh
@@ -1,78 +1,20 @@
-#!/usr/bin/env bash
+#!/bin/bash
+# Store Type creation script using curl
+# Generated by Doctool
-# Creates all 3 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.
+set -e
-if [ -z "${KEYFACTOR_HOSTNAME}" ]; then
- echo "ERROR: KEYFACTOR_HOSTNAME is required"
- exit 1
-fi
+# Configuration - set these variables before running
+KEYFACTOR_HOSTNAME="${KEYFACTOR_HOSTNAME}"
+KEYFACTOR_API_PATH="${KEYFACTOR_API_PATH:-KeyfactorAPI}"
+KEYFACTOR_AUTH_TOKEN="${KEYFACTOR_AUTH_TOKEN}"
-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
-}
-
-# ---------------------------------------------------------------------------
-# AWSSMPEM — AWSSMPEM
-# ---------------------------------------------------------------------------
-create_store_type "AWSSMPEM" '{
+echo "Creating store type: AWSSMPEM"
+curl -s -X POST "https://${KEYFACTOR_HOSTNAME}/${KEYFACTOR_API_PATH}/CertificateStoreTypes" \
+ -H "Authorization: Bearer ${KEYFACTOR_AUTH_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -H "x-keyfactor-requested-with: APIClient" \
+ -d '{
"Name": "AwsSecretsManager PEM",
"ShortName": "AWSSMPEM",
"Capability": "AWSSMPEM",
@@ -92,7 +34,18 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "When enabled, the certificate is stored as a JSON document with separate 'certificate' (PEM certificate and chain, leaf first) and 'private_key' (PEM) properties, rather than a single concatenated PEM string."
+ },
+ {
+ "Name": "IncludeChain",
+ "DisplayName": "Include certificate chain in PEM",
+ "Type": "Bool",
+ "DependsOn": "",
+ "DefaultValue": "false",
+ "Required": false,
+ "IsPAMEligible": false,
+ "Description": "When enabled, the single concatenated PEM secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key. Only applies when 'Store as JSON with separate private key' is disabled; the JSON format always includes the chain."
},
{
"Name": "UseDefaultSdkAuth",
@@ -101,7 +54,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use Default SDK credentials"
},
{
"Name": "DefaultSdkAssumeRole",
@@ -110,7 +64,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "UseDefaultSdkAuth",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to assume a new Role when using Default SDK credentials"
},
{
"Name": "UseOAuth",
@@ -119,7 +74,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use an OAuth provider workflow to authenticate with AWS"
},
{
"Name": "OAuthScope",
@@ -128,7 +84,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "UseOAuth",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "This is the OAuth Scope needed for Okta OAuth, defined in Okta"
},
{
"Name": "OAuthGrantType",
@@ -137,7 +94,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "UseOAuth",
"DefaultValue": "client_credentials",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "In OAuth 2.0, the term 'grant type' refers to the way an application gets an access token. In Okta this is `client_credentials`"
},
{
"Name": "OAuthUrl",
@@ -146,7 +104,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "UseOAuth",
"DefaultValue": "https://***/oauth2/default/v1/token",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "The token endpoint for the OAuth 2.0 provider"
},
{
"Name": "OAuthClientId",
@@ -155,7 +114,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client ID for OAuth."
},
{
"Name": "OAuthClientSecret",
@@ -164,7 +124,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client Secret for OAuth."
},
{
"Name": "UseIAM",
@@ -173,7 +134,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS"
},
{
"Name": "IAMUserAccessKey",
@@ -182,7 +144,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Key for an IAM User"
},
{
"Name": "IAMUserAccessSecret",
@@ -191,7 +154,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Secret for an IAM User."
},
{
"Name": "ExternalId",
@@ -200,7 +164,8 @@ create_store_type "AWSSMPEM" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "An optional parameter sts:ExternalId to pass with Assume Role calls"
}
],
"EntryParameters": [
@@ -238,7 +203,6 @@ create_store_type "AWSSMPEM" '{
},
"StorePathType": "",
"StorePathValue": "",
- "StorePathDescription": "The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' ",
"PrivateKeyAllowed": "Optional",
"JobProperties": [],
"ServerRequired": false,
@@ -247,10 +211,12 @@ create_store_type "AWSSMPEM" '{
"CustomAliasAllowed": "Required"
}'
-# ---------------------------------------------------------------------------
-# AWSSMPFX — AWSSMPFX
-# ---------------------------------------------------------------------------
-create_store_type "AWSSMPFX" '{
+echo "Creating store type: AWSSMPFX"
+curl -s -X POST "https://${KEYFACTOR_HOSTNAME}/${KEYFACTOR_API_PATH}/CertificateStoreTypes" \
+ -H "Authorization: Bearer ${KEYFACTOR_AUTH_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -H "x-keyfactor-requested-with: APIClient" \
+ -d '{
"Name": "AwsSecretsManager PFX",
"ShortName": "AWSSMPFX",
"Capability": "AWSSMPFX",
@@ -270,7 +236,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use Default SDK credentials"
},
{
"Name": "DefaultSdkAssumeRole",
@@ -279,7 +246,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "UseDefaultSdkAuth",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to assume a new Role when using Default SDK credentials"
},
{
"Name": "UseOAuth",
@@ -288,7 +256,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use an OAuth provider workflow to authenticate with AWS"
},
{
"Name": "OAuthScope",
@@ -297,7 +266,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "UseOAuth",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "This is the OAuth Scope needed for Okta OAuth, defined in Okta"
},
{
"Name": "OAuthGrantType",
@@ -306,7 +276,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "UseOAuth",
"DefaultValue": "client_credentials",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "In OAuth 2.0, the term 'grant type' refers to the way an application gets an access token. In Okta this is `client_credentials`"
},
{
"Name": "OAuthUrl",
@@ -315,7 +286,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "UseOAuth",
"DefaultValue": "https://***/oauth2/default/v1/token",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "The token endpoint for the OAuth 2.0 provider"
},
{
"Name": "OAuthClientId",
@@ -324,7 +296,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client ID for OAuth."
},
{
"Name": "OAuthClientSecret",
@@ -333,7 +306,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client Secret for OAuth."
},
{
"Name": "UseIAM",
@@ -342,7 +316,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS"
},
{
"Name": "IAMUserAccessKey",
@@ -351,7 +326,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Key for an IAM User"
},
{
"Name": "IAMUserAccessSecret",
@@ -360,7 +336,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Secret for an IAM User."
},
{
"Name": "ExternalId",
@@ -369,7 +346,8 @@ create_store_type "AWSSMPFX" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "An optional parameter sts:ExternalId to pass with Assume Role calls"
}
],
"EntryParameters": [
@@ -407,7 +385,6 @@ create_store_type "AWSSMPFX" '{
},
"StorePathType": "",
"StorePathValue": "",
- "StorePathDescription": "The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' ",
"PrivateKeyAllowed": "Optional",
"JobProperties": [],
"ServerRequired": false,
@@ -416,10 +393,12 @@ create_store_type "AWSSMPFX" '{
"CustomAliasAllowed": "Required"
}'
-# ---------------------------------------------------------------------------
-# AWSSMJKS — AWSSMJKS
-# ---------------------------------------------------------------------------
-create_store_type "AWSSMJKS" '{
+echo "Creating store type: AWSSMJKS"
+curl -s -X POST "https://${KEYFACTOR_HOSTNAME}/${KEYFACTOR_API_PATH}/CertificateStoreTypes" \
+ -H "Authorization: Bearer ${KEYFACTOR_AUTH_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -H "x-keyfactor-requested-with: APIClient" \
+ -d '{
"Name": "AwsSecretsManager JKS",
"ShortName": "AWSSMJKS",
"Capability": "AWSSMJKS",
@@ -439,7 +418,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use Default SDK credentials"
},
{
"Name": "DefaultSdkAssumeRole",
@@ -448,7 +428,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "UseDefaultSdkAuth",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to assume a new Role when using Default SDK credentials"
},
{
"Name": "UseOAuth",
@@ -457,7 +438,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use an OAuth provider workflow to authenticate with AWS"
},
{
"Name": "OAuthScope",
@@ -466,7 +448,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "UseOAuth",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "This is the OAuth Scope needed for Okta OAuth, defined in Okta"
},
{
"Name": "OAuthGrantType",
@@ -475,7 +458,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "UseOAuth",
"DefaultValue": "client_credentials",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "In OAuth 2.0, the term 'grant type' refers to the way an application gets an access token. In Okta this is `client_credentials`"
},
{
"Name": "OAuthUrl",
@@ -484,7 +468,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "UseOAuth",
"DefaultValue": "https://***/oauth2/default/v1/token",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "The token endpoint for the OAuth 2.0 provider"
},
{
"Name": "OAuthClientId",
@@ -493,7 +478,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client ID for OAuth."
},
{
"Name": "OAuthClientSecret",
@@ -502,7 +488,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client Secret for OAuth."
},
{
"Name": "UseIAM",
@@ -511,7 +498,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS"
},
{
"Name": "IAMUserAccessKey",
@@ -520,7 +508,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Key for an IAM User"
},
{
"Name": "IAMUserAccessSecret",
@@ -529,7 +518,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Secret for an IAM User."
},
{
"Name": "ExternalId",
@@ -538,7 +528,8 @@ create_store_type "AWSSMJKS" '{
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "An optional parameter sts:ExternalId to pass with Assume Role calls"
}
],
"EntryParameters": [
@@ -576,7 +567,6 @@ create_store_type "AWSSMJKS" '{
},
"StorePathType": "",
"StorePathValue": "",
- "StorePathDescription": "The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' ",
"PrivateKeyAllowed": "Optional",
"JobProperties": [],
"ServerRequired": false,
@@ -585,5 +575,3 @@ create_store_type "AWSSMJKS" '{
"CustomAliasAllowed": "Required"
}'
-
-echo "Completed."
diff --git a/scripts/store_types/bash/kfutil_create_store_types.sh b/scripts/store_types/bash/kfutil_create_store_types.sh
index ccef5f1..2c41837 100755
--- a/scripts/store_types/bash/kfutil_create_store_types.sh
+++ b/scripts/store_types/bash/kfutil_create_store_types.sh
@@ -1,30 +1,15 @@
-#!/usr/bin/env bash
+#!/bin/bash
+# Store Type creation script using kfutil
+# Generated by Doctool
-# Creates all 3 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.
+set -e
-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
+echo "Creating store type: AWSSMPEM"
+kfutil store-types create AWSSMPEM
-if [ -z "$KEYFACTOR_HOSTNAME" ]; then
- echo "KEYFACTOR_HOSTNAME not set — launching kfutil login"
- kfutil login
-fi
+echo "Creating store type: AWSSMPFX"
+kfutil store-types create AWSSMPFX
-kfutil store-types create --name "AWSSMPEM"
-kfutil store-types create --name "AWSSMPFX"
-kfutil store-types create --name "AWSSMJKS"
+echo "Creating store type: AWSSMJKS"
+kfutil store-types create AWSSMJKS
-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
index 6a69daf..ce3139c 100644
--- a/scripts/store_types/powershell/kfutil_create_store_types.ps1
+++ b/scripts/store_types/powershell/kfutil_create_store_types.ps1
@@ -1,31 +1,12 @@
-# Creates all 3 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.
+# Store Type creation script using kfutil
+# Generated by Doctool
-# Uncomment if kfutil is not in your PATH
-# Set-Alias -Name kfutil -Value 'C:\Program Files\Keyfactor\kfutil\kfutil.exe'
+Write-Host "Creating store type: AWSSMPEM"
+kfutil store-types create AWSSMPEM
-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
-}
+Write-Host "Creating store type: AWSSMPFX"
+kfutil store-types create AWSSMPFX
-if (-not $env:KEYFACTOR_HOSTNAME) {
- Write-Host "KEYFACTOR_HOSTNAME not set — launching kfutil login"
- & kfutil login
-}
+Write-Host "Creating store type: AWSSMJKS"
+kfutil store-types create AWSSMJKS
-& kfutil store-types create --name "AWSSMPEM"
-& kfutil store-types create --name "AWSSMPFX"
-& kfutil store-types create --name "AWSSMJKS"
-
-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
index ff25e9a..0b793a1 100644
--- a/scripts/store_types/powershell/restmethod_create_store_types.ps1
+++ b/scripts/store_types/powershell/restmethod_create_store_types.ps1
@@ -1,70 +1,19 @@
-# Creates all 3 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.
+# Store Type creation script using Invoke-RestMethod
+# Generated by Doctool
-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
-}
+# Configuration - set these variables before running
+$KeyfactorHostname = $env:KEYFACTOR_HOSTNAME
+$KeyfactorApiPath = if ($env:KEYFACTOR_API_PATH) { $env:KEYFACTOR_API_PATH } else { "KeyfactorAPI" }
+$KeyfactorAuthToken = $env:KEYFACTOR_AUTH_TOKEN
-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)"
- }
+$Headers = @{
+ "Authorization" = "Bearer $KeyfactorAuthToken"
+ "Content-Type" = "application/json"
+ "x-keyfactor-requested-with" = "APIClient"
}
-# ---------------------------------------------------------------------------
-# AWSSMPEM — AWSSMPEM
-# ---------------------------------------------------------------------------
-New-StoreType "AWSSMPEM" @'
+Write-Host "Creating store type: AWSSMPEM"
+$Body = @'
{
"Name": "AwsSecretsManager PEM",
"ShortName": "AWSSMPEM",
@@ -85,7 +34,18 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "When enabled, the certificate is stored as a JSON document with separate 'certificate' (PEM certificate and chain, leaf first) and 'private_key' (PEM) properties, rather than a single concatenated PEM string."
+ },
+ {
+ "Name": "IncludeChain",
+ "DisplayName": "Include certificate chain in PEM",
+ "Type": "Bool",
+ "DependsOn": "",
+ "DefaultValue": "false",
+ "Required": false,
+ "IsPAMEligible": false,
+ "Description": "When enabled, the single concatenated PEM secret contains the leaf certificate, followed by the issuer chain (leaf first), followed by the private key. Only applies when 'Store as JSON with separate private key' is disabled; the JSON format always includes the chain."
},
{
"Name": "UseDefaultSdkAuth",
@@ -94,7 +54,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use Default SDK credentials"
},
{
"Name": "DefaultSdkAssumeRole",
@@ -103,7 +64,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "UseDefaultSdkAuth",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to assume a new Role when using Default SDK credentials"
},
{
"Name": "UseOAuth",
@@ -112,7 +74,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use an OAuth provider workflow to authenticate with AWS"
},
{
"Name": "OAuthScope",
@@ -121,7 +84,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "UseOAuth",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "This is the OAuth Scope needed for Okta OAuth, defined in Okta"
},
{
"Name": "OAuthGrantType",
@@ -130,7 +94,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "UseOAuth",
"DefaultValue": "client_credentials",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "In OAuth 2.0, the term 'grant type' refers to the way an application gets an access token. In Okta this is `client_credentials`"
},
{
"Name": "OAuthUrl",
@@ -139,7 +104,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "UseOAuth",
"DefaultValue": "https://***/oauth2/default/v1/token",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "The token endpoint for the OAuth 2.0 provider"
},
{
"Name": "OAuthClientId",
@@ -148,7 +114,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client ID for OAuth."
},
{
"Name": "OAuthClientSecret",
@@ -157,7 +124,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client Secret for OAuth."
},
{
"Name": "UseIAM",
@@ -166,7 +134,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS"
},
{
"Name": "IAMUserAccessKey",
@@ -175,7 +144,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Key for an IAM User"
},
{
"Name": "IAMUserAccessSecret",
@@ -184,7 +154,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Secret for an IAM User."
},
{
"Name": "ExternalId",
@@ -193,7 +164,8 @@ New-StoreType "AWSSMPEM" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "An optional parameter sts:ExternalId to pass with Assume Role calls"
}
],
"EntryParameters": [
@@ -231,7 +203,6 @@ New-StoreType "AWSSMPEM" @'
},
"StorePathType": "",
"StorePathValue": "",
- "StorePathDescription": "The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' ",
"PrivateKeyAllowed": "Optional",
"JobProperties": [],
"ServerRequired": false,
@@ -241,10 +212,10 @@ New-StoreType "AWSSMPEM" @'
}
'@
-# ---------------------------------------------------------------------------
-# AWSSMPFX — AWSSMPFX
-# ---------------------------------------------------------------------------
-New-StoreType "AWSSMPFX" @'
+Invoke-RestMethod -Uri "https://$KeyfactorHostname/$KeyfactorApiPath/CertificateStoreTypes" -Method POST -Headers $Headers -Body $Body
+
+Write-Host "Creating store type: AWSSMPFX"
+$Body = @'
{
"Name": "AwsSecretsManager PFX",
"ShortName": "AWSSMPFX",
@@ -265,7 +236,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use Default SDK credentials"
},
{
"Name": "DefaultSdkAssumeRole",
@@ -274,7 +246,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "UseDefaultSdkAuth",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to assume a new Role when using Default SDK credentials"
},
{
"Name": "UseOAuth",
@@ -283,7 +256,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use an OAuth provider workflow to authenticate with AWS"
},
{
"Name": "OAuthScope",
@@ -292,7 +266,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "UseOAuth",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "This is the OAuth Scope needed for Okta OAuth, defined in Okta"
},
{
"Name": "OAuthGrantType",
@@ -301,7 +276,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "UseOAuth",
"DefaultValue": "client_credentials",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "In OAuth 2.0, the term 'grant type' refers to the way an application gets an access token. In Okta this is `client_credentials`"
},
{
"Name": "OAuthUrl",
@@ -310,7 +286,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "UseOAuth",
"DefaultValue": "https://***/oauth2/default/v1/token",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "The token endpoint for the OAuth 2.0 provider"
},
{
"Name": "OAuthClientId",
@@ -319,7 +296,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client ID for OAuth."
},
{
"Name": "OAuthClientSecret",
@@ -328,7 +306,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client Secret for OAuth."
},
{
"Name": "UseIAM",
@@ -337,7 +316,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS"
},
{
"Name": "IAMUserAccessKey",
@@ -346,7 +326,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Key for an IAM User"
},
{
"Name": "IAMUserAccessSecret",
@@ -355,7 +336,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Secret for an IAM User."
},
{
"Name": "ExternalId",
@@ -364,7 +346,8 @@ New-StoreType "AWSSMPFX" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "An optional parameter sts:ExternalId to pass with Assume Role calls"
}
],
"EntryParameters": [
@@ -402,7 +385,6 @@ New-StoreType "AWSSMPFX" @'
},
"StorePathType": "",
"StorePathValue": "",
- "StorePathDescription": "The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' ",
"PrivateKeyAllowed": "Optional",
"JobProperties": [],
"ServerRequired": false,
@@ -412,10 +394,10 @@ New-StoreType "AWSSMPFX" @'
}
'@
-# ---------------------------------------------------------------------------
-# AWSSMJKS — AWSSMJKS
-# ---------------------------------------------------------------------------
-New-StoreType "AWSSMJKS" @'
+Invoke-RestMethod -Uri "https://$KeyfactorHostname/$KeyfactorApiPath/CertificateStoreTypes" -Method POST -Headers $Headers -Body $Body
+
+Write-Host "Creating store type: AWSSMJKS"
+$Body = @'
{
"Name": "AwsSecretsManager JKS",
"ShortName": "AWSSMJKS",
@@ -436,7 +418,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use Default SDK credentials"
},
{
"Name": "DefaultSdkAssumeRole",
@@ -445,7 +428,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "UseDefaultSdkAuth",
"DefaultValue": "false",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to assume a new Role when using Default SDK credentials"
},
{
"Name": "UseOAuth",
@@ -454,7 +438,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use an OAuth provider workflow to authenticate with AWS"
},
{
"Name": "OAuthScope",
@@ -463,7 +448,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "UseOAuth",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "This is the OAuth Scope needed for Okta OAuth, defined in Okta"
},
{
"Name": "OAuthGrantType",
@@ -472,7 +458,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "UseOAuth",
"DefaultValue": "client_credentials",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "In OAuth 2.0, the term 'grant type' refers to the way an application gets an access token. In Okta this is `client_credentials`"
},
{
"Name": "OAuthUrl",
@@ -481,7 +468,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "UseOAuth",
"DefaultValue": "https://***/oauth2/default/v1/token",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "The token endpoint for the OAuth 2.0 provider"
},
{
"Name": "OAuthClientId",
@@ -490,7 +478,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client ID for OAuth."
},
{
"Name": "OAuthClientSecret",
@@ -499,7 +488,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The Client Secret for OAuth."
},
{
"Name": "UseIAM",
@@ -508,7 +498,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "false",
"Required": true,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "A switch to enable the store to use IAM User auth to assume a role when authenticating with AWS"
},
{
"Name": "IAMUserAccessKey",
@@ -517,7 +508,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Key for an IAM User"
},
{
"Name": "IAMUserAccessSecret",
@@ -526,7 +518,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": true
+ "IsPAMEligible": true,
+ "Description": "The AWS Access Secret for an IAM User."
},
{
"Name": "ExternalId",
@@ -535,7 +528,8 @@ New-StoreType "AWSSMJKS" @'
"DependsOn": "",
"DefaultValue": "",
"Required": false,
- "IsPAMEligible": false
+ "IsPAMEligible": false,
+ "Description": "An optional parameter sts:ExternalId to pass with Assume Role calls"
}
],
"EntryParameters": [
@@ -573,7 +567,6 @@ New-StoreType "AWSSMJKS" @'
},
"StorePathType": "",
"StorePathValue": "",
- "StorePathDescription": "The store path contains the AWS region where the SecretsManager resides. It can optionally accept values for tags OR path prefix for identifying secrets to be managed by the cert store instance. example:'us-east-2 [prefix='dev/midwest']' or 'us-east1 [tagName='managedBy' tagValue='keyfactor']' ",
"PrivateKeyAllowed": "Optional",
"JobProperties": [],
"ServerRequired": false,
@@ -583,5 +576,5 @@ New-StoreType "AWSSMJKS" @'
}
'@
+Invoke-RestMethod -Uri "https://$KeyfactorHostname/$KeyfactorApiPath/CertificateStoreTypes" -Method POST -Headers $Headers -Body $Body
-Write-Host "Completed."