public static async Task Authorization([ActivityTrigger] DurableActivityContext context, ILogger log)
        {
            var(site, authzUrl) = context.GetInput <(Site, string)>();

            var acme = await CreateAcmeClientAsync();

            var authz = await acme.GetAuthorizationDetailsAsync(authzUrl);

            // http-01 Challenge のみ対応
            var challenge = authz.Challenges.First(x => x.Type == "http-01");

            var challengeValidationDetails = AuthorizationDecoder.ResolveChallengeForHttp01(authz, challenge, acme.Signer);

            var websiteClient = await CreateManagementClientAsync();

            var credentials = await websiteClient.WebApps.ListPublishingCredentialsAsync(site.ResourceGroup, site.Name);

            // Kudu API を使い、Answer 用のファイルを作成
            var kuduClient = new KuduApiClient(site.Name, credentials.PublishingUserName, credentials.PublishingPassword);

            await kuduClient.WriteFileAsync(DefaultWebConfigPath, DefaultWebConfig);

            await kuduClient.WriteFileAsync(challengeValidationDetails.HttpResourcePath, challengeValidationDetails.HttpResourceValue);

            // Answer の準備が出来たことを通知
            await acme.AnswerChallengeAsync(challenge.Url);
        }
Esempio n. 2
0
        public void Test_Decode_OrderChallengeForDns01_ForMultiDns()
        {
            var testCtx = SetTestContext();

            var oldOrder = testCtx.GroupLoadObject <OrderDetails>("order.json");
            var oldAuthz = testCtx.GroupLoadObject <Authorization[]>("order-authz.json");

            var authzIndex = 0;

            foreach (var authz in oldAuthz)
            {
                var chlngIndex = 0;
                foreach (var chlng in authz.Challenges.Where(
                             x => x.Type == Dns01ChallengeValidationDetails.Dns01ChallengeType))
                {
                    Log.LogInformation("Decoding Authorization {0} Challenge {1}",
                                       authzIndex, chlngIndex);

                    var chlngDetails = AuthorizationDecoder.ResolveChallengeForDns01(
                        authz, chlng, Clients.Acme.Signer);

                    Assert.Equal(Dns01ChallengeValidationDetails.Dns01ChallengeType,
                                 chlngDetails.ChallengeType, ignoreCase: true);
                    Assert.NotNull(chlngDetails.DnsRecordName);
                    Assert.NotNull(chlngDetails.DnsRecordValue);
                    Assert.Equal("TXT", chlngDetails.DnsRecordType, ignoreCase: true);

                    testCtx.GroupSaveObject($"order-authz_{authzIndex}-chlng_{chlngIndex}.json",
                                            chlngDetails);
                    ++chlngIndex;
                }
                ++authzIndex;
            }
        }
