Skip to content

Commit 6951cf6

Browse files
committed
Add OVH Provider
1 parent 6f74c03 commit 6951cf6

File tree

4 files changed

+251
-0
lines changed

4 files changed

+251
-0
lines changed

KeyVault.Acmebot/Options/AcmebotOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public class AcmebotOptions
4949

5050
public GoogleDnsOptions GoogleDns { get; set; }
5151

52+
public OVHOptions OVH { get; set; }
53+
5254
public Route53Options Route53 { get; set; }
5355

5456
public TransIpOptions TransIp { get; set; }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace KeyVault.Acmebot.Options;
2+
3+
public class OVHOptions
4+
{
5+
public string ApplicationKey { get; set; }
6+
7+
public string ApplicationSecret { get; set; }
8+
9+
public string ConsumerKey { get; set; }
10+
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Security.Cryptography;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
using KeyVault.Acmebot.Options;
10+
11+
using Newtonsoft.Json;
12+
13+
namespace KeyVault.Acmebot.Providers;
14+
15+
public class OVHDnsProvider : IDnsProvider
16+
{
17+
public string Name => "OVH";
18+
public int PropagationSeconds => 300;
19+
20+
private readonly OVHClient _client;
21+
22+
public OVHDnsProvider(OVHOptions options)
23+
{
24+
_client = new OVHClient(options.ApplicationKey, options.ApplicationSecret, options.ConsumerKey);
25+
}
26+
public Task CreateTxtRecordAsync(DnsZone zone, string relativeRecordName, IEnumerable<string> values)
27+
{
28+
return _client.AddRecordAsync(zone.Name, relativeRecordName, values);
29+
}
30+
31+
public Task DeleteTxtRecordAsync(DnsZone zone, string relativeRecordName)
32+
{
33+
return _client.DeleteRecordAsync(zone.Name, relativeRecordName);
34+
}
35+
public async Task<IReadOnlyList<DnsZone>> ListZonesAsync()
36+
{
37+
var zones = await _client.ListZonesAsync();
38+
39+
return zones.Select(x => new DnsZone(this) { Id = x, Name = x }).ToArray();
40+
}
41+
private class OVHClient
42+
{
43+
44+
public OVHClient(string applicationKey, string applicationSecret, string consumerKey)
45+
{
46+
_httpClient = new HttpClient();
47+
48+
ArgumentNullException.ThrowIfNull(applicationKey);
49+
ArgumentNullException.ThrowIfNull(applicationSecret);
50+
ArgumentNullException.ThrowIfNull(consumerKey);
51+
52+
_applicationKey = applicationKey;
53+
_applicationSecret = applicationSecret;
54+
_consumerKey = consumerKey;
55+
}
56+
57+
private readonly HttpClient _httpClient;
58+
private readonly string _applicationKey;
59+
private readonly string _applicationSecret;
60+
private readonly string _consumerKey;
61+
62+
public async Task<IReadOnlyList<String>> ListZonesAsync()
63+
{
64+
var url = "https://api.ovh.com/1.0/domain/zone";
65+
using (var requestMessage =
66+
new HttpRequestMessage(HttpMethod.Get, url))
67+
{
68+
var time = await GetTime();
69+
var signature = GenerateSignature(_applicationSecret, _consumerKey,
70+
time, requestMessage.Method.Method, url);
71+
72+
requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
73+
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
74+
requestMessage.Headers.Add("X-Ovh-Signature", signature);
75+
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());
76+
77+
78+
var response = await _httpClient.SendAsync(requestMessage);
79+
response.EnsureSuccessStatusCode();
80+
var domains = await response.Content.ReadAsAsync<String[]>();
81+
return domains;
82+
}
83+
}
84+
85+
public async Task DeleteRecordAsync(string zoneName, string relativeRecordName)
86+
{
87+
88+
var recordIds = Array.Empty<string>();
89+
var url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record";
90+
using (var requestMessage =
91+
new HttpRequestMessage(HttpMethod.Get, url))
92+
{
93+
var time = await GetTime();
94+
var signature = GenerateSignature(_applicationSecret, _consumerKey,
95+
time, requestMessage.Method.Method, url);
96+
97+
requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
98+
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
99+
requestMessage.Headers.Add("X-Ovh-Signature", signature);
100+
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());
101+
102+
103+
var response = await _httpClient.SendAsync(requestMessage);
104+
response.EnsureSuccessStatusCode();
105+
recordIds = await response.Content.ReadAsAsync<String[]>();
106+
}
107+
108+
109+
110+
foreach (var recordId in recordIds)
111+
{
112+
url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record/" + recordId;
113+
using (var requestMessage =
114+
new HttpRequestMessage(HttpMethod.Get, url))
115+
{
116+
var time = await GetTime();
117+
var signature = GenerateSignature(_applicationSecret, _consumerKey,
118+
time, requestMessage.Method.Method, url);
119+
120+
requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
121+
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
122+
requestMessage.Headers.Add("X-Ovh-Signature", signature);
123+
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());
124+
125+
var response = await _httpClient.SendAsync(requestMessage);
126+
response.EnsureSuccessStatusCode();
127+
var ovhRecord = await response.Content.ReadAsAsync<OVHRecord>();
128+
if (ovhRecord.fieldType == "TXT" && ovhRecord.subDomain == "_acme-challenge")
129+
{
130+
url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record/" + recordId;
131+
using (var requestMessage2 =
132+
new HttpRequestMessage(HttpMethod.Delete, url))
133+
{
134+
var time2 = await GetTime();
135+
var signature2 = GenerateSignature(_applicationSecret, _consumerKey,
136+
time2, requestMessage2.Method.Method, url);
137+
138+
requestMessage2.Headers.Add("X-Ovh-Application", _applicationKey);
139+
requestMessage2.Headers.Add("X-Ovh-Consumer", _consumerKey);
140+
requestMessage2.Headers.Add("X-Ovh-Signature", signature2);
141+
requestMessage2.Headers.Add("X-Ovh-Timestamp", time2.ToString());
142+
143+
var response2 = await _httpClient.SendAsync(requestMessage2);
144+
response2.EnsureSuccessStatusCode();
145+
}
146+
}
147+
}
148+
}
149+
150+
}
151+
152+
public async Task AddRecordAsync(string zoneName, string relativeRecordName, IEnumerable<string> values)
153+
{
154+
var url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/record";
155+
using (var requestMessage =
156+
new HttpRequestMessage(HttpMethod.Post, url))
157+
{
158+
var body = new
159+
{
160+
fieldType = "TXT",
161+
subDomain = relativeRecordName,
162+
target = values.First(),
163+
ttl = 300
164+
};
165+
var bodyString = JsonConvert.SerializeObject(body);
166+
requestMessage.Content = new StringContent(bodyString, Encoding.UTF8, "application/json");
167+
var time = await GetTime();
168+
var signature = GenerateSignature(_applicationSecret, _consumerKey,
169+
time, requestMessage.Method.Method, url, bodyString);
170+
171+
requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
172+
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
173+
requestMessage.Headers.Add("X-Ovh-Signature", signature);
174+
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());
175+
176+
177+
var response = await _httpClient.SendAsync(requestMessage);
178+
response.EnsureSuccessStatusCode();
179+
}
180+
url = "https://api.ovh.com/1.0/domain/zone/" + zoneName + "/refresh";
181+
using (var requestMessage =
182+
new HttpRequestMessage(HttpMethod.Post, url))
183+
{
184+
var time = await GetTime();
185+
var signature = GenerateSignature(_applicationSecret, _consumerKey,
186+
time, requestMessage.Method.Method, url);
187+
188+
requestMessage.Headers.Add("X-Ovh-Application", _applicationKey);
189+
requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey);
190+
requestMessage.Headers.Add("X-Ovh-Signature", signature);
191+
requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString());
192+
193+
194+
var responseRefresh = await _httpClient.SendAsync(requestMessage);
195+
responseRefresh.EnsureSuccessStatusCode();
196+
}
197+
198+
}
199+
200+
201+
private string GenerateSignature(string applicationSecret, string consumerKey,
202+
long currentTimestamp, string method, string target, string data = null)
203+
{
204+
205+
using (var sha1Hasher = SHA1.Create())
206+
{
207+
var toSign =
208+
string.Join("+", applicationSecret, consumerKey, method,
209+
target, data, currentTimestamp);
210+
var binaryHash = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(toSign));
211+
var signature = string.Join("",
212+
binaryHash.Select(x => x.ToString("X2"))).ToLower();
213+
return $"$1${signature}";
214+
}
215+
}
216+
217+
private async Task<long> GetTime()
218+
{
219+
var response = await _httpClient.GetAsync("https://api.ovh.com/1.0/auth/time");
220+
221+
response.EnsureSuccessStatusCode();
222+
var time = await response.Content.ReadAsAsync<long>();
223+
return time;
224+
}
225+
}
226+
227+
public class OVHRecord
228+
{
229+
public string id { get; set; }
230+
public string zone { get; set; }
231+
public string subDomain { get; set; }
232+
public string fieldType { get; set; }
233+
public string target { get; set; }
234+
public int ttl { get; set; }
235+
}
236+
237+
238+
}

KeyVault.Acmebot/Startup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public override void Configure(IFunctionsHostBuilder builder)
123123
dnsProviders.TryAdd(options.GandiLiveDns, o => new GandiLiveDnsProvider(o));
124124
dnsProviders.TryAdd(options.GoDaddy, o => new GoDaddyProvider(o));
125125
dnsProviders.TryAdd(options.GoogleDns, o => new GoogleDnsProvider(o));
126+
dnsProviders.TryAdd(options.OVH, o => new OVHDnsProvider(o));
126127
dnsProviders.TryAdd(options.Route53, o => new Route53Provider(o));
127128
dnsProviders.TryAdd(options.TransIp, o => new TransIpProvider(options, o, credential));
128129

0 commit comments

Comments
 (0)