Skip to content

Commit

Permalink
Merge pull request #52 from Keyfactor/ab#64704
Browse files Browse the repository at this point in the history
  • Loading branch information
doebrowsk authored Dec 11, 2024
2 parents 8591132 + c6d62ab commit a39caa7
Show file tree
Hide file tree
Showing 50 changed files with 1,133 additions and 762 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/keyfactor-starter-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ on:

jobs:
call-starter-workflow:
uses: keyfactor/actions/.github/workflows/starter.yml@v2
uses: keyfactor/actions/.github/workflows/starter.yml@3.1.2
secrets:
token: ${{ secrets.V2BUILDTOKEN}}
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
scan_token: ${{ secrets.SAST_TOKEN }}
3 changes: 3 additions & 0 deletions Bundle/Discovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public override JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDis
SetPAMSecrets(config.ServerUsername, config.ServerPassword, logger);

F5Client f5 = new F5Client(certificateStore, ServerUserName, ServerPassword, config.UseSSL, string.Empty, true, false, new List<PreviousInventoryItem>());

ValidateF5Release(logger, certificateStore, f5);

List<string> partitions = f5.GetPartitions().Select(p => p.name).ToList();

LogHandlerCommon.Trace(logger, certificateStore, $"Found {partitions?.Count} partitions");
Expand Down
7 changes: 6 additions & 1 deletion Bundle/Inventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,19 @@ public override JobResult ProcessJob(InventoryJobConfiguration config, SubmitInv
{
base.ParseJobProperties();
SetPAMSecrets(config.ServerUsername, config.ServerPassword, logger);
F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, null, IgnoreSSLWarning, UseTokenAuth, config.LastInventory) { F5Version = base.F5Version };
F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, null, IgnoreSSLWarning, UseTokenAuth, config.LastInventory);

ValidateF5Release(logger, JobConfig.CertificateStoreDetails, f5);

LogHandlerCommon.Debug(logger, JobConfig.CertificateStoreDetails, $"Getting inventory for CA Bundle '{config.CertificateStoreDetails.StorePath}'");
inventory = f5.GetCABundleInventory();

LogHandlerCommon.Debug(logger, JobConfig.CertificateStoreDetails, $"Submitting {inventory?.Count} inventory entries for CA Bundle '{config.CertificateStoreDetails.StorePath}'");
submitInventory.Invoke(inventory);

if (UseTokenAuth)
f5.RemoveToken();

LogHandlerCommon.Debug(logger, JobConfig.CertificateStoreDetails, "Job complete");
return new JobResult { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId };
}
Expand Down
8 changes: 6 additions & 2 deletions Bundle/Management.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ public override JobResult ProcessJob(ManagementJobConfiguration config)

F5Client f5 = new F5Client(config.CertificateStoreDetails, ServerUserName, ServerPassword, config.UseSSL, config.JobCertificate.PrivateKeyPassword, IgnoreSSLWarning, UseTokenAuth, config.LastInventory)
{
PrimaryNode = base.PrimaryNode,
F5Version = base.F5Version
PrimaryNode = base.PrimaryNode
};

ValidateF5Release(logger, JobConfig.CertificateStoreDetails, f5);

switch (config.OperationType)
{
case CertStoreOperationType.Add:
Expand All @@ -69,6 +70,9 @@ public override JobResult ProcessJob(ManagementJobConfiguration config)
throw new Exception($"Management job expecting 'Add' or 'Remove' job - received '{Enum.GetName(typeof(CertStoreOperationType), config.OperationType)}'");
}

if (UseTokenAuth)
f5.RemoveToken();

LogHandlerCommon.Debug(logger, JobConfig.CertificateStoreDetails, "Job complete");
return new JobResult { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId};
}
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
v1.7.0
- Deprecate F5 Version Custom Field for all store types.
- Make Store Password a "PAM eligible" field on the orchestrator
- Remove session token at end of each job
- Convert documentation to use Doctool
- Create separate .net6 and .net8 builds on release

v1.6.0
- Add Store Password (optional) to allow for setting key type to "Password" when adding/replacing a certificate. This will encrypt the private key deployed on the F5 device with the password set as the Store Password.

