/// <summary> /// Update/create bindings for all host names in the certificate /// </summary> /// <param name="target"></param> /// <param name="flags"></param> /// <param name="thumbprint"></param> /// <param name="store"></param> public void AddOrUpdateBindings(Target target, SSLFlags flags, CertificateInfo newCertificate, CertificateInfo oldCertificate) { try { var allBindings = WebSites. SelectMany(site => site.Bindings, (site, binding) => new { site, binding }). ToList(); var bindingsUpdated = 0; var found = new List <string>(); var oldThumbprint = oldCertificate?.Certificate?.GetCertHash(); if (oldThumbprint != null) { var siteBindings = allBindings. Where(sb => StructuralComparisons.StructuralEqualityComparer.Equals(sb.binding.CertificateHash, oldThumbprint)). ToList(); // Update all bindings created using the previous certificate foreach (var sb in siteBindings) { try { UpdateBinding(sb.site, sb.binding, flags, newCertificate.Certificate.GetCertHash(), newCertificate.Store?.Name); found.Add(sb.binding.Host); bindingsUpdated += 1; } catch (Exception ex) { _log.Error(ex, "Error updating binding {host}", sb.binding.BindingInformation); throw; } } } // Find all hostnames which are not covered by any of the already updated // bindings yet, because we will want to make sure that those are accessable // in the target site var targetSite = GetWebSite(target.InstallationSiteId ?? target.TargetSiteId ?? -1); IEnumerable <string> todo = target.GetHosts(true); while (todo.Count() > 0) { // Filter by previously matched bindings todo = todo.Where(host => !found.Any(binding => Fits(binding, host, flags) > 0)); if (todo.Count() > 0) { var current = todo.First(); try { var binding = AddOrUpdateBindings( targetSite, current, flags, newCertificate.Certificate.GetCertHash(), newCertificate.Store?.Name, target.SSLPort, true); // Allow a single newly created binding to match with // multiple hostnames on the todo list, e.g. the *.example.com binding // matches with both a.example.com and b.example.com found.Add(binding); bindingsUpdated += 1; } catch (Exception ex) { _log.Error(ex, "Error creating binding {host}: {ex}", current, ex.Message); // Prevent infinite retry loop, we just skip the domain when // an error happens creating a new binding for it. User can // always change/add the bindings manually after all. found.Add(current); } } } if (bindingsUpdated > 0) { _log.Information("Committing {count} {type} binding changes to IIS", bindingsUpdated, "https"); Commit(); _log.Information("IIS will serve the new certificates after the Application Pool IdleTimeout has been reached."); } else { _log.Warning("No bindings have been changed"); } } catch (Exception ex) { _log.Error(ex, "Error installing"); throw; } }
/// <summary> /// Update/create bindings for all host names in the certificate /// </summary> /// <param name="target"></param> /// <param name="flags"></param> /// <param name="thumbprint"></param> /// <param name="store"></param> public void AddOrUpdateBindings(IEnumerable <string> identifiers, BindingOptions bindingOptions, byte[] oldThumbprint) { // Helper function to get updated sites IEnumerable <(IISSiteWrapper site, Binding binding)> GetAllSites() => WebSites. SelectMany(site => site.Site.Bindings, (site, binding) => (site, binding)). ToList(); try { var allBindings = GetAllSites(); var bindingsUpdated = 0; var found = new List <string>(); if (oldThumbprint != null) { var siteBindings = allBindings. Where(sb => StructuralComparisons.StructuralEqualityComparer.Equals(sb.binding.CertificateHash, oldThumbprint)). ToList(); // Update all bindings created using the previous certificate foreach (var(site, binding) in siteBindings) { try { UpdateBinding(site.Site, binding, bindingOptions); found.Add(binding.Host); bindingsUpdated += 1; } catch (Exception ex) { _log.Error(ex, "Error updating binding {host}", binding.BindingInformation); throw; } } } // Find all hostnames which are not covered by any of the already updated // bindings yet, because we will want to make sure that those are accessable // in the target site var targetSite = GetWebSite(bindingOptions.SiteId ?? -1); IEnumerable <string> todo = identifiers; while (todo.Any()) { // Filter by previously matched bindings todo = todo.Where(cert => !found.Any(iis => Fits(iis, cert, bindingOptions.Flags) > 0)); if (!todo.Any()) { break; } allBindings = GetAllSites(); var current = todo.First(); try { var binding = AddOrUpdateBindings( allBindings.Select(x => x.binding).ToArray(), targetSite, bindingOptions.WithHost(current), !bindingOptions.Flags.HasFlag(SSLFlags.CentralSSL)); // Allow a single newly created binding to match with // multiple hostnames on the todo list, e.g. the *.example.com binding // matches with both a.example.com and b.example.com if (binding == null) { // We were unable to create the binding because it would // lead to a duplicate. Pretend that we did add it to // still be able to get out of the loop; found.Add(current); } else { found.Add(binding); bindingsUpdated += 1; } } catch (Exception ex) { _log.Error(ex, "Error creating binding {host}: {ex}", current, ex.Message); // Prevent infinite retry loop, we just skip the domain when // an error happens creating a new binding for it. User can // always change/add the bindings manually after all. found.Add(current); } } if (bindingsUpdated > 0) { _log.Information("Committing {count} {type} binding changes to IIS", bindingsUpdated, "https"); Commit(); } else { _log.Warning("No bindings have been changed"); } } catch (Exception ex) { _log.Error(ex, "Error installing"); throw; } }