Skip to content

Commit 998a563

Browse files
dns updates
1 parent a27abb5 commit 998a563

File tree

5 files changed

+176
-72
lines changed

5 files changed

+176
-72
lines changed

AcmeCaPlugin/AcmeCaPlugin.cs

Lines changed: 32 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
// Copyright 2025 Keyfactor
22
// Licensed under the Apache License, Version 2.0
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Threading.Tasks;
3+
using ACMESharp.Authorizations;
4+
using ACMESharp.Protocol;
5+
using ACMESharp.Protocol.Resources;
66
using Keyfactor.AnyGateway.Extensions;
7+
using Keyfactor.Extensions.CAPlugin.Acme.Clients.Acme;
8+
using Keyfactor.Extensions.CAPlugin.Acme.Clients.DNS;
79
using Keyfactor.Logging;
810
using Keyfactor.PKI.Enums.EJBCA;
911
using Microsoft.Extensions.Logging;
1012
using Newtonsoft.Json;
11-
using System.Linq;
12-
using System.Net.Http;
13-
using ACMESharp.Authorizations;
14-
using Keyfactor.Extensions.CAPlugin.Acme.Clients.Acme;
15-
using System.Threading;
16-
using ACMESharp.Protocol.Resources;
17-
using ACMESharp.Protocol;
18-
using System.Text;
19-
using Keyfactor.Extensions.CAPlugin.Acme.Clients.DNS;
20-
using System.Text.RegularExpressions;
2113
using Org.BouncyCastle.Asn1;
2214
using Org.BouncyCastle.Asn1.Pkcs;
2315
using Org.BouncyCastle.Asn1.X509;
2416
using Org.BouncyCastle.Pkcs;
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Linq;
20+
using System.Net.Http;
21+
using System.Text;
22+
using System.Text.RegularExpressions;
23+
using System.Threading;
24+
using System.Threading.Tasks;
25+
using static Org.BouncyCastle.Math.EC.ECCurve;
2526

