ps/Modules/Alkami.Ops.Certificates/SecretServerImporter.cs
2023-05-30 22:51:22 -07:00

787 lines
38 KiB
C#

using Alkami.Ops.Certificates.Data;
using Alkami.Ops.Certificates.SecretServer;
using Alkami.Ops.Certificates.Utilities;
using Alkami.Ops.Common.Cryptography;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Management.Automation;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Alkami.Ops.Certificates
{
internal class SecretServerImporter : IDisposable
{
private readonly string _tempPath;
private readonly string _globalPassword;
private readonly SecretServerClient _secretServerClient;
private const string CertificateTemplateTypeName = "Automated_Certificate";
private readonly string ExportCertificatesScript;
private static long certCount = 0;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="secretSite">The base URL of the secret server site.</param>
/// <param name="secretUser">Secret Server username with API access.</param>
/// <param name="secretPassword">Secret server user password.</param>
public SecretServerImporter(string secretSite, string secretUser, string secretPassword)
{
_tempPath = Path.Combine(Path.GetTempPath(), "CertificateExport");
if (Directory.Exists(_tempPath))
{
Extensions.ClearDirectory(_tempPath);
}
else
{
Directory.CreateDirectory(_tempPath);
}
_globalPassword = GeneratePassword();
_secretServerClient = new SecretServerClient(secretSite, secretUser, secretPassword);
// Read in powershell scripts.
ExportCertificatesScript = Resources.ExportRemoteCertificates;
}
/// <summary>
/// Cleanup
/// </summary>
public void Dispose()
{
if (Directory.Exists(_tempPath))
{
Directory.Delete(_tempPath, true);
}
_secretServerClient?.Dispose();
}
/// <summary>
/// Creates machine-friendly per-pod secrets, so a server can download 4 secrets instead of 200.
/// </summary>
/// <param name="friendlySecretBaseFolderPath"></param>
/// <param name="machineSecretBaseFolderPath"></param>
public void CreatePodSecrets(string friendlySecretBaseFolderPath, string machineSecretBaseFolderPath, string[] importableUsers)
{
var baseFolder = _secretServerClient.GetOrAddFolderAsync(friendlySecretBaseFolderPath).GetAwaiter().GetResult();
// The secret server will only return -all- folders under a subfolder. Can't just query for folders at a time.
var folders = _secretServerClient.GetFoldersByParentFolder(baseFolder.ID);
// Parse apart the paths of the folders to strip off the base path.
friendlySecretBaseFolderPath = friendlySecretBaseFolderPath.Replace('/', '\\');
var folderPaths = folders.Select(folder => folder.FolderPath)
.Select(path => path.Substring(path.IndexOf(friendlySecretBaseFolderPath) + friendlySecretBaseFolderPath.Length));
// Parse apart the environment types, and pods that exist (+ common)
// Levels:
// 1) EnvironmentType
// 2) Environment (or Common)
// 3) Web / App / All
var splitPaths = folderPaths.Select(path => path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
var environmentTypes = splitPaths
.Where(split => split.Length >= 1)
.Select(split => split[0])
.Distinct();
foreach (var environmentType in environmentTypes)
{
var environments = splitPaths
.Where(split => split.Length >= 2)
.Where(split => string.Equals(split[0], environmentType, StringComparison.OrdinalIgnoreCase))
.Select(split => split[1])
.Distinct();
foreach (var environment in environments)
{
CreatePodSecrets(friendlySecretBaseFolderPath, machineSecretBaseFolderPath, environmentType, environment, importableUsers);
}
}
}
/// <summary>
/// Creates machine-friendly per-pod secrets, so a server can download 4 secrets instead of 200.
/// </summary>
/// <param name="friendlySecretBaseFolderPath">Secret server path of the friendly secrets folder.</param>
/// <param name="machineSecretBaseFolderPath">Secret server path of the machine per-pod secrets folder.</param>
/// <param name="environmentType">The type of environment.</param>
/// <param name="environment">The name of the environment. (Or "Common")</param>
private void CreatePodSecrets(string friendlySecretBaseFolderPath, string machineSecretBaseFolderPath, string environmentType, string environment, string[] importableUsers)
{
using (PowerShell powerShellSession = PowerShell.Create())
{
var environmentPath = Path.Combine(friendlySecretBaseFolderPath, environmentType, environment);
string[] serverTypes = new string[] { "All", "Web", "App" };
// Get the secret template type.
var secretTemplateType = _secretServerClient.GetSecretTemplateByName(CertificateTemplateTypeName);
foreach (var serverType in serverTypes)
{
// Clear out the temp path directory. Only one of these processes should run at a time.
// TODO: Rewrite the function to be pathed by environment type and environment so multiple of them can be run in parallel.
if (Directory.Exists(_tempPath) && Directory.EnumerateFiles(_tempPath).Any())
{
Extensions.ClearDirectory(_tempPath);
}
var secretsPath = Path.Combine(environmentPath, serverType);
var folder = _secretServerClient.GetOrAddFolderAsync(secretsPath).GetAwaiter().GetResult();
var secrets = _secretServerClient.GetSecretsByFolder(folder);
// Load the full secrets.
var fullSecrets = secrets.Select(secret => _secretServerClient.GetSecretByID(secret.ID).GetAwaiter().GetResult());
// Download each secret to a separate folder by ID.
foreach (var secret in fullSecrets)
{
// Download the zip.
Console.WriteLine($"Attempting to download {secret.ID}");
var zipName = $"{secret.ID}.zip";
var zipDownloadLocation = Path.Combine(_tempPath, zipName);
if (!_secretServerClient.DownloadFile(zipDownloadLocation, secret).GetAwaiter().GetResult())
{
Console.WriteLine($"Failed to download secret ID {secret.ID}");
continue;
}
// Unzip the file to a per-secret folder.
var outputFolder = Path.Combine(_tempPath, secret.ID.ToString());
if (!Directory.Exists(outputFolder))
{
Directory.CreateDirectory(outputFolder);
}
// Unzip it.
ZipFile.ExtractToDirectory(zipDownloadLocation, outputFolder);
var pfxFiles = Directory.GetFiles(outputFolder, "*.pfx", SearchOption.AllDirectories);
foreach (var file in pfxFiles)
{
// re-export secret password with global password
var cert = new X509Certificate2(file, secret["Import Password"], X509KeyStorageFlags.Exportable);
byte[] exportedBytes = cert.Export(X509ContentType.Pkcs12, _globalPassword);
File.WriteAllBytes(file, exportedBytes);
}
}
// Copy all of the certificates together, preserving folder paths.
var podSecretOutputFolder = Path.Combine(_tempPath, "PodSecretOutputFolder");
if (!Directory.Exists(podSecretOutputFolder))
{
Directory.CreateDirectory(podSecretOutputFolder);
}
var secretFolders = Directory.GetDirectories(_tempPath);
foreach (var sourceFolder in secretFolders)
{
// Source directories, a folder for each secret server secret.
var sourceDirectories = Directory.GetDirectories(sourceFolder, "*", SearchOption.AllDirectories);
// Create folders in the destination folder
foreach (string dirPath in sourceDirectories)
{
var dstPath = dirPath.Replace(sourceFolder, podSecretOutputFolder);
if (!Directory.Exists(dstPath))
{
Directory.CreateDirectory(dstPath);
}
}
// Copy over the secrets.
foreach (string newPath in Directory.GetFiles(sourceFolder, "*.*", SearchOption.AllDirectories))
{
// If the file already exists in the combined secret, and it isn't the same file, come up with a new non-duplicate name and copy the secret over.
var podSecretDestinationPath = newPath.Replace(sourceFolder, podSecretOutputFolder);
int counter = 1;
bool doCopy = true;
do
{
// Copy over the file if it doesn't exist.
if (!File.Exists(podSecretDestinationPath))
{
break;
}
// If the files are equal, the cert already exists in the combined folder, so break out.
if (Extensions.FilesAreEqual(new FileInfo(newPath), new FileInfo(podSecretDestinationPath)))
{
doCopy = false;
break;
}
// Otherwise we're going to try to come up with a unique filename.
// This loop is going to keep going until we either find the exact same cert, or we arrive on a unique file name.
string nonConflictingPath = Path.Combine(
Path.GetDirectoryName(newPath),
Path.GetFileNameWithoutExtension(newPath),
counter.ToString(),
Path.GetExtension(newPath));
podSecretDestinationPath = nonConflictingPath;
counter++;
} while (true);
if (doCopy)
{
File.Copy(newPath, podSecretDestinationPath, true);
}
}
}
// Zip up the combined certs.
var podSecretName = serverType;
var podSecretZipPath = Path.Combine(_tempPath, $"{podSecretName}.zip");
ZipFile.CreateFromDirectory(podSecretOutputFolder, podSecretZipPath);
// Create secret with combined zip.
var secretPath = Path.Combine(machineSecretBaseFolderPath, environmentType, environment);
var destinationSecretFolder = _secretServerClient.GetOrAddFolderAsync(secretPath).GetAwaiter().GetResult();
// See if a secret already exists, and if it does, delete it.
var existingSecret = _secretServerClient.FindSecretByName(serverType, destinationSecretFolder.ID);
if (existingSecret != null)
{
_secretServerClient.DeleteSecret(existingSecret.ID).GetAwaiter().GetResult();
}
// Create the new secret stub by template ID.
var secretStub = _secretServerClient.CreateSecretStubByTemplateIDAsync(secretTemplateType.ID, podSecretName + "-" + destinationSecretFolder.ID, destinationSecretFolder.ID).GetAwaiter().GetResult();
var currentZipHash = "";
using (var stream = File.OpenRead(podSecretZipPath))
{
currentZipHash = Extensions.GetMd5HashString(stream);
}
// Fill out secret details.
secretStub["Import Password"] = _globalPassword;
secretStub["Certificate Name"] = podSecretName;
secretStub["Thumbprint"] = "N/A";
secretStub["ExpirationDate"] = "";
secretStub["File Hash"] = currentZipHash;
// Create the secret.
var newSecret = _secretServerClient.CreateSecret(secretStub).GetAwaiter().GetResult();
// Upload the zip.
_secretServerClient.UploadFile(newSecret, podSecretZipPath).GetAwaiter().GetResult();
}
}
}
/// <summary>
/// Imports certificates from all of the given servers into the secret server.
/// </summary>
/// <param name="servers">List of server hostnames.</param>
/// <param name="baseSecretFolder">Secret server folder path to drop the secrets in.</param>
public void Import(IEnumerable<string> servers, string baseSecretFolder)
{
// Make sure the servers are unique just in case.
servers = servers.Select(s => s.ToLower()).Distinct();
var serverData = new Dictionary<string, ServerInfo>();
var environmentData = new Dictionary<string, EnvironmentInfo>();
var certificateData = new Dictionary<string, CertificateInfo>();
if (Directory.Exists(_tempPath))
{
// Remove directories and files under _tempPath.
foreach (var directory in Directory.GetDirectories(_tempPath))
{
Directory.Delete(directory, true);
}
foreach (var file in Directory.GetFiles(_tempPath))
{
File.Delete(file);
}
}
// Gather server/environment data from the servers.
using (var timer = new QuickWatch("GatherEnvironmentData"))
{
GatherEnvironmentData(servers, ref environmentData, ref serverData);
}
// Download certificates from each server.
using (var timer = new QuickWatch("DownloadCertificatesFromServers"))
{
DownloadCertificatesFromServers(servers, _tempPath);
}
// Build the tree of certificate information, to weed out unique certificates that need uploading.
using (var timer = new QuickWatch("BuildCertificateData"))
{
BuildCertificateData(_tempPath, ref serverData, ref certificateData);
}
// Upload the secrets to the secret server.
using (var timer = new QuickWatch("UploadCertificatesToSecretServer"))
{
UploadCertificatesToSecretServer(baseSecretFolder, ref environmentData, ref certificateData);
}
}
/// <summary>
/// Gathers environment data from each of the servers, and tracks the data in _environmentData and _serverData
/// </summary>
/// <param name="servers"></param>
private void GatherEnvironmentData(IEnumerable<string> servers, ref Dictionary<string, EnvironmentInfo> environmentData, ref Dictionary<string, ServerInfo> serverData)
{
// Read server/environment data from all of the servers.
foreach (var server in servers)
{
// Grab the server name/type/envType/host from the server's machine config.
var serverInfo = Extensions.GetServerInfo(server);
// Look up the environment.
var environmentNameLower = serverInfo.EnvironmentName.ToLower();
EnvironmentInfo environment = null;
if (!environmentData.TryGetValue(environmentNameLower, out environment))
{
environment = new EnvironmentInfo(serverInfo.EnvironmentName, serverInfo.EnvironmentType);
environmentData[environmentNameLower] = environment;
}
// Recreate the server to include the environment.
serverInfo = new ServerInfo(serverInfo, environment);
serverData[server] = serverInfo;
// Record the server in the environment.
environment.Servers.Add(serverInfo);
}
}
/// <summary>
/// Generates a strong password of [length] characters.
/// </summary>
/// <param name="length">The length of the password</param>
/// <returns></returns>
private string GeneratePassword(int length = 32)
{
RNGCryptoServiceProvider cryptRNG = new RNGCryptoServiceProvider();
byte[] tokenBuffer = new byte[length];
cryptRNG.GetBytes(tokenBuffer);
return Convert.ToBase64String(tokenBuffer);
}
/// <summary>
/// Aliases store string names to the Alkami certificate import folder names.
/// </summary>
/// <param name="storename">String name of the store.</param>
/// <returns></returns>
private string GetStoreFolderName(string storename)
{
switch (storename.ToLower())
{
case "my":
return "Personal";
case "ca":
return "IA";
case "root":
return "Root";
case "trustedpeople":
return "TrustedPeople";
default:
throw new Exception("Unknown Store Name Type");
}
}
/// <summary>
/// Downloads certificates from servers and unzips them to the output directory.
/// </summary>
/// <param name="servers"></param>
/// <param name="outputDirectory"></param>
private void DownloadCertificatesFromServers(IEnumerable<string> servers, string outputDirectory)
{
// Export certs from every server.
var serverString = string.Join(",", servers);
using (PowerShell instance = PowerShell.Create())
{
instance.AddScript(ExportCertificatesScript);
instance.AddParameter("serverString", serverString);
instance.AddParameter("exportPassword", _globalPassword);
instance.AddParameter("importPath", outputDirectory);
var psOutput = instance.Invoke();
if (instance.Streams.Error.Any())
{
Console.WriteLine("There were Errors executing powershell:");
foreach (var error in instance.Streams.Error)
{
Console.WriteLine(error);
}
}
}
}
/// <summary>
/// Loads all certificates into memory, builds certificate chains, and determines certificate cert/server/environment relationships.
/// </summary>
/// <param name="serverCertificateDirectory">The directory of certificates, per server folder.</param>
private void BuildCertificateData(string serverCertificateDirectory, ref Dictionary<string, ServerInfo> serverData, ref Dictionary<string, CertificateInfo> certificateData)
{
var certificateFiles = Directory.GetFiles(serverCertificateDirectory, "*", SearchOption.AllDirectories).Select(p => Path.GetFullPath(p)).ToArray();
var certificateFileHasPrivateKey = new Dictionary<string, bool>();
var fileToCertificate = new ConcurrentDictionary<string, CertificateInfo>();
// Load all of the certificates into memory so we can make sense of them.
var options = new ParallelOptions();
options.MaxDegreeOfParallelism = 32;
foreach (var file in certificateFiles)
{
var extension = Path.GetExtension(file);
string newPassword = null;
X509Certificate2 cert = null;
if (extension == ".cer")
{
cert = new X509Certificate2(file);
}
else if (extension == ".pfx")
{
cert = new X509Certificate2(file, _globalPassword, X509KeyStorageFlags.Exportable);
// Generate a unique password for this cert going forward.
newPassword = GeneratePassword();
}
else
{
throw new Exception($"Unhandled certificate extension {extension}");
}
// Store if the particular cert has a private key.
certificateFileHasPrivateKey[file] = cert.HasPrivateKey;
var certInfo = new CertificateInfo(cert, file, newPassword);
if (!fileToCertificate.TryAdd(file, certInfo))
{
throw new Exception($"Was unable to track certificate {file}. Investigate.");
}
};
// Identify unique certificates from the list of certs from every server which includes duplicates.
// Prefer to select unique certs that include private keys.
var uniqueCertificates = new Dictionary<string, CertificateInfo>();
var fileToCertificateEnum = fileToCertificate.Where(cert => cert.Value.Password != null).Concat(fileToCertificate);
foreach (var certInfoKV in fileToCertificateEnum)
{
var certInfo = certInfoKV.Value;
if (!uniqueCertificates.ContainsKey(certInfo.Thumbprint))
{
uniqueCertificates.Add(certInfo.Thumbprint, certInfo);
}
}
// Run back through each non-unique certificate, and assign servers/environments to each unique cert.
// This is tracked by the folder that the certificate was loaded from. File paths in the form of:
// "basePath\serverName\certificateStore\certificateName.pfx"
foreach (var file in certificateFiles)
{
// Select the server/store out of the folder path.
var fileSplit = file.Split(new char[] { '\\' });
var serverName = fileSplit[fileSplit.Length - 3].ToLower();
var storeName = fileSplit[fileSplit.Length - 2].ToLower();
// Look up the unique certificate by the thumbprint of the non-unique certificate.
var nonUniqueCertInfo = fileToCertificate[file];
var uniqueCertInfo = uniqueCertificates[nonUniqueCertInfo.Thumbprint];
// Add the server/environment that the cert is in to the unique cert info object.
var serverInfo = serverData[serverName];
var environmentName = serverInfo.EnvironmentName.ToLower();
uniqueCertInfo.Servers.TryAdd(serverName, serverInfo);
uniqueCertInfo.Environments.TryAdd(environmentName, serverInfo.Environment);
// Record the store that the certificate belongs in, if it does not already exist.
if (!uniqueCertInfo.Stores.Any(certStore => string.Equals(certStore.StoreName, storeName, StringComparison.OrdinalIgnoreCase)))
{
bool storeHasPrivateKey = certificateFileHasPrivateKey[file];
StoreInfo store = new StoreInfo(storeName, storeHasPrivateKey);
uniqueCertInfo.Stores.Add(store);
}
}
// Copy all of the unique certificates up to _certificateData, which will be the store of certs for other functions.
foreach (var certInfoKV in uniqueCertificates)
{
certificateData.Add(certInfoKV.Key, certInfoKV.Value);
}
// For each unique certificate, build out the certificate chain and figure out which certs require which other certs.
// Later we will use this data to create secrets for the "leaf certificates" that are not intermediates of other certificates.
foreach (var certificateInfo in uniqueCertificates)
{
var certificate = certificateInfo.Value.Certificate;
X509Chain chain = new X509Chain();
chain.Build(certificate);
List<X509Certificate2> chainCerts = chain.ChainElements.ToList();
// Exit if there's no chain to walk.
if (chainCerts.Count <= 1)
{
continue;
}
// Build the tree of parent/child certificate associations.
for (int i = 1; i < chainCerts.Count; i++)
{
var parentCert = chainCerts[i];
var childCert = chainCerts[i - 1];
// Apparently certificates in the chain can exist and validate WITHOUT being loaded into the stores of the remote machines.
// They are most likely getting validated through the domain controller.
if (!certificateData.ContainsKey(parentCert.Thumbprint))
{
string filename = null; // No filename because it wasn't exported from a cert store.
var info = new CertificateInfo(parentCert, filename);
certificateData[parentCert.Thumbprint] = info;
}
// Attach the parent/child info to certificates for tracking.
var parentInfo = certificateData[parentCert.Thumbprint];
var childInfo = certificateData[childCert.Thumbprint];
childInfo.Parent = parentInfo;
parentInfo.Children.Add(childInfo);
}
}
}
/// <summary>
/// Uploads tracked certificates in _certificateData to the secret server at the base path.
/// </summary>
/// <param name="baseSecretFolder">The base folder path in the secret server to upload certs to.</param>
private void UploadCertificatesToSecretServer(string baseSecretFolder, ref Dictionary<string, EnvironmentInfo> environmentData, ref Dictionary<string, CertificateInfo> certificateData)
{
// Grab the list of certificates to create secrets for.
var certsToExport = certificateData.Select(x => x.Value).ToList();
var secretImportZipPath = Path.Combine(_tempPath, "SecretImportTemp");
if (!Directory.Exists(secretImportZipPath))
{
Directory.CreateDirectory(secretImportZipPath);
}
// Create folders on the secret server for every environment, so we can multithread the certificate uploads without fear of stomping.
var majorPodRegex = @"[^\.](\d+)";
foreach (var environment in environmentData)
{
var type = environment.Value.EnvironmentType;
type = char.ToUpper(type[0]) + type.Substring(1);
var path = Path.Combine(baseSecretFolder, type);
// Define the server types.
var serverTypeFolders = new string[] { "Web", "App", "All" };
// Create the common folder for the environment type.
foreach (var serverType in serverTypeFolders)
{
var serverTypePath = Path.Combine(path, "Common", serverType);
var folder = _secretServerClient.GetOrAddFolderAsync(serverTypePath).GetAwaiter().GetResult();
}
// Only production cares about separate cert folders per environment.
if (type == "Production")
{
var matches = Regex.Matches(environment.Value.Name, majorPodRegex);
string majorPod = matches[0].Captures[0].Value.Trim();
path = Path.Combine(path, majorPod);
}
// Server type folders inside the pod.
foreach (var serverType in serverTypeFolders)
{
var serverTypePath = Path.Combine(path, serverType);
var folder = _secretServerClient.GetOrAddFolderAsync(serverTypePath).GetAwaiter().GetResult();
}
}
// Fetch all the secrets from the base folder, so we can check for secrets we've already loaded.
bool includeSubfolders = true;
var baseFolder = _secretServerClient.GetOrAddFolderAsync(baseSecretFolder).GetAwaiter().GetResult();
var allSecrets = _secretServerClient.GetSecretsByFolder(baseFolder, includeSubfolders);
try
{
var secretTemplateType = _secretServerClient.GetSecretTemplateByName(CertificateTemplateTypeName);
// Create separate .zip individual files for each certificate being uploaded.
var options = new ParallelOptions();
options.MaxDegreeOfParallelism = 32;
Parallel.ForEach(certsToExport, options, (cert) =>
{
// Create a folder for the cert.
var certFolderPath = Path.Combine(secretImportZipPath, cert.Thumbprint);
if (!Directory.Exists(certFolderPath))
{
Directory.CreateDirectory(certFolderPath);
}
// Copy all of the certs for the chain
var currentCert = cert;
Console.WriteLine($"Working on {cert.Name}");
do
{
// Don't export this cert because it didn't exist in the stores of the remote machines.
if (cert.FileName == null)
{
currentCert = currentCert.Parent;
continue;
}
// For each store the cert belongs in.
foreach (var store in cert.Stores)
{
// Export certificates to the appropriate place and store folder.
var outputDirectory = Path.Combine(certFolderPath, store.StoreName);
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
var password = store.HasPrivateKey ? cert.Password : null;
CertificateHelper.ExportCertificate(cert.Certificate, outputDirectory, password);
}
// Move along to the next cert in the chain.
currentCert = currentCert.Parent;
} while (currentCert != null);
// Zip up the cert for upload to secret.
var outputZipPath = Path.Combine(secretImportZipPath, $"{cert.Thumbprint}.zip");
ZipFile.CreateFromDirectory(certFolderPath, outputZipPath);
var currentZipHash = "";
using (var stream = File.OpenRead(outputZipPath))
{
currentZipHash = Extensions.GetMd5HashString(stream);
}
// Figure out what environment subfolder the certificate is going in on the secret server.
var serverNames = cert.Servers.Select(serverInfoKV => serverInfoKV.Key.ToLower());
var appServerNames = new string[] { "vma", "app", "fab", "mic" };
var webServerNames = new string[] { "vmw", "web" };
bool onApps = appServerNames.Any(type => serverNames.Any(serverName => serverName.Contains(type)));
bool onWebs = webServerNames.Any(type => serverNames.Any(serverName => serverName.Contains(type)));
bool onBothWebsAndApps = onApps && onWebs;
// Determine the types of environments that this cert exists in.
// We duplicate certs for each environment. It's too reckless to have a "every environment type" common folder.
var environmentTypes = cert.Environments
.Select(e => e.Value.EnvironmentType.ToLower())
.Distinct()
.Select(et => char.ToUpper(et.ElementAt(0)) + et.Substring(1)); // Upper case environment type names.
foreach (var environmentType in environmentTypes)
{
// Build secret server folder path to create the secret.
// Determine if the cert is on multiple pods within an environment type.
var environments = cert.Environments.Where(e => e.Value.EnvironmentType.ToLower() == environmentType.ToLower());
bool onMultiplePods = environments.Count() >= 2;
var secretServerFolderPath = Path.Combine(baseSecretFolder, environmentType);
if (onMultiplePods)
{
secretServerFolderPath = Path.Combine(secretServerFolderPath, "Common");
}
else if (environmentType == "Production")
{
var matches = Regex.Matches(environments.First().Value.Name, majorPodRegex);
string majorPod = matches[0].Captures[0].Value.Trim();
secretServerFolderPath = Path.Combine(secretServerFolderPath, majorPod);
}
string serverFolder = string.Empty;
if (onBothWebsAndApps)
{
serverFolder = "All";
}
else if (onWebs)
{
serverFolder = "Web";
}
else
{
serverFolder = "App";
}
secretServerFolderPath = Path.Combine(secretServerFolderPath, serverFolder);
var fullSecretPath = Path.Combine(secretServerFolderPath, cert.UniqueName);
var folder = _secretServerClient.GetOrAddFolderAsync(secretServerFolderPath).GetAwaiter().GetResult();
// Figure out if the secret already exists for the cert.
var secretName = cert.UniqueName;
if (allSecrets.Any(existingSecret => existingSecret.FolderID == folder.ID && string.Equals(existingSecret.Name, secretName, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
// Create the secret.
var secretStub = _secretServerClient.CreateSecretStubByTemplateIDAsync(secretTemplateType.ID, secretName + "-" + folder.ID, folder.ID).GetAwaiter().GetResult();
var expiration = cert.Certificate.NotAfter.ToString();
// Fill out details about the cert.
secretStub["Import Password"] = cert.Password;
secretStub["Certificate Name"] = secretName;
secretStub["Thumbprint"] = cert.Thumbprint;
secretStub["ExpirationDate"] = expiration;
secretStub["File Hash"] = currentZipHash;
var foundCert = _secretServerClient.FindSecretByName(secretName);
if (foundCert != null)
{
Console.WriteLine($"Secret exists {secretName}");
}
else
{
try
{
// Create the secret.
var secret = _secretServerClient.CreateSecret(secretStub).GetAwaiter().GetResult();
// Upload the cert .zip.
_secretServerClient.UploadFile(secret, outputZipPath).GetAwaiter().GetResult();
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e.Message);
}
}
}
});
}
finally
{
// Clean up the cert directory.
if (Directory.Exists(secretImportZipPath))
{
Directory.Delete(secretImportZipPath, true);
}
}
}
}
}