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 { /// /// A simple client for secret server /// public sealed class Client : IDisposable { private string _authenticationToken; private readonly SSWebServiceSoapClient _client; /// /// Gets all secrets from a folder based on folder name /// /// /// public async Task 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; } } /// /// Authenticates using the supplied username, password, and domain /// /// /// /// /// public async Task 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; } } /// /// Returns a collection of secrets from a folder as a /// /// /// private async Task 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; } } /// /// Downloads a file based on the parent Secret and File ItemId /// /// /// /// private async Task 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; } } /// /// Finds a Secret Folder based on FolderName /// /// /// private async Task 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; } } /// /// Instantiates a new instance of the Client class /// 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 /// /// Implementation of IDisposable. /// /// /// Calls Dispose on the /// public void Dispose() { ((IDisposable)_client)?.Dispose(); } #endregion Implement IDisposable } }