Skip to content

Commit 5b12125

Browse files
leefine02KeyfactorLee Fine
authored
release: 4.0
--------- Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io> Co-authored-by: Lee Fine <lfine@keyfactor.com>
1 parent 8392e61 commit 5b12125

File tree

130 files changed

+462
-41
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+462
-41
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
v4.0.0
2+
- Added ability to run post job commands for Management-Add and ODKG jobs.
3+
- Added "+" as an allowed character for store paths and file names
4+
- Bug Fix: Issue adding certificates without private keys introduced in 3.0.0
5+
- Bug Fix: Issue creating stores on a Linux UO in agent mode (client machine value ending in |LocalMachine)
6+
- Bug Fix: Logging issue with RFORA
7+
18
v3.0.0
29
- Added support for post quantum ML-DSA certificates for store types RFPEM, RFJKS, RFPkcs12, and RFDER
310
- Added support for On Device Key Generation (ODKG)

README.md

Lines changed: 134 additions & 2 deletions
Large diffs are not rendered by default.

RemoteFile/ApplicationSettings.cs

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.Extensions.Logging;
1414
using Keyfactor.Logging;
1515
using System.Reflection;
16+
using Microsoft.PowerShell;
1617

1718

1819
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
@@ -24,25 +25,25 @@ public class ApplicationSettings
2425
private const string DEFAULT_SUDO_IMPERSONATION_SETTING = "";
2526
private const int DEFAULT_SSH_PORT = 22;
2627

