summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWouter Tinus <wouter.tinus@gmail.com>2020-06-14 19:50:41 +0200
committerWouter Tinus <wouter.tinus@gmail.com>2020-06-14 19:50:41 +0200
commite49b52549f6f573d708a882ee70eaebbc0bd9dd5 (patch)
tree042606e067d7bdb37bce7e370d54865ef74d8f17 /src
parent0c1e53dca87941a6ca20e72772ef36c094e3abff (diff)
downloadletsencrypt-win-simple-e49b52549f6f573d708a882ee70eaebbc0bd9dd5.zip
letsencrypt-win-simple-e49b52549f6f573d708a882ee70eaebbc0bd9dd5.tar.gz
letsencrypt-win-simple-e49b52549f6f573d708a882ee70eaebbc0bd9dd5.tar.bz2
enable parallel validation for http-01 selfhosting
Diffstat (limited to 'src')
-rw-r--r--src/main.lib/Clients/Acme/AcmeClient.cs12
-rw-r--r--src/main.lib/Context/ExecutionContext.cs26
-rw-r--r--src/main.lib/Context/ValidationContext.cs71
-rw-r--r--src/main.lib/Plugins/Interfaces/IValidationPlugin.cs61
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Dns/Acme/Acme.cs2
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Dns/DnsValidation.cs3
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Dns/Manual/Manual.cs2
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Dns/Script/Script.cs2
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Http/HttpValidation.cs8
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Http/SelfHosting/SelfHosting.cs77
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Http/WebDav/WebDav.cs2
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Tls/SelfHosting/SelfHosting.cs1
-rw-r--r--src/main.lib/Plugins/ValidationPlugins/Validation.cs6
-rw-r--r--src/main.lib/RenewalExecutor.cs281
-rw-r--r--src/main.lib/RenewalValidator.cs400
-rw-r--r--src/main.lib/Services/SettingsService.cs5
-rw-r--r--src/main/Program.cs1
-rw-r--r--src/main/settings.json1
-rwxr-xr-xsrc/plugin.validation.dns.azure/Azure.cs2
-rw-r--r--src/plugin.validation.dns.cloudflare/Cloudflare.cs2
-rw-r--r--src/plugin.validation.dns.dreamhost/DreamhostDnsValidation.cs2
-rw-r--r--src/plugin.validation.dns.luadns/luadns.cs2
-rw-r--r--src/plugin.validation.dns.route53/Route53.cs6
23 files changed, 614 insertions, 361 deletions
diff --git a/src/main.lib/Clients/Acme/AcmeClient.cs b/src/main.lib/Clients/Acme/AcmeClient.cs
index 2783881..5d8fed9 100644
--- a/src/main.lib/Clients/Acme/AcmeClient.cs
+++ b/src/main.lib/Clients/Acme/AcmeClient.cs
@@ -16,6 +16,7 @@ using System.Net.Http;
using System.Net.Mail;
using System.Security.Authentication;
using System.Security.Cryptography;
+using System.Threading;
using System.Threading.Tasks;
namespace PKISharp.WACS.Clients.Acme
@@ -529,6 +530,7 @@ namespace PKISharp.WACS.Clients.Acme
/// <returns></returns>
private async Task<T> Retry<T>(Func<Task<T>> executor, int attempt = 0)
{
+ await _requestLock.WaitAsync();
try
{
return await executor();
@@ -547,8 +549,18 @@ namespace PKISharp.WACS.Clients.Acme
throw;
}
}
+ finally
+ {
+ _requestLock.Release();
+ }
}
+ /// <summary>
+ /// Prevent sending simulateous requests to the ACME service because it messes
+ /// up the nonce tracking mechanism
+ /// </summary>
+ private readonly SemaphoreSlim _requestLock = new SemaphoreSlim(1, 1);
+
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
diff --git a/src/main.lib/Context/ExecutionContext.cs b/src/main.lib/Context/ExecutionContext.cs
new file mode 100644
index 0000000..23d3373
--- /dev/null
+++ b/src/main.lib/Context/ExecutionContext.cs
@@ -0,0 +1,26 @@
+using Autofac;
+using PKISharp.WACS.DomainObjects;
+
+namespace PKISharp.WACS.Context
+{
+ /// <summary>
+ /// Common objects used throughout the renewal process
+ /// </summary>
+ internal class ExecutionContext
+ {
+ public ILifetimeScope Scope { get; private set; }
+ public Order Order { get; private set; }
+ public RunLevel RunLevel { get; private set; }
+ public RenewResult Result { get; private set; }
+ public Target Target => Order.Target;
+ public Renewal Renewal => Order.Renewal;
+
+ public ExecutionContext(ILifetimeScope scope, Order order, RunLevel runLevel, RenewResult result)
+ {
+ Scope = scope;
+ Order = order;
+ RunLevel = runLevel;
+ Result = result;
+ }
+ }
+}
diff --git a/src/main.lib/Context/ValidationContext.cs b/src/main.lib/Context/ValidationContext.cs
new file mode 100644
index 0000000..c5c9544
--- /dev/null
+++ b/src/main.lib/Context/ValidationContext.cs
@@ -0,0 +1,71 @@
+using ACMESharp.Authorizations;
+using ACMESharp.Protocol.Resources;
+using Autofac;
+using PKISharp.WACS.DomainObjects;
+using PKISharp.WACS.Plugins.Interfaces;
+using System.Collections.Generic;
+
+namespace PKISharp.WACS.Context
+{
+ public class ValidationContextParameters
+ {
+ public ValidationContextParameters(
+ Authorization authorization,
+ TargetPart? targetPart,
+ string challengeType,
+ string pluginName)
+ {
+ TargetPart = targetPart;
+ Authorization = authorization;
+ ChallengeType = challengeType;
+ PluginName = pluginName;
+ }
+
+ public string ChallengeType { get; }
+ public string PluginName { get; }
+ public TargetPart? TargetPart { get; }
+ public Authorization Authorization { get; }
+ }
+
+ public class ValidationContext
+ {
+ public ValidationContext(
+ ILifetimeScope scope,
+ ValidationContextParameters parameters)
+ {
+ Identifier = parameters.Authorization.Identifier.Value;
+ TargetPart = parameters.TargetPart;
+ Authorization = parameters.Authorization;
+ Scope = scope;
+ ChallengeType = parameters.ChallengeType;
+ PluginName = parameters.PluginName;
+ ValidationPlugin = scope.Resolve<IValidationPlugin>();
+ }
+ public ILifetimeScope Scope { get; }
+ public string Identifier { get; }
+ public string ChallengeType { get; }
+ public string PluginName { get; }
+ public TargetPart? TargetPart { get; }
+ public Authorization Authorization { get; }
+ public Challenge? Challenge { get; set; }
+ public IChallengeValidationDetails? ChallengeDetails { get; set; }
+ public IValidationPlugin ValidationPlugin { get; set; }
+ public bool? Success { get; set; }
+ public List<string> ErrorMessages { get; } = new List<string>();
+ public void AddErrorMessage(string? value, bool fatal = true)
+ {
+ if (value != null)
+ {
+ if (!ErrorMessages.Contains(value))
+ {
+ ErrorMessages.Add(value);
+ }
+ }
+ if (fatal)
+ {
+ Success = false;
+ }
+ }
+ }
+
+}
diff --git a/src/main.lib/Plugins/Interfaces/IValidationPlugin.cs b/src/main.lib/Plugins/Interfaces/IValidationPlugin.cs
index 8b6820d..fb3a8e9 100644
--- a/src/main.lib/Plugins/Interfaces/IValidationPlugin.cs
+++ b/src/main.lib/Plugins/Interfaces/IValidationPlugin.cs
@@ -1,8 +1,5 @@
-using ACMESharp.Authorizations;
-using ACMESharp.Protocol.Resources;
-using Autofac;
-using PKISharp.WACS.DomainObjects;
-using System.Collections.Generic;
+using PKISharp.WACS.Context;
+using System;
using System.Threading.Tasks;
namespace PKISharp.WACS.Plugins.Interfaces
@@ -24,50 +21,20 @@ namespace PKISharp.WACS.Plugins.Interfaces
/// <summary>
/// Clean up after validation attempt
/// </summary>
- Task CleanUp(ValidationContext context);
+ Task CleanUp(ValidationContext context);
+
+ /// <summary>
+ /// Indicate level of supported parallelism
+ /// </summary>
+ ParallelOperations Parallelism { get; }
}
- public class ValidationContext
+ [Flags]
+ public enum ParallelOperations
{
- public ValidationContext(
- ILifetimeScope scope,
- Authorization authorization,
- TargetPart targetPart,
- string challengeType,
- string pluginName)
- {
- Identifier = authorization.Identifier.Value;
- TargetPart = targetPart;
- Authorization = authorization;
- Scope = scope;
- ChallengeType = challengeType;
- PluginName = pluginName;
- }
- public ILifetimeScope Scope { get; }
- public string Identifier { get; }
- public string ChallengeType { get; }
- public string PluginName { get; }
- public TargetPart TargetPart { get; }
- public Authorization Authorization { get; }
- public Challenge? Challenge { get; set; }
- public IChallengeValidationDetails? ChallengeDetails { get; set; }
- public IValidationPlugin? ValidationPlugin { get; set; }
- public bool? Success { get; set; }
- public List<string> ErrorMessages { get; } = new List<string>();
- public void AddErrorMessage(string? value, bool fatal = true)
- {
- if (value != null)
- {
- if (!ErrorMessages.Contains(value))
- {
- ErrorMessages.Add(value);
- }
- }
- if (fatal)
- {
- Success = false;
- }
- }
+ None = 0,
+ Prepare = 1,
+ Answer = 2,
+ Clean = 4
}
-
}
diff --git a/src/main.lib/Plugins/ValidationPlugins/Dns/Acme/Acme.cs b/src/main.lib/Plugins/ValidationPlugins/Dns/Acme/Acme.cs
index b24cc38..ab167b1 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Dns/Acme/Acme.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Dns/Acme/Acme.cs
@@ -1,6 +1,6 @@
using PKISharp.WACS.Clients;
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System;
using System.Threading.Tasks;
diff --git a/src/main.lib/Plugins/ValidationPlugins/Dns/DnsValidation.cs b/src/main.lib/Plugins/ValidationPlugins/Dns/DnsValidation.cs
index d88cc47..d22f112 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Dns/DnsValidation.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Dns/DnsValidation.cs
@@ -1,7 +1,6 @@
using ACMESharp.Authorizations;
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.DomainObjects;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System;
using System.Collections.Generic;
diff --git a/src/main.lib/Plugins/ValidationPlugins/Dns/Manual/Manual.cs b/src/main.lib/Plugins/ValidationPlugins/Dns/Manual/Manual.cs
index b65ea07..077833d 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Dns/Manual/Manual.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Dns/Manual/Manual.cs
@@ -1,5 +1,5 @@
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System.Threading.Tasks;
diff --git a/src/main.lib/Plugins/ValidationPlugins/Dns/Script/Script.cs b/src/main.lib/Plugins/ValidationPlugins/Dns/Script/Script.cs
index 0f2c6f4..ad58904 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Dns/Script/Script.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Dns/Script/Script.cs
@@ -1,6 +1,6 @@
using PKISharp.WACS.Clients;
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System.Threading.Tasks;
diff --git a/src/main.lib/Plugins/ValidationPlugins/Http/HttpValidation.cs b/src/main.lib/Plugins/ValidationPlugins/Http/HttpValidation.cs
index 844b92e..01312a2 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Http/HttpValidation.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Http/HttpValidation.cs
@@ -1,4 +1,5 @@
using ACMESharp.Authorizations;
+using PKISharp.WACS.Context;
using PKISharp.WACS.DomainObjects;
using PKISharp.WACS.Plugins.Interfaces;
using PKISharp.WACS.Services;
@@ -79,7 +80,12 @@ namespace PKISharp.WACS.Plugins.ValidationPlugins
/// </summary>
public async override Task PrepareChallenge(ValidationContext context, Http01ChallengeValidationDetails challenge)
{
- Refresh(context.TargetPart);
+ // Should always have a value, confirmed by RenewalExecutor
+ // check only to satifiy the compiler
+ if (context.TargetPart != null)
+ {
+ Refresh(context.TargetPart);
+ }
WriteAuthorizationFile(challenge);
WriteWebConfig(challenge);
_log.Information("Answer should now be browsable at {answerUri}", challenge.HttpResourceUrl);
diff --git a/src/main.lib/Plugins/ValidationPlugins/Http/SelfHosting/SelfHosting.cs b/src/main.lib/Plugins/ValidationPlugins/Http/SelfHosting/SelfHosting.cs
index ad3d3be..c291e59 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Http/SelfHosting/SelfHosting.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Http/SelfHosting/SelfHosting.cs
@@ -1,7 +1,10 @@
using ACMESharp.Authorizations;
+using DnsClient;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Plugins.Interfaces;
using PKISharp.WACS.Services;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
@@ -14,12 +17,19 @@ namespace PKISharp.WACS.Plugins.ValidationPlugins.Http
internal const int DefaultHttpValidationPort = 80;
internal const int DefaultHttpsValidationPort = 443;
+ private readonly object _listenerLock = new object();
private HttpListener? _listener;
- private readonly Dictionary<string, string> _files;
+ private readonly ConcurrentDictionary<string, string> _files;
private readonly SelfHostingOptions _options;
private readonly ILogService _log;
private readonly IUserRoleService _userRoleService;
+ /// <summary>
+ /// We can answer requests for multiple domains
+ /// </summary>
+ public override ParallelOperations Parallelism =>
+ ParallelOperations.Answer | ParallelOperations.Clean | ParallelOperations.Prepare;
+
private bool HasListener => _listener != null;
private HttpListener Listener
{
@@ -38,11 +48,11 @@ namespace PKISharp.WACS.Plugins.ValidationPlugins.Http
{
_log = log;
_options = options;
- _files = new Dictionary<string, string>();
+ _files = new ConcurrentDictionary<string, string>();
_userRoleService = userRoleService;
}
- public async Task ReceiveRequests()
+ private async Task ReceiveRequests()
{
while (Listener.IsListening)
{
@@ -64,40 +74,55 @@ namespace PKISharp.WACS.Plugins.ValidationPlugins.Http
public override Task CleanUp(ValidationContext context, Http01ChallengeValidationDetails challenge)
{
- if (HasListener)
+ // Cleanup listener if nobody else has done it yet
+ lock (_listenerLock)
{
- try
- {
- Listener.Stop();
- Listener.Close();
- }
- catch
+ if (HasListener)
{
+ try
+ {
+ Listener.Stop();
+ Listener.Close();
+ }
+ finally
+ {
+ _listener = null;
+ }
}
}
+
return Task.CompletedTask;
}
public override Task PrepareChallenge(ValidationContext context, Http01ChallengeValidationDetails challenge)
{
- _files.Add("/" + challenge.HttpResourcePath, challenge.HttpResourceValue);
- var protocol = _options.Https == true ? "https" : "http";
- var port = _options.Port ?? (_options.Https == true ?
- DefaultHttpsValidationPort :
- DefaultHttpValidationPort);
- var prefix = $"{protocol}://+:{port}/.well-known/acme-challenge/";
- try
- {
- Listener = new HttpListener();
- Listener.Prefixes.Add(prefix);
- Listener.Start();
- Task.Run(ReceiveRequests);
- }
- catch
+ // Create listener if it doesn't exist yet
+ lock (_listenerLock)
{
- _log.Error("Unable to activate listener, this may be because of insufficient rights or a non-Microsoft webserver using port {port}", port);
- throw;
+ if (_listener == null)
+ {
+ var protocol = _options.Https == true ? "https" : "http";
+ var port = _options.Port ?? (_options.Https == true ?
+ DefaultHttpsValidationPort :
+ DefaultHttpValidationPort);
+ var prefix = $"{protocol}://+:{port}/.well-known/acme-challenge/";
+ try
+ {
+ Listener = new HttpListener();
+ Listener.Prefixes.Add(prefix);
+ Listener.Start();
+ Task.Run(ReceiveRequests);
+ }
+ catch
+ {
+ _log.Error("Unable to activate listener, this may be because of insufficient rights or a non-Microsoft webserver using port {port}", port);
+ throw;
+ }
+ }
}
+
+ // Add validation file
+ _files.GetOrAdd("/" + challenge.HttpResourcePath, challenge.HttpResourceValue);
return Task.CompletedTask;
}
diff --git a/src/main.lib/Plugins/ValidationPlugins/Http/WebDav/WebDav.cs b/src/main.lib/Plugins/ValidationPlugins/Http/WebDav/WebDav.cs
index 9afd9d1..9f3d138 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Http/WebDav/WebDav.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Http/WebDav/WebDav.cs
@@ -1,6 +1,6 @@
using ACMESharp.Authorizations;
using PKISharp.WACS.Client;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System.Threading.Tasks;
diff --git a/src/main.lib/Plugins/ValidationPlugins/Tls/SelfHosting/SelfHosting.cs b/src/main.lib/Plugins/ValidationPlugins/Tls/SelfHosting/SelfHosting.cs
index baefd16..4e6c1e2 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Tls/SelfHosting/SelfHosting.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Tls/SelfHosting/SelfHosting.cs
@@ -1,5 +1,6 @@
using ACMESharp.Authorizations;
using Org.BouncyCastle.Asn1;
+using PKISharp.WACS.Context;
using PKISharp.WACS.DomainObjects;
using PKISharp.WACS.Plugins.Interfaces;
using PKISharp.WACS.Services;
diff --git a/src/main.lib/Plugins/ValidationPlugins/Validation.cs b/src/main.lib/Plugins/ValidationPlugins/Validation.cs
index b97cea6..4205aa2 100644
--- a/src/main.lib/Plugins/ValidationPlugins/Validation.cs
+++ b/src/main.lib/Plugins/ValidationPlugins/Validation.cs
@@ -1,4 +1,5 @@
using ACMESharp.Authorizations;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Plugins.Interfaces;
using System;
using System.Threading.Tasks;
@@ -53,5 +54,10 @@ namespace PKISharp.WACS.Plugins.ValidationPlugins
/// Is the plugin currently disabled
/// </summary>
public virtual (bool, string?) Disabled => (false, null);
+
+ /// <summary>
+ /// No parallelism by default
+ /// </summary>
+ public virtual ParallelOperations Parallelism => ParallelOperations.None;
}
}
diff --git a/src/main.lib/RenewalExecutor.cs b/src/main.lib/RenewalExecutor.cs
index 9af69cd..fb70a28 100644
--- a/src/main.lib/RenewalExecutor.cs
+++ b/src/main.lib/RenewalExecutor.cs
@@ -1,6 +1,8 @@
using Autofac;
+using Newtonsoft.Json.Schema;
using PKISharp.WACS.Clients.Acme;
using PKISharp.WACS.Configuration;
+using PKISharp.WACS.Context;
using PKISharp.WACS.DomainObjects;
using PKISharp.WACS.Extensions;
using PKISharp.WACS.Plugins.Base.Options;
@@ -24,33 +26,15 @@ namespace PKISharp.WACS
private readonly ILogService _log;
private readonly IInputService _input;
private readonly ExceptionHandler _exceptionHandler;
-
- /// <summary>
- /// Common objects used throughout the renewal process
- /// </summary>
- private class ExecutionContext
- {
- public ILifetimeScope Scope { get; private set; }
- public Order Order { get; private set; }
- public RunLevel RunLevel { get; private set; }
- public RenewResult Result { get; private set; }
- public Target Target => Order.Target;
- public Renewal Renewal => Order.Renewal;
-
- public ExecutionContext(ILifetimeScope scope, Order order, RunLevel runLevel, RenewResult result)
- {
- Scope = scope;
- Order = order;
- RunLevel = runLevel;
- Result = result;
- }
- }
+ private readonly RenewalValidator _validator;
public RenewalExecutor(
MainArguments args, IAutofacBuilder scopeBuilder,
ILogService log, IInputService input,
+ RenewalValidator validator,
ExceptionHandler exceptionHandler, IContainer container)
{
+ _validator = validator;
_args = args;
_scopeBuilder = scopeBuilder;
_log = log;
@@ -188,7 +172,7 @@ namespace PKISharp.WACS
var context = new ExecutionContext(execute, order, runLevel, result);
// Authorize the order (validation)
- await AuthorizeOrder(context);
+ await _validator.AuthorizeOrder(context);
if (context.Result.Success)
{
// Execute final steps (CSR, store, install)
@@ -199,79 +183,6 @@ namespace PKISharp.WACS
}
/// <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>
- private async Task AuthorizeOrder(ExecutionContext context)
- {
- // Sanity check
- if (context.Order.Details == null)
- {
- context.Result.AddErrorMessage($"Unable to create order");
- return;
- }
-
- // Answer the challenges
- var client = context.Scope.Resolve<AcmeClient>();
- var authorizations = context.Order.Details.Payload.Authorizations.ToList();
- foreach (var authorizationUri in authorizations)
- {
- _log.Verbose("Handle authorization {n}/{m}",
- authorizations.IndexOf(authorizationUri) + 1,
- authorizations.Count);
-
- // Get authorization challenge details from server
- var authorization = await client.GetAuthorizationDetails(authorizationUri);
-
- // Find a targetPart that matches the challenge
- var targetPart = context.Target.Parts.
- FirstOrDefault(tp => tp.GetHosts(false).
- Any(h => authorization.Identifier.Value == h.Replace("*.", "")));
- if (targetPart == null)
- {
- context.Result.AddErrorMessage("Unable to match challenge to target");
- return;
- }
-
- // Run the validation plugin
- var options = context.Renewal.ValidationPluginOptions;
- using var validation = _scopeBuilder.Validation(context.Scope, options);
- var validationContext = new ValidationContext(validation, authorization, targetPart, options.ChallengeType, options.Name);
- // Prepare answer
- await PrepareChallengeAnswer(validationContext, context.RunLevel);
- if (context.Result.Success)
- {
- // Submit for validation
- await AnswerChallenge(validationContext);
- TransferErrors(validationContext, context.Result, authorization.Identifier.Value);
- }
- if (validationContext.Challenge != null)
- {
- // Cleanup
- await CleanValidation(validationContext);
- TransferErrors(validationContext, context.Result, authorization.Identifier.Value);
- }
- if (!context.Result.Success)
- {
- break;
- }
- }
- }
-
- /// <summary>
- /// Move errors from a validation context up to the renewal result
- /// </summary>
- /// <param name="from"></param>
- /// <param name="to"></param>
- /// <param name="prefix"></param>
- private void TransferErrors(ValidationContext from, RenewResult to, string prefix) =>
- from.ErrorMessages.ForEach(e => to.AddErrorMessage($"[{prefix}] {e}", from.Success == false));
-
- /// <summary>
/// Steps to take on succesful (re)authorization
/// </summary>
/// <param name="partialTarget"></param>
@@ -419,184 +330,6 @@ namespace PKISharp.WACS
context.Result.AddErrorMessage(message);
}
}
-
- /// <summary>
- /// Make sure we have authorization for every host in target
- /// </summary>
- /// <param name="target"></param>
- /// <returns></returns>
- private async Task PrepareChallengeAnswer(ValidationContext context, RunLevel runLevel)
- {
- var client = context.Scope.Resolve<AcmeClient>();
- try
- {
- if (context.Authorization.Status == AcmeClient.AuthorizationValid)
- {
- _log.Information("[{identifier}] Cached authorization result: {Status}", context.Identifier, context.Authorization.Status);
- if (!runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache))
- {
- return;
- }
- // Used to make --force or --test re-validation errors non-fatal
- _log.Information("[{identifier}] Handling challenge anyway because --test and/or --force is active");
- context.Success = true;
- }
-
- _log.Information("[{identifier}] Authorizing...", context.Identifier);
- _log.Verbose("[{identifier}] Initial authorization status: {status}", context.Identifier, context.Authorization.Status);
- _log.Verbose("[{identifier}] Challenge types available: {challenges}", context.Identifier, context.Authorization.Challenges.Select(x => x.Type ?? "[Unknown]"));
- var challenge = context.Authorization.Challenges.FirstOrDefault(c => string.Equals(c.Type, context.ChallengeType, StringComparison.CurrentCultureIgnoreCase));
- if (challenge == null)
- {
- if (context.Success == true)
- {
- var usedType = context.Authorization.Challenges.
- Where(x => x.Status == AcmeClient.ChallengeValid).
- FirstOrDefault();
- _log.Warning("[{identifier}] Expected challenge type {type} not available, already validated using {valided}.",
- context.Identifier,
- context.ChallengeType,
- usedType?.Type ?? "[unknown]");
- return;
- }
- else
- {
- _log.Error("[{identifier}] Expected challenge type {type} not available.",
- context.Identifier,
- context.ChallengeType);
- context.AddErrorMessage("Expected challenge type not available", context.Success == false);
- return;
- }
- }
- else
- {
- _log.Verbose("[{identifier}] Initial challenge status: {status}", context.Identifier, 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 (!runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache))
- {
- _log.Information("[{identifier}] Cached challenge result: {Status}", context.Identifier, context.Authorization.Status);
- return;
- }
- }
- }
-
- // We actually have to do validation now
- try
- {
- context.ValidationPlugin = context.Scope.Resolve<IValidationPlugin>();
- }
- catch (Exception ex)
- {
- _log.Error(ex, "[{identifier}] Error resolving validation plugin", context.Identifier);
- }
- if (context.ValidationPlugin == null)
- {
- _log.Error("[{identifier}] Validation plugin not found or not created", context.Identifier);
- context.AddErrorMessage("Validation plugin not found or not created", context.Success == false);
- return;
- }
- var (disabled, disabledReason) = context.ValidationPlugin.Disabled;
- if (disabled)
- {
- _log.Error($"[{{identifier}}] Validation plugin is not available. {disabledReason}", context.Identifier);
- context.AddErrorMessage("Validation plugin is not available", context.Success == false);
- return;
- }
- _log.Information("[{identifier}] Authorizing using {challengeType} validation ({name})",
- context.Identifier,
- context.ChallengeType,
- context.PluginName);
- try
- {
- // Now that we're going to call into PrepareChallenge, we will assume
- // responsibility to also call CleanUp later, which is signalled by
- // the Challenge propery being not null
- context.ChallengeDetails = await client.DecodeChallengeValidation(context.Authorization, challenge);
- context.Challenge = challenge;
- await context.ValidationPlugin.PrepareChallenge(context);
- }
- catch (Exception ex)
- {
- _log.Error(ex, "[{identifier}] Error preparing for challenge answer", context.Identifier);
- context.AddErrorMessage("Error preparing for challenge answer", context.Success == false);
- return;
- }
- }
- catch (Exception ex)
- {
- _log.Error("[{identifier}] Error preparing challenge answer", context.Identifier);
- var message = _exceptionHandler.HandleException(ex);
- context.AddErrorMessage(message, context.Success == false);
- }
- }
-
- /// <summary>
- /// Make sure we have authorization for every host in target
- /// </summary>
- /// <param name="target"></param>
- /// <returns></returns>
- private async Task AnswerChallenge(ValidationContext validationContext)
- {
- if (validationContext.Challenge == null)
- {
- throw new InvalidOperationException();
- }
- try
- {
- _log.Debug("[{identifier}] Submitting challenge answer", validationContext.Identifier);
- var client = validationContext.Scope.Resolve<AcmeClient>();
- var updatedChallenge = await client.AnswerChallenge(validationContext.Challenge);
- validationContext.Challenge = updatedChallenge;
- if (updatedChallenge.Status != AcmeClient.ChallengeValid)
- {
- if (updatedChallenge.Error != null)
- {
- _log.Error(updatedChallenge.Error.ToString());
- }
- _log.Error("[{identifier}] Authorization result: {Status}", validationContext.Identifier, updatedChallenge.Status);
- validationContext.AddErrorMessage(updatedChallenge.Error?.ToString() ?? "Unspecified error", validationContext.Success == false);
- return;
- }
- else
- {
- _log.Information("[{identifier}] Authorization result: {Status}", validationContext.Identifier, updatedChallenge.Status);
- return;
- }
- }
- catch (Exception ex)
- {
- _log.Error("[{identifier}] Error submitting challenge answer", validationContext.Identifier);
- var message = _exceptionHandler.HandleException(ex);
- validationContext.AddErrorMessage(message, validationContext.Success == false);
- }
- }
-
- /// <summary>
- /// Clean up after (succesful or unsuccesful) validation attempt
- /// </summary>
- /// <param name="validationContext"></param>
- /// <returns></returns>
- private async Task CleanValidation(ValidationContext validationContext)
- {
- if (validationContext.Challenge == null ||
- validationContext.ValidationPlugin == null)
- {
- throw new InvalidOperationException();
- }
- try
- {
- _log.Verbose("[{identifier}] Starting post-validation cleanup", validationContext.Identifier);
- await validationContext.ValidationPlugin.CleanUp(validationContext);
- _log.Verbose("[{identifier}] Post-validation cleanup was succesful", validationContext.Identifier);
- }
- catch (Exception ex)
- {
- _log.Warning("[{identifier}] An error occured during post-validation cleanup: {ex}", ex.Message, validationContext.Identifier);
- }
- }
+
}
}
diff --git a/src/main.lib/RenewalValidator.cs b/src/main.lib/RenewalValidator.cs
new file mode 100644
index 0000000..a064984
--- /dev/null
+++ b/src/main.lib/RenewalValidator.cs
@@ -0,0 +1,400 @@
+using Autofac;
+using PKISharp.WACS.Clients.Acme;
+using PKISharp.WACS.Context;
+using PKISharp.WACS.DomainObjects;
+using PKISharp.WACS.Extensions;
+using PKISharp.WACS.Plugins.Base.Options;
+using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Services;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace PKISharp.WACS
+{
+ /// <summary>
+ /// This part of the code handles the actual creation/renewal of ACME certificates
+ /// </summary>
+ internal class RenewalValidator
+ {
+ private readonly IAutofacBuilder _scopeBuilder;
+ private readonly ILogService _log;
+ private readonly ISettingsService _settings;
+ private readonly ExceptionHandler _exceptionHandler;
+ public RenewalValidator(IAutofacBuilder scopeBuilder, ISettingsService settings, ILogService log, ExceptionHandler exceptionHandler)
+ {
+ _scopeBuilder = scopeBuilder;
+ _log = log;
+ _exceptionHandler = exceptionHandler;
+ _settings = settings;
+ }
+
+ /// <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)
+ {
+ // Sanity check
+ if (context.Order.Details == null)
+ {
+ context.Result.AddErrorMessage($"Unable to create order");
+ return;
+ }
+
+ // 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");
+ return;
+ }
+ var (disabled, disabledReason) = validationPlugin.Disabled;
+ if (disabled)
+ {
+ _log.Error($"Validation plugin is not available. {disabledReason}");
+ context.Result.AddErrorMessage("Validation plugin is not available");
+ return;
+ }
+
+ // Get authorization details
+ var authorizations = context.Order.Details.Payload.Authorizations.ToList();
+ var contextParamTasks = authorizations.Select(authorizationUri => GetValidationContextParameters(context, authorizationUri, options));
+ var contextParams = (await Task.WhenAll(contextParamTasks)).ToList();
+ var missingTarget = contextParams.FirstOrDefault(x => x.TargetPart == null);
+ if (missingTarget != null)
+ {
+ context.Result.AddErrorMessage($"Unable to match challenge {missingTarget.Authorization.Identifier.Value} to target");
+ return;
+ }
+
+ if (_settings.Validation.DisableMultiThreading == true ||
+ validationPlugin.Parallelism == ParallelOperations.None)
+ {
+ await SerialValidation(context, contextParams);
+ }
+ else
+ {
+ await ParallelValidation(validationPlugin.Parallelism, validationScope, context, contextParams);
+ }
+ }
+
+ /// <summary>
+ /// Handle multiple validations in parallel
+ /// </summary>
+ /// <returns></returns>
+ private async Task ParallelValidation(ParallelOperations level, ILifetimeScope scope, ExecutionContext context, List<ValidationContextParameters> parameters)
+ {
+ var contexts = parameters.Select(parameter => new ValidationContext(scope, parameter)).ToList();
+
+ // Prepare for challenge answer
+ if (level.HasFlag(ParallelOperations.Prepare))
+ {
+ // Parallel
+ _log.Verbose("Handle {n} preparation(s)", contexts.Count);
+ var prepareTasks = contexts.Select(vc => PrepareChallengeAnswer(vc, context.RunLevel));
+ await Task.WhenAll(prepareTasks);
+ foreach (var ctx in contexts)
+ {
+ TransferErrors(ctx, context.Result);
+ }
+ if (!context.Result.Success)
+ {
+ return;
+ }
+ }
+ else
+ {
+ // Serial
+ foreach (var ctx in contexts)
+ {
+ await PrepareChallengeAnswer(ctx, context.RunLevel);
+ TransferErrors(ctx, context.Result);
+ if (!context.Result.Success)
+ {
+ return;
+ }
+ }
+ }
+
+ // Submit challenge answer
+ if (level.HasFlag(ParallelOperations.Answer))
+ {
+ // Parallel
+ _log.Verbose("Handle {n} answers(s)", contexts.Count);
+ var answerTasks = contexts.Select(vc => AnswerChallenge(vc));
+ await Task.WhenAll(answerTasks);
+ foreach (var ctx in contexts)
+ {
+ TransferErrors(ctx, context.Result);
+ }
+ if (!context.Result.Success)
+ {
+ return;
+ }
+ }
+ else
+ {
+ // Serial
+ foreach (var ctx in contexts)
+ {
+ await AnswerChallenge(ctx);
+ TransferErrors(ctx, context.Result);
+ if (!context.Result.Success)
+ {
+ return;
+ }
+ }
+ }
+
+ if (level.HasFlag(ParallelOperations.Clean))
+ {
+ // Parallel
+ _log.Verbose("Handle {n} cleanups(s)", contexts.Count);
+ var cleanUpTasks = contexts.Select(vc => CleanValidation(vc));
+ await Task.WhenAll(cleanUpTasks);
+ foreach (var ctx in contexts)
+ {
+ TransferErrors(ctx, context.Result);
+ }
+ }
+ else
+ {
+ // Serial
+ foreach (var ctx in contexts)
+ {
+ if (ctx.Challenge != null)
+ {
+ // Cleanup
+ await CleanValidation(ctx);
+ TransferErrors(ctx, context.Result);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Handle validation in serial order
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="parameters"></param>
+ /// <returns></returns>
+ private async Task SerialValidation(ExecutionContext context, List<ValidationContextParameters> parameters)
+ {
+ foreach (var parameter in parameters)
+ {
+ _log.Verbose("Handle authorization {n}/{m}",
+ parameters.IndexOf(parameter) + 1,
+ parameters.Count);
+ using var identifierScope = _scopeBuilder.Validation(context.Scope, context.Renewal.ValidationPluginOptions);
+ await ParallelValidation(ParallelOperations.None, identifierScope, context, new List<ValidationContextParameters> { parameter });
+ if (!context.Result.Success)
+ {
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Get information needed to construct a validation context (shared between serial and parallel mode)
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="authorizationUri"></param>
+ /// <param name="options"></param>
+ /// <returns></returns>
+ private async Task<ValidationContextParameters> GetValidationContextParameters(ExecutionContext context, string authorizationUri, ValidationPluginOptions options)
+ {
+ // Get authorization challenge details from server
+ var client = context.Scope.Resolve<AcmeClient>();
+ var authorization = await client.GetAuthorizationDetails(authorizationUri);
+
+ // Find a targetPart that matches the challenge
+ var targetPart = context.Target.Parts.
+ FirstOrDefault(tp => tp.GetHosts(false).
+ Any(h => authorization.Identifier.Value == h.Replace("*.", "")));
+
+ return new ValidationContextParameters(authorization, targetPart, options.ChallengeType, options.Name);
+ }
+
+ /// <summary>
+ /// Move errors from a validation context up to the renewal result
+ /// </summary>
+ /// <param name="from"></param>
+ /// <param name="to"></param>
+ /// <param name="prefix"></param>
+ private void TransferErrors(ValidationContext from, RenewResult to)
+ {
+ from.ErrorMessages.ForEach(e => to.AddErrorMessage($"[{from.Identifier}] {e}", from.Success == false));
+ from.ErrorMessages.Clear();
+ }
+
+
+ /// <summary>
+ /// Make sure we have authorization for every host in target
+ /// </summary>
+ /// <param name="target"></param>
+ /// <returns></returns>
+ private async Task PrepareChallengeAnswer(ValidationContext context, RunLevel runLevel)
+ {
+ if (context.ValidationPlugin == null)
+ {
+ throw new InvalidOperationException();
+ }
+ var client = context.Scope.Resolve<AcmeClient>();
+ try
+ {
+ if (context.Authorization.Status == AcmeClient.AuthorizationValid)
+ {
+ _log.Information("[{identifier}] Cached authorization result: {Status}", context.Identifier, context.Authorization.Status);
+ if (!runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache))
+ {
+ return;
+ }
+ // Used to make --force or --test re-validation errors non-fatal
+ _log.Information("[{identifier}] Handling challenge anyway because --test and/or --force is active");
+ context.Success = true;
+ }
+
+ _log.Information("[{identifier}] Authorizing...", context.Identifier);
+ _log.Verbose("[{identifier}] Initial authorization status: {status}", context.Identifier, context.Authorization.Status);
+ _log.Verbose("[{identifier}] Challenge types available: {challenges}", context.Identifier, context.Authorization.Challenges.Select(x => x.Type ?? "[Unknown]"));
+ var challenge = context.Authorization.Challenges.FirstOrDefault(c => string.Equals(c.Type, context.ChallengeType, StringComparison.CurrentCultureIgnoreCase));
+ if (challenge == null)
+ {
+ if (context.Success == true)
+ {
+ var usedType = context.Authorization.Challenges.
+ Where(x => x.Status == AcmeClient.ChallengeValid).
+ FirstOrDefault();
+ _log.Warning("[{identifier}] Expected challenge type {type} not available, already validated using {valided}.",
+ context.Identifier,
+ context.ChallengeType,
+ usedType?.Type ?? "[unknown]");
+ return;
+ }
+ else
+ {
+ _log.Error("[{identifier}] Expected challenge type {type} not available.",
+ context.Identifier,
+ context.ChallengeType);
+ context.AddErrorMessage("Expected challenge type not available", context.Success == false);
+ return;
+ }
+ }
+ else
+ {
+ _log.Verbose("[{identifier}] Initial challenge status: {status}", context.Identifier, 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 (!runLevel.HasFlag(RunLevel.Test) && !runLevel.HasFlag(RunLevel.IgnoreCache))
+ {
+ _log.Information("[{identifier}] Cached challenge result: {Status}", context.Identifier, context.Authorization.Status);
+ return;
+ }
+ }
+ }
+ _log.Information("[{identifier}] Authorizing using {challengeType} validation ({name})",
+ context.Identifier,
+ context.ChallengeType,
+ context.PluginName);
+ try
+ {
+ // Now that we're going to call into PrepareChallenge, we will assume
+ // responsibility to also call CleanUp later, which is signalled by
+ // the Challenge propery being not null
+ context.ChallengeDetails = await client.DecodeChallengeValidation(context.Authorization, challenge);
+ context.Challenge = challenge;
+ await context.ValidationPlugin.PrepareChallenge(context);
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "[{identifier}] Error preparing for challenge answer", context.Identifier);
+ context.AddErrorMessage("Error preparing for challenge answer", context.Success == false);
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ _log.Error("[{identifier}] Error preparing challenge answer", context.Identifier);
+ var message = _exceptionHandler.HandleException(ex);
+ context.AddErrorMessage(message, context.Success == false);
+ }
+ }
+
+ /// <summary>
+ /// Make sure we have authorization for every host in target
+ /// </summary>
+ /// <param name="target"></param>
+ /// <returns></returns>
+ private async Task AnswerChallenge(ValidationContext validationContext)
+ {
+ if (validationContext.Challenge == null)
+ {
+ throw new InvalidOperationException();
+ }
+ try
+ {
+ _log.Debug("[{identifier}] Submitting challenge answer", validationContext.Identifier);
+ var client = validationContext.Scope.Resolve<AcmeClient>();
+ var updatedChallenge = await client.AnswerChallenge(validationContext.Challenge);
+ validationContext.Challenge = updatedChallenge;
+ if (updatedChallenge.Status != AcmeClient.ChallengeValid)
+ {
+ if (updatedChallenge.Error != null)
+ {
+ _log.Error(updatedChallenge.Error.ToString());
+ }
+ _log.Error("[{identifier}] Authorization result: {Status}", validationContext.Identifier, updatedChallenge.Status);
+ validationContext.AddErrorMessage(updatedChallenge.Error?.ToString() ?? "Unspecified error", validationContext.Success == false);
+ return;
+ }
+ else
+ {
+ _log.Information("[{identifier}] Authorization result: {Status}", validationContext.Identifier, updatedChallenge.Status);
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ _log.Error("[{identifier}] Error submitting challenge answer", validationContext.Identifier);
+ var message = _exceptionHandler.HandleException(ex);
+ validationContext.AddErrorMessage(message, validationContext.Success == false);
+ }
+ }
+
+ /// <summary>
+ /// Clean up after (succesful or unsuccesful) validation attempt
+ /// </summary>
+ /// <param name="validationContext"></param>
+ /// <returns></returns>
+ private async Task CleanValidation(ValidationContext validationContext)
+ {
+ if (validationContext.Challenge == null ||
+ validationContext.ValidationPlugin == null)
+ {
+ throw new InvalidOperationException();
+ }
+ try
+ {
+ _log.Verbose("[{identifier}] Starting post-validation cleanup", validationContext.Identifier);
+ await validationContext.ValidationPlugin.CleanUp(validationContext);
+ _log.Verbose("[{identifier}] Post-validation cleanup was succesful", validationContext.Identifier);
+ }
+ catch (Exception ex)
+ {
+ _log.Warning("[{identifier}] An error occured during post-validation cleanup: {ex}", ex.Message, validationContext.Identifier);
+ }
+ }
+ }
+}
diff --git a/src/main.lib/Services/SettingsService.cs b/src/main.lib/Services/SettingsService.cs
index ab03b4e..e930c96 100644
--- a/src/main.lib/Services/SettingsService.cs
+++ b/src/main.lib/Services/SettingsService.cs
@@ -485,6 +485,11 @@ namespace PKISharp.WACS.Services
public string? DefaultValidationMode { get; set; }
/// <summary>
+ /// Disable multithreading for validation
+ /// </summary>
+ public bool? DisableMultiThreading { get; set; }
+
+ /// <summary>
/// If set to True, it will cleanup the folder structure
/// and files it creates under the site for authorization.
/// </summary>
diff --git a/src/main/Program.cs b/src/main/Program.cs
index 4811c3b..e02a212 100644
--- a/src/main/Program.cs
+++ b/src/main/Program.cs
@@ -126,6 +126,7 @@ namespace PKISharp.WACS.Host
_ = builder.RegisterType<TaskSchedulerService>().SingleInstance();
_ = builder.RegisterType<NotificationService>().SingleInstance();
_ = builder.RegisterType<RenewalExecutor>().SingleInstance();
+ _ = builder.RegisterType<RenewalValidator>().SingleInstance();
_ = builder.RegisterType<RenewalManager>().SingleInstance();
_ = builder.RegisterType<RenewalCreator>().SingleInstance();
_ = builder.Register(c => c.Resolve<IArgumentsService>().MainArguments).SingleInstance();
diff --git a/src/main/settings.json b/src/main/settings.json
index 54d61c4..de50241 100644
--- a/src/main/settings.json
+++ b/src/main/settings.json
@@ -61,6 +61,7 @@
"Validation": {
"DefaultValidation": null,
"DefaultValidationMode": null,
+ "DisableMultiThreading": false,
"CleanupFolders": true,
"PreValidateDns": true,
"PreValidateDnsRetryCount": 5,
diff --git a/src/plugin.validation.dns.azure/Azure.cs b/src/plugin.validation.dns.azure/Azure.cs
index f3bffd3..d60ba79 100755
--- a/src/plugin.validation.dns.azure/Azure.cs
+++ b/src/plugin.validation.dns.azure/Azure.cs
@@ -4,7 +4,7 @@ using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Rest;
using Microsoft.Rest.Azure.Authentication;
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System;
using System.Collections.Generic;
diff --git a/src/plugin.validation.dns.cloudflare/Cloudflare.cs b/src/plugin.validation.dns.cloudflare/Cloudflare.cs
index fb6a69a..0d8eb92 100644
--- a/src/plugin.validation.dns.cloudflare/Cloudflare.cs
+++ b/src/plugin.validation.dns.cloudflare/Cloudflare.cs
@@ -3,7 +3,7 @@ using FluentCloudflare.Api;
using FluentCloudflare.Api.Entities;
using FluentCloudflare.Extensions;
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System;
using System.Linq;
diff --git a/src/plugin.validation.dns.dreamhost/DreamhostDnsValidation.cs b/src/plugin.validation.dns.dreamhost/DreamhostDnsValidation.cs
index 4d5f0b2..79409af 100644
--- a/src/plugin.validation.dns.dreamhost/DreamhostDnsValidation.cs
+++ b/src/plugin.validation.dns.dreamhost/DreamhostDnsValidation.cs
@@ -1,5 +1,5 @@
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Plugins.ValidationPlugins.Dreamhost;
using PKISharp.WACS.Services;
using System;
diff --git a/src/plugin.validation.dns.luadns/luadns.cs b/src/plugin.validation.dns.luadns/luadns.cs
index c03fbd7..de6c38a 100644
--- a/src/plugin.validation.dns.luadns/luadns.cs
+++ b/src/plugin.validation.dns.luadns/luadns.cs
@@ -1,5 +1,5 @@
using PKISharp.WACS.Clients.DNS;
-using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
using System;
using System.Collections.Generic;
diff --git a/src/plugin.validation.dns.route53/Route53.cs b/src/plugin.validation.dns.route53/Route53.cs
index ea0c2a2..616195b 100644
--- a/src/plugin.validation.dns.route53/Route53.cs
+++ b/src/plugin.validation.dns.route53/Route53.cs
@@ -1,14 +1,14 @@
-using System;
-using Amazon;
+using Amazon;
using Amazon.Route53;
using Amazon.Route53.Model;
using Amazon.Runtime;
using PKISharp.WACS.Clients.DNS;
+using PKISharp.WACS.Context;
using PKISharp.WACS.Services;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using PKISharp.WACS.Plugins.Interfaces;
namespace PKISharp.WACS.Plugins.ValidationPlugins.Dns
{