/// <summary> /// /// </summary> /// <param name="rule">The rule</param> /// <param name="replaceIfExists">If a rule exists with the same name, replace it, otherwise, throw exception.</param> public void AddRewriteRule(string rule, bool replaceIfExists = true) { var parsedRule = XElement.Parse(rule); string name = parsedRule.DescendantsAndSelf("rule").First().Attribute("name").Value; var webconfigfilepath = this.GetCdnWebConfigPathInitialized(); XDocument webcfg = XDocument.Parse(File.ReadAllText(webconfigfilepath)); XElement ruleselement = webcfg.Root.GetAndEnsureXpath("system.webServer/rewrite/rules"); var existingRule = (from p in ruleselement.Descendants("rule") where p.Attribute("name")?.Value == name select p).FirstOrDefault(); if (existingRule != null && !replaceIfExists) { throw new Exception("Rule already exists"); } existingRule?.Remove(); // Now add from scratchrule ruleselement.Add(parsedRule); // Perist it UtilsIis.WriteWebConfig(webconfigfilepath, webcfg.ToString()); }
/// <summary> /// Remove all rewrite rules that start with the given prefix. /// </summary> /// <param name="prefix"></param> /// <param name="logger"></param> public void RemoveRewriteRulesWithPrefix(string prefix, ILoggerInterface logger) { // If there is no CDN site, do nothing using (ServerManager manager = new ServerManager()) { var site = UtilsIis.FindSiteWithName(manager, this.CstChefCndSiteName, logger).SingleOrDefault(); if (site == null) { return; } } var webconfigfilepath = this.GetCdnWebConfigPathInitialized(); XDocument webcfg = XDocument.Parse(File.ReadAllText(webconfigfilepath)); var rules = (from p in webcfg.Descendants("rule") where p.Attribute("name")?.Value?.StartsWith(prefix) == true select p).ToList(); foreach (var rule in rules) { rule?.Remove(); } UtilsIis.WriteWebConfig(webconfigfilepath, webcfg.ToString()); }
/// <summary> /// Get an instance of SslCertificateProviderService /// </summary> public SslCertificateProviderService( ILoggerInterface logger, string appId, EnvironmentSettings globalSettings, Deployment deployment) { this.AppPoolUtils = new UtilsAppPool(logger); this.UtilsHosts = new UtilsHosts(logger); this.MockEnvironment = UtilsSystem.RunningInContinuousIntegration() || UnitTestDetector.IsRunningInTests || Debugger.IsAttached; // Everything performed against the staging API needs to be kept apart, including signer, etc... this.StoragePath = Path.Combine(globalSettings.GetDefaultContentStorage().path, "letsencrypt" + (this.MockEnvironment ? "_mock" : null)); UtilsSystem.EnsureDirectoryExists(this.StoragePath, true); // If CCS is available, use that, otherwise use the central content storage string sslRenewalStateStorePath = UtilsIis.CentralStoreEnabled() ? UtilsIis.CentralStorePath(logger) : globalSettings.GetDefaultContentStorage().path; this.SimpleStoreRenewalStatus = new SimpleStore(Path.Combine(sslRenewalStateStorePath, "_ssl_renewal_state_store"), true); this.Logger = logger; this.AppId = appId; this.Deployment = deployment; this.GlobalSettings = globalSettings; }
public void TestIpParser() { Assert.Equal("192.168.5.3", UtilsIis.GetSubnetAndMaskFromCidr("192.168.5.3/20").Item1.ToString()); Assert.Equal("255.255.240.0", UtilsIis.GetSubnetAndMaskFromCidr("192.168.5.3/20").Item2.ToString()); Assert.Equal("192.168.5.3", UtilsIis.GetSubnetAndMaskFromCidr("192.168.5.3").Item1.ToString()); Assert.Null(UtilsIis.GetSubnetAndMaskFromCidr("192.168.5.3").Item2); }
/// <summary> /// Deploy's the IIS PHP handler at the application hosts level, but site specific. /// </summary> protected void deployIISHandlers() { using (ServerManager serverManager = new ServerManager()) { var siteName = this.Deployment.getShortId(); string siteAlias = this.Deployment.getShortId(); // fastCgi settings in IIS can only be set at the HOSTS level // we found no way to set this at a web.config level. Microsoft.Web.Administration.Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationElement cfs = null; ConfigurationSection section; ConfigurationElementCollection elems; section = config.GetSection("system.webServer/handlers", siteName); elems = section.GetCollection(); cfs = elems.CreateElement("add"); cfs.SetAttributeValue("name", "php-" + this.Deployment.getShortId()); cfs.SetAttributeValue("path", "*.php"); cfs.SetAttributeValue("verb", "GET,HEAD,POST,PUT,PATCH,DELETE"); cfs.SetAttributeValue("modules", "FastCgiModule"); cfs.SetAttributeValue("scriptProcessor", this.GetFastCgiExe() + "|" + this.Deployment.getShortId()); cfs.SetAttributeValue("resourceType", "Either"); cfs.SetAttributeValue("requireAccess", "Script"); cfs.SetAttributeValue("responseBufferLimit", 0); // Add as the first handler... order matters here. elems.AddAt(0, cfs); // And index.php as a default document... var files = config.GetSection("system.webServer/defaultDocument", siteName).GetChildElement("files"); elems = files.GetCollection(); // We might have inherited settings from a higher level // that already cover the default document configuration. var exists = (from p in elems where p.Schema.Name == "add" && ((string)p.GetAttributeValue("value")) == "index.php" select 1).Any(); if (!exists) { // TODO: This fails if the default document is already configured at a higher level. Ensure it does // not exist before trying to create it! cfs = elems.CreateElement("add"); cfs.SetAttributeValue("value", "index.php"); elems.Add(cfs); } UtilsIis.CommitChanges(serverManager); } }
protected override void ProcessRecord() { ConsoleUtils.RunCode(() => { var logger = new logger.ConsoleLogger(); logger.SetVerbose(true); UtilsIis.EnsureCertificateInCentralCertificateStoreIsRebound(this.Hostname, logger); }); }
public void ConfigureProxy() { // For this local CDN to work we need IIS-ARR installed and configured at the IIS level, otherwise // IIS gets stuck with this config (+ it won't work) try { // Ensure that proxy is enabled and available at the IIS level. // This needs the IIS Application Request Routing extension. using (ServerManager manager = new ServerManager()) { bool configChanged = false; var config = manager.GetApplicationHostConfiguration(); ConfigurationSection proxySection = config.GetSection("system.webServer/proxy"); // Disable reverseRewriteHostInResponseHeaders if (!bool.TryParse(proxySection["reverseRewriteHostInResponseHeaders"]?.ToString(), out var proxyReverseRewrite) || proxyReverseRewrite == true) { proxySection["reverseRewriteHostInResponseHeaders"] = false; configChanged = true; } // Enable proxy functionality if (!bool.TryParse(proxySection["enabled"]?.ToString(), out var proxyEnabled) || proxyEnabled == false) { proxySection["enabled"] = true; configChanged = true; } // Disable disk cache ConfigurationElement cacheElement = proxySection.GetChildElement("cache"); if (!bool.TryParse(cacheElement["enabled"]?.ToString(), out var cacheEnabled) || cacheEnabled == true) { cacheElement["enabled"] = false; configChanged = true; } if (configChanged) { this.Logger.LogWarning(false, "Your IIS-ARR settings have been updated to work with Chef CDN: [proxy.enabled=true] && [proxy.cache.enabled=false]"); UtilsIis.CommitChanges(manager); } } } catch (Exception e) { throw new Exception("Could not configure server-wide proxy settings for CDN related functionality. Make sure that the chocolatey iis-arr package is installed.", e); } }
/// <summary> /// Move a rule to the requested INDEX in the set of rules /// </summary> /// <param name="ruleName"></param> /// <param name="index"></param> protected void SetRuleIndex(string ruleName, int index) { var webconfigfilepath = this.GetCdnWebConfigPathInitialized(); XDocument webcfg = XDocument.Parse(File.ReadAllText(webconfigfilepath)); XElement ruleselement = webcfg.Root.GetAndEnsureXpath("system.webServer/rewrite/rules"); XElement existingRule = null; var nodes = ruleselement.Nodes().ToList(); foreach (var node in nodes.ToList()) { if (node is XElement xNode && xNode.Attribute("name")?.Value == ruleName) { if (nodes.IndexOf(xNode) == index) { return; } existingRule = xNode; nodes.Remove(xNode); break; } } if (existingRule == null) { throw new Exception($"Rule {ruleName} not found."); } nodes.Insert(index, existingRule); ruleselement.ReplaceAll(nodes.ToArray()); UtilsIis.WriteWebConfig(webconfigfilepath, webcfg.ToString()); }
/// <summary> /// Get the webroot for the CDN site, initialized with a base web.config prepared for URL REWRITING /// </summary> /// <returns></returns> public string GetCdnWebConfigPathInitialized() { var basedir = UtilsSystem.EnsureDirectoryExists( UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, "__chef_cdn"), true); var webconfigfilepath = UtilsSystem.CombinePaths(basedir, "web.config"); // Si no hay un web.config plantilla, crearlo ahora. if (!File.Exists(webconfigfilepath)) { File.WriteAllText(webconfigfilepath, @" <configuration> <system.webServer> <rewrite> <rules> </rules> <outboundRules> </outboundRules> </rewrite> </system.webServer> </configuration> "); } UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(new SecurityIdentifier(UtilsWindowsAccounts.WELL_KNOWN_SID_USERS), basedir, FileSystemRights.ReadAndExecute); // Make sure that the site exists using (ServerManager manager = new ServerManager()) { bool configChanged = false; var site = UtilsIis.FindSiteWithName(manager, this.CstChefCndSiteName, this.Logger) .FirstOrDefault(); if (site == null) { manager.Sites.Add(this.CstChefCndSiteName, "http", $"{UtilsIis.LOCALHOST_ADDRESS}:80:{this.CstChefInternalHostname}", basedir); configChanged = true; } else { if (site.Applications.First().VirtualDirectories.First().PhysicalPath != basedir) { site.Applications.First().VirtualDirectories.First().PhysicalPath = basedir; configChanged = true; } } if (configChanged) { UtilsIis.CommitChanges(manager); } } this.UtilsHosts.AddHostsMapping(UtilsIis.LOCALHOST_ADDRESS, this.CstChefInternalHostname, "chf_IISDeployer_CDN"); // Add a cross domain file var crossdomainfilepath = UtilsSystem.CombinePaths(Path.GetDirectoryName(webconfigfilepath), "crossdomain.xml"); File.WriteAllText(crossdomainfilepath, UtilsSystem.GetEmbededResourceAsString(Assembly.GetExecutingAssembly(), "IIS.crossdomain.xml")); // Add common proxy headers UtilsIis.AddAllowedServerVariablesForUrlRewrite( this.CstChefCndSiteName, "HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED_PROTO", "HTTP_X_FORWARDED_HOST"); return(webconfigfilepath); }
/// <summary> /// Provisions a certificate in the central store /// </summary> /// <param name="hostName">Domain to register</param> /// <param name="email">Registration e-mail</param> /// <param name="bindingInfo">IIS binding info</param> /// <param name="ownerSiteName">The site that owns the binding, used to assign identity and application pool permissions.</param> /// <param name="forceSelfSigned">Force a self-signed certificate</param> /// <param name="forceRenewal">Force renewal, even if renewal conditions are not met</param> /// <returns>The certificate's friendly name, ready to be bound in IIS</returns> public void ProvisionCertificateInIis( string hostName, string email, string bindingInfo, string ownerSiteName, bool forceSelfSigned = false, bool forceRenewal = false) { if (hostName.Contains("*")) { throw new Exception($"Provisioning certificates for wildcard host name '{hostName}' is not supported."); } var currentCertificate = UtilsIis.FindCertificateInCentralCertificateStore(hostName, this.Logger, out _); double remainingCertificateDays = (currentCertificate?.NotAfter - DateTime.Now)?.TotalDays ?? 0; this.Logger.LogInfo(true, "Total days remaining for certificate expiration: '{0}'", (int)Math.Floor(remainingCertificateDays)); // Trigger renovation. Do this differently on mock/prod environment. // Next renewal attempt is calculated based on previous renewal attempt var renewalState = this.GetCertificateRenewalState(hostName); // Legacy apps don't have this set, or when a certificate has been manually placed if (renewalState.NextRenewal == null && remainingCertificateDays > 1) { renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays); this.StoreCertificateRenewalState(renewalState); } int remainingDaysForNextRenewal = renewalState.NextRenewal == null ? 0 : (int)(renewalState.NextRenewal - DateTime.UtcNow).Value.TotalDays; int certificateTotalDuration = currentCertificate == null ? 0 : (int)(currentCertificate.NotAfter - currentCertificate.NotBefore).TotalDays; this.Logger.LogInfo(true, "Next renewal attempt for this site SSL targeted in '{0}' days.", remainingDaysForNextRenewal); // Check that the validationfailed request rate is not exceeded for this domain if (!forceRenewal && renewalState.FailedValidations.AsIterable().Count(i => (DateTime.UtcNow - i).TotalHours < 48) >= 2) { // Make this message verbos so that it will not flood the logs, the failed validation message will get logged // anyways and is sufficient. this.Logger.LogWarning(true, "The hostname '{0}' has reached the limit of two failed validations in the last 48 hours.", hostName); return; } if (!forceRenewal && !forceSelfSigned && remainingDaysForNextRenewal > 0 && remainingCertificateDays > 0) { this.Logger.LogWarning(true, "Next renewal attempt date not reached, skipping SSL provisioning."); return; } if (!forceRenewal && remainingDaysForNextRenewal > 0 && (remainingDaysForNextRenewal > certificateTotalDuration * 0.5) && certificateTotalDuration > 0) { this.Logger.LogWarning(false, "Certificate has not yet been through at least 50% of it's lifetime so it will not be renewed.'"); renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays); this.StoreCertificateRenewalState(renewalState); return; } // Check the general too many requests rate exceeded if (!forceRenewal && this.SimpleStoreRenewalStatus.Get <bool>("ssl-certificate-provider-too-many-requests", out var tooManyRequests)) { this.Logger.LogWarning(false, "Certificate provisioning temporarily disabled due to a Too Many Requests ACME error. Flag stored in {0}", tooManyRequests.StorePath); return; } this.Logger.LogInfo(false, "Attempting SSL certificate renewal for site '{0}' and host '{1}'", ownerSiteName, hostName); // Clear old validation failed requests if (renewalState.FailedValidations?.Any() == true) { // Only keep failed validations that happen in the last 5 days renewalState.FailedValidations = renewalState.FailedValidations .Where((i) => (DateTime.UtcNow - i).TotalDays < 5).ToList(); } // This is a little bit inconvenient but... the most reliable and compatible // way to do this is to setup a custom IIS website that uses the binding during // provisioning. long tempSiteId; List <Site> haltedSites = new List <Site>(); var tempSiteName = "cert-" + this.AppId; var tempSiteAppId = "cert-" + this.AppId; string tempHostName = "localcert-" + hostName; this.Logger.LogInfo(true, "Preparing temp site: " + tempSiteName); List <Site> conflictingSites; // Prepare the site using (ServerManager sm = new ServerManager()) { // Query the sites in a resilient way... conflictingSites = UtilsSystem.QueryEnumerable( sm.Sites, (s) => s.State == ObjectState.Started && s.Bindings.Any((i) => i.Host.Equals(hostName)), (s) => s, (s) => s.Name, this.Logger); } // Stop the sites that might prevent this one from starting foreach (var s in conflictingSites) { this.Logger.LogInfo(true, "Stopping site {0} to avoid binding collision.", s.Name); this.AppPoolUtils.WebsiteAction(s.Name, AppPoolActionType.Stop, skipApplicationPools: true); haltedSites.Add(s); } using (ServerManager sm = new ServerManager()) { // Make sure there is no other site (might be stuck?) var existingSite = (from p in sm.Sites where p.Name == tempSiteName select p).FirstOrDefault(); var tempSite = existingSite ?? sm.Sites.Add(tempSiteName, this.GetAcmeTemporarySiteRootForApplication(), 80); // Propagate application pool usage so that permissions are properly handled. var ownerSite = sm.Sites.First((i) => i.Name == ownerSiteName); tempSite.Applications.First().ApplicationPoolName = ownerSite.Applications.First().ApplicationPoolName; // Delete all bindings tempSite.Bindings.Clear(); tempSite.Bindings.Add(bindingInfo, "http"); tempSite.Bindings.Add($"{UtilsIis.LOCALHOST_ADDRESS}:80:" + tempHostName, "http"); tempSiteId = tempSite.Id; this.UtilsHosts.AddHostsMapping(UtilsIis.LOCALHOST_ADDRESS, tempHostName, tempSiteAppId); // Prepare the website contents var sourceDir = UtilsSystem.FindResourcePhysicalPath(typeof(IISDeployer), ".well-known"); UtilsSystem.CopyFilesRecursively(new DirectoryInfo(sourceDir), new DirectoryInfo(this.GetWellKnownSharedPathForApplication()), true); UtilsIis.CommitChanges(sm); } UtilsIis.WaitForSiteToBeAvailable(tempSiteName, this.Logger); UtilsIis.ConfigureAnonymousAuthForIisApplication(tempSiteName, this.Deployment.WindowsUsernameFqdn(), this.Deployment.GetWindowsPassword()); IAcmeSharpProvider provider = null; try { this.AppPoolUtils.WebsiteAction(tempSiteName, AppPoolActionType.Start); // Check that the site does work using the local binding var testDataUrl = $"http://{tempHostName}/.well-known/acme-challenge/test.html"; this.Logger.LogInfo(true, "Validating local challenge setup at: {0}", testDataUrl); if (!string.Equals(UtilsSystem.DownloadUriAsText(testDataUrl), "test data")) { throw new Exception($"Could not locally validate acme challenge site setup at {testDataUrl}"); } // Ssl registration configuration only depends on the e-mail and is signed as such string sslSignerAndRegistrationStoragePath = UtilsSystem.EnsureDirectoryExists( UtilsSystem.CombinePaths(this.StoragePath, "_ssl_config", StringFormating.Instance.ExtremeClean(email)), true); // Initialize the provider bool useMockProvider = this.MockEnvironment || forceSelfSigned; provider = useMockProvider ? (IAcmeSharpProvider) new AcmeSharpProviderMock(this.Logger, tempHostName) : this.GetAcmeProvider(this.Logger, hostName); var signerPath = Path.Combine(this.StoragePath, "_signer.xml"); var registrationPath = Path.Combine(sslSignerAndRegistrationStoragePath, "registration.json"); provider.InitRegistration(signerPath, registrationPath, email); string challengeUrl; string challengeContent; string challengeFilePath; try { provider.GenerateHttpChallenge( out challengeUrl, out challengeContent, out challengeFilePath); } catch (AcmeClient.AcmeWebException acmeException) { if (acmeException.Message.Contains("429")) { int waitHours = 1; this.SimpleStoreRenewalStatus.Set("ssl-certificate-provider-too-many-requests", true, 60 * waitHours); this.Logger.LogError("Let's encrypt too many requests issue. Certificate provisioning disabled for the next {0} hours.", waitHours); this.Logger.LogException(acmeException, EventLogEntryType.Warning); return; } throw; } // Write the challanege contents string challengeFullPath = Path.Combine(this.GetAcmeTemporarySiteRootForApplication(), challengeFilePath); File.WriteAllText( challengeFullPath, challengeContent); this.Logger.LogInfo(false, $"Veryfing challenge at '{challengeUrl}'"); try { // Validate that we can actually access the challenge ourselves! string contents = UtilsSystem.DownloadUriAsText(challengeUrl, false); if (!string.Equals(contents, challengeContent)) { throw new Exception( $"Could not validate ACME challenge, retrieved challenge '{contents}' does not match '{challengeContent}'"); } } catch (Exception e) { this.Logger.LogWarning(true, "Cannot self-verify auth challenge, this can sometimes happeen under some DNS setups. {0}", e.Message + Environment.NewLine + e.InnerException?.Message); } var challengeValidated = false; try { challengeValidated = provider.ValidateChallenge(); } catch (Exception e) { this.Logger.LogException(e, EventLogEntryType.Warning); } this.Logger.LogWarning(true, "Remote challenge validation success: " + (challengeValidated ? "Yes" : "No")); // Download the certificates to this temp location string temporaryCertificatePath = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.StoragePath, this.AppId, "ssl_certificates", hostName), true); CertificatePaths certificatepaths = null; // This is here for testing purposes if (Environment.GetEnvironmentVariable("TEST_FAIL_CHALLENGE_VALIDATION") == true.ToString()) { challengeValidated = false; } if (!challengeValidated) { // There is a Failed Validation limit of 5 failures per account, per hostname, per hour. renewalState.FailedValidations = renewalState.FailedValidations ?? new List <DateTime>(); renewalState.FailedValidations.Add(DateTime.UtcNow); this.Logger.LogError( "Challenge could not be validated at '{0}'. If behind a load balancer, make sure that the site is deployed in ALL nodes, remove the self-signed certificate from the store and redeploy the application.", challengeUrl); this.StoreCertificateRenewalState(renewalState); } else { try { certificatepaths = provider.DownloadCertificate( UtilsEncryption.GetMD5(hostName), hostName, temporaryCertificatePath); } catch (AcmeClient.AcmeWebException acmeException) { this.Logger.LogException(acmeException, EventLogEntryType.Warning); } catch (WebException webException) { this.Logger.LogException(webException, EventLogEntryType.Warning); } catch (Exception e) { this.Logger.LogException(e, EventLogEntryType.Warning); } } if (certificatepaths == null && currentCertificate == null) { this.Logger.LogWarning(false, "Unable to acquire certificate and site does not have a valid existing one, using self-signed fallback."); provider = new AcmeSharpProviderMock(this.Logger, hostName); certificatepaths = provider.DownloadCertificate( UtilsEncryption.GetMD5(hostName), hostName, temporaryCertificatePath); } // Save this, use a fixed name certificate file if (certificatepaths != null) { string certificateFilePath = Path.Combine(UtilsIis.CentralStorePath(this.Logger), hostName + ".pfx"); UtilsSystem.RetryWhile( () => { File.Copy(certificatepaths.pfxPemFile, certificateFilePath, true); }, (e) => true, 2500, this.Logger); this.Logger.LogInfo(false, "Certificate file writen to '{0}'", certificateFilePath); // TODO: Activate this refreshing when it's prooved to work // UtilsIis.EnsureCertificateInCentralCertificateStoreIsRebound(hostName, this.Logger); } // Remove temporary certificates UtilsSystem.DeleteDirectory(temporaryCertificatePath, this.Logger); // Remove the already used challenge if it was validated. Otherwise keep it // for debugging purposes. if (challengeValidated && File.Exists(challengeFullPath)) { File.Delete(challengeFullPath); } // In the end, we always have a certificate. Program a renewal date according to the remaining expiration. currentCertificate = UtilsIis.FindCertificateInCentralCertificateStore(hostName, this.Logger, out _); remainingCertificateDays = (currentCertificate?.NotAfter - DateTime.Now)?.TotalDays ?? 0; // Add some randomness in renewal dates to avoid all certificates being renewed at once and reaching api limits renewalState.LastRenewal = DateTime.UtcNow; renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays); this.StoreCertificateRenewalState(renewalState); } finally { this.Logger.LogInfo(true, "Disposing temporary verification setup"); provider?.Dispose(); this.UtilsHosts.RemoveHostsMapping(tempSiteAppId); // Restore the original state of IIS!!! using (ServerManager sm = new ServerManager()) { var site = sm.Sites.Single(i => i.Id == tempSiteId); UtilsIis.RemoveSite(site, sm, this.Logger); UtilsIis.CommitChanges(sm); } // Give IIS some time to reconfigure itself and free resources. Thread.Sleep(1000); // Start the sites foreach (var site in haltedSites) { // Add some retry logic here because bringing the original sites online is critical UtilsSystem.RetryWhile(() => { this.AppPoolUtils.WebsiteAction(site.Name, AppPoolActionType.Start); }, (e) => true, 5000, this.Logger); } } }
public void TestIisGetConfigIsolationPath() { Assert.NotNull(UtilsIis.GetConfigIsolationPath()); }
public void TestGetMaxWebConfigFileSizeInKb() { UtilsIis.GetMaxWebConfigFileSizeInKb(); }
public void DeleteHttpsBindings() { var logger = new TestLogsLogger(this, nameof(this.DeleteHttpsBindings)); using (ServerManager sm = new ServerManager()) { var site = sm.Sites.Add("site0", Path.GetTempPath(), 443); site.Bindings.Add("127.0.0.1:5879:site0", null, null, SslFlags.CentralCertStore | SslFlags.Sni); site.Bindings.Add("127.0.0.1:5879:site1", null, null, SslFlags.CentralCertStore | SslFlags.Sni); sm.CommitChanges(); } Thread.Sleep(500); var certificates = UtilsIis.GetBoundCertificates(); Assert.True(certificates.Any((i) => i.Attributes.Any((j) => j.Key == "Central Certificate Store" && j.Value == "5879"))); using (ServerManager sm = new ServerManager()) { var site = UtilsIis.FindSiteWithName(sm, "site0", logger).Single(); UtilsIis.RemoveSiteBindings(site, sm, (i) => i.Host == "site1", logger); sm.CommitChanges(); } certificates = UtilsIis.GetBoundCertificates(); Assert.True(certificates.Any((i) => i.Attributes.Any((j) => j.Key == "Central Certificate Store" && j.Value == "5879"))); using (ServerManager sm = new ServerManager()) { var site = UtilsIis.FindSiteWithName(sm, "site0", logger).Single(); UtilsIis.RemoveSiteBindings(site, sm, (i) => i.Host == "site0", logger); sm.CommitChanges(); } Thread.Sleep(500); certificates = UtilsIis.GetBoundCertificates(); Assert.False(certificates.Any((i) => i.Attributes.Any((j) => j.Key == "Central Certificate Store" && j.Value == "5879"))); using (ServerManager sm = new ServerManager()) { var site = UtilsIis.FindSiteWithName(sm, "site0", logger).Single(); site.Bindings.Add("127.0.0.1:5879:site0", null, null, SslFlags.CentralCertStore | SslFlags.Sni); site.Bindings.Add("127.0.0.1:5879:site1", null, null, SslFlags.CentralCertStore | SslFlags.Sni); sm.CommitChanges(); } Thread.Sleep(500); certificates = UtilsIis.GetBoundCertificates(); Assert.True(certificates.Any((i) => i.Attributes.Any((j) => j.Key == "Central Certificate Store" && j.Value == "5879"))); using (ServerManager sm = new ServerManager()) { var site = UtilsIis.FindSiteWithName(sm, "site0", logger).Single(); UtilsIis.RemoveSite(site, sm, logger); sm.CommitChanges(); } Thread.Sleep(500); certificates = UtilsIis.GetBoundCertificates(); Assert.False(certificates.Any((i) => i.Attributes.Any((j) => j.Key == "Central Certificate Store" && j.Value == "5879"))); }
public void ReadBoundCertificates() { var certificates = UtilsIis.GetBoundCertificates(); Assert.NotEmpty(certificates); }
/// <summary> /// Deploy fast-cgi settings /// </summary> protected void DeployFasctCgi() { var limits = this.Deployment.GetApplicationLimits(); using (ServerManager serverManager = new ServerManager()) { this.Logger.LogWarning(false, "Deploying fastCgi based applications causes IIS to internally reset to pick the new configuration because fastCgi is configured at the server level."); var phpRuntime = this.GetFastCgiExe(); var iniFilePath = this.GetIniFilePath(); var phpIniFile = this.GetIniFilePath(); string siteAlias = this.Deployment.getShortId(); // fastCgi settings in IIS can only be set at the HOSTS level // we found no way to set this at a web.config level. Microsoft.Web.Administration.Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection section = config.GetSection("system.webServer/fastCgi"); ConfigurationElement cfs = null; // Each fastCgi in IIS is a unique combination of RUNTIME_PATH|ARGUMENTS, try to find // the current application. foreach (ConfigurationElement sec in section.GetCollection()) { // Cada aplicación se identifica de manera única por la combincación de atributo y path de ejecución. if (sec.HasValue("arguments", siteAlias) && sec.HasValue("fullPath", phpRuntime)) { cfs = sec; break; } } // We need to keep track if the element already existed // in the configuration, or it is new. bool addApplication = false; ConfigurationElementCollection elems = section.GetCollection(); if (cfs == null) { cfs = elems.CreateElement("application"); addApplication = true; } // In this deployment we are not really passing // any argments to PHP, simply use the site Alias to // isolate each PHP site. // OJO: PONER EL SITE ALIAS AQUÍ NO ES ALGO // GRATUITO. LUEGO EN EL WEB.CONFIG DE LA PROPIA // APLICACIÓN DEBE ESTAR EXACTAMENTE IGUAL. cfs.SetAttributeValue("arguments", siteAlias); // Set reasonable defaults, even if the user configuration says differently var instanceMaxRequests = this.PhpSettings.instanceMaxRequests > 200 ? this.PhpSettings.instanceMaxRequests : 10000; var maxInstances = this.PhpSettings.maxInstances > 3 ? this.PhpSettings.maxInstances : 10; var activityTimeout = this.PhpSettings.activityTimeout > 100 ? this.PhpSettings.activityTimeout : 600; var requestTimeout = this.PhpSettings.requestTimeout > 60 ? this.PhpSettings.requestTimeout : 300; // Ensure that all values are within the limits if (maxInstances > limits.FastCgiMaxInstances && limits.FastCgiMaxInstances > 0) { maxInstances = limits.FastCgiMaxInstances.Value; } // Runtime Path. cfs.SetAttributeValue("fullPath", phpRuntime); cfs.SetAttributeValue("maxInstances", maxInstances); cfs.SetAttributeValue("activityTimeout", activityTimeout); cfs.SetAttributeValue("requestTimeout", requestTimeout); cfs.SetAttributeValue("instanceMaxRequests", instanceMaxRequests); // Make sure that changes to PHP.ini are refreshed properly if (File.Exists(iniFilePath)) { cfs.SetAttributeValue("monitorChangesTo", iniFilePath); } // Este setting no sirve para nada según -E- de MS porque // la implementación de FastCGI está mal hecha en IIS. // Los eventos internos de señal no se llegan a ejecutar nunca, // lo único que consigues es demorar el cierre de instancias. cfs.SetAttributeValue("signalBeforeTerminateSeconds", 0); if (!File.Exists(phpIniFile)) { throw new Exception("PHP.ini file not found. This will break the IIS FastCgiModule when using monitorChangesTo feature."); } // Retrieve the environment variables. ConfigurationElement cfgEnvironment = cfs.GetChildElement("environmentVariables"); ConfigurationElementCollection a = cfgEnvironment.GetCollection(); // This is fastcgi specific. a.AddOrUpdateConfigurationElementInCollection("PHP_FCGI_MAX_REQUESTS", instanceMaxRequests.ToString()); // Add all the environment variables. var environmentVariables = this.GetEnvironmentVariables(); foreach (var p in environmentVariables) { a.AddOrUpdateConfigurationElementInCollection(p.Key, p.Value); } if (addApplication) { elems.Add(cfs); } // Cleanup any fastCgi applications that point to non-existent handlers // see the comments in FastCgiRemove() as to why this is here. var fastCgiHandlers = section.GetCollection(); foreach (ConfigurationElement sec in fastCgiHandlers.ToList()) { if (sec.RawAttributes.Keys.Contains("fullPath")) { string fullPath = sec.GetAttributeValue("fullPath").ToString(); if (!File.Exists(fullPath)) { this.Logger.LogInfo(true, "Removed stale fastCgi handler {0}", fullPath); fastCgiHandlers.Remove(sec); } } } UtilsIis.CommitChanges(serverManager); } }