Esempio n. 3
0
        private Task DecodeOrderAuthorizationChallenges(ACMESharp.Crypto.JOSE.IJwsTool signer)
        {
            foreach (var authz in _lastOrder.Authorizations)
            {
                var miscList = new List <Challenge>();
                foreach (var ch in authz.Details.Challenges)
                {
                    switch (ch.Type)
                    {
                    case Dns01ChallengeValidationDetails.Dns01ChallengeType:
                        authz.DnsChallenge = AuthorizationDecoder.ResolveChallengeForDns01(
                            authz.Details, ch, signer);
                        miscList.Add(ch);
                        break;

                    case Http01ChallengeValidationDetails.Http01ChallengeType:
                        authz.HttpChallenge = AuthorizationDecoder.ResolveChallengeForHttp01(
                            authz.Details, ch, signer);
                        miscList.Add(ch);
                        break;

                    default:
                        miscList.Add(ch);
                        break;
                    }
                }
                authz.MiscChallenges = miscList.ToArray();
            }

            return(Task.CompletedTask);
        }
        private async Task PerformChallengesAsync(OrderDetails order)
        {
            var exceptions = new List <Exception>();

            // Could be multiple authorizations if there are multiple distinct domains specified in the cert
            foreach (var authUrl in order.Payload.Authorizations)
            {
                var auth = await client.GetAuthorizationDetailsAsync(authUrl);

                var dnsChallenge = auth.Challenges.FirstOrDefault(c => c.Type == Dns01ChallengeValidationDetails.Dns01ChallengeType);

                if (dnsChallenge == null)
                {
                    exceptions.Add(new Exception("No DNS challenges for this authorization: " + authUrl));
                    continue;
                }

                var cd = (Dns01ChallengeValidationDetails)AuthorizationDecoder.DecodeChallengeValidation(
                    auth,
                    dnsChallenge.Type,
                    client.Signer);

                // Create requested DNS record, keep reference to remove after challenge passes
                logger.LogInformation("[{0}]@{1}ms Creating DNS record '{2}'", auth.Identifier, stopwatch.ElapsedMilliseconds, cd.DnsRecordName);
                var createdRecord = await this.dnsHandler.HandleAsync(cd.DnsRecordType, cd.DnsRecordName, cd.DnsRecordValue);

                logger.LogInformation("[{0}]@{1}ms Created DNS record successfully", auth.Identifier, stopwatch.ElapsedMilliseconds);

                var retries = 0;

                do
                {
                    Thread.Sleep(1000 * retries);

                    logger.LogInformation("[{0}]@{1}ms Answering challenge", auth.Identifier, stopwatch.ElapsedMilliseconds);
                    dnsChallenge = await client.AnswerChallengeAsync(dnsChallenge.Url);

                    auth = await client.GetAuthorizationDetailsAsync(authUrl);

                    retries++;
                } while (auth.Status != "valid" && retries < 5);

                if (auth.Status != "valid")
                {
                    exceptions.Add(new Exception($"Challenge validation was unsuccessful: [{dnsChallenge.Status}]{dnsChallenge.Error}\r\nAuthStatus: [{auth.Status}]"));
                }

                logger.LogInformation("[{0}]@{1}ms Removing DNS record '{2}'", auth.Identifier, stopwatch.ElapsedMilliseconds, cd.DnsRecordName);
                await createdRecord.CleanAsync();

                logger.LogInformation("[{0}]@{1}ms Removed DNS record successfully", auth.Identifier, stopwatch.ElapsedMilliseconds);
            }

            if (exceptions.Any())
            {
                throw new AggregateException("One or more exceptions occured while authorizing the request", exceptions);
            }
        }