Expand Down
2 changes: 1 addition & 1 deletion DiscoveryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public abstract class DiscoveryBase : F5JobBase, IDiscoveryJobExtension

protected DiscoveryJobConfiguration JobConfig { get; set; }

public string ExtensionName => string.Empty;
public string ExtensionName => "Keyfactor.Extensions.Orchestrator.F5Orchestrator.Discovery";

public abstract JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpdate submitDiscovery);
}
Expand Down
134 changes: 54 additions & 80 deletions F5Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

using Newtonsoft.Json;
using System.Collections;
using System.Collections.Concurrent;
using System.Drawing.Printing;
using System.Diagnostics.CodeAnalysis;

namespace Keyfactor.Extensions.Orchestrator.F5Orchestrator
{
Expand All @@ -34,6 +37,8 @@ internal class F5Client
private const string INVALID_KEY_SUBSTR = "key(";
private const string INVALID_KEY_BEG_DELIM = @"/";
private const string INVALID_KEY_END_DELIM = ")";
private const int MIN_VERSION_SUPPORTED = 14;
private const string VERSION_DELIMITER = "?ver=";

public CertificateStore CertificateStore { get; set; }
public string ServerUserName { get; set; }
Expand All @@ -43,7 +48,6 @@ internal class F5Client
public string PFXPassword { get; set; }
public IEnumerable<PreviousInventoryItem> Inventory { get; set; }
public string PrimaryNode { get; set; }
public string F5Version { get; set; }
public bool IgnoreSSLWarning { get; set; }
public bool UseTokenAuth { get; set; }
private RESTHandler REST { get; set; }
Expand Down Expand Up @@ -141,26 +145,23 @@ public void RemoveEntry(string partition, string name)
ArchiveFile($"/config/filestore/files_d/{partition}_d/certificate_key_d/:{partition}:{name}_*", $"{partition}-{name}-{timestamp}.key");
LogHandlerCommon.Trace(logger, CertificateStore, $"Removing certificate and key at '{partition}' and name '{name}'");

string keyName = GetKeyName(name, true);
REST.Delete($"/mgmt/tm/sys/file/ssl-key/~{partition}~{keyName}");
REST.Delete($"/mgmt/tm/sys/file/ssl-key/~{partition}~{name}");
}
LogHandlerCommon.Trace(logger, CertificateStore, $"Archiving certificate at '{partition}' and name '{name}'");
ArchiveFile($"/config/filestore/files_d/{partition}_d/certificate_d/:{partition}:{name}_*", $"{partition}-{name}-{timestamp}.crt");
LogHandlerCommon.Trace(logger, CertificateStore, $"Removing certificate at '{partition}' and name '{name}'");

string crtName = GetCrtName(name, true);
REST.Delete($"/mgmt/tm/sys/file/ssl-cert/~{partition}~{crtName}");
REST.Delete($"/mgmt/tm/sys/file/ssl-cert/~{partition}~{name}");
LogHandlerCommon.MethodExit(logger, CertificateStore, "RemoveEntry");
}

public bool KeyExists(string partition, string name)
public bool KeyExists(string partition, string keyName)
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "KeyExists");
bool exists = false;

try
{
string keyName = GetKeyName(name, true);
string query = $"/mgmt/tm/sys/file/ssl-key/~{partition}~{keyName}";
F5Key key = REST.Get<F5Key>(query);
exists = (key != null);
Expand All @@ -178,14 +179,13 @@ public bool KeyExists(string partition, string name)
return exists;
}

public bool CertificateExists(string partition, string name)
public bool CertificateExists(string partition, string crtName)
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "CertificateExists");
bool exists = false;

try
{
string crtName = GetCrtName(name, true);
string query = $"/mgmt/tm/sys/file/ssl-cert/~{partition}~{crtName}";
F5SSLProfile certificate = REST.Get<F5SSLProfile>(query);
exists = (certificate != null);
Expand Down Expand Up @@ -406,12 +406,12 @@ private void SetItemStatus(CurrentInventoryItem agentInventoryItem)
LogHandlerCommon.MethodExit(logger, CertificateStore, "SetItemStatus");
}

