Skip to content

Commit b981bcf

Browse files
indroraleefine02KeyfactorLee Fine
authored
Release: 3.0 (#36)
--------- Co-authored-by: Lee Fine <50836957+leefine02@users.noreply.github.com> Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io> Co-authored-by: Lee Fine <lfine@keyfactor.com>
1 parent a10c05a commit b981bcf

76 files changed

Lines changed: 318 additions & 2438 deletions

File tree

Some content is hidden

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

.github/workflows/keyfactor-starter-workflow.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,17 @@ on:
1111

1212
jobs:
1313
call-starter-workflow:
14-
uses: keyfactor/actions/.github/workflows/starter.yml@3.1.2-rc.0
14+
uses: keyfactor/actions/.github/workflows/starter.yml@v4
15+
with:
16+
command_token_url: ${{ vars.COMMAND_TOKEN_URL }} # Only required for doctool generated screenshots
17+
command_hostname: ${{ vars.COMMAND_HOSTNAME }} # Only required for doctool generated screenshots
18+
command_base_api_path: ${{ vars.COMMAND_API_PATH }} # Only required for doctool generated screenshots
1519
secrets:
16-
token: ${{ secrets.V2BUILDTOKEN}}
17-
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
18-
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
19-
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
20-
scan_token: ${{ secrets.SAST_TOKEN }}
20+
token: ${{ secrets.V2BUILDTOKEN}} # REQUIRED
21+
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} # Only required for golang builds
22+
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} # Only required for golang builds
23+
scan_token: ${{ secrets.SAST_TOKEN }} # REQUIRED
24+
entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }} # Only required for doctool generated screenshots
25+
entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }} # Only required for doctool generated screenshots
26+
command_client_id: ${{ secrets.COMMAND_CLIENT_ID }} # Only required for doctool generated screenshots
27+
command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }} # Only required for doctool generated screenshots

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
3.0.0
2+
* Add StorePassword as an option to allow clients to encrypt private keys with passwords in situations where their Citrix settings require a password protected key
3+
* Add optional custom field to set timeout for login
4+
15
2.2.1
26
* Add ServerUsername and ServerPassword to the integration-manifest.json to add both fields to the README documentation.
37

