Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Acmebot.Acme.Tests/Acmebot.Acme.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down
1 change: 1 addition & 0 deletions src/Acmebot.Acme/Acmebot.Acme.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

</Project>
6 changes: 2 additions & 4 deletions src/Acmebot.App/Acme/AcmeClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,7 @@ public async Task<AcmeClientContext> CreateClientAsync()
private string[] GetContacts() => [$"mailto:{_options.Contacts}"];

private static bool ContactsEqual(IReadOnlyList<string>? actualContacts, IReadOnlyList<string> expectedContacts)
{
return actualContacts is not null && actualContacts.SequenceEqual(expectedContacts, StringComparer.Ordinal);
}
=> actualContacts is not null && actualContacts.SequenceEqual(expectedContacts, StringComparer.Ordinal);

private TState? LoadState<TState>(string path)
{
Expand All @@ -139,7 +137,7 @@ private void SaveState<TState>(TState value, string path)
var fullPath = ResolveStateFullPath(path);
var directoryPath = Path.GetDirectoryName(fullPath);

if (!Directory.Exists(directoryPath))
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
Expand Down
5 changes: 3 additions & 2 deletions src/Acmebot.App/Acmebot.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

<ItemGroup>
Expand All @@ -23,11 +24,11 @@
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.23.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.51.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.50.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.16.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.16.2" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.3.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" />
<PackageReference Include="Microsoft.DurableTask.Generators" Version="2.0.0-preview.1" />
<PackageReference Include="Microsoft.DurableTask.Generators" Version="2.1.0-preview.1" />
</ItemGroup>

<ItemGroup>
Expand Down
20 changes: 12 additions & 8 deletions src/Acmebot.App/Extensions/DnsProvidersExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ namespace Acmebot.App.Extensions;

internal static class DnsProvidersExtensions
{
public static async Task<IReadOnlyList<(string, IReadOnlyList<DnsZone>)>> ListAllZonesAsync(this IEnumerable<IDnsProvider> dnsProviders)
public static async Task<IReadOnlyList<(string, IReadOnlyList<DnsZone>?)>> ListAllZonesAsync(this IEnumerable<IDnsProvider> dnsProviders, CancellationToken cancellationToken = default)
{
async Task<(string, IReadOnlyList<DnsZone>)> ListDnsZones(IDnsProvider dnsProvider)
async Task<(string, IReadOnlyList<DnsZone>?)> ListDnsZones(IDnsProvider dnsProvider)
{
try
{
var dnsZones = await dnsProvider.ListZonesAsync();
var dnsZones = await dnsProvider.ListZonesAsync(cancellationToken);

return (dnsProvider.Name, dnsZones);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
return (dnsProvider.Name, null);
Expand All @@ -25,7 +29,7 @@ internal static class DnsProvidersExtensions
return zones;
}

public static async Task<IReadOnlyList<DnsZone>> ListZonesAsync(this IEnumerable<IDnsProvider> dnsProviders, string dnsProviderName)
public static async Task<IReadOnlyList<DnsZone>> ListZonesAsync(this IEnumerable<IDnsProvider> dnsProviders, string dnsProviderName, CancellationToken cancellationToken = default)
{
var dnsProvider = dnsProviders.FirstOrDefault(x => x.Name == dnsProviderName);

Expand All @@ -34,16 +38,16 @@ public static async Task<IReadOnlyList<DnsZone>> ListZonesAsync(this IEnumerable
return [];
}

var dnsZones = await dnsProvider.ListZonesAsync();
var dnsZones = await dnsProvider.ListZonesAsync(cancellationToken);

return dnsZones;
}

public static async Task<IReadOnlyList<DnsZone>> FlattenAllZonesAsync(this IEnumerable<IDnsProvider> dnsProviders)
public static async Task<IReadOnlyList<DnsZone>> FlattenAllZonesAsync(this IEnumerable<IDnsProvider> dnsProviders, CancellationToken cancellationToken = default)
{
var zones = await dnsProviders.ListAllZonesAsync();
var zones = await dnsProviders.ListAllZonesAsync(cancellationToken);

return zones.Where(x => x.Item2 is not null).SelectMany(x => x.Item2).ToArray();
return zones.Where(x => x.Item2 is not null).SelectMany(x => x.Item2!).ToArray();
}

public static void TryAdd<TOption>(this IList<IDnsProvider> dnsProviders, TOption? options, Func<TOption, IDnsProvider> factory)
Expand Down
27 changes: 0 additions & 27 deletions src/Acmebot.App/Extensions/HttpClientExtensions.cs

This file was deleted.

16 changes: 16 additions & 0 deletions src/Acmebot.App/Extensions/HttpClientJsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Net.Http.Json;

namespace Acmebot.App.Extensions;

internal static class HttpClientJsonExtensions
{
public static Task<HttpResponseMessage> DeleteAsJsonAsync<T>(this HttpClient client, string requestUri, T value, CancellationToken cancellationToken = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, requestUri)
{
Content = JsonContent.Create(value)
};

return client.SendAsync(request, cancellationToken);
}
}
13 changes: 0 additions & 13 deletions src/Acmebot.App/Extensions/HttpContentExtensions.cs

This file was deleted.

11 changes: 0 additions & 11 deletions src/Acmebot.App/Extensions/X509Certificate2Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,4 @@ internal static class X509Certificate2Extensions

return $"{keyIdentifier}.{serialNumber}";
}

public static async Task<X509Certificate2Collection> ReadAsCertificatesAsync(this HttpContent httpContent)
{
var certificateData = await httpContent.ReadAsStringAsync();

var x509Certificates = new X509Certificate2Collection();

x509Certificates.ImportFromPem(certificateData);

return x509Certificates;
}
}
4 changes: 1 addition & 3 deletions src/Acmebot.App/Functions/Http/RenewCertificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ namespace Acmebot.App.Functions.Http;
public partial class RenewCertificate(IHttpContextAccessor httpContextAccessor, ILogger<RenewCertificate> logger) : HttpFunctionBase(httpContextAccessor)
{
[Function($"{nameof(RenewCertificate)}_{nameof(Orchestrator)}")]
public async Task Orchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
public async Task Orchestrator([OrchestrationTrigger] TaskOrchestrationContext context, string certificateName)
{
var certificateName = context.GetInput<string>();

if (string.IsNullOrEmpty(certificateName))
{
return;
Expand Down
4 changes: 1 addition & 3 deletions src/Acmebot.App/Functions/Http/RevokeCertificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ namespace Acmebot.App.Functions.Http;
public partial class RevokeCertificate(IHttpContextAccessor httpContextAccessor, ILogger<RevokeCertificate> logger) : HttpFunctionBase(httpContextAccessor)
{
[Function($"{nameof(RevokeCertificate)}_{nameof(Orchestrator)}")]
public async Task Orchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
public async Task Orchestrator([OrchestrationTrigger] TaskOrchestrationContext context, string certificateName)
{
var certificateName = context.GetInput<string>();

if (string.IsNullOrEmpty(certificateName))
{
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public async Task AnswerChallenges([ActivityTrigger] IReadOnlyList<AcmeChallenge

foreach (var challengeResult in challengeResults)
{
await acmeContext.Client.AnswerChallengeAsync(acmeContext.Account, new Uri(challengeResult.Url));
await acmeContext.Client.AnswerChallengeAsync(acmeContext.Account, challengeResult.Url);
}
}

Expand All @@ -69,7 +69,7 @@ public async Task CheckIsReady([ActivityTrigger] (OrderDetails, IReadOnlyList<Ac

foreach (var challengeResult in challengeResults)
{
var challenge = (await acmeClient.GetChallengeAsync(acmeContext.Account, new Uri(challengeResult.Url))).Resource;
var challenge = (await acmeClient.GetChallengeAsync(acmeContext.Account, challengeResult.Url)).Resource;

if (challenge.Status != AcmeChallengeStatuses.Invalid || challenge.Error is null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Acmebot.App.Models;
using Acmebot.Acme.Models;
using Acmebot.App.Models;

using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
Expand All @@ -9,12 +10,11 @@ namespace Acmebot.App.Functions.Orchestration;
public partial class CertificateIssuanceOrchestrator
{
[Function(nameof(IssueCertificate))]
public async Task IssueCertificate([OrchestrationTrigger] TaskOrchestrationContext context)
public async Task IssueCertificate([OrchestrationTrigger] TaskOrchestrationContext context, CertificatePolicyItem certificatePolicyItem)
{
var logger = context.CreateReplaySafeLogger<CertificateIssuanceOrchestrator>();
var certificatePolicyItem = context.GetInput<CertificatePolicyItem>();

if (certificatePolicyItem is null)
if (string.IsNullOrEmpty(certificatePolicyItem.CertificateName))
{
return;
}
Expand All @@ -30,7 +30,7 @@ public async Task IssueCertificate([OrchestrationTrigger] TaskOrchestrationConte
var orderDetails = await context.CallOrderAsync(certificatePolicyItem.DnsNames);

// 既に確認済みの場合は Challenge をスキップする
if (orderDetails.Payload.Status != "ready")
if (orderDetails.Payload.Status != AcmeOrderStatuses.Ready)
{
// ACME DNS-01 Challenge を実行
var (challengeResults, propagationSeconds) = await context.CallDns01AuthorizationAsync((certificatePolicyItem.DnsProviderName, certificatePolicyItem.DnsAlias, orderDetails.Payload.Authorizations));
Expand All @@ -57,7 +57,7 @@ public async Task IssueCertificate([OrchestrationTrigger] TaskOrchestrationConte
orderDetails = await context.CallFinalizeOrderAsync((certificatePolicyItem, orderDetails));

// Finalize の時点でステータスが valid の時点はスキップ
if (orderDetails.Payload.Status != "valid")
if (orderDetails.Payload.Status != AcmeOrderStatuses.Valid)
{
// Finalize 後のステータスが valid になるまで 60 秒待機
orderDetails = await context.CallCheckIsValidAsync(orderDetails, TaskOptions.FromRetryPolicy(_retryPolicy));
Expand Down
31 changes: 16 additions & 15 deletions src/Acmebot.App/Functions/Orchestration/DnsChallengeActivities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ public async Task<IReadOnlyList<DnsZoneGroup>> GetAllDnsZones([ActivityTrigger]
{
var zones = await dnsProviders.ListAllZonesAsync();

return zones.Select(x => new DnsZoneGroup
{
DnsProviderName = x.Item1,
DnsZones = x.Item2.Select(xs => xs.ToDnsZoneItem()).OrderBy(xs => xs.Name).ToArray()
}).ToArray();
return zones.Where(x => x.Item2 is not null)
.Select(x => new DnsZoneGroup
{
DnsProviderName = x.Item1,
DnsZones = x.Item2!.Select(xs => xs.ToDnsZoneItem()).OrderBy(xs => xs.Name).ToArray()
}).ToArray();
}
catch
{
Expand Down Expand Up @@ -98,7 +99,7 @@ public async Task<string> Dns01Precondition([ActivityTrigger] CertificatePolicyI
}

[Function(nameof(Dns01Authorization))]
public async Task<(IReadOnlyList<AcmeChallengeResult>, int)> Dns01Authorization([ActivityTrigger] (string, string?, IReadOnlyList<Uri>) input)
public async Task<(IReadOnlyList<AcmeChallengeResult>, int)> Dns01Authorization([ActivityTrigger] (string, string?, IReadOnlyList<Uri>) input, CancellationToken cancellationToken)
{
var (dnsProviderName, dnsAlias, authorizationUrls) = input;

Expand All @@ -109,7 +110,7 @@ public async Task<string> Dns01Precondition([ActivityTrigger] CertificatePolicyI

foreach (var authorizationUrl in authorizationUrls)
{
var authorization = (await acmeClient.GetAuthorizationAsync(acmeContext.Account, authorizationUrl)).Resource;
var authorization = (await acmeClient.GetAuthorizationAsync(acmeContext.Account, authorizationUrl, cancellationToken)).Resource;

if (authorization.Status == AcmeAuthorizationStatuses.Valid)
{
Expand All @@ -127,13 +128,13 @@ public async Task<string> Dns01Precondition([ActivityTrigger] CertificatePolicyI

challengeResults.Add(new AcmeChallengeResult
{
Url = challenge.Url.OriginalString,
Url = challenge.Url,
DnsRecordName = string.IsNullOrEmpty(dnsAlias) ? challengeInstruction.RecordName.TrimEnd('.') : $"_acme-challenge.{dnsAlias}",
DnsRecordValue = challengeInstruction.RecordValue
});
}

var zones = await (string.IsNullOrEmpty(dnsProviderName) ? dnsProviders.FlattenAllZonesAsync() : dnsProviders.ListZonesAsync(dnsProviderName));
var zones = await (string.IsNullOrEmpty(dnsProviderName) ? dnsProviders.FlattenAllZonesAsync(cancellationToken) : dnsProviders.ListZonesAsync(dnsProviderName, cancellationToken));

var propagationSeconds = 0;

Expand All @@ -150,10 +151,10 @@ public async Task<string> Dns01Precondition([ActivityTrigger] CertificatePolicyI

var acmeDnsRecordName = dnsRecordName.Replace($".{zone.Name}", "", StringComparison.OrdinalIgnoreCase);

await zone.DnsProvider.DeleteTxtRecordAsync(zone, acmeDnsRecordName);
await zone.DnsProvider.CreateTxtRecordAsync(zone, acmeDnsRecordName, lookup.Select(x => x.DnsRecordValue));
await zone.DnsProvider.DeleteTxtRecordAsync(zone, acmeDnsRecordName, cancellationToken);
await zone.DnsProvider.CreateTxtRecordAsync(zone, acmeDnsRecordName, lookup.Select(x => x.DnsRecordValue), cancellationToken);

propagationSeconds = Math.Max(propagationSeconds, zone.DnsProvider.PropagationSeconds);
propagationSeconds = Math.Max(propagationSeconds, (int)zone.DnsProvider.PropagationDelay.TotalSeconds);
}

return (challengeResults, propagationSeconds);
Expand Down Expand Up @@ -192,11 +193,11 @@ public async Task CheckDnsChallenge([ActivityTrigger] IReadOnlyList<AcmeChalleng
}

[Function(nameof(CleanupDnsChallenge))]
public async Task CleanupDnsChallenge([ActivityTrigger] (string, IReadOnlyList<AcmeChallengeResult>) input)
public async Task CleanupDnsChallenge([ActivityTrigger] (string, IReadOnlyList<AcmeChallengeResult>) input, CancellationToken cancellationToken)
{
var (dnsProviderName, challengeResults) = input;

var zones = await (string.IsNullOrEmpty(dnsProviderName) ? dnsProviders.FlattenAllZonesAsync() : dnsProviders.ListZonesAsync(dnsProviderName));
var zones = await (string.IsNullOrEmpty(dnsProviderName) ? dnsProviders.FlattenAllZonesAsync(cancellationToken) : dnsProviders.ListZonesAsync(dnsProviderName, cancellationToken));

foreach (var lookup in challengeResults.ToLookup(x => x.DnsRecordName))
{
Expand All @@ -211,7 +212,7 @@ public async Task CleanupDnsChallenge([ActivityTrigger] (string, IReadOnlyList<A

var acmeDnsRecordName = dnsRecordName.Replace($".{zone.Name}", "", StringComparison.OrdinalIgnoreCase);

await zone.DnsProvider.DeleteTxtRecordAsync(zone, acmeDnsRecordName);
await zone.DnsProvider.DeleteTxtRecordAsync(zone, acmeDnsRecordName, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task Orchestrator([OrchestrationTrigger] TaskOrchestrationContext c
catch (Exception ex)
{
// 失敗した場合はログに詳細を書き出して続きを実行する
LogFailedSubOrchestration(logger, ex, certificate.Name, string.Join((string?)",", certificate.DnsNames));
LogFailedSubOrchestration(logger, ex, certificate.Name, string.Join(",", certificate.DnsNames));
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/Acmebot.App/Models/AcmeChallengeResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class AcmeChallengeResult
{
public string? Url { get; set; }
public string? DnsRecordName { get; set; }
public string? DnsRecordValue { get; set; }
public required Uri Url { get; set; }
public required string DnsRecordName { get; set; }
public required string DnsRecordValue { get; set; }
}
6 changes: 3 additions & 3 deletions src/Acmebot.App/Models/CertificateItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ namespace Acmebot.App.Models;
public class CertificateItem
{
[JsonProperty("id")]
public Uri? Id { get; set; }
public required Uri Id { get; set; }

[JsonProperty("name")]
public string? Name { get; set; }
public required string Name { get; set; }

[JsonProperty("dnsNames")]
public IReadOnlyList<string>? DnsNames { get; set; }
public required IReadOnlyList<string> DnsNames { get; set; }

[JsonProperty("dnsProviderName")]
public string? DnsProviderName { get; set; }
Expand Down
Loading
Loading