Create a Custom Metadata Generator Extension
If you choose not to use the included PowerShell extension to populate your Keyfactor ACME metadata Metadata provides information about a piece of data. It is used to summarize basic information about data, which can make working with the data easier. In Keyfactor Command, the certificate metadata feature allows you to create custom metadata fields that allow you to tag certificates with tracking information about certificates., you can create your own extension.
Create a custom extension for the nuget package Keyfactor. ACME.Extensions. ICertificateRequestMetadataGenerator with the following signature:
As long as the interface is implemented, the Keyfactor ACME server will be able to use any class for updating metadata.
To install your custom extension, see ACME Metadata.
Sample PowerShell Project
The sample below demonstrates what is sent to the custom extension (ICertificateRequestMetadataGenerator) using the Keyfactor ACME PowerShell Executor as an example.
using CSS.Common.Logging;
using Keyfactor..Extensions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation;
using System.Text;
namespace Keyfactor..PowershellDriver
{
public class PowershellExecutor : LoggingClientBase, ICertificateRequestMetadataGenerator
{
public string scriptLocation { get; set; }
private const string PS_METADATA_VARIABLE_NAME = "metadata";
private const string PS_CSR_VARIABLE_NAME = "csr";
private const string PS_USER_VARIABLE_NAME = "username";
private const string PS_TEMPLATE_VARIABLE_NAME = "template";
private const string PS_IDENTIFIERS_VARIABLE_NAME = "identifiers";
public void GetMetadata(CertificateRequestMetadataContext context, Dictionary<string, object> currentMetadata)
{
if (string.IsNullOrWhiteSpace(scriptLocation))
{
//No need to do anything if there is not a scrpt to run.
return;
}
using (PowerShell ps = PowerShell.Create())
{
// register stream handlers
ps.Streams.Warning.DataAdded += LogWarngingEventHandler;
ps.Streams.Information.DataAdded += LogInformationEventHandler;
// specify the script code to run.
ps.AddScript(File.ReadAllText(scriptLocation));
// specify the parameters to pass into the script.
// Expose the Arguments Dictionary object to the PowerShell Runspace.
ps.Runspace.SessionStateProxy.PSVariable.Set(PS_METADATA_VARIABLE_NAME, currentMetadata);
ps.Runspace.SessionStateProxy.PSVariable.Set(PS_CSR_VARIABLE_NAME, context.CSR);
ps.Runspace.SessionStateProxy.PSVariable.Set(PS_USER_VARIABLE_NAME, context.User);
ps.Runspace.SessionStateProxy.PSVariable.Set(PS_TEMPLATE_VARIABLE_NAME, context.Template);
ps.Runspace.SessionStateProxy.PSVariable.Set(PS_IDENTIFIERS_VARIABLE_NAME, context.Identifiers);
Logger.Debug($"Running PowerShell script at location: {scriptLocation}");
Collection<PSObject> pipelineObjects = ps.Invoke();
Logger.Debug($"Script execution completed.");
if (ps.HadErrors)
{
string errorMessage = GetPsErrors(ps);
Logger.Error(errorMessage);
throw new Exception(errorMessage);
}
currentMetadata = (Dictionary<string, object>)ps.Runspace.SessionStateProxy.PSVariable.GetValue(PS_METADATA_VARIABLE_NAME);
}
}
private void LogWarngingEventHandler(object sender, DataAddedEventArgs e)
{
WarningRecord record = ((PSDataCollection<WarningRecord>)sender)[e.Index];
Logger.Warn($"Warning encountered during PowerShell script execution: {record.Message}");
}
private void LogInformationEventHandler(object sender, DataAddedEventArgs e)
{
InformationRecord record = ((PSDataCollection<InformationRecord>)sender)[e.Index];
Logger.Info($"Information encountered during PowerShell script execution: {record}");
}
private string GetPsErrors(PowerShell ps)
{
StringBuilder errorBuilder = new StringBuilder();
errorBuilder.AppendLine("The following errors occurred running the provided PowerShell script: ");
foreach (ErrorRecord error in ps.Streams.Error.ReadAll())
{
errorBuilder.AppendLine(LogHandler.FlattenException(error.Exception, true));
}
return errorBuilder.ToString();
}
}
}