protected IAuthJanitorProvider GetProvider(RekeyingAttemptLogger logger, string name, string configuration, MultiCredentialProvider.CredentialType credentialType) { var provider = GetProvider(logger, name, configuration); provider.CredentialType = credentialType; return(provider); }
public async Task Run([TimerTrigger("0 */2 * * * *")] TimerInfo myTimer, ILogger log) { _ = myTimer; // unused but required for attribute log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); var toRekey = await RekeyingTasks.GetAsync(t => (t.ConfirmationType == TaskConfirmationStrategies.AdminCachesSignOff || t.ConfirmationType == TaskConfirmationStrategies.AutomaticRekeyingAsNeeded || t.ConfirmationType == TaskConfirmationStrategies.AutomaticRekeyingScheduled) && DateTimeOffset.UtcNow + TimeSpan.FromHours(ServiceConfiguration.AutomaticRekeyableJustInTimeLeadTimeHours) > t.Expiry); foreach (var task in toRekey) { task.RekeyingInProgress = true; await RekeyingTasks.UpdateAsync(task); var aggregatedStringLogger = new RekeyingAttemptLogger(log) { UserDisplayName = "Agent Identity", UserEmail = string.Empty }; await ExecuteRekeyingWorkflow(task, aggregatedStringLogger); task.RekeyingInProgress = false; task.RekeyingCompleted = aggregatedStringLogger.IsSuccessfulAttempt; task.RekeyingFailed = !aggregatedStringLogger.IsSuccessfulAttempt; task.Attempts.Add(aggregatedStringLogger); await RekeyingTasks.UpdateAsync(task); } }
protected IAuthJanitorProvider GetProvider(RekeyingAttemptLogger logger, string name, string configuration) { var provider = GetProvider(logger, name); if (provider == null) { return(null); } provider.SerializedConfiguration = configuration; return(provider); }
public async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "secrets/{managedSecretId:guid}/{nonce}")] HttpRequest req, Guid managedSecretId, string nonce, ILogger log) { _ = req; // unused but required for attribute log.LogInformation("External signal called to check ManagedSecret ID {0} against nonce {1}", managedSecretId, nonce); var secret = await ManagedSecrets.GetAsync(managedSecretId); if (secret == null) { return(new BadRequestErrorMessageResult("Invalid ManagedSecret ID")); } if (!secret.TaskConfirmationStrategies.HasFlag(TaskConfirmationStrategies.ExternalSignal)) { return(new BadRequestErrorMessageResult("This ManagedSecret cannot be used with External Signals")); } if ((await RekeyingTasks.GetAsync(t => t.ManagedSecretId == secret.ObjectId)) .Any(t => t.RekeyingInProgress)) { return(new OkObjectResult(RETURN_RETRY_SHORTLY)); } if ((secret.IsValid && secret.TimeRemaining <= TimeSpan.FromHours(ServiceConfiguration.ExternalSignalRekeyableLeadTimeHours)) || !secret.IsValid) { var rekeyingTask = new Task(async() => { var task = new RekeyingTask() { ManagedSecretId = secret.ObjectId, Expiry = secret.Expiry, Queued = DateTimeOffset.UtcNow, RekeyingInProgress = true }; await RekeyingTasks.CreateAsync(task); var rekeyingAttemptLogger = new RekeyingAttemptLogger(log) { UserDisplayName = "Agent Identity", UserEmail = string.Empty }; try { await ExecuteRekeyingWorkflow(task, rekeyingAttemptLogger); } catch (Exception ex) { rekeyingAttemptLogger.OuterException = JsonConvert.SerializeObject(ex, Formatting.Indented); } task.RekeyingInProgress = false; task.RekeyingCompleted = rekeyingAttemptLogger.IsSuccessfulAttempt; task.RekeyingFailed = !rekeyingAttemptLogger.IsSuccessfulAttempt; task.Attempts.Add(rekeyingAttemptLogger); await RekeyingTasks.UpdateAsync(task); }, TaskCreationOptions.LongRunning); rekeyingTask.Start(); if (!rekeyingTask.Wait(TimeSpan.FromSeconds(MAX_EXECUTION_SECONDS_BEFORE_RETRY))) { log.LogInformation("Rekeying workflow was started but exceeded the maximum request time! ({0})", TimeSpan.FromSeconds(MAX_EXECUTION_SECONDS_BEFORE_RETRY)); return(new OkObjectResult(RETURN_RETRY_SHORTLY)); } else { log.LogInformation("Completed rekeying workflow within maximum time! ({0})", TimeSpan.FromSeconds(MAX_EXECUTION_SECONDS_BEFORE_RETRY)); return(new OkObjectResult(RETURN_CHANGE_OCCURRED)); } } return(new OkObjectResult(RETURN_NO_CHANGE)); }
public async Task ExecuteTask(Guid taskId, CancellationToken cancellationToken) { // Prepare record var task = await _rekeyingTasks.GetOne(taskId, cancellationToken); task.RekeyingInProgress = true; var rekeyingAttemptLog = new RekeyingAttemptLogger(); task.Attempts.Add(rekeyingAttemptLog); await _rekeyingTasks.Update(task, cancellationToken); var logUpdateCancellationTokenSource = new CancellationTokenSource(); var logUpdateTask = Task.Run(async() => { while (task.RekeyingInProgress) { await Task.Delay(15 * 1000); await _rekeyingTasks.Update(task, cancellationToken); } }, logUpdateCancellationTokenSource.Token); // Retrieve credentials for Task AccessTokenCredential credential = null; try { if (task.ConfirmationType == TaskConfirmationStrategies.AdminCachesSignOff) { if (task.PersistedCredentialId == default) { throw new KeyNotFoundException("Cached sign-off is preferred but no credentials were persisted!"); } if (_secureStorageProvider == null) { throw new NotSupportedException("Must register an ISecureStorageProvider"); } credential = await _secureStorageProvider.Retrieve <AccessTokenCredential>(task.PersistedCredentialId); } else if (task.ConfirmationType == TaskConfirmationStrategies.AdminSignsOffJustInTime) { credential = await _identityService.GetAccessTokenOnBehalfOfCurrentUserAsync(); } else if (task.ConfirmationType.UsesServicePrincipal()) { credential = await _identityService.GetAccessTokenForApplicationAsync(); } else { throw new NotSupportedException("No Access Tokens could be generated for this Task!"); } if (credential == null || string.IsNullOrEmpty(credential.AccessToken)) { throw new InvalidOperationException("Access Token was found, but was blank or invalid"); } } catch (Exception ex) { await EmbedException(task, ex, cancellationToken, "Exception retrieving Access Token"); await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task); return; } // Embed credential context in attempt log rekeyingAttemptLog.UserDisplayName = credential.Username; rekeyingAttemptLog.UserEmail = credential.Username; if (task.ConfirmationType.UsesOBOTokens()) { if (!string.IsNullOrEmpty(task.PersistedCredentialUser)) { rekeyingAttemptLog.UserDisplayName = task.PersistedCredentialUser; } else { rekeyingAttemptLog.UserDisplayName = _identityService.UserName; rekeyingAttemptLog.UserEmail = _identityService.UserEmail; } } // Retrieve targets var secret = await _managedSecrets.GetOne(task.ManagedSecretId, cancellationToken); rekeyingAttemptLog.LogInformation("Beginning rekeying of secret ID {SecretId}", task.ManagedSecretId); var resources = await _resources.Get(r => secret.ResourceIds.Contains(r.ObjectId), cancellationToken); await _rekeyingTasks.Update(task, cancellationToken); // Execute rekeying workflow try { var providers = resources.Select(r => _providerManagerService.GetProviderInstance( r.ProviderType, r.ProviderConfiguration)).ToList(); // Link in automation bindings from the outer flow providers.ForEach(p => p.Credential = credential); await _providerManagerService.ExecuteRekeyingWorkflow(rekeyingAttemptLog, secret.ValidPeriod, providers); } catch (Exception ex) { rekeyingAttemptLog.IsComplete = true; await EmbedException(task, ex, cancellationToken, "Error executing rekeying workflow!"); await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task); } // Update Task record task.RekeyingInProgress = false; task.RekeyingCompleted = rekeyingAttemptLog.IsSuccessfulAttempt; task.RekeyingFailed = !rekeyingAttemptLog.IsSuccessfulAttempt; logUpdateCancellationTokenSource.Cancel(); await _rekeyingTasks.Update(task, cancellationToken); // Run cleanup if Task is complete if (task.RekeyingCompleted) { try { secret.LastChanged = DateTimeOffset.UtcNow; await _managedSecrets.Update(secret, cancellationToken); if (task.PersistedCredentialId != default && task.PersistedCredentialId != Guid.Empty) { rekeyingAttemptLog.LogInformation("Destroying persisted credential"); await _secureStorageProvider.Destroy(task.PersistedCredentialId); task.PersistedCredentialId = default; task.PersistedCredentialUser = default; } rekeyingAttemptLog.LogInformation("Completed rekeying workflow for ManagedSecret '{ManagedSecretName}' (ID {ManagedSecretId})", secret.Name, secret.ObjectId); rekeyingAttemptLog.LogInformation("Rekeying task completed"); await _rekeyingTasks.Update(task, cancellationToken); } catch (Exception ex) { await EmbedException(task, ex, cancellationToken, "Error cleaning up after rekeying!"); } if (task.ConfirmationType.UsesOBOTokens()) { await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskCompletedManually, nameof(TaskExecutionMetaService.ExecuteTask), task); } else { await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskCompletedAutomatically, nameof(TaskExecutionMetaService.ExecuteTask), task); } } else { await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task); } }
public async Task <IActionResult> Approve( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "tasks/{taskId:guid}/approve")] HttpRequest req, Guid taskId, ClaimsPrincipal claimsPrincipal, ILogger log) { if (!req.IsValidUser(AuthJanitorRoles.ServiceOperator, AuthJanitorRoles.GlobalAdmin)) { return(new UnauthorizedResult()); } log.LogInformation("Administrator approved Task ID {0}", taskId); var task = await RekeyingTasks.GetAsync(taskId); log.LogInformation("Registering incoming user credential..."); try { await RegisterUserCredential(req); } catch (Exception ex) { var logger = new RekeyingAttemptLogger(); logger.LogCritical(ex, "Error registering user credential!"); task.Attempts.Add(logger); await RekeyingTasks.UpdateAsync(task); return(new BadRequestErrorMessageResult(ex.Message)); } if (task.ConfirmationType == TaskConfirmationStrategies.AdminCachesSignOff) { var credential = CredentialProvider.Get(MultiCredentialProvider.CredentialType.UserCredential); var persisted = await SecureStorageProvider.Persist <Azure.Core.AccessToken>( credential.Expiry, new Azure.Core.AccessToken(credential.AccessToken, credential.Expiry)); task.PersistedCredentialId = persisted; task.PersistedCredentialUser = credential.Username; await RekeyingTasks.UpdateAsync(task); return(new OkResult()); } else if (task.ConfirmationType == TaskConfirmationStrategies.AdminSignsOffJustInTime) { task.RekeyingInProgress = true; await RekeyingTasks.UpdateAsync(task); var aggregatedStringLogger = new RekeyingAttemptLogger(log) { UserDisplayName = claimsPrincipal.FindFirst(ClaimTypes.GivenName)?.Value + " " + claimsPrincipal.FindFirst(ClaimTypes.Surname)?.Value, UserEmail = claimsPrincipal.FindFirst(ClaimTypes.Email)?.Value }; try { await ExecuteRekeyingWorkflow(task, aggregatedStringLogger); task.RekeyingInProgress = false; task.RekeyingCompleted = aggregatedStringLogger.IsSuccessfulAttempt; task.RekeyingFailed = !aggregatedStringLogger.IsSuccessfulAttempt; } catch (Exception ex) { task.RekeyingInProgress = false; task.RekeyingCompleted = false; task.RekeyingFailed = true; if (aggregatedStringLogger.OuterException == null) { aggregatedStringLogger.OuterException = JsonConvert.SerializeObject(ex, Formatting.Indented); } } task.Attempts.Add(aggregatedStringLogger); await RekeyingTasks.UpdateAsync(task); if (task.RekeyingFailed) { return(new BadRequestErrorMessageResult(aggregatedStringLogger.OuterException)); } else { return(new OkResult()); } } else { log.LogError("Task does not support an Administrator's approval!"); return(new BadRequestErrorMessageResult("Task does not support an Administrator's approval!")); } }
protected async Task <RekeyingAttemptLogger> ExecuteRekeyingWorkflow(RekeyingTask task, RekeyingAttemptLogger log = null) { if (log == null) { log = new RekeyingAttemptLogger(); } MultiCredentialProvider.CredentialType credentialType; if (task.ConfirmationType == TaskConfirmationStrategies.AdminCachesSignOff) { if (task.PersistedCredentialId == default) { log.LogError("Cached sign-off is preferred but no credentials were persisted!"); throw new Exception("Cached sign-off is preferred but no credentials were persisted!"); } var token = await SecureStorageProvider.Retrieve <Azure.Core.AccessToken>(task.PersistedCredentialId); CredentialProvider.Register( MultiCredentialProvider.CredentialType.CachedCredential, token.Token, token.ExpiresOn); credentialType = MultiCredentialProvider.CredentialType.CachedCredential; } else if (task.ConfirmationType == TaskConfirmationStrategies.AdminSignsOffJustInTime) { credentialType = MultiCredentialProvider.CredentialType.UserCredential; } else { credentialType = MultiCredentialProvider.CredentialType.AgentServicePrincipal; } log.LogInformation("Using credential type {0} to access resources", credentialType); var secret = await ManagedSecrets.GetAsync(task.ManagedSecretId); log.LogInformation("Beginning rekeying for ManagedSecret '{0}' (ID {1})", secret.Name, secret.ObjectId); var resources = await Resources.GetAsync(r => secret.ResourceIds.Contains(r.ObjectId)); var workflow = new ProviderActionWorkflow(log, resources.Select(r => GetProvider(log, r.ProviderType, r.ProviderConfiguration, credentialType))); try { await workflow.InvokeAsync(secret.ValidPeriod); secret.LastChanged = DateTimeOffset.UtcNow; await ManagedSecrets.UpdateAsync(secret); log.LogInformation("Completed rekeying workflow for ManagedSecret '{0}' (ID {1})", secret.Name, secret.ObjectId); if (credentialType == MultiCredentialProvider.CredentialType.CachedCredential) { log.LogInformation("Destroying persisted credential"); await SecureStorageProvider.Destroy(task.PersistedCredentialId); } log.LogInformation("Rekeying task completed"); } catch (Exception ex) { log.LogCritical(ex, "Error running rekeying task!"); log.LogCritical(ex.Message); log.LogCritical(ex.StackTrace); log.OuterException = JsonConvert.SerializeObject(ex, Formatting.Indented); } return(log); }
protected IAuthJanitorProvider GetProvider(RekeyingAttemptLogger logger, string name) => _providerFactory(name, logger);