private void GetApiKeysFromA2ARegistrations() { // optionally you can have Safeguard look up all A2A registrations for a given certificate user thumbprint // currently this requires auditor permission, but we will enhance A2A to include read ability without it try { var a2AJson = _connection.InvokeMethod(Service.Core, Method.Get, "A2ARegistrations", parameters: new Dictionary <string, string> { { "filter", $"CertificateUserThumbprint ieq '{_safeguardClientCertificateThumbprint}'" } }); var a2AArray = JArray.Parse(a2AJson); foreach (dynamic a2A in a2AArray) { var credsJson = _connection.InvokeMethod(Service.Core, Method.Get, $"A2ARegistrations/{a2A.Id}/RetrievableAccounts"); var credsArray = JArray.Parse(credsJson); foreach (dynamic cred in credsArray) { _monitoredPasswords.Add(new MonitoredPassword { ApiKey = ExtensionMethods.ToSecureString(cred.ApiKey.ToString()), AssetName = cred.SystemName, AccountName = cred.AccountName }); } } } catch (Exception ex) { throw new Exception("Unable to get API keys using certificate user, did you grant auditor permissions?", ex); } }
private IEnumerable <AccountMapping> GetAccountMappings(Configuration configuration) { ISafeguardConnection connection = null; try { connection = Safeguard.Connect(configuration.SppAddress, configuration.CertificateUserThumbPrint, _safeguardApiVersion, _safeguardIgnoreSsl); var rawJson = connection.InvokeMethod(Service.Core, Method.Get, $"A2ARegistrations/{configuration.A2ARegistrationId}/RetrievableAccounts"); var retrievableAccounts = JsonHelper.DeserializeObject <IEnumerable <RetrievableAccount> >(rawJson); var accountMappings = new List <AccountMapping>(); foreach (var account in retrievableAccounts) { accountMappings.Add(new AccountMapping() { AccountName = account.AccountName, ApiKey = account.ApiKey, VaultName = "" }); } return(accountMappings); } finally { connection?.Dispose(); } }
public IEnumerable <RetrievableAccount> GetRetrievableAccounts() { var configuration = _configurationRepository.GetConfiguration(); if (configuration == null) { _logger.Error("No configuration was found. DevOps service must be configured first"); return(null); } ISafeguardConnection connection = null; try { connection = Safeguard.Connect(configuration.SppAddress, configuration.CertificateUserThumbPrint, _safeguardApiVersion, _safeguardIgnoreSsl); var rawJson = connection.InvokeMethod(Service.Core, Method.Get, $"A2ARegistrations/{configuration.A2ARegistrationId}/RetrievableAccounts"); var retrievableAccounts = JsonHelper.DeserializeObject <IEnumerable <RetrievableAccount> >(rawJson); return(retrievableAccounts.ToList()); } catch (Exception ex) { _logger.Error($"Failed to get the retrievable accounts from SPP: {ex.Message}."); } finally { connection?.Dispose(); } return(null); }
private RetrievableAccount GetRetrievableAccount(Configuration configuration, string apiKey) { var apiKeyInfo = _configurationRepository.GetSetting(apiKey); ISafeguardConnection connection = null; try { connection = Safeguard.Connect(configuration.SppAddress, configuration.CertificateUserThumbPrint, _safeguardApiVersion, _safeguardIgnoreSsl); var rawJson = connection.InvokeMethod(Service.Core, Method.Get, $"A2ARegistrations/{configuration.A2ARegistrationId}/RetrievableAccounts/{apiKeyInfo.Value}"); var retrievableAccount = JsonHelper.DeserializeObject <IEnumerable <RetrievableAccount> >(rawJson); return(retrievableAccount?.FirstOrDefault()); } finally { connection?.Dispose(); } }
public Configuration InitialConfiguration(InitialConfiguration initialConfig) { //TODO: Create a new configuration element here //TODO: Check to see if there is already a configuration. If so, throw. //TODO: Get the registration and store the configuration in the database if (initialConfig == null) { throw new Exception("The initial configuration cannot be null."); } if (initialConfig.CertificateUserThumbprint == null) { throw new Exception("The user certificate thumbprint cannot be null."); } if (initialConfig.SppAddress == null) { throw new Exception("The SPP network address cannot be null."); } ISafeguardConnection connection = null; try { connection = Safeguard.Connect(initialConfig.SppAddress, initialConfig.CertificateUserThumbprint, _safeguardApiVersion, _safeguardIgnoreSsl); var rawJson = connection.InvokeMethod(Service.Core, Method.Get, "A2ARegistrations"); var registrations = JsonHelper.DeserializeObject <IEnumerable <SppRegistration> >(rawJson); // TODO: Assume that we only have one registration that belongs to the cert user var registration = registrations?.FirstOrDefault(); if (registration != null) { var configuration = new Configuration { SppAddress = initialConfig.SppAddress, A2ARegistrationId = registration.Id, A2ARegistrationName = registration.AppName, CertificateUser = registration.CertificateUser, CertificateUserThumbPrint = registration.CertificateUserThumbPrint, CreatedByUserId = registration.CreatedByUserId, CreatedByUserDisplayName = registration.CreatedByUserDisplayName, CreatedDate = registration.CreatedDate, AccountMapping = new List <AccountMapping>() }; _configurationRepository.SaveConfiguration(configuration); return(configuration); } else { _logger.Error("No A2A registrations were found for the configured certificate user"); } } catch (Exception ex) { _logger.Error($"Failed to initialize the DevOps Serivce: {ex.Message}"); } finally { connection?.Dispose(); } throw new Exception("Failed to configure devops."); }
private void HandlePendingApprovalNotification(string eventName, string eventBody) { if (eventName != "AccessRequestPendingApproval") { Log.Information("Received {EventName} event, ignoring it", eventName); return; } try { var approvalEvent = JsonConvert.DeserializeObject <AccessRequestApprovalPendingEvent>(eventBody); var accessRequestId = approvalEvent.RequestId; if (string.IsNullOrEmpty(accessRequestId)) { Log.Warning("Unable to parse access requestId for event {EventBody}", eventBody); return; } var accessRequestJson = _connection.InvokeMethod(Service.Core, Method.Get, $"AccessRequests/{accessRequestId}"); var accessRequest = JsonConvert.DeserializeObject <AccessRequest>(accessRequestJson); // Only ServiceNow and Remedy are supported in Safeguard. We will be adding a generic ticket system // that will allow for arbitrary ticket numbers. Until then, you could overload the comment with // the ticket number. TODO: remove this comment when it becomes obselete var ticketNumber = accessRequest.TicketNumber; if (string.IsNullOrEmpty(ticketNumber)) { Log.Information("Ignoring access request {AccessRequestId} without ticket number", accessRequestId); return; } if (_connection.GetAccessTokenLifetimeRemaining() == 0) { _connection.RefreshAccessToken(); } switch (_validator.CheckTicket(ticketNumber, accessRequest)) { case ValidationResult.Approve: Log.Information("Approving access request {AccessRequestId} with ticket number {TicketNumber}", accessRequestId, ticketNumber); _connection.InvokeMethod(Service.Core, Method.Post, $"AccessRequests/{accessRequestId}/Approve"); break; case ValidationResult.Deny: Log.Information("Denying access request {AccessRequestId} with ticket number {TicketNumber}", accessRequestId, ticketNumber); _connection.InvokeMethod(Service.Core, Method.Post, $"AccessRequests/{accessRequestId}/Deny"); break; default: Log.Information("Ignoring access request {AccessRequestId} with ticket number {TicketNumber}", accessRequestId, ticketNumber); break; } } catch (Exception ex) { Log.Error(ex, "Exception occured while handling event {EventName}, data={EventBody}", eventName, eventBody); } }
private static void TestApiExceptions(ISafeguardConnection connection) { Console.WriteLine("Test catching one with no response body"); try { connection.InvokeMethod(Service.Core, Method.Get, "This/Does/nt/Exist"); throw new Exception("Nonexistent URL did not throw an exception"); } catch (SafeguardDotNetException ex) { if (ex.HttpStatusCode != HttpStatusCode.NotFound) { throw; } if (!ex.HasResponse) { throw; } if (ex.ErrorCode != null) { throw; } if (ex.ErrorMessage != null) { throw; } } Console.WriteLine("Test catching one for bad request no filter"); try { connection.InvokeMethod(Service.Core, Method.Get, "Me/RequestableAssets", parameters: new Dictionary <string, string>() { ["filter"] = "This eq 'broken'" }); throw new Exception("Bad filter did not throw an exception"); } catch (SafeguardDotNetException ex) { if (ex.HttpStatusCode != HttpStatusCode.BadRequest) { throw; } if (!ex.HasResponse) { throw; } if (ex.ErrorCode != 70009) { throw; } if (!string.Equals(ex.ErrorMessage, "Invalid filter property - 'This' is not a valid filter property name.")) { throw; } } Console.WriteLine("Test catching one with model state issues"); try { connection.InvokeMethod(Service.Appliance, Method.Put, "NetworkInterfaces/X1", "{\"Name\":\"X1\",\"LinkDuplex\":\"FakeValue\"}"); throw new Exception("Bad model state did not throw an exception"); } catch (SafeguardDotNetException ex) { if (ex.HttpStatusCode != HttpStatusCode.BadRequest) { throw; } if (!ex.HasResponse) { throw; } if (ex.ErrorCode != 70000) { throw; } if (!string.Equals(ex.ErrorMessage, "The request is invalid.")) { throw; } } }