Ejemplo n.º 1
0
        protected IAuthJanitorProvider GetProvider(RekeyingAttemptLogger logger, string name, string configuration, MultiCredentialProvider.CredentialType credentialType)
        {
            var provider = GetProvider(logger, name, configuration);

            provider.CredentialType = credentialType;
            return(provider);
        }
Ejemplo n.º 2
0
        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);
            }
        }
Ejemplo n.º 3
0
        protected IAuthJanitorProvider GetProvider(RekeyingAttemptLogger logger, string name, string configuration)
        {
            var provider = GetProvider(logger, name);

            if (provider == null)
            {
                return(null);
            }
            provider.SerializedConfiguration = configuration;
            return(provider);
        }
Ejemplo n.º 4
0
        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));
        }
Ejemplo n.º 5
0
        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);
            }
        }
Ejemplo n.º 6
0
        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!"));
            }
        }
Ejemplo n.º 7
0
        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);
        }
Ejemplo n.º 8
0
 protected IAuthJanitorProvider GetProvider(RekeyingAttemptLogger logger, string name) => _providerFactory(name, logger);