public void Install(PrivateKey pk, Crt crt, IEnumerable <Crt> chain, IPkiTool cp) { var bindings = IisHelper.ResolveSiteBindings(WebSiteRef); var existing = IisHelper.ResolveSiteBindings( BindingAddress, BindingPort, BindingHost, bindings).ToArray(); if (existing?.Length > 0 && !Force) { throw new InvalidOperationException( "found existing conflicting bindings for target site;" + " use Force parameter to overwrite"); } // TODO: should we expose these as optional params to be overridden by user? var storeLocation = StoreLocation.LocalMachine; var storeName = StoreName.My; var cert = ImportCertificate(pk, crt, chain, cp, storeName, storeLocation, CertificateFriendlyName); var certStore = Enum.GetName(typeof(StoreName), storeName); var certHash = cert.GetCertHash(); if (existing?.Length > 0) { foreach (var oldBinding in existing) { if (BindingHostRequired.HasValue) { oldBinding.BindingHostRequired = BindingHostRequired; } IisHelper.UpdateSiteBinding(oldBinding, certStore, certHash); } } else { var firstBinding = bindings.First(); var newBinding = new IisWebSiteBinding { // Copy over some existing site info SiteId = firstBinding.SiteId, SiteName = firstBinding.SiteName, SiteRoot = firstBinding.SiteRoot, // New binding specifics BindingProtocol = "https", BindingAddress = this.BindingAddress, BindingPort = this.BindingPort.ToString(), BindingHost = this.BindingHost, BindingHostRequired = this.BindingHostRequired, }; IisHelper.CreateSiteBinding(newBinding, certStore, certHash); } }
public void TestHandleCreateAndCleanUpFiles() { var r = new Random(); var bn = new byte[10]; var bv = new byte[10]; r.NextBytes(bn); r.NextBytes(bv); var rn = BitConverter.ToString(bn); var rv = BitConverter.ToString(bv); var c = new HttpChallenge(AcmeProtocol.CHALLENGE_TYPE_HTTP, new HttpChallengeAnswer()) { Token = "FOOBAR", FileUrl = $"http://foobar.acmetesting.zyborg.io/utest/{rn}", FilePath = $"utest/{rn}", FileContent = rv, }; var awsParams = new AwsCommonParams(); awsParams.InitParams(_handlerParams); var p = GetProvider(); using (var h = p.GetHandler(c, _handlerParams)) { var sites = IisHelper.ListDistinctHttpWebSites(); Assert.IsNotNull(sites); var site = sites.First(x => x.SiteName == _handlerParams.WebSiteRef); Assert.IsNotNull(site); var fullPath = Environment.ExpandEnvironmentVariables( Path.Combine(site.SiteRoot, c.FilePath)); // Assert test file does not exist Assert.IsFalse(File.Exists(fullPath)); // Create the record... h.Handle(new ChallengeHandlingContext(c)); // ...and assert it does exist Assert.IsTrue(File.Exists(fullPath)); Assert.AreEqual(c.FileContent, File.ReadAllText(fullPath)); // Clean up the record... h.CleanUp(new ChallengeHandlingContext(c)); // ...and assert it does not exist once more Assert.IsFalse(File.Exists(fullPath)); } }
private void EditFile(HttpChallenge httpChallenge, bool delete, TextWriter msg) { IisWebSiteBinding site = IisHelper.ResolveSingleSite(WebSiteRef, IisHelper.ListDistinctHttpWebSites()); var siteRoot = site.SiteRoot; if (!string.IsNullOrEmpty(OverrideSiteRoot)) { siteRoot = OverrideSiteRoot; } if (string.IsNullOrEmpty(siteRoot)) { throw new InvalidOperationException("missing root path for resolve site") .With(nameof(IisWebSiteBinding.SiteId), site.SiteId) .With(nameof(IisWebSiteBinding.SiteName), site.SiteName); } // IIS-configured Site Root can use env vars siteRoot = Environment.ExpandEnvironmentVariables(siteRoot); // Make sure we're using the canonical full path siteRoot = Path.GetFullPath(siteRoot); // We need to strip off any leading '/' in the path var filePath = httpChallenge.FilePath; if (filePath.StartsWith("/")) { filePath = filePath.Substring(1); } var fullFilePath = Path.Combine(siteRoot, filePath); var fullDirPath = Path.GetDirectoryName(fullFilePath); var fullConfigPath = Path.Combine(fullDirPath, "web.config"); // This meta-data file will be placed next to the actual // Challenge answer content file and it captures some details // that we need in order to properly clean up the handling of // this Challenge after it has been submitted var fullMetaPath = $"{fullFilePath}-acmesharp_meta"; // Check if user is running with elevated privs and warn if not if (!IisHelper.IsAdministrator()) { Console.Error.WriteLine("WARNING: You are not running with elelvated privileges."); Console.Error.WriteLine(" Write access may be denied to the destination."); } if (delete) { bool skipLocalWebConfig = SkipLocalWebConfig; List <string> dirsCreated = null; // First see if there's a meta file there to help us out if (File.Exists(fullMetaPath)) { var meta = JsonHelper.Load <IisChallengeHandlerMeta>( File.ReadAllText(fullMetaPath)); skipLocalWebConfig = meta.SkippedLocalWebConfig; dirsCreated = meta.DirsCreated; } // Get rid of the Challenge answer content file if (File.Exists(fullFilePath)) { File.Delete(fullFilePath); msg.WriteLine("* Challenge response content has been removed from local file path"); msg.WriteLine(" at: [{0}]", fullFilePath); } // Get rid of web.config if necessary if (!skipLocalWebConfig && File.Exists(fullConfigPath)) { File.Delete(fullConfigPath); msg.WriteLine("* Local web.config has been removed from local file path"); msg.WriteLine(" at: [{0}]", fullFilePath); } // Get rid of the meta file so that we can clean up the dirs if (File.Exists(fullMetaPath)) { File.Delete(fullMetaPath); } // Walk up the tree if needed if (dirsCreated?.Count > 0) { var dirsDeleted = new List <string>(); dirsCreated.Reverse(); foreach (var dir in dirsCreated) { if (Directory.Exists(dir)) { if (Directory.GetFileSystemEntries(dir).Length == 0) { Directory.Delete(dir); dirsDeleted.Add(dir); } } } if (dirsDeleted.Count > 0) { msg.WriteLine("* Removed the following directories:"); foreach (var dd in dirsDeleted) { msg.WriteLine(" - [{0}]", dd); } } } } else { // Figure out which dirs we have to create so // we can capture and clean it up later on var meta = new IisChallengeHandlerMeta { WasAdmin = IisHelper.IsAdministrator(), SkippedLocalWebConfig = SkipLocalWebConfig, DirsCreated = new List <string>(), }; // In theory this ascending of the dir path should work // just fine, but just in case, this path segment counter // should gaurd against the possibility of an infinite loop var dirLimit = 100; var testDir = fullDirPath; while (!Directory.Exists(testDir)) { // Sanity check against an infinite loop if (--dirLimit <= 0) { throw new Exception("Unexpected directory path segment count reached") .With(nameof(dirLimit), "100") .With(nameof(fullDirPath), fullDirPath) .With(nameof(testDir), testDir) .With($"first-{nameof(meta.DirsCreated)}", meta.DirsCreated[0]) .With($"last-{nameof(meta.DirsCreated)}", meta.DirsCreated[meta.DirsCreated.Count - 1]); } if (Path.GetFullPath(testDir) == siteRoot) { break; } // Add to the top of the list meta.DirsCreated.Insert(0, testDir); // Move to the parent testDir = Path.GetDirectoryName(testDir); } foreach (var dir in meta.DirsCreated) { Directory.CreateDirectory(dir); } File.WriteAllText(fullFilePath, httpChallenge.FileContent); File.WriteAllText(fullMetaPath, JsonHelper.Save(meta)); msg.WriteLine("* Challenge response content has been written to local file path"); msg.WriteLine(" at: [{0}]", fullFilePath); msg.WriteLine("* Challenge response should be accessible with a MIME type of [text/json]"); msg.WriteLine(" at: [{0}]", httpChallenge.FileUrl); if (!SkipLocalWebConfig) { var t = typeof(IisChallengeHandler); var r = $"{t.Namespace}.{t.Name}-WebConfig"; using (Stream rs = t.Assembly.GetManifestResourceStream(r)) { using (var fs = new FileStream(fullConfigPath, FileMode.Create)) { rs.CopyTo(fs); } } msg.WriteLine("* Local web.config has been created to serve response file as JSON"); msg.WriteLine(" however, you may need to adjust this file for your environment"); msg.WriteLine(" at: [{0}]", httpChallenge.FileUrl); } else { msg.WriteLine("* Local web.config file creation has been skipped!"); msg.WriteLine(" You may need to manually adjust your configuration to serve the"); msg.WriteLine(" Challenge Response file with a MIME type of [text/json]"); } } }