/// <summary> /// Answer all the challenges in the order /// </summary> /// <param name="execute"></param> /// <param name="order"></param> /// <param name="result"></param> /// <param name="runLevel"></param> /// <returns></returns> public async Task AuthorizeOrder(ExecutionContext context, bool orderValid) { if (context.Order.Details == null) { throw new InvalidOperationException(); } // Get validation plugin var options = context.Renewal.ValidationPluginOptions; var validationScope = _scopeBuilder.Validation(context.Scope, options); var validationPlugin = validationScope.Resolve <IValidationPlugin>(); if (validationPlugin == null) { _log.Error("Validation plugin not found or not created"); context.Result.AddErrorMessage("Validation plugin not found or not created", !orderValid); return; } var(disabled, disabledReason) = validationPlugin.Disabled; if (disabled) { _log.Error($"Validation plugin is not available. {disabledReason}"); context.Result.AddErrorMessage("Validation plugin is not available", !orderValid); return; } // Get authorization details var authorizations = context.Order.Details.Payload.Authorizations.ToList(); var contextParamTasks = authorizations.Select(authorizationUri => GetValidationContextParameters(context, authorizationUri, options, orderValid)); var contextParams = (await Task.WhenAll(contextParamTasks)).OfType <ValidationContextParameters>().ToList(); if (!context.Result.Success) { return; } if (_settings.Validation.DisableMultiThreading != false || validationPlugin.Parallelism == ParallelOperations.None) { await SerialValidation(context, contextParams); } else { await ParallelValidation(validationPlugin.Parallelism, validationScope, context, contextParams); } }
/// <summary> /// Answer all the challenges in the order /// </summary> /// <param name="execute"></param> /// <param name="order"></param> /// <param name="result"></param> /// <param name="runLevel"></param> /// <returns></returns> public async Task AuthorizeOrder(ExecutionContext context, RunLevel runLevel) { // Sanity check if (context.Order.Details == null) { context.Result.AddErrorMessage($"Unable to create order"); return; } if (context.Order.Details.Payload.Status == AcmeClient.OrderInvalid) { context.Result.AddErrorMessage($"Created order was invalid"); return; } // Maybe validation is not needed at all var orderValid = false; if (context.Order.Details.Payload.Status == AcmeClient.OrderReady || context.Order.Details.Payload.Status == AcmeClient.OrderValid) { if (!runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache)) { return; } else { orderValid = true; } } // Get validation plugin var options = context.Renewal.ValidationPluginOptions; var validationScope = _scopeBuilder.Validation(context.Scope, options); var validationPlugin = validationScope.Resolve <IValidationPlugin>(); if (validationPlugin == null) { _log.Error("Validation plugin not found or not created"); context.Result.AddErrorMessage("Validation plugin not found or not created", !orderValid); return; } var(disabled, disabledReason) = validationPlugin.Disabled; if (disabled) { _log.Error($"Validation plugin is not available. {disabledReason}"); context.Result.AddErrorMessage("Validation plugin is not available", !orderValid); return; } // Get authorization details var authorizations = context.Order.Details.Payload.Authorizations.ToList(); var contextParamTasks = authorizations.Select(authorizationUri => GetValidationContextParameters(context, authorizationUri, options, orderValid)); var contextParams = (await Task.WhenAll(contextParamTasks)).OfType <ValidationContextParameters>().ToList(); if (!context.Result.Success) { return; } if (_settings.Validation.DisableMultiThreading != false || validationPlugin.Parallelism == ParallelOperations.None) { await SerialValidation(context, contextParams); } else { await ParallelValidation(validationPlugin.Parallelism, validationScope, context, contextParams); } }
/// <summary> /// Make sure we have authorization for every host in target /// </summary> /// <param name="target"></param> /// <returns></returns> private async Task HandleChallenge(ExecutionContext context, TargetPart targetPart, acme.Authorization authorization) { var valid = false; var client = context.Scope.Resolve <AcmeClient>(); var identifier = authorization.Identifier.Value; var options = context.Renewal.ValidationPluginOptions; IValidationPlugin?validationPlugin = null; using var validation = _scopeBuilder.Validation(context.Scope, options, targetPart, identifier); try { if (authorization.Status == AcmeClient.AuthorizationValid) { _log.Information("Cached authorization result for {identifier}: {Status}", identifier, authorization.Status); if (!context.RunLevel.HasFlag(RunLevel.Test) && !context.RunLevel.HasFlag(RunLevel.IgnoreCache)) { return; } // Used to make --force or --test re-validation errors non-fatal _log.Information("Handling challenge anyway because --test and/or --force is active"); valid = true; } _log.Information("Authorize identifier {identifier}", identifier); _log.Verbose("Initial authorization status: {status}", authorization.Status); _log.Verbose("Challenge types available: {challenges}", authorization.Challenges.Select(x => x.Type ?? "[Unknown]")); var challenge = authorization.Challenges.FirstOrDefault(c => string.Equals(c.Type, options.ChallengeType, StringComparison.CurrentCultureIgnoreCase)); if (challenge == null) { if (valid) { var usedType = authorization.Challenges. Where(x => x.Status == AcmeClient.ChallengeValid). FirstOrDefault(); _log.Warning("Expected challenge type {type} not available for {identifier}, already validated using {valided}.", options.ChallengeType, authorization.Identifier.Value, usedType?.Type ?? "[unknown]"); return; } else { _log.Error("Expected challenge type {type} not available for {identifier}.", options.ChallengeType, authorization.Identifier.Value); context.Result.AddErrorMessage("Expected challenge type not available", !valid); return; } } else { _log.Verbose("Initial challenge status: {status}", challenge.Status); if (challenge.Status == AcmeClient.ChallengeValid) { // We actually should not get here because if one of the // challenges is valid, the authorization itself should also // be valid. if (!context.RunLevel.HasFlag(RunLevel.Test) && !context.RunLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Information("Cached challenge result: {Status}", authorization.Status); return; } } } // We actually have to do validation now try { validationPlugin = validation.Resolve <IValidationPlugin>(); } catch (Exception ex) { _log.Error(ex, "Error resolving validation plugin"); } if (validationPlugin == null) { _log.Error("Validation plugin not found or not created"); context.Result.AddErrorMessage("Validation plugin not found or not created", !valid); return; } var(disabled, disabledReason) = validationPlugin.Disabled; if (disabled) { _log.Error($"Validation plugin is not available. {disabledReason}"); context.Result.AddErrorMessage("Validation plugin is not available", !valid); return; } _log.Information("Authorizing {dnsIdentifier} using {challengeType} validation ({name})", identifier, options.ChallengeType, options.Name); try { var details = await client.DecodeChallengeValidation(authorization, challenge); await validationPlugin.PrepareChallenge(details); } catch (Exception ex) { _log.Error(ex, "Error preparing for challenge answer"); context.Result.AddErrorMessage("Error preparing for challenge answer", !valid); return; } _log.Debug("Submitting challenge answer"); challenge = await client.AnswerChallenge(challenge); if (challenge.Status != AcmeClient.ChallengeValid) { if (challenge.Error != null) { _log.Error(challenge.Error.ToString()); } _log.Error("Authorization result: {Status}", challenge.Status); context.Result.AddErrorMessage(challenge.Error?.ToString() ?? "Unspecified error", !valid); return; } else { _log.Information("Authorization result: {Status}", challenge.Status); return; } } catch (Exception ex) { _log.Error("Error authorizing {renewal}", targetPart); var message = _exceptionHandler.HandleException(ex); context.Result.AddErrorMessage(message, !valid); } finally { if (validationPlugin != null) { try { _log.Verbose("Starting post-validation cleanup"); await validationPlugin.CleanUp(); _log.Verbose("Post-validation cleanup was succesful"); } catch (Exception ex) { _log.Warning("An error occured during post-validation cleanup: {ex}", ex.Message); } } } }
/// <summary> /// Answer all the challenges in the order /// </summary> /// <param name="execute"></param> /// <param name="order"></param> /// <param name="result"></param> /// <param name="runLevel"></param> /// <returns></returns> internal async Task RunAuthorizations(IEnumerable <AuthorizationContext> authorisationContexts, RunLevel runLevel) { // Map authorisations to plugins that are going to execute them var mapping = new Dictionary <ValidationPluginOptions, List <AuthorizationContext> >(); var add = (ValidationPluginOptions o, AuthorizationContext a) => { if (mapping.ContainsKey(o)) { mapping[o].Add(a); } else { mapping.Add(o, new List <AuthorizationContext>() { a }); } }; foreach (var authorisationContext in authorisationContexts) { if (authorisationContext.Authorization == null) { throw new InvalidOperationException(); } var nativeOptions = authorisationContext.Order.Renewal.ValidationPluginOptions; var globalOptions = _validationOptions.GetValidationOptions(Identifier.Parse(authorisationContext.Authorization)); if (globalOptions != null && IsValid(authorisationContext, globalOptions)) { add(globalOptions, authorisationContext); } else if ((globalOptions == null || nativeOptions.Plugin != globalOptions.Plugin) && IsValid(authorisationContext, nativeOptions)) { add(nativeOptions, authorisationContext); } else { _log.Error("No plugin found that can challenge for {authorisation}", authorisationContext.Authorization.Identifier.Value); authorisationContext.Order.Result.AddErrorMessage($"No plugin found that can challenge for {authorisationContext.Authorization.Identifier.Value}", authorisationContext.Order.Order.Valid != true); return; } } // Execute them per group, where one group means one validation plugin foreach (var group in mapping) { var multipleOrders = group.Value.Any(x => x.Order != group.Value.First().Order); var validationScope = _scopeBuilder.Validation(group.Value.First().Order.ExecutionScope, group.Key); var plugin = validationScope.Resolve <IValidationPlugin>(); var contexts = group.Value.Select(context => { var targetPart = context.Order.Target.Parts.FirstOrDefault(p => p.Identifiers.Any(i => i == Identifier.Parse(context.Authorization))); if (targetPart == null) { throw new InvalidOperationException("Authorisation found that doesn't match target"); } return(new ValidationContextParameters(context, targetPart, group.Key)); }).ToList(); if (_settings.Validation.DisableMultiThreading != false || plugin.Parallelism == ParallelOperations.None) { await SerialValidation(contexts, breakOnError : !multipleOrders); } else { await ParallelValidation(plugin.Parallelism, validationScope, contexts, runLevel); } } }
/// <summary> /// Make sure we have authorization for every host in target /// </summary> /// <param name="target"></param> /// <returns></returns> private async Task <Challenge> Authorize( ILifetimeScope execute, RunLevel runLevel, ValidationPluginOptions options, TargetPart targetPart, Authorization authorization) { var invalid = new Challenge { Status = AcmeClient.AuthorizationInvalid }; var valid = new Challenge { Status = AcmeClient.AuthorizationValid }; var client = execute.Resolve <AcmeClient>(); var identifier = authorization.Identifier.Value; try { _log.Information("Authorize identifier: {identifier}", identifier); if (authorization.Status == AcmeClient.AuthorizationValid && !runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Information("Cached authorization result: {Status}", authorization.Status); return(valid); } else { using var validation = _scopeBuilder.Validation(execute, options, targetPart, identifier); IValidationPlugin validationPlugin = null; try { validationPlugin = validation.Resolve <IValidationPlugin>(); } catch (Exception ex) { _log.Error(ex, "Error resolving validation plugin"); } if (validationPlugin == null) { _log.Error("Validation plugin not found or not created."); return(invalid); } if (validationPlugin.Disabled) { _log.Error("Validation plugin is not available to the current user, try running as administrator."); return(invalid); } var challenge = authorization.Challenges.FirstOrDefault(c => c.Type == options.ChallengeType); if (challenge == null) { _log.Error("Expected challenge type {type} not available for {identifier}.", options.ChallengeType, authorization.Identifier.Value); return(invalid); } if (challenge.Status == AcmeClient.AuthorizationValid && !runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Information("{dnsIdentifier} already validated by {challengeType} validation ({name})", authorization.Identifier.Value, options.ChallengeType, options.Name); return(valid); } _log.Information("Authorizing {dnsIdentifier} using {challengeType} validation ({name})", identifier, options.ChallengeType, options.Name); try { var details = client.DecodeChallengeValidation(authorization, challenge); await validationPlugin.PrepareChallenge(details); } catch (Exception ex) { _log.Error(ex, "Error preparing for challenge answer"); return(invalid); } _log.Debug("Submitting challenge answer"); challenge = await client.AnswerChallenge(challenge); if (challenge.Status != AcmeClient.AuthorizationValid) { if (challenge.Error != null) { _log.Error(challenge.Error.ToString()); } _log.Error("Authorization result: {Status}", challenge.Status); return(invalid); } else { _log.Information("Authorization result: {Status}", challenge.Status); return(valid); } } } catch (Exception ex) { _log.Error("Error authorizing {renewal}", targetPart); _exceptionHandler.HandleException(ex); return(invalid); } }
/// <summary> /// Make sure we have authorization for every host in target /// </summary> /// <param name="target"></param> /// <returns></returns> private async Task <Challenge> Authorize( ILifetimeScope execute, RunLevel runLevel, ValidationPluginOptions options, TargetPart targetPart, Authorization authorization) { var invalid = new Challenge { Status = AcmeClient.AuthorizationInvalid }; var valid = new Challenge { Status = AcmeClient.AuthorizationValid }; var client = execute.Resolve <AcmeClient>(); var identifier = authorization.Identifier.Value; IValidationPlugin?validationPlugin = null; try { if (authorization.Status == AcmeClient.AuthorizationValid) { if (!runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Information("Cached authorization result: {Status}", authorization.Status); return(valid); } if (runLevel.HasFlag(RunLevel.IgnoreCache)) { // Due to the IgnoreCache flag (--force switch) // we are going to attempt to re-authorize the // domain even though its already autorized. // On failure, we can still use the cached result. // This helps for migration scenarios. invalid = valid; } } _log.Information("Authorize identifier: {identifier}", identifier); _log.Verbose("Challenge types available: {challenges}", authorization.Challenges.Select(x => x.Type ?? "[Unknown]")); var challenge = authorization.Challenges.FirstOrDefault(c => string.Equals(c.Type, options.ChallengeType, StringComparison.CurrentCultureIgnoreCase)); if (challenge == null) { if (authorization.Status == AcmeClient.AuthorizationValid) { var usedType = authorization.Challenges. Where(x => x.Status == AcmeClient.AuthorizationValid). FirstOrDefault(); _log.Warning("Expected challenge type {type} not available for {identifier}, already validated using {valided}.", options.ChallengeType, authorization.Identifier.Value, usedType?.Type ?? "[unknown]"); return(valid); } else { _log.Error("Expected challenge type {type} not available for {identifier}.", options.ChallengeType, authorization.Identifier.Value); invalid.Error = "Expected challenge type not available"; return(invalid); } } // We actually have to do validation now using var validation = _scopeBuilder.Validation(execute, options, targetPart, identifier); try { validationPlugin = validation.Resolve <IValidationPlugin>(); } catch (Exception ex) { _log.Error(ex, "Error resolving validation plugin"); } if (validationPlugin == null) { _log.Error("Validation plugin not found or not created."); invalid.Error = "Validation plugin not found or not created."; return(invalid); } var(disabled, disabledReason) = validationPlugin.Disabled; if (disabled) { _log.Error($"Validation plugin is not available. {disabledReason}"); invalid.Error = "Validation plugin is not available."; return(invalid); } _log.Information("Authorizing {dnsIdentifier} using {challengeType} validation ({name})", identifier, options.ChallengeType, options.Name); try { var details = await client.DecodeChallengeValidation(authorization, challenge); await validationPlugin.PrepareChallenge(details); } catch (Exception ex) { _log.Error(ex, "Error preparing for challenge answer"); invalid.Error = "Error preparing for challenge answer"; return(invalid); } _log.Debug("Submitting challenge answer"); challenge = await client.AnswerChallenge(challenge); if (challenge.Status != AcmeClient.AuthorizationValid) { if (challenge.Error != null) { _log.Error(challenge.Error.ToString()); } _log.Error("Authorization result: {Status}", challenge.Status); invalid.Error = challenge.Error; return(invalid); } else { _log.Information("Authorization result: {Status}", challenge.Status); return(valid); } } catch (Exception ex) { _log.Error("Error authorizing {renewal}", targetPart); _exceptionHandler.HandleException(ex); invalid.Error = ex.Message; return(invalid); } finally { if (validationPlugin != null) { try { _log.Verbose("Starting post-validation cleanup"); await validationPlugin.CleanUp(); _log.Verbose("Post-validation cleanup was succesful"); } catch (Exception ex) { _log.Warning("An error occured during post-validation cleanup: {ex}", ex.Message); } } } }