Generate a web.config file for IIS to serve properly the challenge files in webroot plugin (#9054)

* Generate a web.config file to serve properly challenge files with IIS

* Fix cleanup, add test

* FIx lint

* Do not overwrite existing web.config. Delete only web.config when it has been created by Certbot and is unmodified.

* Fix lint

* Update certbot/certbot/_internal/plugins/webroot.py

Co-authored-by: alexzorin <alex@zor.io>

* Add log

* Check for POSIX_MODE before web.config deletion attempt.

* Add documentation

* Update certbot/CHANGELOG.md

Co-authored-by: alexzorin <alex@zor.io>

* Update certbot/docs/using.rst

Co-authored-by: alexzorin <alex@zor.io>
This commit is contained in:
Adrien Ferrand
2021-10-24 08:37:40 +11:00
committed by GitHub
co-authored by alexzorin
parent 2375d87831
commit 94af235713
4 changed files with 80 additions and 1 deletions
+3 -1
View File
@@ -6,7 +6,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
### Added
*
* Certbot will generate a `web.config` file on Windows in the challenge path
when the `webroot` plugin is used, if one does not exist. This `web.config` file
lets IIS serve challenge files while they do not have an extension.
### Changed
@@ -9,6 +9,7 @@ from typing import List
from typing import Set
from acme import challenges
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot._internal import cli
@@ -24,6 +25,23 @@ from certbot.util import safe_open
logger = logging.getLogger(__name__)
_WEB_CONFIG_CONTENT = """\
<?xml version="1.0" encoding="UTF-8" ?>
<!--Generated by Certbot-->
<configuration>
<system.webServer>
<staticContent>
<mimeMap fileExtension="." mimeType="text/plain" />
</staticContent>
</system.webServer>
</configuration>
"""
# This list references the hashes of all versions of the web.config files that Certbot could
# have generated during an HTTP-01 challenge. If you modify _WEB_CONFIG_CONTENT, you MUST add
# the new hash in this list.
_WEB_CONFIG_SHA256SUMS = ["20c5ca1bd58fa8ad5f07a2f1be8b7cbb707c20fcb607a8fc8db9393952846a97"]
class Authenticator(common.Plugin, interfaces.Authenticator):
"""Webroot Authenticator."""
@@ -192,6 +210,19 @@ to serve all files under specified web root ({0})."""
finally:
filesystem.umask(old_umask)
# On Windows, generate a local web.config file that allows IIS to serve expose
# challenge files despite the fact they do not have a file extension.
if not filesystem.POSIX_MODE:
web_config_path = os.path.join(self.full_roots[name], "web.config")
if os.path.exists(web_config_path):
logger.info("A web.config file has not been created in "
"%s because another one already exists.", self.full_roots[name])
return
logger.info("Creating a web.config file in %s to allow IIS "
"to serve challenge files.", self.full_roots[name])
with safe_open(web_config_path, mode="w", chmod=0o644) as web_config:
web_config.write(_WEB_CONFIG_CONTENT)
def _get_validation_path(self, root_path, achall):
return os.path.join(root_path, achall.chall.encode("token"))
@@ -223,6 +254,19 @@ to serve all files under specified web root ({0})."""
os.remove(validation_path)
self.performed[root_path].remove(achall)
if not filesystem.POSIX_MODE:
web_config_path = os.path.join(root_path, "web.config")
if os.path.exists(web_config_path):
sha256sum = crypto_util.sha256sum(web_config_path)
if sha256sum in _WEB_CONFIG_SHA256SUMS:
logger.info("Cleaning web.config file generated by Certbot in %s.",
root_path)
os.remove(web_config_path)
else:
logger.info("Not cleaning up the web.config file in %s "
"because it is not generated by Certbot.", root_path)
not_removed: List[str] = []
while self._created_dirs:
path = self._created_dirs.pop()
+4
View File
@@ -147,6 +147,10 @@ your webserver configuration, you might need to modify the configuration
to ensure that files inside ``/.well-known/acme-challenge`` are served by
the webserver.
Under Windows, Certbot will generate a ``web.config`` file, if one does not already exist,
in ``/.well-known/acme-challenge`` in order to let IIS serve the challenge files even if they
do not have an extension.
Nginx
-----
+29
View File
@@ -86,6 +86,35 @@ class AuthenticatorTest(unittest.TestCase):
self.assertEqual(self.config.webroot_map[self.achall.domain],
self.path)
@unittest.skipIf(filesystem.POSIX_MODE, reason='Test specific to Windows')
@test_util.patch_display_util()
def test_webconfig_file_generate_and_cleanup(self, mock_get_utility):
mock_display = mock_get_utility()
mock_display.menu.return_value = (display_util.OK, 1,)
self.auth.perform([self.achall])
self.assertTrue(os.path.exists(os.path.join(self.root_challenge_path, "web.config")))
self.auth.cleanup([self.achall])
self.assertFalse(os.path.exists(os.path.join(self.root_challenge_path, "web.config")))
@unittest.skipIf(filesystem.POSIX_MODE, reason='Test specific to Windows')
@test_util.patch_display_util()
def test_foreign_webconfig_file_handling(self, mock_get_utility):
mock_display = mock_get_utility()
mock_display.menu.return_value = (display_util.OK, 1,)
challenge_path = os.path.join(self.path, ".well-known", "acme-challenge")
filesystem.makedirs(challenge_path)
webconfig_path = os.path.join(challenge_path, "web.config")
with open(webconfig_path, "w") as file:
file.write("something")
self.auth.perform([self.achall])
from certbot import crypto_util
webconfig_hash = crypto_util.sha256sum(webconfig_path)
from certbot._internal.plugins.webroot import _WEB_CONFIG_SHA256SUMS
self.assertTrue(webconfig_hash not in _WEB_CONFIG_SHA256SUMS)
@test_util.patch_display_util()
def test_webroot_from_list_help_and_cancel(self, mock_get_utility):
self.config.webroot_path = []