diff --git a/CHANGELOG.md b/CHANGELOG.md index 46bc5f0..1079756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +2.2.0 +* Removed the ability to manage certificate/key file combinations uploaded but not yet installed on the Citrix ADC device. This was done due to issues centered around inconsistent naming of uploaded certificate and key files. From this release forward only installed certificate objects will be managed by this orchestrator extension. + +2.1.2 +* Fix bug identifying private key entry when certificate and key file names differ + 2.1.1 * Fix issue identifying whether inventoried certificate contains a private key. * Renewing Unbound Certificates Causes The Job To Fail diff --git a/CitrixAdcOrchestratorJobExtension/CitrixAdcStore.cs b/CitrixAdcOrchestratorJobExtension/CitrixAdcStore.cs index be56c46..1792c0c 100644 --- a/CitrixAdcOrchestratorJobExtension/CitrixAdcStore.cs +++ b/CitrixAdcOrchestratorJobExtension/CitrixAdcStore.cs @@ -60,10 +60,10 @@ public CitrixAdcStore(InventoryJobConfiguration config, string serverUserName, s try { Logger = LogHandler.GetClassLogger(); - Logger.LogDebug( - "Begin CitrixAdcStore(InventoryJobConfiguration config) : this((JobConfiguration) config) Constructor..."); + Logger.MethodEntry(LogLevel.Debug); + _clientMachine = config.CertificateStoreDetails.ClientMachine; - StorePath = config.CertificateStoreDetails.StorePath; + StorePath = StripTrailingSlash(config.CertificateStoreDetails.StorePath); var o = new systemfile_args(); _useSsl = config.UseSSL; _username = serverUserName; @@ -82,6 +82,10 @@ public CitrixAdcStore(InventoryJobConfiguration config, string serverUserName, s $"Error Occured in CitrixAdcStore(InventoryJobConfiguration config) : this((JobConfiguration) config): {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } public CitrixAdcStore(ManagementJobConfiguration config, string serverUserName, string serverPassword) @@ -89,10 +93,10 @@ public CitrixAdcStore(ManagementJobConfiguration config, string serverUserName, try { Logger = LogHandler.GetClassLogger(); - Logger.LogDebug( - "Begin CitrixAdcStore(ManagementJobConfiguration config) : this((JobConfiguration) config) Constructor..."); + Logger.MethodEntry(LogLevel.Debug); + _clientMachine = config.CertificateStoreDetails.ClientMachine; - StorePath = config.CertificateStoreDetails.StorePath; + StorePath = StripTrailingSlash(config.CertificateStoreDetails.StorePath); _useSsl = config.UseSSL; _username = serverUserName; _password = serverPassword; @@ -111,6 +115,10 @@ public CitrixAdcStore(ManagementJobConfiguration config, string serverUserName, $"Error Occured in CitrixAdcStore(ManagementJobConfiguration config) : this((JobConfiguration) config): {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } // ReSharper disable once UnusedAutoPropertyAccessor.Local @@ -118,7 +126,7 @@ public CitrixAdcStore(ManagementJobConfiguration config, string serverUserName, public void Login() { - Logger.LogDebug("Entering CitrixAdcStore Login Method..."); + Logger.MethodEntry(LogLevel.Debug); _nss ??= new nitro_service(_clientMachine, _useSsl ? "https" : "http"); base_response response = null; try @@ -133,17 +141,17 @@ public void Login() } finally { + Logger.MethodExit(LogLevel.Debug); if (response != null && !_nss.isLogin()) throw new Exception(response.message); } - - Logger.LogDebug("Exiting CitrixAdcStore Login Method..."); } public bool Logout() { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering Logout Method..."); _nss.logout(); } catch (Exception e) @@ -152,15 +160,17 @@ public bool Logout() return false; } - Logger.LogDebug("Exiting Logout Method..."); + Logger.MethodExit(LogLevel.Debug); return true; } public sslcertkey_binding GetBinding(string certKey) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug($"Entering and Exiting GetBinding Method... CertKey={certKey}"); + Logger.LogDebug($"CertKey={certKey}"); return sslcertkey_binding.get(_nss, certKey); } catch (Exception e) @@ -168,13 +178,18 @@ public sslcertkey_binding GetBinding(string certKey) Logger.LogError($"Error in GetBinding(): {LogHandler.FlattenException(e)}"); return null; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } public sslcertkey GetKeyPairByName(string name) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering and Exiting ListKeyPairs() Method..."); return sslcertkey.get(_nss, name); } catch (Exception e) @@ -182,144 +197,56 @@ public sslcertkey GetKeyPairByName(string name) Logger.LogError($"Error in ListKeyPairs(): {LogHandler.FlattenException(e)}"); throw; } - } - - public sslcertkey[] ListKeyPairs() - { - try - { - Logger.LogDebug("Entering and Exiting ListKeyPairs() Method..."); - return sslcertkey.get(_nss); - } - catch (Exception e) + finally { - Logger.LogError($"Error in ListKeyPairs(): {LogHandler.FlattenException(e)}"); - throw; + Logger.MethodExit(LogLevel.Debug); } } - public systemfile[] ListFiles() + public sslcertkey[] GetCertificates() { - try - { - Logger.LogDebug("Entering and Exiting ListFiles() Method..."); - return systemfile.get(_nss, nitroServiceOptions); - } - catch (Exception e) - { - Logger.LogError($"Error in ListFiles(): {LogHandler.FlattenException(e)}"); - throw; - } - } + Logger.MethodEntry(LogLevel.Debug); - public (systemfile pemFile, systemfile privateKeyFile) UploadCertificate(string contents, string pwd, - string alias, bool overwrite) - { try { - Logger.LogDebug("Entering UploadCertificate() Method..."); - var (pemFile, privateKeyFile) = GetPem(contents, pwd, alias); - - Logger.LogTrace("Starting UploadFile(pemFile,overwrite) call"); - //upload certificate - UploadFile(pemFile, overwrite); - Logger.LogTrace("Finishing UploadFile(pemFile,overwrite) call"); - - - //upload private key - if (privateKeyFile != null) - { - Logger.LogTrace("PrivateKeyFile is not null so uploading private key"); - //we default overwrite private key as certificate upload has already succeeded and this file needs to be in sync - UploadFile(privateKeyFile, true); - Logger.LogTrace("Finished Uploading Private Key"); - } - - return (pemFile, privateKeyFile); + return sslcertkey.get(_nss); } catch (Exception e) { - Logger.LogError($"Error in UploadCertificate(): {LogHandler.FlattenException(e)}"); + Logger.LogError($"Error in ListKeyPairs(): {LogHandler.FlattenException(e)}"); throw; } - } - - private void UploadFile(systemfile f, bool overwrite) - { - Logger.LogDebug("Entering UploadFile() Method..."); - try - { - Logger.LogDebug($"File Content: {JsonConvert.SerializeObject(f)}"); - Logger.LogTrace("Trying to add File"); - var _ = systemfile.add(_nss, f); - Logger.LogTrace("File Added"); - } - catch (nitro_exception ne) + finally { - Logger.LogTrace($"Nitro Exception Occured {ne.Message}"); - // ReSharper disable once SuspiciousTypeConversion.Global - if ((ne.HResult.Equals(0x80131500) || ne.Message.Contains("File already exists")) - && overwrite) - { - var fOld = new systemfile - { - filename = f.filename, - filelocation = f.filelocation - }; - Logger.LogDebug($"Old File Content: {JsonConvert.SerializeObject(fOld)}"); - systemfile.delete(_nss, fOld); - systemfile.add(_nss, f); - } - else - { - Logger.LogError("Unexpected Nitro Error Occurred"); - throw; - } + Logger.MethodExit(LogLevel.Debug); } } - public base_response DeleteFile(string alias) + public systemfile[] ListFiles() { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering DeleteFile(string contents, string alias) Method..."); - Logger.LogTrace($"alias: {alias} storePath: {StorePath}"); - var f = new systemfile - { - filename = alias, - filelocation = StorePath - }; - Logger.LogDebug("Exiting DeleteFile() Method..."); - return DeleteFile(f); + return systemfile.get(_nss, nitroServiceOptions); } catch (Exception e) { - Logger.LogError( - $"Error Occurred in DeleteFile(string contents, string alias): {LogHandler.FlattenException(e)}"); + Logger.LogError($"Error in ListFiles(): {LogHandler.FlattenException(e)}"); throw; } - } - - private base_response DeleteFile(systemfile f) - { - try - { - Logger.LogDebug("Entering and Exiting DeleteFile() Method..."); - Logger.LogTrace($"Deleting certificate at {f.filelocation}/{f.filename}"); - return systemfile.delete(_nss, f); - } - catch (Exception e) + finally { - Logger.LogError($"Error Occurred in DeleteFile(): {LogHandler.FlattenException(e)}"); - throw; + Logger.MethodExit(LogLevel.Debug); } } public base_response DeleteKeyPair(sslcertkey f) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering and Exiting DeleteFile() Method..."); Logger.LogTrace($"Deleting certificate at {f}"); return sslcertkey.delete(_nss, f); } @@ -328,14 +255,19 @@ public base_response DeleteKeyPair(sslcertkey f) Logger.LogError($"Error Occurred in DeleteFile(): {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } public string FindKeyPairByCertPath(string certPath) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering FindKeyPairByCertPath(string certPath) Method..."); Logger.LogTrace($"certPath: {certPath}"); var filters = new filtervalue[1]; filters[0] = new filtervalue("cert", certPath); @@ -351,135 +283,63 @@ public string FindKeyPairByCertPath(string certPath) $"Error Occurred in FindKeyPairByCertPath(string certPath): {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } - private string UpdateKeyPair(string keyPairName, string certPath, string keyPath) + public void UpdateKeyPair(string keyPairName, string certFileName, string keyFileName) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug( - "Entering UpdateKeyPair(string keyPairName, string certPath, string keyPath) Method..."); - Logger.LogTrace($"keyPairName: {keyPairName} certPath:{certPath} keyPath{keyPath}"); + Logger.LogTrace($"keyPairName: {keyPairName} certFileName:{certFileName} keyFileName{keyFileName}"); + + sslcertkey certKeyObject = new sslcertkey() + { + certkey = keyPairName, + cert = certFileName, + key = keyFileName, + inform = "PEM", + nodomaincheck = true, + passplain = "0", + password = false + }; + var filters = new filtervalue[1]; filters[0] = new filtervalue("certKey", keyPairName); - Logger.LogTrace($"Checking to see if existing certificate-key pair exists with name {keyPairName}"); var count = sslcertkey.count_filtered(_nss, filters); - Logger.LogTrace($"Count of certkey with {keyPairName}: {count}"); if (count > 0) { - var result = new sslcertkey - { - certkey = keyPairName, - cert = certPath - }; - - Logger.LogTrace($"result: {JsonConvert.SerializeObject(result)}"); - keyPath = certPath + ".key"; - Logger.LogTrace($"keyPath: {keyPath}"); - - //Existing keypair exists - result.key = keyPath; - result.inform = "PEM"; - result.nodomaincheck = true; - Logger.LogTrace($"Updating certificate-key pair with name {keyPairName}"); - var _ = sslcertkey.change(_nss, result); - var unused = sslcertkey.update(_nss, result); + sslcertkey.change(_nss, certKeyObject); + sslcertkey.update(_nss, certKeyObject); } else { - var s = new sslcertkey - { - certkey = keyPairName, - cert = certPath - }; - if (keyPath != null) - { - s.key = keyPath; - s.password = false; - s.passplain = "0"; // Unused, but required, dummy variable - } - Logger.LogTrace($"Adding certificate-key pair with name {keyPairName}"); - sslcertkey.add(_nss, s); - Logger.LogTrace($"Finished Adding certificate-key pair with name {keyPairName}"); + sslcertkey.add(_nss, certKeyObject); } } catch (nitro_exception ne) { - Logger.LogError($"Exception occured while trying to add or update {keyPairName}"); - if ((((uint)ne.HResult).Equals(0x80138500) || ((uint)ne.HResult).Equals(0x80131500)) && - ne.Message.Contains("Resource already exists")) - { - if (ne.Message.Contains("certkeyName Contents,")) - { - var start = ne.Message.IndexOf("Contents, ", StringComparison.Ordinal) + "Contents, ".Length; - var end = ne.Message.IndexOf(']', start); - keyPairName = ne.Message.Substring(start, end - start); - Logger.LogError($"Certificate keypair already existed on as {keyPairName}"); - } - } - else - { - throw; - } - } - - Logger.LogDebug("Exiting UpdateKeyPair(string keyPairName, string certPath, string keyPath) Method..."); - return keyPairName; - } - - public string UpdateKeyPair(string alias, string keyPairName, systemfile pemFile, systemfile privateKey) - { - try - { - Logger.LogDebug( - "Entering UpdateKeyPair(string alias, string keyPairName, systemfile pemFile, systemfile privateKey) Method..."); - Logger.LogTrace($"alias: {alias} keyPairName: {keyPairName}"); - - var certPath = StorePath + "/" + keyPairName; - Logger.LogTrace($"certPath: {certPath}"); - - //see if keypair already exists, if it does then we have to generate a new name to prevent downtime - Logger.LogTrace($"keyPairName: {keyPairName} certPath:{certPath} checking if already exists."); - - if (string.IsNullOrWhiteSpace(keyPairName)) - { - Logger.LogTrace("string.IsNullOrWhiteSpace(keyPairName) is True"); - var existingKeyPair = FindKeyPairByCertPath(certPath); - Logger.LogTrace($"existingKeyPair: {existingKeyPair}"); - if (existingKeyPair != null) - { - Logger.LogTrace($"existingKeyPair not Null: {existingKeyPair}"); - keyPairName = existingKeyPair; - } - else - { - keyPairName = GenerateKeyPairName(alias); - } - } - - string keyPath = null; - if (privateKey != null) keyPath = StorePath + "/" + alias + ".key"; - Logger.LogTrace($"keyPath: {keyPath}"); - Logger.LogDebug( - "Exiting UpdateKeyPair(string alias, string keyPairName, systemfile pemFile, systemfile privateKey) Method..."); - return UpdateKeyPair(keyPairName, certPath, keyPath); - } - catch (Exception e) - { - Logger.LogError( - $"Error Occurred in UpdateKeyPair(string alias, string keyPairName, systemfile pemFile, systemfile privateKey): {LogHandler.FlattenException(e)}"); + Logger.LogError($"Exception occured while trying to add or update {keyPairName}. {LogHandler.FlattenException(ne)}"); throw; } + + Logger.MethodExit(LogLevel.Debug); } public sslvserver_sslcertkey_binding[] GetBindingByVServer(string vServerName) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug($"Entering and Exiting GetBindingByVServerKey Method... vServerName={vServerName}"); + Logger.LogDebug($"vServerName={vServerName}"); return sslvserver_sslcertkey_binding.get(_nss, vServerName); } catch (Exception e) @@ -487,10 +347,16 @@ public sslvserver_sslcertkey_binding[] GetBindingByVServer(string vServerName) Logger.LogError($"Error in GetBinding(): {LogHandler.FlattenException(e)}"); return null; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } private string GenerateKeyPairName(string alias) { + Logger.MethodEntry(LogLevel.Debug); + if (alias == alias.Substring(0, Math.Min(40, alias.Length))) alias = alias.Substring(0, Math.Min(40, alias.Length)); else @@ -498,60 +364,63 @@ private string GenerateKeyPairName(string alias) Logger.LogTrace($"keyPairName: {alias}"); + Logger.MethodExit(LogLevel.Debug); + return alias; } - public void UpdateBindings(string keyPairName, string virtualServerName, string sniCert) + public void UpdateBindings(string keyPairName, List virtualServerNames, List sniCerts) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Enter UpdateBindings(string keyPairName, string virtualServerName)"); - + if (virtualServerNames.Count != sniCerts.Count) + { + Logger.LogError($"Error attempting to perform binding. Mismatched number of virtual server names ({virtualServerNames.Count.ToString()} and SNI values {sniCerts.Count.ToString()}. Certificate added, but binding not performed."); + return; + } - var sniArray = sniCert.Split(','); + var i = 0; - if (!string.IsNullOrWhiteSpace(virtualServerName)) + foreach (string vsName in virtualServerNames) { - var i = 0; - foreach (var vsName in virtualServerName.Split(",")) + bool sniBool = Convert.ToBoolean(sniCerts[virtualServerNames.IndexOf(vsName)]); + Logger.LogTrace($"Updating binding for {vsName}"); + var ssb = new sslvserver_sslcertkey_binding { - var sniBool = false; - if (!string.IsNullOrEmpty(sniCert) && - (sniArray[i].ToUpper() == "TRUE" || sniArray[i].ToUpper() == "FALSE")) - sniBool = Convert.ToBoolean(sniArray[i]); - - Logger.LogTrace($"Updating bindings for {virtualServerName}"); - //bind key-pair to vserver - var ssb = new sslvserver_sslcertkey_binding - { - certkeyname = keyPairName, - vservername = vsName, - snicert = sniBool - }; - Logger.LogTrace($"Adding binding {keyPairName} for virtual server {virtualServerName}"); - - //Citrix Requires you do delete first when SNI with same domain or you will get a duplicate domain error - var filters = new filtervalue[1]; - filters[0] = new filtervalue("certKeyName", keyPairName); - if (sniBool && sslvserver_sslcertkey_binding.count_filtered(_nss, vsName, filters) > 0) - sslvserver_sslcertkey_binding.delete(_nss, ssb); - sslvserver_sslcertkey_binding.add(_nss, ssb); - - i++; - Logger.LogDebug("Exit UpdateBindings(string keyPairName, string virtualServerName)"); - } + certkeyname = keyPairName, + vservername = vsName, + snicert = sniBool + }; + Logger.LogTrace($"Adding binding {keyPairName} for virtual server {vsName}"); + + //Citrix Requires you do delete first when SNI with same domain or you will get a duplicate domain error + var filters = new filtervalue[1]; + filters[0] = new filtervalue("certKeyName", keyPairName); + if (sniBool && sslvserver_sslcertkey_binding.count_filtered(_nss, vsName, filters) > 0) + sslvserver_sslcertkey_binding.delete(_nss, ssb); + sslvserver_sslcertkey_binding.add(_nss, ssb); + + i++; } } catch (Exception e) { Logger.LogError( - $"Error Occurred in UpdateBindings(string keyPairName, string virtualServerName): {LogHandler.FlattenException(e)}"); + $"Error Occurred in UpdateBindings: {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } public void LinkToIssuer(string cert, string privateKeyPassword, string keyPairName) { + Logger.MethodEntry(LogLevel.Debug); + sslcertificatechain chain = sslcertificatechain.get(_nss, keyPairName); if (chain.chaincomplete == 1) { @@ -569,13 +438,16 @@ public void LinkToIssuer(string cert, string privateKeyPassword, string keyPairN sslcertkey certKey = sslcertkey.get(_nss, keyPairName); certKey.linkcertkeyname = chain.chainpossiblelinks[0]; sslcertkey.link(_nss, certKey); + + Logger.MethodExit(LogLevel.Debug); } private (byte[], byte[]) GetPemFromPfx(byte[] pfxBytes, char[] pfxPassword) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering GetPemFromPfx(byte[] pfxBytes, char[] pfxPassword)"); var p = new Pkcs12Store(new MemoryStream(pfxBytes), pfxPassword); // Extract private key @@ -619,13 +491,18 @@ string Pemify(string ss) $"Error Occurred in GetPemFromPfx(byte[] pfxBytes, char[] pfxPassword): {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } - private (systemfile, systemfile) GetPem(string contents, string pwd, string alias) + private (systemfile, systemfile) GetPem(string contents, string pwd, string certFileName, string keyFileName) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering GetPem(string contents, string pwd, string alias)"); var pemFile = new systemfile(); systemfile privateKeyFile = null; @@ -639,7 +516,7 @@ string Pemify(string ss) privateKeyFile = new systemfile { filecontent = Convert.ToBase64String(privateKey), - filename = alias + ".key", + filename = keyFileName.Substring(keyFileName.LastIndexOf("/") + 1), filelocation = StorePath }; @@ -651,145 +528,87 @@ string Pemify(string ss) pemFile.filecontent = contents; } - pemFile.filename = alias; + pemFile.filename = certFileName.Substring(certFileName.LastIndexOf("/") + 1); pemFile.filelocation = StorePath; - Logger.LogDebug("Exiting GetPem(string contents, string pwd, string alias)"); return (pemFile, privateKeyFile); } catch (Exception e) { Logger.LogError( - $"Error Occurred in GetPem(string contents, string pwd, string alias): {LogHandler.FlattenException(e)}"); + $"Error Occurred in GetPem, Cert File {certFileName}: {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } - public X509Certificate2 GetX509Certificate(string fileLocation, out bool hasKey) + public X509Certificate2 GetX509Certificate(sslcertkey certificate) { - Logger.LogDebug("Entering GetX509Certificate(string fileLocation, out bool hasKey)"); - systemfile f; - string[] privateKeyDelims = new string[3] { "-----BEGIN RSA PRIVATE KEY-----", "-----BEGIN PRIVATE KEY-----", "-----BEGIN ENCRYPTED PRIVATE KEY-----" }; + Logger.MethodEntry(LogLevel.Debug); string certString = null; - string keyString = null; + X509Certificate2 x509Cert = null; try { - Logger.LogTrace($"Trying GetSystemFile(fileLocation): {fileLocation}"); - f = GetSystemFile(fileLocation); - Logger.LogTrace($"Finished GetSystemFile(fileLocation): {fileLocation}"); - } - catch - { - Logger.LogError("Error Occurred in GetSystemFile(fileLocation)"); - hasKey = false; - return null; - } - - //Ignore Directories - if (f.filemode != null && f.filemode[0].ToUpper() == "DIRECTORY") - { - hasKey = false; - return null; - } + Logger.LogTrace($"Trying GetSystemFile(fileLocation): {certificate.cert}"); + systemfile f = GetSystemFile(certificate.cert); + Logger.LogTrace($"Finished GetSystemFile(fileLocation): {certificate.cert}"); - // Determine if it's a cert - X509Certificate2 x = null; - try - { var b = Convert.FromBase64String(f.filecontent); var fileString = Encoding.Default.GetString(b); - // Check if private key is included with certificate - var privateKeyIdx = -1; - foreach(string privateKeyDelim in privateKeyDelims) - { - if (fileString.IndexOf(privateKeyDelim, StringComparison.Ordinal) >= 0) - privateKeyIdx = Array.IndexOf(privateKeyDelims, privateKeyDelim); - } - - var containsCert = fileString.IndexOf("-----BEGIN CERTIFICATE-----", StringComparison.Ordinal) >= 0; - - Logger.LogTrace($"containsKey: {privateKeyIdx > -1} containsCert: {containsCert}"); + string endDelim = "-----END CERTIFICATE-----"; + int startIdx = fileString.IndexOf("-----BEGIN CERTIFICATE-----", StringComparison.Ordinal); + int endIdx = fileString.IndexOf(endDelim, StringComparison.Ordinal); - if (containsCert && privateKeyIdx > -1) + if (startIdx == -1 || endIdx == -1) { - Logger.LogTrace($"File contains certificate and key: {fileLocation}"); - - var keyStart = fileString.IndexOf(privateKeyDelims[privateKeyIdx], StringComparison.Ordinal); - var keyEnd = fileString.IndexOf(privateKeyDelims[privateKeyIdx].Replace("BEGIN","END"), StringComparison.Ordinal) + - privateKeyDelims[privateKeyIdx].Replace("BEGIN", "END").Length; - - // check if need to remove new line - keyString = fileString.Substring(keyStart, keyEnd - keyStart); - certString = fileString.Remove(keyStart, keyEnd - keyStart); - } - else if (containsCert) - { - Logger.LogTrace("containsCert"); - certString = fileString; - // check .key file - try - { - var fileNameWithoutExtension = fileLocation; - if (fileLocation.EndsWith(".crt",StringComparison.CurrentCultureIgnoreCase) || fileLocation.EndsWith(".pem", StringComparison.CurrentCultureIgnoreCase) || fileLocation.EndsWith(".pfx", StringComparison.CurrentCultureIgnoreCase) || fileLocation.EndsWith(".cert", StringComparison.CurrentCultureIgnoreCase) || fileLocation.EndsWith(".der", StringComparison.CurrentCultureIgnoreCase)) - { - fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileLocation); - } - var keyFile = GetSystemFile(fileNameWithoutExtension + ".key"); - keyString = Encoding.UTF8.GetString(Convert.FromBase64String(keyFile.filecontent)); - } - catch (Exception e) - { - Logger.LogError("Unable to evaluate private key - " + LogHandler.FlattenException(e)); - } + Logger.LogWarning($"Certificate {certificate.certkey} does not contain a valid PEM formatted certificate"); } + certString = fileString.Substring(startIdx, endIdx - startIdx + endDelim.Length); + if (certString == null) { - hasKey = false; return null; } try { - x = ReadX509Certificate(certString); + x509Cert = ReadX509Certificate(certString); } catch (Exception e) { - // Not a certificate file - Logger.LogError($"Error reading x509Certificate at {fileLocation}"); - Logger.LogError(LogHandler.FlattenException(e)); - hasKey = false; + Logger.LogError($"Error reading converting {certificate.certkey} to X509 certificate format: {LogHandler.FlattenException(e)}"); return null; } - - hasKey = !string.IsNullOrEmpty(keyString); } catch (Exception e) { // Not a certificate file - Logger.LogError($"{fileLocation} is not a certificate"); - Logger.LogError(LogHandler.FlattenException(e)); - hasKey = false; + Logger.LogError($"Error reading/processing certificate {certificate.certkey}: {LogHandler.FlattenException(e)}"); } - Logger.LogDebug("Exiting GetX509Certificate(string fileLocation, out bool hasKey)"); - return x; + Logger.MethodExit(LogLevel.Debug); + return x509Cert; } private systemfile GetSystemFile(string fileName) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering GetSystemFile(string fileName)"); var option = new systemfile_args(); Logger.LogTrace($"urlPath: {StorePath} fileName:{fileName}"); //option.set_args($"filelocation:{urlPath},filename:{fileName}"); option.filelocation = StorePath; - var f = new systemfile { filelocation = StorePath, filename = fileName }; + var f = new systemfile { filelocation = StorePath, filename = fileName.Substring(fileName.LastIndexOf("/") + 1) }; var result = systemfile.get(_nss, f); Logger.LogDebug("Exiting GetSystemFile(string fileName)"); return result; @@ -799,10 +618,123 @@ private systemfile GetSystemFile(string fileName) Logger.LogError($"Error Occurred in GetSystemFile(string fileName): {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } - public bool IsDuplicateCertificate(string alias) + public (systemfile pemFile, systemfile privateKeyFile) UploadCertificate(string contents, string pwd, + string certFileName, string keyFileName, bool overwrite) { + Logger.MethodEntry(LogLevel.Debug); + + try + { + var (pemFile, privateKeyFile) = GetPem(contents, pwd, certFileName, keyFileName); + + Logger.LogTrace("Starting UploadFile(pemFile,overwrite) call"); + //upload certificate + UploadFile(pemFile, overwrite); + Logger.LogTrace("Finishing UploadFile(pemFile,overwrite) call"); + + + //upload private key + if (privateKeyFile != null) + { + Logger.LogTrace("PrivateKeyFile is not null so uploading private key"); + //we default overwrite private key as certificate upload has already succeeded and this file needs to be in sync + UploadFile(privateKeyFile, true); + Logger.LogTrace("Finished Uploading Private Key"); + } + + return (pemFile, privateKeyFile); + } + catch (Exception e) + { + Logger.LogError($"Error in UploadCertificate(): {LogHandler.FlattenException(e)}"); + throw; + } + finally + { + Logger.MethodExit(LogLevel.Debug); + } + } + + private void UploadFile(systemfile f, bool overwrite) + { + Logger.LogDebug("Entering UploadFile() Method..."); + try + { + Logger.LogDebug($"File Content: {JsonConvert.SerializeObject(f)}"); + Logger.LogTrace("Trying to add File"); + var _ = systemfile.add(_nss, f); + Logger.LogTrace("File Added"); + } + catch (nitro_exception ne) + { + Logger.LogTrace($"Nitro Exception Occured {ne.Message}"); + // ReSharper disable once SuspiciousTypeConversion.Global + if ((ne.HResult.Equals(0x80131500) || ne.Message.Contains("File already exists")) + && overwrite) + { + var fOld = new systemfile + { + filename = f.filename, + filelocation = f.filelocation + }; + Logger.LogDebug($"Old File Content: {JsonConvert.SerializeObject(fOld)}"); + systemfile.delete(_nss, fOld); + systemfile.add(_nss, f); + } + else + { + throw new Exception($"Error attempting to upload file {f.filename}"); + } + } + } + + public base_response DeleteFile(string alias) + { + try + { + Logger.LogDebug("Entering DeleteFile(string contents, string alias) Method..."); + Logger.LogTrace($"alias: {alias} storePath: {StorePath}"); + var f = new systemfile + { + filename = alias, + filelocation = StorePath + }; + Logger.LogDebug("Exiting DeleteFile() Method..."); + return DeleteFile(f); + } + catch (Exception e) + { + Logger.LogError( + $"Error Occurred in DeleteFile(string contents, string alias): {LogHandler.FlattenException(e)}"); + throw; + } + } + + private base_response DeleteFile(systemfile f) + { + try + { + Logger.LogDebug("Entering and Exiting DeleteFile() Method..."); + Logger.LogTrace($"Deleting certificate at {f.filelocation}/{f.filename}"); + return systemfile.delete(_nss, f); + } + catch (Exception e) + { + Logger.LogError($"Error Occurred in DeleteFile(): {LogHandler.FlattenException(e)}"); + throw; + } + } + + public bool AliasExists(string alias) + { + Logger.MethodEntry(LogLevel.Debug); + var filters = new filtervalue[1]; filters[0] = new filtervalue("certKey", alias); Logger.LogTrace($"Checking to see if existing certificate-key pair exists with name {alias}"); @@ -811,14 +743,17 @@ public bool IsDuplicateCertificate(string alias) if (count > 0) return true; + + Logger.MethodExit(LogLevel.Debug); return false; } private X509Certificate2 ReadX509Certificate(string certString) { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering ReadX509Certificate(string certString)"); // Determine if it's a cert byte[] b = null; X509Certificate2 x; @@ -845,7 +780,6 @@ private X509Certificate2 ReadX509Certificate(string certString) throw e; } - Logger.LogDebug("Exiting ReadX509Certificate(string certString)"); return x; } catch (Exception e) @@ -854,36 +788,23 @@ private X509Certificate2 ReadX509Certificate(string certString) $"Error Occurred in ReadX509Certificate(string certString): {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } - private bool EvaluatePrivateKey(X509Certificate2 cert, string keyString) + private string StripTrailingSlash(string storePath) { - Logger.LogDebug("Entering EvaluatePrivateKey(X509Certificate2 cert, string keyString)"); - if (string.IsNullOrEmpty(keyString)) return false; - try - { - var keypair = (AsymmetricCipherKeyPair)new PemReader(new StringReader(keyString)).ReadObject(); - var privateKey = (RsaPrivateCrtKeyParameters)keypair.Private; - - var publicKey = (RsaKeyParameters)DotNetUtilities.FromX509Certificate(cert).GetPublicKey(); - Logger.LogDebug("Exiting EvaluatePrivateKey(X509Certificate2 cert, string keyString)"); - - return privateKey.Modulus.Equals(publicKey.Modulus) && - publicKey.Exponent.Equals(privateKey.PublicExponent); - } - catch (Exception e) - { - Logger.LogError("Unable to evaluate private key - " + e.Message); - Logger.LogError(LogHandler.FlattenException(e)); - return false; - } + return storePath.Substring(storePath.Length - 1, 1) == "/" ? storePath.Substring(0, storePath.Length - 1) : storePath; } public void SaveConfiguration() { + Logger.MethodEntry(LogLevel.Debug); + try { - Logger.LogDebug("Entering and Exiting SaveConfiguration Method..."); _ = _nss.save_config(); } catch (Exception e) @@ -891,6 +812,10 @@ public void SaveConfiguration() Logger.LogError($"Error in SaveConfiguration: {LogHandler.FlattenException(e)}"); throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } } diff --git a/CitrixAdcOrchestratorJobExtension/Inventory.cs b/CitrixAdcOrchestratorJobExtension/Inventory.cs index a8c2704..fe72361 100644 --- a/CitrixAdcOrchestratorJobExtension/Inventory.cs +++ b/CitrixAdcOrchestratorJobExtension/Inventory.cs @@ -21,6 +21,9 @@ using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions.Interfaces; +using com.citrix.netscaler.nitro.resource.config.ssl; +using static Org.BouncyCastle.Math.EC.ECCurve; + namespace Keyfactor.Extensions.Orchestrator.CitricAdc { // ReSharper disable once InconsistentNaming @@ -76,52 +79,32 @@ private string ResolvePamField(string name, string value) private JobResult ProcessJob(CitrixAdcStore store, InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) { - _logger.LogDebug("Begin New Bindings Fix Inventory..."); + _logger.LogDebug($"Begin {jobConfiguration.Capability} for job id {jobConfiguration.JobId}..."); + _logger.MethodEntry(LogLevel.Debug); List inventory = new List(); try { - _logger.LogDebug("Getting file list..."); - var files = store.ListFiles(); - - ///////Dictionary existing = jobConfiguration.LastInventory.ToDictionary(i => i.Alias, i => i.Thumbprints.First()); - // ReSharper disable once CollectionNeverQueried.Local - HashSet processedAliases = new HashSet(); - - //union the remote keys + last Inventory - List contentsToCheck = files?.Select(x => x.filename).ToList() ?? new List(); - - _logger.LogDebug("Getting KeyPair list..."); - var keyPairList = store.ListKeyPairs(); - _logger.LogDebug($"Found {keyPairList.Length} KeyPair results..."); + _logger.LogDebug("Getting certificate list..."); + sslcertkey[] certificates = store.GetCertificates(); + _logger.LogDebug($"Found {certificates.Length} certificate results..."); - //create a lookup by cert(alias) for certkey identifier - Dictionary keyPairMap = keyPairList.ToDictionary(i => i.cert, i => i.certkey); - - _logger.LogDebug("For each file get contents by alias..."); - foreach (string s in contentsToCheck) + _logger.LogDebug("For each certificate..."); + foreach (sslcertkey certificate in certificates) { - _logger.LogDebug($"Checking alias (filename): {s}"); - X509Certificate2 x = store.GetX509Certificate(s, out bool privateKeyEntry); + _logger.LogDebug($"Retrieving certificate file: {certificate.cert} for alias {certificate.certkey}"); + X509Certificate2 x = store.GetX509Certificate(certificate); if (x == null) continue; - processedAliases.Add(s); - Dictionary parameters = new Dictionary(); - var containsKeyWithPath = keyPairMap.ContainsKey(store.StorePath + "/" + s); - var containsKey = keyPairMap.ContainsKey(s); - - if (containsKey || containsKeyWithPath) + if (certificate.key != null) { - var keyPairName = containsKeyWithPath ? keyPairMap[store.StorePath + "/" + s] : keyPairMap[s]; - - _logger.LogDebug($"Found keyPairName: {keyPairName}"); - parameters.Add("keyPairName", keyPairName); + parameters.Add("keyPairName", certificate.certkey); - var binding = store.GetBinding(keyPairName); + var binding = store.GetBinding(certificate.certkey); var vserverBindings = binding?.sslcertkey_sslvserver_binding; if (vserverBindings != null) @@ -135,7 +118,7 @@ private JobResult ProcessJob(CitrixAdcStore store, InventoryJobConfiguration job foreach (string server in virtualServerName.Split(',')) { var bindings = store.GetBindingByVServer(server); - var first = bindings.FirstOrDefault(b => b.certkeyname == keyPairName); + var first = bindings.FirstOrDefault(b => b.certkeyname == certificate.certkey); if (first != null) bindingsCsv += first.snicert + ","; } parameters.Add("sniCert", bindingsCsv.TrimEnd(',')); @@ -150,18 +133,16 @@ private JobResult ProcessJob(CitrixAdcStore store, InventoryJobConfiguration job inventory.Add(new CurrentInventoryItem() { - Alias = s, + Alias = certificate.certkey, Certificates = new[] { Convert.ToBase64String(x.GetRawCertData()) }, - //ItemStatus = itemStatus, - PrivateKeyEntry = privateKeyEntry, + PrivateKeyEntry = certificate.key != null, UseChainLevel = false, Parameters = parameters }); } _logger.LogDebug($"Found {inventory.Count} certificates at {jobConfiguration.CertificateStoreDetails.StorePath}"); - - } + catch (Exception ex) { _logger.LogError("Error performing certificate Inventory: " + ex.Message); @@ -172,14 +153,17 @@ private JobResult ProcessJob(CitrixAdcStore store, InventoryJobConfiguration job { Result = Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = jobConfiguration.JobHistoryId, - FailureMessage = "Error while performing certificate Inventory" + FailureMessage = "Error while performing certificate Inventory" + ex.Message }; } + finally + { + _logger.MethodExit(LogLevel.Debug); + } try { _logger.LogDebug("Sending results back to command"); - //Sends inventoried certificates back to KF Command submitInventoryUpdate.Invoke(inventory); _logger.LogDebug("Successfully Completed Job"); @@ -204,6 +188,10 @@ private JobResult ProcessJob(CitrixAdcStore store, InventoryJobConfiguration job FailureMessage = "Failure while submitting certificate Inventory" }; } + finally + { + _logger.MethodExit(LogLevel.Debug); + } } } } \ No newline at end of file diff --git a/CitrixAdcOrchestratorJobExtension/Management.cs b/CitrixAdcOrchestratorJobExtension/Management.cs index 247bafb..8206a68 100644 --- a/CitrixAdcOrchestratorJobExtension/Management.cs +++ b/CitrixAdcOrchestratorJobExtension/Management.cs @@ -22,6 +22,8 @@ using System.IO; using static Org.BouncyCastle.Math.EC.ECCurve; using com.citrix.netscaler.nitro.resource.config.pq; +using System.Collections.Generic; +using com.citrix.netscaler.nitro.resource.config.ssl; namespace Keyfactor.Extensions.Orchestrator.CitricAdc { @@ -54,6 +56,8 @@ private string ResolvePamField(string name, string value) public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration) { _logger = LogHandler.GetClassLogger(); + _logger.LogDebug($"Begin {jobConfiguration.Capability} for job id {jobConfiguration.JobId}..."); + _logger.MethodEntry(LogLevel.Debug); ServerPassword = ResolvePamField("ServerPassword", jobConfiguration.ServerPassword); ServerUserName = ResolvePamField("ServerUserName", jobConfiguration.ServerUsername); @@ -65,171 +69,84 @@ public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration) _logger.LogDebug("Logging into Citrix..."); store.Login(); - _logger.LogDebug("Entering ProcessJob"); - var result = ProcessJob(store, jobConfiguration); - - if (ApplicationSettings.AutoSaveConfig && result.Result == OrchestratorJobStatusJobResult.Success) - { - _logger.LogDebug("Saving configuration..."); - store.SaveConfiguration(); - } - - _logger.LogDebug("Logging out of Citrix..."); - store.Logout(); - - _logger.LogDebug("Exiting ProcessJob"); - - return result; - } - - private void PerformAdd(CitrixAdcStore store, ManagementJobCertificate cert, string keyPairName, - string virtualServerName, bool overwrite, string sniCert, bool linkToIssuer) - { - _logger.LogTrace("Enter performAdd"); - var alias = cert.Alias; - AddBindCert(store, cert, keyPairName, virtualServerName, overwrite, alias, sniCert, linkToIssuer); - } - - private void AddBindCert(CitrixAdcStore store, ManagementJobCertificate cert, string keyPairName, - string virtualServerName, bool overwrite, string alias, string sniCert, bool linkToIssuer) - { - var (pemFile, privateKeyFile) = - store.UploadCertificate(cert.Contents, cert.PrivateKeyPassword, alias, overwrite); - - _logger.LogDebug("Updating keyPair"); - //update KeyPair - keyPairName = store.UpdateKeyPair(alias, keyPairName, pemFile, privateKeyFile); - - _logger.LogDebug("Updating cert bindings"); - //update cert bindings - if (virtualServerName != null) - store.UpdateBindings(keyPairName, virtualServerName, sniCert); - - if (linkToIssuer) - { - store.LinkToIssuer(cert.Contents, cert.PrivateKeyPassword, keyPairName); - } - } - - private void PerformDelete(CitrixAdcStore store, ManagementJobCertificate cert) - { - _logger.LogTrace("Enter PerformDelete"); - var sslKeyFile = store.GetKeyPairByName(cert.Alias); - - //1. Delete the Keypair - store.DeleteKeyPair(sslKeyFile); - - //2. Remove directory from file name, Delete the Key File - store.DeleteFile(Path.GetFileName(sslKeyFile.key)); - - //3. Remove directory from file name, Delete the Certificate File - store.DeleteFile(Path.GetFileName(sslKeyFile.cert)); - - _logger.LogTrace("Exit PerformDelete"); - } - - private JobResult ProcessJob(CitrixAdcStore store, ManagementJobConfiguration jobConfiguration) - { - _logger.LogDebug("Begin Management..."); - try { //Management jobs, unlike Discovery, Inventory, and ReEnrollment jobs can have 3 different purposes: switch (jobConfiguration.OperationType) { case CertStoreOperationType.Add: - var dup = store.IsDuplicateCertificate(jobConfiguration.JobCertificate.Alias); - if ((dup && jobConfiguration.Overwrite) || !dup || (jobConfiguration.JobProperties.ContainsKey("RenewalThumbprint"))) - { - _logger.LogDebug("Begin Add..."); - var virtualServerName = (string)jobConfiguration.JobProperties["virtualServerName"]; - var sniCert = (string)jobConfiguration.JobProperties["sniCert"]; + List virtualServerNames = new List(); + List sniCerts = new List(); - dynamic properties = JsonConvert.DeserializeObject(jobConfiguration.CertificateStoreDetails.Properties.ToString()); - var linkToIssuer = properties.linkToIssuer == null || string.IsNullOrEmpty(properties.linkToIssuer.Value) ? false : Convert.ToBoolean(properties.linkToIssuer.Value); + bool aliasExists = (store.AliasExists(jobConfiguration.JobCertificate.Alias)); - //Check if Keypair name exists, if so, we need to append something to it so we don't get downtime - var keyPairName = jobConfiguration.JobCertificate.Alias; + if (aliasExists && !jobConfiguration.Overwrite) + { + throw new Exception($"Alias {jobConfiguration.JobCertificate.Alias} already exists in store {jobConfiguration.CertificateStoreDetails.StorePath} and overwrite is set to False. Please try again with overwrite set to True if you wish to replace this entry."); + } - _logger.LogTrace($"keyPairName: {keyPairName} virtualServerName {virtualServerName}"); - if (jobConfiguration.JobProperties.ContainsKey("RenewalThumbprint")) + _logger.LogDebug("Begin Add..."); + var virtualServerName = (string)jobConfiguration.JobProperties["virtualServerName"]; + var sniCert = (string)jobConfiguration.JobProperties["sniCert"]; + + dynamic properties = JsonConvert.DeserializeObject(jobConfiguration.CertificateStoreDetails.Properties.ToString()); + var linkToIssuer = properties.linkToIssuer == null || string.IsNullOrEmpty(properties.linkToIssuer.Value) ? false : Convert.ToBoolean(properties.linkToIssuer.Value); + + _logger.LogTrace($"alias: {jobConfiguration.JobCertificate.Alias} virtualServerName {virtualServerName}"); + + //if (aliasExists) + //{ + // var binding = store.GetBinding(jobConfiguration.JobCertificate.Alias); + + // _logger.LogTrace($"binding: {JsonConvert.SerializeObject(binding)}"); + // if (binding != null && binding?.sslcertkey_sslvserver_binding != null) + // { + // foreach (var sBinding in binding?.sslcertkey_sslvserver_binding) + // { + // virtualServerNames.Add(sBinding.servername); + // sniCerts.Add(sBinding.sn) + // } + // } + //} + if (!aliasExists) + { + if (!string.IsNullOrEmpty(virtualServerName)) { - _thumbprint = jobConfiguration.JobProperties["RenewalThumbprint"].ToString(); - _logger.LogDebug($"It's a renewal with thumbprint {_thumbprint}"); + foreach (string vsName in virtualServerName.Split(",")) + virtualServerNames.Add(vsName); } - //if there is no thumbprint from Keyfactor then it is an Add, else it is a renewal - if (string.IsNullOrEmpty(_thumbprint)) - { - _logger.LogDebug($"Begin Add/Enrollment... overwrite: {jobConfiguration.Overwrite}"); - PerformAdd(store, jobConfiguration.JobCertificate, keyPairName, virtualServerName, - jobConfiguration.Overwrite, sniCert, linkToIssuer); - _logger.LogDebug("End Add/Enrollment..."); - } - else + if (!string.IsNullOrEmpty(sniCert)) { - //PerformRenewal - //1. Get All Keys /config/sslcertkey store.ListKeyPairs() - var keyPairList = store.ListKeyPairs(); - - _logger.LogTrace($"KeyPairList: {JsonConvert.SerializeObject(keyPairList)}"); - - //2. For Each check the binding /config/sslcertkey_binding store.GetBinding(strKey) - foreach (var kp in keyPairList) + foreach(string sni in sniCert.Split(",")) { - //4. Open the file and check the thumbprint - var x = store.GetX509Certificate( - kp.cert.Substring(kp.cert.LastIndexOf("/", StringComparison.Ordinal) + 1), - out _); - - //5. If the Thumbprint matches the cert renewed from KF then PerformAdd With Overwrite - if (x?.Thumbprint == _thumbprint) + bool blnSni; + if (!Boolean.TryParse(sni.ToUpper(), out blnSni)) { - _logger.LogTrace($"Thumbprint Match: {_thumbprint}"); - var binding = store.GetBinding(kp.certkey); - _logger.LogTrace($"binding: {JsonConvert.SerializeObject(binding)}"); - if (binding != null) - { - if (binding?.sslcertkey_sslvserver_binding != null) - { - foreach (var sBinding in binding?.sslcertkey_sslvserver_binding) - { - _logger.LogTrace( - $"Starting PerformAdd Binding Name: {sBinding?.servername} kp.certkey: {kp?.certkey}"); - PerformAdd(store, jobConfiguration.JobCertificate, kp?.certkey, - sBinding?.servername, true, sniCert, linkToIssuer); - _logger.LogTrace( - $"Finished PerformAdd Binding Name: {sBinding?.servername} kp.certkey: {kp?.certkey}"); - } - } - else - { - _logger.LogTrace($"Renewing cert with no binding Information"); - PerformAdd(store, jobConfiguration.JobCertificate, kp?.certkey, null, true, null, linkToIssuer); - _logger.LogTrace($"Finished Renewing cert with no binding Information"); - } - } - else - { - _logger.LogTrace($"Renewing cert with no binding Information"); - PerformAdd(store, jobConfiguration.JobCertificate, kp?.certkey,null, true, null,linkToIssuer); - _logger.LogTrace($"Finished Renewing cert with no binding Information"); - } - + string errMessage = $"Invalid non boolean SNI value - {sni}"; + _logger.LogError(errMessage); + throw new Exception(errMessage); } + sniCerts.Add(blnSni); } } + + if (virtualServerNames.Count != sniCerts.Count) + { + string errMsg = $"Number of virtual server Names ({virtualServerNames.Count.ToString()}) does not match the number of SNI cert values ({sniCerts.Count.ToString()}"; + _logger.LogError(errMsg); + throw new Exception(errMsg); + } } - else + + PerformAdd(store, jobConfiguration.JobCertificate, virtualServerNames, + aliasExists, jobConfiguration.Overwrite, sniCerts, linkToIssuer); + + if (ApplicationSettings.AutoSaveConfig) { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = jobConfiguration.JobHistoryId, - FailureMessage = - $"You must use overwrite, a duplicate alias was found {jobConfiguration.JobCertificate.Alias}" - }; + _logger.LogDebug("Saving configuration..."); + store.SaveConfiguration(); } break; @@ -273,12 +190,70 @@ private JobResult ProcessJob(CitrixAdcStore store, ManagementJobConfiguration jo }; } - return new JobResult + JobResult result = new JobResult { Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = jobConfiguration.JobHistoryId, - FailureMessage = "" + JobHistoryId = jobConfiguration.JobHistoryId }; + + _logger.LogDebug("Logging out of Citrix..."); + store.Logout(); + + _logger.LogDebug("Exiting ProcessJob"); + + _logger.MethodExit(LogLevel.Debug); + return result; + } + + private void PerformAdd(CitrixAdcStore store, ManagementJobCertificate cert, + List virtualServerNames, bool aliasExists, bool overwrite, List sniCerts, bool linkToIssuer) + { + _logger.MethodEntry(LogLevel.Debug); + + _logger.LogDebug("Updating keyPair"); + + string certFileName = cert.Alias; + string keyFileName = certFileName + ".key"; + + if (aliasExists) + { + sslcertkey keyPair = store.GetKeyPairByName(cert.Alias); + certFileName = keyPair.cert; + keyFileName = keyPair.key; + } + + var (pemFile, privateKeyFile) = store.UploadCertificate(cert.Contents, cert.PrivateKeyPassword, certFileName, keyFileName, overwrite); + store.UpdateKeyPair(cert.Alias, pemFile.filename, privateKeyFile.filename); + + _logger.LogDebug("Updating cert bindings"); + //update cert bindings + if (virtualServerNames.Count > 0) + store.UpdateBindings(cert.Alias, virtualServerNames, sniCerts); + + if (linkToIssuer) + { + store.LinkToIssuer(cert.Contents, cert.PrivateKeyPassword, cert.Alias); + } + + _logger.MethodExit(LogLevel.Debug); + } + + private void PerformDelete(CitrixAdcStore store, ManagementJobCertificate cert) + { + _logger.MethodEntry(LogLevel.Debug); + + var sslKeyFile = store.GetKeyPairByName(cert.Alias); + + //1. Delete the Keypair + store.DeleteKeyPair(sslKeyFile); + + //2. Remove directory from file name, Delete the Key File + store.DeleteFile(Path.GetFileName(sslKeyFile.key)); + + //3. Remove directory from file name, Delete the Certificate File + store.DeleteFile(Path.GetFileName(sslKeyFile.cert)); + + _logger.MethodExit(LogLevel.Debug); } } } \ No newline at end of file diff --git a/README.md b/README.md index 60eca85..28e1f5b 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ This text would be entered in as the value for the __Server Password__, instead # Citrix ADC Orchestrator Configuration ## Overview -The Citrix ADC Orchestrator remotely manages certificates on the NetScaler device. Since the ADC supports services including: +The Citrix ADC Orchestrator remotely manages certificate objects on the NetScaler device. Since the ADC supports services including: Load Balancing, Authentication/Authorization/Auditing (AAA), and Gateways, this orchestrator can bind to any of these virtual servers when using unique virtual server names for each service. ### Permissions @@ -129,6 +129,7 @@ Allow * In the Keyfactor Command Database, run the following SQL Script to update the store types and store information [Upgrade Script](https://github.com/Keyfactor/citrix-adc-orchestrator/blob/snipamupdates/UpgradeScript.sql) ### Below are specific notes and limitations +* As of release 2.2.0, ONLY certificate objects (installed certificates) will be managed by the Citrix ADC Orchestrator Extension. Prior versions also managed certificate/key file combinations uploaded to the Citrix ADC device but not yet installed. This functionality has been removed due to issues attempting to match certificate and key files due to inconsistent file naming. * Direct PFX Binding Inventory * In NetScaler you can directly Bind a Pfx file to a Virtual Server. Keyfactor cannot inventory these because it does not have access to the password. The recommended way to Import PFX Files in NetScaler is descibed in this [NetScaler Documentation](https://docs.netscaler.com/en-us/citrix-adc/12-1/ssl/ssl-certificates/export-existing-certs-keys.html#convert-ssl-certificates-for-import-or-export) @@ -143,7 +144,7 @@ Allow * As defined in Test Cases 5 and 13 below, certificates that are bound to a server will not be removed. This was done to limit the possibility of bringing production servers down. Users are currently required to manually unbind the certificate from the server and then remove the cert using Command. This requirement may change in a future version. * Renewals - * The renewal process will find the thumbprint of the cert on all VServers and renew them in all places. See test cases #6 and #10 in the Test Cases section. + * The renewal process will find the thumbprint of the cert on all VServers and renew them in all places. See test cases #6 and #10 in the Test Cases section. Note, as of release 2.2.0, this will no longer be the case. Certificates (certificate objects) will be renewed based on the supplied alias only. Only the underlying system files attached to the provided alias will be replaced. * AutoSave Config * A new config.json file in the extension folder contains the 'AutoSaveConfig' flag with a default value of 'N'. When this flag is set to 'Y', successful configuration changes made by a management job will be automatically saved to disk; no interaction with the Citrix ADC UI is necessary. diff --git a/readme_source.md b/readme_source.md index a184e7a..8d49e84 100644 --- a/readme_source.md +++ b/readme_source.md @@ -1,7 +1,7 @@ # Citrix ADC Orchestrator Configuration ## Overview -The Citrix ADC Orchestrator remotely manages certificates on the NetScaler device. Since the ADC supports services including: +The Citrix ADC Orchestrator remotely manages certificate objects on the NetScaler device. Since the ADC supports services including: Load Balancing, Authentication/Authorization/Auditing (AAA), and Gateways, this orchestrator can bind to any of these virtual servers when using unique virtual server names for each service. ### Permissions @@ -29,6 +29,7 @@ Allow * In the Keyfactor Command Database, run the following SQL Script to update the store types and store information [Upgrade Script](https://github.com/Keyfactor/citrix-adc-orchestrator/blob/snipamupdates/UpgradeScript.sql) ### Below are specific notes and limitations +* As of release 2.2.0, ONLY certificate objects (installed certificates) will be managed by the Citrix ADC Orchestrator Extension. Prior versions also managed certificate/key file combinations uploaded to the Citrix ADC device but not yet installed. This functionality has been removed due to issues attempting to match certificate and key files due to inconsistent file naming. * Direct PFX Binding Inventory * In NetScaler you can directly Bind a Pfx file to a Virtual Server. Keyfactor cannot inventory these because it does not have access to the password. The recommended way to Import PFX Files in NetScaler is descibed in this [NetScaler Documentation](https://docs.netscaler.com/en-us/citrix-adc/12-1/ssl/ssl-certificates/export-existing-certs-keys.html#convert-ssl-certificates-for-import-or-export) @@ -43,7 +44,7 @@ Allow * As defined in Test Cases 5 and 13 below, certificates that are bound to a server will not be removed. This was done to limit the possibility of bringing production servers down. Users are currently required to manually unbind the certificate from the server and then remove the cert using Command. This requirement may change in a future version. * Renewals - * The renewal process will find the thumbprint of the cert on all VServers and renew them in all places. See test cases #6 and #10 in the Test Cases section. + * The renewal process will find the thumbprint of the cert on all VServers and renew them in all places. See test cases #6 and #10 in the Test Cases section. Note, as of release 2.2.0, this will no longer be the case. Certificates (certificate objects) will be renewed based on the supplied alias only. Only the underlying system files attached to the provided alias will be replaced. * AutoSave Config * A new config.json file in the extension folder contains the 'AutoSaveConfig' flag with a default value of 'N'. When this flag is set to 'Y', successful configuration changes made by a management job will be automatically saved to disk; no interaction with the Citrix ADC UI is necessary.