Compare commits
3 Commits
test-apach
...
test-upgra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aab31e4a3 | ||
|
|
37b53ebfab | ||
|
|
31130efa42 |
@@ -4,15 +4,11 @@ coverage:
|
||||
default: off
|
||||
linux:
|
||||
flags: linux
|
||||
# Fixed target instead of auto set by #7173, can
|
||||
# be removed when flags in Codecov are added back.
|
||||
target: 97.5
|
||||
target: auto
|
||||
threshold: 0.1
|
||||
base: auto
|
||||
windows:
|
||||
flags: windows
|
||||
# Fixed target instead of auto set by #7173, can
|
||||
# be removed when flags in Codecov are added back.
|
||||
target: 97.6
|
||||
target: auto
|
||||
threshold: 0.1
|
||||
base: auto
|
||||
|
||||
7
.github/stale.yml
vendored
7
.github/stale.yml
vendored
@@ -5,8 +5,7 @@ daysUntilStale: 365
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
# When changing this value, be sure to also update markComment below.
|
||||
daysUntilClose: 30
|
||||
daysUntilClose: 7
|
||||
|
||||
# Ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: true
|
||||
@@ -19,8 +18,8 @@ markComment: >
|
||||
We've made a lot of changes to Certbot since this issue was opened. If you
|
||||
still have this issue with an up-to-date version of Certbot, can you please
|
||||
add a comment letting us know? This helps us to better see what issues are
|
||||
still affecting our users. If there is no activity in the next 30 days, this
|
||||
issue will be automatically closed.
|
||||
still affecting our users. If there is no further activity, this issue will
|
||||
be automatically closed.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
closeComment: >
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -47,5 +47,3 @@ tests/letstest/venv/
|
||||
|
||||
# certbot tests
|
||||
.certbot_test_workspace
|
||||
**/assets/pebble*
|
||||
**/assets/challtestsrv*
|
||||
|
||||
47
.travis.yml
47
.travis.yml
@@ -16,9 +16,6 @@ before_script:
|
||||
# is a cap of on the number of simultaneous runs.
|
||||
branches:
|
||||
only:
|
||||
# apache-parser-v2 is a temporary branch for doing work related to
|
||||
# rewriting the parser in the Apache plugin.
|
||||
- apache-parser-v2
|
||||
- master
|
||||
- /^\d+\.\d+\.x$/
|
||||
- /^test-.*$/
|
||||
@@ -27,19 +24,37 @@ branches:
|
||||
not-on-master: ¬-on-master
|
||||
if: NOT (type = push AND branch = master)
|
||||
|
||||
# Jobs for the extended test suite are executed for cron jobs and pushes to
|
||||
# non-development branches. See the explanation for apache-parser-v2 above.
|
||||
# Jobs for the extended test suite are executed for cron jobs and pushes on non-master branches.
|
||||
extended-test-suite: &extended-test-suite
|
||||
if: type = cron OR (type = push AND branch NOT IN (apache-parser-v2, master))
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Main test suite
|
||||
- python: "2.7"
|
||||
env: ACME_SERVER=pebble TOXENV=integration
|
||||
sudo: required
|
||||
services: docker
|
||||
<<: *not-on-master
|
||||
- language: generic
|
||||
env: TOXENV=py27
|
||||
os: osx
|
||||
# Using this osx_image is a workaround for
|
||||
# https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798.
|
||||
osx_image: xcode10.2
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python2
|
||||
<<: *extended-test-suite
|
||||
- language: generic
|
||||
env: TOXENV=py3
|
||||
os: osx
|
||||
# Using this osx_image is a workaround for
|
||||
# https://travis-ci.community/t/xcode-8-3-homebrew-outdated-error/3798.
|
||||
osx_image: xcode10.2
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python3
|
||||
<<: *extended-test-suite
|
||||
|
||||
# container-based infrastructure
|
||||
sudo: false
|
||||
|
||||
@@ -47,6 +62,7 @@ addons:
|
||||
apt:
|
||||
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
|
||||
- python-dev
|
||||
- python-virtualenv
|
||||
- gcc
|
||||
- libaugeas0
|
||||
- libssl-dev
|
||||
@@ -56,12 +72,7 @@ addons:
|
||||
- nginx-light
|
||||
- openssl
|
||||
|
||||
# tools/pip_install.py is used to pin packages to a known working version
|
||||
# except in tests where the environment variable CERTBOT_NO_PIN is set.
|
||||
# virtualenv is listed here explicitly to make sure it is upgraded when
|
||||
# CERTBOT_NO_PIN is set to work around failures we've seen when using an older
|
||||
# version of virtualenv.
|
||||
install: "tools/pip_install.py -U codecov tox virtualenv"
|
||||
install: "$(command -v pip || command -v pip3) install codecov tox"
|
||||
script: tox
|
||||
|
||||
after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux'
|
||||
|
||||
@@ -15,7 +15,6 @@ Authors
|
||||
* [Alex Gaynor](https://github.com/alex)
|
||||
* [Alex Halderman](https://github.com/jhalderm)
|
||||
* [Alex Jordan](https://github.com/strugee)
|
||||
* [Alex Zorin](https://github.com/alexzorin)
|
||||
* [Amjad Mashaal](https://github.com/TheNavigat)
|
||||
* [Andrew Murray](https://github.com/radarhere)
|
||||
* [Anselm Levskaya](https://github.com/levskaya)
|
||||
|
||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -2,24 +2,7 @@
|
||||
|
||||
Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## 0.37.0 - master
|
||||
|
||||
### Added
|
||||
|
||||
* Turn off session tickets for apache plugin by default
|
||||
* acme: Authz deactivation added to `acme` module.
|
||||
|
||||
### Changed
|
||||
|
||||
*
|
||||
|
||||
### Fixed
|
||||
|
||||
*
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 0.36.0 - 2019-07-11
|
||||
## 0.36.0 - master
|
||||
|
||||
### Added
|
||||
|
||||
@@ -28,20 +11,13 @@ More details about these changes can be found on our GitHub repo.
|
||||
|
||||
### Changed
|
||||
|
||||
* Support for Ubuntu 14.04 Trusty has been removed.
|
||||
* Update the 'manage your account' help to be more generic.
|
||||
* The error message when Certbot's Apache plugin is unable to modify your
|
||||
Apache configuration has been improved.
|
||||
* Certbot's config_changes subcommand has been deprecated and will be
|
||||
removed in a future release.
|
||||
* `certbot config_changes` no longer accepts a --num parameter.
|
||||
* The functions `certbot.plugins.common.Installer.view_config_changes` and
|
||||
`certbot.reverter.Reverter.view_config_changes` have been deprecated and will
|
||||
be removed in a future release.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Replace some unnecessary platform-specific line separation.
|
||||
*
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
@@ -135,10 +111,6 @@ More details about these changes can be found on our GitHub repo.
|
||||
`malformed` error to be received from the ACME server.
|
||||
* Linode DNS plugin now supports api keys created from their new panel
|
||||
at [cloud.linode.com](https://cloud.linode.com)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed Google DNS Challenge issues when private zones exist
|
||||
* Adding a warning noting that future versions of Certbot will automatically configure the
|
||||
webserver so that all requests redirect to secure HTTPS access. You can control this
|
||||
behavior and disable this warning with the --redirect and --no-redirect flags.
|
||||
|
||||
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
FROM python:2-alpine3.9
|
||||
|
||||
ENTRYPOINT [ "certbot" ]
|
||||
EXPOSE 80 443
|
||||
VOLUME /etc/letsencrypt /var/lib/letsencrypt
|
||||
WORKDIR /opt/certbot
|
||||
|
||||
COPY CHANGELOG.md README.rst setup.py src/
|
||||
|
||||
# Generate constraints file to pin dependency versions
|
||||
COPY letsencrypt-auto-source/pieces/dependency-requirements.txt .
|
||||
COPY tools /opt/certbot/tools
|
||||
RUN sh -c 'cat dependency-requirements.txt | /opt/certbot/tools/strip_hashes.py > unhashed_requirements.txt'
|
||||
RUN sh -c 'cat tools/dev_constraints.txt unhashed_requirements.txt | /opt/certbot/tools/merge_requirements.py > docker_constraints.txt'
|
||||
|
||||
COPY acme src/acme
|
||||
COPY certbot src/certbot
|
||||
|
||||
RUN apk add --no-cache --virtual .certbot-deps \
|
||||
libffi \
|
||||
libssl1.1 \
|
||||
openssl \
|
||||
ca-certificates \
|
||||
binutils
|
||||
RUN apk add --no-cache --virtual .build-deps \
|
||||
gcc \
|
||||
linux-headers \
|
||||
openssl-dev \
|
||||
musl-dev \
|
||||
libffi-dev \
|
||||
&& pip install -r /opt/certbot/dependency-requirements.txt \
|
||||
&& pip install --no-cache-dir --no-deps \
|
||||
--editable /opt/certbot/src/acme \
|
||||
--editable /opt/certbot/src \
|
||||
&& apk del .build-deps
|
||||
@@ -1,5 +1,5 @@
|
||||
# This Dockerfile builds an image for development.
|
||||
FROM debian:buster
|
||||
FROM ubuntu:xenial
|
||||
|
||||
# Note: this only exposes the port to other docker containers.
|
||||
EXPOSE 80 443
|
||||
|
||||
75
Dockerfile-old
Normal file
75
Dockerfile-old
Normal file
@@ -0,0 +1,75 @@
|
||||
# https://github.com/letsencrypt/letsencrypt/pull/431#issuecomment-103659297
|
||||
# it is more likely developers will already have ubuntu:trusty rather
|
||||
# than e.g. debian:jessie and image size differences are negligible
|
||||
FROM ubuntu:trusty
|
||||
MAINTAINER Jakub Warmuz <jakub@warmuz.org>
|
||||
MAINTAINER William Budington <bill@eff.org>
|
||||
|
||||
# Note: this only exposes the port to other docker containers. You
|
||||
# still have to bind to 443@host at runtime, as per the ACME spec.
|
||||
EXPOSE 443
|
||||
|
||||
# TODO: make sure --config-dir and --work-dir cannot be changed
|
||||
# through the CLI (certbot-docker wrapper that uses standalone
|
||||
# authenticator and text mode only?)
|
||||
VOLUME /etc/letsencrypt /var/lib/letsencrypt
|
||||
|
||||
WORKDIR /opt/certbot
|
||||
|
||||
# no need to mkdir anything:
|
||||
# https://docs.docker.com/reference/builder/#copy
|
||||
# If <dest> doesn't exist, it is created along with all missing
|
||||
# directories in its path.
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
COPY letsencrypt-auto-source/letsencrypt-auto /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto
|
||||
RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
/tmp/* \
|
||||
/var/tmp/*
|
||||
|
||||
# the above is not likely to change, so by putting it further up the
|
||||
# Dockerfile we make sure we cache as much as possible
|
||||
|
||||
|
||||
COPY setup.py README.rst CHANGELOG.md MANIFEST.in letsencrypt-auto-source/pieces/pipstrap.py /opt/certbot/src/
|
||||
|
||||
# all above files are necessary for setup.py and venv setup, however,
|
||||
# package source code directory has to be copied separately to a
|
||||
# subdirectory...
|
||||
# https://docs.docker.com/reference/builder/#copy: "If <src> is a
|
||||
# directory, the entire contents of the directory are copied,
|
||||
# including filesystem metadata. Note: The directory itself is not
|
||||
# copied, just its contents." Order again matters, three files are far
|
||||
# more likely to be cached than the whole project directory
|
||||
|
||||
COPY certbot /opt/certbot/src/certbot/
|
||||
COPY acme /opt/certbot/src/acme/
|
||||
COPY certbot-apache /opt/certbot/src/certbot-apache/
|
||||
COPY certbot-nginx /opt/certbot/src/certbot-nginx/
|
||||
|
||||
|
||||
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv
|
||||
|
||||
# PATH is set now so pipstrap upgrades the correct (v)env
|
||||
ENV PATH /opt/certbot/venv/bin:$PATH
|
||||
RUN /opt/certbot/venv/bin/python /opt/certbot/src/pipstrap.py && \
|
||||
/opt/certbot/venv/bin/pip install \
|
||||
-e /opt/certbot/src/acme \
|
||||
-e /opt/certbot/src \
|
||||
-e /opt/certbot/src/certbot-apache \
|
||||
-e /opt/certbot/src/certbot-nginx
|
||||
|
||||
# install in editable mode (-e) to save space: it's not possible to
|
||||
# "rm -rf /opt/certbot/src" (it's stays in the underlaying image);
|
||||
# this might also help in debugging: you can "docker run --entrypoint
|
||||
# bash" and investigate, apply patches, etc.
|
||||
|
||||
# set up certbot/letsencrypt wrapper to warn people about Dockerfile changes
|
||||
COPY tools/docker-warning.sh /opt/certbot/bin/certbot
|
||||
RUN ln -s /opt/certbot/bin/certbot /opt/certbot/bin/letsencrypt
|
||||
ENV PATH /opt/certbot/bin:$PATH
|
||||
|
||||
ENTRYPOINT [ "certbot" ]
|
||||
@@ -123,21 +123,6 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
|
||||
"""
|
||||
return self.update_registration(regr, update={'status': 'deactivated'})
|
||||
|
||||
def deactivate_authorization(self, authzr):
|
||||
# type: (messages.AuthorizationResource) -> messages.AuthorizationResource
|
||||
"""Deactivate authorization.
|
||||
|
||||
:param messages.AuthorizationResource authzr: The Authorization resource
|
||||
to be deactivated.
|
||||
|
||||
:returns: The Authorization resource that was deactivated.
|
||||
:rtype: `.AuthorizationResource`
|
||||
|
||||
"""
|
||||
body = messages.UpdateAuthorization(status='deactivated')
|
||||
response = self._post(authzr.uri, body)
|
||||
return self._authzr_from_response(response)
|
||||
|
||||
def _authzr_from_response(self, response, identifier=None, uri=None):
|
||||
authzr = messages.AuthorizationResource(
|
||||
body=messages.Authorization.from_json(response.json()),
|
||||
|
||||
@@ -637,14 +637,6 @@ class ClientTest(ClientTestBase):
|
||||
errors.PollError, self.client.poll_and_request_issuance,
|
||||
csr, authzrs, mintime=mintime, max_attempts=2)
|
||||
|
||||
def test_deactivate_authorization(self):
|
||||
authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED)
|
||||
self.response.json.return_value = authzb.to_json()
|
||||
authzr = self.client.deactivate_authorization(self.authzr)
|
||||
self.assertEqual(authzb, authzr.body)
|
||||
self.assertEqual(self.client.net.post.call_count, 1)
|
||||
self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0])
|
||||
|
||||
def test_check_cert(self):
|
||||
self.response.headers['Location'] = self.certr.uri
|
||||
self.response.content = CERT_DER
|
||||
|
||||
@@ -168,7 +168,6 @@ STATUS_VALID = Status('valid')
|
||||
STATUS_INVALID = Status('invalid')
|
||||
STATUS_REVOKED = Status('revoked')
|
||||
STATUS_READY = Status('ready')
|
||||
STATUS_DEACTIVATED = Status('deactivated')
|
||||
|
||||
|
||||
class IdentifierType(_Constant):
|
||||
@@ -472,7 +471,7 @@ class Authorization(ResourceBody):
|
||||
:ivar datetime.datetime expires:
|
||||
|
||||
"""
|
||||
identifier = jose.Field('identifier', decoder=Identifier.from_json, omitempty=True)
|
||||
identifier = jose.Field('identifier', decoder=Identifier.from_json)
|
||||
challenges = jose.Field('challenges', omitempty=True)
|
||||
combinations = jose.Field('combinations', omitempty=True)
|
||||
|
||||
@@ -502,12 +501,6 @@ class NewAuthorization(Authorization):
|
||||
resource = fields.Resource(resource_type)
|
||||
|
||||
|
||||
class UpdateAuthorization(Authorization):
|
||||
"""Update authorization."""
|
||||
resource_type = 'authz'
|
||||
resource = fields.Resource(resource_type)
|
||||
|
||||
|
||||
class AuthorizationResource(ResourceWithURI):
|
||||
"""Authorization Resource.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from setuptools import find_packages
|
||||
from setuptools.command.test import test as TestCommand
|
||||
import sys
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
||||
@@ -7,9 +7,6 @@ environment:
|
||||
|
||||
branches:
|
||||
only:
|
||||
# apache-parser-v2 is a temporary branch for doing work related to
|
||||
# rewriting the parser in the Apache plugin.
|
||||
- apache-parser-v2
|
||||
- master
|
||||
- /^\d+\.\d+\.x$/ # Version branches like X.X.X
|
||||
- /^test-.*$/
|
||||
@@ -30,8 +27,7 @@ install:
|
||||
# Upgrade pip to avoid warnings
|
||||
- "python -m pip install --upgrade pip"
|
||||
# Ready to install tox and coverage
|
||||
# tools/pip_install.py is used to pin packages to a known working version.
|
||||
- "python tools\\pip_install.py tox codecov"
|
||||
- "pip install tox codecov"
|
||||
|
||||
build: off
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@ recursive-include certbot_apache/tests/testdata *
|
||||
include certbot_apache/centos-options-ssl-apache.conf
|
||||
include certbot_apache/options-ssl-apache.conf
|
||||
recursive-include certbot_apache/augeas_lens *.aug
|
||||
recursive-include certbot_apache/tls_configs *.conf
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
""" Utility functions for certbot-apache plugin """
|
||||
import binascii
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from certbot import util
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -107,15 +105,3 @@ def parse_define_file(filepath, varname):
|
||||
def unique_id():
|
||||
""" Returns an unique id to be used as a VirtualHost identifier"""
|
||||
return binascii.hexlify(os.urandom(16)).decode("utf-8")
|
||||
|
||||
|
||||
def find_ssl_apache_conf(prefix):
|
||||
"""
|
||||
Find a TLS Apache config file in the dedicated storage.
|
||||
:param str prefix: prefix of the TLS Apache config file to find
|
||||
:return: the path the TLS Apache config file
|
||||
:rtype: str
|
||||
"""
|
||||
return pkg_resources.resource_filename(
|
||||
"certbot_apache",
|
||||
os.path.join("tls_configs", "{0}-options-ssl-apache.conf".format(prefix)))
|
||||
|
||||
207
certbot-apache/certbot_apache/augeas_configurator.py
Normal file
207
certbot-apache/certbot_apache/augeas_configurator.py
Normal file
@@ -0,0 +1,207 @@
|
||||
"""Class of Augeas Configurators."""
|
||||
import logging
|
||||
|
||||
|
||||
from certbot import errors
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_apache import constants
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AugeasConfigurator(common.Installer):
|
||||
"""Base Augeas Configurator class.
|
||||
|
||||
:ivar config: Configuration.
|
||||
:type config: :class:`~certbot.interfaces.IConfig`
|
||||
|
||||
:ivar aug: Augeas object
|
||||
:type aug: :class:`augeas.Augeas`
|
||||
|
||||
:ivar str save_notes: Human-readable configuration change notes
|
||||
:ivar reverter: saves and reverts checkpoints
|
||||
:type reverter: :class:`certbot.reverter.Reverter`
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AugeasConfigurator, self).__init__(*args, **kwargs)
|
||||
|
||||
# Placeholder for augeas
|
||||
self.aug = None
|
||||
|
||||
self.save_notes = ""
|
||||
|
||||
|
||||
def init_augeas(self):
|
||||
""" Initialize the actual Augeas instance """
|
||||
import augeas
|
||||
self.aug = augeas.Augeas(
|
||||
# specify a directory to load our preferred lens from
|
||||
loadpath=constants.AUGEAS_LENS_DIR,
|
||||
# Do not save backup (we do it ourselves), do not load
|
||||
# anything by default
|
||||
flags=(augeas.Augeas.NONE |
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD |
|
||||
augeas.Augeas.ENABLE_SPAN))
|
||||
# See if any temporary changes need to be recovered
|
||||
# This needs to occur before VirtualHost objects are setup...
|
||||
# because this will change the underlying configuration and potential
|
||||
# vhosts
|
||||
self.recovery_routine()
|
||||
|
||||
def check_parsing_errors(self, lens):
|
||||
"""Verify Augeas can parse all of the lens files.
|
||||
|
||||
:param str lens: lens to check for errors
|
||||
|
||||
:raises .errors.PluginError: If there has been an error in parsing with
|
||||
the specified lens.
|
||||
|
||||
"""
|
||||
error_files = self.aug.match("/augeas//error")
|
||||
|
||||
for path in error_files:
|
||||
# Check to see if it was an error resulting from the use of
|
||||
# the httpd lens
|
||||
lens_path = self.aug.get(path + "/lens")
|
||||
# As aug.get may return null
|
||||
if lens_path and lens in lens_path:
|
||||
msg = (
|
||||
"There has been an error in parsing the file {0} on line {1}: "
|
||||
"{2}".format(
|
||||
# Strip off /augeas/files and /error
|
||||
path[13:len(path) - 6],
|
||||
self.aug.get(path + "/line"),
|
||||
self.aug.get(path + "/message")))
|
||||
raise errors.PluginError(msg)
|
||||
|
||||
def ensure_augeas_state(self):
|
||||
"""Makes sure that all Augeas dom changes are written to files to avoid
|
||||
loss of configuration directives when doing additional augeas parsing,
|
||||
causing a possible augeas.load() resulting dom reset
|
||||
"""
|
||||
|
||||
if self.unsaved_files():
|
||||
self.save_notes += "(autosave)"
|
||||
self.save()
|
||||
|
||||
def unsaved_files(self):
|
||||
"""Lists files that have modified Augeas DOM but the changes have not
|
||||
been written to the filesystem yet, used by `self.save()` and
|
||||
ApacheConfigurator to check the file state.
|
||||
|
||||
:raises .errors.PluginError: If there was an error in Augeas, in
|
||||
an attempt to save the configuration, or an error creating a
|
||||
checkpoint
|
||||
|
||||
:returns: `set` of unsaved files
|
||||
"""
|
||||
save_state = self.aug.get("/augeas/save")
|
||||
self.aug.set("/augeas/save", "noop")
|
||||
# Existing Errors
|
||||
ex_errs = self.aug.match("/augeas//error")
|
||||
try:
|
||||
# This is a noop save
|
||||
self.aug.save()
|
||||
except (RuntimeError, IOError):
|
||||
self._log_save_errors(ex_errs)
|
||||
# Erase Save Notes
|
||||
self.save_notes = ""
|
||||
raise errors.PluginError(
|
||||
"Error saving files, check logs for more info.")
|
||||
|
||||
# Return the original save method
|
||||
self.aug.set("/augeas/save", save_state)
|
||||
|
||||
# Retrieve list of modified files
|
||||
# Note: Noop saves can cause the file to be listed twice, I used a
|
||||
# set to remove this possibility. This is a known augeas 0.10 error.
|
||||
save_paths = self.aug.match("/augeas/events/saved")
|
||||
|
||||
save_files = set()
|
||||
if save_paths:
|
||||
for path in save_paths:
|
||||
save_files.add(self.aug.get(path)[6:])
|
||||
return save_files
|
||||
|
||||
def save(self, title=None, temporary=False):
|
||||
"""Saves all changes to the configuration files.
|
||||
|
||||
This function first checks for save errors, if none are found,
|
||||
all configuration changes made will be saved. According to the
|
||||
function parameters. If an exception is raised, a new checkpoint
|
||||
was not created.
|
||||
|
||||
:param str title: The title of the save. If a title is given, the
|
||||
configuration will be saved as a new checkpoint and put in a
|
||||
timestamped directory.
|
||||
|
||||
:param bool temporary: Indicates whether the changes made will
|
||||
be quickly reversed in the future (ie. challenges)
|
||||
|
||||
"""
|
||||
save_files = self.unsaved_files()
|
||||
if save_files:
|
||||
self.add_to_checkpoint(save_files,
|
||||
self.save_notes, temporary=temporary)
|
||||
|
||||
self.save_notes = ""
|
||||
self.aug.save()
|
||||
|
||||
# Force reload if files were modified
|
||||
# This is needed to recalculate augeas directive span
|
||||
if save_files:
|
||||
for sf in save_files:
|
||||
self.aug.remove("/files/"+sf)
|
||||
self.aug.load()
|
||||
if title and not temporary:
|
||||
self.finalize_checkpoint(title)
|
||||
|
||||
def _log_save_errors(self, ex_errs):
|
||||
"""Log errors due to bad Augeas save.
|
||||
|
||||
:param list ex_errs: Existing errors before save
|
||||
|
||||
"""
|
||||
# Check for the root of save problems
|
||||
new_errs = self.aug.match("/augeas//error")
|
||||
# logger.error("During Save - %s", mod_conf)
|
||||
logger.error("Unable to save files: %s. Attempted Save Notes: %s",
|
||||
", ".join(err[13:len(err) - 6] for err in new_errs
|
||||
# Only new errors caused by recent save
|
||||
if err not in ex_errs), self.save_notes)
|
||||
|
||||
# Wrapper functions for Reverter class
|
||||
def recovery_routine(self):
|
||||
"""Revert all previously modified files.
|
||||
|
||||
Reverts all modified files that have not been saved as a checkpoint
|
||||
|
||||
:raises .errors.PluginError: If unable to recover the configuration
|
||||
|
||||
"""
|
||||
super(AugeasConfigurator, self).recovery_routine()
|
||||
# Need to reload configuration after these changes take effect
|
||||
self.aug.load()
|
||||
|
||||
def revert_challenge_config(self):
|
||||
"""Used to cleanup challenge configurations.
|
||||
|
||||
:raises .errors.PluginError: If unable to revert the challenge config.
|
||||
|
||||
"""
|
||||
self.revert_temporary_config()
|
||||
self.aug.load()
|
||||
|
||||
def rollback_checkpoints(self, rollback=1):
|
||||
"""Rollback saved checkpoints.
|
||||
|
||||
:param int rollback: Number of checkpoints to revert
|
||||
|
||||
:raises .errors.PluginError: If there is a problem with the input or
|
||||
the function is unable to correctly revert the configuration
|
||||
|
||||
"""
|
||||
super(AugeasConfigurator, self).rollback_checkpoints(rollback)
|
||||
self.aug.load()
|
||||
@@ -10,10 +10,16 @@ SSLEngine on
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
|
||||
SSLHonorCipherOrder on
|
||||
SSLSessionTickets off
|
||||
|
||||
SSLOptions +StrictRequire
|
||||
|
||||
# Add vhost name to log entries:
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
|
||||
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
|
||||
|
||||
#CustomLog /var/log/apache2/access.log vhost_combined
|
||||
#LogLevel warn
|
||||
#ErrorLog /var/log/apache2/error.log
|
||||
|
||||
# Always ensure Cookies have "Secure" set (JAH 2012/1)
|
||||
#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Apache Configurator."""
|
||||
"""Apache Configuration based off of Augeas Configurator."""
|
||||
# pylint: disable=too-many-lines
|
||||
import copy
|
||||
import fnmatch
|
||||
@@ -9,6 +9,7 @@ import time
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import pkg_resources
|
||||
import six
|
||||
|
||||
import zope.component
|
||||
@@ -22,13 +23,13 @@ from certbot import interfaces
|
||||
from certbot import util
|
||||
|
||||
from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import
|
||||
from certbot.compat import filesystem
|
||||
from certbot.compat import os
|
||||
from certbot.plugins import common
|
||||
from certbot.plugins.util import path_surgery
|
||||
from certbot.plugins.enhancements import AutoHSTSEnhancement
|
||||
|
||||
from certbot_apache import apache_util
|
||||
from certbot_apache import augeas_configurator
|
||||
from certbot_apache import constants
|
||||
from certbot_apache import display_ops
|
||||
from certbot_apache import http_01
|
||||
@@ -69,10 +70,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class ApacheConfigurator(common.Installer):
|
||||
class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||
"""Apache configurator.
|
||||
|
||||
State of Configurator: This code has been been tested and built for Ubuntu
|
||||
14.04 Apache 2.4 and it works for Ubuntu 12.04 Apache 2.2
|
||||
|
||||
:ivar config: Configuration.
|
||||
:type config: :class:`~certbot.interfaces.IConfig`
|
||||
|
||||
@@ -109,24 +113,14 @@ class ApacheConfigurator(common.Installer):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
def option(self, key):
|
||||
"""Get a value from options"""
|
||||
return self.options.get(key)
|
||||
|
||||
def pick_apache_config(self):
|
||||
"""
|
||||
Pick the appropriate TLS Apache configuration file for current version of Apache and OS.
|
||||
:return: the path to the TLS Apache configuration file to use
|
||||
:rtype: str
|
||||
"""
|
||||
# Disabling TLS session tickets is supported by Apache 2.4.11+.
|
||||
# So for old versions of Apache we pick a configuration without this option.
|
||||
if self.version < (2, 4, 11):
|
||||
return apache_util.find_ssl_apache_conf("old")
|
||||
return apache_util.find_ssl_apache_conf("current")
|
||||
|
||||
def _prepare_options(self):
|
||||
"""
|
||||
Set the values possibly changed by command line parameters to
|
||||
@@ -207,8 +201,6 @@ class ApacheConfigurator(common.Installer):
|
||||
self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]]
|
||||
# Temporary state for AutoHSTS enhancement
|
||||
self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]]
|
||||
# Reverter save notes
|
||||
self.save_notes = ""
|
||||
|
||||
# These will be set in the prepare function
|
||||
self._prepared = False
|
||||
@@ -239,6 +231,12 @@ class ApacheConfigurator(common.Installer):
|
||||
:raises .errors.PluginError: If there is any other error
|
||||
|
||||
"""
|
||||
# Perform the actual Augeas initialization to be able to react
|
||||
try:
|
||||
self.init_augeas()
|
||||
except ImportError:
|
||||
raise errors.NoInstallationError("Problem in Augeas installation")
|
||||
|
||||
self._prepare_options()
|
||||
|
||||
# Verify Apache is installed
|
||||
@@ -256,14 +254,16 @@ class ApacheConfigurator(common.Installer):
|
||||
raise errors.NotSupportedError(
|
||||
"Apache Version {0} not supported.".format(str(self.version)))
|
||||
|
||||
# Recover from previous crash before Augeas initialization to have the
|
||||
# correct parse tree from the get go.
|
||||
self.recovery_routine()
|
||||
# Perform the actual Augeas initialization to be able to react
|
||||
if not self._check_aug_version():
|
||||
raise errors.NotSupportedError(
|
||||
"Apache plugin support requires libaugeas0 and augeas-lenses "
|
||||
"version 1.2.0 or higher, please make sure you have you have "
|
||||
"those installed.")
|
||||
|
||||
self.parser = self.get_parser()
|
||||
|
||||
# Check for errors in parsing files with Augeas
|
||||
self.parser.check_parsing_errors("httpd.aug")
|
||||
self.check_parsing_errors("httpd.aug")
|
||||
|
||||
# Get all of the available vhosts
|
||||
self.vhosts = self.get_virtual_hosts()
|
||||
@@ -282,67 +282,6 @@ class ApacheConfigurator(common.Installer):
|
||||
" Apache configuration?".format(self.option("server_root")))
|
||||
self._prepared = True
|
||||
|
||||
def save(self, title=None, temporary=False):
|
||||
"""Saves all changes to the configuration files.
|
||||
|
||||
This function first checks for save errors, if none are found,
|
||||
all configuration changes made will be saved. According to the
|
||||
function parameters. If an exception is raised, a new checkpoint
|
||||
was not created.
|
||||
|
||||
:param str title: The title of the save. If a title is given, the
|
||||
configuration will be saved as a new checkpoint and put in a
|
||||
timestamped directory.
|
||||
|
||||
:param bool temporary: Indicates whether the changes made will
|
||||
be quickly reversed in the future (ie. challenges)
|
||||
|
||||
"""
|
||||
save_files = self.parser.unsaved_files()
|
||||
if save_files:
|
||||
self.add_to_checkpoint(save_files,
|
||||
self.save_notes, temporary=temporary)
|
||||
# Handle the parser specific tasks
|
||||
self.parser.save(save_files)
|
||||
if title and not temporary:
|
||||
self.finalize_checkpoint(title)
|
||||
|
||||
def recovery_routine(self):
|
||||
"""Revert all previously modified files.
|
||||
|
||||
Reverts all modified files that have not been saved as a checkpoint
|
||||
|
||||
:raises .errors.PluginError: If unable to recover the configuration
|
||||
|
||||
"""
|
||||
super(ApacheConfigurator, self).recovery_routine()
|
||||
# Reload configuration after these changes take effect if needed
|
||||
# ie. ApacheParser has been initialized.
|
||||
if self.parser:
|
||||
# TODO: wrap into non-implementation specific parser interface
|
||||
self.parser.aug.load()
|
||||
|
||||
def revert_challenge_config(self):
|
||||
"""Used to cleanup challenge configurations.
|
||||
|
||||
:raises .errors.PluginError: If unable to revert the challenge config.
|
||||
|
||||
"""
|
||||
self.revert_temporary_config()
|
||||
self.parser.aug.load()
|
||||
|
||||
def rollback_checkpoints(self, rollback=1):
|
||||
"""Rollback saved checkpoints.
|
||||
|
||||
:param int rollback: Number of checkpoints to revert
|
||||
|
||||
:raises .errors.PluginError: If there is a problem with the input or
|
||||
the function is unable to correctly revert the configuration
|
||||
|
||||
"""
|
||||
super(ApacheConfigurator, self).rollback_checkpoints(rollback)
|
||||
self.parser.aug.load()
|
||||
|
||||
def _verify_exe_availability(self, exe):
|
||||
"""Checks availability of Apache executable"""
|
||||
if not util.exe_exists(exe):
|
||||
@@ -350,11 +289,26 @@ class ApacheConfigurator(common.Installer):
|
||||
raise errors.NoInstallationError(
|
||||
'Cannot find Apache executable {0}'.format(exe))
|
||||
|
||||
def _check_aug_version(self):
|
||||
""" Checks that we have recent enough version of libaugeas.
|
||||
If augeas version is recent enough, it will support case insensitive
|
||||
regexp matching"""
|
||||
|
||||
self.aug.set("/test/path/testing/arg", "aRgUMeNT")
|
||||
try:
|
||||
matches = self.aug.match(
|
||||
"/test//*[self::arg=~regexp('argument', 'i')]")
|
||||
except RuntimeError:
|
||||
self.aug.remove("/test/path")
|
||||
return False
|
||||
self.aug.remove("/test/path")
|
||||
return matches
|
||||
|
||||
def get_parser(self):
|
||||
"""Initializes the ApacheParser"""
|
||||
# If user provided vhost_root value in command line, use it
|
||||
return parser.ApacheParser(
|
||||
self.option("server_root"), self.conf("vhost-root"),
|
||||
self.aug, self.option("server_root"), self.conf("vhost-root"),
|
||||
self.version, configurator=self)
|
||||
|
||||
def _wildcard_domain(self, domain):
|
||||
@@ -533,8 +487,8 @@ class ApacheConfigurator(common.Installer):
|
||||
# install SSLCertificateFile, SSLCertificateKeyFile,
|
||||
# and SSLCertificateChainFile directives
|
||||
set_cert_path = cert_path
|
||||
self.parser.aug.set(path["cert_path"][-1], cert_path)
|
||||
self.parser.aug.set(path["cert_key"][-1], key_path)
|
||||
self.aug.set(path["cert_path"][-1], cert_path)
|
||||
self.aug.set(path["cert_key"][-1], key_path)
|
||||
if chain_path is not None:
|
||||
self.parser.add_dir(vhost.path,
|
||||
"SSLCertificateChainFile", chain_path)
|
||||
@@ -546,8 +500,8 @@ class ApacheConfigurator(common.Installer):
|
||||
raise errors.PluginError("Please provide the --fullchain-path "
|
||||
"option pointing to your full chain file")
|
||||
set_cert_path = fullchain_path
|
||||
self.parser.aug.set(path["cert_path"][-1], fullchain_path)
|
||||
self.parser.aug.set(path["cert_key"][-1], key_path)
|
||||
self.aug.set(path["cert_path"][-1], fullchain_path)
|
||||
self.aug.set(path["cert_key"][-1], key_path)
|
||||
|
||||
# Enable the new vhost if needed
|
||||
if not vhost.enabled:
|
||||
@@ -847,7 +801,7 @@ class ApacheConfigurator(common.Installer):
|
||||
"""
|
||||
addrs = set()
|
||||
try:
|
||||
args = self.parser.aug.match(path + "/arg")
|
||||
args = self.aug.match(path + "/arg")
|
||||
except RuntimeError:
|
||||
logger.warning("Encountered a problem while parsing file: %s, skipping", path)
|
||||
return None
|
||||
@@ -865,7 +819,7 @@ class ApacheConfigurator(common.Installer):
|
||||
is_ssl = True
|
||||
|
||||
filename = apache_util.get_file_path(
|
||||
self.parser.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path)))
|
||||
self.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path)))
|
||||
if filename is None:
|
||||
return None
|
||||
|
||||
@@ -895,7 +849,7 @@ class ApacheConfigurator(common.Installer):
|
||||
# Make a list of parser paths because the parser_paths
|
||||
# dictionary may be modified during the loop.
|
||||
for vhost_path in list(self.parser.parser_paths):
|
||||
paths = self.parser.aug.match(
|
||||
paths = self.aug.match(
|
||||
("/files%s//*[label()=~regexp('%s')]" %
|
||||
(vhost_path, parser.case_i("VirtualHost"))))
|
||||
paths = [path for path in paths if
|
||||
@@ -905,7 +859,7 @@ class ApacheConfigurator(common.Installer):
|
||||
if not new_vhost:
|
||||
continue
|
||||
internal_path = apache_util.get_internal_aug_path(new_vhost.path)
|
||||
realpath = filesystem.realpath(new_vhost.filep)
|
||||
realpath = os.path.realpath(new_vhost.filep)
|
||||
if realpath not in file_paths:
|
||||
file_paths[realpath] = new_vhost.filep
|
||||
internal_paths[realpath].add(internal_path)
|
||||
@@ -1149,16 +1103,16 @@ class ApacheConfigurator(common.Installer):
|
||||
avail_fp = nonssl_vhost.filep
|
||||
ssl_fp = self._get_ssl_vhost_path(avail_fp)
|
||||
|
||||
orig_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" %
|
||||
orig_matches = self.aug.match("/files%s//* [label()=~regexp('%s')]" %
|
||||
(self._escape(ssl_fp),
|
||||
parser.case_i("VirtualHost")))
|
||||
|
||||
self._copy_create_ssl_vhost_skeleton(nonssl_vhost, ssl_fp)
|
||||
|
||||
# Reload augeas to take into account the new vhost
|
||||
self.parser.aug.load()
|
||||
self.aug.load()
|
||||
# Get Vhost augeas path for new vhost
|
||||
new_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" %
|
||||
new_matches = self.aug.match("/files%s//* [label()=~regexp('%s')]" %
|
||||
(self._escape(ssl_fp),
|
||||
parser.case_i("VirtualHost")))
|
||||
|
||||
@@ -1169,7 +1123,7 @@ class ApacheConfigurator(common.Installer):
|
||||
# Make Augeas aware of the new vhost
|
||||
self.parser.parse_file(ssl_fp)
|
||||
# Try to search again
|
||||
new_matches = self.parser.aug.match(
|
||||
new_matches = self.aug.match(
|
||||
"/files%s//* [label()=~regexp('%s')]" %
|
||||
(self._escape(ssl_fp),
|
||||
parser.case_i("VirtualHost")))
|
||||
@@ -1231,11 +1185,11 @@ class ApacheConfigurator(common.Installer):
|
||||
"""
|
||||
|
||||
if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")):
|
||||
fp = os.path.join(filesystem.realpath(self.option("vhost_root")),
|
||||
fp = os.path.join(os.path.realpath(self.option("vhost_root")),
|
||||
os.path.basename(non_ssl_vh_fp))
|
||||
else:
|
||||
# Use non-ssl filepath
|
||||
fp = filesystem.realpath(non_ssl_vh_fp)
|
||||
fp = os.path.realpath(non_ssl_vh_fp)
|
||||
|
||||
if fp.endswith(".conf"):
|
||||
return fp[:-(len(".conf"))] + self.option("le_vhost_ext")
|
||||
@@ -1319,8 +1273,8 @@ class ApacheConfigurator(common.Installer):
|
||||
"vhost for your HTTPS site located at {1} because they have "
|
||||
"the potential to create redirection loops.".format(
|
||||
vhost.filep, ssl_fp), reporter.MEDIUM_PRIORITY)
|
||||
self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0")
|
||||
self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0")
|
||||
self.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0")
|
||||
self.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0")
|
||||
|
||||
def _sift_rewrite_rules(self, contents):
|
||||
""" Helper function for _copy_create_ssl_vhost_skeleton to prepare the
|
||||
@@ -1395,7 +1349,7 @@ class ApacheConfigurator(common.Installer):
|
||||
"""
|
||||
|
||||
try:
|
||||
span_val = self.parser.aug.span(vhost.path)
|
||||
span_val = self.aug.span(vhost.path)
|
||||
except ValueError:
|
||||
logger.critical("Error while reading the VirtualHost %s from "
|
||||
"file %s", vhost.name, vhost.filep, exc_info=True)
|
||||
@@ -1430,13 +1384,13 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
def _update_ssl_vhosts_addrs(self, vh_path):
|
||||
ssl_addrs = set()
|
||||
ssl_addr_p = self.parser.aug.match(vh_path + "/arg")
|
||||
ssl_addr_p = self.aug.match(vh_path + "/arg")
|
||||
|
||||
for addr in ssl_addr_p:
|
||||
old_addr = obj.Addr.fromstring(
|
||||
str(self.parser.get_arg(addr)))
|
||||
ssl_addr = old_addr.get_addr_obj("443")
|
||||
self.parser.aug.set(addr, str(ssl_addr))
|
||||
self.aug.set(addr, str(ssl_addr))
|
||||
ssl_addrs.add(ssl_addr)
|
||||
|
||||
return ssl_addrs
|
||||
@@ -1455,14 +1409,14 @@ class ApacheConfigurator(common.Installer):
|
||||
vh_path, False)) > 1:
|
||||
directive_path = self.parser.find_dir(directive, None,
|
||||
vh_path, False)
|
||||
self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0]))
|
||||
self.aug.remove(re.sub(r"/\w*$", "", directive_path[0]))
|
||||
|
||||
def _remove_directives(self, vh_path, directives):
|
||||
for directive in directives:
|
||||
while self.parser.find_dir(directive, None, vh_path, False):
|
||||
directive_path = self.parser.find_dir(directive, None,
|
||||
vh_path, False)
|
||||
self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0]))
|
||||
self.aug.remove(re.sub(r"/\w*$", "", directive_path[0]))
|
||||
|
||||
def _add_dummy_ssl_directives(self, vh_path):
|
||||
self.parser.add_dir(vh_path, "SSLCertificateFile",
|
||||
@@ -1501,7 +1455,7 @@ class ApacheConfigurator(common.Installer):
|
||||
"""
|
||||
matches = self.parser.find_dir(
|
||||
"ServerAlias", start=vh_path, exclude=False)
|
||||
aliases = (self.parser.aug.get(match) for match in matches)
|
||||
aliases = (self.aug.get(match) for match in matches)
|
||||
return self.domain_in_names(aliases, target_name)
|
||||
|
||||
def _add_name_vhost_if_necessary(self, vhost):
|
||||
@@ -1684,7 +1638,7 @@ class ApacheConfigurator(common.Installer):
|
||||
if header_path:
|
||||
pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)'
|
||||
for match in header_path:
|
||||
if re.search(pat, self.parser.aug.get(match).lower()):
|
||||
if re.search(pat, self.aug.get(match).lower()):
|
||||
hsts_dirpath = match
|
||||
if not hsts_dirpath:
|
||||
err_msg = ("Certbot was unable to find the existing HSTS header "
|
||||
@@ -1698,7 +1652,7 @@ class ApacheConfigurator(common.Installer):
|
||||
# Our match statement was for string strict-transport-security, but
|
||||
# we need to update the value instead. The next index is for the value
|
||||
hsts_dirpath = hsts_dirpath.replace("arg[3]", "arg[4]")
|
||||
self.parser.aug.set(hsts_dirpath, hsts_maxage)
|
||||
self.aug.set(hsts_dirpath, hsts_maxage)
|
||||
note_msg = ("Increasing HSTS max-age value to {0} for VirtualHost "
|
||||
"in {1}\n".format(nextstep_value, vhost.filep))
|
||||
logger.debug(note_msg)
|
||||
@@ -1780,7 +1734,7 @@ class ApacheConfigurator(common.Installer):
|
||||
# We'll simply delete the directive, so that we'll have a
|
||||
# consistent OCSP cache path.
|
||||
if stapling_cache_aug_path:
|
||||
self.parser.aug.remove(
|
||||
self.aug.remove(
|
||||
re.sub(r"/\w*$", "", stapling_cache_aug_path[0]))
|
||||
|
||||
self.parser.add_dir_to_ifmodssl(ssl_vhost_aug_path,
|
||||
@@ -1857,7 +1811,7 @@ class ApacheConfigurator(common.Installer):
|
||||
# "Existing Header directive for virtualhost"
|
||||
pat = '(?:[ "]|^)(%s)(?:[ "]|$)' % (header_substring.lower())
|
||||
for match in header_path:
|
||||
if re.search(pat, self.parser.aug.get(match).lower()):
|
||||
if re.search(pat, self.aug.get(match).lower()):
|
||||
raise errors.PluginEnhancementAlreadyPresent(
|
||||
"Existing %s header" % (header_substring))
|
||||
|
||||
@@ -1984,11 +1938,11 @@ class ApacheConfigurator(common.Installer):
|
||||
constants.REWRITE_HTTPS_ARGS_WITH_END]
|
||||
|
||||
for dir_path, args_paths in rewrite_args_dict.items():
|
||||
arg_vals = [self.parser.aug.get(x) for x in args_paths]
|
||||
arg_vals = [self.aug.get(x) for x in args_paths]
|
||||
|
||||
# Search for past redirection rule, delete it, set the new one
|
||||
if arg_vals in constants.OLD_REWRITE_HTTPS_ARGS:
|
||||
self.parser.aug.remove(dir_path)
|
||||
self.aug.remove(dir_path)
|
||||
self._set_https_redirection_rewrite_rule(vhost)
|
||||
self.save()
|
||||
raise errors.PluginEnhancementAlreadyPresent(
|
||||
@@ -2044,7 +1998,7 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
redirect_filepath = self._write_out_redirect(ssl_vhost, text)
|
||||
|
||||
self.parser.aug.load()
|
||||
self.aug.load()
|
||||
# Make a new vhost data structure and add it to the lists
|
||||
new_vhost = self._create_vhost(parser.get_aug_path(self._escape(redirect_filepath)))
|
||||
self.vhosts.append(new_vhost)
|
||||
@@ -2348,9 +2302,8 @@ class ApacheConfigurator(common.Installer):
|
||||
# XXX if we ever try to enforce a local privilege boundary (eg, running
|
||||
# certbot for unprivileged users via setuid), this function will need
|
||||
# to be modified.
|
||||
apache_config_path = self.pick_apache_config()
|
||||
return common.install_version_controlled_file(
|
||||
options_ssl, options_ssl_digest, apache_config_path, constants.ALL_SSL_OPTIONS_HASHES)
|
||||
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
|
||||
self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
|
||||
|
||||
def enable_autohsts(self, _unused_lineage, domains):
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,6 @@ MOD_SSL_CONF_DEST = "options-ssl-apache.conf"
|
||||
UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-apache-conf-digest.txt"
|
||||
"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`."""
|
||||
|
||||
# NEVER REMOVE A SINGLE HASH FROM THIS LIST UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING!
|
||||
ALL_SSL_OPTIONS_HASHES = [
|
||||
'2086bca02db48daf93468332543c60ac6acdb6f0b58c7bfdf578a5d47092f82a',
|
||||
'4844d36c9a0f587172d9fa10f4f1c9518e3bcfa1947379f155e16a70a728c21a',
|
||||
@@ -19,10 +18,6 @@ ALL_SSL_OPTIONS_HASHES = [
|
||||
'cfdd7c18d2025836ea3307399f509cfb1ebf2612c87dd600a65da2a8e2f2797b',
|
||||
'80720bd171ccdc2e6b917ded340defae66919e4624962396b992b7218a561791',
|
||||
'c0c022ea6b8a51ecc8f1003d0a04af6c3f2bc1c3ce506b3c2dfc1f11ef931082',
|
||||
'717b0a89f5e4c39b09a42813ac6e747cfbdeb93439499e73f4f70a1fe1473f20',
|
||||
'0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137',
|
||||
'86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c',
|
||||
'06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7',
|
||||
]
|
||||
"""SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC"""
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from acme.magic_typing import List, Set # pylint: disable=unused-import, no-nam
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_apache.obj import VirtualHost # pylint: disable=unused-import
|
||||
@@ -169,7 +168,8 @@ class ApacheHttp01(common.TLSSNI01):
|
||||
|
||||
def _set_up_challenges(self):
|
||||
if not os.path.isdir(self.challenge_dir):
|
||||
filesystem.makedirs(self.challenge_dir, 0o755)
|
||||
os.makedirs(self.challenge_dir)
|
||||
os.chmod(self.challenge_dir, 0o755)
|
||||
|
||||
responses = []
|
||||
for achall in self.achalls:
|
||||
@@ -185,7 +185,7 @@ class ApacheHttp01(common.TLSSNI01):
|
||||
self.configurator.reverter.register_file_creation(True, name)
|
||||
with open(name, 'wb') as f:
|
||||
f.write(validation.encode())
|
||||
filesystem.chmod(name, 0o644)
|
||||
os.chmod(name, 0o644)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -17,3 +17,10 @@ SSLOptions +StrictRequire
|
||||
# Add vhost name to log entries:
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
|
||||
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
|
||||
|
||||
#CustomLog /var/log/apache2/access.log vhost_combined
|
||||
#LogLevel warn
|
||||
#ErrorLog /var/log/apache2/error.log
|
||||
|
||||
# Always ensure Cookies have "Secure" set (JAH 2012/1)
|
||||
#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"
|
||||
@@ -1,4 +1,6 @@
|
||||
""" Distribution specific override class for Arch Linux """
|
||||
import pkg_resources
|
||||
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
@@ -24,4 +26,6 @@ class ArchConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/httpd/conf",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
""" Distribution specific override class for CentOS family (RHEL, Fedora) """
|
||||
import logging
|
||||
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import errors
|
||||
@@ -38,6 +39,8 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/httpd/conf.d",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "centos-options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
def config_test(self):
|
||||
@@ -72,18 +75,6 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
|
||||
# Finish with actual config check to see if systemctl restart helped
|
||||
super(CentOSConfigurator, self).config_test()
|
||||
|
||||
def pick_apache_config(self):
|
||||
"""
|
||||
Pick the appropriate TLS Apache configuration file for current version of Apache and OS.
|
||||
:return: the path to the TLS Apache configuration file to use
|
||||
:rtype: str
|
||||
"""
|
||||
# Disabling TLS session tickets is supported by Apache 2.4.11+.
|
||||
# So for old versions of Apache we pick a configuration without this option.
|
||||
if self.version < (2, 4, 11):
|
||||
return apache_util.find_ssl_apache_conf("centos-old")
|
||||
return apache_util.find_ssl_apache_conf("centos-current")
|
||||
|
||||
def _prepare_options(self):
|
||||
"""
|
||||
Override the options dictionary initialization in order to support
|
||||
@@ -95,7 +86,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
|
||||
def get_parser(self):
|
||||
"""Initializes the ApacheParser"""
|
||||
return CentOSParser(
|
||||
self.option("server_root"), self.option("vhost_root"),
|
||||
self.aug, self.option("server_root"), self.option("vhost_root"),
|
||||
self.version, configurator=self)
|
||||
|
||||
def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ
|
||||
@@ -164,7 +155,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
|
||||
for loadmod_path in loadmod_paths:
|
||||
nodir_path = loadmod_path.split("/directive")[0]
|
||||
# Remove the old LoadModule directive
|
||||
self.parser.aug.remove(loadmod_path)
|
||||
self.aug.remove(loadmod_path)
|
||||
|
||||
# Create a new IfModule !mod_ssl.c if not already found on path
|
||||
ssl_ifmod = self.parser.get_ifmod(nodir_path, "!mod_ssl.c",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
""" Distribution specific override class for macOS """
|
||||
import pkg_resources
|
||||
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
@@ -24,4 +26,6 @@ class DarwinConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2/other",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
""" Distribution specific override class for Debian family (Ubuntu/Debian) """
|
||||
import logging
|
||||
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
from certbot.compat import filesystem
|
||||
from certbot.compat import os
|
||||
|
||||
from certbot_apache import apache_util
|
||||
@@ -34,6 +34,8 @@ class DebianConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=True,
|
||||
handle_sites=True,
|
||||
challenge_location="/etc/apache2",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
def enable_site(self, vhost):
|
||||
@@ -63,7 +65,7 @@ class DebianConfigurator(configurator.ApacheConfigurator):
|
||||
try:
|
||||
os.symlink(vhost.filep, enabled_path)
|
||||
except OSError as err:
|
||||
if os.path.islink(enabled_path) and filesystem.realpath(
|
||||
if os.path.islink(enabled_path) and os.path.realpath(
|
||||
enabled_path) == vhost.filep:
|
||||
# Already in shape
|
||||
vhost.enabled = True
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
""" Distribution specific override class for Fedora 29+ """
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import errors
|
||||
@@ -30,6 +31,9 @@ class FedoraConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/httpd/conf.d",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
# TODO: eventually newest version of Fedora will need their own config
|
||||
"certbot_apache", "centos-options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
def config_test(self):
|
||||
@@ -47,7 +51,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator):
|
||||
def get_parser(self):
|
||||
"""Initializes the ApacheParser"""
|
||||
return FedoraParser(
|
||||
self.option("server_root"), self.option("vhost_root"),
|
||||
self.aug, self.option("server_root"), self.option("vhost_root"),
|
||||
self.version, configurator=self)
|
||||
|
||||
def _try_restart_fedora(self):
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
""" Distribution specific override class for Gentoo Linux """
|
||||
import pkg_resources
|
||||
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
@@ -27,6 +29,8 @@ class GentooConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2/vhosts.d",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
def _prepare_options(self):
|
||||
@@ -40,7 +44,7 @@ class GentooConfigurator(configurator.ApacheConfigurator):
|
||||
def get_parser(self):
|
||||
"""Initializes the ApacheParser"""
|
||||
return GentooParser(
|
||||
self.option("server_root"), self.option("vhost_root"),
|
||||
self.aug, self.option("server_root"), self.option("vhost_root"),
|
||||
self.version, configurator=self)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
""" Distribution specific override class for OpenSUSE """
|
||||
import pkg_resources
|
||||
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
@@ -24,4 +26,6 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2/vhosts.d",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
|
||||
@@ -13,8 +13,6 @@ from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import,
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
|
||||
from certbot_apache import constants
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -34,7 +32,7 @@ class ApacheParser(object):
|
||||
arg_var_interpreter = re.compile(r"\$\{[^ \}]*}")
|
||||
fnmatch_chars = set(["*", "?", "\\", "[", "]"])
|
||||
|
||||
def __init__(self, root, vhostroot=None, version=(2, 4),
|
||||
def __init__(self, aug, root, vhostroot=None, version=(2, 4),
|
||||
configurator=None):
|
||||
# Note: Order is important here.
|
||||
|
||||
@@ -43,20 +41,11 @@ class ApacheParser(object):
|
||||
# issues with aug.load() after adding new files / defines to parse tree
|
||||
self.configurator = configurator
|
||||
|
||||
# Initialize augeas
|
||||
self.aug = None
|
||||
self.init_augeas()
|
||||
|
||||
if not self.check_aug_version():
|
||||
raise errors.NotSupportedError(
|
||||
"Apache plugin support requires libaugeas0 and augeas-lenses "
|
||||
"version 1.2.0 or higher, please make sure you have you have "
|
||||
"those installed.")
|
||||
|
||||
self.modules = set() # type: Set[str]
|
||||
self.parser_paths = {} # type: Dict[str, List[str]]
|
||||
self.variables = {} # type: Dict[str, str]
|
||||
|
||||
self.aug = aug
|
||||
# Find configuration root and make sure augeas can parse it.
|
||||
self.root = os.path.abspath(root)
|
||||
self.loc = {"root": self._find_config_root()}
|
||||
@@ -88,146 +77,6 @@ class ApacheParser(object):
|
||||
if self.find_dir("Define", exclude=False):
|
||||
raise errors.PluginError("Error parsing runtime variables")
|
||||
|
||||
def init_augeas(self):
|
||||
""" Initialize the actual Augeas instance """
|
||||
|
||||
try:
|
||||
import augeas
|
||||
except ImportError: # pragma: no cover
|
||||
raise errors.NoInstallationError("Problem in Augeas installation")
|
||||
|
||||
self.aug = augeas.Augeas(
|
||||
# specify a directory to load our preferred lens from
|
||||
loadpath=constants.AUGEAS_LENS_DIR,
|
||||
# Do not save backup (we do it ourselves), do not load
|
||||
# anything by default
|
||||
flags=(augeas.Augeas.NONE |
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD |
|
||||
augeas.Augeas.ENABLE_SPAN))
|
||||
|
||||
def check_parsing_errors(self, lens):
|
||||
"""Verify Augeas can parse all of the lens files.
|
||||
|
||||
:param str lens: lens to check for errors
|
||||
|
||||
:raises .errors.PluginError: If there has been an error in parsing with
|
||||
the specified lens.
|
||||
|
||||
"""
|
||||
error_files = self.aug.match("/augeas//error")
|
||||
|
||||
for path in error_files:
|
||||
# Check to see if it was an error resulting from the use of
|
||||
# the httpd lens
|
||||
lens_path = self.aug.get(path + "/lens")
|
||||
# As aug.get may return null
|
||||
if lens_path and lens in lens_path:
|
||||
msg = (
|
||||
"There has been an error in parsing the file {0} on line {1}: "
|
||||
"{2}".format(
|
||||
# Strip off /augeas/files and /error
|
||||
path[13:len(path) - 6],
|
||||
self.aug.get(path + "/line"),
|
||||
self.aug.get(path + "/message")))
|
||||
raise errors.PluginError(msg)
|
||||
|
||||
def check_aug_version(self):
|
||||
""" Checks that we have recent enough version of libaugeas.
|
||||
If augeas version is recent enough, it will support case insensitive
|
||||
regexp matching"""
|
||||
|
||||
self.aug.set("/test/path/testing/arg", "aRgUMeNT")
|
||||
try:
|
||||
matches = self.aug.match(
|
||||
"/test//*[self::arg=~regexp('argument', 'i')]")
|
||||
except RuntimeError:
|
||||
self.aug.remove("/test/path")
|
||||
return False
|
||||
self.aug.remove("/test/path")
|
||||
return matches
|
||||
|
||||
def unsaved_files(self):
|
||||
"""Lists files that have modified Augeas DOM but the changes have not
|
||||
been written to the filesystem yet, used by `self.save()` and
|
||||
ApacheConfigurator to check the file state.
|
||||
|
||||
:raises .errors.PluginError: If there was an error in Augeas, in
|
||||
an attempt to save the configuration, or an error creating a
|
||||
checkpoint
|
||||
|
||||
:returns: `set` of unsaved files
|
||||
"""
|
||||
save_state = self.aug.get("/augeas/save")
|
||||
self.aug.set("/augeas/save", "noop")
|
||||
# Existing Errors
|
||||
ex_errs = self.aug.match("/augeas//error")
|
||||
try:
|
||||
# This is a noop save
|
||||
self.aug.save()
|
||||
except (RuntimeError, IOError):
|
||||
self._log_save_errors(ex_errs)
|
||||
# Erase Save Notes
|
||||
self.configurator.save_notes = ""
|
||||
raise errors.PluginError(
|
||||
"Error saving files, check logs for more info.")
|
||||
|
||||
# Return the original save method
|
||||
self.aug.set("/augeas/save", save_state)
|
||||
|
||||
# Retrieve list of modified files
|
||||
# Note: Noop saves can cause the file to be listed twice, I used a
|
||||
# set to remove this possibility. This is a known augeas 0.10 error.
|
||||
save_paths = self.aug.match("/augeas/events/saved")
|
||||
|
||||
save_files = set()
|
||||
if save_paths:
|
||||
for path in save_paths:
|
||||
save_files.add(self.aug.get(path)[6:])
|
||||
return save_files
|
||||
|
||||
def ensure_augeas_state(self):
|
||||
"""Makes sure that all Augeas dom changes are written to files to avoid
|
||||
loss of configuration directives when doing additional augeas parsing,
|
||||
causing a possible augeas.load() resulting dom reset
|
||||
"""
|
||||
|
||||
if self.unsaved_files():
|
||||
self.configurator.save_notes += "(autosave)"
|
||||
self.configurator.save()
|
||||
|
||||
def save(self, save_files):
|
||||
"""Saves all changes to the configuration files.
|
||||
|
||||
save() is called from ApacheConfigurator to handle the parser specific
|
||||
tasks of saving.
|
||||
|
||||
:param list save_files: list of strings of file paths that we need to save.
|
||||
|
||||
"""
|
||||
self.configurator.save_notes = ""
|
||||
self.aug.save()
|
||||
|
||||
# Force reload if files were modified
|
||||
# This is needed to recalculate augeas directive span
|
||||
if save_files:
|
||||
for sf in save_files:
|
||||
self.aug.remove("/files/"+sf)
|
||||
self.aug.load()
|
||||
|
||||
def _log_save_errors(self, ex_errs):
|
||||
"""Log errors due to bad Augeas save.
|
||||
|
||||
:param list ex_errs: Existing errors before save
|
||||
|
||||
"""
|
||||
# Check for the root of save problems
|
||||
new_errs = self.aug.match("/augeas//error")
|
||||
# logger.error("During Save - %s", mod_conf)
|
||||
logger.error("Unable to save files: %s. Attempted Save Notes: %s",
|
||||
", ".join(err[13:len(err) - 6] for err in new_errs
|
||||
# Only new errors caused by recent save
|
||||
if err not in ex_errs), self.configurator.save_notes)
|
||||
|
||||
def add_include(self, main_config, inc_path):
|
||||
"""Add Include for a new configuration file if one does not exist
|
||||
|
||||
@@ -809,7 +658,8 @@ class ApacheParser(object):
|
||||
use_new, remove_old = self._check_path_actions(filepath)
|
||||
# Ensure that we have the latest Augeas DOM state on disk before
|
||||
# calling aug.load() which reloads the state from disk
|
||||
self.ensure_augeas_state()
|
||||
if self.configurator:
|
||||
self.configurator.ensure_augeas_state()
|
||||
# Test if augeas included file for Httpd.lens
|
||||
# Note: This works for augeas globs, ie. *.conf
|
||||
if use_new:
|
||||
|
||||
@@ -15,7 +15,7 @@ SCRIPT_DIRNAME = os.path.dirname(__file__)
|
||||
def main(args=None):
|
||||
if not args:
|
||||
args = sys.argv[1:]
|
||||
with acme_server.ACMEServer('pebble', [], False) as acme_xdist:
|
||||
with acme_server.setup_acme_server('pebble', [], False) as acme_xdist:
|
||||
environ = os.environ.copy()
|
||||
environ['SERVER'] = acme_xdist['directory_url']
|
||||
command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')]
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
"""Test for certbot_apache.configurator implementations of reverter"""
|
||||
"""Test for certbot_apache.augeas_configurator."""
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
|
||||
from certbot_apache.tests import util
|
||||
|
||||
|
||||
class ConfiguratorReverterTest(util.ApacheTest):
|
||||
"""Test for ApacheConfigurator reverter methods"""
|
||||
class AugeasConfiguratorTest(util.ApacheTest):
|
||||
"""Test for Augeas Configurator base class."""
|
||||
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(ConfiguratorReverterTest, self).setUp()
|
||||
super(AugeasConfiguratorTest, self).setUp()
|
||||
|
||||
self.config = util.get_apache_configurator(
|
||||
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
|
||||
@@ -27,6 +28,20 @@ class ConfiguratorReverterTest(util.ApacheTest):
|
||||
shutil.rmtree(self.work_dir)
|
||||
shutil.rmtree(self.temp_dir)
|
||||
|
||||
def test_bad_parse(self):
|
||||
# pylint: disable=protected-access
|
||||
self.config.parser.parse_file(os.path.join(
|
||||
self.config.parser.root, "conf-available", "bad_conf_file.conf"))
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.config.check_parsing_errors, "httpd.aug")
|
||||
|
||||
def test_bad_save(self):
|
||||
mock_save = mock.Mock()
|
||||
mock_save.side_effect = IOError
|
||||
self.config.aug.save = mock_save
|
||||
|
||||
self.assertRaises(errors.PluginError, self.config.save)
|
||||
|
||||
def test_bad_save_checkpoint(self):
|
||||
self.config.reverter.add_to_checkpoint = mock.Mock(
|
||||
side_effect=errors.ReverterError)
|
||||
@@ -48,9 +63,23 @@ class ConfiguratorReverterTest(util.ApacheTest):
|
||||
|
||||
self.assertTrue(mock_finalize.is_called)
|
||||
|
||||
def test_recovery_routine(self):
|
||||
mock_load = mock.Mock()
|
||||
self.config.aug.load = mock_load
|
||||
|
||||
self.config.recovery_routine()
|
||||
self.assertEqual(mock_load.call_count, 1)
|
||||
|
||||
def test_recovery_routine_error(self):
|
||||
self.config.reverter.recovery_routine = mock.Mock(
|
||||
side_effect=errors.ReverterError)
|
||||
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.config.recovery_routine)
|
||||
|
||||
def test_revert_challenge_config(self):
|
||||
mock_load = mock.Mock()
|
||||
self.config.parser.aug.load = mock_load
|
||||
self.config.aug.load = mock_load
|
||||
|
||||
self.config.revert_challenge_config()
|
||||
self.assertEqual(mock_load.call_count, 1)
|
||||
@@ -64,7 +93,7 @@ class ConfiguratorReverterTest(util.ApacheTest):
|
||||
|
||||
def test_rollback_checkpoints(self):
|
||||
mock_load = mock.Mock()
|
||||
self.config.parser.aug.load = mock_load
|
||||
self.config.aug.load = mock_load
|
||||
|
||||
self.config.rollback_checkpoints()
|
||||
self.assertEqual(mock_load.call_count, 1)
|
||||
@@ -74,11 +103,13 @@ class ConfiguratorReverterTest(util.ApacheTest):
|
||||
side_effect=errors.ReverterError)
|
||||
self.assertRaises(errors.PluginError, self.config.rollback_checkpoints)
|
||||
|
||||
def test_recovery_routine_reload(self):
|
||||
mock_load = mock.Mock()
|
||||
self.config.parser.aug.load = mock_load
|
||||
self.config.recovery_routine()
|
||||
self.assertEqual(mock_load.call_count, 1)
|
||||
def test_view_config_changes(self):
|
||||
self.config.view_config_changes()
|
||||
|
||||
def test_view_config_changes_error(self):
|
||||
self.config.reverter.view_config_changes = mock.Mock(
|
||||
side_effect=errors.ReverterError)
|
||||
self.assertRaises(errors.PluginError, self.config.view_config_changes)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -165,6 +165,10 @@ class CentOS6Tests(util.ApacheTest):
|
||||
"LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False)
|
||||
self.assertEqual(len(post_loadmods), 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_loadmod_non_duplicate(self):
|
||||
# the modules/mod_ssl.so exists in ssl.conf
|
||||
sslmod_args = ["ssl_module", "modules/mod_somethingelse.so"]
|
||||
@@ -193,7 +197,7 @@ class CentOS6Tests(util.ApacheTest):
|
||||
exclude=False)
|
||||
for mod in orig_loadmods:
|
||||
noarg_path = mod.rpartition("/")[0]
|
||||
self.config.parser.aug.remove(noarg_path)
|
||||
self.config.aug.remove(noarg_path)
|
||||
self.config.save()
|
||||
self.config.deploy_cert(
|
||||
"random.demo", "example/cert.pem", "example/key.pem",
|
||||
|
||||
@@ -4,7 +4,6 @@ import unittest
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import filesystem
|
||||
from certbot.compat import os
|
||||
|
||||
from certbot_apache import obj
|
||||
@@ -161,7 +160,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
|
||||
"""Make sure we read the sysconfig OPTIONS variable correctly"""
|
||||
# Return nothing for the process calls
|
||||
mock_cfg.return_value = ""
|
||||
self.config.parser.sysconfig_filep = filesystem.realpath(
|
||||
self.config.parser.sysconfig_filep = os.path.realpath(
|
||||
os.path.join(self.config.parser.root, "../sysconfig/httpd"))
|
||||
self.config.parser.variables = {}
|
||||
|
||||
@@ -190,13 +189,6 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
|
||||
errors.SubprocessError]
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.restart)
|
||||
|
||||
def test_pick_correct_tls_config(self):
|
||||
self.config.version = (2, 4, 10)
|
||||
self.assertTrue('centos-old' in self.config.pick_apache_config())
|
||||
|
||||
self.config.version = (2, 4, 11)
|
||||
self.assertTrue('centos-current' in self.config.pick_apache_config())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -16,7 +16,6 @@ from certbot import achallenges
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.compat import filesystem
|
||||
from certbot.tests import acme_util
|
||||
from certbot.tests import util as certbot_util
|
||||
|
||||
@@ -51,14 +50,25 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.deploy_cert = mocked_deploy_cert
|
||||
return self.config
|
||||
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator.init_augeas")
|
||||
@mock.patch("certbot_apache.configurator.path_surgery")
|
||||
def test_prepare_no_install(self, mock_surgery):
|
||||
def test_prepare_no_install(self, mock_surgery, _init_augeas):
|
||||
silly_path = {"PATH": "/tmp/nothingness2342"}
|
||||
mock_surgery.return_value = False
|
||||
with mock.patch.dict('os.environ', silly_path):
|
||||
self.assertRaises(errors.NoInstallationError, self.config.prepare)
|
||||
self.assertEqual(mock_surgery.call_count, 1)
|
||||
|
||||
@mock.patch("certbot_apache.augeas_configurator.AugeasConfigurator.init_augeas")
|
||||
def test_prepare_no_augeas(self, mock_init_augeas):
|
||||
""" Test augeas initialization ImportError """
|
||||
def side_effect_error():
|
||||
""" Side effect error for the test """
|
||||
raise ImportError
|
||||
mock_init_augeas.side_effect = side_effect_error
|
||||
self.assertRaises(
|
||||
errors.NoInstallationError, self.config.prepare)
|
||||
|
||||
@mock.patch("certbot_apache.parser.ApacheParser")
|
||||
@mock.patch("certbot_apache.configurator.util.exe_exists")
|
||||
def test_prepare_version(self, mock_exe_exists, _):
|
||||
@@ -70,6 +80,16 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.assertRaises(
|
||||
errors.NotSupportedError, self.config.prepare)
|
||||
|
||||
@mock.patch("certbot_apache.parser.ApacheParser")
|
||||
@mock.patch("certbot_apache.configurator.util.exe_exists")
|
||||
def test_prepare_old_aug(self, mock_exe_exists, _):
|
||||
mock_exe_exists.return_value = True
|
||||
self.config.config_test = mock.Mock()
|
||||
# pylint: disable=protected-access
|
||||
self.config._check_aug_version = mock.Mock(return_value=False)
|
||||
self.assertRaises(
|
||||
errors.NotSupportedError, self.config.prepare)
|
||||
|
||||
def test_prepare_locked(self):
|
||||
server_root = self.config.conf("server-root")
|
||||
self.config.config_test = mock.Mock()
|
||||
@@ -654,7 +674,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# span excludes the closing </VirtualHost> tag in older versions
|
||||
# of Augeas
|
||||
return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1142]
|
||||
with mock.patch.object(self.config.parser.aug, 'span') as mock_span:
|
||||
with mock.patch.object(self.config.aug, 'span') as mock_span:
|
||||
mock_span.return_value = return_value
|
||||
self.test_make_vhost_ssl()
|
||||
|
||||
@@ -662,7 +682,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# span includes the closing </VirtualHost> tag in newer versions
|
||||
# of Augeas
|
||||
return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1157]
|
||||
with mock.patch.object(self.config.parser.aug, 'span') as mock_span:
|
||||
with mock.patch.object(self.config.aug, 'span') as mock_span:
|
||||
mock_span.return_value = return_value
|
||||
self.test_make_vhost_ssl()
|
||||
|
||||
@@ -675,7 +695,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
def test_make_vhost_ssl_nonexistent_vhost_path(self):
|
||||
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
|
||||
self.assertEqual(os.path.dirname(ssl_vhost.filep),
|
||||
os.path.dirname(filesystem.realpath(self.vh_truth[1].filep)))
|
||||
os.path.dirname(os.path.realpath(
|
||||
self.vh_truth[1].filep)))
|
||||
|
||||
def test_make_vhost_ssl(self):
|
||||
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
|
||||
@@ -1210,7 +1231,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
except errors.PluginEnhancementAlreadyPresent:
|
||||
args_paths = self.config.parser.find_dir(
|
||||
"RewriteRule", None, http_vhost.path, False)
|
||||
arg_vals = [self.config.parser.aug.get(x) for x in args_paths]
|
||||
arg_vals = [self.config.aug.get(x) for x in args_paths]
|
||||
self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS)
|
||||
|
||||
|
||||
@@ -1313,6 +1334,15 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
return account_key, (achall1, achall2, achall3)
|
||||
|
||||
def test_aug_version(self):
|
||||
mock_match = mock.Mock(return_value=["something"])
|
||||
self.config.aug.match = mock_match
|
||||
# pylint: disable=protected-access
|
||||
self.assertEqual(self.config._check_aug_version(),
|
||||
["something"])
|
||||
self.config.aug.match.side_effect = RuntimeError
|
||||
self.assertFalse(self.config._check_aug_version())
|
||||
|
||||
def test_enable_site_nondebian(self):
|
||||
inc_path = "/path/to/wherever"
|
||||
vhost = self.vh_truth[0]
|
||||
@@ -1335,8 +1365,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot"))
|
||||
filesystem.chmod(tmp_path, 0o755)
|
||||
tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot"))
|
||||
os.chmod(tmp_path, 0o755)
|
||||
mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path"
|
||||
mock_a = "certbot_apache.parser.ApacheParser.add_include"
|
||||
|
||||
@@ -1481,7 +1511,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.assertEqual(first_id, second_id)
|
||||
|
||||
def test_realpath_replaces_symlink(self):
|
||||
orig_match = self.config.parser.aug.match
|
||||
orig_match = self.config.aug.match
|
||||
mock_vhost = copy.deepcopy(self.vh_truth[0])
|
||||
mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available')
|
||||
mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available')
|
||||
@@ -1495,7 +1525,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
return orig_match(aug_expr)
|
||||
|
||||
self.config.parser.parser_paths = ["/mocked/path"]
|
||||
self.config.parser.aug.match = mock_match
|
||||
self.config.aug.match = mock_match
|
||||
vhs = self.config.get_virtual_hosts()
|
||||
self.assertEqual(len(vhs), 2)
|
||||
self.assertTrue(vhs[0] == self.vh_truth[1])
|
||||
@@ -1521,8 +1551,8 @@ class AugeasVhostsTest(util.ApacheTest):
|
||||
self.work_dir)
|
||||
|
||||
def test_choosevhost_with_illegal_name(self):
|
||||
self.config.parser.aug = mock.MagicMock()
|
||||
self.config.parser.aug.match.side_effect = RuntimeError
|
||||
self.config.aug = mock.MagicMock()
|
||||
self.config.aug.match.side_effect = RuntimeError
|
||||
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
|
||||
chosen_vhost = self.config._create_vhost(path)
|
||||
self.assertEqual(None, chosen_vhost)
|
||||
@@ -1706,7 +1736,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
self.config.updated_mod_ssl_conf_digest)
|
||||
|
||||
def _current_ssl_options_hash(self):
|
||||
return crypto_util.sha256sum(self.config.pick_apache_config())
|
||||
return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC"))
|
||||
|
||||
def _assert_current_file(self):
|
||||
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
|
||||
@@ -1742,7 +1772,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
self.assertFalse(mock_logger.warning.called)
|
||||
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
|
||||
self.assertEqual(crypto_util.sha256sum(
|
||||
self.config.pick_apache_config()),
|
||||
self.config.option("MOD_SSL_CONF_SRC")),
|
||||
self._current_ssl_options_hash())
|
||||
self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
|
||||
self._current_ssl_options_hash())
|
||||
@@ -1758,31 +1788,18 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
"%s has been manually modified; updated file "
|
||||
"saved to %s. We recommend updating %s for security purposes.")
|
||||
self.assertEqual(crypto_util.sha256sum(
|
||||
self.config.pick_apache_config()),
|
||||
self.config.option("MOD_SSL_CONF_SRC")),
|
||||
self._current_ssl_options_hash())
|
||||
# only print warning once
|
||||
with mock.patch("certbot.plugins.common.logger") as mock_logger:
|
||||
self._call()
|
||||
self.assertFalse(mock_logger.warning.called)
|
||||
|
||||
def test_ssl_config_files_hash_in_all_hashes(self):
|
||||
"""
|
||||
It is really critical that all TLS Apache config files have their SHA256 hash registered in
|
||||
constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config
|
||||
file has been manually edited by the user, and will refuse to update it.
|
||||
This test ensures that all necessary hashes are present.
|
||||
"""
|
||||
def test_current_file_hash_in_all_hashes(self):
|
||||
from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES
|
||||
import pkg_resources
|
||||
tls_configs_dir = pkg_resources.resource_filename("certbot_apache", "tls_configs")
|
||||
all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir)
|
||||
if name.endswith('options-ssl-apache.conf')]
|
||||
self.assertTrue(all_files)
|
||||
for one_file in all_files:
|
||||
file_hash = crypto_util.sha256sum(one_file)
|
||||
self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES,
|
||||
"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 "
|
||||
"hash of {0} when it is updated.".format(one_file))
|
||||
self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES,
|
||||
"Constants.ALL_SSL_OPTIONS_HASHES must be appended"
|
||||
" with the sha256 hash of self.config.mod_ssl_conf when it is updated.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -79,9 +79,9 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
|
||||
def test_enable_site_failure(self):
|
||||
self.config.parser.root = "/tmp/nonexistent"
|
||||
with mock.patch("certbot.compat.os.path.isdir") as mock_dir:
|
||||
with mock.patch("os.path.isdir") as mock_dir:
|
||||
mock_dir.return_value = True
|
||||
with mock.patch("certbot.compat.os.path.islink") as mock_link:
|
||||
with mock.patch("os.path.islink") as mock_link:
|
||||
mock_link.return_value = False
|
||||
self.assertRaises(
|
||||
errors.NotSupportedError,
|
||||
|
||||
@@ -4,7 +4,6 @@ import unittest
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import filesystem
|
||||
from certbot.compat import os
|
||||
|
||||
from certbot_apache import obj
|
||||
@@ -161,7 +160,7 @@ class MultipleVhostsTestFedora(util.ApacheTest):
|
||||
"""Make sure we read the sysconfig OPTIONS variable correctly"""
|
||||
# Return nothing for the process calls
|
||||
mock_cfg.return_value = ""
|
||||
self.config.parser.sysconfig_filep = filesystem.realpath(
|
||||
self.config.parser.sysconfig_filep = os.path.realpath(
|
||||
os.path.join(self.config.parser.root, "../sysconfig/httpd"))
|
||||
self.config.parser.variables = {}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import unittest
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import filesystem
|
||||
from certbot.compat import os
|
||||
|
||||
from certbot_apache import obj
|
||||
@@ -82,7 +81,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
|
||||
"""Make sure we read the Gentoo APACHE2_OPTS variable correctly"""
|
||||
defines = ['DEFAULT_VHOST', 'INFO',
|
||||
'SSL', 'SSL_DEFAULT_VHOST', 'LANGUAGE']
|
||||
self.config.parser.apacheconfig_filep = filesystem.realpath(
|
||||
self.config.parser.apacheconfig_filep = os.path.realpath(
|
||||
os.path.join(self.config.parser.root, "../conf.d/apache2"))
|
||||
self.config.parser.variables = {}
|
||||
with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# pylint: disable=too-many-public-methods
|
||||
"""Tests for certbot_apache.parser."""
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import augeas
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
@@ -22,27 +22,6 @@ class BasicParserTest(util.ParserTest):
|
||||
shutil.rmtree(self.config_dir)
|
||||
shutil.rmtree(self.work_dir)
|
||||
|
||||
def test_bad_parse(self):
|
||||
self.parser.parse_file(os.path.join(self.parser.root,
|
||||
"conf-available", "bad_conf_file.conf"))
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.parser.check_parsing_errors, "httpd.aug")
|
||||
|
||||
def test_bad_save(self):
|
||||
mock_save = mock.Mock()
|
||||
mock_save.side_effect = IOError
|
||||
self.parser.aug.save = mock_save
|
||||
self.assertRaises(errors.PluginError, self.parser.unsaved_files)
|
||||
|
||||
def test_aug_version(self):
|
||||
mock_match = mock.Mock(return_value=["something"])
|
||||
self.parser.aug.match = mock_match
|
||||
# pylint: disable=protected-access
|
||||
self.assertEqual(self.parser.check_aug_version(),
|
||||
["something"])
|
||||
self.parser.aug.match.side_effect = RuntimeError
|
||||
self.assertFalse(self.parser.check_aug_version())
|
||||
|
||||
def test_find_config_root_no_root(self):
|
||||
# pylint: disable=protected-access
|
||||
os.remove(self.parser.loc["root"])
|
||||
@@ -332,38 +311,21 @@ class BasicParserTest(util.ParserTest):
|
||||
class ParserInitTest(util.ApacheTest):
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(ParserInitTest, self).setUp()
|
||||
self.aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
shutil.rmtree(self.config_dir)
|
||||
shutil.rmtree(self.work_dir)
|
||||
|
||||
@mock.patch("certbot_apache.parser.ApacheParser.init_augeas")
|
||||
def test_prepare_no_augeas(self, mock_init_augeas):
|
||||
from certbot_apache.parser import ApacheParser
|
||||
mock_init_augeas.side_effect = errors.NoInstallationError
|
||||
self.config.config_test = mock.Mock()
|
||||
self.assertRaises(
|
||||
errors.NoInstallationError, ApacheParser,
|
||||
os.path.relpath(self.config_path), "/dummy/vhostpath",
|
||||
version=(2, 4, 22), configurator=self.config)
|
||||
|
||||
def test_init_old_aug(self):
|
||||
from certbot_apache.parser import ApacheParser
|
||||
with mock.patch("certbot_apache.parser.ApacheParser.check_aug_version") as mock_c:
|
||||
mock_c.return_value = False
|
||||
self.assertRaises(
|
||||
errors.NotSupportedError,
|
||||
ApacheParser, os.path.relpath(self.config_path),
|
||||
"/dummy/vhostpath", version=(2, 4, 22), configurator=self.config)
|
||||
|
||||
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
|
||||
def test_unparseable(self, mock_cfg):
|
||||
from certbot_apache.parser import ApacheParser
|
||||
mock_cfg.return_value = ('Define: TEST')
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
ApacheParser, os.path.relpath(self.config_path),
|
||||
ApacheParser, self.aug, os.path.relpath(self.config_path),
|
||||
"/dummy/vhostpath", version=(2, 2, 22), configurator=self.config)
|
||||
|
||||
def test_root_normalized(self):
|
||||
@@ -375,7 +337,8 @@ class ParserInitTest(util.ApacheTest):
|
||||
self.temp_dir,
|
||||
"debian_apache_2_4/////multiple_vhosts/../multiple_vhosts/apache2")
|
||||
|
||||
parser = ApacheParser(path, "/dummy/vhostpath", configurator=self.config)
|
||||
parser = ApacheParser(self.aug, path,
|
||||
"/dummy/vhostpath", configurator=self.config)
|
||||
|
||||
self.assertEqual(parser.root, self.config_path)
|
||||
|
||||
@@ -384,7 +347,7 @@ class ParserInitTest(util.ApacheTest):
|
||||
with mock.patch("certbot_apache.parser.ApacheParser."
|
||||
"update_runtime_variables"):
|
||||
parser = ApacheParser(
|
||||
os.path.relpath(self.config_path),
|
||||
self.aug, os.path.relpath(self.config_path),
|
||||
"/dummy/vhostpath", configurator=self.config)
|
||||
|
||||
self.assertEqual(parser.root, self.config_path)
|
||||
@@ -394,7 +357,7 @@ class ParserInitTest(util.ApacheTest):
|
||||
with mock.patch("certbot_apache.parser.ApacheParser."
|
||||
"update_runtime_variables"):
|
||||
parser = ApacheParser(
|
||||
self.config_path + os.path.sep,
|
||||
self.aug, self.config_path + os.path.sep,
|
||||
"/dummy/vhostpath", configurator=self.config)
|
||||
self.assertEqual(parser.root, self.config_path)
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ class ParserTest(ApacheTest):
|
||||
with mock.patch("certbot_apache.parser.ApacheParser."
|
||||
"update_runtime_variables"):
|
||||
self.parser = ApacheParser(
|
||||
self.config_path, self.vhost_path, configurator=self.config)
|
||||
self.aug, self.config_path, self.vhost_path,
|
||||
configurator=self.config)
|
||||
|
||||
|
||||
def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-locals
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# This file contains important security parameters. If you modify this file
|
||||
# manually, Certbot will be unable to automatically provide future security
|
||||
# updates. Instead, Certbot will print and log an error message with a path to
|
||||
# the up-to-date file that you will need to refer to when manually updating
|
||||
# this file.
|
||||
|
||||
SSLEngine on
|
||||
|
||||
# Intermediate configuration, tweak to your needs
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
|
||||
SSLHonorCipherOrder on
|
||||
|
||||
SSLOptions +StrictRequire
|
||||
|
||||
# Add vhost name to log entries:
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
|
||||
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
|
||||
@@ -1,20 +0,0 @@
|
||||
# This file contains important security parameters. If you modify this file
|
||||
# manually, Certbot will be unable to automatically provide future security
|
||||
# updates. Instead, Certbot will print and log an error message with a path to
|
||||
# the up-to-date file that you will need to refer to when manually updating
|
||||
# this file.
|
||||
|
||||
SSLEngine on
|
||||
|
||||
# Intermediate configuration, tweak to your needs
|
||||
SSLProtocol all -SSLv2 -SSLv3
|
||||
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
|
||||
SSLHonorCipherOrder on
|
||||
SSLCompression off
|
||||
SSLSessionTickets off
|
||||
|
||||
SSLOptions +StrictRequire
|
||||
|
||||
# Add vhost name to log entries:
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
|
||||
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
|
||||
@@ -1,3 +1,3 @@
|
||||
# Remember to update setup.py to match the package versions below.
|
||||
acme[dev]==0.29.0
|
||||
-e .[dev]
|
||||
certbot[dev]==0.34.0
|
||||
|
||||
@@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand
|
||||
import sys
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.29.0',
|
||||
'certbot>=0.37.0.dev0',
|
||||
'certbot>=0.34.0',
|
||||
'mock',
|
||||
'python-augeas',
|
||||
'setuptools',
|
||||
|
||||
26
certbot-auto
26
certbot-auto
@@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
|
||||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
|
||||
LE_AUTO_VERSION="0.36.0"
|
||||
LE_AUTO_VERSION="0.35.1"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
@@ -1314,18 +1314,18 @@ letsencrypt==0.7.0 \
|
||||
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
|
||||
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
|
||||
|
||||
certbot==0.36.0 \
|
||||
--hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \
|
||||
--hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2
|
||||
acme==0.36.0 \
|
||||
--hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \
|
||||
--hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668
|
||||
certbot-apache==0.36.0 \
|
||||
--hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \
|
||||
--hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd
|
||||
certbot-nginx==0.36.0 \
|
||||
--hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \
|
||||
--hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322
|
||||
certbot==0.35.1 \
|
||||
--hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \
|
||||
--hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860
|
||||
acme==0.35.1 \
|
||||
--hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \
|
||||
--hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423
|
||||
certbot-apache==0.35.1 \
|
||||
--hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \
|
||||
--hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71
|
||||
certbot-nginx==0.35.1 \
|
||||
--hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \
|
||||
--hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -68,18 +68,17 @@ def _setup_primary_node(config):
|
||||
:param config: Configuration of the pytest primary node
|
||||
"""
|
||||
# Check for runtime compatibility: some tools are required to be available in PATH
|
||||
if 'boulder' in config.option.acme_server:
|
||||
try:
|
||||
subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
raise ValueError('Error: docker is required in PATH to launch the integration tests on'
|
||||
'boulder, but is not installed or not available for current user.')
|
||||
try:
|
||||
subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
raise ValueError('Error: docker is required in PATH to launch the integration tests, '
|
||||
'but is not installed or not available for current user.')
|
||||
|
||||
try:
|
||||
subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, '
|
||||
'but is not installed or not available for current user.')
|
||||
try:
|
||||
subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, '
|
||||
'but is not installed or not available for current user.')
|
||||
|
||||
# Parameter numprocesses is added to option by pytest-xdist
|
||||
workers = ['primary'] if not config.option.numprocesses\
|
||||
@@ -87,7 +86,7 @@ def _setup_primary_node(config):
|
||||
|
||||
# By calling setup_acme_server we ensure that all necessary acme server instances will be
|
||||
# fully started. This runtime is reflected by the acme_xdist returned.
|
||||
acme_server = acme_lib.ACMEServer(config.option.acme_server, workers)
|
||||
acme_server = acme_lib.setup_acme_server(config.option.acme_server, workers)
|
||||
config.add_cleanup(acme_server.stop)
|
||||
print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist))
|
||||
acme_server.start()
|
||||
|
||||
@@ -21,9 +21,9 @@ def construct_nginx_config(nginx_root, nginx_webroot, http_port, https_port, oth
|
||||
:rtype: str
|
||||
"""
|
||||
key_path = key_path if key_path \
|
||||
else pkg_resources.resource_filename('certbot_integration_tests', 'assets/key.pem')
|
||||
else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_key.pem')
|
||||
cert_path = cert_path if cert_path \
|
||||
else pkg_resources.resource_filename('certbot_integration_tests', 'assets/cert.pem')
|
||||
else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_cert.pem')
|
||||
return '''\
|
||||
# This error log will be written regardless of server scope error_log
|
||||
# definitions, so we have to set this here in the main scope.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
"""Module to setup an ACME CA server environment able to run multiple tests in parallel"""
|
||||
from __future__ import print_function
|
||||
import errno
|
||||
import json
|
||||
import tempfile
|
||||
import time
|
||||
@@ -12,178 +11,176 @@ import sys
|
||||
from os.path import join
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
from certbot_integration_tests.utils import misc, proxy, pebble_artifacts
|
||||
from certbot_integration_tests.utils import misc, proxy
|
||||
from certbot_integration_tests.utils.constants import *
|
||||
|
||||
|
||||
class ACMEServer(object):
|
||||
"""
|
||||
ACMEServer configures and handles the lifecycle of an ACME CA server and an HTTP reverse proxy
|
||||
instance, to allow parallel execution of integration tests against the unique http-01 port
|
||||
expected by the ACME CA server.
|
||||
Typically all pytest integration tests will be executed in this context.
|
||||
ACMEServer gives access the acme_xdist parameter, listing the ports and directory url to use
|
||||
for each pytest node. It exposes also start and stop methods in order to start the stack, and
|
||||
stop it with proper resources cleanup.
|
||||
ACMEServer is also a context manager, and so can be used to ensure ACME server is started/stopped
|
||||
upon context enter/exit.
|
||||
Handler exposing methods to start and stop the ACME server, and get its configuration
|
||||
(eg. challenges ports). ACMEServer is also a context manager, and so can be used to
|
||||
ensure ACME server is started/stopped upon context enter/exit.
|
||||
"""
|
||||
def __init__(self, acme_server, nodes, http_proxy=True, stdout=False):
|
||||
"""
|
||||
Create an ACMEServer instance.
|
||||
:param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble)
|
||||
:param list nodes: list of node names that will be setup by pytest xdist
|
||||
:param bool http_proxy: if False do not start the HTTP proxy
|
||||
:param bool stdout: if True stream subprocesses stdout to standard stdout
|
||||
"""
|
||||
self._construct_acme_xdist(acme_server, nodes)
|
||||
|
||||
self._acme_type = 'pebble' if acme_server == 'pebble' else 'boulder'
|
||||
self._proxy = http_proxy
|
||||
self._workspace = tempfile.mkdtemp()
|
||||
self._processes = []
|
||||
self._stdout = sys.stdout if stdout else open(os.devnull, 'w')
|
||||
|
||||
def start(self):
|
||||
"""Start the test stack"""
|
||||
try:
|
||||
if self._proxy:
|
||||
self._prepare_http_proxy()
|
||||
if self._acme_type == 'pebble':
|
||||
self._prepare_pebble_server()
|
||||
if self._acme_type == 'boulder':
|
||||
self._prepare_boulder_server()
|
||||
except BaseException as e:
|
||||
self.stop()
|
||||
raise e
|
||||
def __init__(self, acme_xdist, start, server_cleanup):
|
||||
self._proxy_process = None
|
||||
self._server_cleanup = server_cleanup
|
||||
self.acme_xdist = acme_xdist
|
||||
self.start = start
|
||||
|
||||
def stop(self):
|
||||
"""Stop the test stack, and clean its resources"""
|
||||
print('=> Tear down the test infrastructure...')
|
||||
try:
|
||||
for process in self._processes:
|
||||
try:
|
||||
process.terminate()
|
||||
except OSError as e:
|
||||
# Process may be not started yet, so no PID and terminate fails.
|
||||
# Then the process never started, and the situation is acceptable.
|
||||
if e.errno != errno.ESRCH:
|
||||
raise
|
||||
for process in self._processes:
|
||||
process.wait()
|
||||
|
||||
if os.path.exists(os.path.join(self._workspace, 'boulder')):
|
||||
# Boulder docker generates build artifacts owned by root with 0o744 permissions.
|
||||
# If we started the acme server from a normal user that has access to the Docker
|
||||
# daemon, this user will not be able to delete these artifacts from the host.
|
||||
# We need to do it through a docker.
|
||||
process = self._launch_process(['docker', 'run', '--rm', '-v',
|
||||
'{0}:/workspace'.format(self._workspace),
|
||||
'alpine', 'rm', '-rf', '/workspace/boulder'])
|
||||
process.wait()
|
||||
finally:
|
||||
shutil.rmtree(self._workspace)
|
||||
if self._stdout != sys.stdout:
|
||||
self._stdout.close()
|
||||
print('=> Test infrastructure stopped and cleaned up.')
|
||||
if self._proxy_process:
|
||||
self._proxy_process.terminate()
|
||||
self._proxy_process.wait()
|
||||
self._server_cleanup()
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
self._proxy_process = self.start()
|
||||
return self.acme_xdist
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.stop()
|
||||
|
||||
def _construct_acme_xdist(self, acme_server, nodes):
|
||||
"""Generate and return the acme_xdist dict"""
|
||||
acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT}
|
||||
|
||||
# Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble.
|
||||
if acme_server == 'pebble':
|
||||
acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL
|
||||
else: # boulder
|
||||
acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL \
|
||||
if acme_server == 'boulder-v2' else BOULDER_V1_DIRECTORY_URL
|
||||
def setup_acme_server(acme_server, nodes, proxy=True):
|
||||
"""
|
||||
This method will setup an ACME CA server and an HTTP reverse proxy instance, to allow parallel
|
||||
execution of integration tests against the unique http-01 port expected by the ACME CA server.
|
||||
Typically all pytest integration tests will be executed in this context.
|
||||
An ACMEServer instance will be returned, giving access to the ports and directory url to use
|
||||
for each pytest node, and its start and stop methods are appropriately configured to
|
||||
respectively start the server, and stop it with proper resources cleanup.
|
||||
:param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble)
|
||||
:param str[] nodes: list of node names that will be setup by pytest xdist
|
||||
:param bool proxy: set to False to not start the HTTP proxy
|
||||
:return: a properly configured ACMEServer instance
|
||||
:rtype: ACMEServer
|
||||
"""
|
||||
acme_type = 'pebble' if acme_server == 'pebble' else 'boulder'
|
||||
acme_xdist = _construct_acme_xdist(acme_server, nodes)
|
||||
workspace, server_cleanup = _construct_workspace(acme_type)
|
||||
|
||||
acme_xdist['http_port'] = {node: port for (node, port)
|
||||
in zip(nodes, range(5200, 5200 + len(nodes)))}
|
||||
acme_xdist['https_port'] = {node: port for (node, port)
|
||||
in zip(nodes, range(5100, 5100 + len(nodes)))}
|
||||
acme_xdist['other_port'] = {node: port for (node, port)
|
||||
in zip(nodes, range(5300, 5300 + len(nodes)))}
|
||||
def start():
|
||||
proxy_process = _prepare_http_proxy(acme_xdist) if proxy else None
|
||||
_prepare_acme_server(workspace, acme_type, acme_xdist)
|
||||
|
||||
self.acme_xdist = acme_xdist
|
||||
return proxy_process
|
||||
|
||||
def _prepare_pebble_server(self):
|
||||
"""Configure and launch the Pebble server"""
|
||||
print('=> Starting pebble instance deployment...')
|
||||
pebble_path, challtestsrv_path, pebble_config_path = pebble_artifacts.fetch(self._workspace)
|
||||
return ACMEServer(acme_xdist, start, server_cleanup)
|
||||
|
||||
# Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid
|
||||
# nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment.
|
||||
environ = os.environ.copy()
|
||||
environ['PEBBLE_VA_NOSLEEP'] = '1'
|
||||
environ['PEBBLE_WFE_NONCEREJECT'] = '0'
|
||||
|
||||
self._launch_process(
|
||||
[pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053'],
|
||||
env=environ)
|
||||
def _construct_acme_xdist(acme_server, nodes):
|
||||
"""Generate and return the acme_xdist dict"""
|
||||
acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT}
|
||||
|
||||
self._launch_process(
|
||||
[challtestsrv_path, '-management', ':{0}'.format(CHALLTESTSRV_PORT), '-defaultIPv6', '""',
|
||||
'-defaultIPv4', '127.0.0.1', '-http01', '""', '-tlsalpn01', '""', '-https01', '""'])
|
||||
# Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble.
|
||||
if acme_server == 'pebble':
|
||||
acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL
|
||||
else: # boulder
|
||||
acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL \
|
||||
if acme_server == 'boulder-v2' else BOULDER_V1_DIRECTORY_URL
|
||||
|
||||
acme_xdist['http_port'] = {node: port for (node, port)
|
||||
in zip(nodes, range(5200, 5200 + len(nodes)))}
|
||||
acme_xdist['https_port'] = {node: port for (node, port)
|
||||
in zip(nodes, range(5100, 5100 + len(nodes)))}
|
||||
acme_xdist['other_port'] = {node: port for (node, port)
|
||||
in zip(nodes, range(5300, 5300 + len(nodes)))}
|
||||
|
||||
return acme_xdist
|
||||
|
||||
|
||||
def _construct_workspace(acme_type):
|
||||
"""Create a temporary workspace for integration tests stack"""
|
||||
workspace = tempfile.mkdtemp()
|
||||
|
||||
def cleanup():
|
||||
"""Cleanup function to call that will teardown relevant dockers and their configuration."""
|
||||
print('=> Tear down the {0} instance...'.format(acme_type))
|
||||
instance_path = join(workspace, acme_type)
|
||||
try:
|
||||
if os.path.isfile(join(instance_path, 'docker-compose.yml')):
|
||||
_launch_command(['docker-compose', 'down'], cwd=instance_path)
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
print('=> Finished tear down of {0} instance.'.format(acme_type))
|
||||
|
||||
if acme_type == 'boulder' and os.path.exists(os.path.join(workspace, 'boulder')):
|
||||
# Boulder docker generates build artifacts owned by root user with 0o744 permissions.
|
||||
# If we started the acme server from a normal user that has access to the Docker
|
||||
# daemon, this user will not be able to delete these artifacts from the host.
|
||||
# We need to do it through a docker.
|
||||
_launch_command(['docker', 'run', '--rm', '-v', '{0}:/workspace'.format(workspace),
|
||||
'alpine', 'rm', '-rf', '/workspace/boulder'])
|
||||
|
||||
shutil.rmtree(workspace)
|
||||
|
||||
return workspace, cleanup
|
||||
|
||||
|
||||
def _prepare_acme_server(workspace, acme_type, acme_xdist):
|
||||
"""Configure and launch the ACME server, Boulder or Pebble"""
|
||||
print('=> Starting {0} instance deployment...'.format(acme_type))
|
||||
instance_path = join(workspace, acme_type)
|
||||
try:
|
||||
# Load Boulder/Pebble from git, that includes a docker-compose.yml ready for production.
|
||||
_launch_command(['git', 'clone', 'https://github.com/letsencrypt/{0}'.format(acme_type),
|
||||
'--single-branch', '--depth=1', instance_path])
|
||||
if acme_type == 'boulder':
|
||||
# Allow Boulder to ignore usual limit rate policies, useful for tests.
|
||||
os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'),
|
||||
join(instance_path, 'test/rate-limit-policies.yml'))
|
||||
if acme_type == 'pebble':
|
||||
# Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid
|
||||
# nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment.
|
||||
with open(os.path.join(instance_path, 'docker-compose.yml'), 'r') as file_handler:
|
||||
config = yaml.load(file_handler.read())
|
||||
|
||||
config['services']['pebble'].setdefault('environment', [])\
|
||||
.extend(['PEBBLE_VA_NOSLEEP=1', 'PEBBLE_WFE_NONCEREJECT=0'])
|
||||
with open(os.path.join(instance_path, 'docker-compose.yml'), 'w') as file_handler:
|
||||
file_handler.write(yaml.dump(config))
|
||||
|
||||
# Launch the ACME CA server.
|
||||
_launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path)
|
||||
|
||||
# Wait for the ACME CA server to be up.
|
||||
print('=> Waiting for pebble instance to respond...')
|
||||
misc.check_until_timeout(self.acme_xdist['directory_url'])
|
||||
|
||||
print('=> Finished pebble instance deployment.')
|
||||
|
||||
def _prepare_boulder_server(self):
|
||||
"""Configure and launch the Boulder server"""
|
||||
print('=> Starting boulder instance deployment...')
|
||||
instance_path = join(self._workspace, 'boulder')
|
||||
|
||||
# Load Boulder from git, that includes a docker-compose.yml ready for production.
|
||||
process = self._launch_process(['git', 'clone', 'https://github.com/letsencrypt/boulder',
|
||||
'--single-branch', '--depth=1', instance_path])
|
||||
process.wait()
|
||||
|
||||
# Allow Boulder to ignore usual limit rate policies, useful for tests.
|
||||
os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'),
|
||||
join(instance_path, 'test/rate-limit-policies.yml'))
|
||||
|
||||
# Launch the Boulder server
|
||||
self._launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path)
|
||||
|
||||
# Wait for the ACME CA server to be up.
|
||||
print('=> Waiting for boulder instance to respond...')
|
||||
misc.check_until_timeout(self.acme_xdist['directory_url'], attempts=240)
|
||||
print('=> Waiting for {0} instance to respond...'.format(acme_type))
|
||||
misc.check_until_timeout(acme_xdist['directory_url'])
|
||||
|
||||
# Configure challtestsrv to answer any A record request with ip of the docker host.
|
||||
response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT),
|
||||
json={'ip': '10.77.77.1'})
|
||||
acme_subnet = '10.77.77' if acme_type == 'boulder' else '10.30.50'
|
||||
response = requests.post('http://localhost:{0}/set-default-ipv4'
|
||||
.format(acme_xdist['challtestsrv_port']),
|
||||
json={'ip': '{0}.1'.format(acme_subnet)})
|
||||
response.raise_for_status()
|
||||
|
||||
print('=> Finished boulder instance deployment.')
|
||||
print('=> Finished {0} instance deployment.'.format(acme_type))
|
||||
except BaseException:
|
||||
print('Error while setting up {0} instance.'.format(acme_type))
|
||||
raise
|
||||
|
||||
def _prepare_http_proxy(self):
|
||||
"""Configure and launch an HTTP proxy"""
|
||||
print('=> Configuring the HTTP proxy...')
|
||||
mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port)
|
||||
for node, port in self.acme_xdist['http_port'].items()}
|
||||
command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)]
|
||||
self._launch_process(command)
|
||||
print('=> Finished configuring the HTTP proxy.')
|
||||
|
||||
def _launch_process(self, command, cwd=os.getcwd(), env=None):
|
||||
"""Launch silently an subprocess OS command"""
|
||||
if not env:
|
||||
env = os.environ
|
||||
process = subprocess.Popen(command, stdout=self._stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env)
|
||||
self._processes.append(process)
|
||||
return process
|
||||
def _prepare_http_proxy(acme_xdist):
|
||||
"""Configure and launch an HTTP proxy"""
|
||||
print('=> Configuring the HTTP proxy...')
|
||||
mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port)
|
||||
for node, port in acme_xdist['http_port'].items()}
|
||||
command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)]
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
print('=> Finished configuring the HTTP proxy.')
|
||||
|
||||
return process
|
||||
|
||||
|
||||
def _launch_command(command, cwd=os.getcwd()):
|
||||
"""Launch silently an OS command, output will be displayed in case of failure"""
|
||||
try:
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT, cwd=cwd, universal_newlines=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
sys.stderr.write(e.output)
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
@@ -194,7 +191,8 @@ def main():
|
||||
raise ValueError('Invalid server value {0}, should be one of {1}'
|
||||
.format(server_type, possible_values))
|
||||
|
||||
acme_server = ACMEServer(server_type, [], http_proxy=False, stdout=True)
|
||||
acme_server = setup_acme_server(server_type, [], False)
|
||||
process = None
|
||||
|
||||
try:
|
||||
with acme_server as acme_xdist:
|
||||
@@ -202,10 +200,15 @@ def main():
|
||||
.format(acme_xdist['directory_url']))
|
||||
print('--> Press CTRL+C to stop the ACME server.')
|
||||
|
||||
docker_name = 'pebble_pebble_1' if 'pebble' in server_type else 'boulder_boulder_1'
|
||||
process = subprocess.Popen(['docker', 'logs', '-f', docker_name])
|
||||
|
||||
while True:
|
||||
time.sleep(3600)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
if process:
|
||||
process.terminate()
|
||||
process.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -28,13 +28,12 @@ RSA_KEY_TYPE = 'rsa'
|
||||
ECDSA_KEY_TYPE = 'ecdsa'
|
||||
|
||||
|
||||
def check_until_timeout(url, attempts=30):
|
||||
def check_until_timeout(url):
|
||||
"""
|
||||
Wait and block until given url responds with status 200, or raise an exception
|
||||
after the specified number of attempts.
|
||||
after 150 attempts.
|
||||
:param str url: the URL to test
|
||||
:param int attempts: the number of times to try to connect to the URL
|
||||
:raise ValueError: exception raised if unable to reach the URL
|
||||
:raise ValueError: exception raised after 150 unsuccessful attempts to reach the URL
|
||||
"""
|
||||
try:
|
||||
import urllib3
|
||||
@@ -44,7 +43,7 @@ def check_until_timeout(url, attempts=30):
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
for _ in range(attempts):
|
||||
for _ in range(0, 150):
|
||||
time.sleep(1)
|
||||
try:
|
||||
if requests.get(url, verify=False).status_code == 200:
|
||||
@@ -52,7 +51,7 @@ def check_until_timeout(url, attempts=30):
|
||||
except requests.exceptions.ConnectionError:
|
||||
pass
|
||||
|
||||
raise ValueError('Error, url did not respond after {0} attempts: {1}'.format(attempts, url))
|
||||
raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url))
|
||||
|
||||
|
||||
class GracefulTCPServer(socketserver.TCPServer):
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import json
|
||||
import platform
|
||||
import os
|
||||
import stat
|
||||
|
||||
import pkg_resources
|
||||
import requests
|
||||
|
||||
PEBBLE_VERSION = 'v2.1.0'
|
||||
ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets')
|
||||
|
||||
|
||||
def fetch(workspace):
|
||||
suffix = '{0}-{1}{2}'.format(platform.system().lower(),
|
||||
platform.machine().lower().replace('x86_64', 'amd64'),
|
||||
'.exe' if platform.system() == 'Windows' else '')
|
||||
|
||||
pebble_path = _fetch_asset('pebble', suffix)
|
||||
challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix)
|
||||
pebble_config_path = _build_pebble_config(workspace)
|
||||
|
||||
return pebble_path, challtestsrv_path, pebble_config_path
|
||||
|
||||
|
||||
def _fetch_asset(asset, suffix):
|
||||
asset_path = os.path.join(ASSETS_PATH, '{0}_{1}_{2}'.format(asset, PEBBLE_VERSION, suffix))
|
||||
if not os.path.exists(asset_path):
|
||||
asset_url = ('https://github.com/letsencrypt/pebble/releases/download/{0}/{1}_{2}'
|
||||
.format(PEBBLE_VERSION, asset, suffix))
|
||||
response = requests.get(asset_url)
|
||||
response.raise_for_status()
|
||||
with open(asset_path, 'wb') as file_h:
|
||||
file_h.write(response.content)
|
||||
os.chmod(asset_path, os.stat(asset_path).st_mode | stat.S_IEXEC)
|
||||
|
||||
return asset_path
|
||||
|
||||
|
||||
def _build_pebble_config(workspace):
|
||||
config_path = os.path.join(workspace, 'pebble-config.json')
|
||||
with open(config_path, 'w') as file_h:
|
||||
file_h.write(json.dumps({
|
||||
'pebble': {
|
||||
'listenAddress': '0.0.0.0:14000',
|
||||
'certificate': os.path.join(ASSETS_PATH, 'cert.pem'),
|
||||
'privateKey': os.path.join(ASSETS_PATH, 'key.pem'),
|
||||
'httpPort': 5002,
|
||||
'tlsPort': 5001,
|
||||
},
|
||||
}))
|
||||
|
||||
return config_path
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:stretch
|
||||
FROM debian:jessie
|
||||
MAINTAINER Brad Warren <bmw@eff.org>
|
||||
|
||||
# no need to mkdir anything:
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
|
||||
5
certbot-dns-cloudflare/Dockerfile
Normal file
5
certbot-dns-cloudflare/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-cloudflare
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudflare
|
||||
@@ -22,9 +22,7 @@ Credentials
|
||||
|
||||
Use of this plugin requires a configuration file containing Cloudflare API
|
||||
credentials, obtained from your Cloudflare
|
||||
`account page <https://www.cloudflare.com/a/account/my-account>`_. This plugin
|
||||
does not currently support Cloudflare's "API Tokens", so please ensure you use
|
||||
the "Global API Key" for authentication.
|
||||
`account page <https://www.cloudflare.com/a/account/my-account>`_.
|
||||
|
||||
.. code-block:: ini
|
||||
:name: credentials.ini
|
||||
|
||||
@@ -10,7 +10,7 @@ from certbot.plugins import dns_common
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens'
|
||||
ACCOUNT_URL = 'https://www.cloudflare.com/a/account/my-account'
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-cloudxns/Dockerfile
Normal file
5
certbot-dns-cloudxns/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-cloudxns
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudxns
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-digitalocean/Dockerfile
Normal file
5
certbot-dns-digitalocean/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-digitalocean
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-digitalocean
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-dnsimple/Dockerfile
Normal file
5
certbot-dns-dnsimple/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-dnsimple
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsimple
|
||||
@@ -3,7 +3,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-dnsmadeeasy/Dockerfile
Normal file
5
certbot-dns-dnsmadeeasy/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-dnsmadeeasy
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsmadeeasy
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-gehirn/Dockerfile
Normal file
5
certbot-dns-gehirn/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-gehirn
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-gehirn
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
||||
5
certbot-dns-google/Dockerfile
Normal file
5
certbot-dns-google/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-google
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-google
|
||||
@@ -274,11 +274,10 @@ class _GoogleClient(object):
|
||||
raise errors.PluginError('Encountered error finding managed zone: {0}'
|
||||
.format(e))
|
||||
|
||||
for zone in zones:
|
||||
zone_id = zone['id']
|
||||
if 'privateVisibilityConfig' not in zone:
|
||||
logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name)
|
||||
return zone_id
|
||||
if zones:
|
||||
zone_id = zones[0]['id']
|
||||
logger.debug('Found id of %s for %s using name %s', zone_id, domain, zone_name)
|
||||
return zone_id
|
||||
|
||||
raise errors.PluginError('Unable to determine managed zone for {0} using zone names: {1}.'
|
||||
.format(domain, zone_dns_name_guesses))
|
||||
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-linode/Dockerfile
Normal file
5
certbot-dns-linode/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-linode
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-linode
|
||||
@@ -1,7 +1,7 @@
|
||||
from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
||||
5
certbot-dns-luadns/Dockerfile
Normal file
5
certbot-dns-luadns/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-luadns
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-luadns
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-nsone/Dockerfile
Normal file
5
certbot-dns-nsone/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-nsone
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-nsone
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-ovh/Dockerfile
Normal file
5
certbot-dns-ovh/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-ovh
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-ovh
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-rfc2136/Dockerfile
Normal file
5
certbot-dns-rfc2136/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-rfc2136
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-rfc2136
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-route53/Dockerfile
Normal file
5
certbot-dns-route53/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-route53
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-route53
|
||||
@@ -1,3 +1,3 @@
|
||||
include LICENSE.txt
|
||||
include LICENSE
|
||||
include README
|
||||
recursive-include docs *
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
5
certbot-dns-sakuracloud/Dockerfile
Normal file
5
certbot-dns-sakuracloud/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-sakuracloud
|
||||
|
||||
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-sakuracloud
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
||||
@@ -20,6 +20,7 @@ from certbot import crypto_util
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
from certbot.plugins import common
|
||||
|
||||
@@ -902,9 +903,13 @@ class NginxConfigurator(common.Installer):
|
||||
have permissions of root.
|
||||
|
||||
"""
|
||||
util.make_or_verify_dir(self.config.work_dir, core_constants.CONFIG_DIRS_MODE)
|
||||
util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE)
|
||||
util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE)
|
||||
uid = misc.os_geteuid()
|
||||
util.make_or_verify_dir(
|
||||
self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid)
|
||||
util.make_or_verify_dir(
|
||||
self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid)
|
||||
util.make_or_verify_dir(
|
||||
self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid)
|
||||
|
||||
def get_version(self):
|
||||
"""Return version of Nginx Server.
|
||||
|
||||
@@ -24,7 +24,6 @@ UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt"
|
||||
|
||||
SSL_OPTIONS_HASHES_NEW = [
|
||||
'63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf',
|
||||
'2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2',
|
||||
]
|
||||
"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9"""
|
||||
|
||||
@@ -35,7 +34,6 @@ ALL_SSL_OPTIONS_HASHES = [
|
||||
'7f95624dd95cf5afc708b9f967ee83a24b8025dc7c8d9df2b556bbc64256b3ff',
|
||||
'394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d',
|
||||
'4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16',
|
||||
'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4',
|
||||
] + SSL_OPTIONS_HASHES_NEW
|
||||
"""SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC"""
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# the up-to-date file that you will need to refer to when manually updating
|
||||
# this file.
|
||||
|
||||
ssl_session_cache shared:le_nginx_SSL:10m;
|
||||
ssl_session_cache shared:le_nginx_SSL:1m;
|
||||
ssl_session_timeout 1440m;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# the up-to-date file that you will need to refer to when manually updating
|
||||
# this file.
|
||||
|
||||
ssl_session_cache shared:le_nginx_SSL:10m;
|
||||
ssl_session_cache shared:le_nginx_SSL:1m;
|
||||
ssl_session_timeout 1440m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
|
||||
@@ -427,6 +427,11 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
mock_recovery_routine.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.recovery_routine)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter.view_config_changes")
|
||||
def test_view_config_changes_throws_error_from_reverter(self, mock_view_config_changes):
|
||||
mock_view_config_changes.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.view_config_changes)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter.rollback_checkpoints")
|
||||
def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints):
|
||||
mock_rollback_checkpoints.side_effect = errors.ReverterError("foo")
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Remember to update setup.py to match the package versions below.
|
||||
acme[dev]==0.29.0
|
||||
certbot[dev]==0.36.0
|
||||
certbot[dev]==0.34.0
|
||||
|
||||
@@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand
|
||||
import sys
|
||||
|
||||
|
||||
version = '0.37.0.dev0'
|
||||
version = '0.36.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.29.0',
|
||||
'certbot>=0.35.0',
|
||||
'certbot>=0.34.0',
|
||||
'mock',
|
||||
'PyOpenSSL',
|
||||
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Certbot client."""
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '0.37.0.dev0'
|
||||
__version__ = '0.36.0.dev0'
|
||||
|
||||
@@ -20,6 +20,7 @@ from certbot import constants
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -138,7 +139,8 @@ class AccountFileStorage(interfaces.AccountStorage):
|
||||
"""
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions)
|
||||
util.make_or_verify_dir(config.accounts_dir, 0o700, misc.os_geteuid(),
|
||||
self.config.strict_permissions)
|
||||
|
||||
def _account_dir_path(self, account_id):
|
||||
return self._account_dir_path_for_server_path(account_id, self.config.server_path)
|
||||
@@ -320,7 +322,8 @@ class AccountFileStorage(interfaces.AccountStorage):
|
||||
|
||||
def _save(self, account, acme, regr_only):
|
||||
account_dir_path = self._account_dir_path(account.id)
|
||||
util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions)
|
||||
util.make_or_verify_dir(account_dir_path, 0o700, misc.os_geteuid(),
|
||||
self.config.strict_permissions)
|
||||
try:
|
||||
with open(self._regr_path(account_dir_path), "w") as regr_file:
|
||||
regr = account.regr
|
||||
|
||||
@@ -15,6 +15,7 @@ from certbot import interfaces
|
||||
from certbot import ocsp
|
||||
from certbot import storage
|
||||
from certbot import util
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
from certbot.display import util as display_util
|
||||
|
||||
@@ -105,7 +106,7 @@ def lineage_for_certname(cli_config, certname):
|
||||
"""Find a lineage object with name certname."""
|
||||
configs_dir = cli_config.renewal_configs_dir
|
||||
# Verify the directory is there
|
||||
util.make_or_verify_dir(configs_dir, mode=0o755)
|
||||
util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid())
|
||||
try:
|
||||
renewal_file = storage.renewal_file_for_certname(cli_config, certname)
|
||||
except errors.CertStorageError:
|
||||
@@ -374,7 +375,7 @@ def _search_lineages(cli_config, func, initial_rv, *args):
|
||||
"""
|
||||
configs_dir = cli_config.renewal_configs_dir
|
||||
# Verify the directory is there
|
||||
util.make_or_verify_dir(configs_dir, mode=0o755)
|
||||
util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid())
|
||||
|
||||
rv = initial_rv
|
||||
for renewal_file in storage.renewal_conf_files(cli_config):
|
||||
|
||||
@@ -418,8 +418,8 @@ VERB_HELP = [
|
||||
}),
|
||||
("config_changes", {
|
||||
"short": "Show changes that Certbot has made to server configurations",
|
||||
"opts": "Options for viewing configuration changes",
|
||||
"usage": "\n\n certbot config_changes [options]\n\n"
|
||||
"opts": "Options for controlling which changes are displayed",
|
||||
"usage": "\n\n certbot config_changes --num NUM [options]\n\n"
|
||||
}),
|
||||
("rollback", {
|
||||
"short": "Roll back server conf changes made during certificate installation",
|
||||
@@ -428,7 +428,7 @@ VERB_HELP = [
|
||||
}),
|
||||
("plugins", {
|
||||
"short": "List plugins that are installed and available on your system",
|
||||
"opts": 'Options for the "plugins" subcommand',
|
||||
"opts": 'Options for for the "plugins" subcommand',
|
||||
"usage": "\n\n certbot plugins [options]\n\n"
|
||||
}),
|
||||
("update_symlinks", {
|
||||
@@ -1289,6 +1289,9 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
||||
|
||||
|
||||
def _create_subparsers(helpful):
|
||||
helpful.add("config_changes", "--num", type=int, default=flag_default("num"),
|
||||
help="How many past revisions you want to be displayed")
|
||||
|
||||
from certbot.client import sample_user_agent # avoid import loops
|
||||
helpful.add(
|
||||
None, "--user-agent", default=flag_default("user_agent"),
|
||||
@@ -1418,10 +1421,10 @@ def _plugins_parsing(helpful, plugins):
|
||||
help="Authenticator plugin name.")
|
||||
helpful.add("plugins", "-i", "--installer", default=flag_default("installer"),
|
||||
help="Installer plugin name (also used to find domains).")
|
||||
helpful.add(["plugins", "certonly", "run", "install"],
|
||||
helpful.add(["plugins", "certonly", "run", "install", "config_changes"],
|
||||
"--apache", action="store_true", default=flag_default("apache"),
|
||||
help="Obtain and install certificates using Apache")
|
||||
helpful.add(["plugins", "certonly", "run", "install"],
|
||||
helpful.add(["plugins", "certonly", "run", "install", "config_changes"],
|
||||
"--nginx", action="store_true", default=flag_default("nginx"),
|
||||
help="Obtain and install certificates using Nginx")
|
||||
helpful.add(["plugins", "certonly"], "--standalone", action="store_true",
|
||||
|
||||
@@ -30,6 +30,7 @@ from certbot import interfaces
|
||||
from certbot import reverter
|
||||
from certbot import storage
|
||||
from certbot import util
|
||||
from certbot.compat import misc
|
||||
from certbot.compat import os
|
||||
from certbot.display import enhancements
|
||||
from certbot.display import ops as display_ops
|
||||
@@ -458,7 +459,9 @@ class Client(object):
|
||||
|
||||
"""
|
||||
for path in cert_path, chain_path, fullchain_path:
|
||||
util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions)
|
||||
util.make_or_verify_dir(
|
||||
os.path.dirname(path), 0o755, misc.os_geteuid(),
|
||||
self.config.strict_permissions)
|
||||
|
||||
|
||||
cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path)
|
||||
@@ -699,7 +702,7 @@ def rollback(default_installer, checkpoints, config, plugins):
|
||||
installer.restart()
|
||||
|
||||
|
||||
def view_config_changes(config):
|
||||
def view_config_changes(config, num=None):
|
||||
"""View checkpoints and associated configuration changes.
|
||||
|
||||
.. note:: This assumes that the installation is using a Reverter object.
|
||||
@@ -710,7 +713,7 @@ def view_config_changes(config):
|
||||
"""
|
||||
rev = reverter.Reverter(config)
|
||||
rev.recovery_routine()
|
||||
rev.view_config_changes()
|
||||
rev.view_config_changes(num)
|
||||
|
||||
def _open_pem_file(cli_arg_path, pem_path):
|
||||
"""Open a pem file.
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
"""This compat module wraps os.path to forbid some functions."""
|
||||
# pylint: disable=function-redefined
|
||||
from __future__ import absolute_import
|
||||
|
||||
# First round of wrapping: we import statically all public attributes exposed by the os.path
|
||||
# module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path
|
||||
# members are available in certbot.compat.path.
|
||||
from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden
|
||||
|
||||
# Second round of wrapping: we import dynamically all attributes from the os.path module that have
|
||||
# not yet been imported by the first round (static star import).
|
||||
import os.path as std_os_path # pylint: disable=os-module-forbidden
|
||||
import sys as std_sys
|
||||
|
||||
ourselves = std_sys.modules[__name__]
|
||||
for attribute in dir(std_os_path):
|
||||
# Check if the attribute does not already exist in our module. It could be internal attributes
|
||||
# of the module (__name__, __doc__), or attributes from standard os.path already imported with
|
||||
# `from os.path import *`.
|
||||
if not hasattr(ourselves, attribute):
|
||||
setattr(ourselves, attribute, getattr(std_os_path, attribute))
|
||||
|
||||
# Clean all remaining importables that are not from the core os.path module.
|
||||
del ourselves, std_os_path, std_sys
|
||||
|
||||
|
||||
# Function os.path.realpath is broken on some versions of Python for Windows.
|
||||
def realpath(*unused_args, **unused_kwargs):
|
||||
"""Method os.path.realpath() is forbidden"""
|
||||
raise RuntimeError('Usage of os.path.realpath() is forbidden. '
|
||||
'Use certbot.compat.filesystem.realpath() instead.')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user