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

303 lines
14 KiB
C#

using System;
using System.Linq;
using System.ServiceModel;
using System.Threading.Tasks;
using Alkami.Ops.SecretServer.Enum;
using Alkami.Ops.SecretServer.Messages;
using Alkami.Ops.SecretServer.Model;
using Alkami.Ops.SecretServer.SSWebService;
namespace Alkami.Ops.SecretServer
{
/// <summary>
/// A simple client for secret server
/// </summary>
public sealed class Client : IDisposable
{
private string _authenticationToken;
private readonly SSWebServiceSoapClient _client;
/// <summary>
/// Gets all secrets from a folder based on folder name
/// </summary>
/// <param name="folderName"></param>
/// <returns></returns>
public async Task<SecretSearchResult> GetFolderSecretsAsync(string folderName)
{
try
{
Console.WriteLine($"Attempting to find all secrets in folder: {folderName}");
var folderSearchResult = await FindFolderAsync(folderName);
if (folderSearchResult.Status == ResultStatus.Success)
{
Console.WriteLine($"Attempting to get all secrets from folder with name: {folderName} and folder ID: {folderSearchResult.Folders.First().Id}");
return await GetAllSecretsFromFolderAsync(folderSearchResult.Folders.First());
}
Console.WriteLine($"Attempt to find folder {folderName} resulted in errors: {string.Join(",", folderSearchResult.Errors)}");
return new SecretSearchResult
{
Errors = folderSearchResult.Errors,
Message = folderSearchResult.Message
};
}
catch (Exception e)
{
Console.WriteLine($"An error occurred while attempting to get all secrets from folder with name: {folderName}. Exception: {e}");
throw;
}
}
/// <summary>
/// Authenticates using the supplied username, password, and domain
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <param name="domain"></param>
/// <returns></returns>
public async Task<AuthenticationResult> AuthenticateAsync(string userName, string password, string domain)
{
try
{
Console.WriteLine($"Attempting authentication with username: {userName} and domain {domain}");
var result = await _client.AuthenticateAsync(userName, password, string.Empty, domain);
Console.WriteLine(result.Errors != null && result.Errors.Any()
? $"Encountered errors during authentication: {string.Join(",", result.Errors)}"
: "Authentication successful");
return new AuthenticationResult
{
Status = (result.Errors != null && result.Errors.Any() ? ResultStatus.Failure : ResultStatus.Success),
Message = (result.Errors != null && result.Errors.Any() ? "Authentication Failed" : "Authentication Successful"),
Errors = result.Errors?.Select(e => new ResultError(null, e)).ToList(),
Token = _authenticationToken = result.Token
};
}
catch (Exception e)
{
Console.WriteLine($"An error occurred while attempting to authentice with username: {userName} and domain {domain}. Exception: {e}");
throw;
}
}
/// <summary>
/// Returns a collection of secrets from a folder as a <see cref="SecretSearchResult"/>
/// </summary>
/// <param name="folder"></param>
/// <returns></returns>
private async Task<SecretSearchResult> GetAllSecretsFromFolderAsync(Folder folder)
{
try
{
Console.WriteLine($"Attempting to pull all secrets from folder {folder.Name}");
var folderSearchResponse = await _client.SearchSecretsByFolderAsync(_authenticationToken, "*", folder.Id, false, false, true);
var results = new SecretSearchResult();
if (folderSearchResponse.SearchSecretsByFolderResult.Errors != null && folderSearchResponse.SearchSecretsByFolderResult.Errors.Any())
{
Console.WriteLine($"Attempt to pull secrets from folder {folder.Name} resulted in errors: {string.Join(",", folderSearchResponse.SearchSecretsByFolderResult.Errors)}");
results.Errors = folderSearchResponse.SearchSecretsByFolderResult.Errors.Select(e => new ResultError(null, e)).ToList();
results.Status = ResultStatus.Failure;
}
foreach (var secretSummary in folderSearchResponse.SearchSecretsByFolderResult.SecretSummaries)
{
Console.WriteLine($"Getting secret details for secret ID: {secretSummary.SecretId} with name: {secretSummary.SecretName}");
var secretResponse = await _client.GetSecretAsync(_authenticationToken, secretSummary.SecretId, false, null);
if (secretResponse.GetSecretResult.Errors.Any())
{
Console.WriteLine($"Attempt to pull secrets from folder {folder.Name} resulted in errors: {string.Join(",", secretResponse.GetSecretResult.Errors)}");
results.Errors.Add(new ResultError(secretSummary.SecretId,
$"Error {secretResponse.GetSecretResult.Errors[0]} occurred while getting secret {secretSummary.SecretId}"));
results.Status = ResultStatus.PartialFailure;
continue;
}
switch (secretSummary.SecretTypeName.ToUpperInvariant())
{
case "SSL CERTIFICATE":
case "CERTIFICATE":
{
Console.WriteLine($"Secret with ID {secretSummary.SecretId} determined to be of type Certificate");
var certificate = new Certificate(secretResponse.GetSecretResult.Secret);
if (!certificate.FileId.HasValue)
{
Console.WriteLine($"Certificate with secret ID {secretSummary.SecretId} does not have an attachment");
results.Errors.Add(new ResultError(secretSummary.SecretId,
$"Unable to find a certificate attachemnt for secret {certificate.Id}"));
continue;
}
// Download the PFX Files
Console.WriteLine($"Attempting to download attachment for Certificate with secret ID {secretSummary.SecretId}");
var certificateAttachment = await DownloadFileAsync(certificate.Id, certificate.FileId.Value);
certificate.FileAttachment = certificateAttachment.FileAttachment;
certificate.FileName = certificateAttachment.FileName;
results.Secrets.Add(secretResponse.GetSecretResult.Secret.Id, certificate);
break;
}
case "WINDOWS ACCOUNT":
{
Console.WriteLine($"Secret with ID {secretSummary.SecretId} determined to be of type User");
results.Secrets.Add(secretResponse.GetSecretResult.Secret.Id, new Model.User(secretResponse.GetSecretResult.Secret));
break;
}
case "SQL SERVER ACCOUNT":
{
Console.WriteLine($"Secret with ID {secretSummary.SecretId} determined to be of type ConnectionString");
results.Secrets.Add(secretResponse.GetSecretResult.Secret.Id, new ConnectionString(secretResponse.GetSecretResult.Secret));
break;
}
default:
{
Console.WriteLine($"Secret with ID {secretSummary.SecretId} is an unmapped type");
results.Errors.Add(new ResultError(secretSummary.SecretId,
$"Unknown secret type {secretSummary.SecretTypeName} found for secret ID {secretSummary.SecretId}"));
results.Status = ResultStatus.PartialFailure;
break;
}
}
}
if (!results.Secrets.Any())
{
// All secret searches failed
results.Status = ResultStatus.Failure;
}
else if (results.Status == ResultStatus.Unknown)
{
// All secret searches passed, otherwise this would be PartialFailure
results.Status = ResultStatus.Success;
}
return results;
}
catch (Exception e)
{
Console.WriteLine($"An error occurred while attempting to pull all secrets from folder {folder.Name}. Exception: {e}");
throw;
}
}
/// <summary>
/// Downloads a file based on the parent Secret and File ItemId
/// </summary>
/// <param name="secretId"></param>
/// <param name="fileItemId"></param>
/// <returns></returns>
private async Task<Messages.FileDownloadResult> DownloadFileAsync(int secretId, int fileItemId)
{
try
{
Console.WriteLine($"Attempting to download file attachment for secret with ID: {secretId} and fileItemID: {fileItemId}");
var downloadResponse = await _client.DownloadFileAttachmentByItemIdAsync(_authenticationToken, secretId, fileItemId);
var result = new Messages.FileDownloadResult();
if (downloadResponse.Errors.Any() || downloadResponse.FileAttachment.Length == 0)
{
Console.WriteLine($"Attempt to download attachment from secret with ID {secretId} resulted in errors: {string.Join(",", downloadResponse.Errors)}");
result.Status = ResultStatus.Failure;
result.Errors = downloadResponse.Errors.Select(e => new ResultError(secretId, e)).ToList();
}
else
{
result.Status = ResultStatus.Success;
result.FileAttachment = downloadResponse.FileAttachment;
result.FileName = downloadResponse.FileName;
}
return result;
}
catch (Exception e)
{
Console.WriteLine($"An error occurred while attempting to download file attachment for secret with ID: {secretId} and fileItemID: {fileItemId}. Exception: {e}");
throw;
}
}
/// <summary>
/// Finds a Secret Folder based on FolderName
/// </summary>
/// <param name="folderName"></param>
/// <returns></returns>
private async Task<FolderSearchResult> FindFolderAsync(string folderName)
{
try
{
Console.WriteLine($"Attempting to find folder with name: {folderName}");
var folderResponse = await _client.SearchFoldersAsync(_authenticationToken, folderName);
if (!folderResponse.Errors.Any() && folderResponse.Folders.Count() == 1)
{
return new FolderSearchResult
{
Status = ResultStatus.Success,
Folders = folderResponse.Folders
};
}
if (folderResponse.Errors.Any())
{
Console.WriteLine($"Attempt to find folder with name {folderName} resulted in errors: {string.Join(",", folderResponse.Errors)}");
}
else if (folderResponse.Folders.Count() > 1)
{
Console.WriteLine("More than one folder was returned -- narrow your search and/or check the secret folder configuration.");
}
return new FolderSearchResult
{
Errors = folderResponse.Errors.Select(e => new ResultError(null, e)).ToList(),
Status = ResultStatus.Failure,
Message = $"Folder Search for {folderName} Failed"
};
}
catch (Exception e)
{
Console.WriteLine($"An error occurred while attempting to find folder with name: {folderName}. Exception: {e}");
throw;
}
}
/// <summary>
/// Instantiates a new instance of the Client class
/// </summary>
public Client()
{
const string endpointAddress = "https://secret.corp.alkamitech.com/webservices/SSWebService.asmx";
var binding = new BasicHttpBinding
{
Name = "SSWebServiceSoap",
Security =
{
Mode = BasicHttpSecurityMode.Transport
}
};
var endpoint = new EndpointAddress(endpointAddress);
_client = new SSWebServiceSoapClient(binding, endpoint);
}
#region Implement IDisposable
/// <summary>
/// Implementation of IDisposable.
/// </summary>
/// <remarks>
/// Calls Dispose on the <see cref="SSWebServiceSoapClient"/>
/// </remarks>
public void Dispose()
{
((IDisposable)_client)?.Dispose();
}
#endregion Implement IDisposable
}
}