Compare commits
89 Commits
refactor-c
...
azure-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6192218df5 | ||
|
|
c1038dc139 | ||
|
|
91c63e7529 | ||
|
|
2b78d3c4e2 | ||
|
|
859dc38cb9 | ||
|
|
f66314926a | ||
|
|
9c345ac301 | ||
|
|
af21d1d56e | ||
|
|
49912732ac | ||
|
|
8fb9a395ab | ||
|
|
35fb99b86f | ||
|
|
127d2dc307 | ||
|
|
569df2d37a | ||
|
|
ff732bf975 | ||
|
|
77871ba71c | ||
|
|
cd0acf5dcc | ||
|
|
8e4dc0a48c | ||
|
|
316e4640f8 | ||
|
|
537bee0994 | ||
|
|
e9895d2ec6 | ||
|
|
5992d521e2 | ||
|
|
4ca86d9482 | ||
|
|
bc3088121b | ||
|
|
dbda499b08 | ||
|
|
6df90d17ae | ||
|
|
e4a0edc7af | ||
|
|
1285297b23 | ||
|
|
9e3c348dff | ||
|
|
3bfaf41d3d | ||
|
|
06599a1e18 | ||
|
|
30ec4cafe1 | ||
|
|
c6d35549d6 | ||
|
|
9a256ca4fe | ||
|
|
809cb516c9 | ||
|
|
07abe7a8d6 | ||
|
|
2fd85a4f36 | ||
|
|
44b97df4e9 | ||
|
|
78168a5248 | ||
|
|
69aec55ead | ||
|
|
7f63141e41 | ||
|
|
d72a1a71d2 | ||
|
|
68f4ae12be | ||
|
|
144d4f2b44 | ||
|
|
e362948d45 | ||
|
|
6edb4e1a39 | ||
|
|
b1fb3296e9 | ||
|
|
3147026211 | ||
|
|
9f8e4507ad | ||
|
|
50ea608553 | ||
|
|
fa67b7ba0f | ||
|
|
6309ded92f | ||
|
|
5a4f158c55 | ||
|
|
a2be8e1956 | ||
|
|
2f737ee292 | ||
|
|
8c75a9de9f | ||
|
|
24aa1e9127 | ||
|
|
f4c0a9fd63 | ||
|
|
f169c37153 | ||
|
|
a489079208 | ||
|
|
ddf68aea80 | ||
|
|
2ae090529e | ||
|
|
4ea98d830b | ||
|
|
0b5df9049a | ||
|
|
330977e988 | ||
|
|
4fd04366aa | ||
|
|
2633c3ffb6 | ||
|
|
5b29e4616c | ||
|
|
32904d8c9e | ||
|
|
d68f37ae88 | ||
|
|
b3071aab29 | ||
|
|
2aac24c982 | ||
|
|
20df5507ae | ||
|
|
36311a276b | ||
|
|
22685ef86f | ||
|
|
c3cfd412c9 | ||
|
|
0b21e716ca | ||
|
|
8b90b55518 | ||
|
|
247d9cd887 | ||
|
|
d6ef34a03e | ||
|
|
9819443440 | ||
|
|
84b57fac93 | ||
|
|
7d79c91e9b | ||
|
|
df584a3b90 | ||
|
|
d3a4b8fd8c | ||
|
|
f3ed133744 | ||
|
|
601a114d1b | ||
|
|
86926dff92 | ||
|
|
097c76f512 | ||
|
|
2bc64183a8 |
13
.azure-pipelines/advanced-test.yml
Normal file
13
.azure-pipelines/advanced-test.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Advanced pipeline for running our full test suite on demand.
|
||||
trigger:
|
||||
# When changing these triggers, please ensure the documentation under
|
||||
# "Running tests in CI" is still correct.
|
||||
- azure-test-*
|
||||
- test-*
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
# Any addition here should be reflected in the advanced and release pipelines.
|
||||
# It is advised to declare all jobs here as templates to improve maintainability.
|
||||
- template: templates/tests-suite.yml
|
||||
- template: templates/installer-tests.yml
|
||||
@@ -1,12 +1,7 @@
|
||||
# Advanced pipeline for isolated checks and release purpose
|
||||
# Advanced pipeline for running our full test suite on protected branches.
|
||||
trigger:
|
||||
# When changing these triggers, please ensure the documentation under
|
||||
# "Running tests in CI" is still correct.
|
||||
- azure-test-*
|
||||
- test-*
|
||||
- '*.x'
|
||||
pr:
|
||||
- test-*
|
||||
pr: none
|
||||
# This pipeline is also nightly run on master
|
||||
schedules:
|
||||
- cron: "0 4 * * *"
|
||||
@@ -17,7 +12,7 @@ schedules:
|
||||
always: true
|
||||
|
||||
jobs:
|
||||
# Any addition here should be reflected in the release pipeline.
|
||||
# Any addition here should be reflected in the advanced-test and release pipelines.
|
||||
# It is advised to declare all jobs here as templates to improve maintainability.
|
||||
- template: templates/tests-suite.yml
|
||||
- template: templates/installer-tests.yml
|
||||
|
||||
@@ -6,7 +6,7 @@ trigger:
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
# Any addition here should be reflected in the advanced pipeline.
|
||||
# Any addition here should be reflected in the advanced and advanced-test pipelines.
|
||||
# It is advised to declare all jobs here as templates to improve maintainability.
|
||||
- template: templates/tests-suite.yml
|
||||
- template: templates/installer-tests.yml
|
||||
|
||||
@@ -28,15 +28,20 @@ jobs:
|
||||
imageName: windows-2019
|
||||
win2016:
|
||||
imageName: vs2017-win2016
|
||||
win2012r2:
|
||||
imageName: vs2015-win2012r2
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
steps:
|
||||
- powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe
|
||||
displayName: Get Python
|
||||
- script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3
|
||||
displayName: Install Python
|
||||
- powershell: |
|
||||
$currentVersion = $PSVersionTable.PSVersion
|
||||
if ($currentVersion.Major -ne 5) {
|
||||
throw "Powershell version is not 5.x"
|
||||
}
|
||||
condition: eq(variables['imageName'], 'vs2017-win2016')
|
||||
displayName: Check Powershell 5.x is used in vs2017-win2016
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: 3.8
|
||||
addToPath: true
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
artifact: windows-installer
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
jobs:
|
||||
- job: test
|
||||
pool:
|
||||
vmImage: vs2017-win2016
|
||||
strategy:
|
||||
matrix:
|
||||
py35:
|
||||
macos-py27:
|
||||
IMAGE_NAME: macOS-10.14
|
||||
PYTHON_VERSION: 2.7
|
||||
TOXENV: py27
|
||||
macos-py38:
|
||||
IMAGE_NAME: macOS-10.14
|
||||
PYTHON_VERSION: 3.8
|
||||
TOXENV: py38
|
||||
windows-py35:
|
||||
IMAGE_NAME: vs2017-win2016
|
||||
PYTHON_VERSION: 3.5
|
||||
TOXENV: py35
|
||||
py37-cover:
|
||||
windows-py37-cover:
|
||||
IMAGE_NAME: vs2017-win2016
|
||||
PYTHON_VERSION: 3.7
|
||||
TOXENV: py37-cover
|
||||
integration-certbot:
|
||||
windows-integration-certbot:
|
||||
IMAGE_NAME: vs2017-win2016
|
||||
PYTHON_VERSION: 3.7
|
||||
TOXENV: integration-certbot
|
||||
PYTEST_ADDOPTS: --numprocesses 4
|
||||
variables:
|
||||
- group: certbot-common
|
||||
pool:
|
||||
vmImage: $(IMAGE_NAME)
|
||||
steps:
|
||||
- bash: brew install augeas
|
||||
condition: startswith(variables['IMAGE_NAME'], 'macOS')
|
||||
displayName: Install Augeas
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: $(PYTHON_VERSION)
|
||||
@@ -25,14 +37,3 @@ jobs:
|
||||
displayName: Install dependencies
|
||||
- script: python -m tox
|
||||
displayName: Run tox
|
||||
# We do not require codecov report upload to succeed. So to avoid to break the pipeline if
|
||||
# something goes wrong, each command is suffixed with a command that hides any non zero exit
|
||||
# codes and echoes an informative message instead.
|
||||
- bash: |
|
||||
curl -s https://codecov.io/bash -o codecov-bash || echo "Failed to download codecov-bash"
|
||||
chmod +x codecov-bash || echo "Failed to apply execute permissions on codecov-bash"
|
||||
./codecov-bash -F windows || echo "Codecov did not collect coverage reports"
|
||||
condition: in(variables['TOXENV'], 'py37-cover', 'integration-certbot')
|
||||
env:
|
||||
CODECOV_TOKEN: $(codecov_token)
|
||||
displayName: Publish coverage
|
||||
|
||||
18
.codecov.yml
18
.codecov.yml
@@ -1,18 +0,0 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
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.4
|
||||
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.4
|
||||
threshold: 0.1
|
||||
base: auto
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,6 +35,7 @@ tags
|
||||
tests/letstest/letest-*/
|
||||
tests/letstest/*.pem
|
||||
tests/letstest/venv/
|
||||
tests/letstest/venv3/
|
||||
|
||||
.venv
|
||||
|
||||
|
||||
41
.travis.yml
41
.travis.yml
@@ -6,7 +6,6 @@ cache:
|
||||
- $HOME/.cache/pip
|
||||
|
||||
before_script:
|
||||
- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 1024 ; fi'
|
||||
# On Travis, the fastest parallelization for integration tests has proved to be 4.
|
||||
- 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi'
|
||||
# Use Travis retry feature for farm tests since they are flaky
|
||||
@@ -45,8 +44,8 @@ matrix:
|
||||
<<: *not-on-master
|
||||
|
||||
# This job is always executed, including on master
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27-cover FYI="py27 tests + code coverage"
|
||||
- python: "3.8"
|
||||
env: TOXENV=py38-cover FYI="py38 tests + code coverage"
|
||||
|
||||
- python: "3.7"
|
||||
env: TOXENV=lint
|
||||
@@ -61,12 +60,12 @@ matrix:
|
||||
dist: trusty
|
||||
env: TOXENV='py27-{acme,apache,apache-v2,certbot,dns,nginx}-oldest'
|
||||
<<: *not-on-master
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27
|
||||
<<: *not-on-master
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35
|
||||
<<: *not-on-master
|
||||
- python: "3.8"
|
||||
env: TOXENV=py38
|
||||
<<: *not-on-master
|
||||
- sudo: required
|
||||
env: TOXENV=apache_compat
|
||||
services: docker
|
||||
@@ -91,24 +90,24 @@ matrix:
|
||||
before_install:
|
||||
addons:
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
- python: "3.7"
|
||||
env:
|
||||
- TOXENV=travis-test-farm-apache2
|
||||
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
- python: "3.7"
|
||||
env:
|
||||
- TOXENV=travis-test-farm-leauto-upgrades
|
||||
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
|
||||
git:
|
||||
depth: false # This is needed to have the history to checkout old versions of certbot-auto.
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
- python: "3.7"
|
||||
env:
|
||||
- TOXENV=travis-test-farm-certonly-standalone
|
||||
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
|
||||
<<: *extended-test-suite
|
||||
- python: "2.7"
|
||||
- python: "3.7"
|
||||
env:
|
||||
- TOXENV=travis-test-farm-sdists
|
||||
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
|
||||
@@ -224,24 +223,6 @@ matrix:
|
||||
packages: # don't install nginx and apache
|
||||
- libaugeas0
|
||||
<<: *extended-test-suite
|
||||
- language: generic
|
||||
env: TOXENV=py27
|
||||
os: osx
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python2
|
||||
<<: *extended-test-suite
|
||||
- language: generic
|
||||
env: TOXENV=py3
|
||||
os: osx
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python3
|
||||
<<: *extended-test-suite
|
||||
|
||||
# container-based infrastructure
|
||||
sudo: false
|
||||
@@ -266,15 +247,13 @@ addons:
|
||||
# version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also
|
||||
# set, pip updates dependencies it thinks are already satisfied to avoid some
|
||||
# problems with its lack of real dependency resolution.
|
||||
install: 'tools/pip_install.py -I codecov tox virtualenv'
|
||||
install: 'tools/pip_install.py -I tox virtualenv'
|
||||
# Most of the time TRAVIS_RETRY is an empty string, and has no effect on the
|
||||
# script command. It is set only to `travis_retry` during farm tests, in
|
||||
# order to trigger the Travis retry feature, and compensate the inherent
|
||||
# flakiness of these specific tests.
|
||||
script: '$TRAVIS_RETRY tox'
|
||||
|
||||
after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux'
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
|
||||
@@ -21,6 +21,7 @@ Authors
|
||||
* [Andrzej Górski](https://github.com/andrzej3393)
|
||||
* [Anselm Levskaya](https://github.com/levskaya)
|
||||
* [Antoine Jacoutot](https://github.com/ajacoutot)
|
||||
* [April King](https://github.com/april)
|
||||
* [asaph](https://github.com/asaph)
|
||||
* [Axel Beckert](https://github.com/xtaran)
|
||||
* [Bas](https://github.com/Mechazawa)
|
||||
@@ -103,6 +104,7 @@ Authors
|
||||
* [Henry Chen](https://github.com/henrychen95)
|
||||
* [Hugo van Kemenade](https://github.com/hugovk)
|
||||
* [Ingolf Becker](https://github.com/watercrossing)
|
||||
* [Ivan Nejgebauer](https://github.com/inejge)
|
||||
* [Jaap Eldering](https://github.com/eldering)
|
||||
* [Jacob Hoffman-Andrews](https://github.com/jsha)
|
||||
* [Jacob Sachs](https://github.com/jsachs)
|
||||
@@ -266,5 +268,6 @@ Authors
|
||||
* [Yomna](https://github.com/ynasser)
|
||||
* [Yoni Jah](https://github.com/yonjah)
|
||||
* [YourDaddyIsHere](https://github.com/YourDaddyIsHere)
|
||||
* [Yuseong Cho](https://github.com/g6123)
|
||||
* [Zach Shepherd](https://github.com/zjs)
|
||||
* [陈三](https://github.com/chenxsan)
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
"""ACME Identifier Validation Challenges."""
|
||||
import abc
|
||||
import codecs
|
||||
import functools
|
||||
import hashlib
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from cryptography.hazmat.primitives import hashes # type: ignore
|
||||
import josepy as jose
|
||||
import requests
|
||||
import six
|
||||
from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052
|
||||
from OpenSSL import crypto
|
||||
|
||||
from acme import crypto_util
|
||||
from acme import errors
|
||||
from acme import fields
|
||||
from acme.mixins import ResourceMixin, TypeMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,7 +35,7 @@ class Challenge(jose.TypedJSONObjectWithFields):
|
||||
return UnrecognizedChallenge.from_json(jobj)
|
||||
|
||||
|
||||
class ChallengeResponse(jose.TypedJSONObjectWithFields):
|
||||
class ChallengeResponse(ResourceMixin, TypeMixin, jose.TypedJSONObjectWithFields):
|
||||
# _fields_to_partial_json
|
||||
"""ACME challenge response."""
|
||||
TYPES = {} # type: dict
|
||||
@@ -362,29 +369,163 @@ class HTTP01(KeyAuthorizationChallenge):
|
||||
|
||||
@ChallengeResponse.register
|
||||
class TLSALPN01Response(KeyAuthorizationChallengeResponse):
|
||||
"""ACME TLS-ALPN-01 challenge response.
|
||||
|
||||
This class only allows initiating a TLS-ALPN-01 challenge returned from the
|
||||
CA. Full support for responding to TLS-ALPN-01 challenges by generating and
|
||||
serving the expected response certificate is not currently provided.
|
||||
"""
|
||||
"""ACME tls-alpn-01 challenge response."""
|
||||
typ = "tls-alpn-01"
|
||||
|
||||
PORT = 443
|
||||
"""Verification port as defined by the protocol.
|
||||
|
||||
@Challenge.register
|
||||
You can override it (e.g. for testing) by passing ``port`` to
|
||||
`simple_verify`.
|
||||
|
||||
"""
|
||||
|
||||
ID_PE_ACME_IDENTIFIER_V1 = b"1.3.6.1.5.5.7.1.30.1"
|
||||
ACME_TLS_1_PROTOCOL = "acme-tls/1"
|
||||
|
||||
@property
|
||||
def h(self):
|
||||
"""Hash value stored in challenge certificate"""
|
||||
return hashlib.sha256(self.key_authorization.encode('utf-8')).digest()
|
||||
|
||||
def gen_cert(self, domain, key=None, bits=2048):
|
||||
"""Generate tls-alpn-01 certificate.
|
||||
|
||||
:param unicode domain: Domain verified by the challenge.
|
||||
:param OpenSSL.crypto.PKey key: Optional private key used in
|
||||
certificate generation. If not provided (``None``), then
|
||||
fresh key will be generated.
|
||||
:param int bits: Number of bits for newly generated key.
|
||||
|
||||
:rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
|
||||
|
||||
"""
|
||||
if key is None:
|
||||
key = crypto.PKey()
|
||||
key.generate_key(crypto.TYPE_RSA, bits)
|
||||
|
||||
|
||||
der_value = b"DER:" + codecs.encode(self.h, 'hex')
|
||||
acme_extension = crypto.X509Extension(self.ID_PE_ACME_IDENTIFIER_V1,
|
||||
critical=True, value=der_value)
|
||||
|
||||
return crypto_util.gen_ss_cert(key, [domain], force_san=True,
|
||||
extensions=[acme_extension]), key
|
||||
|
||||
def probe_cert(self, domain, host=None, port=None):
|
||||
"""Probe tls-alpn-01 challenge certificate.
|
||||
|
||||
:param unicode domain: domain being validated, required.
|
||||
:param string host: IP address used to probe the certificate.
|
||||
:param int port: Port used to probe the certificate.
|
||||
|
||||
"""
|
||||
if host is None:
|
||||
host = socket.gethostbyname(domain)
|
||||
logger.debug('%s resolved to %s', domain, host)
|
||||
if port is None:
|
||||
port = self.PORT
|
||||
|
||||
return crypto_util.probe_sni(host=host, port=port, name=domain,
|
||||
alpn_protocols=[self.ACME_TLS_1_PROTOCOL])
|
||||
|
||||
def verify_cert(self, domain, cert):
|
||||
"""Verify tls-alpn-01 challenge certificate.
|
||||
|
||||
:param unicode domain: Domain name being validated.
|
||||
:param OpensSSL.crypto.X509 cert: Challenge certificate.
|
||||
|
||||
:returns: Whether the certificate was successfully verified.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
names = crypto_util._pyopenssl_cert_or_req_all_names(cert)
|
||||
logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), names)
|
||||
if len(names) != 1 or names[0].lower() != domain.lower():
|
||||
return False
|
||||
|
||||
for i in range(cert.get_extension_count()):
|
||||
ext = cert.get_extension(i)
|
||||
# FIXME: assume this is the ACME extension. Currently there is no
|
||||
# way to get full OID of an unknown extension from pyopenssl.
|
||||
if ext.get_short_name() == b'UNDEF':
|
||||
data = ext.get_data()
|
||||
return data == self.h
|
||||
|
||||
return False
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def simple_verify(self, chall, domain, account_public_key,
|
||||
cert=None, host=None, port=None):
|
||||
"""Simple verify.
|
||||
|
||||
Verify ``validation`` using ``account_public_key``, optionally
|
||||
probe tls-alpn-01 certificate and check using `verify_cert`.
|
||||
|
||||
:param .challenges.TLSALPN01 chall: Corresponding challenge.
|
||||
:param str domain: Domain name being validated.
|
||||
:param JWK account_public_key:
|
||||
:param OpenSSL.crypto.X509 cert: Optional certificate. If not
|
||||
provided (``None``) certificate will be retrieved using
|
||||
`probe_cert`.
|
||||
:param string host: IP address used to probe the certificate.
|
||||
:param int port: Port used to probe the certificate.
|
||||
|
||||
|
||||
:returns: ``True`` if and only if client's control of the domain has been verified.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if not self.verify(chall, account_public_key):
|
||||
logger.debug("Verification of key authorization in response failed")
|
||||
return False
|
||||
|
||||
if cert is None:
|
||||
try:
|
||||
cert = self.probe_cert(domain=domain, host=host, port=port)
|
||||
except errors.Error as error:
|
||||
logger.debug(str(error), exc_info=True)
|
||||
return False
|
||||
|
||||
return self.verify_cert(domain, cert)
|
||||
|
||||
|
||||
@Challenge.register # pylint: disable=too-many-ancestors
|
||||
class TLSALPN01(KeyAuthorizationChallenge):
|
||||
"""ACME tls-alpn-01 challenge.
|
||||
|
||||
This class simply allows parsing the TLS-ALPN-01 challenge returned from
|
||||
the CA. Full TLS-ALPN-01 support is not currently provided.
|
||||
|
||||
"""
|
||||
typ = "tls-alpn-01"
|
||||
"""ACME tls-alpn-01 challenge."""
|
||||
response_cls = TLSALPN01Response
|
||||
typ = response_cls.typ
|
||||
|
||||
def validation(self, account_key, **kwargs):
|
||||
"""Generate validation for the challenge."""
|
||||
raise NotImplementedError()
|
||||
"""Generate validation.
|
||||
|
||||
:param JWK account_key:
|
||||
:param unicode domain: Domain verified by the challenge.
|
||||
:param OpenSSL.crypto.PKey cert_key: Optional private key used
|
||||
in certificate generation. If not provided (``None``), then
|
||||
fresh key will be generated.
|
||||
|
||||
:rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
|
||||
|
||||
"""
|
||||
return self.response(account_key).gen_cert(
|
||||
key=kwargs.get('cert_key'),
|
||||
domain=kwargs.get('domain'))
|
||||
|
||||
@staticmethod
|
||||
def is_supported():
|
||||
"""
|
||||
Check if TLS-ALPN-01 challenge is supported on this machine.
|
||||
This implies that a recent version of OpenSSL is installed (>= 1.0.2),
|
||||
or a recent cryptography version shipped with the OpenSSL library is installed.
|
||||
|
||||
:returns: ``True`` if TLS-ALPN-01 is supported on this machine, ``False`` otherwise.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
return (hasattr(SSL.Connection, "set_alpn_protos")
|
||||
and hasattr(SSL.Context, "set_alpn_select_callback"))
|
||||
|
||||
|
||||
@Challenge.register
|
||||
|
||||
@@ -25,6 +25,7 @@ from acme.magic_typing import Dict
|
||||
from acme.magic_typing import List
|
||||
from acme.magic_typing import Set
|
||||
from acme.magic_typing import Text
|
||||
from acme.mixins import VersionedLEACMEMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -987,6 +988,8 @@ class ClientNetwork(object):
|
||||
:rtype: `josepy.JWS`
|
||||
|
||||
"""
|
||||
if isinstance(obj, VersionedLEACMEMixin):
|
||||
obj.le_acme_version = acme_version
|
||||
jobj = obj.json_dumps(indent=2).encode() if obj else b''
|
||||
logger.debug('JWS payload:\n%s', jobj)
|
||||
kwargs = {
|
||||
@@ -1022,6 +1025,9 @@ class ClientNetwork(object):
|
||||
|
||||
"""
|
||||
response_ct = response.headers.get('Content-Type')
|
||||
# Strip parameters from the media-type (rfc2616#section-3.7)
|
||||
if response_ct:
|
||||
response_ct = response_ct.split(';')[0].strip()
|
||||
try:
|
||||
# TODO: response.json() is called twice, once here, and
|
||||
# once in _get and _post clients
|
||||
@@ -1117,8 +1123,8 @@ class ClientNetwork(object):
|
||||
debug_content = response.content.decode("utf-8")
|
||||
logger.debug('Received response:\nHTTP %d\n%s\n\n%s',
|
||||
response.status_code,
|
||||
"\n".join(["{0}: {1}".format(k, v)
|
||||
for k, v in response.headers.items()]),
|
||||
"\n".join("{0}: {1}".format(k, v)
|
||||
for k, v in response.headers.items()),
|
||||
debug_content)
|
||||
return response
|
||||
|
||||
|
||||
@@ -27,19 +27,41 @@ logger = logging.getLogger(__name__)
|
||||
_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore
|
||||
|
||||
|
||||
class SSLSocket(object):
|
||||
class _DefaultCertSelection(object):
|
||||
def __init__(self, certs):
|
||||
self.certs = certs
|
||||
|
||||
def __call__(self, connection):
|
||||
server_name = connection.get_servername()
|
||||
return self.certs.get(server_name, None)
|
||||
|
||||
|
||||
class SSLSocket(object): # pylint: disable=too-few-public-methods
|
||||
"""SSL wrapper for sockets.
|
||||
|
||||
:ivar socket sock: Original wrapped socket.
|
||||
:ivar dict certs: Mapping from domain names (`bytes`) to
|
||||
`OpenSSL.crypto.X509`.
|
||||
:ivar method: See `OpenSSL.SSL.Context` for allowed values.
|
||||
:ivar alpn_selection: Hook to select negotiated ALPN protocol for
|
||||
connection.
|
||||
:ivar cert_selection: Hook to select certificate for connection. If given,
|
||||
`certs` parameter would be ignored, and therefore must be empty.
|
||||
|
||||
"""
|
||||
def __init__(self, sock, certs, method=_DEFAULT_SSL_METHOD):
|
||||
def __init__(self, sock, certs=None,
|
||||
method=_DEFAULT_SSL_METHOD, alpn_selection=None,
|
||||
cert_selection=None):
|
||||
self.sock = sock
|
||||
self.certs = certs
|
||||
self.alpn_selection = alpn_selection
|
||||
self.method = method
|
||||
if not cert_selection and not certs:
|
||||
raise ValueError("Neither cert_selection or certs specified.")
|
||||
if cert_selection and certs:
|
||||
raise ValueError("Both cert_selection and certs specified.")
|
||||
if cert_selection is None:
|
||||
cert_selection = _DefaultCertSelection(certs)
|
||||
self.cert_selection = cert_selection
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.sock, name)
|
||||
@@ -56,18 +78,19 @@ class SSLSocket(object):
|
||||
:type connection: :class:`OpenSSL.Connection`
|
||||
|
||||
"""
|
||||
server_name = connection.get_servername()
|
||||
try:
|
||||
key, cert = self.certs[server_name]
|
||||
except KeyError:
|
||||
logger.debug("Server name (%s) not recognized, dropping SSL",
|
||||
server_name)
|
||||
pair = self.cert_selection(connection)
|
||||
if pair is None:
|
||||
logger.debug("Certificate selection for server name %s failed, dropping SSL",
|
||||
connection.get_servername())
|
||||
return
|
||||
key, cert = pair
|
||||
new_context = SSL.Context(self.method)
|
||||
new_context.set_options(SSL.OP_NO_SSLv2)
|
||||
new_context.set_options(SSL.OP_NO_SSLv3)
|
||||
new_context.use_privatekey(key)
|
||||
new_context.use_certificate(cert)
|
||||
if self.alpn_selection is not None:
|
||||
new_context.set_alpn_select_callback(self.alpn_selection)
|
||||
connection.set_context(new_context)
|
||||
|
||||
class FakeConnection(object):
|
||||
@@ -92,6 +115,8 @@ class SSLSocket(object):
|
||||
context.set_options(SSL.OP_NO_SSLv2)
|
||||
context.set_options(SSL.OP_NO_SSLv3)
|
||||
context.set_tlsext_servername_callback(self._pick_certificate_cb)
|
||||
if self.alpn_selection is not None:
|
||||
context.set_alpn_select_callback(self.alpn_selection)
|
||||
|
||||
ssl_sock = self.FakeConnection(SSL.Connection(context, sock))
|
||||
ssl_sock.set_accept_state()
|
||||
@@ -107,8 +132,9 @@ class SSLSocket(object):
|
||||
return ssl_sock, addr
|
||||
|
||||
|
||||
def probe_sni(name, host, port=443, timeout=300,
|
||||
method=_DEFAULT_SSL_METHOD, source_address=('', 0)):
|
||||
def probe_sni(name, host, port=443, timeout=300, # pylint: disable=too-many-arguments
|
||||
method=_DEFAULT_SSL_METHOD, source_address=('', 0),
|
||||
alpn_protocols=None):
|
||||
"""Probe SNI server for SSL certificate.
|
||||
|
||||
:param bytes name: Byte string to send as the server name in the
|
||||
@@ -120,6 +146,8 @@ def probe_sni(name, host, port=443, timeout=300,
|
||||
:param tuple source_address: Enables multi-path probing (selection
|
||||
of source interface). See `socket.creation_connection` for more
|
||||
info. Available only in Python 2.7+.
|
||||
:param alpn_protocols: Protocols to request using ALPN.
|
||||
:type alpn_protocols: `list` of `bytes`
|
||||
|
||||
:raises acme.errors.Error: In case of any problems.
|
||||
|
||||
@@ -149,6 +177,8 @@ def probe_sni(name, host, port=443, timeout=300,
|
||||
client_ssl = SSL.Connection(context, client)
|
||||
client_ssl.set_connect_state()
|
||||
client_ssl.set_tlsext_host_name(name) # pyOpenSSL>=0.13
|
||||
if alpn_protocols is not None:
|
||||
client_ssl.set_alpn_protos(alpn_protocols)
|
||||
try:
|
||||
client_ssl.do_handshake()
|
||||
client_ssl.shutdown()
|
||||
@@ -239,12 +269,14 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
|
||||
|
||||
|
||||
def gen_ss_cert(key, domains, not_before=None,
|
||||
validity=(7 * 24 * 60 * 60), force_san=True):
|
||||
validity=(7 * 24 * 60 * 60), force_san=True, extensions=None):
|
||||
"""Generate new self-signed certificate.
|
||||
|
||||
:type domains: `list` of `unicode`
|
||||
:param OpenSSL.crypto.PKey key:
|
||||
:param bool force_san:
|
||||
:param extensions: List of additional extensions to include in the cert.
|
||||
:type extensions: `list` of `OpenSSL.crypto.X509Extension`
|
||||
|
||||
If more than one domain is provided, all of the domains are put into
|
||||
``subjectAltName`` X.509 extension and first domain is set as the
|
||||
@@ -257,10 +289,13 @@ def gen_ss_cert(key, domains, not_before=None,
|
||||
cert.set_serial_number(int(binascii.hexlify(os.urandom(16)), 16))
|
||||
cert.set_version(2)
|
||||
|
||||
extensions = [
|
||||
if extensions is None:
|
||||
extensions = []
|
||||
|
||||
extensions.append(
|
||||
crypto.X509Extension(
|
||||
b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
|
||||
]
|
||||
)
|
||||
|
||||
cert.get_subject().CN = domains[0]
|
||||
# TODO: what to put into cert.get_subject()?
|
||||
|
||||
@@ -11,6 +11,5 @@ try:
|
||||
# mypy doesn't respect modifying sys.modules
|
||||
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
from typing import Collection, IO # type: ignore
|
||||
# pylint: enable=unused-import
|
||||
except ImportError:
|
||||
sys.modules[__name__] = TypingClass()
|
||||
|
||||
@@ -9,6 +9,7 @@ from acme import errors
|
||||
from acme import fields
|
||||
from acme import jws
|
||||
from acme import util
|
||||
from acme.mixins import ResourceMixin
|
||||
|
||||
try:
|
||||
from collections.abc import Hashable
|
||||
@@ -356,13 +357,13 @@ class Registration(ResourceBody):
|
||||
|
||||
|
||||
@Directory.register
|
||||
class NewRegistration(Registration):
|
||||
class NewRegistration(ResourceMixin, Registration):
|
||||
"""New registration."""
|
||||
resource_type = 'new-reg'
|
||||
resource = fields.Resource(resource_type)
|
||||
|
||||
|
||||
class UpdateRegistration(Registration):
|
||||
class UpdateRegistration(ResourceMixin, Registration):
|
||||
"""Update registration."""
|
||||
resource_type = 'reg'
|
||||
resource = fields.Resource(resource_type)
|
||||
@@ -498,13 +499,13 @@ class Authorization(ResourceBody):
|
||||
|
||||
|
||||
@Directory.register
|
||||
class NewAuthorization(Authorization):
|
||||
class NewAuthorization(ResourceMixin, Authorization):
|
||||
"""New authorization."""
|
||||
resource_type = 'new-authz'
|
||||
resource = fields.Resource(resource_type)
|
||||
|
||||
|
||||
class UpdateAuthorization(Authorization):
|
||||
class UpdateAuthorization(ResourceMixin, Authorization):
|
||||
"""Update authorization."""
|
||||
resource_type = 'authz'
|
||||
resource = fields.Resource(resource_type)
|
||||
@@ -522,7 +523,7 @@ class AuthorizationResource(ResourceWithURI):
|
||||
|
||||
|
||||
@Directory.register
|
||||
class CertificateRequest(jose.JSONObjectWithFields):
|
||||
class CertificateRequest(ResourceMixin, jose.JSONObjectWithFields):
|
||||
"""ACME new-cert request.
|
||||
|
||||
:ivar josepy.util.ComparableX509 csr:
|
||||
@@ -548,7 +549,7 @@ class CertificateResource(ResourceWithURI):
|
||||
|
||||
|
||||
@Directory.register
|
||||
class Revocation(jose.JSONObjectWithFields):
|
||||
class Revocation(ResourceMixin, jose.JSONObjectWithFields):
|
||||
"""Revocation message.
|
||||
|
||||
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
|
||||
|
||||
65
acme/acme/mixins.py
Normal file
65
acme/acme/mixins.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Useful mixins for Challenge and Resource objects"""
|
||||
|
||||
|
||||
class VersionedLEACMEMixin(object):
|
||||
"""This mixin stores the version of Let's Encrypt's endpoint being used."""
|
||||
@property
|
||||
def le_acme_version(self):
|
||||
"""Define the version of ACME protocol to use"""
|
||||
return getattr(self, '_le_acme_version', 1)
|
||||
|
||||
@le_acme_version.setter
|
||||
def le_acme_version(self, version):
|
||||
# We need to use object.__setattr__ to not depend on the specific implementation of
|
||||
# __setattr__ in current class (eg. jose.TypedJSONObjectWithFields raises AttributeError
|
||||
# for any attempt to set an attribute to make objects immutable).
|
||||
object.__setattr__(self, '_le_acme_version', version)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key == 'le_acme_version':
|
||||
# Required for @property to operate properly. See comment above.
|
||||
object.__setattr__(self, key, value)
|
||||
else:
|
||||
super(VersionedLEACMEMixin, self).__setattr__(key, value) # pragma: no cover
|
||||
|
||||
|
||||
class ResourceMixin(VersionedLEACMEMixin):
|
||||
"""
|
||||
This mixin generates a RFC8555 compliant JWS payload
|
||||
by removing the `resource` field if needed (eg. ACME v2 protocol).
|
||||
"""
|
||||
def to_partial_json(self):
|
||||
"""See josepy.JSONDeserializable.to_partial_json()"""
|
||||
return _safe_jobj_compliance(super(ResourceMixin, self),
|
||||
'to_partial_json', 'resource')
|
||||
|
||||
def fields_to_partial_json(self):
|
||||
"""See josepy.JSONObjectWithFields.fields_to_partial_json()"""
|
||||
return _safe_jobj_compliance(super(ResourceMixin, self),
|
||||
'fields_to_partial_json', 'resource')
|
||||
|
||||
|
||||
class TypeMixin(VersionedLEACMEMixin):
|
||||
"""
|
||||
This mixin allows generation of a RFC8555 compliant JWS payload
|
||||
by removing the `type` field if needed (eg. ACME v2 protocol).
|
||||
"""
|
||||
def to_partial_json(self):
|
||||
"""See josepy.JSONDeserializable.to_partial_json()"""
|
||||
return _safe_jobj_compliance(super(TypeMixin, self),
|
||||
'to_partial_json', 'type')
|
||||
|
||||
def fields_to_partial_json(self):
|
||||
"""See josepy.JSONObjectWithFields.fields_to_partial_json()"""
|
||||
return _safe_jobj_compliance(super(TypeMixin, self),
|
||||
'fields_to_partial_json', 'type')
|
||||
|
||||
|
||||
def _safe_jobj_compliance(instance, jobj_method, uncompliant_field):
|
||||
if hasattr(instance, jobj_method):
|
||||
jobj = getattr(instance, jobj_method)()
|
||||
if instance.le_acme_version == 2:
|
||||
jobj.pop(uncompliant_field, None)
|
||||
return jobj
|
||||
|
||||
raise AttributeError('Method {0}() is not implemented.'.format(jobj_method)) # pragma: no cover
|
||||
@@ -33,7 +33,14 @@ class TLSServer(socketserver.TCPServer):
|
||||
|
||||
def _wrap_sock(self):
|
||||
self.socket = crypto_util.SSLSocket(
|
||||
self.socket, certs=self.certs, method=self.method)
|
||||
self.socket, cert_selection=self._cert_selection,
|
||||
alpn_selection=getattr(self, '_alpn_selection', None),
|
||||
method=self.method)
|
||||
|
||||
def _cert_selection(self, connection): # pragma: no cover
|
||||
"""Callback selecting certificate for connection."""
|
||||
server_name = connection.get_servername()
|
||||
return self.certs.get(server_name, None)
|
||||
|
||||
def server_bind(self):
|
||||
self._wrap_sock()
|
||||
@@ -120,6 +127,40 @@ class BaseDualNetworkedServers(object):
|
||||
self.threads = []
|
||||
|
||||
|
||||
class TLSALPN01Server(TLSServer, ACMEServerMixin):
|
||||
"""TLSALPN01 Server."""
|
||||
|
||||
ACME_TLS_1_PROTOCOL = b"acme-tls/1"
|
||||
|
||||
def __init__(self, server_address, certs, challenge_certs, ipv6=False):
|
||||
TLSServer.__init__(
|
||||
self, server_address, _BaseRequestHandlerWithLogging, certs=certs,
|
||||
ipv6=ipv6)
|
||||
self.challenge_certs = challenge_certs
|
||||
|
||||
def _cert_selection(self, connection):
|
||||
# TODO: We would like to serve challenge cert only if asked for it via
|
||||
# ALPN. To do this, we need to retrieve the list of protos from client
|
||||
# hello, but this is currently impossible with openssl [0], and ALPN
|
||||
# negotiation is done after cert selection.
|
||||
# Therefore, currently we always return challenge cert, and terminate
|
||||
# handshake in alpn_selection() if ALPN protos are not what we expect.
|
||||
# [0] https://github.com/openssl/openssl/issues/4952
|
||||
server_name = connection.get_servername()
|
||||
logger.debug("Serving challenge cert for server name %s", server_name)
|
||||
return self.challenge_certs.get(server_name, None)
|
||||
|
||||
def _alpn_selection(self, _connection, alpn_protos):
|
||||
"""Callback to select alpn protocol."""
|
||||
if len(alpn_protos) == 1 and alpn_protos[0] == self.ACME_TLS_1_PROTOCOL:
|
||||
logger.debug("Agreed on %s ALPN", self.ACME_TLS_1_PROTOCOL)
|
||||
return self.ACME_TLS_1_PROTOCOL
|
||||
logger.debug("Cannot agree on ALPN proto. Got: %s", str(alpn_protos))
|
||||
# Explicitly close the connection now, by returning an empty string.
|
||||
# See https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_alpn_select_callback # pylint: disable=line-too-long
|
||||
return b""
|
||||
|
||||
|
||||
class HTTPServer(BaseHTTPServer.HTTPServer):
|
||||
"""Generic HTTP Server."""
|
||||
|
||||
@@ -135,10 +176,10 @@ class HTTPServer(BaseHTTPServer.HTTPServer):
|
||||
class HTTP01Server(HTTPServer, ACMEServerMixin):
|
||||
"""HTTP01 Server."""
|
||||
|
||||
def __init__(self, server_address, resources, ipv6=False):
|
||||
def __init__(self, server_address, resources, ipv6=False, timeout=30):
|
||||
HTTPServer.__init__(
|
||||
self, server_address, HTTP01RequestHandler.partial_init(
|
||||
simple_http_resources=resources), ipv6=ipv6)
|
||||
simple_http_resources=resources, timeout=timeout), ipv6=ipv6)
|
||||
|
||||
|
||||
class HTTP01DualNetworkedServers(BaseDualNetworkedServers):
|
||||
@@ -163,6 +204,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.simple_http_resources = kwargs.pop("simple_http_resources", set())
|
||||
self.timeout = kwargs.pop('timeout', 30)
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
||||
|
||||
def log_message(self, format, *args): # pylint: disable=redefined-builtin
|
||||
@@ -212,7 +254,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
self.path)
|
||||
|
||||
@classmethod
|
||||
def partial_init(cls, simple_http_resources):
|
||||
def partial_init(cls, simple_http_resources, timeout):
|
||||
"""Partially initialize this handler.
|
||||
|
||||
This is useful because `socketserver.BaseServer` takes
|
||||
@@ -221,4 +263,18 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
"""
|
||||
return functools.partial(
|
||||
cls, simple_http_resources=simple_http_resources)
|
||||
cls, simple_http_resources=simple_http_resources,
|
||||
timeout=timeout)
|
||||
|
||||
|
||||
class _BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler):
|
||||
"""BaseRequestHandler with logging."""
|
||||
|
||||
def log_message(self, format, *args): # pylint: disable=redefined-builtin
|
||||
"""Log arbitrary message."""
|
||||
logger.debug("%s - - %s", self.client_address[0], format % args)
|
||||
|
||||
def handle(self):
|
||||
"""Handle request."""
|
||||
self.log_message("Incoming request")
|
||||
socketserver.BaseRequestHandler.handle(self)
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
# serve to show the default.
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
@@ -15,7 +17,6 @@ install_requires = [
|
||||
# 1.1.0+ is required to avoid the warnings described at
|
||||
# https://github.com/certbot/josepy/issues/13.
|
||||
'josepy>=1.1.0',
|
||||
'mock',
|
||||
# Connection.set_tlsext_host_name (>=0.13)
|
||||
'PyOpenSSL>=0.13.1',
|
||||
'pyrfc3339',
|
||||
@@ -26,6 +27,15 @@ install_requires = [
|
||||
'six>=1.9.0', # needed for python_2_unicode_compatible
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
dev_extras = [
|
||||
'pytest',
|
||||
'pytest-xdist',
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
import unittest
|
||||
|
||||
import josepy as jose
|
||||
import mock
|
||||
import OpenSSL
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import requests
|
||||
from six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from acme import errors
|
||||
|
||||
import test_util
|
||||
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
@@ -256,30 +262,87 @@ class HTTP01Test(unittest.TestCase):
|
||||
class TLSALPN01ResponseTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
from acme.challenges import TLSALPN01Response
|
||||
self.msg = TLSALPN01Response(key_authorization=u'foo')
|
||||
from acme.challenges import TLSALPN01
|
||||
self.chall = TLSALPN01(
|
||||
token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e'))
|
||||
self.domain = u'example.com'
|
||||
self.domain2 = u'example2.com'
|
||||
|
||||
self.response = self.chall.response(KEY)
|
||||
self.jmsg = {
|
||||
'resource': 'challenge',
|
||||
'type': 'tls-alpn-01',
|
||||
'keyAuthorization': u'foo',
|
||||
'keyAuthorization': self.response.key_authorization,
|
||||
}
|
||||
|
||||
from acme.challenges import TLSALPN01
|
||||
self.chall = TLSALPN01(token=(b'x' * 16))
|
||||
self.response = self.chall.response(KEY)
|
||||
|
||||
def test_to_partial_json(self):
|
||||
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
|
||||
self.msg.to_partial_json())
|
||||
self.response.to_partial_json())
|
||||
|
||||
def test_from_json(self):
|
||||
from acme.challenges import TLSALPN01Response
|
||||
self.assertEqual(self.msg, TLSALPN01Response.from_json(self.jmsg))
|
||||
self.assertEqual(self.response, TLSALPN01Response.from_json(self.jmsg))
|
||||
|
||||
def test_from_json_hashable(self):
|
||||
from acme.challenges import TLSALPN01Response
|
||||
hash(TLSALPN01Response.from_json(self.jmsg))
|
||||
|
||||
def test_gen_verify_cert(self):
|
||||
key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem')
|
||||
cert, key2 = self.response.gen_cert(self.domain, key1)
|
||||
self.assertEqual(key1, key2)
|
||||
self.assertTrue(self.response.verify_cert(self.domain, cert))
|
||||
|
||||
def test_gen_verify_cert_gen_key(self):
|
||||
cert, key = self.response.gen_cert(self.domain)
|
||||
self.assertTrue(isinstance(key, OpenSSL.crypto.PKey))
|
||||
self.assertTrue(self.response.verify_cert(self.domain, cert))
|
||||
|
||||
def test_verify_bad_cert(self):
|
||||
self.assertFalse(self.response.verify_cert(self.domain,
|
||||
test_util.load_cert('cert.pem')))
|
||||
|
||||
def test_verify_bad_domain(self):
|
||||
key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem')
|
||||
cert, key2 = self.response.gen_cert(self.domain, key1)
|
||||
self.assertEqual(key1, key2)
|
||||
self.assertFalse(self.response.verify_cert(self.domain2, cert))
|
||||
|
||||
def test_simple_verify_bad_key_authorization(self):
|
||||
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
|
||||
self.response.simple_verify(self.chall, "local", key2.public_key())
|
||||
|
||||
@mock.patch('acme.challenges.TLSALPN01Response.verify_cert', autospec=True)
|
||||
def test_simple_verify(self, mock_verify_cert):
|
||||
mock_verify_cert.return_value = mock.sentinel.verification
|
||||
self.assertEqual(
|
||||
mock.sentinel.verification, self.response.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key(),
|
||||
cert=mock.sentinel.cert))
|
||||
mock_verify_cert.assert_called_once_with(
|
||||
self.response, self.domain, mock.sentinel.cert)
|
||||
|
||||
@mock.patch('acme.challenges.socket.gethostbyname')
|
||||
@mock.patch('acme.challenges.crypto_util.probe_sni')
|
||||
def test_probe_cert(self, mock_probe_sni, mock_gethostbyname):
|
||||
mock_gethostbyname.return_value = '127.0.0.1'
|
||||
self.response.probe_cert('foo.com')
|
||||
mock_gethostbyname.assert_called_once_with('foo.com')
|
||||
mock_probe_sni.assert_called_once_with(
|
||||
host='127.0.0.1', port=self.response.PORT, name='foo.com',
|
||||
alpn_protocols=['acme-tls/1'])
|
||||
|
||||
self.response.probe_cert('foo.com', host='8.8.8.8')
|
||||
mock_probe_sni.assert_called_with(
|
||||
host='8.8.8.8', port=mock.ANY, name='foo.com',
|
||||
alpn_protocols=['acme-tls/1'])
|
||||
|
||||
@mock.patch('acme.challenges.TLSALPN01Response.probe_cert')
|
||||
def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
|
||||
mock_probe_cert.side_effect = errors.Error
|
||||
self.assertFalse(self.response.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key()))
|
||||
|
||||
|
||||
class TLSALPN01Test(unittest.TestCase):
|
||||
|
||||
@@ -309,8 +372,13 @@ class TLSALPN01Test(unittest.TestCase):
|
||||
self.assertRaises(
|
||||
jose.DeserializationError, TLSALPN01.from_json, self.jmsg)
|
||||
|
||||
def test_validation(self):
|
||||
self.assertRaises(NotImplementedError, self.msg.validation, KEY)
|
||||
@mock.patch('acme.challenges.TLSALPN01Response.gen_cert')
|
||||
def test_validation(self, mock_gen_cert):
|
||||
mock_gen_cert.return_value = ('cert', 'key')
|
||||
self.assertEqual(('cert', 'key'), self.msg.validation(
|
||||
KEY, cert_key=mock.sentinel.cert_key, domain=mock.sentinel.domain))
|
||||
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key,
|
||||
domain=mock.sentinel.domain)
|
||||
|
||||
|
||||
class DNSTest(unittest.TestCase):
|
||||
@@ -413,5 +481,18 @@ class DNSResponseTest(unittest.TestCase):
|
||||
self.msg.check_validation(self.chall, KEY.public_key()))
|
||||
|
||||
|
||||
class JWSPayloadRFC8555Compliant(unittest.TestCase):
|
||||
"""Test for RFC8555 compliance of JWS generated from resources/challenges"""
|
||||
def test_challenge_payload(self):
|
||||
from acme.challenges import HTTP01Response
|
||||
|
||||
challenge_body = HTTP01Response()
|
||||
challenge_body.le_acme_version = 2
|
||||
|
||||
jobj = challenge_body.json_dumps(indent=2).encode()
|
||||
# RFC8555 states that challenge responses must have an empty payload.
|
||||
self.assertEqual(jobj, b'{}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -6,7 +6,10 @@ import json
|
||||
import unittest
|
||||
|
||||
import josepy as jose
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import OpenSSL
|
||||
import requests
|
||||
from six.moves import http_client # pylint: disable=import-error
|
||||
@@ -15,7 +18,7 @@ from acme import challenges
|
||||
from acme import errors
|
||||
from acme import jws as acme_jws
|
||||
from acme import messages
|
||||
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
|
||||
from acme.mixins import VersionedLEACMEMixin
|
||||
import messages_test
|
||||
import test_util
|
||||
|
||||
@@ -886,7 +889,7 @@ class ClientV2Test(ClientTestBase):
|
||||
self.client.net.get.assert_not_called()
|
||||
|
||||
|
||||
class MockJSONDeSerializable(jose.JSONDeSerializable):
|
||||
class MockJSONDeSerializable(VersionedLEACMEMixin, jose.JSONDeSerializable):
|
||||
# pylint: disable=missing-docstring
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
@@ -980,6 +983,35 @@ class ClientNetworkTest(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
self.response, self.net._check_response(self.response))
|
||||
|
||||
@mock.patch('acme.client.logger')
|
||||
def test_check_response_ok_ct_with_charset(self, mock_logger):
|
||||
self.response.json.return_value = {}
|
||||
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||
# pylint: disable=protected-access
|
||||
self.assertEqual(self.response, self.net._check_response(
|
||||
self.response, content_type='application/json'))
|
||||
try:
|
||||
mock_logger.debug.assert_called_with(
|
||||
'Ignoring wrong Content-Type (%r) for JSON decodable response',
|
||||
'application/json; charset=utf-8'
|
||||
)
|
||||
except AssertionError:
|
||||
return
|
||||
raise AssertionError('Expected Content-Type warning ' #pragma: no cover
|
||||
'to not have been logged')
|
||||
|
||||
@mock.patch('acme.client.logger')
|
||||
def test_check_response_ok_bad_ct(self, mock_logger):
|
||||
self.response.json.return_value = {}
|
||||
self.response.headers['Content-Type'] = 'text/plain'
|
||||
# pylint: disable=protected-access
|
||||
self.assertEqual(self.response, self.net._check_response(
|
||||
self.response, content_type='application/json'))
|
||||
mock_logger.debug.assert_called_with(
|
||||
'Ignoring wrong Content-Type (%r) for JSON decodable response',
|
||||
'text/plain'
|
||||
)
|
||||
|
||||
def test_check_response_conflict(self):
|
||||
self.response.ok = False
|
||||
self.response.status_code = 409
|
||||
|
||||
@@ -11,14 +11,12 @@ import six
|
||||
from six.moves import socketserver # type: ignore # pylint: disable=import-error
|
||||
|
||||
from acme import errors
|
||||
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
|
||||
import test_util
|
||||
|
||||
|
||||
class SSLSocketAndProbeSNITest(unittest.TestCase):
|
||||
"""Tests for acme.crypto_util.SSLSocket/probe_sni."""
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.cert = test_util.load_comparable_cert('rsa2048_cert.pem')
|
||||
key = test_util.load_pyopenssl_private_key('rsa2048_key.pem')
|
||||
@@ -32,7 +30,8 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
|
||||
# six.moves.* | pylint: disable=attribute-defined-outside-init,no-init
|
||||
|
||||
def server_bind(self): # pylint: disable=missing-docstring
|
||||
self.socket = SSLSocket(socket.socket(), certs=certs)
|
||||
self.socket = SSLSocket(socket.socket(),
|
||||
certs)
|
||||
socketserver.TCPServer.server_bind(self)
|
||||
|
||||
self.server = _TestServer(('', 0), socketserver.BaseRequestHandler)
|
||||
@@ -73,6 +72,18 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
|
||||
socket.setdefaulttimeout(original_timeout)
|
||||
|
||||
|
||||
class SSLSocketTest(unittest.TestCase):
|
||||
"""Tests for acme.crypto_util.SSLSocket."""
|
||||
|
||||
def test_ssl_socket_invalid_arguments(self):
|
||||
from acme.crypto_util import SSLSocket
|
||||
with self.assertRaises(ValueError):
|
||||
_ = SSLSocket(None, {'sni': ('key', 'cert')},
|
||||
cert_selection=lambda _: None)
|
||||
with self.assertRaises(ValueError):
|
||||
_ = SSLSocket(None)
|
||||
|
||||
|
||||
class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase):
|
||||
"""Test for acme.crypto_util._pyopenssl_cert_or_req_all_names."""
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Tests for acme.errors."""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
|
||||
class BadNonceTest(unittest.TestCase):
|
||||
@@ -35,7 +38,7 @@ class PollErrorTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from acme.errors import PollError
|
||||
self.timeout = PollError(
|
||||
exhausted=set([mock.sentinel.AR]),
|
||||
exhausted={mock.sentinel.AR},
|
||||
updated={})
|
||||
self.invalid = PollError(exhausted=set(), updated={
|
||||
mock.sentinel.AR: mock.sentinel.AR2})
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
|
||||
class MagicTypingTest(unittest.TestCase):
|
||||
@@ -18,7 +21,7 @@ class MagicTypingTest(unittest.TestCase):
|
||||
sys.modules['typing'] = typing_class_mock
|
||||
if 'acme.magic_typing' in sys.modules:
|
||||
del sys.modules['acme.magic_typing'] # pragma: no cover
|
||||
from acme.magic_typing import Text # pylint: disable=no-name-in-module
|
||||
from acme.magic_typing import Text
|
||||
self.assertEqual(Text, text_mock)
|
||||
del sys.modules['acme.magic_typing']
|
||||
sys.modules['typing'] = temp_typing
|
||||
@@ -31,7 +34,7 @@ class MagicTypingTest(unittest.TestCase):
|
||||
sys.modules['typing'] = None
|
||||
if 'acme.magic_typing' in sys.modules:
|
||||
del sys.modules['acme.magic_typing'] # pragma: no cover
|
||||
from acme.magic_typing import Text # pylint: disable=no-name-in-module
|
||||
from acme.magic_typing import Text
|
||||
self.assertTrue(Text is None)
|
||||
del sys.modules['acme.magic_typing']
|
||||
sys.modules['typing'] = temp_typing
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
import unittest
|
||||
|
||||
import josepy as jose
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from acme import challenges
|
||||
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
|
||||
import test_util
|
||||
|
||||
CERT = test_util.load_comparable_cert('cert.der')
|
||||
@@ -453,6 +455,7 @@ class OrderResourceTest(unittest.TestCase):
|
||||
'authorizations': None,
|
||||
})
|
||||
|
||||
|
||||
class NewOrderTest(unittest.TestCase):
|
||||
"""Tests for acme.messages.NewOrder."""
|
||||
|
||||
@@ -467,5 +470,18 @@ class NewOrderTest(unittest.TestCase):
|
||||
})
|
||||
|
||||
|
||||
class JWSPayloadRFC8555Compliant(unittest.TestCase):
|
||||
"""Test for RFC8555 compliance of JWS generated from resources/challenges"""
|
||||
def test_message_payload(self):
|
||||
from acme.messages import NewAuthorization
|
||||
|
||||
new_order = NewAuthorization()
|
||||
new_order.le_acme_version = 2
|
||||
|
||||
jobj = new_order.json_dumps(indent=2).encode()
|
||||
# RFC8555 states that JWS bodies must not have a resource field.
|
||||
self.assertEqual(jobj, b'{}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -4,13 +4,18 @@ import threading
|
||||
import unittest
|
||||
|
||||
import josepy as jose
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import requests
|
||||
from six.moves import http_client # pylint: disable=import-error
|
||||
from six.moves import socketserver # type: ignore # pylint: disable=import-error
|
||||
|
||||
from acme import challenges
|
||||
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
|
||||
from acme import crypto_util
|
||||
from acme import errors
|
||||
|
||||
import test_util
|
||||
|
||||
|
||||
@@ -83,6 +88,81 @@ class HTTP01ServerTest(unittest.TestCase):
|
||||
def test_http01_not_found(self):
|
||||
self.assertFalse(self._test_http01(add=False))
|
||||
|
||||
def test_timely_shutdown(self):
|
||||
from acme.standalone import HTTP01Server
|
||||
server = HTTP01Server(('', 0), resources=set(), timeout=0.05)
|
||||
server_thread = threading.Thread(target=server.serve_forever)
|
||||
server_thread.start()
|
||||
|
||||
client = socket.socket()
|
||||
client.connect(('localhost', server.socket.getsockname()[1]))
|
||||
|
||||
stop_thread = threading.Thread(target=server.shutdown)
|
||||
stop_thread.start()
|
||||
server_thread.join(5.)
|
||||
|
||||
is_hung = server_thread.is_alive()
|
||||
try:
|
||||
client.shutdown(socket.SHUT_RDWR)
|
||||
except: # pragma: no cover, pylint: disable=bare-except
|
||||
# may raise error because socket could already be closed
|
||||
pass
|
||||
|
||||
self.assertFalse(is_hung, msg='Server shutdown should not be hung')
|
||||
|
||||
|
||||
@unittest.skipIf(not challenges.TLSALPN01.is_supported(), "pyOpenSSL too old")
|
||||
class TLSALPN01ServerTest(unittest.TestCase):
|
||||
"""Test for acme.standalone.TLSALPN01Server."""
|
||||
|
||||
def setUp(self):
|
||||
self.certs = {b'localhost': (
|
||||
test_util.load_pyopenssl_private_key('rsa2048_key.pem'),
|
||||
test_util.load_cert('rsa2048_cert.pem'),
|
||||
)}
|
||||
# Use different certificate for challenge.
|
||||
self.challenge_certs = {b'localhost': (
|
||||
test_util.load_pyopenssl_private_key('rsa1024_key.pem'),
|
||||
test_util.load_cert('rsa1024_cert.pem'),
|
||||
)}
|
||||
from acme.standalone import TLSALPN01Server
|
||||
self.server = TLSALPN01Server(("localhost", 0), certs=self.certs,
|
||||
challenge_certs=self.challenge_certs)
|
||||
# pylint: disable=no-member
|
||||
self.thread = threading.Thread(target=self.server.serve_forever)
|
||||
self.thread.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.server.shutdown() # pylint: disable=no-member
|
||||
self.thread.join()
|
||||
|
||||
# TODO: This is not implemented yet, see comments in standalone.py
|
||||
# def test_certs(self):
|
||||
# host, port = self.server.socket.getsockname()[:2]
|
||||
# cert = crypto_util.probe_sni(
|
||||
# b'localhost', host=host, port=port, timeout=1)
|
||||
# # Expect normal cert when connecting without ALPN.
|
||||
# self.assertEqual(jose.ComparableX509(cert),
|
||||
# jose.ComparableX509(self.certs[b'localhost'][1]))
|
||||
|
||||
def test_challenge_certs(self):
|
||||
host, port = self.server.socket.getsockname()[:2]
|
||||
cert = crypto_util.probe_sni(
|
||||
b'localhost', host=host, port=port, timeout=1,
|
||||
alpn_protocols=[b"acme-tls/1"])
|
||||
# Expect challenge cert when connecting with ALPN.
|
||||
self.assertEqual(
|
||||
jose.ComparableX509(cert),
|
||||
jose.ComparableX509(self.challenge_certs[b'localhost'][1])
|
||||
)
|
||||
|
||||
def test_bad_alpn(self):
|
||||
host, port = self.server.socket.getsockname()[:2]
|
||||
with self.assertRaises(errors.Error):
|
||||
crypto_util.probe_sni(
|
||||
b'localhost', host=host, port=port, timeout=1,
|
||||
alpn_protocols=[b"bad-alpn"])
|
||||
|
||||
|
||||
class BaseDualNetworkedServersTest(unittest.TestCase):
|
||||
"""Test for acme.standalone.BaseDualNetworkedServers."""
|
||||
@@ -138,7 +218,6 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
|
||||
class HTTP01DualNetworkedServersTest(unittest.TestCase):
|
||||
"""Tests for acme.standalone.HTTP01DualNetworkedServers."""
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.account_key = jose.JWK.load(
|
||||
test_util.load_vector('rsa1024_key.pem'))
|
||||
|
||||
6
acme/tests/testdata/README
vendored
6
acme/tests/testdata/README
vendored
@@ -10,6 +10,8 @@ and for the CSR:
|
||||
|
||||
openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der
|
||||
|
||||
and for the certificate:
|
||||
and for the certificates:
|
||||
|
||||
openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der
|
||||
openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der
|
||||
openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -x509 > rsa2048_cert.pem
|
||||
openssl req -key rsa1024_key.pem -new -subj '/CN=example.com' -x509 > rsa1024_cert.pem
|
||||
|
||||
13
acme/tests/testdata/rsa1024_cert.pem
vendored
Normal file
13
acme/tests/testdata/rsa1024_cert.pem
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB/TCCAWagAwIBAgIJAOyRIBs3QT8QMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
|
||||
BAMMC2V4YW1wbGUuY29tMB4XDTE4MDQyMzEwMzE0NFoXDTE4MDUyMzEwMzE0NFow
|
||||
FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
|
||||
AoGBAJqJ87R8aVwByONxgQA9hwgvQd/QqI1r1UInXhEF2VnEtZGtUWLi100IpIqr
|
||||
Mq4qusDwNZ3g8cUPtSkvJGs89djoajMDIJP7lQUEKUYnYrI0q755Tr/DgLWSk7iW
|
||||
l5ezym0VzWUD0/xXUz8yRbNMTjTac80rS5SZk2ja2wWkYlRJAgMBAAGjUzBRMB0G
|
||||
A1UdDgQWBBSsaX0IVZ4XXwdeffVAbG7gnxSYjTAfBgNVHSMEGDAWgBSsaX0IVZ4X
|
||||
XwdeffVAbG7gnxSYjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB
|
||||
ADe7SVmvGH2nkwVfONk8TauRUDkePN1CJZKFb2zW1uO9ANJ2v5Arm/OQp0BG/xnI
|
||||
Djw/aLTNVESF89oe15dkrUErtcaF413MC1Ld5lTCaJLHLGqDKY69e02YwRuxW7jY
|
||||
qarpt7k7aR5FbcfO5r4V/FK/Gvp4Dmoky8uap7SJIW6x
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,7 +1,7 @@
|
||||
include LICENSE.txt
|
||||
include README.rst
|
||||
recursive-include tests *
|
||||
include certbot_apache/_internal/options-ssl-apache.conf
|
||||
recursive-include certbot_apache/_internal/augeas_lens *.aug
|
||||
recursive-include certbot_apache/_internal/tls_configs *.conf
|
||||
global-exclude __pycache__
|
||||
global-exclude *.py[cod]
|
||||
|
||||
@@ -5,6 +5,8 @@ import logging
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from certbot import errors
|
||||
from certbot import util
|
||||
|
||||
@@ -128,7 +130,7 @@ def included_in_paths(filepath, paths):
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
return any([fnmatch.fnmatch(filepath, path) for path in paths])
|
||||
return any(fnmatch.fnmatch(filepath, path) for path in paths)
|
||||
|
||||
|
||||
def parse_defines(apachectl):
|
||||
@@ -142,7 +144,7 @@ def parse_defines(apachectl):
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
variables = dict()
|
||||
variables = {}
|
||||
define_cmd = [apachectl, "-t", "-D",
|
||||
"DUMP_RUN_CFG"]
|
||||
matches = parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)")
|
||||
@@ -241,3 +243,14 @@ def _get_runtime_cfg(command):
|
||||
"loaded because Apache is misconfigured.")
|
||||
|
||||
return stdout
|
||||
|
||||
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("_internal", "tls_configs", "{0}-options-ssl-apache.conf".format(prefix)))
|
||||
|
||||
@@ -73,7 +73,7 @@ class ApacheDirectiveNode(ApacheParserNode):
|
||||
self.metadata == other.metadata)
|
||||
return False
|
||||
|
||||
def set_parameters(self, _parameters):
|
||||
def set_parameters(self, _parameters): # pragma: no cover
|
||||
"""Sets the parameters for DirectiveNode"""
|
||||
return
|
||||
|
||||
@@ -97,7 +97,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
|
||||
self.metadata == other.metadata)
|
||||
return False
|
||||
|
||||
def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument
|
||||
# pylint: disable=unused-argument
|
||||
def add_child_block(self, name, parameters=None, position=None): # pragma: no cover
|
||||
"""Adds a new BlockNode to the sequence of children"""
|
||||
new_block = ApacheBlockNode(name=assertions.PASS,
|
||||
parameters=assertions.PASS,
|
||||
@@ -107,7 +108,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
|
||||
self.children += (new_block,)
|
||||
return new_block
|
||||
|
||||
def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument
|
||||
# pylint: disable=unused-argument
|
||||
def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover
|
||||
"""Adds a new DirectiveNode to the sequence of children"""
|
||||
new_dir = ApacheDirectiveNode(name=assertions.PASS,
|
||||
parameters=assertions.PASS,
|
||||
@@ -144,7 +146,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
|
||||
filepath=assertions.PASS,
|
||||
metadata=self.metadata)]
|
||||
|
||||
def find_comments(self, comment, exact=False): # pylint: disable=unused-argument
|
||||
# pylint: disable=unused-argument
|
||||
def find_comments(self, comment, exact=False): # pragma: no cover
|
||||
"""Recursive search of DirectiveNodes from the sequence of children"""
|
||||
return [ApacheCommentNode(comment=assertions.PASS,
|
||||
ancestor=self,
|
||||
|
||||
@@ -132,9 +132,9 @@ def assertEqualPathsList(first, second): # pragma: no cover
|
||||
Checks that the two lists of file paths match. This assertion allows for wildcard
|
||||
paths.
|
||||
"""
|
||||
if any([isPass(path) for path in first]):
|
||||
if any(isPass(path) for path in first):
|
||||
return
|
||||
if any([isPass(path) for path in second]):
|
||||
if any(isPass(path) for path in second):
|
||||
return
|
||||
for fpath in first:
|
||||
assert any([fnmatch.fnmatch(fpath, spath) for spath in second])
|
||||
|
||||
@@ -64,7 +64,7 @@ Translates over to:
|
||||
"/files/etc/apache2/apache2.conf/bLoCk[1]",
|
||||
]
|
||||
"""
|
||||
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
|
||||
from acme.magic_typing import Set
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -339,7 +339,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
|
||||
def find_blocks(self, name, exclude=True):
|
||||
"""Recursive search of BlockNodes from the sequence of children"""
|
||||
|
||||
nodes = list()
|
||||
nodes = []
|
||||
paths = self._aug_find_blocks(name)
|
||||
if exclude:
|
||||
paths = self.parser.exclude_dirs(paths)
|
||||
@@ -351,7 +351,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
|
||||
def find_directives(self, name, exclude=True):
|
||||
"""Recursive search of DirectiveNodes from the sequence of children"""
|
||||
|
||||
nodes = list()
|
||||
nodes = []
|
||||
ownpath = self.metadata.get("augeaspath")
|
||||
|
||||
directives = self.parser.find_dir(name, start=ownpath, exclude=exclude)
|
||||
@@ -374,7 +374,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
|
||||
:param str comment: Comment content to search for.
|
||||
"""
|
||||
|
||||
nodes = list()
|
||||
nodes = []
|
||||
ownpath = self.metadata.get("augeaspath")
|
||||
|
||||
comments = self.parser.find_comments(comment, start=ownpath)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Apache Configurator."""
|
||||
# pylint: disable=too-many-lines
|
||||
from collections import defaultdict
|
||||
from distutils.version import LooseVersion
|
||||
import copy
|
||||
import fnmatch
|
||||
import logging
|
||||
@@ -8,10 +9,14 @@ import re
|
||||
import socket
|
||||
import time
|
||||
|
||||
import pkg_resources
|
||||
import six
|
||||
import zope.component
|
||||
import zope.interface
|
||||
try:
|
||||
import apacheconfig
|
||||
HAS_APACHECONFIG = True
|
||||
except ImportError: # pragma: no cover
|
||||
HAS_APACHECONFIG = False
|
||||
|
||||
from acme import challenges
|
||||
from acme.magic_typing import DefaultDict
|
||||
@@ -110,14 +115,29 @@ class ApacheConfigurator(common.Installer):
|
||||
handle_modules=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
def option(self, key):
|
||||
"""Get a value from options"""
|
||||
return self.options.get(key)
|
||||
|
||||
def pick_apache_config(self, warn_on_no_mod_ssl=True):
|
||||
"""
|
||||
Pick the appropriate TLS Apache configuration file for current version of Apache and OS.
|
||||
|
||||
:param bool warn_on_no_mod_ssl: True if we should warn if mod_ssl is not found.
|
||||
|
||||
:return: the path to the TLS Apache configuration file to use
|
||||
:rtype: str
|
||||
"""
|
||||
# Disabling TLS session tickets is supported by Apache 2.4.11+ and OpenSSL 1.0.2l+.
|
||||
# So for old versions of Apache we pick a configuration without this option.
|
||||
openssl_version = self.openssl_version(warn_on_no_mod_ssl)
|
||||
if self.version < (2, 4, 11) or not openssl_version or\
|
||||
LooseVersion(openssl_version) < LooseVersion('1.0.2l'):
|
||||
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
|
||||
@@ -184,15 +204,16 @@ class ApacheConfigurator(common.Installer):
|
||||
"""
|
||||
version = kwargs.pop("version", None)
|
||||
use_parsernode = kwargs.pop("use_parsernode", False)
|
||||
openssl_version = kwargs.pop("openssl_version", None)
|
||||
super(ApacheConfigurator, self).__init__(*args, **kwargs)
|
||||
|
||||
# Add name_server association dict
|
||||
self.assoc = dict() # type: Dict[str, obj.VirtualHost]
|
||||
self.assoc = {} # type: Dict[str, obj.VirtualHost]
|
||||
# Outstanding challenges
|
||||
self._chall_out = set() # type: Set[KeyAuthorizationAnnotatedChallenge]
|
||||
# List of vhosts configured per wildcard domain on this run.
|
||||
# used by deploy_cert() and enhance()
|
||||
self._wildcard_vhosts = dict() # type: Dict[str, List[obj.VirtualHost]]
|
||||
self._wildcard_vhosts = {} # type: Dict[str, List[obj.VirtualHost]]
|
||||
# Maps enhancements to vhosts we've enabled the enhancement for
|
||||
self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]]
|
||||
# Temporary state for AutoHSTS enhancement
|
||||
@@ -209,6 +230,7 @@ class ApacheConfigurator(common.Installer):
|
||||
self.parser = None
|
||||
self.parser_root = None
|
||||
self.version = version
|
||||
self._openssl_version = openssl_version
|
||||
self.vhosts = None
|
||||
self.options = copy.deepcopy(self.OS_DEFAULTS)
|
||||
self._enhance_func = {"redirect": self._enable_redirect,
|
||||
@@ -225,6 +247,52 @@ class ApacheConfigurator(common.Installer):
|
||||
"""Full absolute path to digest of updated SSL configuration file."""
|
||||
return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST)
|
||||
|
||||
def _open_module_file(self, ssl_module_location):
|
||||
"""Extract the open lines of openssl_version for testing purposes"""
|
||||
try:
|
||||
with open(ssl_module_location, mode="rb") as f:
|
||||
contents = f.read()
|
||||
except IOError as error:
|
||||
logger.debug(str(error), exc_info=True)
|
||||
return None
|
||||
return contents
|
||||
|
||||
def openssl_version(self, warn_on_no_mod_ssl=True):
|
||||
"""Lazily retrieve openssl version
|
||||
|
||||
:param bool warn_on_no_mod_ssl: `True` if we should warn if mod_ssl is not found. Set to
|
||||
`False` when we know we'll try to enable mod_ssl later. This is currently debian/ubuntu,
|
||||
when called from `prepare`.
|
||||
|
||||
:return: the OpenSSL version as a string, or None.
|
||||
:rtype: str or None
|
||||
"""
|
||||
if self._openssl_version:
|
||||
return self._openssl_version
|
||||
# Step 1. Check for LoadModule directive
|
||||
try:
|
||||
ssl_module_location = self.parser.modules['ssl_module']
|
||||
except KeyError:
|
||||
if warn_on_no_mod_ssl:
|
||||
logger.warning("Could not find ssl_module; not disabling session tickets.")
|
||||
return None
|
||||
if not ssl_module_location:
|
||||
logger.warning("Could not find ssl_module; not disabling session tickets.")
|
||||
return None
|
||||
ssl_module_location = self.parser.standard_path_from_server_root(ssl_module_location)
|
||||
# Step 2. Grep in the .so for openssl version
|
||||
contents = self._open_module_file(ssl_module_location)
|
||||
if not contents:
|
||||
logger.warning("Unable to read ssl_module file; not disabling session tickets.")
|
||||
return None
|
||||
# looks like: OpenSSL 1.0.2s 28 May 2019
|
||||
matches = re.findall(br"OpenSSL ([0-9]\.[^ ]+) ", contents)
|
||||
if not matches:
|
||||
logger.warning("Could not find OpenSSL version; not disabling session tickets.")
|
||||
return None
|
||||
self._openssl_version = matches[0].decode('UTF-8')
|
||||
return self._openssl_version
|
||||
|
||||
def prepare(self):
|
||||
"""Prepare the authenticator/installer.
|
||||
|
||||
@@ -271,8 +339,12 @@ class ApacheConfigurator(common.Installer):
|
||||
# Get all of the available vhosts
|
||||
self.vhosts = self.get_virtual_hosts()
|
||||
|
||||
# We may try to enable mod_ssl later. If so, we shouldn't warn if we can't find it now.
|
||||
# This is currently only true for debian/ubuntu.
|
||||
warn_on_no_mod_ssl = not self.option("handle_modules")
|
||||
self.install_ssl_options_conf(self.mod_ssl_conf,
|
||||
self.updated_mod_ssl_conf_digest)
|
||||
self.updated_mod_ssl_conf_digest,
|
||||
warn_on_no_mod_ssl)
|
||||
|
||||
# Prevent two Apache plugins from modifying a config at once
|
||||
try:
|
||||
@@ -363,11 +435,17 @@ class ApacheConfigurator(common.Installer):
|
||||
def get_parsernode_root(self, metadata):
|
||||
"""Initializes the ParserNode parser root instance."""
|
||||
|
||||
apache_vars = dict()
|
||||
apache_vars["defines"] = apache_util.parse_defines(self.option("ctl"))
|
||||
apache_vars["includes"] = apache_util.parse_includes(self.option("ctl"))
|
||||
apache_vars["modules"] = apache_util.parse_modules(self.option("ctl"))
|
||||
metadata["apache_vars"] = apache_vars
|
||||
if HAS_APACHECONFIG:
|
||||
apache_vars = {}
|
||||
apache_vars["defines"] = apache_util.parse_defines(self.option("ctl"))
|
||||
apache_vars["includes"] = apache_util.parse_includes(self.option("ctl"))
|
||||
apache_vars["modules"] = apache_util.parse_modules(self.option("ctl"))
|
||||
metadata["apache_vars"] = apache_vars
|
||||
|
||||
with open(self.parser.loc["root"]) as f:
|
||||
with apacheconfig.make_loader(writable=True,
|
||||
**apacheconfig.flavors.NATIVE_APACHE) as loader:
|
||||
metadata["ac_ast"] = loader.loads(f.read())
|
||||
|
||||
return dualparser.DualBlockNode(
|
||||
name=assertions.PASS,
|
||||
@@ -470,7 +548,7 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
# Go through the vhosts, making sure that we cover all the names
|
||||
# present, but preferring the SSL vhosts
|
||||
filtered_vhosts = dict()
|
||||
filtered_vhosts = {}
|
||||
for vhost in vhosts:
|
||||
for name in vhost.get_names():
|
||||
if vhost.ssl:
|
||||
@@ -496,7 +574,7 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
# Make sure we create SSL vhosts for the ones that are HTTP only
|
||||
# if requested.
|
||||
return_vhosts = list()
|
||||
return_vhosts = []
|
||||
for vhost in dialog_output:
|
||||
if not vhost.ssl:
|
||||
return_vhosts.append(self.make_vhost_ssl(vhost))
|
||||
@@ -907,7 +985,7 @@ class ApacheConfigurator(common.Installer):
|
||||
"""
|
||||
|
||||
v1_vhosts = self.get_virtual_hosts_v1()
|
||||
if self.USE_PARSERNODE:
|
||||
if self.USE_PARSERNODE and HAS_APACHECONFIG:
|
||||
v2_vhosts = self.get_virtual_hosts_v2()
|
||||
|
||||
for v1_vh in v1_vhosts:
|
||||
@@ -1241,6 +1319,14 @@ class ApacheConfigurator(common.Installer):
|
||||
self.enable_mod("socache_shmcb", temp=temp)
|
||||
if "ssl_module" not in self.parser.modules:
|
||||
self.enable_mod("ssl", temp=temp)
|
||||
# Make sure we're not throwing away any unwritten changes to the config
|
||||
self.parser.ensure_augeas_state()
|
||||
self.parser.aug.load()
|
||||
self.parser.reset_modules() # Reset to load the new ssl_module path
|
||||
# Call again because now we can gate on openssl version
|
||||
self.install_ssl_options_conf(self.mod_ssl_conf,
|
||||
self.updated_mod_ssl_conf_digest,
|
||||
warn_on_no_mod_ssl=True)
|
||||
|
||||
def make_vhost_ssl(self, nonssl_vhost):
|
||||
"""Makes an ssl_vhost version of a nonssl_vhost.
|
||||
@@ -1493,7 +1579,7 @@ class ApacheConfigurator(common.Installer):
|
||||
result.append(comment)
|
||||
sift = True
|
||||
|
||||
result.append('\n'.join(['# ' + l for l in chunk]))
|
||||
result.append('\n'.join('# ' + l for l in chunk))
|
||||
else:
|
||||
result.append('\n'.join(chunk))
|
||||
return result, sift
|
||||
@@ -1633,7 +1719,7 @@ class ApacheConfigurator(common.Installer):
|
||||
for addr in vhost.addrs:
|
||||
# In Apache 2.2, when a NameVirtualHost directive is not
|
||||
# set, "*" and "_default_" will conflict when sharing a port
|
||||
addrs = set((addr,))
|
||||
addrs = {addr,}
|
||||
if addr.get_addr() in ("*", "_default_"):
|
||||
addrs.update(obj.Addr((a, addr.get_port(),))
|
||||
for a in ("*", "_default_"))
|
||||
@@ -1824,7 +1910,7 @@ class ApacheConfigurator(common.Installer):
|
||||
try:
|
||||
self._autohsts = self.storage.fetch("autohsts")
|
||||
except KeyError:
|
||||
self._autohsts = dict()
|
||||
self._autohsts = {}
|
||||
|
||||
def _autohsts_save_state(self):
|
||||
"""
|
||||
@@ -2385,7 +2471,7 @@ class ApacheConfigurator(common.Installer):
|
||||
if len(matches) != 1:
|
||||
raise errors.PluginError("Unable to find Apache version")
|
||||
|
||||
return tuple([int(i) for i in matches[0].split(".")])
|
||||
return tuple(int(i) for i in matches[0].split("."))
|
||||
|
||||
def more_info(self):
|
||||
"""Human-readable string to help understand the module"""
|
||||
@@ -2454,14 +2540,19 @@ class ApacheConfigurator(common.Installer):
|
||||
self.restart()
|
||||
self.parser.reset_modules()
|
||||
|
||||
def install_ssl_options_conf(self, options_ssl, options_ssl_digest):
|
||||
"""Copy Certbot's SSL options file into the system's config dir if required."""
|
||||
def install_ssl_options_conf(self, options_ssl, options_ssl_digest, warn_on_no_mod_ssl=True):
|
||||
"""Copy Certbot's SSL options file into the system's config dir if required.
|
||||
|
||||
:param bool warn_on_no_mod_ssl: True if we should warn if mod_ssl is not found.
|
||||
"""
|
||||
|
||||
# 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.
|
||||
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
|
||||
self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
|
||||
apache_config_path = self.pick_apache_config(warn_on_no_mod_ssl)
|
||||
|
||||
return common.install_version_controlled_file(
|
||||
options_ssl, options_ssl_digest, apache_config_path, constants.ALL_SSL_OPTIONS_HASHES)
|
||||
|
||||
def enable_autohsts(self, _unused_lineage, domains):
|
||||
"""
|
||||
|
||||
@@ -26,6 +26,7 @@ ALL_SSL_OPTIONS_HASHES = [
|
||||
'06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7',
|
||||
'5cc003edd93fb9cd03d40c7686495f8f058f485f75b5e764b789245a386e6daf',
|
||||
'007cd497a56a3bb8b6a2c1aeb4997789e7e38992f74e44cc5d13a625a738ac73',
|
||||
'34783b9e2210f5c4a23bced2dfd7ec289834716673354ed7c7abf69fe30192a3',
|
||||
]
|
||||
"""SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC"""
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ def select_vhost_multiple(vhosts):
|
||||
:rtype: :class:`list`of type `~obj.Vhost`
|
||||
"""
|
||||
if not vhosts:
|
||||
return list()
|
||||
return []
|
||||
tags_list = [vhost.display_repr()+"\n" for vhost in vhosts]
|
||||
# Remove the extra newline from the last entry
|
||||
if tags_list:
|
||||
@@ -37,7 +37,7 @@ def select_vhost_multiple(vhosts):
|
||||
def _reversemap_vhosts(names, vhosts):
|
||||
"""Helper function for select_vhost_multiple for mapping string
|
||||
representations back to actual vhost objects"""
|
||||
return_vhosts = list()
|
||||
return_vhosts = []
|
||||
|
||||
for selection in names:
|
||||
for vhost in vhosts:
|
||||
|
||||
@@ -49,7 +49,7 @@ class DualNodeBase(object):
|
||||
|
||||
pass_primary = assertions.isPassNodeList(primary_res)
|
||||
pass_secondary = assertions.isPassNodeList(secondary_res)
|
||||
new_nodes = list()
|
||||
new_nodes = []
|
||||
|
||||
if pass_primary and pass_secondary:
|
||||
# Both unimplemented
|
||||
@@ -221,7 +221,7 @@ class DualBlockNode(DualNodeBase):
|
||||
implementations to a list of tuples.
|
||||
"""
|
||||
|
||||
matched = list()
|
||||
matched = []
|
||||
for p in primary_list:
|
||||
match = None
|
||||
for s in secondary_list:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
""" Entry point for Apache Plugin """
|
||||
# Pylint does not like disutils.version when running inside a venv.
|
||||
# See: https://github.com/PyCQA/pylint/issues/73
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from certbot import util
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""A class that performs HTTP-01 challenges for Apache"""
|
||||
import logging
|
||||
import errno
|
||||
|
||||
from acme.magic_typing import List
|
||||
from acme.magic_typing import Set
|
||||
@@ -168,7 +169,15 @@ class ApacheHttp01(common.ChallengePerformer):
|
||||
|
||||
def _set_up_challenges(self):
|
||||
if not os.path.isdir(self.challenge_dir):
|
||||
filesystem.makedirs(self.challenge_dir, 0o755)
|
||||
old_umask = os.umask(0o022)
|
||||
try:
|
||||
filesystem.makedirs(self.challenge_dir, 0o755)
|
||||
except OSError as exception:
|
||||
if exception.errno not in (errno.EEXIST, errno.EISDIR):
|
||||
raise errors.PluginError(
|
||||
"Couldn't create root for http-01 challenge")
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
|
||||
responses = []
|
||||
for achall in self.achalls:
|
||||
|
||||
@@ -102,7 +102,6 @@ For this reason the internal representation of data should not ignore the case.
|
||||
import abc
|
||||
import six
|
||||
|
||||
from acme.magic_typing import Any, Dict, Optional, Tuple # pylint: disable=unused-import, no-name-in-module
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
""" Distribution specific override class for Arch Linux """
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot.compat import os
|
||||
from certbot_apache._internal import configurator
|
||||
|
||||
|
||||
@@ -26,6 +24,4 @@ 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", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
""" Distribution specific override class for CentOS family (RHEL, Fedora) """
|
||||
import logging
|
||||
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from acme.magic_typing import List
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
from certbot.compat import os
|
||||
from certbot.errors import MisconfigurationError
|
||||
from certbot_apache._internal import apache_util
|
||||
from certbot_apache._internal import configurator
|
||||
@@ -37,8 +35,6 @@ 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", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
def config_test(self):
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
""" Distribution specific override class for macOS """
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot.compat import os
|
||||
from certbot_apache._internal import configurator
|
||||
|
||||
|
||||
@@ -26,6 +24,4 @@ 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", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
""" Distribution specific override class for Debian family (Ubuntu/Debian) """
|
||||
import logging
|
||||
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import errors
|
||||
@@ -34,8 +33,6 @@ class DebianConfigurator(configurator.ApacheConfigurator):
|
||||
handle_modules=True,
|
||||
handle_sites=True,
|
||||
challenge_location="/etc/apache2",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
def enable_site(self, vhost):
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
""" Distribution specific override class for Fedora 29+ """
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
from certbot.compat import os
|
||||
from certbot_apache._internal import apache_util
|
||||
from certbot_apache._internal import configurator
|
||||
from certbot_apache._internal import parser
|
||||
@@ -31,9 +29,6 @@ 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", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
def config_test(self):
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
""" Distribution specific override class for Gentoo Linux """
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot.compat import os
|
||||
from certbot_apache._internal import apache_util
|
||||
from certbot_apache._internal import configurator
|
||||
from certbot_apache._internal import parser
|
||||
@@ -29,8 +27,6 @@ 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", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
def _prepare_options(self):
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
""" Distribution specific override class for OpenSUSE """
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot.compat import os
|
||||
from certbot_apache._internal import configurator
|
||||
|
||||
|
||||
@@ -26,6 +24,4 @@ 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", os.path.join("_internal", "options-ssl-apache.conf"))
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ import six
|
||||
|
||||
from acme.magic_typing import Dict
|
||||
from acme.magic_typing import List
|
||||
from acme.magic_typing import Set
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot_apache._internal import apache_util
|
||||
@@ -31,7 +30,7 @@ class ApacheParser(object):
|
||||
|
||||
"""
|
||||
arg_var_interpreter = re.compile(r"\$\{[^ \}]*}")
|
||||
fnmatch_chars = set(["*", "?", "\\", "[", "]"])
|
||||
fnmatch_chars = {"*", "?", "\\", "[", "]"}
|
||||
|
||||
def __init__(self, root, vhostroot=None, version=(2, 4),
|
||||
configurator=None):
|
||||
@@ -52,7 +51,7 @@ class ApacheParser(object):
|
||||
"version 1.2.0 or higher, please make sure you have you have "
|
||||
"those installed.")
|
||||
|
||||
self.modules = set() # type: Set[str]
|
||||
self.modules = {} # type: Dict[str, str]
|
||||
self.parser_paths = {} # type: Dict[str, List[str]]
|
||||
self.variables = {} # type: Dict[str, str]
|
||||
|
||||
@@ -249,14 +248,14 @@ class ApacheParser(object):
|
||||
def add_mod(self, mod_name):
|
||||
"""Shortcut for updating parser modules."""
|
||||
if mod_name + "_module" not in self.modules:
|
||||
self.modules.add(mod_name + "_module")
|
||||
self.modules[mod_name + "_module"] = None
|
||||
if "mod_" + mod_name + ".c" not in self.modules:
|
||||
self.modules.add("mod_" + mod_name + ".c")
|
||||
self.modules["mod_" + mod_name + ".c"] = None
|
||||
|
||||
def reset_modules(self):
|
||||
"""Reset the loaded modules list. This is called from cleanup to clear
|
||||
temporarily loaded modules."""
|
||||
self.modules = set()
|
||||
self.modules = {}
|
||||
self.update_modules()
|
||||
self.parse_modules()
|
||||
|
||||
@@ -267,7 +266,7 @@ class ApacheParser(object):
|
||||
the iteration issue. Else... parse and enable mods at same time.
|
||||
|
||||
"""
|
||||
mods = set() # type: Set[str]
|
||||
mods = {} # type: Dict[str, str]
|
||||
matches = self.find_dir("LoadModule")
|
||||
iterator = iter(matches)
|
||||
# Make sure prev_size != cur_size for do: while: iteration
|
||||
@@ -281,8 +280,8 @@ class ApacheParser(object):
|
||||
mod_name = self.get_arg(match_name)
|
||||
mod_filename = self.get_arg(match_filename)
|
||||
if mod_name and mod_filename:
|
||||
mods.add(mod_name)
|
||||
mods.add(os.path.basename(mod_filename)[:-2] + "c")
|
||||
mods[mod_name] = mod_filename
|
||||
mods[os.path.basename(mod_filename)[:-2] + "c"] = mod_filename
|
||||
else:
|
||||
logger.debug("Could not read LoadModule directive from Augeas path: %s",
|
||||
match_name[6:])
|
||||
@@ -621,7 +620,7 @@ class ApacheParser(object):
|
||||
|
||||
def exclude_dirs(self, matches):
|
||||
"""Exclude directives that are not loaded into the configuration."""
|
||||
filters = [("ifmodule", self.modules), ("ifdefine", self.variables)]
|
||||
filters = [("ifmodule", self.modules.keys()), ("ifdefine", self.variables)]
|
||||
|
||||
valid_matches = []
|
||||
|
||||
@@ -662,6 +661,25 @@ class ApacheParser(object):
|
||||
|
||||
return True
|
||||
|
||||
def standard_path_from_server_root(self, arg):
|
||||
"""Ensure paths are consistent and absolute
|
||||
|
||||
:param str arg: Argument of directive
|
||||
|
||||
:returns: Standardized argument path
|
||||
:rtype: str
|
||||
"""
|
||||
# Remove beginning and ending quotes
|
||||
arg = arg.strip("'\"")
|
||||
|
||||
# Standardize the include argument based on server root
|
||||
if not arg.startswith("/"):
|
||||
# Normpath will condense ../
|
||||
arg = os.path.normpath(os.path.join(self.root, arg))
|
||||
else:
|
||||
arg = os.path.normpath(arg)
|
||||
return arg
|
||||
|
||||
def _get_include_path(self, arg):
|
||||
"""Converts an Apache Include directive into Augeas path.
|
||||
|
||||
@@ -682,16 +700,7 @@ class ApacheParser(object):
|
||||
# if matchObj.group() != arg:
|
||||
# logger.error("Error: Invalid regexp characters in %s", arg)
|
||||
# return []
|
||||
|
||||
# Remove beginning and ending quotes
|
||||
arg = arg.strip("'\"")
|
||||
|
||||
# Standardize the include argument based on server root
|
||||
if not arg.startswith("/"):
|
||||
# Normpath will condense ../
|
||||
arg = os.path.normpath(os.path.join(self.root, arg))
|
||||
else:
|
||||
arg = os.path.normpath(arg)
|
||||
arg = self.standard_path_from_server_root(arg)
|
||||
|
||||
# Attempts to add a transform to the file if one does not already exist
|
||||
if os.path.isdir(arg):
|
||||
@@ -732,7 +741,7 @@ class ApacheParser(object):
|
||||
"""
|
||||
if sys.version_info < (3, 6):
|
||||
# This strips off final /Z(?ms)
|
||||
return fnmatch.translate(clean_fn_match)[:-7]
|
||||
return fnmatch.translate(clean_fn_match)[:-7] # pragma: no cover
|
||||
# Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z
|
||||
return fnmatch.translate(clean_fn_match)[4:-3] # pragma: no cover
|
||||
|
||||
@@ -936,8 +945,8 @@ def case_i(string):
|
||||
:param str string: string to make case i regex
|
||||
|
||||
"""
|
||||
return "".join(["[" + c.upper() + c.lower() + "]"
|
||||
if c.isalpha() else c for c in re.escape(string)])
|
||||
return "".join("[" + c.upper() + c.lower() + "]"
|
||||
if c.isalpha() else c for c in re.escape(string))
|
||||
|
||||
|
||||
def get_aug_path(file_path):
|
||||
|
||||
@@ -11,7 +11,7 @@ def validate_kwargs(kwargs, required_names):
|
||||
:param list required_names: List of required parameter names.
|
||||
"""
|
||||
|
||||
validated_kwargs = dict()
|
||||
validated_kwargs = {}
|
||||
for name in required_names:
|
||||
try:
|
||||
validated_kwargs[name] = kwargs.pop(name)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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 -TLSv1 -TLSv1.1
|
||||
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||
SSLHonorCipherOrder 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
|
||||
certbot[dev]==1.1.0
|
||||
certbot[dev]==1.1.0
|
||||
@@ -1,25 +1,35 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.29.0',
|
||||
'certbot>=1.1.0',
|
||||
'mock',
|
||||
'python-augeas',
|
||||
'setuptools',
|
||||
'zope.component',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
dev_extras = [
|
||||
'apacheconfig>=0.3.1',
|
||||
'apacheconfig>=0.3.2',
|
||||
]
|
||||
|
||||
class PyTest(TestCommand):
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
"""Tests for AugeasParserNode classes"""
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
import os
|
||||
import util
|
||||
|
||||
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
|
||||
from certbot import errors
|
||||
|
||||
from certbot_apache._internal import assertions
|
||||
from certbot_apache._internal import augeasparser
|
||||
|
||||
|
||||
def _get_augeasnode_mock(filepath):
|
||||
""" Helper function for mocking out DualNode instance with an AugeasNode """
|
||||
def augeasnode_mock(metadata):
|
||||
return augeasparser.AugeasBlockNode(
|
||||
name=assertions.PASS,
|
||||
ancestor=None,
|
||||
filepath=filepath,
|
||||
metadata=metadata)
|
||||
return augeasnode_mock
|
||||
|
||||
class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods
|
||||
"""Test AugeasParserNode using available test configurations"""
|
||||
@@ -16,8 +29,11 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(AugeasParserNodeTest, self).setUp()
|
||||
|
||||
self.config = util.get_apache_configurator(
|
||||
self.config_path, self.vhost_path, self.config_dir, self.work_dir, use_parsernode=True)
|
||||
with mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.get_parsernode_root") as mock_parsernode:
|
||||
mock_parsernode.side_effect = _get_augeasnode_mock(
|
||||
os.path.join(self.config_path, "apache2.conf"))
|
||||
self.config = util.get_apache_configurator(
|
||||
self.config_path, self.vhost_path, self.config_dir, self.work_dir, use_parsernode=True)
|
||||
self.vh_truth = util.get_vh_truth(
|
||||
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
|
||||
|
||||
@@ -111,7 +127,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
name=servernames[0].name,
|
||||
parameters=["test", "setting", "these"],
|
||||
ancestor=assertions.PASS,
|
||||
metadata=servernames[0].primary.metadata
|
||||
metadata=servernames[0].metadata
|
||||
)
|
||||
self.assertTrue(mock_set.called)
|
||||
self.assertEqual(
|
||||
@@ -146,26 +162,26 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
self.assertTrue(found)
|
||||
|
||||
def test_add_child_comment(self):
|
||||
newc = self.config.parser_root.primary.add_child_comment("The content")
|
||||
newc = self.config.parser_root.add_child_comment("The content")
|
||||
comments = self.config.parser_root.find_comments("The content")
|
||||
self.assertEqual(len(comments), 1)
|
||||
self.assertEqual(
|
||||
newc.metadata["augeaspath"],
|
||||
comments[0].primary.metadata["augeaspath"]
|
||||
comments[0].metadata["augeaspath"]
|
||||
)
|
||||
self.assertEqual(newc.comment, comments[0].comment)
|
||||
|
||||
def test_delete_child(self):
|
||||
listens = self.config.parser_root.primary.find_directives("Listen")
|
||||
listens = self.config.parser_root.find_directives("Listen")
|
||||
self.assertEqual(len(listens), 1)
|
||||
self.config.parser_root.primary.delete_child(listens[0])
|
||||
self.config.parser_root.delete_child(listens[0])
|
||||
|
||||
listens = self.config.parser_root.primary.find_directives("Listen")
|
||||
listens = self.config.parser_root.find_directives("Listen")
|
||||
self.assertEqual(len(listens), 0)
|
||||
|
||||
def test_delete_child_not_found(self):
|
||||
listen = self.config.parser_root.find_directives("Listen")[0]
|
||||
listen.primary.metadata["augeaspath"] = "/files/something/nonexistent"
|
||||
listen.metadata["augeaspath"] = "/files/something/nonexistent"
|
||||
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
@@ -178,10 +194,10 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
"NewBlock",
|
||||
["first", "second"]
|
||||
)
|
||||
rpath, _, directive = nb.primary.metadata["augeaspath"].rpartition("/")
|
||||
rpath, _, directive = nb.metadata["augeaspath"].rpartition("/")
|
||||
self.assertEqual(
|
||||
rpath,
|
||||
self.config.parser_root.primary.metadata["augeaspath"]
|
||||
self.config.parser_root.metadata["augeaspath"]
|
||||
)
|
||||
self.assertTrue(directive.startswith("NewBlock"))
|
||||
|
||||
@@ -190,8 +206,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
"Beginning",
|
||||
position=0
|
||||
)
|
||||
parser = self.config.parser_root.primary.parser
|
||||
root_path = self.config.parser_root.primary.metadata["augeaspath"]
|
||||
parser = self.config.parser_root.parser
|
||||
root_path = self.config.parser_root.metadata["augeaspath"]
|
||||
# Get first child
|
||||
first = parser.aug.match("{}/*[1]".format(root_path))
|
||||
self.assertTrue(first[0].endswith("Beginning"))
|
||||
@@ -200,8 +216,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
self.config.parser_root.add_child_block(
|
||||
"VeryLast",
|
||||
)
|
||||
parser = self.config.parser_root.primary.parser
|
||||
root_path = self.config.parser_root.primary.metadata["augeaspath"]
|
||||
parser = self.config.parser_root.parser
|
||||
root_path = self.config.parser_root.metadata["augeaspath"]
|
||||
# Get last child
|
||||
last = parser.aug.match("{}/*[last()]".format(root_path))
|
||||
self.assertTrue(last[0].endswith("VeryLast"))
|
||||
@@ -211,8 +227,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
"VeryLastAlt",
|
||||
position=99999
|
||||
)
|
||||
parser = self.config.parser_root.primary.parser
|
||||
root_path = self.config.parser_root.primary.metadata["augeaspath"]
|
||||
parser = self.config.parser_root.parser
|
||||
root_path = self.config.parser_root.metadata["augeaspath"]
|
||||
# Get last child
|
||||
last = parser.aug.match("{}/*[last()]".format(root_path))
|
||||
self.assertTrue(last[0].endswith("VeryLastAlt"))
|
||||
@@ -222,15 +238,15 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
"Middle",
|
||||
position=5
|
||||
)
|
||||
parser = self.config.parser_root.primary.parser
|
||||
root_path = self.config.parser_root.primary.metadata["augeaspath"]
|
||||
parser = self.config.parser_root.parser
|
||||
root_path = self.config.parser_root.metadata["augeaspath"]
|
||||
# Augeas indices start at 1 :(
|
||||
middle = parser.aug.match("{}/*[6]".format(root_path))
|
||||
self.assertTrue(middle[0].endswith("Middle"))
|
||||
|
||||
def test_add_child_block_existing_name(self):
|
||||
parser = self.config.parser_root.primary.parser
|
||||
root_path = self.config.parser_root.primary.metadata["augeaspath"]
|
||||
parser = self.config.parser_root.parser
|
||||
root_path = self.config.parser_root.metadata["augeaspath"]
|
||||
# There already exists a single VirtualHost in the base config
|
||||
new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path))
|
||||
self.assertEqual(len(new_block), 0)
|
||||
@@ -239,7 +255,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
)
|
||||
new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path))
|
||||
self.assertEqual(len(new_block), 1)
|
||||
self.assertTrue(vh.primary.metadata["augeaspath"].endswith("VirtualHost[2]"))
|
||||
self.assertTrue(vh.metadata["augeaspath"].endswith("VirtualHost[2]"))
|
||||
|
||||
def test_node_init_error_bad_augeaspath(self):
|
||||
from certbot_apache._internal.augeasparser import AugeasBlockNode
|
||||
@@ -284,7 +300,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
self.assertEqual(len(dirs), 1)
|
||||
self.assertEqual(dirs[0].parameters, ("with", "parameters"))
|
||||
# The new directive was added to the very first line of the config
|
||||
self.assertTrue(dirs[0].primary.metadata["augeaspath"].endswith("[1]"))
|
||||
self.assertTrue(dirs[0].metadata["augeaspath"].endswith("[1]"))
|
||||
|
||||
def test_add_child_directive_exception(self):
|
||||
self.assertRaises(
|
||||
@@ -314,6 +330,6 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
self.assertTrue(nonmacro_test)
|
||||
|
||||
def test_find_ancestors_bad_path(self):
|
||||
self.config.parser_root.primary.metadata["augeaspath"] = ""
|
||||
ancs = self.config.parser_root.primary.find_ancestors("Anything")
|
||||
self.config.parser_root.metadata["augeaspath"] = ""
|
||||
ancs = self.config.parser_root.find_ancestors("Anything")
|
||||
self.assertEqual(len(ancs), 0)
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
import re
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import six # pylint: disable=unused-import # six is used in mock.patch()
|
||||
|
||||
from certbot import errors
|
||||
@@ -20,10 +23,10 @@ class AutoHSTSTest(util.ApacheTest):
|
||||
|
||||
self.config = util.get_apache_configurator(
|
||||
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.config.parser.modules.add("mod_headers.c")
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["headers_module"] = None
|
||||
self.config.parser.modules["mod_headers.c"] = None
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
|
||||
self.vh_truth = util.get_vh_truth(
|
||||
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
|
||||
@@ -42,8 +45,8 @@ class AutoHSTSTest(util.ApacheTest):
|
||||
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart")
|
||||
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod")
|
||||
def test_autohsts_enable_headers_mod(self, mock_enable, _restart):
|
||||
self.config.parser.modules.discard("headers_module")
|
||||
self.config.parser.modules.discard("mod_header.c")
|
||||
self.config.parser.modules.pop("headers_module", None)
|
||||
self.config.parser.modules.pop("mod_header.c", None)
|
||||
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
|
||||
self.assertTrue(mock_enable.called)
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ def get_vh_truth(temp_dir, config_name):
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "test.example.com.conf"),
|
||||
os.path.join(aug_pre, "test.example.com.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "test.example.com"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "ssl.conf"),
|
||||
os.path.join(aug_pre, "ssl.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("_default_:443")]),
|
||||
{obj.Addr.fromstring("_default_:443")},
|
||||
True, True, None)
|
||||
]
|
||||
return vh_truth
|
||||
@@ -104,7 +104,7 @@ class CentOS6Tests(util.ApacheTest):
|
||||
pre_loadmods = self.config.parser.find_dir(
|
||||
"LoadModule", "ssl_module", exclude=False)
|
||||
# LoadModules are not within IfModule blocks
|
||||
self.assertFalse(any(["ifmodule" in m.lower() for m in pre_loadmods]))
|
||||
self.assertFalse(any("ifmodule" in m.lower() for m in pre_loadmods))
|
||||
self.config.assoc["test.example.com"] = self.vh_truth[0]
|
||||
self.config.deploy_cert(
|
||||
"random.demo", "example/cert.pem", "example/key.pem",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Test for certbot_apache._internal.configurator for Centos overrides"""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import filesystem
|
||||
@@ -21,12 +24,12 @@ def get_vh_truth(temp_dir, config_name):
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "centos.example.com.conf"),
|
||||
os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "centos.example.com"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "ssl.conf"),
|
||||
os.path.join(aug_pre, "ssl.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("_default_:443")]),
|
||||
{obj.Addr.fromstring("_default_:443")},
|
||||
True, True, None)
|
||||
]
|
||||
return vh_truth
|
||||
@@ -126,7 +129,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
|
||||
return mod_val
|
||||
return ""
|
||||
mock_get.side_effect = mock_get_cfg
|
||||
self.config.parser.modules = set()
|
||||
self.config.parser.modules = {}
|
||||
self.config.parser.variables = {}
|
||||
|
||||
with mock.patch("certbot.util.get_os_info") as mock_osi:
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
import util
|
||||
|
||||
@@ -6,7 +6,10 @@ import socket
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import six # pylint: disable=unused-import # six is used in mock.patch()
|
||||
|
||||
from acme import challenges
|
||||
@@ -99,7 +102,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext",
|
||||
"vhost_root", "logs_root", "challenge_location",
|
||||
"handle_modules", "handle_sites", "ctl"]
|
||||
exp = dict()
|
||||
exp = {}
|
||||
|
||||
for k in ApacheConfigurator.OS_DEFAULTS:
|
||||
if k in parserargs:
|
||||
@@ -140,11 +143,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_utility = mock_getutility()
|
||||
mock_utility.notification = mock.MagicMock(return_value=True)
|
||||
names = self.config.get_all_names()
|
||||
self.assertEqual(names, set(
|
||||
["certbot.demo", "ocspvhost.com", "encryption-example.demo",
|
||||
self.assertEqual(names, {"certbot.demo", "ocspvhost.com", "encryption-example.demo",
|
||||
"nonsym.link", "vhost.in.rootconf", "www.certbot.demo",
|
||||
"duplicate.example.com"]
|
||||
))
|
||||
"duplicate.example.com"})
|
||||
|
||||
@certbot_util.patch_get_utility()
|
||||
@mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr")
|
||||
@@ -154,9 +155,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_utility.notification.return_value = True
|
||||
vhost = obj.VirtualHost(
|
||||
"fp", "ap",
|
||||
set([obj.Addr(("8.8.8.8", "443")),
|
||||
{obj.Addr(("8.8.8.8", "443")),
|
||||
obj.Addr(("zombo.com",)),
|
||||
obj.Addr(("192.168.1.2"))]),
|
||||
obj.Addr(("192.168.1.2"))},
|
||||
True, False)
|
||||
|
||||
self.config.vhosts.append(vhost)
|
||||
@@ -185,7 +186,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
def test_bad_servername_alias(self):
|
||||
ssl_vh1 = obj.VirtualHost(
|
||||
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
|
||||
"fp1", "ap1", {obj.Addr(("*", "443"))},
|
||||
True, False)
|
||||
# pylint: disable=protected-access
|
||||
self.config._add_servernames(ssl_vh1)
|
||||
@@ -198,7 +199,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# pylint: disable=protected-access
|
||||
self.config._add_servernames(self.vh_truth[2])
|
||||
self.assertEqual(
|
||||
self.vh_truth[2].get_names(), set(["*.le.co", "ip-172-30-0-17"]))
|
||||
self.vh_truth[2].get_names(), {"*.le.co", "ip-172-30-0-17"})
|
||||
|
||||
def test_get_virtual_hosts(self):
|
||||
"""Make sure all vhosts are being properly found."""
|
||||
@@ -269,7 +270,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select):
|
||||
mock_select.return_value = self.vh_truth[3]
|
||||
conflicting_vhost = obj.VirtualHost(
|
||||
"path", "aug_path", set([obj.Addr.fromstring("*:443")]),
|
||||
"path", "aug_path", {obj.Addr.fromstring("*:443")},
|
||||
True, True)
|
||||
self.config.vhosts.append(conflicting_vhost)
|
||||
|
||||
@@ -278,14 +279,14 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
def test_find_best_http_vhost_default(self):
|
||||
vh = obj.VirtualHost(
|
||||
"fp", "ap", set([obj.Addr.fromstring("_default_:80")]), False, True)
|
||||
"fp", "ap", {obj.Addr.fromstring("_default_:80")}, False, True)
|
||||
self.config.vhosts = [vh]
|
||||
self.assertEqual(self.config.find_best_http_vhost("foo.bar", False), vh)
|
||||
|
||||
def test_find_best_http_vhost_port(self):
|
||||
port = "8080"
|
||||
vh = obj.VirtualHost(
|
||||
"fp", "ap", set([obj.Addr.fromstring("*:" + port)]),
|
||||
"fp", "ap", {obj.Addr.fromstring("*:" + port)},
|
||||
False, True, "encryption-example.demo")
|
||||
self.config.vhosts.append(vh)
|
||||
self.assertEqual(self.config.find_best_http_vhost("foo.bar", False, port), vh)
|
||||
@@ -313,8 +314,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
def test_find_best_vhost_variety(self):
|
||||
# pylint: disable=protected-access
|
||||
ssl_vh = obj.VirtualHost(
|
||||
"fp", "ap", set([obj.Addr(("*", "443")),
|
||||
obj.Addr(("zombo.com",))]),
|
||||
"fp", "ap", {obj.Addr(("*", "443")),
|
||||
obj.Addr(("zombo.com",))},
|
||||
True, False)
|
||||
self.config.vhosts.append(ssl_vh)
|
||||
self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh)
|
||||
@@ -341,9 +342,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
def test_deploy_cert_enable_new_vhost(self):
|
||||
# Create
|
||||
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["socache_shmcb_module"] = None
|
||||
|
||||
self.assertFalse(ssl_vhost.enabled)
|
||||
self.config.deploy_cert(
|
||||
@@ -377,9 +378,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# pragma: no cover
|
||||
|
||||
def test_deploy_cert(self):
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["socache_shmcb_module"] = None
|
||||
# Patch _add_dummy_ssl_directives to make sure we write them correctly
|
||||
# pylint: disable=protected-access
|
||||
orig_add_dummy = self.config._add_dummy_ssl_directives
|
||||
@@ -459,9 +460,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
method is called with an invalid vhost parameter. Currently this tests
|
||||
that a PluginError is appropriately raised when important directives
|
||||
are missing in an SSL module."""
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["socache_shmcb_module"] = None
|
||||
|
||||
def side_effect(*args):
|
||||
"""Mocks case where an SSLCertificateFile directive can be found
|
||||
@@ -544,7 +545,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
call_found = True
|
||||
self.assertTrue(call_found)
|
||||
|
||||
def test_prepare_server_https(self):
|
||||
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
|
||||
def test_prepare_server_https(self, mock_reset):
|
||||
mock_enable = mock.Mock()
|
||||
self.config.enable_mod = mock_enable
|
||||
|
||||
@@ -570,7 +572,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
self.assertEqual(mock_add_dir.call_count, 2)
|
||||
|
||||
def test_prepare_server_https_named_listen(self):
|
||||
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
|
||||
def test_prepare_server_https_named_listen(self, mock_reset):
|
||||
mock_find = mock.Mock()
|
||||
mock_find.return_value = ["test1", "test2", "test3"]
|
||||
mock_get = mock.Mock()
|
||||
@@ -608,7 +611,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# self.config.prepare_server_https("8080", temp=True)
|
||||
# self.assertEqual(self.listens, 0)
|
||||
|
||||
def test_prepare_server_https_needed_listen(self):
|
||||
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
|
||||
def test_prepare_server_https_needed_listen(self, mock_reset):
|
||||
mock_find = mock.Mock()
|
||||
mock_find.return_value = ["test1", "test2"]
|
||||
mock_get = mock.Mock()
|
||||
@@ -624,8 +628,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.prepare_server_https("443")
|
||||
self.assertEqual(mock_add_dir.call_count, 1)
|
||||
|
||||
def test_prepare_server_https_mixed_listen(self):
|
||||
|
||||
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
|
||||
def test_prepare_server_https_mixed_listen(self, mock_reset):
|
||||
mock_find = mock.Mock()
|
||||
mock_find.return_value = ["test1", "test2"]
|
||||
mock_get = mock.Mock()
|
||||
@@ -682,7 +686,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.assertEqual(ssl_vhost.path,
|
||||
"/files" + ssl_vhost.filep + "/IfModule/Virtualhost")
|
||||
self.assertEqual(len(ssl_vhost.addrs), 1)
|
||||
self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs)
|
||||
self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs)
|
||||
self.assertEqual(ssl_vhost.name, "encryption-example.demo")
|
||||
self.assertTrue(ssl_vhost.ssl)
|
||||
self.assertFalse(ssl_vhost.enabled)
|
||||
@@ -904,10 +908,10 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot_apache._internal.display_ops.select_vhost")
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
mock_exe.return_value = True
|
||||
ssl_vh1 = obj.VirtualHost(
|
||||
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
|
||||
"fp1", "ap1", {obj.Addr(("*", "443"))},
|
||||
True, False)
|
||||
ssl_vh1.name = "satoshi.com"
|
||||
self.config.vhosts.append(ssl_vh1)
|
||||
@@ -942,8 +946,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_ocsp_stapling(self, mock_exe):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["socache_shmcb_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
|
||||
mock_exe.return_value = True
|
||||
|
||||
@@ -969,8 +973,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_ocsp_stapling_twice(self, mock_exe):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["socache_shmcb_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
|
||||
mock_exe.return_value = True
|
||||
|
||||
@@ -997,8 +1001,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
def test_ocsp_unsupported_apache_version(self, mock_exe):
|
||||
mock_exe.return_value = True
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["socache_shmcb_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
|
||||
@@ -1008,7 +1012,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
def test_get_http_vhost_third_filter(self):
|
||||
ssl_vh = obj.VirtualHost(
|
||||
"fp", "ap", set([obj.Addr(("*", "443"))]),
|
||||
"fp", "ap", {obj.Addr(("*", "443"))},
|
||||
True, False)
|
||||
ssl_vh.name = "satoshi.com"
|
||||
self.config.vhosts.append(ssl_vh)
|
||||
@@ -1021,8 +1025,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_http_header_hsts(self, mock_exe, _):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["headers_module"] = None
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
@@ -1042,9 +1046,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.assertEqual(len(hsts_header), 4)
|
||||
|
||||
def test_http_header_hsts_twice(self):
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
# skip the enable mod
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.config.parser.modules["headers_module"] = None
|
||||
|
||||
# This will create an ssl vhost for encryption-example.demo
|
||||
self.config.choose_vhost("encryption-example.demo")
|
||||
@@ -1060,8 +1064,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_http_header_uir(self, mock_exe, _):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["headers_module"] = None
|
||||
|
||||
mock_exe.return_value = True
|
||||
|
||||
@@ -1084,9 +1088,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.assertEqual(len(uir_header), 4)
|
||||
|
||||
def test_http_header_uir_twice(self):
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
# skip the enable mod
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.config.parser.modules["headers_module"] = None
|
||||
|
||||
# This will create an ssl vhost for encryption-example.demo
|
||||
self.config.choose_vhost("encryption-example.demo")
|
||||
@@ -1101,7 +1105,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot.util.run_script")
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_redirect_well_formed_http(self, mock_exe, _):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
mock_exe.return_value = True
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2))
|
||||
@@ -1127,7 +1131,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
def test_rewrite_rule_exists(self):
|
||||
# Skip the enable mod
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
|
||||
self.config.parser.add_dir(
|
||||
self.vh_truth[3].path, "RewriteRule", ["Unknown"])
|
||||
@@ -1136,7 +1140,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
def test_rewrite_engine_exists(self):
|
||||
# Skip the enable mod
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
|
||||
self.config.parser.add_dir(
|
||||
self.vh_truth[3].path, "RewriteEngine", "on")
|
||||
@@ -1146,7 +1150,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot.util.run_script")
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_redirect_with_existing_rewrite(self, mock_exe, _):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
mock_exe.return_value = True
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
|
||||
@@ -1180,7 +1184,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot.util.run_script")
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_redirect_with_old_https_redirection(self, mock_exe, _):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
mock_exe.return_value = True
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
|
||||
@@ -1209,10 +1213,10 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
|
||||
def test_redirect_with_conflict(self):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
ssl_vh = obj.VirtualHost(
|
||||
"fp", "ap", set([obj.Addr(("*", "443")),
|
||||
obj.Addr(("zombo.com",))]),
|
||||
"fp", "ap", {obj.Addr(("*", "443")),
|
||||
obj.Addr(("zombo.com",))},
|
||||
True, False)
|
||||
# No names ^ this guy should conflict.
|
||||
|
||||
@@ -1222,7 +1226,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
def test_redirect_two_domains_one_vhost(self):
|
||||
# Skip the enable mod
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
|
||||
|
||||
# Creates ssl vhost for the domain
|
||||
@@ -1237,7 +1241,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
def test_redirect_from_previous_run(self):
|
||||
# Skip the enable mod
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
|
||||
self.config.choose_vhost("red.blue.purple.com")
|
||||
self.config.enhance("red.blue.purple.com", "redirect")
|
||||
@@ -1250,22 +1254,22 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.enhance, "green.blue.purple.com", "redirect")
|
||||
|
||||
def test_create_own_redirect(self):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
|
||||
# For full testing... give names...
|
||||
self.vh_truth[1].name = "default.com"
|
||||
self.vh_truth[1].aliases = set(["yes.default.com"])
|
||||
self.vh_truth[1].aliases = {"yes.default.com"}
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.config._enable_redirect(self.vh_truth[1], "")
|
||||
self.assertEqual(len(self.config.vhosts), 13)
|
||||
|
||||
def test_create_own_redirect_for_old_apache_version(self):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2))
|
||||
# For full testing... give names...
|
||||
self.vh_truth[1].name = "default.com"
|
||||
self.vh_truth[1].aliases = set(["yes.default.com"])
|
||||
self.vh_truth[1].aliases = {"yes.default.com"}
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.config._enable_redirect(self.vh_truth[1], "")
|
||||
@@ -1326,9 +1330,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
def test_deploy_cert_not_parsed_path(self):
|
||||
# Make sure that we add include to root config for vhosts when
|
||||
# handle-sites is false
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["socache_shmcb_module"] = None
|
||||
tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot"))
|
||||
filesystem.chmod(tmp_path, 0o755)
|
||||
mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path"
|
||||
@@ -1441,8 +1445,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard")
|
||||
def test_enhance_wildcard_after_install(self, mock_choose):
|
||||
# pylint: disable=protected-access
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["headers_module"] = None
|
||||
self.vh_truth[3].ssl = True
|
||||
self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]]
|
||||
self.config.enhance("*.certbot.demo", "ensure-http-header",
|
||||
@@ -1453,8 +1457,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
def test_enhance_wildcard_no_install(self, mock_choose):
|
||||
self.vh_truth[3].ssl = True
|
||||
mock_choose.return_value = [self.vh_truth[3]]
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.parser.modules["headers_module"] = None
|
||||
self.config.enhance("*.certbot.demo", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
self.assertTrue(mock_choose.called)
|
||||
@@ -1606,7 +1610,7 @@ class MultiVhostsTest(util.ApacheTest):
|
||||
self.assertEqual(ssl_vhost.path,
|
||||
"/files" + ssl_vhost.filep + "/IfModule/VirtualHost")
|
||||
self.assertEqual(len(ssl_vhost.addrs), 1)
|
||||
self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs)
|
||||
self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs)
|
||||
self.assertEqual(ssl_vhost.name, "banana.vomit.com")
|
||||
self.assertTrue(ssl_vhost.ssl)
|
||||
self.assertFalse(ssl_vhost.enabled)
|
||||
@@ -1638,7 +1642,7 @@ class MultiVhostsTest(util.ApacheTest):
|
||||
|
||||
@certbot_util.patch_get_utility()
|
||||
def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_get_utility):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
|
||||
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4])
|
||||
|
||||
@@ -1658,7 +1662,7 @@ class MultiVhostsTest(util.ApacheTest):
|
||||
|
||||
@certbot_util.patch_get_utility()
|
||||
def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_get_utility):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.parser.modules["rewrite_module"] = None
|
||||
|
||||
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3])
|
||||
|
||||
@@ -1700,7 +1704,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
|
||||
self.config.updated_mod_ssl_conf_digest)
|
||||
|
||||
def _current_ssl_options_hash(self):
|
||||
return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC"))
|
||||
return crypto_util.sha256sum(self.config.pick_apache_config())
|
||||
|
||||
def _assert_current_file(self):
|
||||
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
|
||||
@@ -1736,7 +1740,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.option("MOD_SSL_CONF_SRC")),
|
||||
self.config.pick_apache_config()),
|
||||
self._current_ssl_options_hash())
|
||||
self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
|
||||
self._current_ssl_options_hash())
|
||||
@@ -1752,19 +1756,99 @@ 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.option("MOD_SSL_CONF_SRC")),
|
||||
self.config.pick_apache_config()),
|
||||
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_current_file_hash_in_all_hashes(self):
|
||||
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.
|
||||
"""
|
||||
from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES
|
||||
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.")
|
||||
import pkg_resources
|
||||
|
||||
tls_configs_dir = pkg_resources.resource_filename(
|
||||
"certbot_apache", os.path.join("_internal", "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))
|
||||
|
||||
def test_openssl_version(self):
|
||||
self.config._openssl_version = None
|
||||
some_string_contents = b"""
|
||||
SSLOpenSSLConfCmd
|
||||
OpenSSL configuration command
|
||||
SSLv3 not supported by this version of OpenSSL
|
||||
'%s': invalid OpenSSL configuration command
|
||||
OpenSSL 1.0.2g 1 Mar 2016
|
||||
OpenSSL
|
||||
AH02407: "SSLOpenSSLConfCmd %s %s" failed for %s
|
||||
AH02556: "SSLOpenSSLConfCmd %s %s" applied to %s
|
||||
OpenSSL 1.0.2g 1 Mar 2016
|
||||
"""
|
||||
self.config.parser.modules['ssl_module'] = '/fake/path'
|
||||
with mock.patch("certbot_apache._internal.configurator."
|
||||
"ApacheConfigurator._open_module_file") as mock_omf:
|
||||
mock_omf.return_value = some_string_contents
|
||||
self.assertEqual(self.config.openssl_version(), "1.0.2g")
|
||||
|
||||
def test_current_version(self):
|
||||
self.config.version = (2, 4, 10)
|
||||
self.config._openssl_version = '1.0.2m'
|
||||
self.assertTrue('old' in self.config.pick_apache_config())
|
||||
|
||||
self.config.version = (2, 4, 11)
|
||||
self.config._openssl_version = '1.0.2m'
|
||||
self.assertTrue('current' in self.config.pick_apache_config())
|
||||
|
||||
self.config._openssl_version = '1.0.2a'
|
||||
self.assertTrue('old' in self.config.pick_apache_config())
|
||||
|
||||
def test_openssl_version_warns(self):
|
||||
self.config._openssl_version = '1.0.2a'
|
||||
self.assertEqual(self.config.openssl_version(), '1.0.2a')
|
||||
|
||||
self.config._openssl_version = None
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
self.assertEqual(self.config.openssl_version(), None)
|
||||
self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0])
|
||||
|
||||
self.config._openssl_version = None
|
||||
self.config.parser.modules['ssl_module'] = None
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
self.assertEqual(self.config.openssl_version(), None)
|
||||
self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0])
|
||||
|
||||
self.config.parser.modules['ssl_module'] = "/fake/path"
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
# Check that correct logger.warning was printed
|
||||
self.assertEqual(self.config.openssl_version(), None)
|
||||
self.assertTrue("Unable to read" in mock_log.call_args[0][0])
|
||||
|
||||
contents_missing_openssl = b"these contents won't match the regex"
|
||||
with mock.patch("certbot_apache._internal.configurator."
|
||||
"ApacheConfigurator._open_module_file") as mock_omf:
|
||||
mock_omf.return_value = contents_missing_openssl
|
||||
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
|
||||
# Check that correct logger.warning was printed
|
||||
self.assertEqual(self.config.openssl_version(), None)
|
||||
self.assertTrue("Could not find OpenSSL" in mock_log.call_args[0][0])
|
||||
|
||||
def test_open_module_file(self):
|
||||
mock_open = mock.mock_open(read_data="testing 12 3")
|
||||
with mock.patch("six.moves.builtins.open", mock_open):
|
||||
self.assertEqual(self.config._open_module_file("/nonsense/"), "testing 12 3")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
@@ -61,8 +64,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
def test_deploy_cert_enable_new_vhost(self):
|
||||
# Create
|
||||
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.assertFalse(ssl_vhost.enabled)
|
||||
self.config.deploy_cert(
|
||||
"encryption-example.demo", "example/cert.pem", "example/key.pem",
|
||||
@@ -92,8 +95,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
self.config_path, self.vhost_path, self.config_dir,
|
||||
self.work_dir, version=(2, 4, 16))
|
||||
self.config = self.mock_deploy_cert(self.config)
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
|
||||
# Get the default 443 vhost
|
||||
self.config.assoc["random.demo"] = self.vh_truth[1]
|
||||
@@ -128,8 +131,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
self.config_path, self.vhost_path, self.config_dir,
|
||||
self.work_dir, version=(2, 4, 16))
|
||||
self.config = self.mock_deploy_cert(self.config)
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
|
||||
# Get the default 443 vhost
|
||||
self.config.assoc["random.demo"] = self.vh_truth[1]
|
||||
@@ -143,8 +146,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
self.config_path, self.vhost_path, self.config_dir,
|
||||
self.work_dir, version=(2, 4, 7))
|
||||
self.config = self.mock_deploy_cert(self.config)
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["ssl_module"] = None
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
|
||||
# Get the default 443 vhost
|
||||
self.config.assoc["random.demo"] = self.vh_truth[1]
|
||||
@@ -157,7 +160,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_ocsp_stapling_enable_mod(self, mock_exe, _):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
|
||||
mock_exe.return_value = True
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
@@ -169,7 +172,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_ensure_http_header_enable_mod(self, mock_exe, _):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules["mod_ssl.c"] = None
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Test certbot_apache._internal.display_ops."""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.display import util as display_util
|
||||
@@ -93,9 +96,9 @@ class SelectVhostTest(unittest.TestCase):
|
||||
|
||||
self.vhosts.append(
|
||||
obj.VirtualHost(
|
||||
"path", "aug_path", set([obj.Addr.fromstring("*:80")]),
|
||||
"path", "aug_path", {obj.Addr.fromstring("*:80")},
|
||||
False, False,
|
||||
"wildcard.com", set(["*.wildcard.com"])))
|
||||
"wildcard.com", {"*.wildcard.com"}))
|
||||
|
||||
self.assertEqual(self.vhosts[5], self._call(self.vhosts))
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Tests for DualParserNode implementation"""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot_apache._internal import assertions
|
||||
from certbot_apache._internal import augeasparser
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Test for certbot_apache._internal.entrypoint for override class resolution"""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot_apache._internal import configurator
|
||||
from certbot_apache._internal import entrypoint
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Test for certbot_apache._internal.configurator for Fedora 29+ overrides"""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import filesystem
|
||||
@@ -120,7 +123,7 @@ class MultipleVhostsTestFedora(util.ApacheTest):
|
||||
return mod_val
|
||||
return ""
|
||||
mock_get.side_effect = mock_get_cfg
|
||||
self.config.parser.modules = set()
|
||||
self.config.parser.modules = {}
|
||||
self.config.parser.variables = {}
|
||||
|
||||
with mock.patch("certbot.util.get_os_info") as mock_osi:
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Test for certbot_apache._internal.configurator for Gentoo overrides"""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import filesystem
|
||||
@@ -21,19 +24,19 @@ def get_vh_truth(temp_dir, config_name):
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "gentoo.example.com.conf"),
|
||||
os.path.join(aug_pre, "gentoo.example.com.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "gentoo.example.com"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "00_default_vhost.conf"),
|
||||
os.path.join(aug_pre, "00_default_vhost.conf/IfDefine/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "localhost"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "00_default_ssl_vhost.conf"),
|
||||
os.path.join(aug_pre,
|
||||
"00_default_ssl_vhost.conf" +
|
||||
"/IfDefine/IfDefine/IfModule/VirtualHost"),
|
||||
set([obj.Addr.fromstring("_default_:443")]),
|
||||
{obj.Addr.fromstring("_default_:443")},
|
||||
True, True, "localhost")
|
||||
]
|
||||
return vh_truth
|
||||
@@ -117,7 +120,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
|
||||
return mod_val
|
||||
return None # pragma: no cover
|
||||
mock_get.side_effect = mock_get_cfg
|
||||
self.config.parser.modules = set()
|
||||
self.config.parser.modules = {}
|
||||
|
||||
with mock.patch("certbot.util.get_os_info") as mock_osi:
|
||||
# Make sure we have the have the Gentoo httpd constants
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""Test for certbot_apache._internal.http_01."""
|
||||
import unittest
|
||||
import errno
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from acme import challenges
|
||||
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
|
||||
from certbot import achallenges
|
||||
from certbot import errors
|
||||
from certbot.compat import filesystem
|
||||
@@ -40,8 +43,8 @@ class ApacheHttp01Test(util.ApacheTest):
|
||||
|
||||
modules = ["ssl", "rewrite", "authz_core", "authz_host"]
|
||||
for mod in modules:
|
||||
self.config.parser.modules.add("mod_{0}.c".format(mod))
|
||||
self.config.parser.modules.add(mod + "_module")
|
||||
self.config.parser.modules["mod_{0}.c".format(mod)] = None
|
||||
self.config.parser.modules[mod + "_module"] = None
|
||||
|
||||
from certbot_apache._internal.http_01 import ApacheHttp01
|
||||
self.http = ApacheHttp01(self.config)
|
||||
@@ -52,24 +55,24 @@ class ApacheHttp01Test(util.ApacheTest):
|
||||
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod")
|
||||
def test_enable_modules_apache_2_2(self, mock_enmod):
|
||||
self.config.version = (2, 2)
|
||||
self.config.parser.modules.remove("authz_host_module")
|
||||
self.config.parser.modules.remove("mod_authz_host.c")
|
||||
del self.config.parser.modules["authz_host_module"]
|
||||
del self.config.parser.modules["mod_authz_host.c"]
|
||||
|
||||
enmod_calls = self.common_enable_modules_test(mock_enmod)
|
||||
self.assertEqual(enmod_calls[0][0][0], "authz_host")
|
||||
|
||||
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod")
|
||||
def test_enable_modules_apache_2_4(self, mock_enmod):
|
||||
self.config.parser.modules.remove("authz_core_module")
|
||||
self.config.parser.modules.remove("mod_authz_core.c")
|
||||
del self.config.parser.modules["authz_core_module"]
|
||||
del self.config.parser.modules["mod_authz_host.c"]
|
||||
|
||||
enmod_calls = self.common_enable_modules_test(mock_enmod)
|
||||
self.assertEqual(enmod_calls[0][0][0], "authz_core")
|
||||
|
||||
def common_enable_modules_test(self, mock_enmod):
|
||||
"""Tests enabling mod_rewrite and other modules."""
|
||||
self.config.parser.modules.remove("rewrite_module")
|
||||
self.config.parser.modules.remove("mod_rewrite.c")
|
||||
del self.config.parser.modules["rewrite_module"]
|
||||
del self.config.parser.modules["mod_rewrite.c"]
|
||||
|
||||
self.http.prepare_http01_modules()
|
||||
|
||||
@@ -197,6 +200,12 @@ class ApacheHttp01Test(util.ApacheTest):
|
||||
|
||||
self.assertTrue(os.path.exists(challenge_dir))
|
||||
|
||||
@mock.patch("certbot_apache._internal.http_01.filesystem.makedirs")
|
||||
def test_failed_makedirs(self, mock_makedirs):
|
||||
mock_makedirs.side_effect = OSError(errno.EACCES, "msg")
|
||||
self.http.add_chall(self.achalls[0])
|
||||
self.assertRaises(errors.PluginError, self.http.perform)
|
||||
|
||||
def _test_challenge_conf(self):
|
||||
with open(self.http.challenge_conf_pre) as f:
|
||||
pre_conf_contents = f.read()
|
||||
|
||||
@@ -14,13 +14,13 @@ class VirtualHostTest(unittest.TestCase):
|
||||
self.addr_default = Addr.fromstring("_default_:443")
|
||||
|
||||
self.vhost1 = VirtualHost(
|
||||
"filep", "vh_path", set([self.addr1]), False, False, "localhost")
|
||||
"filep", "vh_path", {self.addr1}, False, False, "localhost")
|
||||
|
||||
self.vhost1b = VirtualHost(
|
||||
"filep", "vh_path", set([self.addr1]), False, False, "localhost")
|
||||
"filep", "vh_path", {self.addr1}, False, False, "localhost")
|
||||
|
||||
self.vhost2 = VirtualHost(
|
||||
"fp", "vhp", set([self.addr2]), False, False, "localhost")
|
||||
"fp", "vhp", {self.addr2}, False, False, "localhost")
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(repr(self.addr2),
|
||||
@@ -42,7 +42,7 @@ class VirtualHostTest(unittest.TestCase):
|
||||
|
||||
complex_vh = VirtualHost(
|
||||
"fp", "vhp",
|
||||
set([Addr.fromstring("*:443"), Addr.fromstring("1.2.3.4:443")]),
|
||||
{Addr.fromstring("*:443"), Addr.fromstring("1.2.3.4:443")},
|
||||
False, False)
|
||||
self.assertTrue(complex_vh.conflicts([self.addr1]))
|
||||
self.assertTrue(complex_vh.conflicts([self.addr2]))
|
||||
@@ -57,14 +57,14 @@ class VirtualHostTest(unittest.TestCase):
|
||||
def test_same_server(self):
|
||||
from certbot_apache._internal.obj import VirtualHost
|
||||
no_name1 = VirtualHost(
|
||||
"fp", "vhp", set([self.addr1]), False, False, None)
|
||||
"fp", "vhp", {self.addr1}, False, False, None)
|
||||
no_name2 = VirtualHost(
|
||||
"fp", "vhp", set([self.addr2]), False, False, None)
|
||||
"fp", "vhp", {self.addr2}, False, False, None)
|
||||
no_name3 = VirtualHost(
|
||||
"fp", "vhp", set([self.addr_default]),
|
||||
"fp", "vhp", {self.addr_default},
|
||||
False, False, None)
|
||||
no_name4 = VirtualHost(
|
||||
"fp", "vhp", set([self.addr2, self.addr_default]),
|
||||
"fp", "vhp", {self.addr2, self.addr_default},
|
||||
False, False, None)
|
||||
|
||||
self.assertTrue(self.vhost1.same_server(self.vhost2))
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
@@ -114,7 +117,7 @@ class BasicParserTest(util.ParserTest):
|
||||
"""
|
||||
from certbot_apache._internal.parser import get_aug_path
|
||||
# This makes sure that find_dir will work
|
||||
self.parser.modules.add("mod_ssl.c")
|
||||
self.parser.modules["mod_ssl.c"] = "/fake/path"
|
||||
|
||||
self.parser.add_dir_to_ifmodssl(
|
||||
get_aug_path(self.parser.loc["default"]),
|
||||
@@ -128,7 +131,7 @@ class BasicParserTest(util.ParserTest):
|
||||
def test_add_dir_to_ifmodssl_multiple(self):
|
||||
from certbot_apache._internal.parser import get_aug_path
|
||||
# This makes sure that find_dir will work
|
||||
self.parser.modules.add("mod_ssl.c")
|
||||
self.parser.modules["mod_ssl.c"] = "/fake/path"
|
||||
|
||||
self.parser.add_dir_to_ifmodssl(
|
||||
get_aug_path(self.parser.loc["default"]),
|
||||
@@ -260,7 +263,7 @@ class BasicParserTest(util.ParserTest):
|
||||
expected_vars = {"TEST": "", "U_MICH": "", "TLS": "443",
|
||||
"example_path": "Documents/path"}
|
||||
|
||||
self.parser.modules = set()
|
||||
self.parser.modules = {}
|
||||
with mock.patch(
|
||||
"certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse:
|
||||
self.parser.update_runtime_variables()
|
||||
@@ -282,7 +285,7 @@ class BasicParserTest(util.ParserTest):
|
||||
os.path.dirname(self.parser.loc["root"]))
|
||||
|
||||
mock_cfg.return_value = inc_val
|
||||
self.parser.modules = set()
|
||||
self.parser.modules = {}
|
||||
|
||||
with mock.patch(
|
||||
"certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse:
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
"""Tests for ApacheConfigurator for AugeasParserNode classes"""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
import util
|
||||
|
||||
try:
|
||||
import apacheconfig
|
||||
HAS_APACHECONFIG = True
|
||||
except ImportError: # pragma: no cover
|
||||
HAS_APACHECONFIG = False
|
||||
|
||||
|
||||
@unittest.skipIf(not HAS_APACHECONFIG, reason='Tests require apacheconfig dependency')
|
||||
class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods
|
||||
"""Test AugeasParserNode using available test configurations"""
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ import unittest
|
||||
|
||||
import augeas
|
||||
import josepy as jose
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import zope.component
|
||||
|
||||
from certbot.compat import os
|
||||
@@ -85,7 +88,8 @@ def get_apache_configurator(
|
||||
config_dir, work_dir, version=(2, 4, 7),
|
||||
os_info="generic",
|
||||
conf_vhost_path=None,
|
||||
use_parsernode=False):
|
||||
use_parsernode=False,
|
||||
openssl_version="1.1.1a"):
|
||||
"""Create an Apache Configurator with the specified options.
|
||||
|
||||
:param conf: Function that returns binary paths. self.conf in Configurator
|
||||
@@ -118,7 +122,8 @@ def get_apache_configurator(
|
||||
except KeyError:
|
||||
config_class = configurator.ApacheConfigurator
|
||||
config = config_class(config=mock_le_config, name="apache",
|
||||
version=version, use_parsernode=use_parsernode)
|
||||
version=version, use_parsernode=use_parsernode,
|
||||
openssl_version=openssl_version)
|
||||
if not conf_vhost_path:
|
||||
config_class.OS_DEFAULTS["vhost_root"] = vhost_path
|
||||
else:
|
||||
@@ -140,71 +145,71 @@ def get_vh_truth(temp_dir, config_name):
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "encryption-example.conf"),
|
||||
os.path.join(aug_pre, "encryption-example.conf/Virtualhost"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "encryption-example.demo"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "default-ssl.conf"),
|
||||
os.path.join(aug_pre,
|
||||
"default-ssl.conf/IfModule/VirtualHost"),
|
||||
set([obj.Addr.fromstring("_default_:443")]), True, True),
|
||||
{obj.Addr.fromstring("_default_:443")}, True, True),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "000-default.conf"),
|
||||
os.path.join(aug_pre, "000-default.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80"),
|
||||
obj.Addr.fromstring("[::]:80")]),
|
||||
{obj.Addr.fromstring("*:80"),
|
||||
obj.Addr.fromstring("[::]:80")},
|
||||
False, True, "ip-172-30-0-17"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "certbot.conf"),
|
||||
os.path.join(aug_pre, "certbot.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, True,
|
||||
{obj.Addr.fromstring("*:80")}, False, True,
|
||||
"certbot.demo", aliases=["www.certbot.demo"]),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "mod_macro-example.conf"),
|
||||
os.path.join(aug_pre,
|
||||
"mod_macro-example.conf/Macro/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, True,
|
||||
{obj.Addr.fromstring("*:80")}, False, True,
|
||||
modmacro=True),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "default-ssl-port-only.conf"),
|
||||
os.path.join(aug_pre, ("default-ssl-port-only.conf/"
|
||||
"IfModule/VirtualHost")),
|
||||
set([obj.Addr.fromstring("_default_:443")]), True, True),
|
||||
{obj.Addr.fromstring("_default_:443")}, True, True),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "wildcard.conf"),
|
||||
os.path.join(aug_pre, "wildcard.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, True,
|
||||
{obj.Addr.fromstring("*:80")}, False, True,
|
||||
"ip-172-30-0-17", aliases=["*.blue.purple.com"]),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "ocsp-ssl.conf"),
|
||||
os.path.join(aug_pre, "ocsp-ssl.conf/IfModule/VirtualHost"),
|
||||
set([obj.Addr.fromstring("10.2.3.4:443")]), True, True,
|
||||
{obj.Addr.fromstring("10.2.3.4:443")}, True, True,
|
||||
"ocspvhost.com"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "non-symlink.conf"),
|
||||
os.path.join(aug_pre, "non-symlink.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, True,
|
||||
{obj.Addr.fromstring("*:80")}, False, True,
|
||||
"nonsym.link"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "default-ssl-port-only.conf"),
|
||||
os.path.join(aug_pre,
|
||||
"default-ssl-port-only.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), True, True, ""),
|
||||
{obj.Addr.fromstring("*:80")}, True, True, ""),
|
||||
obj.VirtualHost(
|
||||
os.path.join(temp_dir, config_name,
|
||||
"apache2/apache2.conf"),
|
||||
"/files" + os.path.join(temp_dir, config_name,
|
||||
"apache2/apache2.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, True,
|
||||
{obj.Addr.fromstring("*:80")}, False, True,
|
||||
"vhost.in.rootconf"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "duplicatehttp.conf"),
|
||||
os.path.join(aug_pre, "duplicatehttp.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("10.2.3.4:80")]), False, True,
|
||||
{obj.Addr.fromstring("10.2.3.4:80")}, False, True,
|
||||
"duplicate.example.com"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "duplicatehttps.conf"),
|
||||
os.path.join(aug_pre, "duplicatehttps.conf/IfModule/VirtualHost"),
|
||||
set([obj.Addr.fromstring("10.2.3.4:443")]), True, True,
|
||||
{obj.Addr.fromstring("10.2.3.4:443")}, True, True,
|
||||
"duplicate.example.com")]
|
||||
return vh_truth
|
||||
if config_name == "debian_apache_2_4/multi_vhosts":
|
||||
@@ -215,27 +220,27 @@ def get_vh_truth(temp_dir, config_name):
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "default.conf"),
|
||||
os.path.join(aug_pre, "default.conf/VirtualHost[1]"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "ip-172-30-0-17"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "default.conf"),
|
||||
os.path.join(aug_pre, "default.conf/VirtualHost[2]"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "banana.vomit.com"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "multi-vhost.conf"),
|
||||
os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[1]"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "1.multi.vhost.tld"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "multi-vhost.conf"),
|
||||
os.path.join(aug_pre, "multi-vhost.conf/IfModule/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "2.multi.vhost.tld"),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "multi-vhost.conf"),
|
||||
os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[2]"),
|
||||
set([obj.Addr.fromstring("*:80")]),
|
||||
{obj.Addr.fromstring("*:80")},
|
||||
False, True, "3.multi.vhost.tld")]
|
||||
return vh_truth
|
||||
return None # pragma: no cover
|
||||
|
||||
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="1.2.0"
|
||||
LE_AUTO_VERSION="1.3.0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
@@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \
|
||||
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
|
||||
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
|
||||
|
||||
certbot==1.2.0 \
|
||||
--hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \
|
||||
--hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c
|
||||
acme==1.2.0 \
|
||||
--hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \
|
||||
--hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab
|
||||
certbot-apache==1.2.0 \
|
||||
--hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \
|
||||
--hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3
|
||||
certbot-nginx==1.2.0 \
|
||||
--hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \
|
||||
--hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db
|
||||
certbot==1.3.0 \
|
||||
--hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \
|
||||
--hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8
|
||||
acme==1.3.0 \
|
||||
--hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \
|
||||
--hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d
|
||||
certbot-apache==1.3.0 \
|
||||
--hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \
|
||||
--hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52
|
||||
certbot-nginx==1.3.0 \
|
||||
--hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \
|
||||
--hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Module to handle the context of integration tests."""
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
@@ -595,6 +595,23 @@ def test_ocsp_status_live(context):
|
||||
assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
|
||||
|
||||
|
||||
def test_ocsp_renew(context):
|
||||
"""Test that revoked certificates are renewed."""
|
||||
# Obtain a certificate
|
||||
certname = context.get_domain('ocsp-renew')
|
||||
context.certbot(['--domains', certname])
|
||||
|
||||
# Test that "certbot renew" does not renew the certificate
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 1)
|
||||
context.certbot(['renew'], force_renew=False)
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 1)
|
||||
|
||||
# Revoke the certificate and test that it does renew the certificate
|
||||
context.certbot(['revoke', '--cert-name', certname, '--no-delete-after-revoke'])
|
||||
context.certbot(['renew'], force_renew=False)
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 2)
|
||||
|
||||
|
||||
def test_dry_run_deactivate_authzs(context):
|
||||
"""Test that Certbot deactivates authorizations when performing a dry run"""
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class ACMEServer(object):
|
||||
: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
|
||||
:param bool stdout: if True stream all subprocesses stdout to standard stdout
|
||||
"""
|
||||
self._construct_acme_xdist(acme_server, nodes)
|
||||
|
||||
@@ -131,7 +131,7 @@ class ACMEServer(object):
|
||||
environ['PEBBLE_AUTHZREUSE'] = '100'
|
||||
|
||||
self._launch_process(
|
||||
[pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053'],
|
||||
[pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053', '-strict'],
|
||||
env=environ)
|
||||
|
||||
self._launch_process(
|
||||
@@ -165,17 +165,24 @@ class ACMEServer(object):
|
||||
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)
|
||||
try:
|
||||
# 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)
|
||||
# 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=300)
|
||||
|
||||
# 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'})
|
||||
response.raise_for_status()
|
||||
# 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'})
|
||||
response.raise_for_status()
|
||||
except BaseException:
|
||||
# If we failed to set up boulder, print its logs.
|
||||
print('=> Boulder setup failed. Boulder logs are:')
|
||||
process = self._launch_process(['docker-compose', 'logs'], cwd=instance_path, force_stderr=True)
|
||||
process.wait()
|
||||
raise
|
||||
|
||||
print('=> Finished boulder instance deployment.')
|
||||
|
||||
@@ -188,11 +195,12 @@ class ACMEServer(object):
|
||||
self._launch_process(command)
|
||||
print('=> Finished configuring the HTTP proxy.')
|
||||
|
||||
def _launch_process(self, command, cwd=os.getcwd(), env=None):
|
||||
def _launch_process(self, command, cwd=os.getcwd(), env=None, force_stderr=False):
|
||||
"""Launch silently a subprocess OS command"""
|
||||
if not env:
|
||||
env = os.environ
|
||||
process = subprocess.Popen(command, stdout=self._stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env)
|
||||
stdout = sys.stderr if force_stderr else self._stdout
|
||||
process = subprocess.Popen(command, stdout=stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env)
|
||||
self._processes.append(process)
|
||||
return process
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE):
|
||||
file_h.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
|
||||
|
||||
req = crypto.X509Req()
|
||||
san = ', '.join(['DNS:{0}'.format(item) for item in domains])
|
||||
san = ', '.join('DNS:{0}'.format(item) for item in domains)
|
||||
san_constraint = crypto.X509Extension(b'subjectAltName', False, san.encode('utf-8'))
|
||||
req.add_extensions([san_constraint])
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import requests
|
||||
|
||||
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
|
||||
|
||||
PEBBLE_VERSION = 'v2.2.1'
|
||||
PEBBLE_VERSION = 'v2.3.0'
|
||||
ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets')
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ COPY certbot-nginx /opt/certbot/src/certbot-nginx/
|
||||
COPY certbot-compatibility-test /opt/certbot/src/certbot-compatibility-test/
|
||||
COPY tools /opt/certbot/src/tools
|
||||
|
||||
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
|
||||
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv -p python2 /opt/certbot/venv && \
|
||||
/opt/certbot/venv/bin/pip install -U setuptools && \
|
||||
/opt/certbot/venv/bin/pip install -U pip
|
||||
ENV PATH /opt/certbot/venv/bin:$PATH
|
||||
|
||||
@@ -3,7 +3,10 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import zope.interface
|
||||
|
||||
from certbot import errors as le_errors
|
||||
|
||||
@@ -96,7 +96,7 @@ def test_authenticator(plugin, config, temp_dir):
|
||||
|
||||
def _create_achalls(plugin):
|
||||
"""Returns a list of annotated challenges to test on plugin"""
|
||||
achalls = list()
|
||||
achalls = []
|
||||
names = plugin.get_testable_domain_names()
|
||||
for domain in names:
|
||||
prefs = plugin.get_chall_pref(domain)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Tests for certbot_compatibility_test.validator."""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
import OpenSSL
|
||||
import requests
|
||||
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
'certbot-apache',
|
||||
'mock',
|
||||
'six',
|
||||
'requests',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
if sys.version_info < (2, 7, 9):
|
||||
# For secure SSL connexion with Python 2.7 (InsecurePlatformWarning)
|
||||
install_requires.append('ndg-httpsclient')
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
@@ -12,11 +14,19 @@ install_requires = [
|
||||
'acme>=0.29.0',
|
||||
'certbot>=1.1.0',
|
||||
'cloudflare>=1.5.1',
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
import unittest
|
||||
|
||||
import CloudFlare
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
@@ -12,11 +14,19 @@ install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
from requests.exceptions import HTTPError
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.29.0',
|
||||
'certbot>=1.1.0',
|
||||
'mock',
|
||||
'python-digitalocean>=1.11',
|
||||
'setuptools',
|
||||
'six',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
import unittest
|
||||
|
||||
import digitalocean
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
from distutils.version import StrictVersion
|
||||
import os
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
# This package normally depends on dns-lexicon>=3.2.1 to address the
|
||||
# problem described in https://github.com/AnalogJ/lexicon/issues/387,
|
||||
# however, the fix there has been backported to older versions of
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
@@ -12,11 +14,19 @@ install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'dns-lexicon>=2.1.22',
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
@@ -12,7 +14,6 @@ install_requires = [
|
||||
'acme>=0.29.0',
|
||||
'certbot>=1.1.0',
|
||||
'google-api-python-client>=1.5.5',
|
||||
'mock',
|
||||
'oauth2client>=4.0',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
@@ -20,6 +21,15 @@ install_requires = [
|
||||
'httplib2'
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -6,7 +6,10 @@ from googleapiclient import discovery
|
||||
from googleapiclient.errors import Error
|
||||
from googleapiclient.http import HttpMock
|
||||
from httplib2 import ServerNotFoundError
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'dns-lexicon>=2.2.3',
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
@@ -12,11 +14,19 @@ install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from certbot.compat import os
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from distutils.version import StrictVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
version = '1.3.0.dev0'
|
||||
version = '1.4.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
@@ -12,11 +14,19 @@ install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
'of setuptools. Version 36.2+ of setuptools is required.')
|
||||
elif sys.version_info < (3,3):
|
||||
install_requires.append('mock')
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock # type: ignore
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from certbot.compat import os
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user