mirror of
https://github.com/certbot/certbot.git
synced 2026-05-28 04:34:11 -04:00
Merge pull request #5668 from certbot/update-test-everything
Fix merge conflicts and add more ACMEv2 integration tests
This commit is contained in:
commit
e64137fb53
56 changed files with 698 additions and 135 deletions
26
.travis.yml
26
.travis.yml
|
|
@ -24,7 +24,11 @@ matrix:
|
|||
- python: "2.7"
|
||||
env: TOXENV=lint
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27-oldest BOULDER_INTEGRATION=1
|
||||
env: TOXENV=py27-nginx-oldest BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27-nginx-oldest BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "2.7"
|
||||
|
|
@ -35,6 +39,8 @@ matrix:
|
|||
env: TOXENV=py27_install BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "2.7"
|
||||
env: TOXENV='py27-{acme,apache,certbot,dns}-oldest'
|
||||
- sudo: required
|
||||
env: TOXENV=apache_compat
|
||||
services: docker
|
||||
|
|
@ -74,15 +80,27 @@ matrix:
|
|||
env: TOXENV=apacheconftest
|
||||
sudo: required
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34 BOULDER_INTEGRATION=1
|
||||
env: TOXENV=py34 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35 BOULDER_INTEGRATION=1
|
||||
env: TOXENV=py35 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36 BOULDER_INTEGRATION=1
|
||||
env: TOXENV=py36 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "2.7"
|
||||
|
|
|
|||
|
|
@ -310,9 +310,17 @@ class Client(ClientBase):
|
|||
:returns: Authorization Resource.
|
||||
:rtype: `.AuthorizationResource`
|
||||
|
||||
:raises errors.WildcardUnsupportedError: if a wildcard is requested
|
||||
|
||||
"""
|
||||
if new_authzr_uri is not None:
|
||||
logger.debug("request_challenges with new_authzr_uri deprecated.")
|
||||
|
||||
if identifier.value.startswith("*"):
|
||||
raise errors.WildcardUnsupportedError(
|
||||
"Requesting an authorization for a wildcard name is"
|
||||
" forbidden by this version of the ACME protocol.")
|
||||
|
||||
new_authz = messages.NewAuthorization(identifier=identifier)
|
||||
response = self._post(self.directory.new_authz, new_authz)
|
||||
# TODO: handle errors
|
||||
|
|
@ -333,6 +341,8 @@ class Client(ClientBase):
|
|||
:returns: Authorization Resource.
|
||||
:rtype: `.AuthorizationResource`
|
||||
|
||||
:raises errors.WildcardUnsupportedError: if a wildcard is requested
|
||||
|
||||
"""
|
||||
return self.request_challenges(messages.Identifier(
|
||||
typ=messages.IDENTIFIER_FQDN, value=domain), new_authzr_uri)
|
||||
|
|
@ -752,6 +762,10 @@ class BackwardsCompatibleClientV2(object):
|
|||
|
||||
:returns: The newly created order.
|
||||
:rtype: OrderResource
|
||||
|
||||
:raises errors.WildcardUnsupportedError: if a wildcard domain is
|
||||
requested but unsupported by the ACME version
|
||||
|
||||
"""
|
||||
if self.acme_version == 1:
|
||||
csr = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr_pem)
|
||||
|
|
@ -795,8 +809,8 @@ class BackwardsCompatibleClientV2(object):
|
|||
'certificate, please rerun the command for a new one.')
|
||||
|
||||
cert = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)
|
||||
chain = crypto_util.dump_pyopenssl_chain(chain)
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode()
|
||||
chain = crypto_util.dump_pyopenssl_chain(chain).decode()
|
||||
|
||||
return orderr.update(fullchain_pem=(cert + chain))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -99,10 +99,10 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
|
|||
self.chain = [wrapped, wrapped]
|
||||
|
||||
self.cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped)
|
||||
OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped).decode()
|
||||
|
||||
single_chain = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, loaded)
|
||||
OpenSSL.crypto.FILETYPE_PEM, loaded).decode()
|
||||
self.chain_pem = single_chain + single_chain
|
||||
|
||||
self.fullchain_pem = self.cert_pem + self.chain_pem
|
||||
|
|
@ -376,6 +376,13 @@ class ClientTest(ClientTestBase):
|
|||
errors.UnexpectedUpdate, self.client.request_challenges,
|
||||
self.identifier)
|
||||
|
||||
def test_request_challenges_wildcard(self):
|
||||
wildcard_identifier = messages.Identifier(
|
||||
typ=messages.IDENTIFIER_FQDN, value='*.example.org')
|
||||
self.assertRaises(
|
||||
errors.WildcardUnsupportedError, self.client.request_challenges,
|
||||
wildcard_identifier)
|
||||
|
||||
def test_request_domain_challenges(self):
|
||||
self.client.request_challenges = mock.MagicMock()
|
||||
self.assertEqual(
|
||||
|
|
|
|||
|
|
@ -287,6 +287,9 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
|
|||
:param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
|
||||
:class:`josepy.util.ComparableX509`).
|
||||
|
||||
:returns: certificate chain bundle
|
||||
:rtype: bytes
|
||||
|
||||
"""
|
||||
# XXX: returns empty string when no chain is available, which
|
||||
# shuts up RenewableCert, but might not be the best solution...
|
||||
|
|
|
|||
|
|
@ -115,3 +115,6 @@ class ConflictError(ClientError):
|
|||
self.location = location
|
||||
super(ConflictError, self).__init__()
|
||||
|
||||
|
||||
class WildcardUnsupportedError(Error):
|
||||
"""Error for when a wildcard is requested but is unsupported by ACME CA."""
|
||||
|
|
|
|||
2
certbot-apache/local-oldest-requirements.txt
Normal file
2
certbot-apache/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'mock',
|
||||
'python-augeas',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-cloudflare/Dockerfile
Normal file
5
certbot-dns-cloudflare/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-cloudflare
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-cloudflare
|
||||
2
certbot-dns-cloudflare/local-oldest-requirements.txt
Normal file
2
certbot-dns-cloudflare/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'cloudflare>=1.5.1',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-cloudxns/Dockerfile
Normal file
5
certbot-dns-cloudxns/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-cloudxns
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-cloudxns
|
||||
2
certbot-dns-cloudxns/local-oldest-requirements.txt
Normal file
2
certbot-dns-cloudxns/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-digitalocean/Dockerfile
Normal file
5
certbot-dns-digitalocean/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-digitalocean
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-digitalocean
|
||||
2
certbot-dns-digitalocean/local-oldest-requirements.txt
Normal file
2
certbot-dns-digitalocean/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'mock',
|
||||
'python-digitalocean>=1.11',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-dnsimple/Dockerfile
Normal file
5
certbot-dns-dnsimple/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-dnsimple
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-dnsimple
|
||||
2
certbot-dns-dnsimple/local-oldest-requirements.txt
Normal file
2
certbot-dns-dnsimple/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-dnsmadeeasy/Dockerfile
Normal file
5
certbot-dns-dnsmadeeasy/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-dnsmadeeasy
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-dnsmadeeasy
|
||||
2
certbot-dns-dnsmadeeasy/local-oldest-requirements.txt
Normal file
2
certbot-dns-dnsmadeeasy/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-google/Dockerfile
Normal file
5
certbot-dns-google/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-google
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-google
|
||||
2
certbot-dns-google/local-oldest-requirements.txt
Normal file
2
certbot-dns-google/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
# 1.5 is the first version that supports oauth2client>=2.0
|
||||
'google-api-python-client>=1.5',
|
||||
'mock',
|
||||
|
|
|
|||
5
certbot-dns-luadns/Dockerfile
Normal file
5
certbot-dns-luadns/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-luadns
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-luadns
|
||||
2
certbot-dns-luadns/local-oldest-requirements.txt
Normal file
2
certbot-dns-luadns/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-nsone/Dockerfile
Normal file
5
certbot-dns-nsone/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-nsone
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-nsone
|
||||
2
certbot-dns-nsone/local-oldest-requirements.txt
Normal file
2
certbot-dns-nsone/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-rfc2136/Dockerfile
Normal file
5
certbot-dns-rfc2136/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-rfc2136
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-rfc2136
|
||||
2
certbot-dns-rfc2136/local-oldest-requirements.txt
Normal file
2
certbot-dns-rfc2136/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -6,10 +6,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dnspython',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
5
certbot-dns-route53/Dockerfile
Normal file
5
certbot-dns-route53/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM certbot/certbot
|
||||
|
||||
COPY . src/certbot-dns-route53
|
||||
|
||||
RUN pip install --no-cache-dir --editable src/certbot-dns-route53
|
||||
2
certbot-dns-route53/local-oldest-requirements.txt
Normal file
2
certbot-dns-route53/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
acme[dev]==0.21.1
|
||||
certbot[dev]==0.21.1
|
||||
|
|
@ -5,9 +5,11 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'boto3',
|
||||
'mock',
|
||||
'setuptools',
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from certbot import util
|
|||
from certbot.plugins import common
|
||||
|
||||
from certbot_nginx import constants
|
||||
from certbot_nginx import display_ops
|
||||
from certbot_nginx import nginxparser
|
||||
from certbot_nginx import parser
|
||||
from certbot_nginx import tls_sni_01
|
||||
|
|
@ -92,6 +93,11 @@ class NginxConfigurator(common.Installer):
|
|||
# For creating new vhosts if no names match
|
||||
self.new_vhost = None
|
||||
|
||||
# List of vhosts configured per wildcard domain on this run.
|
||||
# used by deploy_cert() and enhance()
|
||||
self._wildcard_vhosts = {}
|
||||
self._wildcard_redirect_vhosts = {}
|
||||
|
||||
# Add number of outstanding challenges
|
||||
self._chall_out = 0
|
||||
|
||||
|
|
@ -146,6 +152,7 @@ class NginxConfigurator(common.Installer):
|
|||
raise errors.PluginError(
|
||||
'Unable to lock %s', self.conf('server-root'))
|
||||
|
||||
|
||||
# Entry point in main.py for installing cert
|
||||
def deploy_cert(self, domain, cert_path, key_path,
|
||||
chain_path=None, fullchain_path=None):
|
||||
|
|
@ -166,14 +173,24 @@ class NginxConfigurator(common.Installer):
|
|||
"The nginx plugin currently requires --fullchain-path to "
|
||||
"install a cert.")
|
||||
|
||||
vhost = self.choose_vhost(domain, create_if_no_match=True)
|
||||
vhosts = self.choose_vhosts(domain, create_if_no_match=True)
|
||||
for vhost in vhosts:
|
||||
self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path)
|
||||
|
||||
def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path):
|
||||
# pylint: disable=unused-argument
|
||||
"""
|
||||
Helper function for deploy_cert() that handles the actual deployment
|
||||
this exists because we might want to do multiple deployments per
|
||||
domain originally passed for deploy_cert(). This is especially true
|
||||
with wildcard certificates
|
||||
"""
|
||||
cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path],
|
||||
['\n ', 'ssl_certificate_key', ' ', key_path]]
|
||||
|
||||
self.parser.add_server_directives(vhost,
|
||||
cert_directives, replace=True)
|
||||
logger.info("Deployed Certificate to VirtualHost %s for %s",
|
||||
vhost.filep, ", ".join(vhost.names))
|
||||
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
|
||||
|
||||
self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
|
||||
(vhost.filep,
|
||||
|
|
@ -181,10 +198,61 @@ class NginxConfigurator(common.Installer):
|
|||
self.save_notes += "\tssl_certificate %s\n" % fullchain_path
|
||||
self.save_notes += "\tssl_certificate_key %s\n" % key_path
|
||||
|
||||
def _choose_vhosts_wildcard(self, domain, prefer_ssl, no_ssl_filter_port=None):
|
||||
"""Prompts user to choose vhosts to install a wildcard certificate for"""
|
||||
if prefer_ssl:
|
||||
vhosts_cache = self._wildcard_vhosts
|
||||
preference_test = lambda x: x.ssl
|
||||
else:
|
||||
vhosts_cache = self._wildcard_redirect_vhosts
|
||||
preference_test = lambda x: not x.ssl
|
||||
|
||||
# Caching!
|
||||
if domain in vhosts_cache:
|
||||
# Vhosts for a wildcard domain were already selected
|
||||
return vhosts_cache[domain]
|
||||
|
||||
# Get all vhosts whether or not they are covered by the wildcard domain
|
||||
vhosts = self.parser.get_vhosts()
|
||||
|
||||
# Go through the vhosts, making sure that we cover all the names
|
||||
# present, but preferring the SSL or non-SSL vhosts
|
||||
filtered_vhosts = {}
|
||||
for vhost in vhosts:
|
||||
# Ensure we're listening non-sslishly on no_ssl_filter_port
|
||||
if no_ssl_filter_port is not None:
|
||||
if not self._vhost_listening_on_port_no_ssl(vhost, no_ssl_filter_port):
|
||||
continue
|
||||
for name in vhost.names:
|
||||
if preference_test(vhost):
|
||||
# Prefer either SSL or non-SSL vhosts
|
||||
filtered_vhosts[name] = vhost
|
||||
elif name not in filtered_vhosts:
|
||||
# Add if not in list previously
|
||||
filtered_vhosts[name] = vhost
|
||||
|
||||
# Only unique VHost objects
|
||||
dialog_input = set([vhost for vhost in filtered_vhosts.values()])
|
||||
|
||||
# Ask the user which of names to enable, expect list of names back
|
||||
return_vhosts = display_ops.select_vhost_multiple(list(dialog_input))
|
||||
|
||||
for vhost in return_vhosts:
|
||||
if domain not in vhosts_cache:
|
||||
vhosts_cache[domain] = []
|
||||
vhosts_cache[domain].append(vhost)
|
||||
|
||||
return return_vhosts
|
||||
|
||||
#######################
|
||||
# Vhost parsing methods
|
||||
#######################
|
||||
def choose_vhost(self, target_name, create_if_no_match=False):
|
||||
def _choose_vhost_single(self, target_name):
|
||||
matches = self._get_ranked_matches(target_name)
|
||||
vhosts = [x for x in [self._select_best_name_match(matches)] if x is not None]
|
||||
return vhosts
|
||||
|
||||
def choose_vhosts(self, target_name, create_if_no_match=False):
|
||||
"""Chooses a virtual host based on the given domain name.
|
||||
|
||||
.. note:: This makes the vhost SSL-enabled if it isn't already. Follows
|
||||
|
|
@ -202,17 +270,19 @@ class NginxConfigurator(common.Installer):
|
|||
when there is no match found. If we can't choose a default, raise a
|
||||
MisconfigurationError.
|
||||
|
||||
:returns: ssl vhost associated with name
|
||||
:rtype: :class:`~certbot_nginx.obj.VirtualHost`
|
||||
:returns: ssl vhosts associated with name
|
||||
:rtype: list of :class:`~certbot_nginx.obj.VirtualHost`
|
||||
|
||||
"""
|
||||
vhost = None
|
||||
|
||||
matches = self._get_ranked_matches(target_name)
|
||||
vhost = self._select_best_name_match(matches)
|
||||
if not vhost:
|
||||
if util.is_wildcard_domain(target_name):
|
||||
# Ask user which VHosts to support.
|
||||
vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=True)
|
||||
else:
|
||||
vhosts = self._choose_vhost_single(target_name)
|
||||
if not vhosts:
|
||||
if create_if_no_match:
|
||||
vhost = self._vhost_from_duplicated_default(target_name)
|
||||
# result will not be [None] because it errors on failure
|
||||
vhosts = [self._vhost_from_duplicated_default(target_name)]
|
||||
else:
|
||||
# No matches. Raise a misconfiguration error.
|
||||
raise errors.MisconfigurationError(
|
||||
|
|
@ -222,10 +292,11 @@ class NginxConfigurator(common.Installer):
|
|||
"nginx configuration: "
|
||||
"https://nginx.org/en/docs/http/server_names.html") % (target_name))
|
||||
# Note: if we are enhancing with ocsp, vhost should already be ssl.
|
||||
if not vhost.ssl:
|
||||
self._make_server_ssl(vhost)
|
||||
for vhost in vhosts:
|
||||
if not vhost.ssl:
|
||||
self._make_server_ssl(vhost)
|
||||
|
||||
return vhost
|
||||
return vhosts
|
||||
|
||||
def ipv6_info(self, port):
|
||||
"""Returns tuple of booleans (ipv6_active, ipv6only_present)
|
||||
|
|
@ -240,6 +311,9 @@ class NginxConfigurator(common.Installer):
|
|||
configuration, and existence of ipv6only directive for specified port
|
||||
:rtype: tuple of type (bool, bool)
|
||||
"""
|
||||
# port should be a string, but it's easy to mess up, so let's
|
||||
# make sure it is one
|
||||
port = str(port)
|
||||
vhosts = self.parser.get_vhosts()
|
||||
ipv6_active = False
|
||||
ipv6only_present = False
|
||||
|
|
@ -359,7 +433,7 @@ class NginxConfigurator(common.Installer):
|
|||
return sorted(matches, key=lambda x: x['rank'])
|
||||
|
||||
|
||||
def choose_redirect_vhost(self, target_name, port, create_if_no_match=False):
|
||||
def choose_redirect_vhosts(self, target_name, port, create_if_no_match=False):
|
||||
"""Chooses a single virtual host for redirect enhancement.
|
||||
|
||||
Chooses the vhost most closely matching target_name that is
|
||||
|
|
@ -377,15 +451,20 @@ class NginxConfigurator(common.Installer):
|
|||
when there is no match found. If we can't choose a default, raise a
|
||||
MisconfigurationError.
|
||||
|
||||
:returns: vhost associated with name
|
||||
:rtype: :class:`~certbot_nginx.obj.VirtualHost`
|
||||
:returns: vhosts associated with name
|
||||
:rtype: list of :class:`~certbot_nginx.obj.VirtualHost`
|
||||
|
||||
"""
|
||||
matches = self._get_redirect_ranked_matches(target_name, port)
|
||||
vhost = self._select_best_name_match(matches)
|
||||
if not vhost and create_if_no_match:
|
||||
vhost = self._vhost_from_duplicated_default(target_name, port=port)
|
||||
return vhost
|
||||
if util.is_wildcard_domain(target_name):
|
||||
# Ask user which VHosts to enhance.
|
||||
vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=False,
|
||||
no_ssl_filter_port=port)
|
||||
else:
|
||||
matches = self._get_redirect_ranked_matches(target_name, port)
|
||||
vhosts = [x for x in [self._select_best_name_match(matches)]if x is not None]
|
||||
if not vhosts and create_if_no_match:
|
||||
vhosts = [self._vhost_from_duplicated_default(target_name, port=port)]
|
||||
return vhosts
|
||||
|
||||
def _port_matches(self, test_port, matching_port):
|
||||
# test_port is a number, matching is a number or "" or None
|
||||
|
|
@ -395,6 +474,23 @@ class NginxConfigurator(common.Installer):
|
|||
else:
|
||||
return test_port == matching_port
|
||||
|
||||
def _vhost_listening_on_port_no_ssl(self, vhost, port):
|
||||
found_matching_port = False
|
||||
if len(vhost.addrs) == 0:
|
||||
# if there are no listen directives at all, Nginx defaults to
|
||||
# listening on port 80.
|
||||
found_matching_port = (port == self.DEFAULT_LISTEN_PORT)
|
||||
else:
|
||||
for addr in vhost.addrs:
|
||||
if self._port_matches(port, addr.get_port()) and addr.ssl == False:
|
||||
found_matching_port = True
|
||||
|
||||
if found_matching_port:
|
||||
# make sure we don't have an 'ssl on' directive
|
||||
return not self.parser.has_ssl_on_directive(vhost)
|
||||
else:
|
||||
return False
|
||||
|
||||
def _get_redirect_ranked_matches(self, target_name, port):
|
||||
"""Gets a ranked list of plaintextish port-listening vhosts matching target_name
|
||||
|
||||
|
|
@ -411,21 +507,7 @@ class NginxConfigurator(common.Installer):
|
|||
all_vhosts = self.parser.get_vhosts()
|
||||
|
||||
def _vhost_matches(vhost, port):
|
||||
found_matching_port = False
|
||||
if len(vhost.addrs) == 0:
|
||||
# if there are no listen directives at all, Nginx defaults to
|
||||
# listening on port 80.
|
||||
found_matching_port = (port == self.DEFAULT_LISTEN_PORT)
|
||||
else:
|
||||
for addr in vhost.addrs:
|
||||
if self._port_matches(port, addr.get_port()) and addr.ssl == False:
|
||||
found_matching_port = True
|
||||
|
||||
if found_matching_port:
|
||||
# make sure we don't have an 'ssl on' directive
|
||||
return not self.parser.has_ssl_on_directive(vhost)
|
||||
else:
|
||||
return False
|
||||
return self._vhost_listening_on_port_no_ssl(vhost, port)
|
||||
|
||||
matching_vhosts = [vhost for vhost in all_vhosts if _vhost_matches(vhost, port)]
|
||||
|
||||
|
|
@ -587,17 +669,31 @@ class NginxConfigurator(common.Installer):
|
|||
"""
|
||||
|
||||
port = self.DEFAULT_LISTEN_PORT
|
||||
vhost = None
|
||||
# If there are blocks listening plaintextishly on self.DEFAULT_LISTEN_PORT,
|
||||
# choose the most name-matching one.
|
||||
|
||||
vhost = self.choose_redirect_vhost(domain, port)
|
||||
vhosts = self.choose_redirect_vhosts(domain, port)
|
||||
|
||||
if vhost is None:
|
||||
if not vhosts:
|
||||
logger.info("No matching insecure server blocks listening on port %s found.",
|
||||
self.DEFAULT_LISTEN_PORT)
|
||||
return
|
||||
|
||||
for vhost in vhosts:
|
||||
self._enable_redirect_single(domain, vhost)
|
||||
|
||||
def _enable_redirect_single(self, domain, vhost):
|
||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||
|
||||
If the vhost is listening plaintextishly, separate out the
|
||||
relevant directives into a new server block and add a rewrite directive.
|
||||
|
||||
.. note:: This function saves the configuration
|
||||
|
||||
:param str domain: domain to enable redirect for
|
||||
:param `~obj.Vhost` vhost: vhost to enable redirect for
|
||||
"""
|
||||
|
||||
new_vhost = None
|
||||
if vhost.ssl:
|
||||
new_vhost = self.parser.duplicate_vhost(vhost,
|
||||
|
|
@ -638,7 +734,18 @@ class NginxConfigurator(common.Installer):
|
|||
:type chain_path: `str` or `None`
|
||||
|
||||
"""
|
||||
vhost = self.choose_vhost(domain)
|
||||
vhosts = self.choose_vhosts(domain)
|
||||
for vhost in vhosts:
|
||||
self._enable_ocsp_stapling_single(vhost, chain_path)
|
||||
|
||||
def _enable_ocsp_stapling_single(self, vhost, chain_path):
|
||||
"""Include OCSP response in TLS handshake
|
||||
|
||||
:param str vhost: vhost to enable OCSP response for
|
||||
:param chain_path: chain file path
|
||||
:type chain_path: `str` or `None`
|
||||
|
||||
"""
|
||||
if self.version < (1, 3, 7):
|
||||
raise errors.PluginError("Version 1.3.7 or greater of nginx "
|
||||
"is needed to enable OCSP stapling")
|
||||
|
|
@ -889,14 +996,23 @@ def _test_block_from_block(block):
|
|||
parser.comment_directive(test_block, 0)
|
||||
return test_block[:-1]
|
||||
|
||||
|
||||
def _redirect_block_for_domain(domain):
|
||||
updated_domain = domain
|
||||
match_symbol = '='
|
||||
if util.is_wildcard_domain(domain):
|
||||
match_symbol = '~'
|
||||
updated_domain = updated_domain.replace('.', r'\.')
|
||||
updated_domain = updated_domain.replace('*', '[^.]+')
|
||||
updated_domain = '^' + updated_domain + '$'
|
||||
redirect_block = [[
|
||||
['\n ', 'if', ' ', '($host', ' ', '=', ' ', '%s)' % domain, ' '],
|
||||
['\n ', 'if', ' ', '($host', ' ', match_symbol, ' ', '%s)' % updated_domain, ' '],
|
||||
[['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'],
|
||||
'\n ']],
|
||||
['\n']]
|
||||
return redirect_block
|
||||
|
||||
|
||||
def nginx_restart(nginx_ctl, nginx_conf):
|
||||
"""Restarts the Nginx Server.
|
||||
|
||||
|
|
|
|||
44
certbot-nginx/certbot_nginx/display_ops.py
Normal file
44
certbot-nginx/certbot_nginx/display_ops.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""Contains UI methods for Nginx operations."""
|
||||
import logging
|
||||
|
||||
import zope.component
|
||||
|
||||
from certbot import interfaces
|
||||
|
||||
import certbot.display.util as display_util
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def select_vhost_multiple(vhosts):
|
||||
"""Select multiple Vhosts to install the certificate for
|
||||
:param vhosts: Available Nginx VirtualHosts
|
||||
:type vhosts: :class:`list` of type `~obj.Vhost`
|
||||
:returns: List of VirtualHosts
|
||||
:rtype: :class:`list`of type `~obj.Vhost`
|
||||
"""
|
||||
if not vhosts:
|
||||
return list()
|
||||
tags_list = [vhost.display_repr()+"\n" for vhost in vhosts]
|
||||
# Remove the extra newline from the last entry
|
||||
if len(tags_list):
|
||||
tags_list[-1] = tags_list[-1][:-1]
|
||||
code, names = zope.component.getUtility(interfaces.IDisplay).checklist(
|
||||
"Which server blocks would you like to modify?",
|
||||
tags=tags_list, force_interactive=True)
|
||||
if code == display_util.OK:
|
||||
return_vhosts = _reversemap_vhosts(names, vhosts)
|
||||
return return_vhosts
|
||||
return []
|
||||
|
||||
def _reversemap_vhosts(names, vhosts):
|
||||
"""Helper function for select_vhost_multiple for mapping string
|
||||
representations back to actual vhost objects"""
|
||||
return_vhosts = list()
|
||||
|
||||
for selection in names:
|
||||
for vhost in vhosts:
|
||||
if vhost.display_repr().strip() == selection.strip():
|
||||
return_vhosts.append(vhost)
|
||||
return return_vhosts
|
||||
|
|
@ -179,13 +179,17 @@ class NginxHttp01(common.ChallengePerformer):
|
|||
|
||||
"""
|
||||
try:
|
||||
vhost = self.configurator.choose_redirect_vhost(achall.domain,
|
||||
vhosts = self.configurator.choose_redirect_vhosts(achall.domain,
|
||||
'%i' % self.configurator.config.http01_port, create_if_no_match=True)
|
||||
except errors.MisconfigurationError:
|
||||
# Couldn't find either a matching name+port server block
|
||||
# or a port+default_server block, so create a dummy block
|
||||
return self._make_server_block(achall)
|
||||
|
||||
# len is max 1 because Nginx doesn't authenticate wildcards
|
||||
# if len were or vhosts None, we would have errored
|
||||
vhost = vhosts[0]
|
||||
|
||||
# Modify existing server block
|
||||
validation = achall.validation(achall.account_key)
|
||||
validation_path = self._get_validation_path(achall)
|
||||
|
|
|
|||
|
|
@ -193,6 +193,11 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.filep, tuple(self.path),
|
||||
tuple(self.addrs), tuple(self.names),
|
||||
self.ssl, self.enabled))
|
||||
|
||||
def contains_list(self, test):
|
||||
"""Determine if raw server block contains test list at top level
|
||||
"""
|
||||
|
|
@ -216,3 +221,15 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
for a in self.addrs:
|
||||
if not a.ipv6:
|
||||
return True
|
||||
|
||||
def display_repr(self):
|
||||
"""Return a representation of VHost to be used in dialog"""
|
||||
return (
|
||||
"File: {filename}\n"
|
||||
"Addresses: {addrs}\n"
|
||||
"Names: {names}\n"
|
||||
"HTTPS: {https}\n".format(
|
||||
filename=self.filep,
|
||||
addrs=", ".join(str(addr) for addr in self.addrs),
|
||||
names=", ".join(self.names),
|
||||
https="Yes" if self.ssl else "No"))
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
['#', parser.COMMENT]]]],
|
||||
parsed[0])
|
||||
|
||||
def test_choose_vhost(self):
|
||||
def test_choose_vhosts(self):
|
||||
localhost_conf = set(['localhost', r'~^(www\.)?(example|bar)\.'])
|
||||
server_conf = set(['somename', 'another.alias', 'alias'])
|
||||
example_conf = set(['.example.com', 'example.*'])
|
||||
|
|
@ -159,7 +159,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
'69.255.225.155']
|
||||
|
||||
for name in results:
|
||||
vhost = self.config.choose_vhost(name)
|
||||
vhost = self.config.choose_vhosts(name)[0]
|
||||
path = os.path.relpath(vhost.filep, self.temp_dir)
|
||||
|
||||
self.assertEqual(results[name], vhost.names)
|
||||
|
|
@ -173,7 +173,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
|
||||
for name in bad_results:
|
||||
self.assertRaises(errors.MisconfigurationError,
|
||||
self.config.choose_vhost, name)
|
||||
self.config.choose_vhosts, name)
|
||||
|
||||
def test_ipv6only(self):
|
||||
# ipv6_info: (ipv6_active, ipv6only_present)
|
||||
|
|
@ -181,6 +181,18 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
# Port 443 has ipv6only=on because of ipv6ssl.com vhost
|
||||
self.assertEquals((True, True), self.config.ipv6_info("443"))
|
||||
|
||||
def test_ipv6only_detection(self):
|
||||
self.config.version = (1, 3, 1)
|
||||
|
||||
self.config.deploy_cert(
|
||||
"ipv6.com",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
"example/chain.pem",
|
||||
"example/fullchain.pem")
|
||||
|
||||
for addr in self.config.choose_vhosts("ipv6.com")[0].addrs:
|
||||
self.assertFalse(addr.ipv6only)
|
||||
|
||||
def test_more_info(self):
|
||||
self.assertTrue('nginx.conf' in self.config.more_info())
|
||||
|
|
@ -702,6 +714,100 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
self.config.rollback_checkpoints()
|
||||
self.assertTrue(mock_parser_load.call_count == 3)
|
||||
|
||||
def test_choose_vhosts_wildcard(self):
|
||||
# pylint: disable=protected-access
|
||||
mock_path = "certbot_nginx.display_ops.select_vhost_multiple"
|
||||
with mock.patch(mock_path) as mock_select_vhs:
|
||||
vhost = [x for x in self.config.parser.get_vhosts()
|
||||
if 'summer.com' in x.names][0]
|
||||
mock_select_vhs.return_value = [vhost]
|
||||
vhs = self.config._choose_vhosts_wildcard("*.com",
|
||||
prefer_ssl=True)
|
||||
# Check that the dialog was called with migration.com
|
||||
self.assertTrue(vhost in mock_select_vhs.call_args[0][0])
|
||||
|
||||
# And the actual returned values
|
||||
self.assertEquals(len(vhs), 1)
|
||||
self.assertEqual(vhs[0], vhost)
|
||||
|
||||
def test_choose_vhosts_wildcard_redirect(self):
|
||||
# pylint: disable=protected-access
|
||||
mock_path = "certbot_nginx.display_ops.select_vhost_multiple"
|
||||
with mock.patch(mock_path) as mock_select_vhs:
|
||||
vhost = [x for x in self.config.parser.get_vhosts()
|
||||
if 'summer.com' in x.names][0]
|
||||
mock_select_vhs.return_value = [vhost]
|
||||
vhs = self.config._choose_vhosts_wildcard("*.com",
|
||||
prefer_ssl=False)
|
||||
# Check that the dialog was called with migration.com
|
||||
self.assertTrue(vhost in mock_select_vhs.call_args[0][0])
|
||||
|
||||
# And the actual returned values
|
||||
self.assertEquals(len(vhs), 1)
|
||||
self.assertEqual(vhs[0], vhost)
|
||||
|
||||
def test_deploy_cert_wildcard(self):
|
||||
# pylint: disable=protected-access
|
||||
mock_choose_vhosts = mock.MagicMock()
|
||||
vhost = [x for x in self.config.parser.get_vhosts()
|
||||
if 'geese.com' in x.names][0]
|
||||
mock_choose_vhosts.return_value = [vhost]
|
||||
self.config._choose_vhosts_wildcard = mock_choose_vhosts
|
||||
mock_d = "certbot_nginx.configurator.NginxConfigurator._deploy_cert"
|
||||
with mock.patch(mock_d) as mock_dep:
|
||||
self.config.deploy_cert("*.com", "/tmp/path",
|
||||
"/tmp/path", "/tmp/path", "/tmp/path")
|
||||
self.assertTrue(mock_dep.called)
|
||||
self.assertEquals(len(mock_dep.call_args_list), 1)
|
||||
self.assertEqual(vhost, mock_dep.call_args_list[0][0][0])
|
||||
|
||||
@mock.patch("certbot_nginx.display_ops.select_vhost_multiple")
|
||||
def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog):
|
||||
# pylint: disable=protected-access
|
||||
mock_dialog.return_value = []
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.config.deploy_cert,
|
||||
"*.wild.cat", "/tmp/path", "/tmp/path",
|
||||
"/tmp/path", "/tmp/path")
|
||||
|
||||
@mock.patch("certbot_nginx.display_ops.select_vhost_multiple")
|
||||
def test_enhance_wildcard_ocsp_after_install(self, mock_dialog):
|
||||
# pylint: disable=protected-access
|
||||
vhost = [x for x in self.config.parser.get_vhosts()
|
||||
if 'geese.com' in x.names][0]
|
||||
self.config._wildcard_vhosts["*.com"] = [vhost]
|
||||
self.config.enhance("*.com", "staple-ocsp", "example/chain.pem")
|
||||
self.assertFalse(mock_dialog.called)
|
||||
|
||||
@mock.patch("certbot_nginx.display_ops.select_vhost_multiple")
|
||||
def test_enhance_wildcard_redirect_or_ocsp_no_install(self, mock_dialog):
|
||||
vhost = [x for x in self.config.parser.get_vhosts()
|
||||
if 'summer.com' in x.names][0]
|
||||
mock_dialog.return_value = [vhost]
|
||||
self.config.enhance("*.com", "staple-ocsp", "example/chain.pem")
|
||||
self.assertTrue(mock_dialog.called)
|
||||
|
||||
@mock.patch("certbot_nginx.display_ops.select_vhost_multiple")
|
||||
def test_enhance_wildcard_double_redirect(self, mock_dialog):
|
||||
# pylint: disable=protected-access
|
||||
vhost = [x for x in self.config.parser.get_vhosts()
|
||||
if 'summer.com' in x.names][0]
|
||||
self.config._wildcard_redirect_vhosts["*.com"] = [vhost]
|
||||
self.config.enhance("*.com", "redirect")
|
||||
self.assertFalse(mock_dialog.called)
|
||||
|
||||
def test_choose_vhosts_wildcard_no_ssl_filter_port(self):
|
||||
# pylint: disable=protected-access
|
||||
mock_path = "certbot_nginx.display_ops.select_vhost_multiple"
|
||||
with mock.patch(mock_path) as mock_select_vhs:
|
||||
mock_select_vhs.return_value = []
|
||||
self.config._choose_vhosts_wildcard("*.com",
|
||||
prefer_ssl=False,
|
||||
no_ssl_filter_port='80')
|
||||
# Check that the dialog was called with only port 80 vhosts
|
||||
self.assertEqual(len(mock_select_vhs.call_args[0][0]), 4)
|
||||
|
||||
|
||||
class InstallSslOptionsConfTest(util.NginxTest):
|
||||
"""Test that the options-ssl-nginx.conf file is installed and updated properly."""
|
||||
|
||||
|
|
|
|||
45
certbot-nginx/certbot_nginx/tests/display_ops_test.py
Normal file
45
certbot-nginx/certbot_nginx/tests/display_ops_test.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Test certbot_apache.display_ops."""
|
||||
import unittest
|
||||
|
||||
from certbot.display import util as display_util
|
||||
|
||||
from certbot.tests import util as certbot_util
|
||||
|
||||
from certbot_nginx import parser
|
||||
|
||||
from certbot_nginx.display_ops import select_vhost_multiple
|
||||
from certbot_nginx.tests import util
|
||||
|
||||
|
||||
class SelectVhostMultiTest(util.NginxTest):
|
||||
"""Tests for certbot_nginx.display_ops.select_vhost_multiple."""
|
||||
|
||||
def setUp(self):
|
||||
super(SelectVhostMultiTest, self).setUp()
|
||||
nparser = parser.NginxParser(self.config_path)
|
||||
self.vhosts = nparser.get_vhosts()
|
||||
|
||||
def test_select_no_input(self):
|
||||
self.assertFalse(select_vhost_multiple([]))
|
||||
|
||||
@certbot_util.patch_get_utility()
|
||||
def test_select_correct(self, mock_util):
|
||||
mock_util().checklist.return_value = (
|
||||
display_util.OK, [self.vhosts[3].display_repr(),
|
||||
self.vhosts[2].display_repr()])
|
||||
vhs = select_vhost_multiple([self.vhosts[3],
|
||||
self.vhosts[2],
|
||||
self.vhosts[1]])
|
||||
self.assertTrue(self.vhosts[2] in vhs)
|
||||
self.assertTrue(self.vhosts[3] in vhs)
|
||||
self.assertFalse(self.vhosts[1] in vhs)
|
||||
|
||||
@certbot_util.patch_get_utility()
|
||||
def test_select_cancel(self, mock_util):
|
||||
mock_util().checklist.return_value = (display_util.CANCEL, "whatever")
|
||||
vhs = select_vhost_multiple([self.vhosts[2], self.vhosts[3]])
|
||||
self.assertFalse(vhs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl ipv6only=on;
|
||||
listen 5001 ssl;
|
||||
listen [::]:5001 ssl ipv6only=on;
|
||||
server_name ipv6ssl.com;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,10 +61,10 @@ class TlsSniPerformTest(util.NginxTest):
|
|||
shutil.rmtree(self.work_dir)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator"
|
||||
".NginxConfigurator.choose_vhost")
|
||||
".NginxConfigurator.choose_vhosts")
|
||||
def test_perform(self, mock_choose):
|
||||
self.sni.add_chall(self.achalls[1])
|
||||
mock_choose.return_value = None
|
||||
mock_choose.return_value = []
|
||||
result = self.sni.perform()
|
||||
self.assertFalse(result is None)
|
||||
|
||||
|
|
|
|||
|
|
@ -55,10 +55,11 @@ class NginxTlsSni01(common.TLSSNI01):
|
|||
self.configurator.config.tls_sni_01_port)
|
||||
|
||||
for achall in self.achalls:
|
||||
vhost = self.configurator.choose_vhost(achall.domain, create_if_no_match=True)
|
||||
vhosts = self.configurator.choose_vhosts(achall.domain, create_if_no_match=True)
|
||||
|
||||
if vhost is not None and vhost.addrs:
|
||||
addresses.append(list(vhost.addrs))
|
||||
# len is max 1 because Nginx doesn't authenticate wildcards
|
||||
if vhosts and vhosts[0].addrs:
|
||||
addresses.append(list(vhosts[0].addrs))
|
||||
else:
|
||||
if ipv6:
|
||||
# If IPv6 is active in Nginx configuration
|
||||
|
|
|
|||
2
certbot-nginx/local-oldest-requirements.txt
Normal file
2
certbot-nginx/local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
-e acme[dev]
|
||||
-e .[dev]
|
||||
|
|
@ -6,10 +6,14 @@ from setuptools import find_packages
|
|||
|
||||
version = '0.22.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
'certbot=={0}'.format(version),
|
||||
# This plugin works with an older version of acme, but Certbot does not.
|
||||
# 0.22.0 is specified here to work around
|
||||
# https://github.com/pypa/pip/issues/988.
|
||||
'acme>0.21.1',
|
||||
'certbot>0.21.1',
|
||||
'mock',
|
||||
'PyOpenSSL',
|
||||
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import zope.component
|
|||
|
||||
from acme import client as acme_client
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
from acme import errors as acme_errors
|
||||
from acme import messages
|
||||
|
||||
import certbot
|
||||
|
|
@ -243,7 +244,7 @@ class Client(object):
|
|||
than `authkey`.
|
||||
:param acme.messages.OrderResource orderr: contains authzrs
|
||||
|
||||
:returns: certificate and chain as PEM strings
|
||||
:returns: certificate and chain as PEM byte strings
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
|
|
@ -258,14 +259,12 @@ class Client(object):
|
|||
logger.debug("CSR: %s", csr)
|
||||
|
||||
if orderr is None:
|
||||
orderr = self.acme.new_order(csr.data)
|
||||
authzr = self.auth_handler.handle_authorizations(orderr)
|
||||
orderr = orderr.update(authorizations=authzr)
|
||||
authzr = orderr.authorizations
|
||||
orderr = self._get_order_and_authorizations(csr.data, best_effort=False)
|
||||
|
||||
deadline = datetime.datetime.now() + datetime.timedelta(seconds=90)
|
||||
orderr = self.acme.finalize_order(orderr, deadline)
|
||||
return crypto_util.cert_and_chain_from_fullchain(orderr.fullchain_pem)
|
||||
cert, chain = crypto_util.cert_and_chain_from_fullchain(orderr.fullchain_pem)
|
||||
return cert.encode(), chain.encode()
|
||||
|
||||
def obtain_certificate(self, domains):
|
||||
"""Obtains a certificate from the ACME server.
|
||||
|
|
@ -292,9 +291,8 @@ class Client(object):
|
|||
self.config.rsa_key_size, self.config.key_dir)
|
||||
csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir)
|
||||
|
||||
orderr = self.acme.new_order(csr.data)
|
||||
authzr = self.auth_handler.handle_authorizations(orderr, self.config.allow_subset_of_names)
|
||||
orderr = orderr.update(authorizations=authzr)
|
||||
orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)
|
||||
authzr = orderr.authorizations
|
||||
auth_domains = set(a.body.identifier.value for a in authzr)
|
||||
successful_domains = [d for d in domains if d in auth_domains]
|
||||
|
||||
|
|
@ -313,6 +311,25 @@ class Client(object):
|
|||
|
||||
return cert, chain, key, csr
|
||||
|
||||
def _get_order_and_authorizations(self, csr_pem, best_effort):
|
||||
"""Request a new order and complete its authorizations.
|
||||
|
||||
:param str csr_pem: A CSR in PEM format.
|
||||
:param bool best_effort: True if failing to complete all
|
||||
authorizations should not raise an exception
|
||||
|
||||
:returns: order resource containing its completed authorizations
|
||||
:rtype: acme.messages.OrderResource
|
||||
|
||||
"""
|
||||
try:
|
||||
orderr = self.acme.new_order(csr_pem)
|
||||
except acme_errors.WildcardUnsupportedError:
|
||||
raise errors.Error("The currently selected ACME CA endpoint does"
|
||||
" not support issuing wildcard certificates.")
|
||||
authzr = self.auth_handler.handle_authorizations(orderr, best_effort)
|
||||
return orderr.update(authorizations=authzr)
|
||||
|
||||
# pylint: disable=no-member
|
||||
def obtain_and_enroll_certificate(self, domains, certname):
|
||||
"""Obtain and enroll certificate.
|
||||
|
|
@ -338,7 +355,14 @@ class Client(object):
|
|||
"Non-standard path(s), might not work with crontab installed "
|
||||
"by your operating system package manager")
|
||||
|
||||
new_name = certname if certname else domains[0]
|
||||
if certname:
|
||||
new_name = certname
|
||||
elif util.is_wildcard_domain(domains[0]):
|
||||
# Don't make files and directories starting with *.
|
||||
new_name = domains[0][2:]
|
||||
else:
|
||||
new_name = domains[0]
|
||||
|
||||
if self.config.dry_run:
|
||||
logger.debug("Dry run: Skipping creating new lineage for %s",
|
||||
new_name)
|
||||
|
|
|
|||
|
|
@ -441,8 +441,9 @@ def cert_and_chain_from_fullchain(fullchain_pem):
|
|||
|
||||
:returns: tuple of string cert_pem and chain_pem
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, fullchain_pem))
|
||||
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, fullchain_pem)).decode()
|
||||
chain = fullchain_pem[len(cert):]
|
||||
return (cert, chain)
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ class ClientTest(ClientTestCommon):
|
|||
self.eg_domains = ["example.com", "www.example.com"]
|
||||
self.eg_order = mock.MagicMock(
|
||||
authorizations=[None],
|
||||
fullchain_pem=mock.sentinel.fullchain_pem,
|
||||
csr_pem=mock.sentinel.csr_pem)
|
||||
|
||||
def test_init_acme_verify_ssl(self):
|
||||
|
|
@ -165,8 +164,7 @@ class ClientTest(ClientTestCommon):
|
|||
self._mock_obtain_certificate()
|
||||
test_csr = util.CSR(form="pem", file=None, data=CSR_SAN)
|
||||
auth_handler = self.client.auth_handler
|
||||
mock_crypto_util.cert_and_chain_from_fullchain.return_value = (mock.sentinel.cert,
|
||||
mock.sentinel.chain)
|
||||
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
|
||||
|
||||
orderr = self.acme.new_order(test_csr.data)
|
||||
auth_handler.handle_authorizations(orderr, False)
|
||||
|
|
@ -184,7 +182,7 @@ class ClientTest(ClientTestCommon):
|
|||
self.client.obtain_certificate_from_csr(
|
||||
test_csr,
|
||||
orderr=None))
|
||||
auth_handler.handle_authorizations.assert_called_with(self.eg_order)
|
||||
auth_handler.handle_authorizations.assert_called_with(self.eg_order, False)
|
||||
|
||||
# Test for no auth_handler
|
||||
self.client.auth_handler = None
|
||||
|
|
@ -199,8 +197,7 @@ class ClientTest(ClientTestCommon):
|
|||
csr = util.CSR(form="pem", file=None, data=CSR_SAN)
|
||||
mock_crypto_util.init_save_csr.return_value = csr
|
||||
mock_crypto_util.init_save_key.return_value = mock.sentinel.key
|
||||
mock_crypto_util.cert_and_chain_from_fullchain.return_value = (mock.sentinel.cert,
|
||||
mock.sentinel.chain)
|
||||
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
|
||||
|
||||
self._test_obtain_certificate_common(mock.sentinel.key, csr)
|
||||
|
||||
|
|
@ -209,7 +206,7 @@ class ClientTest(ClientTestCommon):
|
|||
mock_crypto_util.init_save_csr.assert_called_once_with(
|
||||
mock.sentinel.key, self.eg_domains, self.config.csr_dir)
|
||||
mock_crypto_util.cert_and_chain_from_fullchain.assert_called_once_with(
|
||||
mock.sentinel.fullchain_pem)
|
||||
self.eg_order.fullchain_pem)
|
||||
|
||||
@mock.patch("certbot.client.crypto_util")
|
||||
@mock.patch("os.remove")
|
||||
|
|
@ -218,8 +215,7 @@ class ClientTest(ClientTestCommon):
|
|||
key = util.CSR(form="pem", file=mock.sentinel.key_file, data=CSR_SAN)
|
||||
mock_crypto_util.init_save_csr.return_value = csr
|
||||
mock_crypto_util.init_save_key.return_value = key
|
||||
mock_crypto_util.cert_and_chain_from_fullchain.return_value = (mock.sentinel.cert,
|
||||
mock.sentinel.chain)
|
||||
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
|
||||
|
||||
authzr = self._authzr_from_domains(["example.com"])
|
||||
self.config.allow_subset_of_names = True
|
||||
|
|
@ -237,8 +233,7 @@ class ClientTest(ClientTestCommon):
|
|||
mock_acme_crypto.make_csr.return_value = CSR_SAN
|
||||
mock_crypto.make_key.return_value = mock.sentinel.key_pem
|
||||
key = util.Key(file=None, pem=mock.sentinel.key_pem)
|
||||
mock_crypto.cert_and_chain_from_fullchain.return_value = (mock.sentinel.cert,
|
||||
mock.sentinel.chain)
|
||||
self._set_mock_from_fullchain(mock_crypto.cert_and_chain_from_fullchain)
|
||||
|
||||
self.client.config.dry_run = True
|
||||
self._test_obtain_certificate_common(key, csr)
|
||||
|
|
@ -250,6 +245,13 @@ class ClientTest(ClientTestCommon):
|
|||
mock_crypto.init_save_csr.assert_not_called()
|
||||
self.assertEqual(mock_crypto.cert_and_chain_from_fullchain.call_count, 1)
|
||||
|
||||
def _set_mock_from_fullchain(self, mock_from_fullchain):
|
||||
mock_cert = mock.Mock()
|
||||
mock_cert.encode.return_value = mock.sentinel.cert
|
||||
mock_chain = mock.Mock()
|
||||
mock_chain.encode.return_value = mock.sentinel.chain
|
||||
mock_from_fullchain.return_value = (mock_cert, mock_chain)
|
||||
|
||||
def _authzr_from_domains(self, domains):
|
||||
authzr = []
|
||||
|
||||
|
|
@ -285,7 +287,7 @@ class ClientTest(ClientTestCommon):
|
|||
@mock.patch('certbot.storage.RenewableCert.new_lineage')
|
||||
def test_obtain_and_enroll_certificate(self,
|
||||
mock_storage, mock_obtain_certificate):
|
||||
domains = ["example.com", "www.example.com"]
|
||||
domains = ["*.example.com", "example.com"]
|
||||
mock_obtain_certificate.return_value = (mock.MagicMock(),
|
||||
mock.MagicMock(), mock.MagicMock(), None)
|
||||
|
||||
|
|
@ -293,12 +295,14 @@ class ClientTest(ClientTestCommon):
|
|||
self.assertTrue(self.client.obtain_and_enroll_certificate(domains, "example_cert"))
|
||||
|
||||
self.assertTrue(self.client.obtain_and_enroll_certificate(domains, None))
|
||||
self.assertTrue(self.client.obtain_and_enroll_certificate(domains[1:], None))
|
||||
|
||||
self.client.config.dry_run = True
|
||||
|
||||
self.assertFalse(self.client.obtain_and_enroll_certificate(domains, None))
|
||||
|
||||
self.assertTrue(mock_storage.call_count == 2)
|
||||
names = [call[0][0] for call in mock_storage.call_args_list]
|
||||
self.assertEqual(names, ["example_cert", "example.com", "example.com"])
|
||||
|
||||
@mock.patch("certbot.cli.helpful_parser")
|
||||
def test_save_certificate(self, mock_parser):
|
||||
|
|
|
|||
|
|
@ -377,8 +377,8 @@ class CertAndChainFromFullchainTest(unittest.TestCase):
|
|||
"""Tests for certbot.crypto_util.cert_and_chain_from_fullchain"""
|
||||
|
||||
def test_cert_and_chain_from_fullchain(self):
|
||||
cert_pem = CERT
|
||||
chain_pem = CERT + SS_CERT
|
||||
cert_pem = CERT.decode()
|
||||
chain_pem = cert_pem + SS_CERT.decode()
|
||||
fullchain_pem = cert_pem + chain_pem
|
||||
from certbot.crypto_util import cert_and_chain_from_fullchain
|
||||
cert_out, chain_out = cert_and_chain_from_fullchain(fullchain_pem)
|
||||
|
|
|
|||
1
local-oldest-requirements.txt
Normal file
1
local-oldest-requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
-e acme[dev]
|
||||
4
setup.py
4
setup.py
|
|
@ -34,7 +34,9 @@ version = meta['version']
|
|||
# specified here to avoid masking the more specific request requirements in
|
||||
# acme. See https://github.com/pypa/pip/issues/988 for more info.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
# Remember to update local-oldest-requirements.txt when changing the
|
||||
# minimum acme version.
|
||||
'acme>0.21.1',
|
||||
# We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but
|
||||
# saying so here causes a runtime error against our temporary fork of 0.9.3
|
||||
# in which we added 2.6 support (see #2243), so we relax the requirement.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,30 @@
|
|||
#!/bin/bash -e
|
||||
# pip installs packages using pinned package versions. If CERTBOT_OLDEST is set
|
||||
# to 1, a combination of tools/oldest_constraints.txt and
|
||||
# tools/dev_constraints.txt is used, otherwise, a combination of certbot-auto's
|
||||
# requirements file and tools/dev_constraints.txt is used. The other file
|
||||
# always takes precedence over tools/dev_constraints.txt.
|
||||
# to 1, a combination of tools/oldest_constraints.txt,
|
||||
# tools/dev_constraints.txt, and local-oldest-requirements.txt contained in the
|
||||
# top level of the package's directory is used, otherwise, a combination of
|
||||
# certbot-auto's requirements file and tools/dev_constraints.txt is used. The
|
||||
# other file always takes precedence over tools/dev_constraints.txt. If
|
||||
# CERTBOT_OLDEST is set, this script must be run with `-e <package-name>` and
|
||||
# no other arguments.
|
||||
|
||||
# get the root of the Certbot repo
|
||||
tools_dir=$(dirname $("$(dirname $0)/readlink.py" $0))
|
||||
dev_constraints="$tools_dir/dev_constraints.txt"
|
||||
merge_reqs="$tools_dir/merge_requirements.py"
|
||||
all_constraints=$(mktemp)
|
||||
test_constraints=$(mktemp)
|
||||
trap "rm -f $test_constraints" EXIT
|
||||
trap "rm -f $all_constraints $test_constraints" EXIT
|
||||
|
||||
if [ "$CERTBOT_OLDEST" = 1 ]; then
|
||||
if [ "$1" != "-e" -o "$#" -ne "2" ]; then
|
||||
echo "When CERTBOT_OLDEST is set, this script must be run with a single -e <path> argument."
|
||||
exit 1
|
||||
fi
|
||||
pkg_dir=$(echo $2 | cut -f1 -d\[) # remove any extras such as [dev]
|
||||
requirements="$pkg_dir/local-oldest-requirements.txt"
|
||||
# packages like acme don't have any local oldest requirements
|
||||
if [ ! -f "$requirements" ]; then
|
||||
unset requirements
|
||||
fi
|
||||
cp "$tools_dir/oldest_constraints.txt" "$test_constraints"
|
||||
else
|
||||
repo_root=$(dirname "$tools_dir")
|
||||
|
|
@ -20,7 +32,13 @@ else
|
|||
sed -n -e 's/^\([^[:space:]]*==[^[:space:]]*\).*$/\1/p' "$certbot_requirements" > "$test_constraints"
|
||||
fi
|
||||
|
||||
"$tools_dir/merge_requirements.py" "$tools_dir/dev_constraints.txt" \
|
||||
"$test_constraints" > "$all_constraints"
|
||||
|
||||
set -x
|
||||
|
||||
# install the requested packages using the pinned requirements as constraints
|
||||
pip install -q --constraint <("$merge_reqs" "$dev_constraints" "$test_constraints") "$@"
|
||||
if [ -n "$requirements" ]; then
|
||||
pip install -q --constraint "$all_constraints" --requirement "$requirements"
|
||||
fi
|
||||
pip install -q --constraint "$all_constraints" "$@"
|
||||
|
|
|
|||
53
tox.ini
53
tox.ini
|
|
@ -14,10 +14,7 @@ pip_install = {toxinidir}/tools/pip_install_editable.sh
|
|||
# before the script moves on to the next package. All dependencies are pinned
|
||||
# to a specific version for increased stability for developers.
|
||||
install_and_test = {toxinidir}/tools/install_and_test.sh
|
||||
all_packages =
|
||||
acme[dev] \
|
||||
.[dev] \
|
||||
certbot-apache \
|
||||
dns_packages =
|
||||
certbot-dns-cloudflare \
|
||||
certbot-dns-cloudxns \
|
||||
certbot-dns-digitalocean \
|
||||
|
|
@ -27,7 +24,12 @@ all_packages =
|
|||
certbot-dns-luadns \
|
||||
certbot-dns-nsone \
|
||||
certbot-dns-rfc2136 \
|
||||
certbot-dns-route53 \
|
||||
certbot-dns-route53
|
||||
all_packages =
|
||||
acme[dev] \
|
||||
.[dev] \
|
||||
certbot-apache \
|
||||
{[base]dns_packages} \
|
||||
certbot-nginx \
|
||||
letshelp-certbot
|
||||
install_packages =
|
||||
|
|
@ -70,6 +72,47 @@ setenv =
|
|||
passenv =
|
||||
{[testenv]passenv}
|
||||
|
||||
[testenv:py27-acme-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} acme[dev]
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-apache-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} certbot-apache
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-certbot-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} .[dev]
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-dns-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} {[base]dns_packages}
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-nginx-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} certbot-nginx
|
||||
python tests/lock_test.py
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27_install]
|
||||
basepython = python2.7
|
||||
commands =
|
||||
|
|
|
|||
Loading…
Reference in a new issue