2627
namespace Keyfactor.Extensions.CAPlugin.Acme
2728
{
@@ -62,6 +63,7 @@ public class AcmeCaPlugin : IAnyCAPlugin
6263
{
6364
private static readonly ILogger _logger = LogHandler.GetClassLogger<AcmeCaPlugin>();
6465
private IAnyCAPluginConfigProvider Config { get; set; }
66+
private IDomainValidator _domainValidator;
6567

6668
// Constants for better maintainability
6769
private const string DEFAULT_PRODUCT_ID = "default";
@@ -76,6 +78,8 @@ public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDa
7678
{
7779
_logger.MethodEntry();
7880
Config = configProvider ?? throw new ArgumentNullException(nameof(configProvider));
81+
_domainValidator = new Dns01DomainValidator();
82+
_domainValidator.Initialize(new DomainValidatorConfigProvider(configProvider.CAConnectionData));
7983
_logger.MethodExit();
8084
}
8185

@@ -230,6 +234,7 @@ public async Task<EnrollmentResult> Enroll(
230234
{
231235
_logger.MethodEntry();
232236

237+
233238
if (string.IsNullOrWhiteSpace(csr))
234239
throw new ArgumentException("CSR cannot be null or empty", nameof(csr));
235240
if (string.IsNullOrWhiteSpace(subject))
@@ -471,74 +476,47 @@ private async Task ProcessAuthorizations(AcmeClient acmeClient, OrderDetails ord
471476
var dnsVerifier = new DnsVerificationHelper(_logger, config.DnsVerificationServer);
472477
var pendingChallenges = new List<(Authorization authz, Challenge challenge, Dns01ChallengeValidationDetails validation)>();
473478

474-
// First pass: Create all DNS records
479+
// First pass: Create all DNS records using IDomainValidator
475480
foreach (var authzUrl in payload.Authorizations)
476481
{
477482
var authz = await acmeClient.GetAuthorizationAsync(authzUrl);
478483

479-
// Skip if authorization is already valid (cached)
480484
if (authz.Status == "valid")
481485
{
482486
_logger.LogInformation("Using cached authorization for {Domain}", authz.Identifier.Value);
483487
continue;
484488
}
485489

486-
// Find DNS-01 challenge
487490
var challenge = authz.Challenges.FirstOrDefault(c => c.Type == DNS_CHALLENGE_TYPE);
488491
if (challenge == null)
489492
throw new InvalidOperationException($"{DNS_CHALLENGE_TYPE} challenge not available");
490493

491-
// Decode challenge validation details
492494
var validation = acmeClient.DecodeChallengeValidation(authz, challenge) as Dns01ChallengeValidationDetails;
493495
if (validation == null)
494496
throw new InvalidOperationException($"Failed to decode {DNS_CHALLENGE_TYPE} challenge validation details");
495497

496-
// Create DNS record (will throw exception with details if it fails)
497-
var dnsProvider = DnsProviderFactory.Create(config, _logger);
498-
await dnsProvider.CreateRecordAsync(validation.DnsRecordName, validation.DnsRecordValue);
498+
// Use IDomainValidator instead of DnsProviderFactory directly
499+
var result = await _domainValidator.StageValidation(
500+
validation.DnsRecordName,
501+
validation.DnsRecordValue,
502+
CancellationToken.None);
503+
504+
if (!result.Success)
505+
throw new InvalidOperationException($"Failed to stage DNS validation: {result.ErrorMessage}");
499506

500507
_logger.LogInformation("Created DNS record {RecordName} for domain {Domain}",
501508
validation.DnsRecordName, authz.Identifier.Value);
502509

503510
pendingChallenges.Add((authz, challenge, validation));
504511
}
505512

506-
// Second pass: Wait for DNS propagation and submit challenges
513+
// Second pass: Wait for propagation and submit challenges
514+
// ... rest of your existing code ...
515+
516+
// Optional: Cleanup after challenges complete
507517
foreach (var (authz, challenge, validation) in pendingChallenges)
508518
{
509-
// Skip external DNS verification for Infoblox since it cannot ping external DNS providers
510-
bool isInfoblox = config.DnsProvider?.Trim().Equals("infoblox", StringComparison.OrdinalIgnoreCase) ?? false;
511-
512-
if (isInfoblox)
513-
{
514-
_logger.LogInformation("Skipping external DNS propagation check for Infoblox provider for {Domain}. Adding short delay...", authz.Identifier.Value);
515-
// Add a short delay to allow Infoblox to process the record internally
516-
await Task.Delay(TimeSpan.FromSeconds(5));
517-
}
518-
else
519-
{
520-
_logger.LogInformation("Waiting for DNS propagation for {Domain}...", authz.Identifier.Value);
521-
522-
// Wait for DNS propagation with verification
523-
var propagated = await dnsVerifier.WaitForDnsPropagationAsync(
524-
validation.DnsRecordName,
525-
validation.DnsRecordValue,
526-
minimumServers: 3 // Require at least 3 DNS servers to confirm
527-
);
528-
529-
if (!propagated)
530-
{
531-
_logger.LogWarning("DNS record may not have fully propagated for {Domain}. Proceeding anyway...",
532-
authz.Identifier.Value);
533-
534-
// Optional: Add a final delay as fallback
535-
await Task.Delay(TimeSpan.FromSeconds(30));
536-
}
537-
}
538-
539-
// Submit challenge response
540-
_logger.LogInformation("Submitting challenge for {Domain}", authz.Identifier.Value);
541-
await acmeClient.AnswerChallengeAsync(challenge);
519+
await _domainValidator.CleanupValidation(validation.DnsRecordName, CancellationToken.None);
542520
}
543521
}
544522

AcmeCaPlugin/AcmeCaPlugin.csproj

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,26 @@
99
<AssemblyName>AcmeCaPlugin</AssemblyName>
1010
</PropertyGroup>
1111
<ItemGroup>
12-
<PackageReference Include="ACMESharpCore" Version="2.2.0.148"/>
13-
<PackageReference Include="Autofac" Version="8.3.0"/>
14-
<PackageReference Include="AWSSDK.Route53" Version="4.0.1"/>
15-
<PackageReference Include="Azure.Identity" Version="1.14.0"/>
16-
<PackageReference Include="Azure.ResourceManager.Cdn" Version="1.4.0"/>
17-
<PackageReference Include="Azure.ResourceManager.Dns" Version="1.1.1"/>
18-
<PackageReference Include="DnsClient" Version="1.8.0"/>
19-
<PackageReference Include="ARSoft.Tools.Net" Version="3.6.0"/>
20-
<PackageReference Include="Google.Apis.Dns.v1" Version="1.69.0.3753"/>
21-
<PackageReference Include="Keyfactor.AnyGateway.IAnyCAPlugin" Version="3.0.0"/>
22-
<PackageReference Include="Keyfactor.Logging" Version="1.1.1"/>
23-
<PackageReference Include="Keyfactor.PKI" Version="5.5.0"/>
24-
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5"/>
25-
<PackageReference Include="Nager.PublicSuffix" Version="3.5.0"/>
26-
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
27-
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="9.0.5"/>
28-
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="9.0.5"/>
12+
<PackageReference Include="ACMESharpCore" Version="2.2.0.148" />
13+
<PackageReference Include="Autofac" Version="8.3.0" />
14+
<PackageReference Include="AWSSDK.Core" Version="4.0.3.11" />
15+
<PackageReference Include="AWSSDK.Route53" Version="4.0.8.8" />
16+
<PackageReference Include="Azure.Identity" Version="1.14.0" />
17+
<PackageReference Include="Azure.ResourceManager.Cdn" Version="1.4.0" />
18+
<PackageReference Include="Azure.ResourceManager.Dns" Version="1.1.1" />
19+
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
20+
<PackageReference Include="DnsClient" Version="1.8.0" />
21+
<PackageReference Include="ARSoft.Tools.Net" Version="3.6.0" />
22+
<PackageReference Include="Google.Apis.Dns.v1" Version="1.69.0.3753" />
23+
<PackageReference Include="Keyfactor.AnyGateway.IAnyCAPlugin" Version="3.3.0-PRERELEASE-78770-979f582005" />
24+
<PackageReference Include="Keyfactor.Logging" Version="1.1.1" />
25+
<PackageReference Include="Keyfactor.PKI" Version="5.5.0" />
26+
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
27+
<PackageReference Include="Nager.PublicSuffix" Version="3.5.0" />
28+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
29+
<PackageReference Include="System.Drawing.Common" Version="10.0.2" />
30+
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="9.0.5" />
31+
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="9.0.5" />
2932
</ItemGroup>
3033
<ItemGroup>
3134
<None Update="manifest.json">
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using Keyfactor.AnyGateway.Extensions;
2+
using Keyfactor.Extensions.CAPlugin.Acme.Clients.DNS;
3+
using Keyfactor.Logging;
4+
using Microsoft.Extensions.Logging;
5+
using Newtonsoft.Json;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace Keyfactor.Extensions.CAPlugin.Acme
12+
{
13+
public class Dns01DomainValidator : IDomainValidator
14+
{
15+
private static readonly ILogger _logger = LogHandler.GetClassLogger<Dns01DomainValidator>();
16+
private AcmeClientConfig _config;
17+
private IDnsProvider _dnsProvider;
18+
19+
public void Initialize(IDomainValidatorConfigProvider configProvider)
20+
{
21+
if (configProvider?.DomainValidationConfiguration == null)
22+
throw new ArgumentNullException(nameof(configProvider));
23+
24+
var raw = JsonConvert.SerializeObject(configProvider.DomainValidationConfiguration);
25+
_config = JsonConvert.DeserializeObject<AcmeClientConfig>(raw);
26+
27+
// Create the DNS provider using your existing factory
28+
_dnsProvider = DnsProviderFactory.Create(_config, _logger);
29+
30+
_logger.LogInformation("Dns01DomainValidator initialized with provider: {Provider}",
31+
_config.DnsProvider ?? "default");
32+
}
33+
34+
public async Task<DomainValidationResult> StageValidation(string key, string value, CancellationToken cancellationToken)
35+
{
36+
_logger.LogInformation("Staging DNS-01 validation: {Key} -> {Value}", key, value);
37+
38+
try
39+
{
40+
var success = await _dnsProvider.CreateRecordAsync(key, value);
41+
42+
return new DomainValidationResult
43+
{
44+
Success = success,
45+
ErrorMessage = success ? null : $"Failed to create DNS TXT record for {key}"
46+
};
47+
}
48+
catch (Exception ex)
49+
{
50+
_logger.LogError(ex, "Failed to stage DNS validation for {Key}", key);
51+
return new DomainValidationResult
52+
{
53+
Success = false,
54+
ErrorMessage = ex.Message
55+
};
56+
}
57+
}
58+
59+
public async Task<DomainValidationResult> CleanupValidation(string key, CancellationToken cancellationToken)
60+
{
61+
_logger.LogInformation("Cleaning up DNS-01 validation: {Key}", key);
62+
63+
try
64+
{
65+
// Use the overload without value if available, or pass empty string
66+
var success = await _dnsProvider.DeleteRecordAsync(key);
67+
68+
return new DomainValidationResult
69+
{
70+
Success = success,
71+
ErrorMessage = success ? null : $"Failed to delete DNS TXT record for {key}"
72+
};
73+
}
74+
catch (Exception ex)
75+
{
76+
_logger.LogError(ex, "Failed to cleanup DNS validation for {Key}", key);
77+
return new DomainValidationResult
78+
{
79+
Success = false,
80+
ErrorMessage = ex.Message
81+
};
82+
}
83+
}
84+
85+
public Task ValidateConfiguration(Dictionary<string, object> configuration)
86+
{
87+
// Reuse existing validation logic or add specific checks
88+
if (configuration == null)
89+
throw new ArgumentNullException(nameof(configuration));
90+
91+
return Task.CompletedTask;
92+
}
93+
94+
public Dictionary<string, PropertyConfigInfo> GetDomainValidatorAnnotations()
95+
{
96+
// Return DNS-related annotations from your existing config
97+
// Or return a subset specific to domain validation
98+
return AcmeCaPluginConfig.GetPluginAnnotations();
99+
}
100+
101+
public string GetValidationType() => "dns-01";
102+
}
103+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Keyfactor.AnyGateway.Extensions;
2+
using System.Collections.Generic;
3+
4+
namespace Keyfactor.Extensions.CAPlugin.Acme
5+
{
6+
public class DomainValidatorConfigProvider : IDomainValidatorConfigProvider
7+
{
8+
public Dictionary<string, object> DomainValidationConfiguration { get; }
9+
10+
public DomainValidatorConfigProvider(Dictionary<string, object> config)
11+
{
12+
DomainValidationConfiguration = config;
13+
}
14+
}
15+
}

TestProgram/TestProgram.csproj

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Keyfactor.AnyGateway.IAnyCAPlugin" Version="3.0.0" />
11+
<PackageReference Include="AWSSDK.Core" Version="4.0.3.11" />
12+
<PackageReference Include="AWSSDK.Route53" Version="4.0.8.8" />
13+
<PackageReference Include="Azure.Identity" Version="1.14.0" />
14+
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
15+
<PackageReference Include="Keyfactor.AnyGateway.IAnyCAPlugin" Version="3.3.0-PRERELEASE-78770-979f582005" />
1216
<PackageReference Include="Keyfactor.Logging" Version="1.1.1" />
1317
<PackageReference Include="Keyfactor.PKI" Version="5.5.0" />
1418
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.5" />
19+
<PackageReference Include="System.Drawing.Common" Version="10.0.2" />
1520
</ItemGroup>
1621

1722
<ItemGroup>

0 commit comments

Comments
 (0)