From dbcda1685a62123b0885bb459ff048ecece7ab38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=BCller?= Date: Thu, 6 Nov 2025 20:51:51 +0100 Subject: [PATCH] Added DNS firewall Reverse DNS --- .../Cloudflare.Dns/DnsFirewallExtensions.cs | 42 +++++++ ...dateDNSFirewallClusterReverseDNSRequest.cs | 8 ++ src/Extensions/Cloudflare.Dns/README.md | 8 ++ ...dateDNSFirewallClusterReverseDNSRequest.cs | 34 +++++ .../Responses/ReverseDnsResponse.cs | 14 +++ .../ShowDNSFirewallClusterReverseDNSTest.cs | 79 ++++++++++++ .../UpdateDNSFirewallClusterReverseDNSTest.cs | 118 ++++++++++++++++++ 7 files changed, 303 insertions(+) create mode 100644 src/Extensions/Cloudflare.Dns/Internals/InternalUpdateDNSFirewallClusterReverseDNSRequest.cs create mode 100644 src/Extensions/Cloudflare.Dns/Requests/UpdateDNSFirewallClusterReverseDNSRequest.cs create mode 100644 src/Extensions/Cloudflare.Dns/Responses/ReverseDnsResponse.cs create mode 100644 test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/ShowDNSFirewallClusterReverseDNSTest.cs create mode 100644 test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/UpdateDNSFirewallClusterReverseDNSTest.cs diff --git a/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs b/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs index cc7332b..287ce6e 100644 --- a/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs +++ b/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs @@ -9,6 +9,8 @@ namespace AMWD.Net.Api.Cloudflare.Dns /// public static class DnsFirewallExtensions { + #region DNS Firewall + /// /// List DNS Firewall clusters for an account. /// @@ -150,5 +152,45 @@ namespace AMWD.Net.Api.Cloudflare.Dns return client.DeleteAsync($"/accounts/{accountId}/dns_firewall/{dnsFirewallId}", null, cancellationToken); } + + #endregion DNS Firewall + + #region Reverse DNS + + /// + /// Show reverse DNS configuration (PTR records) for a DNS Firewall cluster + /// + /// The instance. + /// The account identifier. + /// The DNS firewall identifier. + /// A cancellation token used to propagate notification that this operation should be canceled. + public static Task> ShowDNSFirewallClusterReverseDNS(this ICloudflareClient client, string accountId, string dnsFirewallId, CancellationToken cancellationToken = default) + { + accountId.ValidateCloudflareId(); + dnsFirewallId.ValidateCloudflareId(); + + return client.GetAsync($"/accounts/{accountId}/dns_firewall/{dnsFirewallId}/reverse_dns", null, cancellationToken); + } + + /// + /// Update reverse DNS configuration (PTR records) for a DNS Firewall cluster. + /// + /// The instance. + /// The request. + /// A cancellation token used to propagate notification that this operation should be canceled. + public static Task> UpdateDNSFirewallClusterReverseDNS(this ICloudflareClient client, UpdateDNSFirewallClusterReverseDNSRequest request, CancellationToken cancellationToken = default) + { + request.AccountId.ValidateCloudflareId(); + request.DnsFirewallId.ValidateCloudflareId(); + + var req = new InternalUpdateDNSFirewallClusterReverseDNSRequest + { + Ptr = request.ReverseDNS + }; + + return client.PatchAsync($"/accounts/{request.AccountId}/dns_firewall/{request.DnsFirewallId}/reverse_dns", req, cancellationToken); + } + + #endregion Reverse DNS } } diff --git a/src/Extensions/Cloudflare.Dns/Internals/InternalUpdateDNSFirewallClusterReverseDNSRequest.cs b/src/Extensions/Cloudflare.Dns/Internals/InternalUpdateDNSFirewallClusterReverseDNSRequest.cs new file mode 100644 index 0000000..4e61614 --- /dev/null +++ b/src/Extensions/Cloudflare.Dns/Internals/InternalUpdateDNSFirewallClusterReverseDNSRequest.cs @@ -0,0 +1,8 @@ +namespace AMWD.Net.Api.Cloudflare.Dns.Internals +{ + internal class InternalUpdateDNSFirewallClusterReverseDNSRequest + { + [JsonProperty("ptr")] + public IReadOnlyDictionary? Ptr { get; set; } + } +} diff --git a/src/Extensions/Cloudflare.Dns/README.md b/src/Extensions/Cloudflare.Dns/README.md index 1a75866..2292e74 100644 --- a/src/Extensions/Cloudflare.Dns/README.md +++ b/src/Extensions/Cloudflare.Dns/README.md @@ -122,6 +122,13 @@ This package contains the feature set of the _DNS_ section of the Cloudflare API - [Delete DNS Firewall Cluster](https://developers.cloudflare.com/api/resources/dns_firewall/methods/delete/) +#### [Reverse DNS] + +- [Show DNS Firewall Cluster Reverse DNS](https://developers.cloudflare.com/api/resources/dns_firewall/subresources/reverse_dns/methods/get/) +- [Update DNS Firewall Cluster Reverse DNS](https://developers.cloudflare.com/api/resources/dns_firewall/subresources/reverse_dns/methods/edit/) + + + --- Published under MIT License (see [choose a license]) @@ -146,3 +153,4 @@ Published under MIT License (see [choose a license]) [Peers]: https://developers.cloudflare.com/api/resources/dns/subresources/zone_transfers/subresources/peers/ [TSIGs]: https://developers.cloudflare.com/api/resources/dns/subresources/zone_transfers/subresources/tsigs/ [DNS Firewall]: https://developers.cloudflare.com/api/resources/dns_firewall/ + [Reverse DNS]: https://developers.cloudflare.com/api/resources/dns_firewall/subresources/reverse_dns/ diff --git a/src/Extensions/Cloudflare.Dns/Requests/UpdateDNSFirewallClusterReverseDNSRequest.cs b/src/Extensions/Cloudflare.Dns/Requests/UpdateDNSFirewallClusterReverseDNSRequest.cs new file mode 100644 index 0000000..e72b2ed --- /dev/null +++ b/src/Extensions/Cloudflare.Dns/Requests/UpdateDNSFirewallClusterReverseDNSRequest.cs @@ -0,0 +1,34 @@ +namespace AMWD.Net.Api.Cloudflare.Dns +{ + /// + /// Represents a request to update the reverse DNS configuration for a DNS firewall cluster. + /// + public class UpdateDNSFirewallClusterReverseDNSRequest + { + /// + /// Initializes a new instance of the class. + /// + /// The account identifier. + /// The DNS firewall cluster identifier. + public UpdateDNSFirewallClusterReverseDNSRequest(string accountId, string dnsFirewallId) + { + AccountId = accountId; + DnsFirewallId = dnsFirewallId; + } + + /// + /// The account identifier. + /// + public string AccountId { get; set; } + + /// + /// The DNS firewall cluster identifier. + /// + public string DnsFirewallId { get; set; } + + /// + /// Map of cluster IP addresses to PTR record contents. + /// + public IReadOnlyDictionary? ReverseDNS { get; set; } + } +} diff --git a/src/Extensions/Cloudflare.Dns/Responses/ReverseDnsResponse.cs b/src/Extensions/Cloudflare.Dns/Responses/ReverseDnsResponse.cs new file mode 100644 index 0000000..3aed691 --- /dev/null +++ b/src/Extensions/Cloudflare.Dns/Responses/ReverseDnsResponse.cs @@ -0,0 +1,14 @@ +namespace AMWD.Net.Api.Cloudflare.Dns +{ + /// + /// Represents the response of reverse DNS records of a firewall cluster. + /// + public class ReverseDnsResponse + { + /// + /// Map of cluster IP addresses to PTR record contents. + /// + [JsonProperty("ptr")] + public IReadOnlyDictionary? ReverseDNS { get; set; } + } +} diff --git a/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/ShowDNSFirewallClusterReverseDNSTest.cs b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/ShowDNSFirewallClusterReverseDNSTest.cs new file mode 100644 index 0000000..07168c1 --- /dev/null +++ b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/ShowDNSFirewallClusterReverseDNSTest.cs @@ -0,0 +1,79 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AMWD.Net.Api.Cloudflare; +using AMWD.Net.Api.Cloudflare.Dns; +using Moq; + +namespace Cloudflare.Dns.Tests.DnsFirewallExtensions.ReverseDns +{ + [TestClass] + public class ShowDNSFirewallClusterReverseDNSTest + { + public TestContext TestContext { get; set; } + + private const string AccountId = "023e105f4ecef8ad9ca31a8372d0c353"; + private const string ClusterId = "023e105f4ecef8ad9ca31a8372d0c355"; + + private Mock _clientMock; + private CloudflareResponse _response; + private List<(string RequestPath, IQueryParameterFilter QueryFilter)> _callbacks; + + [TestInitialize] + public void Initialize() + { + _callbacks = []; + + _response = new CloudflareResponse + { + Success = true, + Messages = [new ResponseInfo(1000, "Message 1")], + Errors = [new ResponseInfo(1000, "Error 1")], + Result = new ReverseDnsResponse + { + ReverseDNS = new Dictionary + { + ["192.0.2.1"] = "ptr.example.com" + } + } + }; + } + + [TestMethod] + public async Task ShouldShowDnsFirewallClusterReverseDns() + { + // Arrange + var client = GetClient(); + + // Act + var response = await client.ShowDNSFirewallClusterReverseDNS(AccountId, ClusterId, TestContext.CancellationToken); + + // Assert + Assert.IsNotNull(response); + Assert.IsTrue(response.Success); + Assert.IsNotNull(response.Result); + Assert.IsTrue(response.Result.ReverseDNS?.ContainsKey("192.0.2.1") ?? false); + Assert.AreEqual("ptr.example.com", response.Result.ReverseDNS!["192.0.2.1"]); + + Assert.HasCount(1, _callbacks); + + var (requestPath, queryFilter) = _callbacks.First(); + Assert.AreEqual($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", requestPath); + Assert.IsNull(queryFilter); + + _clientMock.Verify(m => m.GetAsync($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", null, TestContext.CancellationToken), Times.Once); + _clientMock.VerifyNoOtherCalls(); + } + + private ICloudflareClient GetClient() + { + _clientMock = new Mock(); + _clientMock + .Setup(m => m.GetAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((requestPath, queryFilter, _) => _callbacks.Add((requestPath, queryFilter))) + .ReturnsAsync(() => _response); + + return _clientMock.Object; + } + } +} diff --git a/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/UpdateDNSFirewallClusterReverseDNSTest.cs b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/UpdateDNSFirewallClusterReverseDNSTest.cs new file mode 100644 index 0000000..8508141 --- /dev/null +++ b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/UpdateDNSFirewallClusterReverseDNSTest.cs @@ -0,0 +1,118 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AMWD.Net.Api.Cloudflare; +using AMWD.Net.Api.Cloudflare.Dns; +using AMWD.Net.Api.Cloudflare.Dns.Internals; +using Moq; + +namespace Cloudflare.Dns.Tests.DnsFirewallExtensions.ReverseDns +{ + [TestClass] + public class UpdateDNSFirewallClusterReverseDNSTest + { + public TestContext TestContext { get; set; } + + private const string AccountId = "023e105f4ecef8ad9ca31a8372d0c353"; + private const string ClusterId = "023e105f4ecef8ad9ca31a8372d0c355"; + + private Mock _clientMock; + private CloudflareResponse _response; + private List<(string RequestPath, InternalUpdateDNSFirewallClusterReverseDNSRequest Request)> _callbacks; + private UpdateDNSFirewallClusterReverseDNSRequest _request; + + [TestInitialize] + public void Initialize() + { + _callbacks = []; + + _response = new CloudflareResponse + { + Success = true, + Messages = [new ResponseInfo(1000, "Message 1")], + Errors = [new ResponseInfo(1000, "Error 1")], + Result = new ReverseDnsResponse + { + ReverseDNS = new Dictionary + { + ["10.0.0.1"] = "ptr1.example.com" + } + } + }; + + _request = new UpdateDNSFirewallClusterReverseDNSRequest(AccountId, ClusterId) + { + ReverseDNS = new Dictionary + { + ["10.0.0.1"] = "ptr1.example.com" + } + }; + } + + [TestMethod] + public async Task ShouldUpdateDnsFirewallClusterReverseDns() + { + // Arrange + var client = GetClient(); + + // Act + var response = await client.UpdateDNSFirewallClusterReverseDNS(_request, TestContext.CancellationToken); + + // Assert + Assert.IsNotNull(response); + Assert.IsTrue(response.Success); + Assert.IsNotNull(response.Result); + Assert.IsTrue(response.Result.ReverseDNS?.ContainsKey("10.0.0.1") ?? false); + Assert.AreEqual("ptr1.example.com", response.Result.ReverseDNS!["10.0.0.1"]); + + Assert.HasCount(1, _callbacks); + + var (requestPath, request) = _callbacks.First(); + Assert.AreEqual($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", requestPath); + + Assert.IsNotNull(request); + Assert.IsNotNull(request.Ptr); + Assert.IsTrue(request.Ptr.ContainsKey("10.0.0.1")); + Assert.AreEqual("ptr1.example.com", request.Ptr["10.0.0.1"]); + + _clientMock.Verify(m => m.PatchAsync($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", It.IsAny(), TestContext.CancellationToken), Times.Once); + _clientMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public async Task ShouldUpdateDnsFirewallClusterReverseDnsMinimalSet() + { + // Arrange + _request = new UpdateDNSFirewallClusterReverseDNSRequest(AccountId, ClusterId); + var client = GetClient(); + + // Act + var response = await client.UpdateDNSFirewallClusterReverseDNS(_request, TestContext.CancellationToken); + + // Assert + Assert.IsNotNull(response); + Assert.IsTrue(response.Success); + Assert.AreEqual(_response.Result, response.Result); + + Assert.HasCount(1, _callbacks); + + var (requestPath, request) = _callbacks.First(); + Assert.AreEqual($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", requestPath); + Assert.IsNull(request.Ptr); + + _clientMock.Verify(m => m.PatchAsync($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", It.IsAny(), TestContext.CancellationToken), Times.Once); + _clientMock.VerifyNoOtherCalls(); + } + + private ICloudflareClient GetClient() + { + _clientMock = new Mock(); + _clientMock + .Setup(m => m.PatchAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((requestPath, request, _) => _callbacks.Add((requestPath, request))) + .ReturnsAsync(() => _response); + + return _clientMock.Object; + } + } +}