/// <summary> /// /// </summary> /// <param name="dir"></param> public void CleanUpDir(string dir) { if (!Directory.Exists(dir)) { return; } foreach (var difo in new DirectoryInfo(dir).EnumerateDirectories()) { foreach (var appDifo in difo.EnumerateDirectories()) { try { // Delete folders older than 1 month if ((DateTime.Now - appDifo.LastWriteTime).TotalDays > 30) { UtilsSystem.DeleteDirectory(appDifo.FullName, this.Logger); this.Logger.LogInfo(true, "Deleted temp dir: {0}", appDifo.FullName); } } catch (Exception e) { this.Logger.LogException(e); } } } }
public void undeploy(bool isUninstall = false) { // Not that we want to accidentally delete contents and files // for an application... if (!isUninstall) { return; } var diskSettings = this.DeployerSettings.castTo <DiskServiceSettings>(); var mounts = this.Deployment.GetSettingCollection <DiskStore>($"service.{diskSettings.id}"); foreach (var m in mounts.Values) { // Most of the time this directory will not exist, as the base storage deployer will already have // deleted the application folder. But for "local" installed applications, this removes // the symlinks. if (!string.IsNullOrWhiteSpace(m.junctionRealPath) && Directory.Exists(m.junctionRealPath)) { UtilsJunction.RemoveJunction(m.junctionRealPath); } if (Directory.Exists(m.path)) { UtilsSystem.DeleteDirectory(m.path, this.Logger); } } var baseStoragePath = this.GetStoragePath(diskSettings); if (!string.IsNullOrWhiteSpace(baseStoragePath) && Directory.Exists(baseStoragePath)) { UtilsSystem.DeleteDirectory(baseStoragePath, this.Logger); } }
/// <summary> /// Delete the artifact's source if it is remote /// </summary> /// <param name="artifact"></param> /// <param name="logger"></param> public static void DeleteIfRemote(this Artifact artifact, ILoggerInterface logger) { if (!artifact.isRemote) { return; } UtilsSystem.DeleteDirectory(artifact.localPath, logger); }
protected override void ProcessRecord() { ConsoleUtils.RunCode(() => { var logger = new logger.ConsoleLogger(); logger.SetVerbose(true); UtilsSystem.DeleteDirectory(this.Directory, logger, 30); }); }
/// <summary> /// /// </summary> /// <param name="settings"></param> protected void DeployFonts(AppBaseStorageDeployerSettings settings) { // Nada que desplegar if (settings.fonts?.Any() != true) { return; } // TODO: Newer windows versions allow for per-user font registration, we should implement that // because with this approach fonts are installed first come first-served, and cannot be updated // if necessary. Plus we don't know who owns a font, se we can't delete them on cleanup var utilsFont = new UtilsFont(); foreach (var font in settings.fonts.AsIterable()) { // Check that the provided path is a zip with the font var fontSourceZip = Path.Combine(this.Deployment.appPath, font.Path); if (Path.GetExtension(fontSourceZip) != ".zip") { throw new Exception($"Unable to install the font '{font.Path}' because fonts have to be in a .zip file."); } if (!File.Exists(fontSourceZip)) { throw new Exception($"Font file not found '{font.Path}'."); } // We use a know directory var fontTempPath = UtilsSystem.GetTempPath("font-" + Guid.NewGuid()); var fontPersitentTempPath = UtilsSystem.GetTempPath("font-chef"); try { ZipFile.ExtractToDirectory(fontSourceZip, fontTempPath); UtilsSystem.CopyFilesRecursively(new DirectoryInfo(fontTempPath), new DirectoryInfo(fontPersitentTempPath), true, true); utilsFont.InstallFont(fontPersitentTempPath); } finally { UtilsSystem.DeleteDirectory(fontTempPath, this.Logger, 8); // This will some fonts get locked by the native API without explanation try { UtilsSystem.DeleteDirectory(fontPersitentTempPath, this.Logger, 4); } catch { // ignored } } } }
public void TestSymlinksAreNotRemoved() { var logger = new TestLogsLogger(this, nameof(this.TestSymlinksAreNotRemoved)); string testPath = UtilsSystem.GetTempPath("symlink_test" + Guid.NewGuid()); var pathWebsite = Path.Combine(testPath, "website"); var pathContentsPersistent = Path.Combine(testPath, "content_store_persistent"); Directory.CreateDirectory(pathWebsite); Directory.CreateDirectory(pathContentsPersistent); Directory.CreateDirectory(Path.Combine(pathContentsPersistent, "empty_directory")); string linkedDir = Path.Combine(pathWebsite, "contents"); string linkedDir2 = Path.Combine(pathWebsite, "contents2", "contents"); UtilsSystem.EnsureDirectoryExists(linkedDir); UtilsSystem.EnsureDirectoryExists(linkedDir2); UtilsJunction.EnsureLink(linkedDir, pathContentsPersistent, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction); UtilsJunction.EnsureLink(linkedDir2, pathContentsPersistent, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink); Assert.True(UtilsJunction.IsJunctionOrSymlink(linkedDir)); Assert.True(UtilsJunction.IsJunctionOrSymlink(linkedDir2)); string fileInContentsPeristent = Path.Combine(pathContentsPersistent, "test.txt"); string fileInSymlinkDir = Path.Combine(linkedDir, "test.txt"); Assert.Equal(fileInContentsPeristent, UtilsJunction.ResolvePath(fileInSymlinkDir)); string fileInContentsPeristent2 = Path.Combine(pathContentsPersistent, "test2.txt"); string fileInSymlinkDir2 = Path.Combine(linkedDir2, "test2.txt"); Assert.Equal(fileInContentsPeristent2, UtilsJunction.ResolvePath(fileInSymlinkDir2)); File.WriteAllText(fileInSymlinkDir, "testfile"); File.WriteAllText(fileInSymlinkDir2, "testfile"); Assert.True(File.Exists(fileInSymlinkDir), $"File exists {fileInSymlinkDir}"); Assert.True(File.Exists(fileInSymlinkDir2), $"File exists {fileInSymlinkDir2}"); Assert.True(File.Exists(fileInContentsPeristent), $"File exists {fileInContentsPeristent}"); Assert.True(File.Exists(fileInContentsPeristent2), $"File exists {fileInContentsPeristent2}"); // If we delete the directory containing the symlink, the file still exists UtilsSystem.DeleteDirectory(pathWebsite, logger); Assert.False(Directory.Exists(pathWebsite), "Directory exists " + pathWebsite); Assert.False(File.Exists(fileInSymlinkDir), $"File exists {fileInSymlinkDir}"); Assert.True(File.Exists(fileInContentsPeristent), $"File exists {fileInContentsPeristent}"); Assert.False(File.Exists(fileInSymlinkDir2), $"File exists {fileInSymlinkDir2}"); Assert.True(File.Exists(fileInContentsPeristent2), $"File exists {fileInContentsPeristent2}"); Directory.Delete(testPath, true); }
public void TestResolveJunctionPath() { var logger = new TestLogsLogger(this, nameof(this.TestResolveJunctionPath)); string testPath = UtilsSystem.GetTempPath("symlink_test" + Guid.NewGuid()); // Probar resolución de nivel 1 string test1OriginalPath = UtilsSystem.EnsureDirectoryExists(Path.Combine(testPath, "test1"), true); string test1LinkPath = Path.Combine(testPath, "test1_link"); string test1JunctionPath = Path.Combine(testPath, "test1_junction"); UtilsJunction.EnsureLink(test1LinkPath, test1OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink); UtilsJunction.EnsureLink(test1JunctionPath, test1OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction); Assert.Equal(test1OriginalPath, UtilsJunction.ResolvePath(test1LinkPath)); Assert.Equal(test1OriginalPath, UtilsJunction.ResolvePath(test1JunctionPath)); // Probar resolución de subdirectorio existente y no existente string test2OriginalPath = UtilsSystem.EnsureDirectoryExists(Path.Combine(testPath, "test2"), true); string test2LinkPath = Path.Combine(testPath, "test2_link"); string test2JunctionPath = Path.Combine(testPath, "test2_junction"); UtilsJunction.EnsureLink(test2LinkPath, test2OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink); UtilsJunction.EnsureLink(test2JunctionPath, test2OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction); string test2LinkSubDir = UtilsSystem.EnsureDirectoryExists(Path.Combine(test2LinkPath, "sub1", "sub2"), true); string test2JunctionSubDir = UtilsSystem.EnsureDirectoryExists(Path.Combine(test2JunctionPath, "sub3", "sub4"), true); Assert.Equal(Path.Combine(test2OriginalPath, "sub1", "sub2"), UtilsJunction.ResolvePath(test2LinkSubDir)); Assert.Equal(Path.Combine(test2OriginalPath, "sub3", "sub4"), UtilsJunction.ResolvePath(test2JunctionSubDir)); // Ahora subdirectorios que no existen Assert.Equal(Path.Combine(test2OriginalPath, "sub4", "sub5"), UtilsJunction.ResolvePath(Path.Combine(test2LinkPath, "sub4", "sub5"))); Assert.Equal(Path.Combine(test2OriginalPath, "sub6", "sub7"), UtilsJunction.ResolvePath(Path.Combine(test2JunctionPath, "sub6", "sub7"))); // Ahora una cadena de enlaces dentro de otro enlace... string test3LinkSubDir = Path.Combine(test2LinkPath, "sub8"); UtilsSystem.EnsureDirectoryExists(Path.Combine(test2LinkPath, "test3"), true); UtilsJunction.EnsureLink(test3LinkSubDir, Path.Combine(test2LinkPath, "test3"), logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink); Assert.Equal(Path.Combine(test2OriginalPath, "test3"), UtilsJunction.ResolvePath(test3LinkSubDir)); UtilsSystem.DeleteDirectory(testPath, logger, 2); // Non existent and malformed network uri get reconstructed as-is string testNetworkUri = "\\\\147.83.73.25\\a\\b\\c\\\\d"; Assert.Equal(testNetworkUri, UtilsJunction.ResolvePath(testNetworkUri)); }
/// <inheritdoc cref="IDownloaderInterface"/> public Artifact PullFromId(string version, string preferredLocalArtifactPath) { if (string.IsNullOrWhiteSpace(version)) { version = this.Settings.path; } Artifact artifact = new Artifact { id = version, localPath = preferredLocalArtifactPath, isRemote = true }; // Use artifact temp path, or local system temporary directory. if (Directory.Exists(artifact.localPath)) { UtilsSystem.DeleteDirectory(artifact.localPath, this.Logger); } // The ID is the PATH to the local zip, so just unzip this.Logger.LogInfo(true, "Unzipping file...."); ZipFile.ExtractToDirectory(version, artifact.localPath); this.Logger.LogInfo(true, "Unzipping finished...."); artifact.obtainedAt = DateTime.UtcNow; artifact.artifactSettings = new ArtifactSettings(); // We will merge data from both git and settings file, local settigns file // will override anything from GIT (if available). artifact.artifactSettings.PopulateFromGit(artifact.localPath); artifact.artifactSettings.PopulateFromSettingsFile(artifact.localPath, this.Logger); artifact.artifactSettings.PopulateFromEnvironment(); // Branch name is critical to some deployment... populate with a no-branch-found.... if (string.IsNullOrEmpty(artifact.artifactSettings.branch)) { artifact.artifactSettings.branch = "no-branch-found"; this.Logger.LogInfo(true, "Could not identify git branch for artifact. Using default: 'no-branch-found'"); } return(artifact); }
/// <inheritdoc cref="IDownloaderInterface"/> public Artifact PullFromId(string version, string preferredLocalArtifactPath) { Artifact artifact = new Artifact { id = version, localPath = preferredLocalArtifactPath, isRemote = true }; // Use artifact temp path, or local system temporary directory. if (Directory.Exists(artifact.localPath)) { UtilsSystem.DeleteDirectory(artifact.localPath, this.Logger); } // Use the build version to pull the build information. Build build = this.Client.GetBuildFromVersion(version, this.Settings.username, this.Settings.project); // Make sure that the builds matches the current active branch, otherwise throw an exception if (!build.branch.Equals(this.Settings.branch, StringComparison.CurrentCultureIgnoreCase)) { throw new Exception($"Requested version '{version}' with branch '{build.branch}' does not belong to active settings branch '{this.Settings.branch}'"); } this.Client.DownloadSingleArtifactFromBuild(this.ApplicationId, build, this.Settings.artifact_regex, artifact.localPath, this.Logger); artifact.artifactSettings = new ArtifactSettings(); artifact.artifactSettings.PopulateFromSettingsFile(artifact.localPath, this.Logger); if (string.IsNullOrWhiteSpace(artifact.artifactSettings.branch)) { artifact.artifactSettings.branch = Convert.ToString(build.branch); } if (string.IsNullOrWhiteSpace(artifact.artifactSettings.commit_sha)) { artifact.artifactSettings.commit_sha = Convert.ToString(build.commitId); } return(artifact); }
public void TestCannotDeleteDirectoriesWithSmallDepth() { var directory = Directory.CreateDirectory("c:\\testdirectory"); var logger = new TestLogsLogger(this, nameof(this.TestCannotDeleteDirectoriesWithSmallDepth)); Assert.Throws <InvalidOperationException>(() => { UtilsSystem.DeleteDirectory(directory.FullName, logger); }); Assert.Throws <InvalidOperationException>(() => { UtilsSystem.DeleteDirectory(directory.FullName, logger, 5); }); Assert.Throws <InvalidOperationException>(() => { UtilsSystem.DeleteDirectory(directory.FullName, logger); }); directory.Delete(); }
/// <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); } } }