mirror of
https://github.com/certbot/certbot.git
synced 2026-06-08 08:12:15 -04:00
Merge branch 'master' into dialogerror
This commit is contained in:
commit
d1e9ffbe9a
29 changed files with 418 additions and 272 deletions
25
.travis.yml
25
.travis.yml
|
|
@ -4,26 +4,18 @@ cache:
|
|||
directories:
|
||||
- $HOME/.cache/pip
|
||||
|
||||
services:
|
||||
- rabbitmq
|
||||
- mariadb
|
||||
# apacheconftest
|
||||
#- apache2
|
||||
# This makes sure we get a host with docker-compose present.
|
||||
dist: trusty
|
||||
|
||||
# http://docs.travis-ci.com/user/ci-environment/#CI-environment-OS
|
||||
# gimme has to be kept in sync with Boulder's Go version setting in .travis.yml
|
||||
before_install:
|
||||
- 'dpkg -s libaugeas0'
|
||||
- '[ "xxx$BOULDER_INTEGRATION" = "xxx" ] || eval "$(gimme 1.5.1)"'
|
||||
|
||||
# using separate envs with different TOXENVs creates 4x1 Travis build
|
||||
# matrix, which allows us to clearly distinguish which component under
|
||||
# test has failed
|
||||
env:
|
||||
global:
|
||||
- GOPATH=/tmp/go
|
||||
- PATH=$GOPATH/bin:$PATH
|
||||
- GO15VENDOREXPERIMENT=1 # Fixes problems with vendor directories
|
||||
- BOULDERPATH=$PWD/boulder/
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
|
@ -93,7 +85,6 @@ addons:
|
|||
- boulder
|
||||
- boulder-mysql
|
||||
- boulder-rabbitmq
|
||||
mariadb: "10.0"
|
||||
apt:
|
||||
sources:
|
||||
- augeas
|
||||
|
|
@ -109,13 +100,11 @@ addons:
|
|||
# For certbot-nginx integration testing
|
||||
- nginx-light
|
||||
- openssl
|
||||
# For Boulder integration testing
|
||||
- rsyslog
|
||||
# for apacheconftest
|
||||
#- apache2
|
||||
#- libapache2-mod-wsgi
|
||||
#- libapache2-mod-macro
|
||||
#- sudo
|
||||
- apache2
|
||||
- libapache2-mod-wsgi
|
||||
- libapache2-mod-macro
|
||||
- sudo
|
||||
|
||||
install: "travis_retry pip install tox coveralls"
|
||||
script: 'travis_retry tox && ([ "xxx$BOULDER_INTEGRATION" = "xxx" ] || ./tests/travis-integration.sh)'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Crypto utilities."""
|
||||
import binascii
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
|
|
@ -203,7 +204,7 @@ def gen_ss_cert(key, domains, not_before=None,
|
|||
"""
|
||||
assert domains, "Must provide one or more hostnames for the cert."
|
||||
cert = OpenSSL.crypto.X509()
|
||||
cert.set_serial_number(1337)
|
||||
cert.set_serial_number(int(binascii.hexlify(OpenSSL.rand.bytes(16)), 16))
|
||||
cert.set_version(2)
|
||||
|
||||
extensions = [
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import unittest
|
|||
import six
|
||||
from six.moves import socketserver # pylint: disable=import-error
|
||||
|
||||
import OpenSSL
|
||||
|
||||
from acme import errors
|
||||
from acme import jose
|
||||
from acme import test_util
|
||||
|
|
@ -126,5 +128,23 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
|
|||
self._get_idn_names())
|
||||
|
||||
|
||||
class RandomSnTest(unittest.TestCase):
|
||||
"""Test for random certificate serial numbers."""
|
||||
|
||||
def setUp(self):
|
||||
self.cert_count = 5
|
||||
self.serial_num = []
|
||||
self.key = OpenSSL.crypto.PKey()
|
||||
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
|
||||
|
||||
def test_sn_collisions(self):
|
||||
from acme.crypto_util import gen_ss_cert
|
||||
|
||||
for _ in range(self.cert_count):
|
||||
cert = gen_ss_cert(self.key, ['dummy'], force_san=True)
|
||||
self.serial_num.append(cert.get_serial_number())
|
||||
self.assertTrue(len(set(self.serial_num)) > 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ import logging
|
|||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import configargparse
|
||||
import OpenSSL
|
||||
import six
|
||||
|
||||
import certbot
|
||||
|
|
@ -336,36 +334,41 @@ class HelpfulArgumentParser(object):
|
|||
|
||||
# Do any post-parsing homework here
|
||||
|
||||
if self.verb == "renew":
|
||||
parsed_args.noninteractive_mode = True
|
||||
|
||||
if parsed_args.staging or parsed_args.dry_run:
|
||||
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
|
||||
conflicts = ["--staging"] if parsed_args.staging else []
|
||||
conflicts += ["--dry-run"] if parsed_args.dry_run else []
|
||||
raise errors.Error("--server value conflicts with {0}".format(
|
||||
" and ".join(conflicts)))
|
||||
|
||||
parsed_args.server = constants.STAGING_URI
|
||||
|
||||
if parsed_args.dry_run:
|
||||
if self.verb not in ["certonly", "renew"]:
|
||||
raise errors.Error("--dry-run currently only works with the "
|
||||
"'certonly' or 'renew' subcommands (%r)" % self.verb)
|
||||
parsed_args.break_my_certs = parsed_args.staging = True
|
||||
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
|
||||
# The user has a prod account, but might not have a staging
|
||||
# one; we don't want to start trying to perform interactive registration
|
||||
parsed_args.tos = True
|
||||
parsed_args.register_unsafely_without_email = True
|
||||
self.set_test_server(parsed_args)
|
||||
|
||||
if parsed_args.csr:
|
||||
if parsed_args.allow_subset_of_names:
|
||||
raise errors.Error("--allow-subset-of-names "
|
||||
"cannot be used with --csr")
|
||||
self.handle_csr(parsed_args)
|
||||
|
||||
hooks.validate_hooks(parsed_args)
|
||||
|
||||
return parsed_args
|
||||
|
||||
def set_test_server(self, parsed_args):
|
||||
"""We have --staging/--dry-run; perform sanity check and set config.server"""
|
||||
|
||||
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
|
||||
conflicts = ["--staging"] if parsed_args.staging else []
|
||||
conflicts += ["--dry-run"] if parsed_args.dry_run else []
|
||||
raise errors.Error("--server value conflicts with {0}".format(
|
||||
" and ".join(conflicts)))
|
||||
|
||||
parsed_args.server = constants.STAGING_URI
|
||||
|
||||
if parsed_args.dry_run:
|
||||
if self.verb not in ["certonly", "renew"]:
|
||||
raise errors.Error("--dry-run currently only works with the "
|
||||
"'certonly' or 'renew' subcommands (%r)" % self.verb)
|
||||
parsed_args.break_my_certs = parsed_args.staging = True
|
||||
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
|
||||
# The user has a prod account, but might not have a staging
|
||||
# one; we don't want to start trying to perform interactive registration
|
||||
parsed_args.tos = True
|
||||
parsed_args.register_unsafely_without_email = True
|
||||
|
||||
def handle_csr(self, parsed_args):
|
||||
"""Process a --csr flag."""
|
||||
if parsed_args.verb != "certonly":
|
||||
|
|
@ -373,21 +376,11 @@ class HelpfulArgumentParser(object):
|
|||
"when obtaining a new or replacement "
|
||||
"via the certonly command. Please try the "
|
||||
"certonly command instead.")
|
||||
if parsed_args.allow_subset_of_names:
|
||||
raise errors.Error("--allow-subset-of-names cannot be used with --csr")
|
||||
|
||||
try:
|
||||
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der")
|
||||
typ = OpenSSL.crypto.FILETYPE_ASN1
|
||||
domains = crypto_util.get_sans_from_csr(csr.data, OpenSSL.crypto.FILETYPE_ASN1)
|
||||
except OpenSSL.crypto.Error:
|
||||
try:
|
||||
e1 = traceback.format_exc()
|
||||
typ = OpenSSL.crypto.FILETYPE_PEM
|
||||
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="pem")
|
||||
domains = crypto_util.get_sans_from_csr(csr.data, typ)
|
||||
except OpenSSL.crypto.Error:
|
||||
logger.debug("DER CSR parse error %s", e1)
|
||||
logger.debug("PEM CSR parse error %s", traceback.format_exc())
|
||||
raise errors.Error("Failed to parse CSR file: {0}".format(parsed_args.csr[0]))
|
||||
csrfile, contents = parsed_args.csr[0:2]
|
||||
typ, csr, domains = crypto_util.import_csr_file(csrfile, contents)
|
||||
|
||||
# This is not necessary for webroot to work, however,
|
||||
# obtain_certificate_from_csr requires parsed_args.domains to be set
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from certbot import interfaces
|
|||
from certbot import le_util
|
||||
from certbot import reverter
|
||||
from certbot import storage
|
||||
from certbot import cli
|
||||
|
||||
from certbot.display import ops as display_ops
|
||||
from certbot.display import enhancements
|
||||
|
|
@ -317,23 +318,30 @@ class Client(object):
|
|||
|
||||
cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)
|
||||
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
|
||||
|
||||
cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path)
|
||||
|
||||
try:
|
||||
cert_file.write(cert_pem)
|
||||
finally:
|
||||
cert_file.close()
|
||||
logger.info("Server issued certificate; certificate written to %s",
|
||||
act_cert_path)
|
||||
abs_cert_path)
|
||||
|
||||
cert_chain_abspath = None
|
||||
fullchain_abspath = None
|
||||
if chain_cert:
|
||||
if not chain_cert:
|
||||
return abs_cert_path, None, None
|
||||
else:
|
||||
chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert)
|
||||
cert_chain_abspath = _save_chain(chain_pem, chain_path)
|
||||
fullchain_abspath = _save_chain(cert_pem + chain_pem,
|
||||
fullchain_path)
|
||||
|
||||
return os.path.abspath(act_cert_path), cert_chain_abspath, fullchain_abspath
|
||||
chain_file, abs_chain_path =\
|
||||
_open_pem_file('chain_path', chain_path)
|
||||
fullchain_file, abs_fullchain_path =\
|
||||
_open_pem_file('fullchain_path', fullchain_path)
|
||||
|
||||
_save_chain(chain_pem, chain_file)
|
||||
_save_chain(cert_pem + chain_pem, fullchain_file)
|
||||
|
||||
return abs_cert_path, abs_chain_path, abs_fullchain_path
|
||||
|
||||
def deploy_certificate(self, domains, privkey_path,
|
||||
cert_path, chain_path, fullchain_path):
|
||||
|
|
@ -565,24 +573,35 @@ def view_config_changes(config, num=None):
|
|||
rev.recovery_routine()
|
||||
rev.view_config_changes(num)
|
||||
|
||||
def _open_pem_file(cli_arg_path, pem_path):
|
||||
"""Open a pem file.
|
||||
|
||||
def _save_chain(chain_pem, chain_path):
|
||||
If cli_arg_path was set by the client, open that.
|
||||
Otherwise, uniquify the file path.
|
||||
|
||||
:param str cli_arg_path: the cli arg name, e.g. cert_path
|
||||
:param str pem_path: the pem file path to open
|
||||
|
||||
:returns: a tuple of file object and its absolute file path
|
||||
|
||||
"""
|
||||
if cli.set_by_cli(cli_arg_path):
|
||||
return le_util.safe_open(pem_path, chmod=0o644),\
|
||||
os.path.abspath(pem_path)
|
||||
else:
|
||||
uniq = le_util.unique_file(pem_path, 0o644)
|
||||
return uniq[0], os.path.abspath(uniq[1])
|
||||
|
||||
def _save_chain(chain_pem, chain_file):
|
||||
"""Saves chain_pem at a unique path based on chain_path.
|
||||
|
||||
:param str chain_pem: certificate chain in PEM format
|
||||
:param str chain_path: candidate path for the cert chain
|
||||
|
||||
:returns: absolute path to saved cert chain
|
||||
:rtype: str
|
||||
:param str chain_file: chain file object
|
||||
|
||||
"""
|
||||
chain_file, act_chain_path = le_util.unique_file(chain_path, 0o644)
|
||||
try:
|
||||
chain_file.write(chain_pem)
|
||||
finally:
|
||||
chain_file.close()
|
||||
|
||||
logger.info("Cert chain written to %s", act_chain_path)
|
||||
|
||||
# This expects a valid chain file
|
||||
return os.path.abspath(act_chain_path)
|
||||
logger.info("Cert chain written to %s", chain_file.name)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"""
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
|
||||
import OpenSSL
|
||||
import pyrfc3339
|
||||
|
|
@ -179,6 +180,30 @@ def csr_matches_pubkey(csr, privkey):
|
|||
return False
|
||||
|
||||
|
||||
def import_csr_file(csrfile, data):
|
||||
"""Import a CSR file, which can be either PEM or DER.
|
||||
|
||||
:param str csrfile: CSR filename
|
||||
:param str data: contents of the CSR file
|
||||
|
||||
:returns: (`OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`,
|
||||
le_util.CSR object representing the CSR,
|
||||
list of domains requested in the CSR)
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
for form, typ in (("der", OpenSSL.crypto.FILETYPE_ASN1,),
|
||||
("pem", OpenSSL.crypto.FILETYPE_PEM,),):
|
||||
try:
|
||||
domains = get_names_from_csr(data, typ)
|
||||
except OpenSSL.crypto.Error:
|
||||
logger.debug("CSR parse error (form=%s, typ=%s):", form, typ)
|
||||
logger.debug(traceback.format_exc())
|
||||
continue
|
||||
return typ, le_util.CSR(file=csrfile, data=data, form=form), domains
|
||||
raise errors.Error("Failed to parse CSR file: {0}".format(csrfile))
|
||||
|
||||
|
||||
def make_key(bits):
|
||||
"""Generate PEM encoded RSA key.
|
||||
|
||||
|
|
@ -228,15 +253,20 @@ def pyopenssl_load_certificate(data):
|
|||
str(error) for error in openssl_errors)))
|
||||
|
||||
|
||||
def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
|
||||
typ=OpenSSL.crypto.FILETYPE_PEM):
|
||||
def _load_cert_or_req(cert_or_req_str, load_func,
|
||||
typ=OpenSSL.crypto.FILETYPE_PEM):
|
||||
try:
|
||||
cert_or_req = load_func(typ, cert_or_req_str)
|
||||
return load_func(typ, cert_or_req_str)
|
||||
except OpenSSL.crypto.Error as error:
|
||||
logger.exception(error)
|
||||
raise
|
||||
|
||||
|
||||
def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
|
||||
typ=OpenSSL.crypto.FILETYPE_PEM):
|
||||
# pylint: disable=protected-access
|
||||
return acme_crypto_util._pyopenssl_cert_or_req_san(cert_or_req)
|
||||
return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req(
|
||||
cert_or_req_str, load_func, typ))
|
||||
|
||||
|
||||
def get_sans_from_cert(cert, typ=OpenSSL.crypto.FILETYPE_PEM):
|
||||
|
|
@ -267,6 +297,25 @@ def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
|
|||
csr, OpenSSL.crypto.load_certificate_request, typ)
|
||||
|
||||
|
||||
def get_names_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
|
||||
"""Get a list of domains from a CSR, including the CN if it is set.
|
||||
|
||||
:param str csr: CSR (encoded).
|
||||
:param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`
|
||||
|
||||
:returns: A list of domain names.
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
loaded_csr = _load_cert_or_req(
|
||||
csr, OpenSSL.crypto.load_certificate_request, typ)
|
||||
# Use a set to avoid duplication with CN and Subject Alt Names
|
||||
domains = set(d for d in (loaded_csr.get_subject().CN,) if d is not None)
|
||||
# pylint: disable=protected-access
|
||||
domains.update(acme_crypto_util._pyopenssl_cert_or_req_san(loaded_csr))
|
||||
return list(domains)
|
||||
|
||||
|
||||
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
|
||||
"""Dump certificate chain into a bundle.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
"""Utilities for all Certbot."""
|
||||
import argparse
|
||||
import collections
|
||||
# distutils.version under virtualenv confuses pylint
|
||||
# For more info, see: https://github.com/PyCQA/pylint/issues/73
|
||||
import distutils.version # pylint: disable=import-error,no-name-in-module
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -151,7 +154,8 @@ def _unique_file(path, filename_pat, count, mode):
|
|||
while True:
|
||||
current_path = os.path.join(path, filename_pat(count))
|
||||
try:
|
||||
return safe_open(current_path, chmod=mode), current_path
|
||||
return safe_open(current_path, chmod=mode),\
|
||||
os.path.abspath(current_path)
|
||||
except OSError as err:
|
||||
# "File exists," is okay, try a different name.
|
||||
if err.errno != errno.EEXIST:
|
||||
|
|
@ -342,3 +346,17 @@ def enforce_domain_sanity(domain):
|
|||
if not fqdn.match(domain):
|
||||
raise errors.ConfigurationError("Requested domain {0} is not a FQDN".format(domain))
|
||||
return domain
|
||||
|
||||
|
||||
def get_strict_version(normalized):
|
||||
"""Converts a normalized version to a strict version.
|
||||
|
||||
:param str normalized: normalized version string
|
||||
|
||||
:returns: An equivalent strict version
|
||||
:rtype: distutils.version.StrictVersion
|
||||
|
||||
"""
|
||||
# strict version ending with "a" and a number designates a pre-release
|
||||
# pylint: disable=no-member
|
||||
return distutils.version.StrictVersion(normalized.replace(".dev", "a"))
|
||||
|
|
|
|||
|
|
@ -685,9 +685,6 @@ def main(cli_args=sys.argv[1:]):
|
|||
displayer = display_util.NoninteractiveDisplay(sys.stdout)
|
||||
elif config.text_mode:
|
||||
displayer = display_util.FileDisplay(sys.stdout)
|
||||
elif config.verb == "renew":
|
||||
config.noninteractive_mode = True
|
||||
displayer = display_util.NoninteractiveDisplay(sys.stdout)
|
||||
else:
|
||||
displayer = display_util.NcursesDisplay()
|
||||
zope.component.provideUtility(displayer)
|
||||
|
|
|
|||
|
|
@ -181,12 +181,8 @@ to serve all files under specified web root ({0})."""
|
|||
os.chown(self.full_roots[name], stat_path.st_uid,
|
||||
stat_path.st_gid)
|
||||
except OSError as exception:
|
||||
if exception.errno == errno.EACCES:
|
||||
logger.debug("Insufficient permissions to change owner and uid - ignoring")
|
||||
else:
|
||||
raise errors.PluginError(
|
||||
"Couldn't create root for {0} http-01 "
|
||||
"challenge responses: {1}", name, exception)
|
||||
logger.info("Unable to change owner and uid of webroot directory")
|
||||
logger.debug("Error was: %s", exception)
|
||||
|
||||
except OSError as exception:
|
||||
if exception.errno != errno.EEXIST:
|
||||
|
|
@ -235,17 +231,9 @@ to serve all files under specified web root ({0})."""
|
|||
logger.debug("All challenges cleaned up, removing %s",
|
||||
root_path)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.ENOTEMPTY:
|
||||
logger.debug("Challenges cleaned up but %s not empty",
|
||||
root_path)
|
||||
elif exc.errno == errno.EACCES:
|
||||
logger.debug("Challenges cleaned up but no permissions for %s",
|
||||
root_path)
|
||||
elif exc.errno == errno.ENOENT:
|
||||
logger.debug("Challenges cleaned up, %s does not exists",
|
||||
root_path)
|
||||
else:
|
||||
raise
|
||||
logger.info(
|
||||
"Unable to clean up challenge directory %s", root_path)
|
||||
logger.debug("Error was: %s", exc)
|
||||
|
||||
|
||||
class _WebrootMapAction(argparse.Action):
|
||||
|
|
|
|||
|
|
@ -138,15 +138,10 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
os.chmod(self.path, 0o700)
|
||||
|
||||
@mock.patch("certbot.plugins.webroot.os.chown")
|
||||
def test_failed_chown_eacces(self, mock_chown):
|
||||
def test_failed_chown(self, mock_chown):
|
||||
mock_chown.side_effect = OSError(errno.EACCES, "msg")
|
||||
self.auth.perform([self.achall]) # exception caught and logged
|
||||
|
||||
@mock.patch("certbot.plugins.webroot.os.chown")
|
||||
def test_failed_chown_not_eacces(self, mock_chown):
|
||||
mock_chown.side_effect = OSError()
|
||||
self.assertRaises(errors.PluginError, self.auth.perform, [])
|
||||
|
||||
def test_perform_permissions(self):
|
||||
self.auth.prepare()
|
||||
|
||||
|
|
@ -200,7 +195,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
os.rmdir(leftover_path)
|
||||
|
||||
@mock.patch('os.rmdir')
|
||||
def test_cleanup_permission_denied(self, mock_rmdir):
|
||||
def test_cleanup_failure(self, mock_rmdir):
|
||||
self.auth.prepare()
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
|
|
@ -212,32 +207,6 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
self.assertFalse(os.path.exists(self.validation_path))
|
||||
self.assertTrue(os.path.exists(self.root_challenge_path))
|
||||
|
||||
@mock.patch('os.rmdir')
|
||||
def test_cleanup_oserror(self, mock_rmdir):
|
||||
self.auth.prepare()
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
os_error = OSError()
|
||||
os_error.errno = errno.EPERM
|
||||
mock_rmdir.side_effect = os_error
|
||||
|
||||
self.assertRaises(OSError, self.auth.cleanup, [self.achall])
|
||||
self.assertFalse(os.path.exists(self.validation_path))
|
||||
self.assertTrue(os.path.exists(self.root_challenge_path))
|
||||
|
||||
@mock.patch('os.rmdir')
|
||||
def test_cleanup_file_not_exists(self, mock_rmdir):
|
||||
self.auth.prepare()
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
os_error = OSError()
|
||||
os_error.errno = errno.ENOENT
|
||||
mock_rmdir.side_effect = os_error
|
||||
|
||||
self.auth.cleanup([self.achall])
|
||||
self.assertFalse(os.path.exists(self.validation_path))
|
||||
self.assertTrue(os.path.exists(self.root_challenge_path))
|
||||
|
||||
|
||||
class WebrootActionTest(unittest.TestCase):
|
||||
"""Tests for webroot argparse actions."""
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import shutil
|
|||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
import zope.component
|
||||
|
||||
from certbot import constants
|
||||
|
|
@ -489,7 +490,7 @@ class Reverter(object):
|
|||
|
||||
if not os.path.exists(changes_since_path):
|
||||
logger.info("Rollback checkpoint is empty (no changes made?)")
|
||||
with open(self.config.changes_since_path) as f:
|
||||
with open(changes_since_path, 'w') as f:
|
||||
f.write("No changes\n")
|
||||
|
||||
# Add title to self.config.in_progress_dir CHANGES_SINCE
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import configobj
|
|||
import parsedatetime
|
||||
import pytz
|
||||
|
||||
import certbot
|
||||
from certbot import constants
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
|
|
@ -17,6 +18,7 @@ from certbot import le_util
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
ALL_FOUR = ("cert", "privkey", "chain", "fullchain")
|
||||
CURRENT_VERSION = le_util.get_strict_version(certbot.__version__)
|
||||
|
||||
|
||||
def config_with_defaults(config=None):
|
||||
|
|
@ -63,6 +65,7 @@ def write_renewal_config(o_filename, n_filename, target, relevant_data):
|
|||
|
||||
"""
|
||||
config = configobj.ConfigObj(o_filename)
|
||||
config["version"] = certbot.__version__
|
||||
for kind in ALL_FOUR:
|
||||
config[kind] = target[kind]
|
||||
|
||||
|
|
@ -259,6 +262,14 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
|||
"renewal config file {0} is missing a required "
|
||||
"file reference".format(self.configfile))
|
||||
|
||||
conf_version = self.configuration.get("version")
|
||||
if (conf_version is not None and
|
||||
le_util.get_strict_version(conf_version) > CURRENT_VERSION):
|
||||
logger.warning(
|
||||
"Attempting to parse the version %s renewal configuration "
|
||||
"file found at %s with version %s of Certbot. This might not "
|
||||
"work.", conf_version, config_filename, certbot.__version__)
|
||||
|
||||
self.cert = self.configuration["cert"]
|
||||
self.privkey = self.configuration["privkey"]
|
||||
self.chain = self.configuration["chain"]
|
||||
|
|
|
|||
|
|
@ -349,8 +349,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
['-d', '204.11.231.35'])
|
||||
|
||||
def test_csr_with_besteffort(self):
|
||||
args = ["--csr", CSR, "--allow-subset-of-names"]
|
||||
self.assertRaises(errors.Error, self._call, args)
|
||||
self.assertRaises(
|
||||
errors.Error, self._call,
|
||||
'certonly --csr {0} --allow-subset-of-names'.format(CSR).split())
|
||||
|
||||
def test_run_with_csr(self):
|
||||
# This is an error because you can only use --csr with certonly
|
||||
|
|
@ -361,6 +362,17 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
return
|
||||
assert False, "Expected supplying --csr to fail with default verb"
|
||||
|
||||
def test_csr_with_no_domains(self):
|
||||
self.assertRaises(
|
||||
errors.Error, self._call,
|
||||
'certonly --csr {0}'.format(
|
||||
test_util.vector_path('csr-nonames.pem')).split())
|
||||
|
||||
def test_csr_with_inconsistent_domains(self):
|
||||
self.assertRaises(
|
||||
errors.Error, self._call,
|
||||
'certonly -d example.org --csr {0}'.format(CSR).split())
|
||||
|
||||
def _get_argument_parser(self):
|
||||
plugins = disco.PluginsRegistry.find_all()
|
||||
return functools.partial(cli.prepare_and_parse_args, plugins)
|
||||
|
|
|
|||
|
|
@ -134,57 +134,39 @@ class ClientTest(unittest.TestCase):
|
|||
|
||||
self.acme.fetch_chain.assert_called_once_with(mock.sentinel.certr)
|
||||
|
||||
# FIXME move parts of this to test_cli.py...
|
||||
@mock.patch("certbot.client.logger")
|
||||
def test_obtain_certificate_from_csr(self, mock_logger):
|
||||
self._mock_obtain_certificate()
|
||||
from certbot import cli
|
||||
test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
|
||||
mock_parsed_args = mock.MagicMock()
|
||||
# The CLI should believe that this is a certonly request, because
|
||||
# a CSR would not be allowed with other kinds of requests!
|
||||
mock_parsed_args.verb = "certonly"
|
||||
with mock.patch("certbot.client.le_util.CSR") as mock_CSR:
|
||||
mock_CSR.return_value = test_csr
|
||||
mock_parsed_args.domains = self.eg_domains[:]
|
||||
mock_parser = mock.MagicMock(cli.HelpfulArgumentParser)
|
||||
cli.HelpfulArgumentParser.handle_csr(mock_parser, mock_parsed_args)
|
||||
auth_handler = self.client.auth_handler
|
||||
|
||||
# Now provoke an inconsistent domains error...
|
||||
mock_parsed_args.domains.append("hippopotamus.io")
|
||||
self.assertRaises(errors.ConfigurationError,
|
||||
cli.HelpfulArgumentParser.handle_csr, mock_parser, mock_parsed_args)
|
||||
|
||||
authzr = self.client.auth_handler.get_authorizations(self.eg_domains, False)
|
||||
|
||||
self.assertEqual(
|
||||
(mock.sentinel.certr, mock.sentinel.chain),
|
||||
self.client.obtain_certificate_from_csr(
|
||||
self.eg_domains,
|
||||
test_csr,
|
||||
authzr=authzr))
|
||||
# and that the cert was obtained correctly
|
||||
self._check_obtain_certificate()
|
||||
|
||||
# Test for authzr=None
|
||||
self.assertEqual(
|
||||
(mock.sentinel.certr, mock.sentinel.chain),
|
||||
self.client.obtain_certificate_from_csr(
|
||||
self.eg_domains,
|
||||
test_csr,
|
||||
authzr=None))
|
||||
|
||||
self.client.auth_handler.get_authorizations.assert_called_with(
|
||||
self.eg_domains)
|
||||
|
||||
# Test for no auth_handler
|
||||
self.client.auth_handler = None
|
||||
self.assertRaises(
|
||||
errors.Error,
|
||||
self.client.obtain_certificate_from_csr,
|
||||
authzr = auth_handler.get_authorizations(self.eg_domains, False)
|
||||
self.assertEqual(
|
||||
(mock.sentinel.certr, mock.sentinel.chain),
|
||||
self.client.obtain_certificate_from_csr(
|
||||
self.eg_domains,
|
||||
test_csr)
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
test_csr,
|
||||
authzr=authzr))
|
||||
# and that the cert was obtained correctly
|
||||
self._check_obtain_certificate()
|
||||
|
||||
# Test for authzr=None
|
||||
self.assertEqual(
|
||||
(mock.sentinel.certr, mock.sentinel.chain),
|
||||
self.client.obtain_certificate_from_csr(
|
||||
self.eg_domains,
|
||||
test_csr,
|
||||
authzr=None))
|
||||
auth_handler.get_authorizations.assert_called_with(self.eg_domains)
|
||||
|
||||
# Test for no auth_handler
|
||||
self.client.auth_handler = None
|
||||
self.assertRaises(
|
||||
errors.Error,
|
||||
self.client.obtain_certificate_from_csr,
|
||||
self.eg_domains,
|
||||
test_csr)
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
|
||||
@mock.patch("certbot.client.crypto_util")
|
||||
def test_obtain_certificate(self, mock_crypto_util):
|
||||
|
|
@ -221,7 +203,9 @@ class ClientTest(unittest.TestCase):
|
|||
mock.sentinel.key, domains, self.config.csr_dir)
|
||||
self._check_obtain_certificate()
|
||||
|
||||
def test_save_certificate(self):
|
||||
@mock.patch("certbot.cli.helpful_parser")
|
||||
def test_save_certificate(self, mock_parser):
|
||||
# pylint: disable=too-many-locals
|
||||
certs = ["matching_cert.pem", "cert.pem", "cert-san.pem"]
|
||||
tmp_path = tempfile.mkdtemp()
|
||||
os.chmod(tmp_path, 0o755) # TODO: really??
|
||||
|
|
@ -232,6 +216,10 @@ class ClientTest(unittest.TestCase):
|
|||
candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem")
|
||||
candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem")
|
||||
candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem")
|
||||
mock_parser.verb = "certonly"
|
||||
mock_parser.args = ["--cert-path", candidate_cert_path,
|
||||
"--chain-path", candidate_chain_path,
|
||||
"--fullchain-path", candidate_fullchain_path]
|
||||
|
||||
cert_path, chain_path, fullchain_path = self.client.save_certificate(
|
||||
certr, chain_cert, candidate_cert_path, candidate_chain_path,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import zope.component
|
|||
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import le_util
|
||||
from certbot.tests import test_util
|
||||
|
||||
|
||||
|
|
@ -159,6 +160,44 @@ class CSRMatchesPubkeyTest(unittest.TestCase):
|
|||
test_util.load_vector('csr.pem'), RSA256_KEY))
|
||||
|
||||
|
||||
class ImportCSRFileTest(unittest.TestCase):
|
||||
"""Tests for certbot.certbot_util.import_csr_file."""
|
||||
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from certbot.crypto_util import import_csr_file
|
||||
return import_csr_file(*args, **kwargs)
|
||||
|
||||
def test_der_csr(self):
|
||||
csrfile = test_util.vector_path('csr.der')
|
||||
data = test_util.load_vector('csr.der')
|
||||
|
||||
self.assertEqual(
|
||||
(OpenSSL.crypto.FILETYPE_ASN1,
|
||||
le_util.CSR(file=csrfile,
|
||||
data=data,
|
||||
form="der"),
|
||||
["example.com"],),
|
||||
self._call(csrfile, data))
|
||||
|
||||
def test_pem_csr(self):
|
||||
csrfile = test_util.vector_path('csr.pem')
|
||||
data = test_util.load_vector('csr.pem')
|
||||
|
||||
self.assertEqual(
|
||||
(OpenSSL.crypto.FILETYPE_PEM,
|
||||
le_util.CSR(file=csrfile,
|
||||
data=data,
|
||||
form="pem"),
|
||||
["example.com"],),
|
||||
self._call(csrfile, data))
|
||||
|
||||
def test_bad_csr(self):
|
||||
self.assertRaises(errors.Error, self._call,
|
||||
test_util.vector_path('cert.pem'),
|
||||
test_util.load_vector('cert.pem'))
|
||||
|
||||
|
||||
class MakeKeyTest(unittest.TestCase): # pylint: disable=too-few-public-methods
|
||||
"""Tests for certbot.crypto_util.make_key."""
|
||||
|
||||
|
|
@ -234,6 +273,36 @@ class GetSANsFromCSRTest(unittest.TestCase):
|
|||
[], self._call(test_util.load_vector('csr-nosans.pem')))
|
||||
|
||||
|
||||
class GetNamesFromCSRTest(unittest.TestCase):
|
||||
"""Tests for certbot.crypto_util.get_names_from_csr."""
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from certbot.crypto_util import get_names_from_csr
|
||||
return get_names_from_csr(*args, **kwargs)
|
||||
|
||||
def test_extract_one_san(self):
|
||||
self.assertEqual(['example.com'], self._call(
|
||||
test_util.load_vector('csr.pem')))
|
||||
|
||||
def test_extract_two_sans(self):
|
||||
self.assertEqual(set(('example.com', 'www.example.com',)), set(
|
||||
self._call(test_util.load_vector('csr-san.pem'))))
|
||||
|
||||
def test_extract_six_sans(self):
|
||||
self.assertEqual(
|
||||
set(self._call(test_util.load_vector('csr-6sans.pem'))),
|
||||
set(("example.com", "example.org", "example.net",
|
||||
"example.info", "subdomain.example.com",
|
||||
"other.subdomain.example.com",)))
|
||||
|
||||
def test_parse_non_csr(self):
|
||||
self.assertRaises(OpenSSL.crypto.Error, self._call, "hello there")
|
||||
|
||||
def test_parse_no_sans(self):
|
||||
self.assertEqual(["example.org"],
|
||||
self._call(test_util.load_vector('csr-nosans.pem')))
|
||||
|
||||
|
||||
class CertLoaderTest(unittest.TestCase):
|
||||
"""Tests for certbot.crypto_util.pyopenssl_load_certificate"""
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import unittest
|
|||
import mock
|
||||
import six
|
||||
|
||||
import certbot
|
||||
from certbot import errors
|
||||
|
||||
|
||||
|
|
@ -339,5 +340,32 @@ class EnforceDomainSanityTest(unittest.TestCase):
|
|||
u"eichh\u00f6rnchen.example.com")
|
||||
|
||||
|
||||
class GetStrictVersionTest(unittest.TestCase):
|
||||
"""Tests for certbot.le_util.get_strict_version."""
|
||||
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from certbot.le_util import get_strict_version
|
||||
return get_strict_version(*args, **kwargs)
|
||||
|
||||
def test_two_dev_versions(self):
|
||||
self.assertTrue(
|
||||
self._call("0.0.0.dev20151006") < self._call("0.0.0.dev20151008"))
|
||||
|
||||
def test_one_dev_one_release_version(self):
|
||||
self.assertTrue(self._call("1.0.0.dev0") < self._call("1.0.0"))
|
||||
self.assertTrue(self._call("1.0.0") < self._call("1.0.1.dev0"))
|
||||
|
||||
def test_two_release_versions(self):
|
||||
self.assertTrue(self._call("0.0.0") < self._call("0.0.1"))
|
||||
self.assertTrue(self._call("0.0.0") < self._call("0.1.0"))
|
||||
self.assertTrue(self._call("0.0.0") < self._call("1.0.0"))
|
||||
|
||||
def test_current_version(self):
|
||||
current_version = self._call(certbot.__version__)
|
||||
self.assertTrue(self._call("0.6.0") < current_version)
|
||||
self.assertTrue(current_version < self._call("99.99.99"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -34,6 +34,20 @@ class ReverterCheckpointLocalTest(unittest.TestCase):
|
|||
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
@mock.patch("certbot.reverter.Reverter._read_and_append")
|
||||
def test_no_change(self, mock_read):
|
||||
mock_read.side_effect = OSError("cannot even")
|
||||
try:
|
||||
self.reverter.add_to_checkpoint(self.sets[0], "save1")
|
||||
except OSError:
|
||||
pass
|
||||
self.reverter.finalize_checkpoint("blah")
|
||||
path = os.listdir(self.reverter.config.backup_dir)[0]
|
||||
no_change = os.path.join(self.reverter.config.backup_dir, path, "CHANGES_SINCE")
|
||||
with open(no_change, "r") as f:
|
||||
x = f.read()
|
||||
self.assertTrue("No changes" in x)
|
||||
|
||||
def test_basic_add_to_temp_checkpoint(self):
|
||||
# These shouldn't conflict even though they are both named config.txt
|
||||
self.reverter.add_to_temp_checkpoint(self.sets[0], "save1")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import configobj
|
|||
import mock
|
||||
import pytz
|
||||
|
||||
import certbot
|
||||
from certbot import configuration
|
||||
from certbot import errors
|
||||
from certbot.storage import ALL_FOUR
|
||||
|
|
@ -137,6 +138,28 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
self.assertRaises(errors.CertStorageError, storage.RenewableCert,
|
||||
config.filename, self.cli_config)
|
||||
|
||||
def test_no_renewal_version(self):
|
||||
from certbot import storage
|
||||
|
||||
self._write_out_ex_kinds()
|
||||
self.assertTrue("version" not in self.config)
|
||||
|
||||
with mock.patch("certbot.storage.logger") as mock_logger:
|
||||
storage.RenewableCert(self.config.filename, self.cli_config)
|
||||
self.assertFalse(mock_logger.warning.called)
|
||||
|
||||
def test_renewal_newer_version(self):
|
||||
from certbot import storage
|
||||
|
||||
self._write_out_ex_kinds()
|
||||
self.config["version"] = "99.99.99"
|
||||
self.config.write()
|
||||
|
||||
with mock.patch("certbot.storage.logger") as mock_logger:
|
||||
storage.RenewableCert(self.config.filename, self.cli_config)
|
||||
self.assertTrue(mock_logger.warning.called)
|
||||
self.assertTrue("version" in mock_logger.warning.call_args[0][0])
|
||||
|
||||
def test_consistent(self):
|
||||
# pylint: disable=too-many-statements,protected-access
|
||||
oldcert = self.test_rc.cert
|
||||
|
|
@ -760,11 +783,14 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
with open(temp2, "r") as f:
|
||||
content = f.read()
|
||||
# useful value was updated
|
||||
assert "useful = new_value" in content
|
||||
self.assertTrue("useful = new_value" in content)
|
||||
# associated comment was preserved
|
||||
assert "A useful value" in content
|
||||
self.assertTrue("A useful value" in content)
|
||||
# useless value was deleted
|
||||
assert "useless" not in content
|
||||
self.assertTrue("useless" not in content)
|
||||
# check version was stored
|
||||
self.assertTrue("version = {0}".format(certbot.__version__) in content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
8
certbot/tests/testdata/csr-nonames.pem
vendored
Normal file
8
certbot/tests/testdata/csr-nonames.pem
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIH/MIGqAgEAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF
|
||||
AANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+
|
||||
6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoAAwDQYJKoZIhvcNAQELBQAD
|
||||
QQBt9XLSZ9DGfWcGGaBUTCiSY7lWBegpNlCeo8pK3ydWmKpjcza+j7lF5paph2LH
|
||||
lKWVQ8+xwYMscGWK0NApHGco
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
|
|
@ -460,7 +460,7 @@ repo, if you have not already done so. Then run:
|
|||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo apt-get install certbot python-certbot-apache -t jessie-backports
|
||||
sudo apt-get install letsencrypt python-letsencrypt-apache -t jessie-backports
|
||||
|
||||
**Fedora**
|
||||
|
||||
|
|
|
|||
|
|
@ -425,7 +425,8 @@ BootstrapMac() {
|
|||
|
||||
$pkgcmd augeas
|
||||
$pkgcmd dialog
|
||||
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" ]; then
|
||||
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
|
||||
-o "$(which python)" = "/usr/bin/python" ]; then
|
||||
# We want to avoid using the system Python because it requires root to use pip.
|
||||
# python.org, MacPorts or HomeBrew Python installations should all be OK.
|
||||
echo "Installing python..."
|
||||
|
|
@ -531,6 +532,7 @@ if [ "$1" = "--le-auto-phase2" ]; then
|
|||
|
||||
echo "Installing Python packages..."
|
||||
TEMP_DIR=$(TempDir)
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
# There is no $ interpolation due to quotes on starting heredoc delimiter.
|
||||
# -------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
|
||||
|
|
@ -888,7 +890,6 @@ UNLIKELY_EOF
|
|||
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
|
||||
PIP_STATUS=$?
|
||||
set -e
|
||||
rm -rf "$TEMP_DIR"
|
||||
if [ "$PIP_STATUS" != 0 ]; then
|
||||
# Report error. (Otherwise, be quiet.)
|
||||
echo "Had a problem while installing Python packages:"
|
||||
|
|
@ -934,6 +935,7 @@ else
|
|||
|
||||
if [ "$NO_SELF_UPGRADE" != 1 ]; then
|
||||
TEMP_DIR=$(TempDir)
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
# ---------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
|
||||
"""Do downloading and JSON parsing without additional dependencies. ::
|
||||
|
|
@ -1006,7 +1008,7 @@ def latest_stable_version(get):
|
|||
"""Return the latest stable release of letsencrypt."""
|
||||
metadata = loads(get(
|
||||
environ.get('LE_AUTO_JSON_URL',
|
||||
'https://pypi.python.org/pypi/letsencrypt/json')))
|
||||
'https://pypi.python.org/pypi/certbot/json')))
|
||||
# metadata['info']['version'] actually returns the latest of any kind of
|
||||
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
|
||||
# The regex is a sufficient regex for picking out prereleases for most
|
||||
|
|
@ -1088,8 +1090,6 @@ UNLIKELY_EOF
|
|||
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
|
||||
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
|
||||
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
|
||||
# TODO: Clean up temp dir safely, even if it has quotes in its path.
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi # A newer version is available.
|
||||
fi # Self-upgrading is allowed.
|
||||
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ if [ "$1" = "--le-auto-phase2" ]; then
|
|||
|
||||
echo "Installing Python packages..."
|
||||
TEMP_DIR=$(TempDir)
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
# There is no $ interpolation due to quotes on starting heredoc delimiter.
|
||||
# -------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
|
||||
|
|
@ -245,7 +246,6 @@ UNLIKELY_EOF
|
|||
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
|
||||
PIP_STATUS=$?
|
||||
set -e
|
||||
rm -rf "$TEMP_DIR"
|
||||
if [ "$PIP_STATUS" != 0 ]; then
|
||||
# Report error. (Otherwise, be quiet.)
|
||||
echo "Had a problem while installing Python packages:"
|
||||
|
|
@ -291,6 +291,7 @@ else
|
|||
|
||||
if [ "$NO_SELF_UPGRADE" != 1 ]; then
|
||||
TEMP_DIR=$(TempDir)
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
# ---------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
|
||||
{{ fetch.py }}
|
||||
|
|
@ -319,8 +320,6 @@ UNLIKELY_EOF
|
|||
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
|
||||
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
|
||||
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
|
||||
# TODO: Clean up temp dir safely, even if it has quotes in its path.
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi # A newer version is available.
|
||||
fi # Self-upgrading is allowed.
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def latest_stable_version(get):
|
|||
"""Return the latest stable release of letsencrypt."""
|
||||
metadata = loads(get(
|
||||
environ.get('LE_AUTO_JSON_URL',
|
||||
'https://pypi.python.org/pypi/letsencrypt/json')))
|
||||
'https://pypi.python.org/pypi/certbot/json')))
|
||||
# metadata['info']['version'] actually returns the latest of any kind of
|
||||
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
|
||||
# The regex is a sufficient regex for picking out prereleases for most
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ def run_le_auto(venv_dir, base_url, **kwargs):
|
|||
d = dict(XDG_DATA_HOME=venv_dir,
|
||||
# URL to PyPI-style JSON that tell us the latest released version
|
||||
# of LE:
|
||||
LE_AUTO_JSON_URL=base_url + 'letsencrypt/json',
|
||||
LE_AUTO_JSON_URL=base_url + 'certbot/json',
|
||||
# URL to dir containing letsencrypt-auto and letsencrypt-auto.sig:
|
||||
LE_AUTO_DIR_TEMPLATE=base_url + '%s/',
|
||||
# The public key corresponding to signing.key:
|
||||
|
|
@ -258,7 +258,7 @@ class AutoTests(TestCase):
|
|||
with ephemeral_dir() as venv_dir:
|
||||
# This serves a PyPI page with a higher version, a GitHub-alike
|
||||
# with a corresponding le-auto script, and a matching signature.
|
||||
resources = {'letsencrypt/json': dumps({'releases': {'99.9.9': None}}),
|
||||
resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}),
|
||||
'v99.9.9/letsencrypt-auto': NEW_LE_AUTO,
|
||||
'v99.9.9/letsencrypt-auto.sig': NEW_LE_AUTO_SIG}
|
||||
with serving(resources) as base_url:
|
||||
|
|
@ -301,8 +301,8 @@ class AutoTests(TestCase):
|
|||
with ephemeral_dir() as venv_dir:
|
||||
# Serve an unrelated hash signed with the good key (easier than
|
||||
# making a bad key, and a mismatch is a mismatch):
|
||||
resources = {'': '<a href="letsencrypt/">letsencrypt/</a>',
|
||||
'letsencrypt/json': dumps({'releases': {'99.9.9': None}}),
|
||||
resources = {'': '<a href="certbot/">certbot/</a>',
|
||||
'certbot/json': dumps({'releases': {'99.9.9': None}}),
|
||||
'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'),
|
||||
'v99.9.9/letsencrypt-auto.sig': signed('something else')}
|
||||
with serving(resources) as base_url:
|
||||
|
|
@ -320,8 +320,8 @@ class AutoTests(TestCase):
|
|||
def test_pip_failure(self):
|
||||
"""Make sure pip stops us if there is a hash mismatch."""
|
||||
with ephemeral_dir() as venv_dir:
|
||||
resources = {'': '<a href="letsencrypt/">letsencrypt/</a>',
|
||||
'letsencrypt/json': dumps({'releases': {'99.9.9': None}})}
|
||||
resources = {'': '<a href="certbot/">certbot/</a>',
|
||||
'certbot/json': dumps({'releases': {'99.9.9': None}})}
|
||||
with serving(resources) as base_url:
|
||||
# Build a le-auto script embedding a bad requirements file:
|
||||
install_le_auto(
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -71,6 +71,7 @@ dev_extras = [
|
|||
'nose',
|
||||
'nosexcover',
|
||||
'pep8',
|
||||
'pkginfo<=1.2.1',
|
||||
'pylint==1.4.2', # upstream #248
|
||||
'tox',
|
||||
'twine',
|
||||
|
|
|
|||
|
|
@ -1,40 +1,10 @@
|
|||
#!/bin/bash
|
||||
# Download and run Boulder instance for integration testing
|
||||
|
||||
# ugh, go version output is like:
|
||||
# go version go1.4.2 linux/amd64
|
||||
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
|
||||
|
||||
# version comparison
|
||||
function verlte {
|
||||
#OS X doesn't support version sorting; emulate with sed
|
||||
if [ `uname` == 'Darwin' ]; then
|
||||
[ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \
|
||||
| sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ]
|
||||
else
|
||||
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
|
||||
fi
|
||||
}
|
||||
|
||||
if ! verlte 1.5 "$GOVER" ; then
|
||||
echo "We require go version 1.5 or later; you have... $GOVER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -xe
|
||||
|
||||
# `/...` avoids `no buildable Go source files` errors, for more info
|
||||
# see `go help packages`
|
||||
go get -d github.com/letsencrypt/boulder/...
|
||||
cd $GOPATH/src/github.com/letsencrypt/boulder
|
||||
# goose is needed for ./test/create_db.sh
|
||||
wget https://github.com/jsha/boulder-tools/raw/master/goose.gz && \
|
||||
mkdir $GOPATH/bin && \
|
||||
zcat goose.gz > $GOPATH/bin/goose && \
|
||||
chmod +x $GOPATH/bin/goose
|
||||
./test/create_db.sh
|
||||
go run cmd/rabbitmq-setup/main.go -server amqp://localhost
|
||||
# listenbuddy is needed for ./start.py
|
||||
go get github.com/jsha/listenbuddy
|
||||
cd -
|
||||
# Check out special branch until latest docker changes land in Boulder master.
|
||||
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
|
||||
cd $BOULDERPATH
|
||||
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
|
||||
docker-compose up -d
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
|
|||
common auth --csr "$CSR_PATH" \
|
||||
--cert-path "${root}/csr/cert.pem" \
|
||||
--chain-path "${root}/csr/chain.pem"
|
||||
openssl x509 -in "${root}/csr/0000_cert.pem" -text
|
||||
openssl x509 -in "${root}/csr/0000_chain.pem" -text
|
||||
openssl x509 -in "${root}/csr/cert.pem" -text
|
||||
openssl x509 -in "${root}/csr/chain.pem" -text
|
||||
|
||||
common --domains le3.wtf install \
|
||||
--cert-path "${root}/csr/cert.pem" \
|
||||
|
|
|
|||
|
|
@ -2,27 +2,8 @@
|
|||
|
||||
# >>>> only tested on Ubuntu 14.04LTS <<<<
|
||||
|
||||
# non-interactive install of mariadb and other dependencies
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
sudo debconf-set-selections <<< 'mariadb-server mysql-server/root_password password PASS'
|
||||
sudo debconf-set-selections <<< 'mariadb-server mysql-server/root_password_again password PASS'
|
||||
apt-get -y --no-upgrade install git make libltdl3-dev mariadb-server rabbitmq-server
|
||||
sudo mysql -uroot -pPASS -e "SET PASSWORD = PASSWORD(\'\');"
|
||||
|
||||
# install go
|
||||
wget https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
|
||||
tar xzvf go1.5.1.linux-amd64.tar.gz
|
||||
mkdir gocode
|
||||
echo "export GOROOT=/home/ubuntu/go \n\
|
||||
export GOPATH=/home/ubuntu/gocode\n\
|
||||
export PATH=/home/ubuntu/go/bin:/home/ubuntu/gocode/bin:$PATH" >> .bashrc
|
||||
|
||||
# install boulder and its go dependencies
|
||||
go get -d github.com/letsencrypt/boulder/...
|
||||
cd $GOPATH/src/github.com/letsencrypt/boulder
|
||||
wget https://github.com/jsha/boulder-tools/raw/master/goose.gz
|
||||
mkdir $GOPATH/bin
|
||||
zcat goose.gz > $GOPATH/bin/goose
|
||||
chmod +x $GOPATH/bin/goose
|
||||
./test/create_db.sh
|
||||
go get github.com/jsha/listenbuddy
|
||||
# Check out special branch until latest docker changes land in Boulder master.
|
||||
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
|
||||
cd $BOULDERPATH
|
||||
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
|
||||
docker-compose up -d
|
||||
|
|
|
|||
|
|
@ -6,14 +6,9 @@ set -o errexit
|
|||
|
||||
source .tox/$TOXENV/bin/activate
|
||||
|
||||
export CERTBOT_PATH=`pwd`
|
||||
until curl http://boulder:4000/directory 2>/dev/null; do
|
||||
echo waiting for boulder
|
||||
sleep 1
|
||||
done
|
||||
|
||||
cd $GOPATH/src/github.com/letsencrypt/boulder/
|
||||
|
||||
# boulder's integration-test.py has code that knows to start and wait for the
|
||||
# boulder processes to start reliably and then will run the certbot
|
||||
# boulder-interation.sh on its own. The --certbot flag says to run only the
|
||||
# certbot tests (instead of any other client tests it might run). We're
|
||||
# going to want to define a more robust interaction point between the boulder
|
||||
# and certbot tests, but that will be better built off of this.
|
||||
python test/integration-test.py --certbot
|
||||
./tests/boulder-integration.sh
|
||||
|
|
|
|||
Loading…
Reference in a new issue