mirror of
https://github.com/certbot/certbot.git
synced 2026-08-04 20:42:47 +02:00
Allow http block inclusion at top-level nginx.conf (#10178)
Fixes #9928. Thanks to @vanviethieuanh for tracking down the source of the issue!
This commit is contained in:
@@ -77,11 +77,11 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
"""
|
"""
|
||||||
included = False
|
included = False
|
||||||
include_directive = ['\n', 'include', ' ', self.challenge_conf]
|
include_directive = ['\n', 'include', ' ', self.challenge_conf]
|
||||||
root = self.configurator.parser.config_root
|
http_path = self.configurator.parser.http_path
|
||||||
|
|
||||||
bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128']
|
bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128']
|
||||||
|
|
||||||
main = self.configurator.parser.parsed[root]
|
main = self.configurator.parser.parsed[http_path]
|
||||||
# insert include directive
|
# insert include directive
|
||||||
for line in main:
|
for line in main:
|
||||||
if line[0] == ['http']:
|
if line[0] == ['http']:
|
||||||
@@ -126,6 +126,7 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
body.insert(0, bucket_directive)
|
body.insert(0, bucket_directive)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
root = self.configurator.parser.config_root
|
||||||
if not included:
|
if not included:
|
||||||
raise errors.MisconfigurationError(
|
raise errors.MisconfigurationError(
|
||||||
'Certbot could not find a block to include '
|
'Certbot could not find a block to include '
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""NginxParser is a member object of the NginxConfigurator class."""
|
"""NginxParser is a member object of the NginxConfigurator class."""
|
||||||
|
from __future__ import annotations
|
||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import glob
|
import glob
|
||||||
@@ -41,6 +42,7 @@ class NginxParser:
|
|||||||
self.parsed: Dict[str, UnspacedList] = {}
|
self.parsed: Dict[str, UnspacedList] = {}
|
||||||
self.root = os.path.abspath(root)
|
self.root = os.path.abspath(root)
|
||||||
self.config_root = self._find_config_root()
|
self.config_root = self._find_config_root()
|
||||||
|
self._http_path: str | None = None
|
||||||
|
|
||||||
# Parse nginx.conf and included files.
|
# Parse nginx.conf and included files.
|
||||||
# TODO: Check sites-available/ as well. For now, the configurator does
|
# TODO: Check sites-available/ as well. For now, the configurator does
|
||||||
@@ -54,6 +56,14 @@ class NginxParser:
|
|||||||
self.parsed = {}
|
self.parsed = {}
|
||||||
self._parse_recursively(self.config_root)
|
self._parse_recursively(self.config_root)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def http_path(self) -> str:
|
||||||
|
""" Filepath of file containing nginx http block. Set in self._parse_recursively
|
||||||
|
"""
|
||||||
|
if self._http_path is None:
|
||||||
|
raise errors.MisconfigurationError('No nginx http block found')
|
||||||
|
return self._http_path
|
||||||
|
|
||||||
def _parse_recursively(self, filepath: str) -> None:
|
def _parse_recursively(self, filepath: str) -> None:
|
||||||
"""Parses nginx config files recursively by looking at 'include'
|
"""Parses nginx config files recursively by looking at 'include'
|
||||||
directives inside 'http' and 'server' blocks. Note that this only
|
directives inside 'http' and 'server' blocks. Note that this only
|
||||||
@@ -64,13 +74,16 @@ class NginxParser:
|
|||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-nested-blocks
|
# pylint: disable=too-many-nested-blocks
|
||||||
filepath = self.abs_path(filepath)
|
filepath = self.abs_path(filepath)
|
||||||
trees = self._parse_files(filepath)
|
trees: dict[str, UnspacedList] = self._parse_files(filepath)
|
||||||
for tree in trees:
|
for filename, tree in trees.items():
|
||||||
for entry in tree:
|
for entry in tree:
|
||||||
if _is_include_directive(entry):
|
if _is_include_directive(entry):
|
||||||
# Parse the top-level included file
|
# Parse the top-level included file
|
||||||
self._parse_recursively(entry[1])
|
self._parse_recursively(entry[1])
|
||||||
elif entry[0] == ['http'] or entry[0] == ['server']:
|
elif entry[0] == ['http'] or entry[0] == ['server']:
|
||||||
|
# Note http block location for http_01.py
|
||||||
|
if entry[0] == ['http']:
|
||||||
|
self._http_path = filename
|
||||||
# Look for includes in the top-level 'http'/'server' context
|
# Look for includes in the top-level 'http'/'server' context
|
||||||
for subentry in entry[1]:
|
for subentry in entry[1]:
|
||||||
if _is_include_directive(subentry):
|
if _is_include_directive(subentry):
|
||||||
@@ -193,35 +206,35 @@ class NginxParser:
|
|||||||
pass
|
pass
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _parse_files(self, filepath: str, override: bool = False) -> List[UnspacedList]:
|
def _parse_files(self, filepath: str, override: bool = False) -> dict[str, UnspacedList]:
|
||||||
"""Parse files from a glob
|
"""Parse files from a glob
|
||||||
|
|
||||||
:param str filepath: Nginx config file path
|
:param str filepath: Nginx config file path
|
||||||
:param bool override: Whether to parse a file that has been parsed
|
:param bool override: Whether to parse a file that has been parsed
|
||||||
:returns: list of parsed tree structures
|
:returns: dict of parsed tree structures indexed by filename
|
||||||
:rtype: list
|
:rtype: dict[str, UnspacedList]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
files = glob.glob(filepath) # nginx on unix calls glob(3) for this
|
files: list[str] = glob.glob(filepath) # nginx on unix calls glob(3) for this
|
||||||
# XXX Windows nginx uses FindFirstFile, and
|
# XXX Windows nginx uses FindFirstFile, and
|
||||||
# should have a narrower call here
|
# should have a narrower call here
|
||||||
trees = []
|
trees: dict[str, UnspacedList] = {}
|
||||||
for item in files:
|
for filename in files:
|
||||||
if item in self.parsed and not override:
|
if filename in self.parsed and not override:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with open(item, "r", encoding="utf-8") as _file:
|
with open(filename, "r", encoding="utf-8") as _file:
|
||||||
parsed = nginxparser.load(_file)
|
parsed = nginxparser.load(_file)
|
||||||
self.parsed[item] = parsed
|
self.parsed[filename] = parsed
|
||||||
trees.append(parsed)
|
trees[filename] = parsed
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.warning("Could not open file: %s", item)
|
logger.warning("Could not open file: %s", filename)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
logger.warning("Could not read file: %s due to invalid "
|
logger.warning("Could not read file: %s due to invalid "
|
||||||
"character. Only UTF-8 encoding is "
|
"character. Only UTF-8 encoding is "
|
||||||
"supported.", item)
|
"supported.", filename)
|
||||||
except pyparsing.ParseException as err:
|
except pyparsing.ParseException as err:
|
||||||
logger.warning("Could not parse file: %s due to %s", item, err)
|
logger.warning("Could not parse file: %s due to %s", filename, err)
|
||||||
return trees
|
return trees
|
||||||
|
|
||||||
def _find_config_root(self) -> str:
|
def _find_config_root(self) -> str:
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||||||
['server_name', 'example.*'],
|
['server_name', 'example.*'],
|
||||||
['listen', '5001', 'ssl'],
|
['listen', '5001', 'ssl'],
|
||||||
['#', parser.COMMENT]]]] == \
|
['#', parser.COMMENT]]]] == \
|
||||||
parsed[0]
|
parsed[filep]
|
||||||
|
|
||||||
def test_choose_vhosts_alias(self):
|
def test_choose_vhosts_alias(self):
|
||||||
self._test_choose_vhosts_common('alias', 'server_conf')
|
self._test_choose_vhosts_common('alias', 'server_conf')
|
||||||
|
|||||||
@@ -74,6 +74,15 @@ class NginxParserTest(util.NginxTest):
|
|||||||
nparser.parsed[nparser.abs_path(
|
nparser.parsed[nparser.abs_path(
|
||||||
'sites-enabled/example.com')]
|
'sites-enabled/example.com')]
|
||||||
|
|
||||||
|
def test_included_load(self):
|
||||||
|
# Test for when the root file doesn't have http in it
|
||||||
|
nparser = parser.NginxParser(self.config_path)
|
||||||
|
nparser.config_root = os.path.join(self.config_path, "nginx-include.conf")
|
||||||
|
nparser.load()
|
||||||
|
assert len(nparser.parsed) > 1
|
||||||
|
assert len(nparser.parsed[nparser.config_root]) == 4
|
||||||
|
assert os.path.join(self.config_path, "nginx.conf") in nparser.parsed
|
||||||
|
|
||||||
def test_abs_path(self):
|
def test_abs_path(self):
|
||||||
nparser = parser.NginxParser(self.config_path)
|
nparser = parser.NginxParser(self.config_path)
|
||||||
if os.name != 'nt':
|
if os.name != 'nt':
|
||||||
@@ -99,7 +108,7 @@ class NginxParserTest(util.NginxTest):
|
|||||||
['listen', '127.0.0.1'],
|
['listen', '127.0.0.1'],
|
||||||
['server_name', '.example.com'],
|
['server_name', '.example.com'],
|
||||||
['server_name', 'example.*']]]] == \
|
['server_name', 'example.*']]]] == \
|
||||||
parsed[0]
|
parsed[nparser.abs_path('sites-enabled/example.com.test')]
|
||||||
|
|
||||||
def test__do_for_subarray(self):
|
def test__do_for_subarray(self):
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
@@ -490,8 +499,8 @@ class NginxParserTest(util.NginxTest):
|
|||||||
nparser = parser.NginxParser(self.config_path)
|
nparser = parser.NginxParser(self.config_path)
|
||||||
path = nparser.abs_path('valid_unicode_comments.conf')
|
path = nparser.abs_path('valid_unicode_comments.conf')
|
||||||
parsed = nparser._parse_files(path) # pylint: disable=protected-access
|
parsed = nparser._parse_files(path) # pylint: disable=protected-access
|
||||||
assert ['server'] == parsed[0][2][0]
|
assert ['server'] == parsed[path][2][0]
|
||||||
assert ['listen', '80'] == parsed[0][2][1][3]
|
assert ['listen', '80'] == parsed[path][2][1][3]
|
||||||
|
|
||||||
def test_valid_unicode_roundtrip(self):
|
def test_valid_unicode_roundtrip(self):
|
||||||
"""This tests the parser's ability to load and save a config containing Unicode"""
|
"""This tests the parser's ability to load and save a config containing Unicode"""
|
||||||
@@ -507,7 +516,7 @@ class NginxParserTest(util.NginxTest):
|
|||||||
path = nparser.abs_path('invalid_unicode_comments.conf')
|
path = nparser.abs_path('invalid_unicode_comments.conf')
|
||||||
parsed = nparser._parse_files(path) # pylint: disable=protected-access
|
parsed = nparser._parse_files(path) # pylint: disable=protected-access
|
||||||
|
|
||||||
assert [] == parsed
|
assert {} == parsed
|
||||||
assert any(
|
assert any(
|
||||||
('invalid character' in output) and ('UTF-8' in output)
|
('invalid character' in output) and ('UTF-8' in output)
|
||||||
for output in log.output
|
for output in log.output
|
||||||
|
|||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
# just a comment on top
|
||||||
|
# so we're not at the very top
|
||||||
|
include nginx.conf;
|
||||||
|
# and another comment for fun
|
||||||
@@ -30,6 +30,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
3.1.0.
|
3.1.0.
|
||||||
* When adding ssl listen directives in nginx server blocks, IP addresses are now
|
* When adding ssl listen directives in nginx server blocks, IP addresses are now
|
||||||
preserved.
|
preserved.
|
||||||
|
* Nginx configurations can now have the http block in files other than the root (nginx.conf)
|
||||||
|
|
||||||
More details about these changes can be found on our GitHub repo.
|
More details about these changes can be found on our GitHub repo.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user