Skip to content

Commit

Permalink
Add OVH Provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Bastien Hubert committed Dec 9, 2023
1 parent 6f74c03 commit 191b1f2
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 0 deletions.
2 changes: 2 additions & 0 deletions KeyVault.Acmebot/Options/AcmebotOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class AcmebotOptions

public GoogleDnsOptions GoogleDns { get; set; }

public OVHOptions OVH { get; set; }

public Route53Options Route53 { get; set; }

public TransIpOptions TransIp { get; set; }
Expand Down
10 changes: 10 additions & 0 deletions KeyVault.Acmebot/Options/OVHOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace KeyVault.Acmebot.Options;

public class OVHOptions
{
public string ApplicationKey { get; set; }

public string ApplicationSecret { get; set; }

public string ConsumerKey { get; set; }
}
237 changes: 237 additions & 0 deletions KeyVault.Acmebot/Providers/OVHProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Net.Http;
using System.Text;
using System.Linq;
using System;
using KeyVault.Acmebot.Options;
using Newtonsoft.Json;
using System.Net;

namespace KeyVault.Acmebot.Providers;

public class OVHDnsProvider : IDnsProvider
{
public string Name => "OVH";
public int PropagationSeconds => 300;

private readonly OVHClient _client;

public OVHDnsProvider(OVHOptions options)
{
_client = new OVHClient(options.ApplicationKey, options.ApplicationSecret, options.ConsumerKey);
}
public Task CreateTxtRecordAsync(DnsZone zone, string relativeRecordName, IEnumerable<string> values)
{
return _client.AddRecordAsync(zone.Name, relativeRecordName, values);
}

public Task DeleteTxtRecordAsync(DnsZone zone, string relativeRecordName)
{
return _client.DeleteRecordAsync(zone.Name, relativeRecordName);
}
public async Task<IReadOnlyList<DnsZone>> ListZonesAsync()
{
var zones = await _client.ListZonesAsync();

return zones.Select(x => new DnsZone(this) { Id = x, Name = x }).ToArray();
}
private class OVHClient
{

public OVHClient(string applicationKey, string applicationSecret, string consumerKey)
{
_httpClient = new HttpClient();

ArgumentNullException.ThrowIfNull(applicationKey);
ArgumentNullException.ThrowIfNull(applicationSecret);
ArgumentNullException.ThrowIfNull(consumerKey);

_applicationKey = applicationKey;
_applicationSecret = applicationSecret;
_consumerKey = consumerKey;
}

private readonly HttpClient _httpClient;
private readonly string _applicationKey;
private readonly string _applicationSecret;
private readonly string _consumerKey;

public async Task<IReadOnlyList<String>> ListZonesAsync()
{
var url = "https://api.ovh.com/1.0/domain/zone";
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Get, url))
{
var time = await GetTime();
var signature = GenerateSignature(_applicationSecret, _consumerKey,
time, requestMessage.Method.Method, url);

requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
requestMessage.Headers.Add("X-Ovh-Signature", signature);
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());


var response = await _httpClient.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
var domains = await response.Content.ReadAsAsync<String[]>();
return domains;
}
}

public async Task DeleteRecordAsync(string zoneName, string relativeRecordName)
{

var recordIds = Array.Empty<string>();
var url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record";
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Get, url))
{
var time = await GetTime();
var signature = GenerateSignature(_applicationSecret, _consumerKey,
time, requestMessage.Method.Method, url);

requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
requestMessage.Headers.Add("X-Ovh-Signature", signature);
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());


var response = await _httpClient.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
recordIds = await response.Content.ReadAsAsync<String[]>();
}



foreach (var recordId in recordIds)
{
url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record/" + recordId;
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Get, url))
{
var time = await GetTime();
var signature = GenerateSignature(_applicationSecret, _consumerKey,
time, requestMessage.Method.Method, url);

requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
requestMessage.Headers.Add("X-Ovh-Signature", signature);
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());

var response = await _httpClient.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
var ovhRecord = await response.Content.ReadAsAsync<OVHRecord>();
if (ovhRecord.fieldType == "TXT" && ovhRecord.subDomain == "_acme-challenge")
{
url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record/" + recordId;
using (var requestMessage2 =
new HttpRequestMessage(HttpMethod.Delete, url))
{
var time2 = await GetTime();
var signature2 = GenerateSignature(_applicationSecret, _consumerKey,
time2, requestMessage2.Method.Method, url);

requestMessage2.Headers.Add("X-Ovh-Application", _applicationKey);
requestMessage2.Headers.Add("X-Ovh-Consumer", _consumerKey);
requestMessage2.Headers.Add("X-Ovh-Signature", signature2);
requestMessage2.Headers.Add("X-Ovh-Timestamp", time2.ToString());

var response2 = await _httpClient.SendAsync(requestMessage2);
response2.EnsureSuccessStatusCode();
}
}
}
}

}

public async Task AddRecordAsync(string zoneName, string relativeRecordName, IEnumerable<string> values)
{
var url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record";
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Post, url))
{
var body = new
{
fieldType = "TXT",
subDomain = relativeRecordName,
target = values.First(),
ttl = 300
};
var bodyString = JsonConvert.SerializeObject(body);
requestMessage.Content = new StringContent(bodyString, Encoding.UTF8, "application/json");
var time = await GetTime();
var signature = GenerateSignature(_applicationSecret, _consumerKey,
time, requestMessage.Method.Method, url, bodyString);

requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
requestMessage.Headers.Add("X-Ovh-Signature", signature);
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());


var response = await _httpClient.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
}
url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/refresh";
using (var requestMessage =
new HttpRequestMessage(HttpMethod.Post, url))
{
var time = await GetTime();
var signature = GenerateSignature(_applicationSecret, _consumerKey,
time, requestMessage.Method.Method, url);

requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
requestMessage.Headers.Add("X-Ovh-Signature", signature);
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());


var responseRefresh = await _httpClient.SendAsync(requestMessage);
responseRefresh.EnsureSuccessStatusCode();
}

}


private string GenerateSignature(string applicationSecret, string consumerKey,
long currentTimestamp, string method, string target, string data = null)
{

using (var sha1Hasher = SHA1.Create())
{
var toSign =
string.Join("+", applicationSecret, consumerKey, method,
target, data, currentTimestamp);
var binaryHash = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(toSign));
var signature = string.Join("",
binaryHash.Select(x => x.ToString("X2"))).ToLower();
return $"$1${signature}";
}
}

private async Task<long> GetTime()
{
var response = await _httpClient.GetAsync("https://api.ovh.com/1.0/auth/time");

response.EnsureSuccessStatusCode();
var time = await response.Content.ReadAsAsync<long>();
return time;
}
}

public class OVHRecord
{
public string id { get; set; }
public string zone { get; set; }
public string subDomain { get; set; }
public string fieldType { get; set; }
public string target { get; set; }
public int ttl { get; set; }
}


}
1 change: 1 addition & 0 deletions KeyVault.Acmebot/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public override void Configure(IFunctionsHostBuilder builder)
dnsProviders.TryAdd(options.GandiLiveDns, o => new GandiLiveDnsProvider(o));
dnsProviders.TryAdd(options.GoDaddy, o => new GoDaddyProvider(o));
dnsProviders.TryAdd(options.GoogleDns, o => new GoogleDnsProvider(o));
dnsProviders.TryAdd(options.OVH, o => new OVHDnsProvider(o));
dnsProviders.TryAdd(options.Route53, o => new Route53Provider(o));
dnsProviders.TryAdd(options.TransIp, o => new TransIpProvider(options, o, credential));

Expand Down

0 comments on commit 191b1f2

Please sign in to comment.