Esempio n. 5
0
        protected async Task <bool> ResolveChallenges(AcmeProtocolClient acme)
        {
            if (AcmeState.PendingStatus == _state.Order?.Payload?.Status)
            {
                _logger.LogInformation("Order is pending, resolving Authorizations");
                if (_state.Authorizations == null)
                {
                    _state.Authorizations = new Dictionary <string, Authorization>();
                }
                foreach (var authzUrl in _state.Order.Payload.Authorizations)
                {
                    var authz = await acme.GetAuthorizationDetailsAsync(authzUrl);

                    _state.Authorizations[authzUrl] = authz;

                    if (AcmeState.PendingStatus == authz.Status)
                    {
                        foreach (var chlng in authz.Challenges)
                        {
                            if (string.IsNullOrEmpty(_options.ChallengeType) ||
                                _options.ChallengeType == chlng.Type)
                            {
                                var chlngValidation = AuthorizationDecoder.DecodeChallengeValidation(
                                    authz, chlng.Type, acme.Signer);
                                if (_options.ChallengeHandler(_services, chlngValidation))
                                {
                                    _logger.LogInformation("Challenge Handler has handled challenge:");
                                    _logger.LogInformation(JsonConvert.SerializeObject(chlngValidation, Formatting.Indented));
                                    var chlngUpdated = await acme.AnswerChallengeAsync(chlng.Url);

                                    if (chlngUpdated.Error != null)
                                    {
                                        _logger.LogError("Submitting Challenge Answer reported an error:");
                                        _logger.LogError(JsonConvert.SerializeObject(chlngUpdated.Error));
                                    }
                                }

                                _logger.LogInformation("Refreshing Authorization status");
                                authz = await acme.GetAuthorizationDetailsAsync(authzUrl);

                                if (AcmeState.PendingStatus != authz.Status)
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
                Save(_state.AuthorizationsFile, _state.Authorizations);

                _logger.LogInformation("Refreshing Order status");
                _state.Order = await acme.GetOrderDetailsAsync(_state.Order.OrderUrl, _state.Order);

                Save(_state.OrderFile, _state.Order);
            }
            return(true);
        }
Esempio n. 6
0
        public async Task <IList <AcmeChallengeResult> > Dns01Authorization([ActivityTrigger] string[] authorizationUrls)
        {
            var acmeProtocolClient = await _acmeProtocolClientFactory.CreateClientAsync();

            var challengeResults = new List <AcmeChallengeResult>();

            foreach (var authorizationUrl in authorizationUrls)
            {
                // Authorization の詳細を取得
                var authorization = await acmeProtocolClient.GetAuthorizationDetailsAsync(authorizationUrl);

                // DNS-01 Challenge の情報を拾う
                var challenge = authorization.Challenges.First(x => x.Type == "dns-01");

                var challengeValidationDetails = AuthorizationDecoder.ResolveChallengeForDns01(authorization, challenge, acmeProtocolClient.Signer);

                // Challenge の情報を保存する
                challengeResults.Add(new AcmeChallengeResult
                {
                    Url            = challenge.Url,
                    DnsRecordName  = challengeValidationDetails.DnsRecordName,
                    DnsRecordValue = challengeValidationDetails.DnsRecordValue
                });
            }

            // Azure DNS zone の一覧を取得する
            var zones = await _dnsManagementClient.Zones.ListAllAsync();

            // DNS-01 の検証レコード名毎に Azure DNS に TXT レコードを作成
            foreach (var lookup in challengeResults.ToLookup(x => x.DnsRecordName))
            {
                var dnsRecordName = lookup.Key;

                var zone = zones.Where(x => dnsRecordName.EndsWith($".{x.Name}", StringComparison.OrdinalIgnoreCase))
                           .OrderByDescending(x => x.Name.Length)
                           .First();

                var resourceGroup = ExtractResourceGroup(zone.Id);

                // Challenge の詳細から Azure DNS 向けにレコード名を作成
                var acmeDnsRecordName = dnsRecordName.Replace($".{zone.Name}", "", StringComparison.OrdinalIgnoreCase);

                // 既存の TXT レコードがあれば取得する
                var recordSet = await _dnsManagementClient.RecordSets.GetOrDefaultAsync(resourceGroup, zone.Name, acmeDnsRecordName, RecordType.TXT) ?? new RecordSet();

                // TXT レコードに TTL と値をセットする
                recordSet.TTL        = 60;
                recordSet.TxtRecords = lookup.Select(x => new TxtRecord(new[] { x.DnsRecordValue })).ToArray();

                await _dnsManagementClient.RecordSets.CreateOrUpdateAsync(resourceGroup, zone.Name, acmeDnsRecordName, RecordType.TXT, recordSet);
            }

            return(challengeResults);
        }
Esempio n. 7
0
        public async Task <(IReadOnlyList <AcmeChallengeResult>, int)> Dns01Authorization([ActivityTrigger] IReadOnlyList <string> authorizationUrls)
        {
            var acmeProtocolClient = await _acmeProtocolClientFactory.CreateClientAsync();

            var challengeResults = new List <AcmeChallengeResult>();

            foreach (var authorizationUrl in authorizationUrls)
            {
                // Authorization の詳細を取得
                var authorization = await acmeProtocolClient.GetAuthorizationDetailsAsync(authorizationUrl);

                // DNS-01 Challenge の情報を拾う
                var challenge = authorization.Challenges.First(x => x.Type == "dns-01");

                var challengeValidationDetails = AuthorizationDecoder.ResolveChallengeForDns01(authorization, challenge, acmeProtocolClient.Signer);

                // Challenge の情報を保存する
                challengeResults.Add(new AcmeChallengeResult
                {
                    Url            = challenge.Url,
                    DnsRecordName  = challengeValidationDetails.DnsRecordName,
                    DnsRecordValue = challengeValidationDetails.DnsRecordValue
                });
            }

            // DNS zone の一覧を取得する
            var zones = await _dnsProvider.ListZonesAsync();

            // DNS-01 の検証レコード名毎に DNS に TXT レコードを作成
            foreach (var lookup in challengeResults.ToLookup(x => x.DnsRecordName))
            {
                var dnsRecordName = lookup.Key;

                var zone = zones.Where(x => dnsRecordName.EndsWith($".{x.Name}", StringComparison.OrdinalIgnoreCase))
                           .OrderByDescending(x => x.Name.Length)
                           .First();

                // Challenge の詳細から DNS 向けにレコード名を作成
                var acmeDnsRecordName = dnsRecordName.Replace($".{zone.Name}", "", StringComparison.OrdinalIgnoreCase);

                await _dnsProvider.DeleteTxtRecordAsync(zone, acmeDnsRecordName);

                await _dnsProvider.CreateTxtRecordAsync(zone, acmeDnsRecordName, lookup.Select(x => x.DnsRecordValue));
            }

            return(challengeResults, _dnsProvider.PropagationSeconds);
        }
Esempio n. 8
0
        internal async Task <IChallengeValidationDetails> DecodeChallengeValidation(acme.Authorization auth, acme.Challenge challenge)
        {
            var client = await GetClient();

            return(AuthorizationDecoder.DecodeChallengeValidation(auth, challenge.Type, client.Signer));
        }
Esempio n. 9
0
 internal IChallengeValidationDetails DecodeChallengeValidation(Authorization auth, Challenge challenge)
 {
     return(AuthorizationDecoder.DecodeChallengeValidation(auth, challenge.Type, _client.Signer));
 }
Esempio n. 10
0
        public static async Task <ChallengeResult> Dns01Authorization([ActivityTrigger] DurableActivityContext context, ILogger log)
        {
            var authzUrl = context.GetInput <string>();

            var acme = await CreateAcmeClientAsync();

            var authz = await acme.GetAuthorizationDetailsAsync(authzUrl);

            // DNS-01 Challenge の情報を拾う
            var challenge = authz.Challenges.First(x => x.Type == "dns-01");

            var challengeValidationDetails = AuthorizationDecoder.ResolveChallengeForDns01(authz, challenge, acme.Signer);

            // Azure DNS の TXT レコードを書き換え
            var dnsClient = await CreateDnsManagementClientAsync();

            var zone = (await dnsClient.Zones.ListAsync()).First(x => challengeValidationDetails.DnsRecordName.EndsWith(x.Name));

            var resourceId = ParseResourceId(zone.Id);

            // Challenge の詳細から Azure DNS 向けにレコード名を作成
            var acmeDnsRecordName = challengeValidationDetails.DnsRecordName.Replace("." + zone.Name, "");

            RecordSet recordSet;

            try
            {
                recordSet = await dnsClient.RecordSets.GetAsync(resourceId["resourceGroups"], zone.Name, acmeDnsRecordName, RecordType.TXT);
            }
            catch
            {
                recordSet = null;
            }

            if (recordSet != null)
            {
                if (recordSet.Metadata == null || !recordSet.Metadata.TryGetValue(nameof(context.InstanceId), out var instanceId) || instanceId != context.InstanceId)
                {
                    recordSet.Metadata = new Dictionary <string, string>
                    {
                        { nameof(context.InstanceId), context.InstanceId }
                    };

                    recordSet.TxtRecords.Clear();
                }

                recordSet.TTL = 60;

                // 既存の TXT レコードに値を追加する
                recordSet.TxtRecords.Add(new TxtRecord(new[] { challengeValidationDetails.DnsRecordValue }));
            }
            else
            {
                // 新しく TXT レコードを作成する
                recordSet = new RecordSet
                {
                    TTL      = 60,
                    Metadata = new Dictionary <string, string>
                    {
                        { nameof(context.InstanceId), context.InstanceId }
                    },
                    TxtRecords = new[]
                    {
                        new TxtRecord(new[] { challengeValidationDetails.DnsRecordValue })
                    }
                };
            }

            await dnsClient.RecordSets.CreateOrUpdateAsync(resourceId["resourceGroups"], zone.Name, acmeDnsRecordName, RecordType.TXT, recordSet);

            return(new ChallengeResult
            {
                Url = challenge.Url,
                DnsRecordName = challengeValidationDetails.DnsRecordName,
                DnsRecordValue = challengeValidationDetails.DnsRecordValue
            });
        }
Esempio n. 11
0
        private async Task <ChallengeResult> DnsAuthorizationAsync(string authorizationUrl, string instanceId)
        {
            var acmeProtocolClient = await CreateAcmeClientAsync();

            var authorizationDetails = await acmeProtocolClient.GetAuthorizationDetailsAsync(authorizationUrl);

            var challenge = authorizationDetails.Challenges.First(x => x.Type == "dns-01");
            var challengeValidationDetails = AuthorizationDecoder.ResolveChallengeForDns01(authorizationDetails, challenge, acmeProtocolClient.Signer);

            var dnsManagementClient = await CreateDnsManagementClientAsync();

            var zone              = (await dnsManagementClient.Zones.ListAsync()).First(x => challengeValidationDetails.DnsRecordName.EndsWith(x.Name));
            var resourceId        = ParseResourceId(zone.Id);
            var acmeDnsRecordName = challengeValidationDetails.DnsRecordName.Replace("." + zone.Name, "");

            RecordSetInner recordSet;

            try
            {
                recordSet = await dnsManagementClient.RecordSets.GetAsync(resourceId["resourceGroups"], zone.Name, acmeDnsRecordName, RecordType.TXT);
            }
            catch
            {
                recordSet = null;
            }

            if (recordSet != null)
            {
                if (recordSet.Metadata == null || !recordSet.Metadata.TryGetValue("InstanceId", out var dnsInstanceId) || dnsInstanceId != instanceId)
                {
                    recordSet.Metadata = new Dictionary <string, string>
                    {
                        { "InstanceId", instanceId }
                    };

                    recordSet.TxtRecords.Clear();
                }

                recordSet.TTL = 60;
                recordSet.TxtRecords.Add(new TxtRecord(new[] { challengeValidationDetails.DnsRecordValue }));
            }
            else
            {
                recordSet = new RecordSetInner()
                {
                    TTL      = 60,
                    Metadata = new Dictionary <string, string>
                    {
                        { "InstanceId", instanceId }
                    },
                    TxtRecords = new[]
                    {
                        new TxtRecord(new[] { challengeValidationDetails.DnsRecordValue })
                    }
                };
            }

            await dnsManagementClient.RecordSets.CreateOrUpdateAsync(resourceId["resourceGroups"], zone.Name, acmeDnsRecordName, RecordType.TXT, recordSet);

            return(new ChallengeResult()
            {
                Url = challenge.Url,
                DnsRecordName = challengeValidationDetails.DnsRecordName,
                DnsRecordValue = challengeValidationDetails.DnsRecordValue
            });
        }
Esempio n. 12
0
        public async Task <IActionResult> Post([FromBody] DomainRequest request)
        {
            if (!IsValidDomain(request.Domain))
            {
                return(BadRequest(new DomainResponse {
                    Error = "Invalid domain"
                }));
            }

            var domain    = string.Join(".", request.Domain.Split(".").TakeLast(2));
            var subDomain = string.Join(".", request.Domain.Split(".").SkipLast(2));

            var credentials = new AzureCredentialsFactory()
                              .FromServicePrincipal(
                _configuration["Azure:ClientId"],
                _configuration["Azure:ClientSecret"],
                _configuration["Azure:TenantId"],
                AzureEnvironment.AzureGlobalCloud
                );

            var azure = Azure
                        .Configure()
                        .WithRetryPolicy(new RetryPolicy(new TransientErrorIgnoreStrategy(), 0))
                        .Authenticate(credentials)
                        .WithSubscription(_configuration["Azure:SubscriptionId"]);

            var webApp = await azure.AppServices.WebApps.GetByIdAsync(
                _configuration["Azure:AppId"]);

            try
            {
                webApp.Update()
                .DefineHostnameBinding()
                .WithThirdPartyDomain(domain)
                .WithSubDomain(subDomain)
                .WithDnsRecordType(CustomHostNameDnsRecordType.CName)
                .Attach()
                .Apply();
            }
            catch (Exception e)
            {
                return(BadRequest(new DomainResponse {
                    Error = "Unable to validate domain ownership"
                }));
            }

            _ = Task.Run(async() =>
            {
                using var airtableBase = new AirtableBase(_configuration["Airtable:Key"], _configuration["Airtable:Base"]);
                try
                {
                    HttpClient httpClient = new HttpClient {
                        BaseAddress = new Uri(_configuration["Acme:Endpoint"])
                    };
                    AcmeProtocolClient acme = new AcmeProtocolClient(httpClient, usePostAsGet: true);

                    var acmeDir    = await acme.GetDirectoryAsync();
                    acme.Directory = acmeDir;

                    await acme.GetNonceAsync();

                    var account  = await acme.CreateAccountAsync(new[] { "mailto:" + _configuration["Acme:Email"] }, true);
                    acme.Account = account;

                    var order = await acme.CreateOrderAsync(new[] { request.Domain });
                    if (order.Payload.Status == "invalid")
                    {
                        return;
                    }

                    var authorizationUrl = order.Payload.Authorizations.FirstOrDefault();
                    if (string.IsNullOrEmpty(authorizationUrl))
                    {
                        return;
                    }
                    var authorization = await acme.GetAuthorizationDetailsAsync(authorizationUrl);

                    foreach (var challenge in authorization.Challenges.Where(x => x.Type == "http-01").ToList())
                    {
                        var challengeValidationDetails = (Http01ChallengeValidationDetails)
                                                         AuthorizationDecoder.DecodeChallengeValidation(authorization, challenge.Type, acme.Signer);

                        var path        = challengeValidationDetails.HttpResourcePath;
                        var token       = path.Split("/", StringSplitOptions.RemoveEmptyEntries).Last();
                        var value       = challengeValidationDetails.HttpResourceValue;
                        var contentType = challengeValidationDetails.HttpResourceContentType;

                        await airtableBase.CreateRecord("Acme", new Fields
                        {
                            FieldsCollection = new Dictionary <string, object>
                            {
                                ["Token"]       = token,
                                ["Value"]       = value,
                                ["ContentType"] = contentType
                            }
                        });

                        await Task.Delay(10 * 1000);
                        var challengeUpdated = await acme.AnswerChallengeAsync(challenge.Url);
                    }

                    //Wait for challenge to be resolved
                    var waitUntil = DateTime.Now.AddSeconds(300);
                    Authorization authorizationUpdated;
                    do
                    {
                        await Task.Delay(10 * 1000);
                        authorizationUpdated = await acme.GetAuthorizationDetailsAsync(authorizationUrl);
                    } while (authorizationUpdated.Status != "valid" && DateTime.Now < waitUntil);

                    if (authorizationUpdated.Status != "valid")
                    {
                        return;
                    }

                    //Generate certificate private key and CSR (Certificate signing request)
                    var keyPair = PkiKeyPair.GenerateEcdsaKeyPair(256);
                    var csr     = new PkiCertificateSigningRequest($"CN={request.Domain}", keyPair, PkiHashAlgorithm.Sha256);
                    var certCsr = csr.ExportSigningRequest(PkiEncodingFormat.Der);

                    order = await acme.FinalizeOrderAsync(order.Payload.Finalize, certCsr);
                    if (order.Payload.Status != "valid")
                    {
                        return;
                    }

                    if (string.IsNullOrEmpty(order.Payload.Certificate))
                    {
                        //Wait for certificate
                        var waitUntil2 = DateTime.Now.AddSeconds(300);
                        while (DateTime.Now < waitUntil2)
                        {
                            await Task.Delay(10 * 1000);
                            order = await acme.GetOrderDetailsAsync(order.OrderUrl, existing: order);

                            if (!string.IsNullOrEmpty(order.Payload.Certificate))
                            {
                                break;
                            }
                        }
                    }
                    if (string.IsNullOrEmpty(order.Payload.Certificate))
                    {
                        return;
                    }

                    var certResp = await acme.GetAsync(order.Payload.Certificate);
                    if (!certResp.IsSuccessStatusCode)
                    {
                        return;
                    }

                    var certByteArray = await certResp.Content.ReadAsByteArrayAsync();

                    //Export PFX file
                    var pfxPassword = _configuration["Acme:PfxPassword"];
                    var privateKey  = keyPair.PrivateKey;

                    using var cert = new X509Certificate2(certByteArray);

                    X509Chain chain = new X509Chain();
                    chain.Build(cert);
                    List <PkiCertificate> chainList = new List <PkiCertificate>();
                    foreach (var e in chain.ChainElements)
                    {
                        chainList.Add(PkiCertificate.From(e.Certificate));
                    }

                    var pfx = chainList[0].Export(PkiArchiveFormat.Pkcs12,
                                                  chain: chainList.Skip(1),
                                                  privateKey: privateKey,
                                                  password: pfxPassword?.ToCharArray());

                    webApp.Update()
                    .DefineSslBinding()
                    .ForHostname(request.Domain)
                    .WithPfxByteArrayToUpload(pfx, pfxPassword)
                    .WithSniBasedSsl()
                    .Attach()
                    .Apply();
                }
                catch (Exception e)
                {
                    await airtableBase.CreateRecord("Logs", new Fields
                    {
                        FieldsCollection = new Dictionary <string, object>
                        {
                            ["Hostname"] = request.Domain,
                            ["Event"]    = "exception-thrown",
                            ["Data"]     = JsonConvert.SerializeObject(e)
                        }
                    });
                }
            });



            return(Ok(new DomainResponse {
                IsSuccessful = true
            }));
        }