private CurrentInventoryItem GetInventoryItem(string partition, string name, bool hasPrivateKey)
private CurrentInventoryItem GetInventoryItem(string partition, string crtName, bool hasPrivateKey)
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "GetInventoryItem");

// Get the pfx/certificate contents from the filesystem (using a wildcard as the files have slightly randomized name suffixes)
X509Certificate2Collection certificateCollection = GetCertificateEntry($"/config/filestore/files_d/{partition}_d/certificate_d/:{partition}:{name}_*");
X509Certificate2Collection certificateCollection = GetCertificateEntry($"/config/filestore/files_d/{partition}_d/certificate_d/:{partition}:{crtName}_*");
List<string> certContents = new List<string>();
bool useChainLevel = certificateCollection.Count > 1;
foreach (X509Certificate2 certificate in certificateCollection)
Expand All @@ -420,7 +420,6 @@ private CurrentInventoryItem GetInventoryItem(string partition, string name, boo
//LogHandlerCommon.Debug(logger, CertificateStore, $"ALIAS: {name}: {Convert.ToBase64String(certificate.Export(X509ContentType.Cert))}");
}

string crtName = GetCrtName(name, false);
CurrentInventoryItem inventoryItem = new CurrentInventoryItem
{
ItemStatus = OrchestratorInventoryItemStatus.Unknown,
Expand All @@ -434,61 +433,6 @@ private CurrentInventoryItem GetInventoryItem(string partition, string name, boo
return inventoryItem;
}

private string GetCrtName(string name, bool addExtension)
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "GetCrtName");
string crtName = name;

switch (F5Version.ToLowerInvariant())
{
case "v12":
throw new Exception($"F5 Version 12 is not supported by the REST-based orchestrator. The legacy SOAP-based orchestrator should be used.");
case "v13":
if (addExtension)
{
// The .crt extension must be added
if (!crtName.EndsWith(".crt", StringComparison.OrdinalIgnoreCase)) { crtName = $"{crtName}.crt"; }
}
else
{
// The .crt extension must be removed
if (crtName.EndsWith(".crt", StringComparison.OrdinalIgnoreCase)) { crtName = crtName.Substring(0, crtName.Length - 4); }
}
break;
};

LogHandlerCommon.MethodExit(logger, CertificateStore, "GetCrtName");
return crtName;
}

private string GetKeyName(string name, bool addExtension)
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "GetKeyName");
string keyName = name;

// No longer checking past version 14 for future-proofing
switch (F5Version.ToLowerInvariant())
{
case "v12":
throw new Exception($"F5 Version 12 is not supported by the REST-based orchestrator. The legacy SOAP-based orchestrator should be used.");
case "v13":
if (addExtension)
{
// The .key extension must be added
if (!keyName.EndsWith(".key", StringComparison.OrdinalIgnoreCase)) { keyName = $"{keyName}.key"; }
}
else
{
// The .key extension must be removed
if (keyName.EndsWith(".key", StringComparison.OrdinalIgnoreCase)) { keyName = keyName.Substring(0, keyName.Length - 4); }
}
break;
};

LogHandlerCommon.MethodExit(logger, CertificateStore, "GetKeyName");
return keyName;
}

// Certificate PFX Shared
#endregion

Expand Down Expand Up @@ -728,7 +672,7 @@ public List<CurrentInventoryItem> GetSSLProfiles(int pageSize)
// SSL Profiles
#endregion

#region Auth
#region Auth & Version

private string GetToken(string userName, string userPassword)
{
Expand All @@ -739,6 +683,39 @@ private string GetToken(string userName, string userPassword)

return loginResponse.token.token;
}

internal void RemoveToken()
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "RemoveToken");
REST.Delete($"/mgmt/shared/authz/tokens/{REST.Token}");
LogHandlerCommon.MethodExit(logger, CertificateStore, "RemoveToken");
}

internal void ValidateF5Version()
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "IsVersionSupported");

string query = $"/mgmt/tm/sys/version";
F5Version f5Version = REST.Get<F5Version>(query);
LogHandlerCommon.Debug(logger, CertificateStore, $"Version supported self link: {f5Version.selfLink}");
if (!f5Version.selfLink.Contains(VERSION_DELIMITER))
return;