CitrixAdcOrchestratorJobExtension.sln

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 16
4-
VisualStudioVersion = 16.0.31229.75
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.13.35931.197 d17.13
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Keyfactor.Extensions.Orchestrator.CitricAdc", "CitrixAdcOrchestratorJobExtension\Keyfactor.Extensions.Orchestrator.CitricAdc.csproj", "{2B3106BF-A1B4-4BCC-9650-597B576E14D0}"
77
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CitrixAdcTestConsole", "CitrixAdcTestConsole\CitrixAdcTestConsole.csproj", "{28D3BFB3-1484-4A4A-9BC1-3D20255943FD}"
9-
EndProject
108
Global
119
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1210
Debug|Any CPU = Debug|Any CPU
@@ -17,10 +15,6 @@ Global
1715
{2B3106BF-A1B4-4BCC-9650-597B576E14D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
1816
{2B3106BF-A1B4-4BCC-9650-597B576E14D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
1917
{2B3106BF-A1B4-4BCC-9650-597B576E14D0}.Release|Any CPU.Build.0 = Release|Any CPU
20-
{28D3BFB3-1484-4A4A-9BC1-3D20255943FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21-
{28D3BFB3-1484-4A4A-9BC1-3D20255943FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
22-
{28D3BFB3-1484-4A4A-9BC1-3D20255943FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
23-
{28D3BFB3-1484-4A4A-9BC1-3D20255943FD}.Release|Any CPU.Build.0 = Release|Any CPU
2418
EndGlobalSection
2519
GlobalSection(SolutionProperties) = preSolution
2620
HideSolutionNode = FALSE

CitrixAdcOrchestratorJobExtension/CitrixAdcStore.cs

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@
1616
using System.IO;
1717
using System.Linq;
1818
using System.Collections.Generic;
19-
using System.Runtime.ConstrainedExecution;
2019
using System.Security.Cryptography.X509Certificates;
2120
using System.Text;
22-
using System.Xml.Linq;
2321
using com.citrix.netscaler.nitro.exception;
2422
using com.citrix.netscaler.nitro.resource.Base;
2523
using com.citrix.netscaler.nitro.resource.config.ssl;
@@ -28,18 +26,22 @@
2826
using com.citrix.netscaler.nitro.util;
2927
using Keyfactor.Logging;
3028
using Keyfactor.Orchestrators.Extensions;
29+
using Keyfactor.PKI.CryptographicObjects.Formatters;
30+
using Keyfactor.PKI.PEM;
31+
using Keyfactor.PKI.PrivateKeys;
3132
using Microsoft.Extensions.Logging;
3233
using Newtonsoft.Json;
3334
using Org.BouncyCastle.Crypto;
3435
using Org.BouncyCastle.OpenSsl;
3536
using Org.BouncyCastle.Pkcs;
37+
using Keyfactor.Orchestrators.Common.Enums;
3638

3739
namespace Keyfactor.Extensions.Orchestrator.CitricAdc
3840
{
3941
// ReSharper disable once InconsistentNaming
4042
internal class CitrixAdcStore
4143
{
42-
private const uint Timeout = 3600;
44+
private const string DefaultTimeout = "3600";
4345
public static readonly string StoreType = "CitrixAdc";
4446

4547
private readonly string _clientMachine;
@@ -50,6 +52,7 @@ internal class CitrixAdcStore
5052
public readonly string StorePath;
5153
private readonly string _username;
5254
private readonly bool _useSsl;
55+
private uint _timeout;
5356

5457
private nitro_service _nss;
5558

@@ -60,6 +63,8 @@ public CitrixAdcStore(InventoryJobConfiguration config, string serverUserName, s
6063
Logger = LogHandler.GetClassLogger<CitrixAdcStore>();
6164
Logger.MethodEntry(LogLevel.Debug);
6265

66+
SetTimeout(JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()));
67+
6368
_clientMachine = config.CertificateStoreDetails.ClientMachine;
6469
StorePath = StripTrailingSlash(config.CertificateStoreDetails.StorePath);
6570
var o = new systemfile_args();
@@ -93,6 +98,8 @@ public CitrixAdcStore(ManagementJobConfiguration config, string serverUserName,
9398
Logger = LogHandler.GetClassLogger<CitrixAdcStore>();
9499
Logger.MethodEntry(LogLevel.Debug);
95100

101+
SetTimeout(JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()));
102+
96103
_clientMachine = config.CertificateStoreDetails.ClientMachine;
97104
StorePath = StripTrailingSlash(config.CertificateStoreDetails.StorePath);
98105
_useSsl = config.UseSSL;
@@ -125,11 +132,14 @@ public CitrixAdcStore(ManagementJobConfiguration config, string serverUserName,
125132
public void Login()
126133
{
127134
Logger.MethodEntry(LogLevel.Debug);
135+
Logger.LogTrace($"Timeout Value Used: {_timeout}");
128136
_nss ??= new nitro_service(_clientMachine, _useSsl ? "https" : "http");
137+
_nss.set_timeout(_timeout);
138+
129139
base_response response = null;
130140
try
131141
{
132-
response = _nss.login(_username, _password, Timeout);
142+
response = _nss.login(_username, _password, _timeout);
133143
Logger.LogDebug($"Login Response: {JsonConvert.SerializeObject(response)}");
134144
}
135145
catch (Exception ex)
@@ -287,7 +297,7 @@ public string FindKeyPairByCertPath(string certPath)
287297
}
288298
}
289299

290-
public void UpdateKeyPair(string keyPairName, string certFileName, string keyFileName)
300+
public void UpdateKeyPair(string keyPairName, string certFileName, string keyFileName, string keyPassword)
291301
{
292302
Logger.MethodEntry(LogLevel.Debug);
293303

@@ -302,8 +312,8 @@ public void UpdateKeyPair(string keyPairName, string certFileName, string keyFil
302312
key = keyFileName,
303313
inform = "PEM",
304314
nodomaincheck = true,
305-
passplain = "0",
306-
password = false
315+
passplain = keyPassword,
316+
password = keyPassword == null ? null : false
307317
};
308318

309319
var filters = new filtervalue[1];
@@ -328,8 +338,9 @@ public void UpdateKeyPair(string keyPairName, string certFileName, string keyFil
328338
}
329339
catch (nitro_exception ne)
330340
{
331-
Logger.LogError($"Exception occured while trying to add or update {keyPairName}. {LogHandler.FlattenException(ne)}");
332-
throw;
341+
string error = $"Exception occured while trying to add or update {keyPairName}.";
342+
Logger.LogError(error + LogHandler.FlattenException(ne));
343+
throw new Exception(error, ne);
333344
}
334345

335346
Logger.MethodExit(LogLevel.Debug);
@@ -478,47 +489,33 @@ public void LinkToIssuer(string cert, string privateKeyPassword, string keyPairN
478489
Logger.MethodExit(LogLevel.Debug);
479490
}
480491

481-
private (string, string) GetPemFromPfx(byte[] pfxBytes, char[] pfxPassword)
492+
private (string, string) GetPemFromPfx(byte[] pfxBytes, char[] pfxPassword, string storePassword)
482493
{
483494
Logger.MethodEntry(LogLevel.Debug);
484495

485496
try
486497
{
487-
var p = new Pkcs12Store(new MemoryStream(pfxBytes), pfxPassword);
498+
Pkcs12StoreBuilder storeBuilder = new Pkcs12StoreBuilder();
499+
Pkcs12Store store = storeBuilder.Build();
500+
store.Load(new MemoryStream(pfxBytes), pfxPassword);
488501

489-
// Extract private key
490-
var memoryStream = new MemoryStream();
491-
TextWriter streamWriter = new StreamWriter(memoryStream);
492-
var pemWriter = new PemWriter(streamWriter);
502+
var alias = store.Aliases.Cast<string>().SingleOrDefault(p => store.IsKeyEntry(p));
493503

494-
var alias = p.Aliases.Cast<string>().SingleOrDefault(a => p.IsKeyEntry(a));
495-
Logger.LogTrace($"alias: {alias}");
504+
X509CertificateEntry[] chainEntries = store.GetCertificateChain(alias);
505+
Org.BouncyCastle.X509.X509Certificate endCertificate = chainEntries[0].Certificate;
496506

497-
var publicKey = p.GetCertificate(alias).Certificate.GetPublicKey();
498-
if (p.GetKey(alias) == null) throw new Exception($"Unable to get the key for alias: {alias}");
499-
var privateKey = p.GetKey(alias).Key;
500-
var keyPair = new AsymmetricCipherKeyPair(publicKey, privateKey);
507+
AsymmetricKeyParameter privateKey = store.GetKey(alias).Key;
508+
PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCPrivateKeyAndCert(privateKey, endCertificate);
501509

502-
pemWriter.WriteObject(keyPair.Private);
503-
streamWriter.Flush();
504-
var privateKeyString = Encoding.ASCII.GetString(memoryStream.GetBuffer()).Trim().Replace("\r", "")
505-
.Replace("\0", "");
506-
memoryStream.Close();
507-
streamWriter.Close();
510+
string pemString = CryptographicObjectFormatter.PEM.Format(endCertificate, false);
511+
string keyString = string.Empty;
508512

509-
// Extract server certificate
510-
var certStart = "-----BEGIN CERTIFICATE-----\n";
511-
var certEnd = "\n-----END CERTIFICATE-----";
512-
513-
string Pemify(string ss)
514-
{
515-
return ss.Length <= 64 ? ss : ss.Substring(0, 64) + "\n" + Pemify(ss.Substring(64));
516-
}
513+
if (string.IsNullOrEmpty(storePassword))
514+
keyString = PemUtilities.DERToPEM(keyConverter.ToPkcs8BlobUnencrypted(), Keyfactor.PKI.PEM.PemUtilities.PemObjectType.PrivateKey);
515+
else
516+
keyString = CryptographicObjectFormatter.PEM.Format(keyConverter, storePassword);
517517

518-
var certPem =
519-
certStart + Pemify(Convert.ToBase64String(p.GetCertificate(alias).Certificate.GetEncoded())) +
520-
certEnd;
521-
return (certPem, privateKeyString);
518+
return (pemString, keyString);
522519
}
523520
catch (Exception e)
524521
{
@@ -611,14 +608,14 @@ private systemfile GetSystemFile(string fileName)
611608
}
612609
}
613610

614-
public (systemfile pemFile, systemfile privateKeyFile) UploadCertificate(string contents, string pwd,
611+
public (systemfile pemFile, systemfile privateKeyFile) UploadCertificate(string contents, string certTempPassword, string storePassword,
615612
string alias, bool overwrite)
616613
{
617614
Logger.MethodEntry(LogLevel.Debug);
618615

619616
try
620617
{
621-
var (certificate, privateKey) = GetPemFromPfx(Convert.FromBase64String(contents), pwd.ToCharArray());
618+
var (certificate, privateKey) = GetPemFromPfx(Convert.FromBase64String(contents), certTempPassword.ToCharArray(), storePassword);
622619

623620
//upload certificate and key
624621
systemfile certificateFile = UploadFile(alias, certificate, true, 0);
@@ -637,6 +634,15 @@ private systemfile GetSystemFile(string fileName)
637634
}
638635
}
639636

637+
private void SetTimeout(dynamic properties)
638+
{
639+
if (!UInt32.TryParse((properties.timeout == null || string.IsNullOrEmpty(properties.timeout.Value) ? DefaultTimeout : properties.timeout.Value), out _timeout))
640+
{
641+
Logger.LogWarning($"Invalid Custom Field 'timeout' value {properties.timeout.Value}. Value must be an integer. Will use default value of {DefaultTimeout.ToString()}");
642+
_timeout = Convert.ToUInt32(DefaultTimeout);
643+
}
644+
}
645+
640646
private systemfile UploadFile(string alias, string contents, bool isCertificate, int fileNameSuffix)
641647
{
642648
Logger.LogDebug("Entering UploadFile() Method...");

CitrixAdcOrchestratorJobExtension/Inventory.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
using Keyfactor.Orchestrators.Extensions.Interfaces;
2323

2424
using com.citrix.netscaler.nitro.resource.config.ssl;
25+
using Newtonsoft.Json;
26+
using Keyfactor.Orchestrators.Common.Enums;
2527

2628
namespace Keyfactor.Extensions.Orchestrator.CitricAdc
2729
{
@@ -49,10 +51,10 @@ public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitIn
4951
_logger.LogDebug($"Client Machine: {jobConfiguration.CertificateStoreDetails.ClientMachine}");
5052
_logger.LogDebug($"UseSSL: {jobConfiguration.UseSSL}");
5153
_logger.LogDebug($"StorePath: {jobConfiguration.CertificateStoreDetails.StorePath}");
54+
5255
ServerPassword = ResolvePamField("ServerPassword", jobConfiguration.ServerPassword);
5356
ServerUserName = ResolvePamField("ServerUserName", jobConfiguration.ServerUsername);
5457

55-
5658
_logger.LogDebug("Entering ProcessJob");
5759
CitrixAdcStore store = new CitrixAdcStore(jobConfiguration, ServerUserName, ServerPassword);
5860

CitrixAdcOrchestratorJobExtension/Keyfactor.Extensions.Orchestrator.CitricAdc.csproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
<PropertyGroup>
44
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
5-
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
5+
<TargetFramework>net8.0</TargetFramework>
66
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
77
<ImplicitUsings>disable</ImplicitUsings>
88
</PropertyGroup>
99

1010
<ItemGroup>
1111
<PackageReference Include="Keyfactor.Common" Version="2.3.6" />
12-
<PackageReference Include="Keyfactor.Logging" Version="1.1.1" />
1312
<PackageReference Include="Keyfactor.Orchestrators.IOrchestratorJobExtensions" Version="0.7.0" />
14-
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
13+
<PackageReference Include="Keyfactor.PKI" Version="8.2.2" />
1514
<PackageReference Include="System.Text.Encodings.Web" Version="6.0.0" />
1615

1716
<None Update="manifest.json">

CitrixAdcOrchestratorJobExtension/Management.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public class Management : IManagementJobExtension
4040

4141
private string ServerPassword { get; set; }
4242

43+
private string StorePassword { get; set; }
44+
4345
public Management(IPAMSecretResolver resolver)
4446
{
4547
this.resolver = resolver;
@@ -61,6 +63,10 @@ public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration)
6163

6264
ServerPassword = ResolvePamField("ServerPassword", jobConfiguration.ServerPassword);
6365
ServerUserName = ResolvePamField("ServerUserName", jobConfiguration.ServerUsername);
66+
StorePassword = ResolvePamField("StorePassword", jobConfiguration.CertificateStoreDetails.StorePassword);
67+
68+
dynamic properties = JsonConvert.DeserializeObject(jobConfiguration.CertificateStoreDetails.Properties.ToString());
69+
var linkToIssuer = properties.linkToIssuer == null || string.IsNullOrEmpty(properties.linkToIssuer.Value) ? false : Convert.ToBoolean(properties.linkToIssuer.Value);
6470

6571
ApplicationSettings.Initialize(this.GetType().Assembly.Location);
6672

@@ -89,9 +95,6 @@ public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration)
8995
var virtualServerName = (string)jobConfiguration.JobProperties["virtualServerName"];
9096
var sniCert = (string)jobConfiguration.JobProperties["sniCert"];
9197

92-
dynamic properties = JsonConvert.DeserializeObject(jobConfiguration.CertificateStoreDetails.Properties.ToString());
93-
var linkToIssuer = properties.linkToIssuer == null || string.IsNullOrEmpty(properties.linkToIssuer.Value) ? false : Convert.ToBoolean(properties.linkToIssuer.Value);
94-
9598
_logger.LogTrace($"alias: {jobConfiguration.JobCertificate.Alias} virtualServerName {virtualServerName}");
9699

97100
if (!aliasExists)
@@ -126,7 +129,7 @@ public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration)
126129
}
127130
}
128131

129-
PerformAdd(store, jobConfiguration.JobCertificate, virtualServerNames,
132+
PerformAdd(store, jobConfiguration.JobCertificate, StorePassword, virtualServerNames,
130133
aliasExists, jobConfiguration.Overwrite, sniCerts, linkToIssuer);
131134

132135
if (ApplicationSettings.AutoSaveConfig)
@@ -162,7 +165,7 @@ public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration)
162165
{
163166
Result = OrchestratorJobStatusJobResult.Warning,
164167
JobHistoryId = jobConfiguration.JobHistoryId,
165-
FailureMessage = ex.Message
168+
FailureMessage = LogHandler.FlattenException(ex, true)
166169
};
167170
}
168171
catch (Exception ex)
@@ -172,7 +175,7 @@ public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration)
172175
{
173176
Result = OrchestratorJobStatusJobResult.Failure,
174177
JobHistoryId = jobConfiguration.JobHistoryId,
175-
FailureMessage = ex.Message
178+
FailureMessage = LogHandler.FlattenException(ex, true)
176179
};
177180
}
178181

@@ -191,15 +194,15 @@ public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration)
191194
return result;
192195
}
193196

194-
private void PerformAdd(CitrixAdcStore store, ManagementJobCertificate cert,
197+
private void PerformAdd(CitrixAdcStore store, ManagementJobCertificate cert, string storePassword,
195198
List<string> virtualServerNames, bool aliasExists, bool overwrite, List<bool> sniCerts, bool linkToIssuer)
196199
{
197200
_logger.MethodEntry(LogLevel.Debug);
198201

199202
_logger.LogDebug("Updating keyPair");
200203

201-
var (pemFile, privateKeyFile) = store.UploadCertificate(cert.Contents, cert.PrivateKeyPassword, cert.Alias, overwrite);
202-
store.UpdateKeyPair(cert.Alias, pemFile.filename, privateKeyFile.filename);
204+
var (pemFile, privateKeyFile) = store.UploadCertificate(cert.Contents, cert.PrivateKeyPassword, storePassword, cert.Alias, overwrite);
205+
store.UpdateKeyPair(cert.Alias, pemFile.filename, privateKeyFile.filename, storePassword);
203206

204207
_logger.LogDebug("Updating cert bindings");
205208
//update cert bindings

CitrixAdcTestConsole/CitrixAdcTestConsole.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
</PropertyGroup>
77

88
<ItemGroup>
9-
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
109
<PackageReference Include="Moq.AutoMock" Version="3.4.0" />
1110
<PackageReference Include="RestSharp" Version="112.1.0" />
1211
</ItemGroup>
-2.8 MB
Binary file not shown.
-273 KB
Binary file not shown.

0 commit comments

Comments
 (0)