/// <summary> /// Find a site with a specific name in a resilient way /// </summary> /// <param name="manager"></param> /// <param name="siteName"></param> /// <param name="logger"></param> /// <returns></returns> public static List <Site> FindSiteWithName(ServerManager manager, string siteName, ILoggerInterface logger) { return(UtilsSystem.QueryEnumerable( manager.Sites, (s) => s.Name == siteName, (s) => s, (s) => s.Name, logger)); }
/// <summary> /// Delete a site from the server manager. /// /// The behaviour has been enhanced to prevent deleting a site from affecting bindings from /// other sites. See related article. Doing a simple sites.Remove() call can totally mess up bindings from /// other websites, which becomes even worse when using CCS (central certificate store). /// /// @see https://stackoverflow.com/questions/37792421/removing-secured-site-programmatically-spoils-other-bindings /// </summary> /// <param name="site"></param> /// <param name="manager"></param> /// <param name="criteria"></param> /// <param name="logger"></param> public static void RemoveSiteBindings( Site site, ServerManager manager, Func <Binding, bool> criteria, ILoggerInterface logger) { // Site bindings var siteBindingsToRemove = site.Bindings.Where(criteria).ToList(); // Make sure that this is "resilient" var allBindings = UtilsSystem.QueryEnumerable( manager.Sites, (s) => true, (s) => s.Bindings, (s) => s.Name, logger); // We have to collect all existing bindings from other sites, including ourse var existingBindings = (from p in allBindings select p.Where((i) => i.Protocol == "https")).SelectMany((i) => i) .ToList(); // Remove all bindings for our site, we only care about SSL bindings,the other onse // will be removed with the site itself foreach (var b in siteBindingsToRemove) { existingBindings.Remove(b); // The central certificate store bool bindingUsedInAnotherSite = (from p in existingBindings where (p.Host == b.Host && p.BindingInformation == b.BindingInformation) || // A combination of port and usage of CCS is a positive, even if in different IP addresses (p.SslFlags.HasFlag(SslFlags.CentralCertStore) && b.SslFlags.HasFlag(SslFlags.CentralCertStore) && p.EndPoint.Port.ToString() == b.EndPoint.Port.ToString()) select 1).Any(); site.Bindings.Remove(b, bindingUsedInAnotherSite); } }
/// <summary> /// There is a delay between a serverManager.CommitChanges() and the actual /// materialization of the configuration. /// /// This methods waits for a specific site to be available. /// </summary> public static void WaitForSiteToBeAvailable(string siteName, ILoggerInterface logger) { UtilsSystem.RetryWhile( () => { using (ServerManager sm = new ServerManager()) { // Site is ready when state is available var state = UtilsSystem.QueryEnumerable( sm.Sites, (s) => s.Name == siteName, (s) => s, (s) => s.Name, logger).Single().State; } }, (e) => true, 3000, logger); }
/// <summary> /// IIS is very bad at detecting and handling changes in certificates stored in the /// central certificate store, use this method to ensure that a hostname bound /// to a SSL termination is properly updated throughout IIS /// /// https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-85/certificate-rebind-in-iis85 /// https://delpierosysadmin.wordpress.com/2015/02/23/iis-8-5-enable-automatic-rebind-of-renewed-certificate-via-command-line/ /// </summary> public static void EnsureCertificateInCentralCertificateStoreIsRebound(string hostname, ILoggerInterface logger) { Dictionary <string, List <Binding> > temporaryBindings = new Dictionary <string, List <Binding> >(); using (var sm = new ServerManager()) { // Al sites that have an SSL termination bound to this hostname var sites = UtilsSystem.QueryEnumerable( sm.Sites, (s) => s.Bindings.Any(i => i.Protocol == "https" && hostname.Equals(i.Host, StringComparison.CurrentCultureIgnoreCase)), (s) => s, (s) => s.Name, logger).ToList(); // Remove temporarily foreach (var site in sites) { foreach (var binding in site.Bindings.Where((i) => i.Protocol == "https" && hostname.Equals(i.Host, StringComparison.CurrentCultureIgnoreCase)).ToList()) { if (!temporaryBindings.ContainsKey(site.Name)) { temporaryBindings[site.Name] = new List <Binding>(); } logger.LogInfo(true, "Removed binding {0} from site {1}", binding.BindingInformation, site.Name); temporaryBindings[site.Name].Add(binding); site.Bindings.Remove(binding); } } CommitChanges(sm); } // This wait here helps... Thread.Sleep(2000); // Now restore... using (var sm = new ServerManager()) { foreach (var siteName in temporaryBindings.Keys) { var site = FindSiteWithName(sm, siteName, logger).Single(); foreach (var binding in temporaryBindings[siteName]) { var b = site.Bindings.Add(binding.BindingInformation, binding.Protocol); b.SslFlags = binding.SslFlags; b.CertificateStoreName = binding.CertificateStoreName; b.UseDsMapper = binding.UseDsMapper; logger.LogInfo(true, "Restored binding {0} to site {1}", binding.BindingInformation, site.Name); } } CommitChanges(sm); } // This wait here helps also... Thread.Sleep(2000); }