public async Task ExecuteTask(Guid taskId, CancellationToken cancellationToken) { // Prepare record _logger.LogInformation("Retrieving task {TaskId}", taskId); var task = await _rekeyingTasks.GetOne(taskId, cancellationToken); task.RekeyingInProgress = true; // Create task to perform regular updates to UI (every 15s) _logger.LogInformation("Starting log update task"); 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 the secret configuration and its resources var secret = await _managedSecrets.GetOne(task.ManagedSecretId, cancellationToken); _logger.LogInformation("Retrieving resources for secret {SecretId}", secret.ObjectId); var resources = await _resources.Get(r => secret.ResourceIds.Contains(r.ObjectId), cancellationToken); ProviderWorkflowActionCollection workflowCollection = null; try { // Create configured providers for each resource _logger.LogInformation("Getting providers for {ResourceCount} resources", resources.Count); var providers = resources.Select(r => _providerManagerService.GetProviderInstance( r.ProviderType, r.ProviderConfiguration)).ToList(); // Generate a workflow collection from the configured providers _logger.LogInformation("Creating workflow collection"); workflowCollection = _providerManagerService.CreateWorkflowCollection( secret.ValidPeriod, providers); // Get the token credential for this task and embed it _logger.LogInformation("Embedding credentials"); workflowCollection.EmbedCredentials(await GetTokenCredentialAsync(taskId, cancellationToken)); // TODO: Per-action credentialing will go here eventually // For now, use the snippet above for all instances //workflowCollection.Actions.ToList().ForEach(a => //{ // a.Instance.Credential = credential; //}); try { // Update the UI with the data from this attempt _logger.LogInformation("Adding workflow collection to task"); task.Attempts.Add(workflowCollection); await _rekeyingTasks.Update(task, cancellationToken); } catch (Exception ex) { _logger.LogError(ex, "Error updating task: {ex}", ex.ToString()); throw ex; } // Execute the workflow collection _logger.LogInformation("Executing {ActionCount} actions", workflowCollection.Actions.Count); try { await workflowCollection.Run(); } catch (Exception ex) { _logger.LogError(ex, "Error executing action(s)"); await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task); } } catch (Exception ex) { _logger.LogError(ex, "Error preparing workflow: {ex}", ex); await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(TaskExecutionMetaService.ExecuteTask), task); } // Update Task record _logger.LogInformation("Completing task record"); task.RekeyingInProgress = false; task.RekeyingCompleted = (workflowCollection?.HasBeenExecuted).GetValueOrDefault(); task.RekeyingCompleted = (workflowCollection?.HasBeenExecutedSuccessfully).GetValueOrDefault(); // End the regular update task and perform one final data update with the results logUpdateCancellationTokenSource.Cancel(); await _rekeyingTasks.Update(task, cancellationToken); // Run cleanup if Task is complete if (!task.RekeyingFailed) { _logger.LogInformation("Performing cleanup"); try { secret.LastChanged = DateTimeOffset.UtcNow; if (task.PersistedCredentialId != default && task.PersistedCredentialId != Guid.Empty) { await _secureStorageProvider.Destroy(task.PersistedCredentialId); task.PersistedCredentialId = default; task.PersistedCredentialUser = default; } await _rekeyingTasks.Update(task, cancellationToken); } catch (Exception ex) { _logger.LogError(ex, "Error deleting persisted credentials"); await _eventDispatcherMetaService.DispatchEvent(AuthJanitorSystemEvents.AnomalousEventOccurred, nameof(TaskExecutionMetaService.ExecuteTask), "Failure to clean up persisted credentials"); } 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); } }
/// <summary> /// Process a given set of providerConfigurations and execute the /// workflow locally. If the Instance ID doesn't match the expected /// Agent ID, this will return immediately. /// /// The periodicUpdateFunction will be executed regularly throughout /// the execution of the workflow. /// </summary> /// <param name="secretValidPeriod">Secret's period of validity</param> /// <param name="periodicUpdateFunction">Function which is invoked periodically to communicate runtime status</param> /// <param name="providerConfigurations">Provider configurations</param> /// <returns></returns> public async Task <ProviderWorkflowActionCollection> ExecuteAsync( TimeSpan secretValidPeriod, Func <ProviderWorkflowActionCollection, Task> periodicUpdateFunction, params ProviderExecutionParameters[] providerConfigurations) { ProviderWorkflowActionCollection workflowCollection = new ProviderWorkflowActionCollection(); try { var persisted = new Dictionary <Guid, AccessTokenCredential>(); if (providerConfigurations.Any(p => p.TokenSource == TokenSources.Persisted)) { _logger.LogInformation("Downloading persisted tokens"); foreach (var item in providerConfigurations.Where(p => p.TokenSource == TokenSources.Persisted)) { var guid = Guid.Parse(item.TokenParameter); persisted[guid] = await _secureStorage.Retrieve <AccessTokenCredential>(guid); } } AccessTokenCredential obo = null, msi = null; if (providerConfigurations.Any(p => p.TokenSource == TokenSources.OBO)) { _logger.LogInformation("Acquiring OBO token"); obo = await _identityService.GetAccessTokenOnBehalfOfCurrentUserAsync(); } if (providerConfigurations.Any(p => p.TokenSource == TokenSources.ServicePrincipal)) { _logger.LogInformation("Acquiring application token"); msi = await _identityService.GetAccessTokenForApplicationAsync(); } _logger.LogInformation("Getting providers for {ResourceCount} resources", providerConfigurations.Count()); await Task.WhenAll(providerConfigurations.Select(async r => { switch (r.TokenSource) { case TokenSources.Explicit: r.AccessToken = JsonConvert.DeserializeObject <AccessTokenCredential>(r.TokenParameter); break; case TokenSources.OBO: r.AccessToken = obo; r.AccessToken.DisplayEmail = _identityService.UserEmail; r.AccessToken.DisplayUserName = _identityService.UserName; break; case TokenSources.Persisted: r.AccessToken = persisted[Guid.Parse(r.TokenParameter)]; r.AccessToken.DisplayEmail = r.AccessToken.Username; r.AccessToken.DisplayUserName = r.AccessToken.Username; break; case TokenSources.ServicePrincipal: r.AccessToken = msi; break; case TokenSources.Unknown: default: await _eventDispatcher.DispatchEvent(AuthJanitorSystemEvents.AnomalousEventOccurred, nameof(AuthJanitorService.ExecuteAsync), $"TokenSource was unknown for a provider! ({r.ProviderType})"); break; } return(r); })); // --- end access token acquisition/embed --- var providers = providerConfigurations.Select(r => { var p = _providerManagerService.GetProviderInstance( r.ProviderType, r.ProviderConfiguration); if (r.AccessToken != null) { p.Credential = r.AccessToken; } return(p); }).ToList(); workflowCollection = _providerManagerService.CreateWorkflowCollection( secretValidPeriod, providers); _logger.LogInformation("Creating workflow execution task"); Task workflowCollectionRunTask = new Task(async() => { try { await workflowCollection.Run(); } catch (Exception ex) { _logger.LogError(ex, "Error executing action(s)"); throw ex; } }); _logger.LogInformation("Creating tracker task for workflow collection"); var logUpdateCancellationTokenSource = new CancellationTokenSource(); var periodicUpdateTask = Task.Run(async() => { while (!workflowCollectionRunTask.IsCompleted && !workflowCollectionRunTask.IsCanceled) { await Task.Delay(5 * 1000); await periodicUpdateFunction(workflowCollection); } }, logUpdateCancellationTokenSource.Token); _logger.LogInformation("Executing {ActionCount} actions", workflowCollection.Actions.Count); await workflowCollectionRunTask; _logger.LogInformation("Execution complete", workflowCollection.Actions.Count); logUpdateCancellationTokenSource.Cancel(); await periodicUpdateFunction(workflowCollection); if (workflowCollection.HasBeenExecutedSuccessfully) { if (providerConfigurations.Any(p => p.TokenSource == TokenSources.Persisted)) { _logger.LogInformation("Cleaning up persisted tokens"); foreach (var item in providerConfigurations.Where(p => p.TokenSource == TokenSources.Persisted)) { await _secureStorage.Destroy(Guid.Parse(item.TokenParameter)); } } } return(workflowCollection); } catch (Exception ex) { _logger.LogError(ex, "Error preparing workflow: {ex}", ex); await _eventDispatcher.DispatchEvent( AuthJanitorSystemEvents.RotationTaskAttemptFailed, nameof(AuthJanitorService.ExecuteAsync), "Error executing provider workflow"); return(workflowCollection); } }
/// <summary> /// Create a Workflow Collection based on actions which need to /// be taken to execute a given set of Providers. This includes /// proper ordering of actions as required. /// </summary> /// <param name="validPeriod">Valid period for secret</param> /// <param name="providers">Providers to generate workflow collection from</param> /// <returns>Workflow collection</returns> public ProviderWorkflowActionCollection CreateWorkflowCollection( TimeSpan validPeriod, IEnumerable <IAuthJanitorProvider> providers) { var workflowCollection = new ProviderWorkflowActionCollection(_serviceProvider); workflowCollection.AddWithOneIncrement(providers .OfType <ICanRunSanityTests>() .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Sanity Test", p, p => p.Test())).ToArray()); // --- workflowCollection.AddWithOneIncrement(providers .OfType <ICanGenerateTemporarySecretValue>() .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Generate Temporary Secrets", p, p => p.GenerateTemporarySecretValue())).ToArray()); workflowCollection.AddWithOneIncrement(providers .OfType <ICanDistributeTemporarySecretValues>() .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Distribute Temporary Secrets", p, p => { var actions = workflowCollection.GetActions <ICanGenerateTemporarySecretValue, RegeneratedSecret>(); if (actions.Any()) { return(p.DistributeTemporarySecretValues(actions.Select(a => a.Result).ToList())); } return(Task.FromResult(false)); })).ToArray()); workflowCollection.AddWithOneIncrement(providers .OfType <ICanPerformUnifiedCommitForTemporarySecretValues>() .GroupBy(p => p.GenerateResourceIdentifierHashCode()) .Select(p => p.First()) .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Perform Unified Commit", p, p => p.UnifiedCommitForTemporarySecretValues())).ToArray()); // --- workflowCollection.AddWithOneIncrement(providers .OfType <ICanRekey>() .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Rekey Object", p, p => p.Rekey(validPeriod))).ToArray()); workflowCollection.AddWithOneIncrement(providers .OfType <ICanDistributeLongTermSecretValues>() .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Distribute Rekeyed Secrets", p, p => { return(p.DistributeLongTermSecretValues( workflowCollection.GetActions <ICanRekey, RegeneratedSecret>() .Select(a => a.Result).ToList())); })).ToArray()); workflowCollection.AddWithOneIncrement(providers .OfType <ICanPerformUnifiedCommit>() .GroupBy(p => p.GenerateResourceIdentifierHashCode()) .Select(p => p.First()) .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Perform Unified Commit on Rekeyed Secrets", p, p => p.UnifiedCommit())).ToArray()); // --- workflowCollection.AddWithOneIncrement(providers .OfType <ICanCleanup>() .Select(DuplicateProvider) .Select(p => ProviderWorkflowAction.Create( "Cleanup", p, p => p.Cleanup())).ToArray()); return(workflowCollection); }