Example #1
0
        /// <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);
            }
        }
Example #2
0
        /// <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);
            }
        }
Example #3
0
        /// <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);
                    }
                }
            }
        }
Example #4
0
        /// <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);
                }
            }
        }
Example #5
0
        /// <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);
            }
        }
Example #6
0
        /// <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);
                    }
                }
            }
        }