Example #1
0
        /// <summary>
        /// Checks whether the Azure resource needs a new certificate
        /// </summary>
        /// <returns>True if a new certificate should be requested</returns>
        public static async Task <bool> NeedsNewCertificateAsync()
        {
            ResourceConfiguration resource = await GetResourceConfigurationAsync();

            IAppServiceCertificate existingCert = resource.ExistingCertificates?
                                                  .Where(c => c.Issuer.Contains(Constants.DefaultCA))
                                                  .OrderByDescending(c => c.ExpirationDate)
                                                  .FirstOrDefault();

            if (existingCert == null)
            {
                return(true);
            }

            TimeSpan timeUntilExpiry = existingCert.ExpirationDate - DateTime.Now;

            if (timeUntilExpiry < Settings.TimeBeforeExpiryToRenew)
            {
                return(true);
            }

            _logger.LogInformation($"   Existing certificate with thumbprint {existingCert.Thumbprint} is not close to expiry. A new certificate is not required.");

            return(false);
        }
        /// <summary>
        /// Update an existing certificate
        /// </summary>
        private async Task <IAppServiceCertificate> UpdateExistingCertificateAsync(IAppServiceCertificate existingCertificate, byte[] bytes)
        {
            _logger.LogInformation("Updating existing Azure certificate name {CertificateName}.", existingCertificate.Name);

            // Azure doesn't let us update a certificate which is bound (the api fails with 409 Conflict), and we can't
            // unbind the running certificate without causing problems for a running site
            // So we will need to create a new certificate, bind to that, and then delete the existing one
            return(await CreateNewCertificateAsync(bytes, existingCertificate.RegionName));
        }
 private void RemoveCertificate(IAppServiceManager webSiteClient, IAppServiceCertificate s, AzureWebAppSettings setting)
 {
     try
     {
         webSiteClient.AppServiceCertificates.DeleteByResourceGroup(setting.ServicePlanResourceGroupName ?? setting.ResourceGroupName, s.Name);
     }
     catch
     {
     }
 }
        /// <summary>
        /// If we've had to replace an existing certificate, then we'll need to delete the old one
        /// We actually delete any matching certificate created by FluffySpoon which ISN'T the one we
        /// just created
        /// </summary>
        /// <returns></returns>
        private async Task DeleteOldCertificatesAsync(IAppServiceCertificate newCertificate)
        {
            foreach (var certificate in await GetExistingAzureCertificatesAsync(CertificateType.Site))
            {
                if (certificate.Thumbprint != newCertificate.Thumbprint)
                {
                    _logger.LogInformation("Deleting old Azure certificate {CertificateName}", certificate.Name);

                    await _client.WebApps.Manager
                    .AppServiceCertificates
                    .Inner
                    .DeleteAsync(
                        _azureOptions.ResourceGroupName,
                        certificate.Name);
                }
            }
        }
        /// <summary>
        /// Create a new certificate from scratch
        /// </summary>
        private async Task <IAppServiceCertificate> CreateNewCertificateAsync(byte[] bytes, string regionName)
        {
            var certificateName = TagName + "_" + Guid.NewGuid();

            _logger.LogInformation("Creating new Azure certificate with name {CertificateName} in resource group {ResourceGroupName}, region {Region}.",
                                   certificateName, _azureOptions.ResourceGroupName, regionName);

            IAppServiceCertificate azureCertificate = await _client.WebApps.Manager
                                                      .AppServiceCertificates
                                                      .Define(certificateName)
                                                      .WithRegion(regionName)
                                                      .WithExistingResourceGroup(_azureOptions.ResourceGroupName)
                                                      .WithPfxByteArray(bytes)
                                                      .WithPfxPassword(nameof(FluffySpoon))
                                                      .CreateAsync();

            _logger.LogTrace("Created new Azure certificate with name {CertificateName}", azureCertificate.Name);

            var tags = new Dictionary <string, string>();

            foreach (var tag in azureCertificate.Tags)
            {
                tags.Add(tag.Key, tag.Value);
            }

            tags.Add(TagName, GetTagValue(CertificateType.Site));

            _logger.LogInformation("Updating tags: {Tags} on certificate {CertificateName}", tags, azureCertificate.Name);

            await _client.WebApps.Manager
            .AppServiceCertificates
            .Inner
            .UpdateAsync(
                _azureOptions.ResourceGroupName,
                azureCertificate.Name,
                new CertificatePatchResource()
            {
                Tags = tags
            });

            return(azureCertificate);
        }
        private async Task UpdateAppBindingsAsync(AzureAppInstance appInstance,
                                                  IAppServiceCertificate azureCertificate,
                                                  string[] domains)
        {
            _logger.LogInformation("Checking host name bindings for app {AppName}", appInstance.DisplayName);

            string[] domainsToUpgrade = appInstance.HostNames
                                        .Where(boundDomain => DoesDomainMatch(boundDomain, domains))
                                        .ToArray();

            foreach (var domain in domainsToUpgrade)
            {
                _logger.LogDebug("Checking host name binding for domain {DomainName}", domain);

                HostNameBindingInner existingBinding =
                    await appInstance.GetHostNameBindingAsync(_client, _azureOptions.ResourceGroupName, domain);

                if (DoesBindingNeedUpdating(existingBinding, azureCertificate.Thumbprint))
                {
                    _logger.LogDebug("Updating host name binding for app {AppName} domain {DomainName}",
                                     appInstance.DisplayName, domain);

                    var newBinding = new HostNameBindingInner(
                        azureResourceType: AzureResourceType.Website,
                        hostNameType: HostNameType.Verified,
                        customHostNameDnsRecordType: CustomHostNameDnsRecordType.CName,
                        sslState: SslState.SniEnabled,
                        thumbprint: azureCertificate.Thumbprint);

                    await appInstance.SetHostNameBindingAsync(_client,
                                                              _azureOptions.ResourceGroupName,
                                                              domain,
                                                              newBinding);
                }
            }
        }
        public async Task PersistAsync(CertificateType persistenceType, IPersistableCertificate certificate)
        {
            if (certificate.RawData.Length == 0)
            {
                _logger.LogWarning("Tried to persist empty certificate.");
                return;
            }

            if (persistenceType != CertificateType.Site)
            {
                _logger.LogTrace("Skipping certificate persistence because a certificate of type {CertificateType} can't be persisted in Azure.", persistenceType);
                return;
            }

            var domains = _letsEncryptOptions.Domains.ToArray();

            _logger.LogInformation("Creating new Azure certificate of type {CertificateType} and domains {DomainNames}.", persistenceType, String.Join(", ", domains));

            var apps = await _client.WebApps.ListByResourceGroupAsync(_azureOptions.ResourceGroupName);

            var relevantApps = new HashSet <AzureAppInstance>();

            foreach (var app in apps)
            {
                _logger.LogTrace("Checking hostnames of app {AppName} (AppHostNames: {HostNames}) against domains {DomainNames}.", app.Name, app.HostNames, String.Join(", ", domains));

                if (DoDomainsMatch(app.HostNames, domains))
                {
                    _logger.LogTrace("App {AppName} matches a domain", app.Name);

                    relevantApps.Add(new AzureAppInstance(app));
                }

                if (app.DeploymentSlots != null)
                {
                    var slots = await app.DeploymentSlots.ListAsync();

                    foreach (var slot in slots)
                    {
                        _logger.LogTrace(
                            "Checking hostnames of app {AppName}/slot {slot} (AppHostNames: {HostNames}) against domains {Domains}.",
                            app.Name, slot.Name,
                            slot.HostNames, String.Join(", ", domains));

                        if (DoDomainsMatch(slot.HostNames, domains))
                        {
                            _logger.LogTrace("App {AppName}/slot {slot} matches a domain", app.Name, slot.Name);

                            relevantApps.Add(new AzureAppInstance(app, slot));
                        }
                    }
                }
            }

            if (!relevantApps.Any())
            {
                throw new InvalidOperationException(
                          $"Could not find an app that has a hostname created for domains {String.Join(", ", domains)}.");
            }

            var regionName = relevantApps.FirstOrDefault()?.RegionName;

            _logger.LogInformation("Found region name to use to use for new certificate: {RegionName}", regionName);

            IAppServiceCertificate newCertificate = await CreateOrUpdateCertificateAsync(certificate.RawData, regionName);

            foreach (var appTuple in relevantApps)
            {
                await UpdateAppBindingsAsync(appTuple, newCertificate, domains);
            }

            await DeleteOldCertificatesAsync(newCertificate);
        }
 public AzureCertificate(IAppServiceCertificate certificate)
 {
     _certificate = certificate;
 }
 private async Task RemoveCertificate(IAppServiceManager webSiteClient, IAppServiceCertificate s, AzureWebAppSettings setting)
 {
     await webSiteClient.AppServiceCertificates.DeleteByResourceGroupAsync(setting.ServicePlanResourceGroupName ?? setting.ResourceGroupName, s.Name);
 }
Example #10
0
 private bool IsValidCertificate(IAppServiceCertificate certificate)
 {
     return(certificate.HostNames.Contains(_hostname) &&
            certificate.ExpirationDate > DateTime.Now.AddDays(7) &&
            certificate.Issuer.ToLower().Contains("let's encrypt"));
 }