diff --git a/.travis.yml b/.travis.yml
index 16b5700e5..6f964dbec 100644
--- a/.travis.yml
+++ b/.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)'
diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py
index 73f7f8f62..2b2133475 100644
--- a/acme/acme/crypto_util.py
+++ b/acme/acme/crypto_util.py
@@ -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 = [
diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py
index 147cd5a2a..75a908d4f 100644
--- a/acme/acme/crypto_util_test.py
+++ b/acme/acme/crypto_util_test.py
@@ -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
diff --git a/certbot/cli.py b/certbot/cli.py
index 5dbad3ed4..3bd565496 100644
--- a/certbot/cli.py
+++ b/certbot/cli.py
@@ -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
diff --git a/certbot/client.py b/certbot/client.py
index 0159d3946..ba31f8760 100644
--- a/certbot/client.py
+++ b/certbot/client.py
@@ -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)
diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py
index 3f2267af2..68e07e059 100644
--- a/certbot/crypto_util.py
+++ b/certbot/crypto_util.py
@@ -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.
diff --git a/certbot/le_util.py b/certbot/le_util.py
index f5148b949..020050f6d 100644
--- a/certbot/le_util.py
+++ b/certbot/le_util.py
@@ -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"))
diff --git a/certbot/main.py b/certbot/main.py
index fa5d43b72..4ef2e6ac8 100644
--- a/certbot/main.py
+++ b/certbot/main.py
@@ -681,9 +681,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)
diff --git a/certbot/plugins/webroot.py b/certbot/plugins/webroot.py
index c344954ae..624ee2ff4 100644
--- a/certbot/plugins/webroot.py
+++ b/certbot/plugins/webroot.py
@@ -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):
diff --git a/certbot/plugins/webroot_test.py b/certbot/plugins/webroot_test.py
index ab2a9e9a4..5d784a75c 100644
--- a/certbot/plugins/webroot_test.py
+++ b/certbot/plugins/webroot_test.py
@@ -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."""
diff --git a/certbot/reverter.py b/certbot/reverter.py
index fe6d9f24f..16ee5d8a4 100644
--- a/certbot/reverter.py
+++ b/certbot/reverter.py
@@ -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
diff --git a/certbot/storage.py b/certbot/storage.py
index c4bfb3e28..6c13eb844 100644
--- a/certbot/storage.py
+++ b/certbot/storage.py
@@ -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"]
diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py
index d7965a24e..4ae69e69d 100644
--- a/certbot/tests/cli_test.py
+++ b/certbot/tests/cli_test.py
@@ -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)
diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py
index 8ceefe8ae..8490efd9f 100644
--- a/certbot/tests/client_test.py
+++ b/certbot/tests/client_test.py
@@ -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,
diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py
index ff8d8142e..eeea0f4ab 100644
--- a/certbot/tests/crypto_util_test.py
+++ b/certbot/tests/crypto_util_test.py
@@ -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"""
diff --git a/certbot/tests/le_util_test.py b/certbot/tests/le_util_test.py
index b6da4525f..6e4eef0f1 100644
--- a/certbot/tests/le_util_test.py
+++ b/certbot/tests/le_util_test.py
@@ -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
diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py
index eda5ffb36..85234b76a 100644
--- a/certbot/tests/reverter_test.py
+++ b/certbot/tests/reverter_test.py
@@ -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")
diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py
index be626edc5..f19b7d89d 100644
--- a/certbot/tests/storage_test.py
+++ b/certbot/tests/storage_test.py
@@ -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
diff --git a/certbot/tests/testdata/csr-nonames.pem b/certbot/tests/testdata/csr-nonames.pem
new file mode 100644
index 000000000..abe1029ca
--- /dev/null
+++ b/certbot/tests/testdata/csr-nonames.pem
@@ -0,0 +1,8 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIH/MIGqAgEAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF
+AANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+
+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoAAwDQYJKoZIhvcNAQELBQAD
+QQBt9XLSZ9DGfWcGGaBUTCiSY7lWBegpNlCeo8pK3ydWmKpjcza+j7lF5paph2LH
+lKWVQ8+xwYMscGWK0NApHGco
+-----END CERTIFICATE REQUEST-----
diff --git a/docs/using.rst b/docs/using.rst
index b10532259..f9af07613 100644
--- a/docs/using.rst
+++ b/docs/using.rst
@@ -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**
diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto
index eb5561070..fee63c2f5 100755
--- a/letsencrypt-auto-source/letsencrypt-auto
+++ b/letsencrypt-auto-source/letsencrypt-auto
@@ -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.
diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template
index f1ed82c4c..43d8bc7e1 100755
--- a/letsencrypt-auto-source/letsencrypt-auto.template
+++ b/letsencrypt-auto-source/letsencrypt-auto.template
@@ -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.
diff --git a/letsencrypt-auto-source/pieces/fetch.py b/letsencrypt-auto-source/pieces/fetch.py
index ca3e94b80..d11f8da61 100644
--- a/letsencrypt-auto-source/pieces/fetch.py
+++ b/letsencrypt-auto-source/pieces/fetch.py
@@ -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
diff --git a/letsencrypt-auto-source/tests/auto_test.py b/letsencrypt-auto-source/tests/auto_test.py
index 3b7e8731b..7e131f4cf 100644
--- a/letsencrypt-auto-source/tests/auto_test.py
+++ b/letsencrypt-auto-source/tests/auto_test.py
@@ -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 = {'': 'letsencrypt/',
- 'letsencrypt/json': dumps({'releases': {'99.9.9': None}}),
+ resources = {'': 'certbot/',
+ '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 = {'': 'letsencrypt/',
- 'letsencrypt/json': dumps({'releases': {'99.9.9': None}})}
+ resources = {'': 'certbot/',
+ '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(
diff --git a/setup.py b/setup.py
index 4ee56576b..59da23de4 100644
--- a/setup.py
+++ b/setup.py
@@ -71,6 +71,7 @@ dev_extras = [
'nose',
'nosexcover',
'pep8',
+ 'pkginfo<=1.2.1',
'pylint==1.4.2', # upstream #248
'tox',
'twine',
diff --git a/tests/boulder-fetch.sh b/tests/boulder-fetch.sh
index 0d8a3de38..469c5cd80 100755
--- a/tests/boulder-fetch.sh
+++ b/tests/boulder-fetch.sh
@@ -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
diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh
index 201343525..323ea004b 100755
--- a/tests/boulder-integration.sh
+++ b/tests/boulder-integration.sh
@@ -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" \
diff --git a/tests/letstest/scripts/boulder_install.sh b/tests/letstest/scripts/boulder_install.sh
index b5ddf2c5b..7e298783f 100755
--- a/tests/letstest/scripts/boulder_install.sh
+++ b/tests/letstest/scripts/boulder_install.sh
@@ -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
diff --git a/tests/travis-integration.sh b/tests/travis-integration.sh
index 159a2ef80..b42617400 100755
--- a/tests/travis-integration.sh
+++ b/tests/travis-integration.sh
@@ -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