27-
private static Dictionary<string,string> configuration;
28-
29-
public static bool UseSudo { get { return configuration.ContainsKey("UseSudo") ? configuration["UseSudo"]?.ToUpper() == "Y" : false; } }
30-
public static bool CreateStoreIfMissing { get { return configuration.ContainsKey("CreateStoreIfMissing") ? configuration["CreateStoreIfMissing"]?.ToUpper() == "Y" : false; } }
31-
public static bool UseNegotiate { get { return configuration.ContainsKey("UseNegotiate") ? configuration["UseNegotiate"]?.ToUpper() == "Y" : false; } }
32-
public static string SeparateUploadFilePath { get { return configuration.ContainsKey("SeparateUploadFilePath") ? AddTrailingSlash(configuration["SeparateUploadFilePath"]) : string.Empty; } }
33-
public static string DefaultLinuxPermissionsOnStoreCreation { get { return configuration.ContainsKey("DefaultLinuxPermissionsOnStoreCreation") ? configuration["DefaultLinuxPermissionsOnStoreCreation"] : DEFAULT_LINUX_PERMISSION_SETTING; } }
34-
public static string DefaultOwnerOnStoreCreation { get { return configuration.ContainsKey("DefaultOwnerOnStoreCreation") ? configuration["DefaultOwnerOnStoreCreation"] : DEFAULT_OWNER_SETTING; } }
35-
public static string DefaultSudoImpersonatedUser { get { return configuration.ContainsKey("DefaultSudoImpersonatedUser") ? configuration["DefaultSudoImpersonatedUser"] : DEFAULT_SUDO_IMPERSONATION_SETTING; } }
36-
public static string TempFilePathForODKG { get { return configuration.ContainsKey("TempFilePathForODKG") ? configuration["TempFilePathForODKG"] : string.Empty; } }
37-
public static bool UseShellCommands { get { return configuration.ContainsKey("UseShellCommands") ? configuration["UseShellCommands"]?.ToUpper() == "Y" : true; } }
28+
private static Dictionary<string,object> configuration;
29+
30+
public static bool UseSudo { get { return configuration.ContainsKey("UseSudo") ? configuration["UseSudo"]?.ToString().ToUpper() == "Y" : false; } }
31+
public static bool CreateStoreIfMissing { get { return configuration.ContainsKey("CreateStoreIfMissing") ? configuration["CreateStoreIfMissing"]?.ToString().ToUpper() == "Y" : false; } }
32+
public static bool UseNegotiate { get { return configuration.ContainsKey("UseNegotiate") ? configuration["UseNegotiate"]?.ToString().ToUpper() == "Y" : false; } }
33+
public static string SeparateUploadFilePath { get { return configuration.ContainsKey("SeparateUploadFilePath") ? AddTrailingSlash(configuration["SeparateUploadFilePath"].ToString()) : string.Empty; } }
34+
public static string DefaultLinuxPermissionsOnStoreCreation { get { return configuration.ContainsKey("DefaultLinuxPermissionsOnStoreCreation") ? configuration["DefaultLinuxPermissionsOnStoreCreation"].ToString() : DEFAULT_LINUX_PERMISSION_SETTING; } }
35+
public static string DefaultOwnerOnStoreCreation { get { return configuration.ContainsKey("DefaultOwnerOnStoreCreation") ? configuration["DefaultOwnerOnStoreCreation"].ToString() : DEFAULT_OWNER_SETTING; } }
36+
public static string DefaultSudoImpersonatedUser { get { return configuration.ContainsKey("DefaultSudoImpersonatedUser") ? configuration["DefaultSudoImpersonatedUser"].ToString() : DEFAULT_SUDO_IMPERSONATION_SETTING; } }
37+
public static string TempFilePathForODKG { get { return configuration.ContainsKey("TempFilePathForODKG") ? configuration["TempFilePathForODKG"].ToString() : string.Empty; } }
38+
public static bool UseShellCommands { get { return configuration.ContainsKey("UseShellCommands") ? configuration["UseShellCommands"]?.ToString().ToUpper() == "Y" : true; } }
3839
public static int SSHPort
3940
{
4041
get
4142
{
42-
if (configuration.ContainsKey("SSHPort") && !string.IsNullOrEmpty(configuration["SSHPort"]))
43+
if (configuration.ContainsKey("SSHPort") && !string.IsNullOrEmpty(configuration["SSHPort"]?.ToString()))
4344
{
4445
int sshPort;
45-
if (int.TryParse(configuration["SSHPort"], out sshPort))
46+
if (int.TryParse(configuration["SSHPort"]?.ToString(), out sshPort))
4647
return sshPort;
4748
else
4849
throw new RemoteFileException($"Invalid optional config.json SSHPort value of {configuration["SSHPort"]}. If present, this must be an integer value.");
@@ -53,13 +54,27 @@ public static int SSHPort
5354
}
5455
}
5556
}
57+
public static List<PostJobCommand> PostJobCommands
58+
{
59+
get
60+
{
61+
if (configuration.ContainsKey("PostJobCommands") && configuration["PostJobCommands"] != null)
62+
{
63+
return JsonConvert.DeserializeObject<List<PostJobCommand>>(configuration["PostJobCommands"]?.ToString());
64+
}
65+
else
66+
{
67+
return new List<PostJobCommand>();
68+
}
69+
}
70+
}
5671

5772
static ApplicationSettings()
5873
{
5974
ILogger logger = LogHandler.GetClassLogger<ApplicationSettings>();
6075
logger.MethodEntry(LogLevel.Debug);
6176

62-
configuration = new Dictionary<string, string>();
77+
configuration = new Dictionary<string, object>();
6378
string configLocation = $"{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}{Path.DirectorySeparatorChar}config.json";
6479
string configContents = string.Empty;
6580

@@ -81,11 +96,18 @@ static ApplicationSettings()
8196
return;
8297
}
8398

84-
configuration = JsonConvert.DeserializeObject<Dictionary<string, string>>(configContents);
99+
try
100+
{
101+
configuration = JsonConvert.DeserializeObject<Dictionary<string, object>>(configContents);
102+
}
103+
catch (Exception ex)
104+
{
105+
throw new RemoteFileException(RemoteFileException.FlattenExceptionMessages(ex, "Error attempting to serialize config.json file. Please review your config.json file for proper formatting."));
106+
}
85107
ValidateConfiguration(logger);
86108

