mirror of
https://github.com/certbot/certbot.git
synced 2026-06-03 13:59:02 -04:00
Merge remote-tracking branch 'github/letsencrypt/master' into standalone2
This commit is contained in:
commit
73ae361559
17 changed files with 220 additions and 83 deletions
|
|
@ -21,6 +21,15 @@ env:
|
|||
- TOXENV=lint
|
||||
- TOXENV=cover
|
||||
|
||||
|
||||
# Only build pushes to the master branch, PRs, and branches beginning with
|
||||
# `test-`. This reduces the number of simultaneous Travis runs, which speeds
|
||||
# turnaround time on review since there is a cap of 5 simultaneous runs.
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^test-.*$/
|
||||
|
||||
sudo: false # containers
|
||||
addons:
|
||||
# make sure simplehttp simple verification works (custom /etc/hosts)
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ include CONTRIBUTING.md
|
|||
include LICENSE.txt
|
||||
include linter_plugin.py
|
||||
include letsencrypt/EULA
|
||||
recursive-include docs *
|
||||
recursive-include letsencrypt/tests/testdata *
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ class JSONDeSerializable(object):
|
|||
:rtype: str
|
||||
|
||||
"""
|
||||
return self.json_dumps(sort_keys=True, indent=4, separators=(',', ': '))
|
||||
return self.json_dumps(sort_keys=True, indent=4)
|
||||
|
||||
@classmethod
|
||||
def json_dump_default(cls, python_object):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"""Tests for acme.jose.interfaces."""
|
||||
import unittest
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class JSONDeSerializableTest(unittest.TestCase):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
|
@ -90,8 +92,9 @@ class JSONDeSerializableTest(unittest.TestCase):
|
|||
self.assertEqual('["foo1", "foo2"]', self.seq.json_dumps())
|
||||
|
||||
def test_json_dumps_pretty(self):
|
||||
self.assertEqual(
|
||||
self.seq.json_dumps_pretty(), '[\n "foo1",\n "foo2"\n]')
|
||||
filler = ' ' if six.PY2 else ''
|
||||
self.assertEqual(self.seq.json_dumps_pretty(),
|
||||
'[\n "foo1",{0}\n "foo2"\n]'.format(filler))
|
||||
|
||||
def test_json_dump_default(self):
|
||||
from acme.jose.interfaces import JSONDeSerializable
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"""JSON Web Key."""
|
||||
import abc
|
||||
import binascii
|
||||
import json
|
||||
import logging
|
||||
|
||||
import cryptography.exceptions
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
|
@ -27,6 +29,32 @@ class JWK(json_util.TypedJSONObjectWithFields):
|
|||
cryptography_key_types = ()
|
||||
"""Subclasses should override."""
|
||||
|
||||
required = NotImplemented
|
||||
"""Required members of public key's representation as defined by JWK/JWA."""
|
||||
|
||||
_thumbprint_json_dumps_params = {
|
||||
# "no whitespace or line breaks before or after any syntactic
|
||||
# elements"
|
||||
'indent': 0,
|
||||
'separators': (',', ':'),
|
||||
# "members ordered lexicographically by the Unicode [UNICODE]
|
||||
# code points of the member names"
|
||||
'sort_keys': True,
|
||||
}
|
||||
|
||||
def thumbprint(self, hash_function=hashes.SHA256):
|
||||
"""Compute JWK Thumbprint.
|
||||
|
||||
https://tools.ietf.org/html/rfc7638
|
||||
|
||||
"""
|
||||
digest = hashes.Hash(hash_function(), backend=default_backend())
|
||||
digest.update(json.dumps(
|
||||
dict((k, v) for k, v in six.iteritems(self.to_json())
|
||||
if k in self.required),
|
||||
**self._thumbprint_json_dumps_params).encode())
|
||||
return digest.finalize()
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self): # pragma: no cover
|
||||
"""Generate JWK with public key.
|
||||
|
|
@ -60,7 +88,7 @@ class JWK(json_util.TypedJSONObjectWithFields):
|
|||
exceptions[loader] = error
|
||||
|
||||
# no luck
|
||||
raise errors.Error("Unable to deserialize key: {0}".format(exceptions))
|
||||
raise errors.Error('Unable to deserialize key: {0}'.format(exceptions))
|
||||
|
||||
@classmethod
|
||||
def load(cls, data, password=None, backend=None):
|
||||
|
|
@ -81,17 +109,17 @@ class JWK(json_util.TypedJSONObjectWithFields):
|
|||
try:
|
||||
key = cls._load_cryptography_key(data, password, backend)
|
||||
except errors.Error as error:
|
||||
logger.debug("Loading symmetric key, assymentric failed: %s", error)
|
||||
logger.debug('Loading symmetric key, assymentric failed: %s', error)
|
||||
return JWKOct(key=data)
|
||||
|
||||
if cls.typ is not NotImplemented and not isinstance(
|
||||
key, cls.cryptography_key_types):
|
||||
raise errors.Error("Unable to deserialize {0} into {1}".format(
|
||||
raise errors.Error('Unable to deserialize {0} into {1}'.format(
|
||||
key.__class__, cls.__class__))
|
||||
for jwk_cls in six.itervalues(cls.TYPES):
|
||||
if isinstance(key, jwk_cls.cryptography_key_types):
|
||||
return jwk_cls(key=key)
|
||||
raise errors.Error("Unsupported algorithm: {0}".format(key.__class__))
|
||||
raise errors.Error('Unsupported algorithm: {0}'.format(key.__class__))
|
||||
|
||||
|
||||
@JWK.register
|
||||
|
|
@ -105,6 +133,7 @@ class JWKES(JWK): # pragma: no cover
|
|||
typ = 'ES'
|
||||
cryptography_key_types = (
|
||||
ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey)
|
||||
required = ('crv', JWK.type_field_name, 'x', 'y')
|
||||
|
||||
def fields_to_partial_json(self):
|
||||
raise NotImplementedError()
|
||||
|
|
@ -122,6 +151,7 @@ class JWKOct(JWK):
|
|||
"""Symmetric JWK."""
|
||||
typ = 'oct'
|
||||
__slots__ = ('key',)
|
||||
required = ('k', JWK.type_field_name)
|
||||
|
||||
def fields_to_partial_json(self):
|
||||
# TODO: An "alg" member SHOULD also be present to identify the
|
||||
|
|
@ -150,6 +180,7 @@ class JWKRSA(JWK):
|
|||
typ = 'RSA'
|
||||
cryptography_key_types = (rsa.RSAPublicKey, rsa.RSAPrivateKey)
|
||||
__slots__ = ('key',)
|
||||
required = ('e', JWK.type_field_name, 'n')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'key' in kwargs and not isinstance(
|
||||
|
|
@ -204,7 +235,7 @@ class JWKRSA(JWK):
|
|||
jobj.get(x) for x in ('p', 'q', 'dp', 'dq', 'qi'))
|
||||
if tuple(param for param in all_params if param is None):
|
||||
raise errors.Error(
|
||||
"Some private parameters are missing: {0}".format(
|
||||
'Some private parameters are missing: {0}'.format(
|
||||
all_params))
|
||||
p, q, dp, dq, qi = tuple(
|
||||
cls._decode_param(x) for x in all_params)
|
||||
|
|
|
|||
|
|
@ -25,9 +25,24 @@ class JWKTest(unittest.TestCase):
|
|||
self.assertRaises(errors.Error, JWKRSA.load, DSA_PEM)
|
||||
|
||||
|
||||
class JWKOctTest(unittest.TestCase):
|
||||
class JWKTestBaseMixin(object):
|
||||
"""Mixin test for JWK subclass tests."""
|
||||
|
||||
thumbprint = NotImplemented
|
||||
|
||||
def test_thumbprint_private(self):
|
||||
self.assertEqual(self.thumbprint, self.jwk.thumbprint())
|
||||
|
||||
def test_thumbprint_public(self):
|
||||
self.assertEqual(self.thumbprint, self.jwk.public_key().thumbprint())
|
||||
|
||||
|
||||
class JWKOctTest(unittest.TestCase, JWKTestBaseMixin):
|
||||
"""Tests for acme.jose.jwk.JWKOct."""
|
||||
|
||||
thumbprint = (b"=,\xdd;I\x1a+i\x02x\x8a\x12?06IM\xc2\x80"
|
||||
b"\xe4\xc3\x1a\xfc\x89\xf3)'\xce\xccm\xfd5")
|
||||
|
||||
def setUp(self):
|
||||
from acme.jose.jwk import JWKOct
|
||||
self.jwk = JWKOct(key=b'foo')
|
||||
|
|
@ -52,10 +67,13 @@ class JWKOctTest(unittest.TestCase):
|
|||
self.assertTrue(self.jwk.public_key() is self.jwk)
|
||||
|
||||
|
||||
class JWKRSATest(unittest.TestCase):
|
||||
class JWKRSATest(unittest.TestCase, JWKTestBaseMixin):
|
||||
"""Tests for acme.jose.jwk.JWKRSA."""
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
thumbprint = (b'\x08\xfa1\x87\x1d\x9b6H/*\x1eW\xc2\xe3\xf6P'
|
||||
b'\xefs\x0cKB\x87\xcf\x85yO\x045\x0e\x91\x80\x0b')
|
||||
|
||||
def setUp(self):
|
||||
from acme.jose.jwk import JWKRSA
|
||||
self.jwk256 = JWKRSA(key=RSA256_KEY.public_key())
|
||||
|
|
@ -87,6 +105,7 @@ class JWKRSATest(unittest.TestCase):
|
|||
'dq': 'bHh2u7etM8LKKCF2pY2UdQ',
|
||||
'qi': 'oi45cEkbVoJjAbnQpFY87Q',
|
||||
})
|
||||
self.jwk = self.private
|
||||
|
||||
def test_init_auto_comparable(self):
|
||||
self.assertTrue(isinstance(
|
||||
|
|
|
|||
|
|
@ -267,6 +267,22 @@ Please:
|
|||
.. _PEP 8 - Style Guide for Python Code:
|
||||
https://www.python.org/dev/peps/pep-0008
|
||||
|
||||
Submitting a pull request
|
||||
=========================
|
||||
|
||||
Steps:
|
||||
|
||||
1. Write your code!
|
||||
2. Make sure your environment is set up properly and that you're in your
|
||||
virtualenv. You can do this by running ``./bootstrap/dev/venv.sh``.
|
||||
(this is a **very important** step)
|
||||
3. Run ``./pep8.travis.sh`` to do a cursory check of your code style.
|
||||
Fix any errors.
|
||||
4. Run ``tox -e lint`` to check for pylint errors. Fix any errors.
|
||||
5. Run ``tox`` to run the entire test suite including coverage. Fix any errors.
|
||||
6. If your code touches communication with an ACME server/Boulder, you
|
||||
should run the integration tests, see `integration`_.
|
||||
7. Submit the PR.
|
||||
|
||||
Updating the documentation
|
||||
==========================
|
||||
|
|
|
|||
|
|
@ -1,33 +1,18 @@
|
|||
#!/bin/bash
|
||||
# An extremely simplified version of `a2enmod` for enabling modules in the
|
||||
# httpd docker image. First argument is the server_root and the second is the
|
||||
# module to be enabled.
|
||||
# httpd docker image. First argument is the Apache ServerRoot which should be
|
||||
# an absolute path. The second is the module to be enabled, such as `ssl`.
|
||||
|
||||
APACHE_CONFDIR=$1
|
||||
confdir=$1
|
||||
module=$2
|
||||
|
||||
enable () {
|
||||
echo "LoadModule "$1"_module /usr/local/apache2/modules/mod_"$1".so" >> \
|
||||
$APACHE_CONFDIR"/test.conf"
|
||||
available_base="/mods-available/"$1".conf"
|
||||
available_conf=$APACHE_CONFDIR$available_base
|
||||
enabled_dir=$APACHE_CONFDIR"/mods-enabled"
|
||||
enabled_conf=$enabled_dir"/"$1".conf"
|
||||
if [ -e "$available_conf" -a -d "$enabled_dir" -a ! -e "$enabled_conf" ]
|
||||
then
|
||||
ln -s "..$available_base" $enabled_conf
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $2 == "ssl" ]
|
||||
echo "LoadModule ${module}_module " \
|
||||
"/usr/local/apache2/modules/mod_${module}.so" >> "${confdir}/test.conf"
|
||||
availbase="/mods-available/${module}.conf"
|
||||
availconf=$confdir$availbase
|
||||
enabldir="$confdir/mods-enabled"
|
||||
enablconf="$enabldir/${module}.conf"
|
||||
if [ -e $availconf -a -d $enabldir -a ! -e $enablconf ]
|
||||
then
|
||||
# Enables ssl and all its dependencies
|
||||
enable "setenvif"
|
||||
enable "mime"
|
||||
enable "socache_shmcb"
|
||||
enable "ssl"
|
||||
elif [ $2 == "rewrite" ]
|
||||
then
|
||||
enable "rewrite"
|
||||
else
|
||||
exit 1
|
||||
ln -s "..$availbase" $enablconf
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ def create_le_config(parent_dir):
|
|||
|
||||
def extract_configs(configs, parent_dir):
|
||||
"""Extracts configs to a new dir under parent_dir and returns it"""
|
||||
config_dir = os.path.join(parent_dir, "renewal")
|
||||
config_dir = os.path.join(parent_dir, "configs")
|
||||
|
||||
if os.path.isdir(configs):
|
||||
shutil.copytree(configs, config_dir, symlinks=True)
|
||||
|
|
|
|||
|
|
@ -839,9 +839,23 @@ def _plugins_parsing(helpful, plugins):
|
|||
helpful.add_plugin_args(plugins)
|
||||
|
||||
|
||||
def _setup_logging(args):
|
||||
level = -args.verbose_count * 10
|
||||
fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s"
|
||||
def setup_log_file_handler(args, logfile, fmt):
|
||||
"""Setup file debug logging."""
|
||||
log_file_path = os.path.join(args.logs_dir, logfile)
|
||||
handler = logging.handlers.RotatingFileHandler(
|
||||
log_file_path, maxBytes=2 ** 20, backupCount=10)
|
||||
# rotate on each invocation, rollover only possible when maxBytes
|
||||
# is nonzero and backupCount is nonzero, so we set maxBytes as big
|
||||
# as possible not to overrun in single CLI invocation (1MB).
|
||||
handler.doRollover() # TODO: creates empty letsencrypt.log.1 file
|
||||
handler.setLevel(logging.DEBUG)
|
||||
handler_formatter = logging.Formatter(fmt=fmt)
|
||||
handler_formatter.converter = time.gmtime # don't use localtime
|
||||
handler.setFormatter(handler_formatter)
|
||||
return handler, log_file_path
|
||||
|
||||
|
||||
def _cli_log_handler(args, level, fmt):
|
||||
if args.text_mode:
|
||||
handler = colored_logging.StreamHandler()
|
||||
handler.setFormatter(logging.Formatter(fmt))
|
||||
|
|
@ -850,30 +864,26 @@ def _setup_logging(args):
|
|||
# dialog box is small, display as less as possible
|
||||
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||
handler.setLevel(level)
|
||||
return handler
|
||||
|
||||
|
||||
def setup_logging(args, cli_handler_factory, logfile):
|
||||
"""Setup logging."""
|
||||
fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s"
|
||||
level = -args.verbose_count * 10
|
||||
file_handler, log_file_path = setup_log_file_handler(
|
||||
args, logfile=logfile, fmt=fmt)
|
||||
cli_handler = cli_handler_factory(args, level, fmt)
|
||||
|
||||
# TODO: use fileConfig?
|
||||
|
||||
# unconditionally log to file for debugging purposes
|
||||
# TODO: change before release?
|
||||
log_file_name = os.path.join(args.logs_dir, 'letsencrypt.log')
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
log_file_name, maxBytes=2 ** 20, backupCount=10)
|
||||
# rotate on each invocation, rollover only possible when maxBytes
|
||||
# is nonzero and backupCount is nonzero, so we set maxBytes as big
|
||||
# as possible not to overrun in single CLI invocation (1MB).
|
||||
file_handler.doRollover() # TODO: creates empty letsencrypt.log.1 file
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
file_handler_formatter = logging.Formatter(fmt=fmt)
|
||||
file_handler_formatter.converter = time.gmtime # don't use localtime
|
||||
file_handler.setFormatter(file_handler_formatter)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.DEBUG) # send all records to handlers
|
||||
root_logger.addHandler(handler)
|
||||
root_logger.addHandler(cli_handler)
|
||||
root_logger.addHandler(file_handler)
|
||||
|
||||
logger.debug("Root logging level set at %d", level)
|
||||
logger.info("Saving debug log to %s", log_file_name)
|
||||
logger.info("Saving debug log to %s", log_file_path)
|
||||
|
||||
|
||||
def _handle_exception(exc_type, exc_value, trace, args):
|
||||
|
|
@ -942,7 +952,7 @@ def main(cli_args=sys.argv[1:]):
|
|||
# private key! #525
|
||||
le_util.make_or_verify_dir(
|
||||
args.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args)
|
||||
_setup_logging(args)
|
||||
setup_logging(args, _cli_log_handler, logfile='letsencrypt.log')
|
||||
|
||||
# do not log `args`, as it contains sensitive data (e.g. revoke --key)!
|
||||
logger.debug("Arguments: %r", cli_args)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ CLI_DEFAULTS = dict(
|
|||
|
||||
auth_cert_path="./cert.pem",
|
||||
auth_chain_path="./chain.pem",
|
||||
strict_permissions=False,
|
||||
)
|
||||
"""Defaults for CLI flags and `.IConfig` attributes."""
|
||||
|
||||
|
|
|
|||
|
|
@ -129,12 +129,18 @@ binary for temporary key/certificate generation.""".replace("\n", "")
|
|||
ct=response.CONTENT_TYPE, port=port)
|
||||
if self.conf("test-mode"):
|
||||
logger.debug("Test mode. Executing the manual command: %s", command)
|
||||
# sh shipped with OS X does't support echo -n
|
||||
if sys.platform == "darwin":
|
||||
executable = "/bin/bash"
|
||||
else:
|
||||
executable = None
|
||||
try:
|
||||
self._httpd = subprocess.Popen(
|
||||
command,
|
||||
# don't care about setting stdout and stderr,
|
||||
# we're in test mode anyway
|
||||
shell=True,
|
||||
executable=executable,
|
||||
# "preexec_fn" is UNIX specific, but so is "command"
|
||||
preexec_fn=os.setsid)
|
||||
except OSError as error: # ValueError should not happen!
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Plugin utilities."""
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import psutil
|
||||
|
|
@ -7,6 +8,9 @@ import zope.component
|
|||
from letsencrypt import interfaces
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def already_listening(port):
|
||||
"""Check if a process is already listening on the port.
|
||||
|
||||
|
|
@ -17,9 +21,20 @@ def already_listening(port):
|
|||
run as root.
|
||||
|
||||
:param int port: The TCP port in question.
|
||||
:returns: True or False."""
|
||||
:returns: True or False.
|
||||
|
||||
listeners = [conn.pid for conn in psutil.net_connections()
|
||||
"""
|
||||
try:
|
||||
net_connections = psutil.net_connections()
|
||||
except psutil.AccessDenied as error:
|
||||
logger.info("Access denied when trying to list network "
|
||||
"connections: %s. Are you root?", error)
|
||||
# this function is just a pre-check that often causes false
|
||||
# positives and problems in testing (c.f. #680 on Mac, #255
|
||||
# generally); we will fail later in bind() anyway
|
||||
return False
|
||||
|
||||
listeners = [conn.pid for conn in net_connections
|
||||
if conn.status == 'LISTEN' and
|
||||
conn.type == socket.SOCK_STREAM and
|
||||
conn.laddr[1] == port]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ within lineages of successor certificates, according to configuration.
|
|||
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
@ -17,10 +18,13 @@ import zope.component
|
|||
|
||||
from letsencrypt import account
|
||||
from letsencrypt import configuration
|
||||
from letsencrypt import constants
|
||||
from letsencrypt import colored_logging
|
||||
from letsencrypt import cli
|
||||
from letsencrypt import client
|
||||
from letsencrypt import crypto_util
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import le_util
|
||||
from letsencrypt import notify
|
||||
from letsencrypt import storage
|
||||
|
||||
|
|
@ -28,6 +32,9 @@ from letsencrypt.display import util as display_util
|
|||
from letsencrypt.plugins import disco as plugins_disco
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _AttrDict(dict):
|
||||
"""Attribute dictionary.
|
||||
|
||||
|
|
@ -105,6 +112,12 @@ def renew(cert, old_version):
|
|||
# (where fewer than all names were renewed)
|
||||
|
||||
|
||||
def _cli_log_handler(args, level, fmt): # pylint: disable=unused-argument
|
||||
handler = colored_logging.StreamHandler()
|
||||
handler.setFormatter(logging.Formatter(fmt))
|
||||
return handler
|
||||
|
||||
|
||||
def _paths_parser(parser):
|
||||
add = parser.add_argument_group("paths").add_argument
|
||||
add("--config-dir", default=cli.flag_default("config_dir"),
|
||||
|
|
@ -120,11 +133,16 @@ def _paths_parser(parser):
|
|||
def _create_parser():
|
||||
parser = argparse.ArgumentParser()
|
||||
#parser.add_argument("--cron", action="store_true", help="Run as cronjob.")
|
||||
# pylint: disable=protected-access
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", dest="verbose_count", action="count",
|
||||
default=cli.flag_default("verbose_count"), help="This flag can be used "
|
||||
"multiple times to incrementally increase the verbosity of output, "
|
||||
"e.g. -vvv.")
|
||||
|
||||
return _paths_parser(parser)
|
||||
|
||||
|
||||
def main(config=None, args=sys.argv[1:]):
|
||||
def main(config=None, cli_args=sys.argv[1:]):
|
||||
"""Main function for autorenewer script."""
|
||||
# TODO: Distinguish automated invocation from manual invocation,
|
||||
# perhaps by looking at sys.argv[0] and inhibiting automated
|
||||
|
|
@ -134,8 +152,13 @@ def main(config=None, args=sys.argv[1:]):
|
|||
|
||||
zope.component.provideUtility(display_util.FileDisplay(sys.stdout))
|
||||
|
||||
cli_config = configuration.RenewerConfiguration(
|
||||
_create_parser().parse_args(args))
|
||||
args = _create_parser().parse_args(cli_args)
|
||||
|
||||
uid = os.geteuid()
|
||||
le_util.make_or_verify_dir(args.logs_dir, 0o700, uid)
|
||||
cli.setup_logging(args, _cli_log_handler, logfile='renewer.log')
|
||||
|
||||
cli_config = configuration.RenewerConfiguration(args)
|
||||
|
||||
config = storage.config_with_defaults(config)
|
||||
# Now attempt to read the renewer config file and augment or replace
|
||||
|
|
@ -146,6 +169,9 @@ def main(config=None, args=sys.argv[1:]):
|
|||
# specify a config file on the command line, which, if provided, should
|
||||
# take precedence over this one.
|
||||
config.merge(configobj.ConfigObj(cli_config.renewer_config_file))
|
||||
# Ensure that all of the needed folders have been created before continuing
|
||||
le_util.make_or_verify_dir(
|
||||
cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid)
|
||||
|
||||
for i in os.listdir(cli_config.renewal_configs_dir):
|
||||
print "Processing", i
|
||||
|
|
|
|||
|
|
@ -45,8 +45,15 @@ class BaseRenewableCertTest(unittest.TestCase):
|
|||
|
||||
self.cli_config = configuration.RenewerConfiguration(
|
||||
namespace=mock.MagicMock(
|
||||
config_dir=self.tempdir, no_simple_http_tls=False))
|
||||
config_dir=self.tempdir,
|
||||
work_dir=self.tempdir,
|
||||
logs_dir=self.tempdir,
|
||||
no_simple_http_tls=False,
|
||||
)
|
||||
)
|
||||
|
||||
# TODO: maybe provide RenewerConfiguration.make_dirs?
|
||||
# TODO: main() should create those dirs, c.f. #902
|
||||
os.makedirs(os.path.join(self.tempdir, "live", "example.org"))
|
||||
os.makedirs(os.path.join(self.tempdir, "archive", "example.org"))
|
||||
os.makedirs(os.path.join(self.tempdir, "renewal"))
|
||||
|
|
@ -63,6 +70,9 @@ class BaseRenewableCertTest(unittest.TestCase):
|
|||
self.test_rc = storage.RenewableCert(
|
||||
self.config, self.defaults, self.cli_config)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def _write_out_ex_kinds(self):
|
||||
for kind in ALL_FOUR:
|
||||
where = getattr(self.test_rc, kind)
|
||||
|
|
@ -80,11 +90,6 @@ class BaseRenewableCertTest(unittest.TestCase):
|
|||
class RenewableCertTests(BaseRenewableCertTest):
|
||||
# pylint: disable=too-many-public-methods
|
||||
"""Tests for letsencrypt.renewer.*."""
|
||||
def setUp(self):
|
||||
super(RenewableCertTests, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def test_initialization(self):
|
||||
self.assertEqual(self.test_rc.lineagename, "example.org")
|
||||
|
|
@ -667,11 +672,17 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
# This should fail because the renewal itself appears to fail
|
||||
self.assertFalse(renewer.renew(self.test_rc, 1))
|
||||
|
||||
def _common_cli_args(self):
|
||||
return [
|
||||
"--config-dir", self.cli_config.config_dir,
|
||||
"--work-dir", self.cli_config.work_dir,
|
||||
"--logs-dir", self.cli_config.logs_dir,
|
||||
]
|
||||
|
||||
@mock.patch("letsencrypt.renewer.notify")
|
||||
@mock.patch("letsencrypt.storage.RenewableCert")
|
||||
@mock.patch("letsencrypt.renewer.renew")
|
||||
def test_main(self, mock_renew, mock_rc, mock_notify):
|
||||
"""Test for main() function."""
|
||||
from letsencrypt import renewer
|
||||
mock_rc_instance = mock.MagicMock()
|
||||
mock_rc_instance.should_autodeploy.return_value = True
|
||||
|
|
@ -693,8 +704,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
"example.com.conf"), "w") as f:
|
||||
f.write("cert = cert.pem\nprivkey = privkey.pem\n")
|
||||
f.write("chain = chain.pem\nfullchain = fullchain.pem\n")
|
||||
renewer.main(self.defaults, args=[
|
||||
'--config-dir', self.cli_config.config_dir])
|
||||
renewer.main(self.defaults, cli_args=self._common_cli_args())
|
||||
self.assertEqual(mock_rc.call_count, 2)
|
||||
self.assertEqual(mock_rc_instance.update_all_links_to.call_count, 2)
|
||||
self.assertEqual(mock_notify.notify.call_count, 4)
|
||||
|
|
@ -707,8 +717,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
mock_happy_instance.should_autorenew.return_value = False
|
||||
mock_happy_instance.latest_common_version.return_value = 10
|
||||
mock_rc.return_value = mock_happy_instance
|
||||
renewer.main(self.defaults, args=[
|
||||
'--config-dir', self.cli_config.config_dir])
|
||||
renewer.main(self.defaults, cli_args=self._common_cli_args())
|
||||
self.assertEqual(mock_rc.call_count, 4)
|
||||
self.assertEqual(mock_happy_instance.update_all_links_to.call_count, 0)
|
||||
self.assertEqual(mock_notify.notify.call_count, 4)
|
||||
|
|
@ -719,8 +728,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
with open(os.path.join(self.cli_config.renewal_configs_dir,
|
||||
"bad.conf"), "w") as f:
|
||||
f.write("incomplete = configfile\n")
|
||||
renewer.main(self.defaults, args=[
|
||||
'--config-dir', self.cli_config.config_dir])
|
||||
renewer.main(self.defaults, cli_args=self._common_cli_args())
|
||||
# The errors.CertStorageError is caught inside and nothing happens.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ export PATH="/usr/sbin:$PATH" # /usr/sbin/nginx
|
|||
export GOPATH="${GOPATH:-/tmp/go}"
|
||||
export PATH="$GOPATH/bin:$PATH"
|
||||
|
||||
if [ `uname` == 'Darwin' ]; then
|
||||
readlink="greadlink"
|
||||
else
|
||||
readlink="readlink"
|
||||
fi
|
||||
|
||||
common() {
|
||||
letsencrypt_test \
|
||||
--authenticator standalone \
|
||||
|
|
@ -49,7 +55,7 @@ dir="$root/conf/archive/le1.wtf"
|
|||
for x in cert chain fullchain privkey;
|
||||
do
|
||||
latest="$(ls -1t $dir/ | grep -e "^${x}" | head -n1)"
|
||||
live="$(readlink -f "$root/conf/live/le1.wtf/${x}.pem")"
|
||||
live="$($readlink -f "$root/conf/live/le1.wtf/${x}.pem")"
|
||||
[ "${dir}/${latest}" = "$live" ] # renewer fails this test
|
||||
done
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ echo "Testing packages"
|
|||
cd "dist.$version"
|
||||
# start local PyPI
|
||||
python -m SimpleHTTPServer $PORT &
|
||||
# cd .. is NOT done on purpose: we make sure that all subpacakges are
|
||||
# cd .. is NOT done on purpose: we make sure that all subpackages are
|
||||
# installed from local PyPI rather than current directory (repo root)
|
||||
virtualenv --no-site-packages ../venv
|
||||
. ../venv/bin/activate
|
||||
|
|
@ -82,15 +82,16 @@ pip install \
|
|||
# stop local PyPI
|
||||
kill $!
|
||||
|
||||
# freeze before installing anythin else, so that we know end-user KGS
|
||||
mkdir kgs
|
||||
kgs="kgs/$version"
|
||||
# freeze before installing anything else, so that we know end-user KGS
|
||||
# make sure "twine upload" doesn't catch "kgs"
|
||||
mkdir ../kgs
|
||||
kgs="../kgs/$version"
|
||||
pip freeze | tee $kgs
|
||||
pip install nose
|
||||
# TODO: letsencrypt_apache fails due to symlink, c.f. #838
|
||||
nosetests letsencrypt $SUBPKGS || true
|
||||
|
||||
echo "New root: $root"
|
||||
echo "KGS is at $root/$kgs"
|
||||
echo "KGS is at $root/kgs"
|
||||
echo "In order to upload packages run the following command:"
|
||||
echo twine upload "$root/dist.$version/*/*"
|
||||
|
|
|
|||
Loading…
Reference in a new issue