Skip to content

Commit

Permalink
Use direct DNS query for challenge pre-validation (#36)
Browse files Browse the repository at this point in the history
It makes no sense to order challenge on ACME server while DNS record is being propagated thru DNS servers.
Moreover such behavior causes premature failure of challenge on ACME server (e.g. Let's encrypt).
So it is better to previously wait while DNS challenge is being resolved in a right way before send
validation request to ACME server.

Co-authored-by: Pavel Sviridov <[email protected]>
  • Loading branch information
pasviridov and Pavel Sviridov authored Jun 4, 2023
1 parent 6b9f912 commit 1140995
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/AzAcme.Cli/Commands/Options/OrderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public class OrderOptions : Options
[Option("cf-api-token", Required = false, HelpText = "Cloudflare API Token with permissions to modify DNS. Required when using Cloudflare DNS provider.")]
public string CloudlfareApiToken { get; set; }


[Option("dns-lookup", Required = false, Default = "", HelpText = "DNS lookup server pre-defined name or custom IP address for challenge pre-validation.")]
public string DnsLookup { get; set; }

#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.


Expand Down
19 changes: 15 additions & 4 deletions src/AzAcme.Cli/Commands/OrderCommand.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using AzAcme.Cli.Commands.Options;
using AzAcme.Cli.Util;
using AzAcme.Core;
using AzAcme.Core.Exceptions;
using AzAcme.Core.Extensions;
using AzAcme.Core.Providers.Models;
using Microsoft.Extensions.Logging;
Expand All @@ -14,16 +13,19 @@ public class OrderCommand : Command<OrderOptions>
private readonly ICertificateStore certificateStore;
private readonly IAcmeDirectory acmeDirectory;
private readonly IDnsZone dnsZone;
private readonly IDnsLookup dnsLookup;

public OrderCommand(ILogger logger,
EnvironmentVariableResolver environmentVariableResolver,
ICertificateStore certificateStore,
IAcmeDirectory acmeDirectory,
IDnsZone dnsZone) : base(logger, environmentVariableResolver)
IDnsZone dnsZone,
IDnsLookup dnsLookup) : base(logger, environmentVariableResolver)
{
this.certificateStore = certificateStore ?? throw new ArgumentNullException(nameof(certificateStore));
this.acmeDirectory = acmeDirectory ?? throw new ArgumentNullException(nameof(acmeDirectory));
this.dnsZone = dnsZone ?? throw new ArgumentNullException(nameof(dnsZone));
this.dnsLookup = dnsLookup ?? throw new ArgumentNullException(nameof(dnsLookup));
}

protected override async Task<int> OnExecute(OrderOptions opts)
Expand Down Expand Up @@ -143,7 +145,8 @@ private async Task WaitForVerificationWithTable(Order order, IAcmeDirectory dire
int attempt = 1;
while (attempt <= attempts)
{
await directory.ValidateChallenges(order);
await ValidateChallenges(directory, order);

table.Rows.Clear();
foreach (var item in order.Challenges)
{
Expand Down Expand Up @@ -171,7 +174,7 @@ await AnsiConsole.Live(table)
int attempt = 1;
while (attempt <= attempts)
{
await directory.ValidateChallenges(order);
await ValidateChallenges(directory, order);
table.Rows.Clear();
foreach (var item in order.Challenges)
Expand All @@ -192,5 +195,13 @@ await AnsiConsole.Live(table)
});
}
}

private async Task ValidateChallenges(IAcmeDirectory directory, Order order)
{
if (await dnsLookup.ValidateTxtRecords(order))
{
await directory.ValidateChallenges(order);
}
}
}
}
17 changes: 16 additions & 1 deletion src/AzAcme.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
using Microsoft.Extensions.Logging;
using Spectre.Console;
using System.Diagnostics;
using System.Net;
using System.Text;
using AzAcme.Core.Providers;
using DnsClient;

namespace AzAcmi
{
Expand Down Expand Up @@ -126,8 +129,20 @@ static async Task<OrderCommand> BuildOrderCommand(ILogger logger, OrderOptions o
}
);
var dnsLookup = new DnsLookup(options.DnsLookup.ToLowerInvariant() switch
{
"" => null,
"google" => NameServer.GooglePublicDns,
"google2" => NameServer.GooglePublicDns2,
"cloudflare" => NameServer.Cloudflare,
"cloudflare2" => NameServer.Cloudflare2,
_ => IPEndPoint.TryParse(options.DnsLookup, out var nameServerIp)
? nameServerIp
: throw new ConfigurationException("Error parsing DNS lookup server IP address.")
});
// command
var rc = new OrderCommand(logger, envResolver, certificateStore, acmeProvider, dns);
var rc = new OrderCommand(logger, envResolver, certificateStore, acmeProvider, dns, dnsLookup);
return rc;
});
Expand Down
1 change: 1 addition & 0 deletions src/AzAcme.Core/AzAcme.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.3.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.3.0" />
<PackageReference Include="Certes" Version="3.0.3" />
<PackageReference Include="DnsClient" Version="1.7.0" />
<PackageReference Include="Microsoft.Azure.Management.Dns" Version="3.0.1" />
<PackageReference Include="Microsoft.Azure.Management.ResourceManager.Fluent" Version="1.38.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
Expand Down
9 changes: 9 additions & 0 deletions src/AzAcme.Core/IDnsLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using AzAcme.Core.Providers.Models;

namespace AzAcme.Core
{
public interface IDnsLookup
{
Task<bool> ValidateTxtRecords(Order order);
}
}
42 changes: 42 additions & 0 deletions src/AzAcme.Core/Providers/DnsLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Net;
using DnsClient;

using AzAcme.Core.Providers.Helpers;
using AzAcme.Core.Providers.Models;

namespace AzAcme.Core.Providers
{
public class DnsLookup : IDnsLookup
{
private readonly IPEndPoint? nameServer;

public DnsLookup() : this(null) { }

public DnsLookup(IPEndPoint? nameServer)
{
this.nameServer = nameServer;
}

public async Task<bool> ValidateTxtRecords(Order order)
{
bool dnsResolved = true;

var lookup = nameServer != null ? new LookupClient(nameServer) : new LookupClient();

foreach (var challenge in order.Challenges)
{
var dnsName = DnsHelpers.DetermineTxtRecordName(challenge.Identitifer, string.Empty);

var result = await lookup.QueryAsync(dnsName, QueryType.TXT);

if (!result.Answers.TxtRecords().SelectMany(x => x.Text).Contains(challenge.TxtValue))
{
challenge.SetStatus(DnsChallenge.DnsChallengeStatus.Pending);
dnsResolved = false;
}
}

return dnsResolved;
}
}
}

0 comments on commit 1140995

Please sign in to comment.