87109
logger.LogDebug("Configuration Settings:");
88-
foreach(KeyValuePair<string,string> keyValue in configuration)
110+
foreach(KeyValuePair<string,object> keyValue in configuration)
89111
{
90112
logger.LogDebug($" {keyValue.Key}: {keyValue.Value}");
91113
}
@@ -95,11 +117,11 @@ static ApplicationSettings()
95117

96118
private static void ValidateConfiguration(ILogger logger)
97119
{
98-
if (!configuration.ContainsKey("UseSudo") || (configuration["UseSudo"].ToUpper() != "Y" && configuration["UseSudo"].ToUpper() != "N"))
120+
if (!configuration.ContainsKey("UseSudo") || (configuration["UseSudo"]?.ToString().ToUpper() != "Y" && configuration["UseSudo"]?.ToString().ToUpper() != "N"))
99121
logger.LogDebug($"Missing or invalid configuration parameter - UseSudo. Will set to default value of 'False'");
100-
if (!configuration.ContainsKey("CreateStoreIfMissing") || (configuration["CreateStoreIfMissing"].ToUpper() != "Y" && configuration["CreateStoreIfMissing"].ToUpper() != "N"))
122+
if (!configuration.ContainsKey("CreateStoreIfMissing") || (configuration["CreateStoreIfMissing"]?.ToString().ToUpper() != "Y" && configuration["CreateStoreIfMissing"]?.ToString().ToUpper() != "N"))
101123
logger.LogDebug($"Missing or invalid configuration parameter - CreateStoreIfMissing. Will set to default value of 'False'");
102-
if (!configuration.ContainsKey("UseNegotiate") || (configuration["UseNegotiate"].ToUpper() != "Y" && configuration["UseNegotiate"].ToUpper() != "N"))
124+
if (!configuration.ContainsKey("UseNegotiate") || (configuration["UseNegotiate"]?.ToString().ToUpper() != "Y" && configuration["UseNegotiate"]?.ToString().ToUpper() != "N"))
103125
logger.LogDebug($"Missing or invalid configuration parameter - UseNegotiate. Will set to default value of 'False'");
104126
if (!configuration.ContainsKey("SeparateUploadFilePath"))
105127
logger.LogDebug($"Missing configuration parameter - SeparateUploadFilePath. Will set to default value of ''");
@@ -113,5 +135,12 @@ private static string AddTrailingSlash(string path)
113135
{
114136
return string.IsNullOrEmpty(path) ? path : path.Substring(path.Length - 1, 1) == @"/" ? path : path += @"/";
115137
}
138+
139+
public class PostJobCommand
140+
{
141+
public string Name { get; set; }
142+
public string Environment { get; set; }
143+
public string Command { get; set; }
144+
}
116145
}
117146
}

RemoteFile/ImplementedStoreTypes/OraWlt/OraWltCertificateStoreSerializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
9393
try
9494
{
9595
remoteHandler.UploadCertificateFile($"{WorkFolder}", $"{tempStoreFileJKS}", jksStoreInfo[0].Contents);
96-
remoteHandler.RunCommand(orapkiCommand1, null, ApplicationSettings.UseSudo, null);
97-
remoteHandler.RunCommand(orapkiCommand2, null, ApplicationSettings.UseSudo, null);
96+
remoteHandler.RunCommand(orapkiCommand1, null, ApplicationSettings.UseSudo, [storePassword]);
97+
remoteHandler.RunCommand(orapkiCommand2, null, ApplicationSettings.UseSudo, [storePassword]);
9898

9999
byte[] storeContents = remoteHandler.DownloadCertificateFile($"{WorkFolder}ewallet.p12");
100100

RemoteFile/ManagementBase.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Org.BouncyCastle.X509;
1616
using System;
1717
using System.IO;
18+
using System.Linq.Expressions;
1819
using static Org.BouncyCastle.Math.EC.ECCurve;
1920

2021
namespace Keyfactor.Extensions.Orchestrator.RemoteFile
@@ -55,7 +56,21 @@ public JobResult ProcessJob(ManagementJobConfiguration config)
5556
certificateStore.AddCertificate(config.JobCertificate.Alias ?? GetThumbprint(config.JobCertificate, logger), config.JobCertificate.Contents, config.Overwrite, config.JobCertificate.PrivateKeyPassword, RemoveRootCertificate);
5657
certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, StorePassword, certificateStore.RemoteHandler));
5758

