Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #491 - Add support for DomeneShop #492

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions KeyVault.Acmebot/Options/AcmebotOptions.cs
Original file line number Diff line number Diff line change
@@ -36,6 +36,8 @@ public class AcmebotOptions

public CustomDnsOptions CustomDns { get; set; }

public DomeneShopOptions DomeneShop { get; set; }

public DnsMadeEasyOptions DnsMadeEasy { get; set; }

public GandiOptions Gandi { get; set; }
8 changes: 8 additions & 0 deletions KeyVault.Acmebot/Options/DomeneShopOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace KeyVault.Acmebot.Options;

public class DomeneShopOptions
{
public int PropagationSeconds { get; set; } = 180;
public string ApiKeyUser { get; set; }
public string ApiKeyPassword { get; set; }
}
113 changes: 113 additions & 0 deletions KeyVault.Acmebot/Providers/DomeneShopProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

using KeyVault.Acmebot.Internal;
using KeyVault.Acmebot.Options;

namespace KeyVault.Acmebot.Providers;

public class DomeneShopProvider : IDnsProvider
{
private const string Endpoint = @"https://api.domeneshop.no/v0/";
private const string AuthorizationHeader = @"Authorization";

public DomeneShopProvider(DomeneShopOptions options)
{
_httpClient = new HttpClient
{
BaseAddress = new Uri(Endpoint)
};

_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.DefaultRequestHeaders.TryAddWithoutValidation(AuthorizationHeader, CreateAuthorizationHeader(options));

PropagationSeconds = options.PropagationSeconds;
}

private string CreateAuthorizationHeader(DomeneShopOptions options)
{
if (string.IsNullOrWhiteSpace(options.ApiKeyUser)) { throw new ArgumentNullException(nameof(options.ApiKeyUser)); }
if (string.IsNullOrWhiteSpace(options.ApiKeyPassword)) { throw new ArgumentNullException(nameof(options.ApiKeyPassword)); }
return "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{options.ApiKeyUser}:{options.ApiKeyPassword}"));
}

private readonly HttpClient _httpClient;

public int PropagationSeconds { get; }

public async Task<IReadOnlyList<DnsZone>> ListZonesAsync()
{
var response = await _httpClient.GetAsync("domains");

response.EnsureSuccessStatusCode();

var domains = await response.Content.ReadAsAsync<DomeneShopDomain[]>();
var zones = domains.Select(d =>
new DnsZone(this)
{
Id = d.Id,
Name = d.Domain,
NameServers = d.Nameservers
})
.ToArray();

return zones;
}

public async Task CreateTxtRecordAsync(DnsZone zone, string relativeRecordName, IEnumerable<string> values)
{
var response = await _httpClient.PostAsync($"domains/{zone.Id}/dns", new { host = relativeRecordName, type = "TXT", ttl = 60, data = string.Join(",", values) });

response.EnsureSuccessStatusCode();
}

public async Task DeleteTxtRecordAsync(DnsZone zone, string relativeRecordName)
{
DomeneShopDomainRecord record = await FindRecordAsync(zone, relativeRecordName);

if (record != null)
{
var response = await _httpClient.DeleteAsync($"domains/{zone.Id}/dns/{record.Id}");

response.EnsureSuccessStatusCode();
}
}

private async Task<DomeneShopDomainRecord> FindRecordAsync(DnsZone zone, string relativeRecordName)
{
IEnumerable<DomeneShopDomainRecord> records = await ListRecordsAsync(zone);
return records.Where(r => r.Host == relativeRecordName).FirstOrDefault();
}

private async Task<IEnumerable<DomeneShopDomainRecord>> ListRecordsAsync(DnsZone zone)
{
var response = await _httpClient.GetAsync($"domains/{zone.Id}/dns");

response.EnsureSuccessStatusCode();

var records = await response.Content.ReadAsAsync<DomeneShopDomainRecord[]>();

return records;
}

public class DomeneShopDomain
{
public string Domain { get; set; }
public string Id { get; set; }
public string[] Nameservers { get; set; }
}

public class DomeneShopDomainRecord
{
public string Id { get; set; }
public string Host { get; set; }
public int Ttl { get; set; }
public string Type { get; set; }
public string Data { get; set; }
}
}
1 change: 1 addition & 0 deletions KeyVault.Acmebot/Startup.cs
Original file line number Diff line number Diff line change
@@ -81,6 +81,7 @@ public override void Configure(IFunctionsHostBuilder builder)
dnsProviders.TryAdd(options.AzureDns, o => new AzureDnsProvider(o, environment));
dnsProviders.TryAdd(options.Cloudflare, o => new CloudflareProvider(o));
dnsProviders.TryAdd(options.CustomDns, o => new CustomDnsProvider(o));
dnsProviders.TryAdd(options.DomeneShop, o => new DomeneShopProvider(o));
dnsProviders.TryAdd(options.DnsMadeEasy, o => new DnsMadeEasyProvider(o));
dnsProviders.TryAdd(options.Gandi, o => new GandiProvider(o));
dnsProviders.TryAdd(options.GoDaddy, o => new GoDaddyProvider(o));