string selfLink = f5Version.selfLink;
string strVersion = selfLink.Substring(selfLink.IndexOf(VERSION_DELIMITER, StringComparison.CurrentCultureIgnoreCase) + VERSION_DELIMITER.Length, 2);
int version;
if (!int.TryParse(strVersion, out version))
return;

LogHandlerCommon.MethodExit(logger, CertificateStore, "IsVersionSupported");

if (version < MIN_VERSION_SUPPORTED)
{
string errMesage = $"F5 version {version.ToString()} not supported by this version of the F5 Orchestrator Extension. This orchestrator extension only supports verion {MIN_VERSION_SUPPORTED.ToString()} and later.";
logger.LogError(errMesage);
throw new Exception(errMesage);
}
}
#endregion

#region Bundles
Expand Down Expand Up @@ -822,8 +799,7 @@ public bool EntryExistsInBundle(string alias)
List<string> bundleIncludes = new List<string>(GetCABundleIncludes());
string partition = GetPartitionFromStorePath();

string crtName = GetCrtName(alias, true);
exists = bundleIncludes.Any<string>(i => i.Equals($"/{partition}/{crtName}", StringComparison.OrdinalIgnoreCase));
exists = bundleIncludes.Any<string>(i => i.Equals($"/{partition}/{alias}", StringComparison.OrdinalIgnoreCase));

LogHandlerCommon.MethodExit(logger, CertificateStore, "EntryExistsInBundle");
return exists;
Expand Down Expand Up @@ -855,26 +831,25 @@ private string[] GetCABundleIncludes()
return includeBundle;
}

public void AddBundleEntry(string bundle, string partition, string name, string b64Certificate, string alias, bool overwrite)
public void AddBundleEntry(string bundle, string partition, string crtName, string b64Certificate, string alias, bool overwrite)
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "AddBundleEntry");

// Add the entry to inventory
if (!CertificateExists(partition, name))
if (!CertificateExists(partition, crtName))
{
LogHandlerCommon.Debug(logger, CertificateStore, $"Add entry '{name}' in '{CertificateStore.StorePath}'");
AddEntry(partition, name, b64Certificate, null);
LogHandlerCommon.Debug(logger, CertificateStore, $"Add entry '{crtName}' in '{CertificateStore.StorePath}'");
AddEntry(partition, crtName, b64Certificate, null);
}
else
{
if (!overwrite) { throw new Exception($"An entry named '{name}' exists and 'overwrite' was not selected"); }
if (!overwrite) { throw new Exception($"An entry named '{crtName}' exists and 'overwrite' was not selected"); }

LogHandlerCommon.Debug(logger, CertificateStore, $"Replace entry '{name}' in '{CertificateStore.StorePath}'");
ReplaceEntry(partition, name, b64Certificate, null);
LogHandlerCommon.Debug(logger, CertificateStore, $"Replace entry '{crtName}' in '{CertificateStore.StorePath}'");
ReplaceEntry(partition, crtName, b64Certificate, null);
}

// Add the entry to the bundle
string crtName = GetCrtName(name, true);
string crt = $"/{partition}/{crtName}";
List<string> bundleIncludes = new List<string>(GetCABundleIncludes());
if (!bundleIncludes.Contains(crt))
Expand All @@ -886,11 +861,10 @@ public void AddBundleEntry(string bundle, string partition, string name, string
LogHandlerCommon.MethodExit(logger, CertificateStore, "AddBundleEntry");
}

public void RemoveBundleEntry(string bundle, string partition, string name)
public void RemoveBundleEntry(string bundle, string partition, string crtName)
{
LogHandlerCommon.MethodEntry(logger, CertificateStore, "RemoveBundleEntry");

string crtName = GetCrtName(name, true);
string crtEntry = $"/{partition}/{crtName}";

LogHandlerCommon.Trace(logger, CertificateStore, $"Preparing to remove bundle entry '{crtEntry}'");
Expand Down
5 changes: 5 additions & 0 deletions F5DataModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ public class F5LoginToken
public string token { get; set; }
}

public class F5Version
{
public string selfLink { get; set; }
}

// F5 data models
#endregion
}
Loading

0 comments on commit a39caa7

Please sign in to comment.