58-
logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}.");
59+
try
60+
{
61+
if (!string.IsNullOrEmpty(PostJobApplicationRestart))
62+
certificateStore.RunPostJobCommand(PostJobApplicationRestart, config.CertificateStoreDetails.StorePath, certificateStoreSerializer.GetPrivateKeyPath());
63+
}
64+
catch (Exception ex)
65+
{
66+
logger.LogError($"Exception for {config.Capability} attempting post job command for {PostJobApplicationRestart}: {RemoteFileException.FlattenExceptionMessages(ex, string.Empty)} for job id {config.JobId}");
67+
return new JobResult() { Result = OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Certificate was successfully added to store, but post job command for {PostJobApplicationRestart} failed with: ") };
68+
}
69+
finally
70+
{
71+
logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}.");
72+
}
73+
5974
break;
6075

6176
case CertStoreOperationType.Remove:
@@ -112,17 +127,25 @@ private string GetThumbprint (ManagementJobCertificate jobCertificate, ILogger l
112127

113128
string thumbprint = string.Empty;
114129

115-
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(jobCertificate.Contents)))
130+
if (string.IsNullOrEmpty(jobCertificate.PrivateKeyPassword))
131+
{
132+
X509Certificate x = new X509Certificate(Convert.FromBase64String(jobCertificate.Contents));
133+
thumbprint = x.Thumbprint();
134+
}
135+
else
116136
{
117-
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
118-
Pkcs12Store store = storeBuilder.Build();
137+
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(jobCertificate.Contents)))
138+
{
139+
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
140+
Pkcs12Store store = storeBuilder.Build();
119141

120-
store.Load(ms, jobCertificate.PrivateKeyPassword.ToCharArray());
142+
store.Load(ms, jobCertificate.PrivateKeyPassword.ToCharArray());
121143

122-
foreach (string alias in store.Aliases)
123-
{
124-
thumbprint = store.GetCertificate(alias).Certificate.Thumbprint();
125-
break;
144+
foreach (string alias in store.Aliases)
145+
{
146+
thumbprint = store.GetCertificate(alias).Certificate.Thumbprint();
147+
break;
148+
}
126149
}
127150
}
128151

RemoteFile/ReenrollmentBase.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,20 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm
104104
certificateStore.AddCertificate(config.Alias ?? cert.Thumbprint, Convert.ToBase64String(cert.Export(X509ContentType.Pfx)), config.Overwrite, null, RemoveRootCertificate);
105105
certificateStore.SaveCertificateStore(certificateStoreSerializer.SerializeRemoteCertificateStore(certificateStore.GetCertificateStore(), storePathFile.Path, storePathFile.File, StorePassword, certificateStore.RemoteHandler));
106106

107-
logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}.");
107+
try
108+
{
109+
if (!string.IsNullOrEmpty(PostJobApplicationRestart))
110+
certificateStore.RunPostJobCommand(PostJobApplicationRestart, config.CertificateStoreDetails.StorePath, certificateStoreSerializer.GetPrivateKeyPath());
111+
}
112+
catch (Exception ex)
113+
{
114+
logger.LogError($"Exception for {config.Capability} attempting post job command for {PostJobApplicationRestart}: {RemoteFileException.FlattenExceptionMessages(ex, string.Empty)} for job id {config.JobId}");
115+
return new JobResult() { Result = OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, FailureMessage = RemoteFileException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Certificate was successfully added to store, but post job command for {PostJobApplicationRestart} failed with: ") };
116+
}
117+
finally
118+
{
119+
logger.LogDebug($"END add Operation for {config.CertificateStoreDetails.StorePath} on {config.CertificateStoreDetails.ClientMachine}.");
120+
}
108121
}
109122

110123
catch (Exception ex)

0 commit comments

Comments
 (0)