mirror of
https://github.com/certbot/certbot.git
synced 2026-06-03 22:08:07 -04:00
Merge branch 'revert-fix-macos-pytest' into test-revert-fix-macos-pytest
This commit is contained in:
commit
328a710393
110 changed files with 2971 additions and 437 deletions
147
CHANGELOG.md
147
CHANGELOG.md
|
|
@ -2,6 +2,153 @@
|
|||
|
||||
Certbot adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 0.23.0 - 2018-04-04
|
||||
|
||||
### Added
|
||||
|
||||
* Support for OpenResty was added to the Nginx plugin.
|
||||
|
||||
### Changed
|
||||
|
||||
* The timestamps in Certbot's logfiles now use the system's local time zone
|
||||
rather than UTC.
|
||||
* Certbot's DNS plugins that use Lexicon now rely on Lexicon>=2.2.1 to be able
|
||||
to create and delete multiple TXT records on a single domain.
|
||||
* certbot-dns-google's test suite now works without an internet connection.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Removed a small window that if during which an error occurred, Certbot
|
||||
wouldn't clean up performed challenges.
|
||||
* The parameters `default` and `ipv6only` are now removed from `listen`
|
||||
directives when creating a new server block in the Nginx plugin.
|
||||
* `server_name` directives enclosed in quotation marks in Nginx are now properly
|
||||
supported.
|
||||
* Resolved an issue preventing the Apache plugin from starting Apache when it's
|
||||
not currently running on RHEL and Gentoo based systems.
|
||||
|
||||
Despite us having broken lockstep, we are continuing to release new versions of
|
||||
all Certbot components during releases for the time being, however, the only
|
||||
packages with changes other than their version number were:
|
||||
|
||||
* certbot
|
||||
* certbot-apache
|
||||
* certbot-dns-cloudxns
|
||||
* certbot-dns-dnsimple
|
||||
* certbot-dns-dnsmadeeasy
|
||||
* certbot-dns-google
|
||||
* certbot-dns-luadns
|
||||
* certbot-dns-nsone
|
||||
* certbot-dns-rfc2136
|
||||
* certbot-nginx
|
||||
|
||||
More details about these changes can be found on our GitHub repo:
|
||||
https://github.com/certbot/certbot/milestone/50?closed=1
|
||||
|
||||
## 0.22.2 - 2018-03-19
|
||||
|
||||
### Fixed
|
||||
|
||||
* A type error introduced in 0.22.1 that would occur during challenge cleanup
|
||||
when a Certbot plugin raises an exception while trying to complete the
|
||||
challenge was fixed.
|
||||
|
||||
Despite us having broken lockstep, we are continuing to release new versions of
|
||||
all Certbot components during releases for the time being, however, the only
|
||||
packages with changes other than their version number were:
|
||||
|
||||
* certbot
|
||||
|
||||
More details about these changes can be found on our GitHub repo:
|
||||
https://github.com/certbot/certbot/milestone/53?closed=1
|
||||
|
||||
## 0.22.1 - 2018-03-19
|
||||
|
||||
### Changed
|
||||
|
||||
* The ACME server used with Certbot's --dry-run and --staging flags is now
|
||||
Let's Encrypt's ACMEv2 staging server which allows people to also test ACMEv2
|
||||
features with these flags.
|
||||
|
||||
### Fixed
|
||||
|
||||
* The HTTP Content-Type header is now set to the correct value during
|
||||
certificate revocation with new versions of the ACME protocol.
|
||||
* When using Certbot with Let's Encrypt's ACMEv2 server, it would add a blank
|
||||
line to the top of chain.pem and between the certificates in fullchain.pem
|
||||
for each lineage. These blank lines have been removed.
|
||||
* Resolved a bug that caused Certbot's --allow-subset-of-names flag not to
|
||||
work.
|
||||
* Fixed a regression in acme.client.Client that caused the class to not work
|
||||
when it was initialized without a ClientNetwork which is done by some of the
|
||||
other projects using our ACME library.
|
||||
|
||||
Despite us having broken lockstep, we are continuing to release new versions of
|
||||
all Certbot components during releases for the time being, however, the only
|
||||
packages with changes other than their version number were:
|
||||
|
||||
* acme
|
||||
* certbot
|
||||
|
||||
More details about these changes can be found on our GitHub repo:
|
||||
https://github.com/certbot/certbot/milestone/51?closed=1
|
||||
|
||||
## 0.22.0 - 2018-03-07
|
||||
|
||||
### Added
|
||||
|
||||
* Support for obtaining wildcard certificates and a newer version of the ACME
|
||||
protocol such as the one implemented by Let's Encrypt's upcoming ACMEv2
|
||||
endpoint was added to Certbot and its ACME library. Certbot still works with
|
||||
older ACME versions and will automatically change the version of the protocol
|
||||
used based on the version the ACME CA implements.
|
||||
* The Apache and Nginx plugins are now able to automatically install a wildcard
|
||||
certificate to multiple virtual hosts that you select from your server
|
||||
configuration.
|
||||
* The `certbot install` command now accepts the `--cert-name` flag for
|
||||
selecting a certificate.
|
||||
* `acme.client.BackwardsCompatibleClientV2` was added to Certbot's ACME library
|
||||
which automatically handles most of the differences between new and old ACME
|
||||
versions. `acme.client.ClientV2` is also available for people who only want
|
||||
to support one version of the protocol or want to handle the differences
|
||||
between versions themselves.
|
||||
* certbot-auto now supports the flag --install-only which has the script
|
||||
install Certbot and its dependencies and exit without invoking Certbot.
|
||||
* Support for issuing a single certificate for a wildcard and base domain was
|
||||
added to our Google Cloud DNS plugin. To do this, we now require your API
|
||||
credentials have additional permissions, however, your credentials will
|
||||
already have these permissions unless you defined a custom role with fewer
|
||||
permissions than the standard DNS administrator role provided by Google.
|
||||
These permissions are also only needed for the case described above so it
|
||||
will continue to work for existing users. For more information about the
|
||||
permissions changes, see the documentation in the plugin.
|
||||
|
||||
### Changed
|
||||
|
||||
* We have broken lockstep between our ACME library, Certbot, and its plugins.
|
||||
This means that the different components do not need to be the same version
|
||||
to work together like they did previously. This makes packaging easier
|
||||
because not every piece of Certbot needs to be repackaged to ship a change to
|
||||
a subset of its components.
|
||||
* Support for Python 2.6 and Python 3.3 has been removed from ACME, Certbot,
|
||||
Certbot's plugins, and certbot-auto. If you are using certbot-auto on a RHEL
|
||||
6 based system, it will walk you through the process of installing Certbot
|
||||
with Python 3 and refuse to upgrade to a newer version of Certbot until you
|
||||
have done so.
|
||||
* Certbot's components now work with older versions of setuptools to simplify
|
||||
packaging for EPEL 7.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Issues caused by Certbot's Nginx plugin adding multiple ipv6only directives
|
||||
has been resolved.
|
||||
* A problem where Certbot's Apache plugin would add redundant include
|
||||
directives for the TLS configuration managed by Certbot has been fixed.
|
||||
* Certbot's webroot plugin now properly deletes any directories it creates.
|
||||
|
||||
More details about these changes can be found on our GitHub repo:
|
||||
https://github.com/certbot/certbot/milestone/48?closed=1
|
||||
|
||||
## 0.21.1 - 2018-01-25
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -259,11 +259,12 @@ class Client(ClientBase):
|
|||
"""
|
||||
# pylint: disable=too-many-arguments
|
||||
self.key = key
|
||||
self.net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) if net is None else net
|
||||
if net is None:
|
||||
net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl)
|
||||
|
||||
if isinstance(directory, six.string_types):
|
||||
directory = messages.Directory.from_json(
|
||||
self.net.get(directory).json())
|
||||
net.get(directory).json())
|
||||
super(Client, self).__init__(directory=directory,
|
||||
net=net, acme_version=1)
|
||||
|
||||
|
|
|
|||
|
|
@ -299,6 +299,16 @@ class ClientTest(ClientTestBase):
|
|||
directory=uri, key=KEY, alg=jose.RS256, net=self.net)
|
||||
self.net.get.assert_called_once_with(uri)
|
||||
|
||||
@mock.patch('acme.client.ClientNetwork')
|
||||
def test_init_without_net(self, mock_net):
|
||||
mock_net.return_value = mock.sentinel.net
|
||||
alg = jose.RS256
|
||||
from acme.client import Client
|
||||
self.client = Client(
|
||||
directory=self.directory, key=KEY, alg=alg)
|
||||
mock_net.called_once_with(KEY, alg=alg, verify_ssl=True)
|
||||
self.assertEqual(self.client.net, mock.sentinel.net)
|
||||
|
||||
def test_register(self):
|
||||
# "Instance of 'Field' has no to_json/update member" bug:
|
||||
# pylint: disable=no-member
|
||||
|
|
|
|||
|
|
@ -435,6 +435,7 @@ class Authorization(ResourceBody):
|
|||
# be absent'... then acme-spec gives example with 'expires'
|
||||
# present... That's confusing!
|
||||
expires = fields.RFC3339Field('expires', omitempty=True)
|
||||
wildcard = jose.Field('wildcard', omitempty=True)
|
||||
|
||||
@challenges.decoder
|
||||
def challenges(value): # pylint: disable=missing-docstring,no-self-argument
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
|
|||
|
|
@ -285,8 +285,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
chain_path=None, fullchain_path=None):
|
||||
"""Deploys certificate to specified virtual host.
|
||||
|
||||
Currently tries to find the last directives to deploy the cert in
|
||||
the VHost associated with the given domain. If it can't find the
|
||||
Currently tries to find the last directives to deploy the certificate
|
||||
in the VHost associated with the given domain. If it can't find the
|
||||
directives, it searches the "included" confs. The function verifies
|
||||
that it has located the three directives and finally modifies them
|
||||
to point to the correct destination. After the certificate is
|
||||
|
|
@ -424,14 +424,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
path["chain_path"] = self.parser.find_dir(
|
||||
"SSLCertificateChainFile", None, vhost.path)
|
||||
|
||||
if not path["cert_path"] or not path["cert_key"]:
|
||||
# Throw some can't find all of the directives error"
|
||||
# Handle errors when certificate/key directives cannot be found
|
||||
if not path["cert_path"]:
|
||||
logger.warning(
|
||||
"Cannot find a cert or key directive in %s. "
|
||||
"Cannot find an SSLCertificateFile directive in %s. "
|
||||
"VirtualHost was not modified", vhost.path)
|
||||
# Presumably break here so that the virtualhost is not modified
|
||||
raise errors.PluginError(
|
||||
"Unable to find cert and/or key directives")
|
||||
"Unable to find an SSLCertificateFile directive")
|
||||
elif not path["cert_key"]:
|
||||
logger.warning(
|
||||
"Cannot find an SSLCertificateKeyFile directive for "
|
||||
"certificate in %s. VirtualHost was not modified", vhost.path)
|
||||
raise errors.PluginError(
|
||||
"Unable to find an SSLCertificateKeyFile directive for "
|
||||
"certificate")
|
||||
|
||||
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
|
||||
|
||||
|
|
@ -1994,10 +2000,27 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
:raises .errors.MisconfigurationError: If reload fails
|
||||
|
||||
"""
|
||||
error = ""
|
||||
try:
|
||||
util.run_script(self.constant("restart_cmd"))
|
||||
except errors.SubprocessError as err:
|
||||
raise errors.MisconfigurationError(str(err))
|
||||
logger.info("Unable to restart apache using %s",
|
||||
self.constant("restart_cmd"))
|
||||
alt_restart = self.constant("restart_cmd_alt")
|
||||
if alt_restart:
|
||||
logger.debug("Trying alternative restart command: %s",
|
||||
alt_restart)
|
||||
# There is an alternative restart command available
|
||||
# This usually is "restart" verb while original is "graceful"
|
||||
try:
|
||||
util.run_script(self.constant(
|
||||
"restart_cmd_alt"))
|
||||
return
|
||||
except errors.SubprocessError as secerr:
|
||||
error = str(secerr)
|
||||
else:
|
||||
error = str(err)
|
||||
raise errors.MisconfigurationError(error)
|
||||
|
||||
def config_test(self): # pylint: disable=no-self-use
|
||||
"""Check the configuration of Apache for errors.
|
||||
|
|
@ -2117,5 +2140,3 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
# to be modified.
|
||||
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
|
||||
self.constant("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
|
|||
version_cmd=['apachectl', '-v'],
|
||||
apache_cmd="apachectl",
|
||||
restart_cmd=['apachectl', 'graceful'],
|
||||
restart_cmd_alt=['apachectl', 'restart'],
|
||||
conftest_cmd=['apachectl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class GentooConfigurator(configurator.ApacheConfigurator):
|
|||
version_cmd=['/usr/sbin/apache2', '-v'],
|
||||
apache_cmd="apache2ctl",
|
||||
restart_cmd=['apache2ctl', 'graceful'],
|
||||
restart_cmd_alt=['apache2ctl', 'restart'],
|
||||
conftest_cmd=['apache2ctl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import unittest
|
|||
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot_apache import obj
|
||||
from certbot_apache import override_centos
|
||||
from certbot_apache.tests import util
|
||||
|
|
@ -121,5 +123,17 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
|
|||
self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys())
|
||||
self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"])
|
||||
|
||||
@mock.patch("certbot_apache.configurator.util.run_script")
|
||||
def test_alt_restart_works(self, mock_run_script):
|
||||
mock_run_script.side_effect = [None, errors.SubprocessError, None]
|
||||
self.config.restart()
|
||||
self.assertEquals(mock_run_script.call_count, 3)
|
||||
|
||||
@mock.patch("certbot_apache.configurator.util.run_script")
|
||||
def test_alt_restart_errors(self, mock_run_script):
|
||||
mock_run_script.side_effect = [None,
|
||||
errors.SubprocessError,
|
||||
errors.SubprocessError]
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.restart)
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -441,13 +441,37 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.vh_truth[1].path))
|
||||
|
||||
def test_deploy_cert_invalid_vhost(self):
|
||||
"""For test cases where the `ApacheConfigurator` class' `_deploy_cert`
|
||||
method is called with an invalid vhost parameter. Currently this tests
|
||||
that a PluginError is appropriately raised when important directives
|
||||
are missing in an SSL module."""
|
||||
self.config.parser.modules.add("ssl_module")
|
||||
mock_find = mock.MagicMock()
|
||||
mock_find.return_value = []
|
||||
self.config.parser.find_dir = mock_find
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
|
||||
def side_effect(*args):
|
||||
"""Mocks case where an SSLCertificateFile directive can be found
|
||||
but an SSLCertificateKeyFile directive is missing."""
|
||||
if "SSLCertificateFile" in args:
|
||||
return ["example/cert.pem"]
|
||||
else:
|
||||
return []
|
||||
|
||||
mock_find_dir = mock.MagicMock(return_value=[])
|
||||
mock_find_dir.side_effect = side_effect
|
||||
|
||||
self.config.parser.find_dir = mock_find_dir
|
||||
|
||||
# Get the default 443 vhost
|
||||
self.config.assoc["random.demo"] = self.vh_truth[1]
|
||||
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.config.deploy_cert, "random.demo",
|
||||
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
|
||||
|
||||
# Remove side_effect to mock case where both SSLCertificateFile
|
||||
# and SSLCertificateKeyFile directives are missing
|
||||
self.config.parser.find_dir.side_effect = None
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.config.deploy_cert, "random.demo",
|
||||
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import unittest
|
|||
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot_apache import override_gentoo
|
||||
from certbot_apache import obj
|
||||
from certbot_apache.tests import util
|
||||
|
|
@ -123,5 +125,11 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
|
|||
self.assertEquals(len(self.config.parser.modules), 4)
|
||||
self.assertTrue("mod_another.c" in self.config.parser.modules)
|
||||
|
||||
@mock.patch("certbot_apache.configurator.util.run_script")
|
||||
def test_alt_restart_works(self, mock_run_script):
|
||||
mock_run_script.side_effect = [None, errors.SubprocessError, None]
|
||||
self.config.restart()
|
||||
self.assertEquals(mock_run_script.call_count, 3)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
|
|||
104
certbot-auto
104
certbot-auto
|
|
@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
|
|||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
|
||||
LE_AUTO_VERSION="0.21.1"
|
||||
LE_AUTO_VERSION="0.23.0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -47,6 +47,7 @@ Help for certbot itself cannot be provided until it is installed.
|
|||
--no-bootstrap do not install OS dependencies
|
||||
--no-self-upgrade do not download updates
|
||||
--os-packages-only install OS dependencies and exit
|
||||
--install-only install certbot, upgrade if needed, and exit
|
||||
-v, --verbose provide more output
|
||||
-q, --quiet provide only update/error output;
|
||||
implies --non-interactive
|
||||
|
|
@ -60,6 +61,8 @@ for arg in "$@" ; do
|
|||
DEBUG=1;;
|
||||
--os-packages-only)
|
||||
OS_PACKAGES_ONLY=1;;
|
||||
--install-only)
|
||||
INSTALL_ONLY=1;;
|
||||
--no-self-upgrade)
|
||||
# Do not upgrade this script (also prevents client upgrades, because each
|
||||
# copy of the script pins a hash of the python client)
|
||||
|
|
@ -246,7 +249,7 @@ DeprecationBootstrap() {
|
|||
fi
|
||||
}
|
||||
|
||||
MIN_PYTHON_VERSION="2.6"
|
||||
MIN_PYTHON_VERSION="2.7"
|
||||
MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//')
|
||||
# Sets LE_PYTHON to Python version string and PYVER to the first two
|
||||
# digits of the python version
|
||||
|
|
@ -1196,24 +1199,24 @@ letsencrypt==0.7.0 \
|
|||
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
|
||||
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
|
||||
|
||||
certbot==0.21.1 \
|
||||
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
|
||||
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
|
||||
acme==0.21.1 \
|
||||
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
|
||||
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
|
||||
certbot-apache==0.21.1 \
|
||||
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
|
||||
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
|
||||
certbot-nginx==0.21.1 \
|
||||
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
|
||||
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
|
||||
#!/usr/bin/env python
|
||||
"""A small script that can act as a trust root for installing pip 8
|
||||
"""A small script that can act as a trust root for installing pip >=8
|
||||
|
||||
Embed this in your project, and your VCS checkout is all you have to trust. In
|
||||
a post-peep era, this lets you claw your way to a hash-checking version of pip,
|
||||
|
|
@ -1237,6 +1240,7 @@ anything goes wrong, it will exit with a non-zero status code.
|
|||
from __future__ import print_function
|
||||
from distutils.version import StrictVersion
|
||||
from hashlib import sha256
|
||||
from os import environ
|
||||
from os.path import join
|
||||
from pipes import quote
|
||||
from shutil import rmtree
|
||||
|
|
@ -1270,14 +1274,14 @@ except ImportError:
|
|||
from urllib.parse import urlparse # 3.4
|
||||
|
||||
|
||||
__version__ = 1, 3, 0
|
||||
__version__ = 1, 5, 1
|
||||
PIP_VERSION = '9.0.1'
|
||||
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
|
||||
|
||||
|
||||
# wheel has a conditional dependency on argparse:
|
||||
maybe_argparse = (
|
||||
[('https://pypi.python.org/packages/18/dd/'
|
||||
'e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
|
||||
[('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
|
||||
'argparse-1.4.0.tar.gz',
|
||||
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
|
||||
if version_info < (2, 7, 0) else [])
|
||||
|
|
@ -1285,18 +1289,14 @@ maybe_argparse = (
|
|||
|
||||
PACKAGES = maybe_argparse + [
|
||||
# Pip has no dependencies, as it vendors everything:
|
||||
('https://pypi.python.org/packages/11/b6/'
|
||||
'abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
|
||||
'pip-{0}.tar.gz'
|
||||
.format(PIP_VERSION),
|
||||
('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
|
||||
'pip-{0}.tar.gz'.format(PIP_VERSION),
|
||||
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
|
||||
# This version of setuptools has only optional dependencies:
|
||||
('https://pypi.python.org/packages/69/65/'
|
||||
'4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
|
||||
'setuptools-20.2.2.tar.gz',
|
||||
'24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'),
|
||||
('https://pypi.python.org/packages/c9/1d/'
|
||||
'bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
|
||||
('59/88/2f3990916931a5de6fa9706d6d75eb32ee8b78627bb2abaab7ed9e6d0622/'
|
||||
'setuptools-29.0.1.tar.gz',
|
||||
'b539118819a4857378398891fa5366e090690e46b3e41421a1e07d6e9fd8feb0'),
|
||||
('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
|
||||
'wheel-0.29.0.tar.gz',
|
||||
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
|
||||
]
|
||||
|
|
@ -1317,12 +1317,13 @@ def hashed_download(url, temp, digest):
|
|||
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
|
||||
# authenticity has only privacy (not arbitrary code execution)
|
||||
# implications, since we're checking hashes.
|
||||
def opener():
|
||||
def opener(using_https=True):
|
||||
opener = build_opener(HTTPSHandler())
|
||||
# Strip out HTTPHandler to prevent MITM spoof:
|
||||
for handler in opener.handlers:
|
||||
if isinstance(handler, HTTPHandler):
|
||||
opener.handlers.remove(handler)
|
||||
if using_https:
|
||||
# Strip out HTTPHandler to prevent MITM spoof:
|
||||
for handler in opener.handlers:
|
||||
if isinstance(handler, HTTPHandler):
|
||||
opener.handlers.remove(handler)
|
||||
return opener
|
||||
|
||||
def read_chunks(response, chunk_size):
|
||||
|
|
@ -1332,8 +1333,9 @@ def hashed_download(url, temp, digest):
|
|||
break
|
||||
yield chunk
|
||||
|
||||
response = opener().open(url)
|
||||
path = join(temp, urlparse(url).path.split('/')[-1])
|
||||
parsed_url = urlparse(url)
|
||||
response = opener(using_https=parsed_url.scheme == 'https').open(url)
|
||||
path = join(temp, parsed_url.path.split('/')[-1])
|
||||
actual_hash = sha256()
|
||||
with open(path, 'wb') as file:
|
||||
for chunk in read_chunks(response, 4096):
|
||||
|
|
@ -1346,6 +1348,24 @@ def hashed_download(url, temp, digest):
|
|||
return path
|
||||
|
||||
|
||||
def get_index_base():
|
||||
"""Return the URL to the dir containing the "packages" folder.
|
||||
|
||||
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
|
||||
end if it's there; that is likely to give us the right dir.
|
||||
|
||||
"""
|
||||
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
|
||||
if env_var:
|
||||
SIMPLE = '/simple'
|
||||
if env_var.endswith(SIMPLE):
|
||||
return env_var[:-len(SIMPLE)]
|
||||
else:
|
||||
return env_var
|
||||
else:
|
||||
return DEFAULT_INDEX_BASE
|
||||
|
||||
|
||||
def main():
|
||||
pip_version = StrictVersion(check_output(['pip', '--version'])
|
||||
.decode('utf-8').split()[1])
|
||||
|
|
@ -1353,11 +1373,13 @@ def main():
|
|||
if pip_version >= min_pip_version:
|
||||
return 0
|
||||
has_pip_cache = pip_version >= StrictVersion('6.0')
|
||||
|
||||
index_base = get_index_base()
|
||||
temp = mkdtemp(prefix='pipstrap-')
|
||||
try:
|
||||
downloads = [hashed_download(url, temp, digest)
|
||||
for url, digest in PACKAGES]
|
||||
downloads = [hashed_download(index_base + '/packages/' + path,
|
||||
temp,
|
||||
digest)
|
||||
for path, digest in PACKAGES]
|
||||
check_output('pip install --no-index --no-deps -U ' +
|
||||
# Disable cache since we're not using it and it otherwise
|
||||
# sometimes throws permission warnings:
|
||||
|
|
@ -1428,6 +1450,12 @@ UNLIKELY_EOF
|
|||
|
||||
say "Installation succeeded."
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_ONLY" = 1 ]; then
|
||||
say "Certbot is installed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
"$VENV_BIN/letsencrypt" "$@"
|
||||
|
||||
else
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import sys
|
|||
|
||||
import OpenSSL
|
||||
|
||||
from six.moves import xrange # pylint: disable=import-error,redefined-builtin
|
||||
|
||||
from acme import challenges
|
||||
from acme import crypto_util
|
||||
from acme import messages
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import requests
|
|||
import zope.interface
|
||||
|
||||
import six
|
||||
from six.moves import xrange # pylint: disable=import-error,redefined-builtin
|
||||
|
||||
from acme import crypto_util
|
||||
from acme import errors as acme_errors
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from certbot_nginx import nginxparser
|
|||
def roundtrip(stuff):
|
||||
success = True
|
||||
for t in stuff:
|
||||
print t
|
||||
print(t)
|
||||
if not os.path.isfile(t):
|
||||
continue
|
||||
with open(t, "r") as f:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-cloudflare's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_cloudflare
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_cloudflare
|
||||
:members:
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
|
|
|||
12
certbot-dns-cloudflare/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-cloudflare/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-cloudflare[docs]
|
||||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-cloudxns's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_cloudxns
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_cloudxns
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-cloudxns/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-cloudxns/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-cloudxns[docs]
|
||||
|
|
@ -4,14 +4,14 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-digitalocean's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_digitalocean
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_digitalocean
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-digitalocean/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-digitalocean/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-digitalocean[docs]
|
||||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-dnsimple's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_dnsimple
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_dnsimple
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-dnsimple/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-dnsimple/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-dnsimple[docs]
|
||||
|
|
@ -4,14 +4,14 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-dnsmadeeasy's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_dnsmadeeasy
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_dnsmadeeasy
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-dnsmadeeasy[docs]
|
||||
|
|
@ -4,14 +4,14 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
include LICENSE.txt
|
||||
include README.rst
|
||||
recursive-include docs *
|
||||
recursive-include certbot_dns_google/testdata *
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class _GoogleClient(object):
|
|||
Encapsulates all communication with the Google Cloud DNS API.
|
||||
"""
|
||||
|
||||
def __init__(self, account_json=None):
|
||||
def __init__(self, account_json=None, dns_api=None):
|
||||
|
||||
scopes = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite']
|
||||
if account_json is not None:
|
||||
|
|
@ -92,7 +92,12 @@ class _GoogleClient(object):
|
|||
credentials = None
|
||||
self.project_id = self.get_project_id()
|
||||
|
||||
self.dns = discovery.build('dns', 'v1', credentials=credentials, cache_discovery=False)
|
||||
if not dns_api:
|
||||
self.dns = discovery.build('dns', 'v1',
|
||||
credentials=credentials,
|
||||
cache_discovery=False)
|
||||
else:
|
||||
self.dns = dns_api
|
||||
|
||||
def add_txt_record(self, domain, record_name, record_content, record_ttl):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import os
|
|||
import unittest
|
||||
|
||||
import mock
|
||||
from googleapiclient import discovery
|
||||
from googleapiclient.errors import Error
|
||||
from googleapiclient.http import HttpMock
|
||||
from httplib2 import ServerNotFoundError
|
||||
|
||||
from certbot import errors
|
||||
|
|
@ -68,7 +70,13 @@ class GoogleClientTest(unittest.TestCase):
|
|||
def _setUp_client_with_mock(self, zone_request_side_effect):
|
||||
from certbot_dns_google.dns_google import _GoogleClient
|
||||
|
||||
client = _GoogleClient(ACCOUNT_JSON_PATH)
|
||||
pwd = os.path.dirname(__file__)
|
||||
rel_path = 'testdata/discovery.json'
|
||||
discovery_file = os.path.join(pwd, rel_path)
|
||||
http_mock = HttpMock(discovery_file, {'status': '200'})
|
||||
dns_api = discovery.build('dns', 'v1', http=http_mock)
|
||||
|
||||
client = _GoogleClient(ACCOUNT_JSON_PATH, dns_api)
|
||||
|
||||
# Setup
|
||||
mock_mz = mock.MagicMock()
|
||||
|
|
|
|||
1401
certbot-dns-google/certbot_dns_google/testdata/discovery.json
vendored
Normal file
1401
certbot-dns-google/certbot_dns_google/testdata/discovery.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-google's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_google
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_google
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-google/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-google/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-google[docs]
|
||||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-luadns's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_luadns
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_luadns
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-luadns/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-luadns/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-luadns[docs]
|
||||
|
|
@ -4,14 +4,14 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-nsone's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_nsone
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_nsone
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-nsone/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-nsone/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-nsone[docs]
|
||||
|
|
@ -4,14 +4,14 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.21.1',
|
||||
'certbot>=0.21.1',
|
||||
'dns-lexicon',
|
||||
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
|
|
|
|||
|
|
@ -70,11 +70,11 @@ class Authenticator(dns_common.DNSAuthenticator):
|
|||
self._validate_algorithm
|
||||
)
|
||||
|
||||
def _perform(self, domain, validation_name, validation):
|
||||
self._get_rfc2136_client().add_txt_record(domain, validation_name, validation, self.ttl)
|
||||
def _perform(self, _domain, validation_name, validation):
|
||||
self._get_rfc2136_client().add_txt_record(validation_name, validation, self.ttl)
|
||||
|
||||
def _cleanup(self, domain, validation_name, validation):
|
||||
self._get_rfc2136_client().del_txt_record(domain, validation_name, validation)
|
||||
def _cleanup(self, _domain, validation_name, validation):
|
||||
self._get_rfc2136_client().del_txt_record(validation_name, validation)
|
||||
|
||||
def _get_rfc2136_client(self):
|
||||
return _RFC2136Client(self.credentials.conf('server'),
|
||||
|
|
@ -95,18 +95,17 @@ class _RFC2136Client(object):
|
|||
})
|
||||
self.algorithm = key_algorithm
|
||||
|
||||
def add_txt_record(self, domain_name, record_name, record_content, record_ttl):
|
||||
def add_txt_record(self, record_name, record_content, record_ttl):
|
||||
"""
|
||||
Add a TXT record using the supplied information.
|
||||
|
||||
:param str domain: The domain to use to find the closest SOA.
|
||||
:param str record_name: The record name (typically beginning with '_acme-challenge.').
|
||||
:param str record_content: The record content (typically the challenge validation).
|
||||
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
|
||||
:raises certbot.errors.PluginError: if an error occurs communicating with the DNS server
|
||||
"""
|
||||
|
||||
domain = self._find_domain(domain_name)
|
||||
domain = self._find_domain(record_name)
|
||||
|
||||
n = dns.name.from_text(record_name)
|
||||
o = dns.name.from_text(domain)
|
||||
|
|
@ -131,18 +130,17 @@ class _RFC2136Client(object):
|
|||
raise errors.PluginError('Received response from server: {0}'
|
||||
.format(dns.rcode.to_text(rcode)))
|
||||
|
||||
def del_txt_record(self, domain_name, record_name, record_content):
|
||||
def del_txt_record(self, record_name, record_content):
|
||||
"""
|
||||
Delete a TXT record using the supplied information.
|
||||
|
||||
:param str domain: The domain to use to find the closest SOA.
|
||||
:param str record_name: The record name (typically beginning with '_acme-challenge.').
|
||||
:param str record_content: The record content (typically the challenge validation).
|
||||
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
|
||||
:raises certbot.errors.PluginError: if an error occurs communicating with the DNS server
|
||||
"""
|
||||
|
||||
domain = self._find_domain(domain_name)
|
||||
domain = self._find_domain(record_name)
|
||||
|
||||
n = dns.name.from_text(record_name)
|
||||
o = dns.name.from_text(domain)
|
||||
|
|
@ -167,17 +165,17 @@ class _RFC2136Client(object):
|
|||
raise errors.PluginError('Received response from server: {0}'
|
||||
.format(dns.rcode.to_text(rcode)))
|
||||
|
||||
def _find_domain(self, domain_name):
|
||||
def _find_domain(self, record_name):
|
||||
"""
|
||||
Find the closest domain with an SOA record for a given domain name.
|
||||
|
||||
:param str domain_name: The domain name for which to find the closest SOA record.
|
||||
:param str record_name: The record name for which to find the closest SOA record.
|
||||
:returns: The domain, if found.
|
||||
:rtype: str
|
||||
:raises certbot.errors.PluginError: if no SOA record can be found.
|
||||
"""
|
||||
|
||||
domain_name_guesses = dns_common.base_domain_name_guesses(domain_name)
|
||||
domain_name_guesses = dns_common.base_domain_name_guesses(record_name)
|
||||
|
||||
# Loop through until we find an authoritative SOA record
|
||||
for guess in domain_name_guesses:
|
||||
|
|
@ -185,7 +183,7 @@ class _RFC2136Client(object):
|
|||
return guess
|
||||
|
||||
raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.'
|
||||
.format(domain_name, domain_name_guesses))
|
||||
.format(record_name, domain_name_guesses))
|
||||
|
||||
def _query_soa(self, domain_name):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
|
|||
def test_perform(self):
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)]
|
||||
expected = [mock.call.add_txt_record('_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)]
|
||||
self.assertEqual(expected, self.mock_client.mock_calls)
|
||||
|
||||
def test_cleanup(self):
|
||||
|
|
@ -49,7 +49,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
|
|||
self.auth._attempt_cleanup = True
|
||||
self.auth.cleanup([self.achall])
|
||||
|
||||
expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)]
|
||||
expected = [mock.call.del_txt_record('_acme-challenge.'+DOMAIN, mock.ANY)]
|
||||
self.assertEqual(expected, self.mock_client.mock_calls)
|
||||
|
||||
def test_invalid_algorithm_raises(self):
|
||||
|
|
@ -82,7 +82,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
# _find_domain | pylint: disable=protected-access
|
||||
self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com")
|
||||
|
||||
self.rfc2136_client.add_txt_record(DOMAIN, "bar", "baz", 42)
|
||||
self.rfc2136_client.add_txt_record("bar", "baz", 42)
|
||||
|
||||
query_mock.assert_called_with(mock.ANY, SERVER)
|
||||
self.assertTrue("bar. 42 IN TXT \"baz\"" in str(query_mock.call_args[0][0]))
|
||||
|
|
@ -96,7 +96,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
self.rfc2136_client.add_txt_record,
|
||||
DOMAIN, "bar", "baz", 42)
|
||||
"bar", "baz", 42)
|
||||
|
||||
@mock.patch("dns.query.tcp")
|
||||
def test_add_txt_record_server_error(self, query_mock):
|
||||
|
|
@ -107,7 +107,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
self.rfc2136_client.add_txt_record,
|
||||
DOMAIN, "bar", "baz", 42)
|
||||
"bar", "baz", 42)
|
||||
|
||||
@mock.patch("dns.query.tcp")
|
||||
def test_del_txt_record(self, query_mock):
|
||||
|
|
@ -115,7 +115,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
# _find_domain | pylint: disable=protected-access
|
||||
self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com")
|
||||
|
||||
self.rfc2136_client.del_txt_record(DOMAIN, "bar", "baz")
|
||||
self.rfc2136_client.del_txt_record("bar", "baz")
|
||||
|
||||
query_mock.assert_called_with(mock.ANY, SERVER)
|
||||
self.assertTrue("bar. 0 NONE TXT \"baz\"" in str(query_mock.call_args[0][0]))
|
||||
|
|
@ -129,7 +129,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
self.rfc2136_client.del_txt_record,
|
||||
DOMAIN, "bar", "baz")
|
||||
"bar", "baz")
|
||||
|
||||
@mock.patch("dns.query.tcp")
|
||||
def test_del_txt_record_server_error(self, query_mock):
|
||||
|
|
@ -140,7 +140,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
|||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
self.rfc2136_client.del_txt_record,
|
||||
DOMAIN, "bar", "baz")
|
||||
"bar", "baz")
|
||||
|
||||
def test_find_domain(self):
|
||||
# _query_soa | pylint: disable=protected-access
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-rfc2136's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_rfc2136
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_rfc2136
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-rfc2136/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-rfc2136/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-rfc2136[docs]
|
||||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ Welcome to certbot-dns-route53's documentation!
|
|||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: certbot_dns_route53
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api
|
||||
|
||||
.. automodule:: certbot_dns_route53
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
|
|||
12
certbot-dns-route53/readthedocs.org.requirements.txt
Normal file
12
certbot-dns-route53/readthedocs.org.requirements.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# readthedocs.org gives no way to change the install command to "pip
|
||||
# install -e .[docs]" (that would in turn install documentation
|
||||
# dependencies), but it allows to specify a requirements.txt file at
|
||||
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
|
||||
|
||||
# Although ReadTheDocs certainly doesn't need to install the project
|
||||
# in --editable mode (-e), just "pip install .[docs]" does not work as
|
||||
# expected and "pip install -e .[docs]" must be used instead
|
||||
|
||||
-e acme
|
||||
-e .
|
||||
-e certbot-dns-route53[docs]
|
||||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
from distutils.core import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ class NginxConfigurator(common.Installer):
|
|||
|
||||
DEFAULT_LISTEN_PORT = '80'
|
||||
|
||||
# SSL directives that Certbot can add when installing a new certificate.
|
||||
SSL_DIRECTIVES = ['ssl_certificate', 'ssl_certificate_key', 'ssl_dhparam']
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
add("server-root", default=constants.CLI_DEFAULTS["server_root"],
|
||||
|
|
@ -105,6 +108,7 @@ class NginxConfigurator(common.Installer):
|
|||
self.parser = None
|
||||
self.version = version
|
||||
self._enhance_func = {"redirect": self._enable_redirect,
|
||||
"ensure-http-header": self._set_http_header,
|
||||
"staple-ocsp": self._enable_ocsp_stapling}
|
||||
|
||||
self.reverter.recovery_routine()
|
||||
|
|
@ -188,8 +192,8 @@ class NginxConfigurator(common.Installer):
|
|||
cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path],
|
||||
['\n ', 'ssl_certificate_key', ' ', key_path]]
|
||||
|
||||
self.parser.add_server_directives(vhost,
|
||||
cert_directives, replace=True)
|
||||
self.parser.update_or_add_server_directives(vhost,
|
||||
cert_directives)
|
||||
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
|
||||
|
||||
self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
|
||||
|
|
@ -328,7 +332,8 @@ class NginxConfigurator(common.Installer):
|
|||
def _vhost_from_duplicated_default(self, domain, port=None):
|
||||
if self.new_vhost is None:
|
||||
default_vhost = self._get_default_vhost(port)
|
||||
self.new_vhost = self.parser.duplicate_vhost(default_vhost, delete_default=True)
|
||||
self.new_vhost = self.parser.duplicate_vhost(default_vhost,
|
||||
remove_singleton_listen_params=True)
|
||||
self.new_vhost.names = set()
|
||||
|
||||
self._add_server_name_to_vhost(self.new_vhost, domain)
|
||||
|
|
@ -340,7 +345,7 @@ class NginxConfigurator(common.Installer):
|
|||
for name in vhost.names:
|
||||
name_block[0].append(' ')
|
||||
name_block[0].append(name)
|
||||
self.parser.add_server_directives(vhost, name_block, replace=True)
|
||||
self.parser.update_or_add_server_directives(vhost, name_block)
|
||||
|
||||
def _get_default_vhost(self, port):
|
||||
vhost_list = self.parser.get_vhosts()
|
||||
|
|
@ -580,7 +585,7 @@ class NginxConfigurator(common.Installer):
|
|||
# have it continue to do so.
|
||||
if len(vhost.addrs) == 0:
|
||||
listen_block = [['\n ', 'listen', ' ', self.DEFAULT_LISTEN_PORT]]
|
||||
self.parser.add_server_directives(vhost, listen_block, replace=False)
|
||||
self.parser.add_server_directives(vhost, listen_block)
|
||||
|
||||
if vhost.ipv6_enabled():
|
||||
ipv6_block = ['\n ',
|
||||
|
|
@ -614,14 +619,14 @@ class NginxConfigurator(common.Installer):
|
|||
])
|
||||
|
||||
self.parser.add_server_directives(
|
||||
vhost, ssl_block, replace=False)
|
||||
vhost, ssl_block)
|
||||
|
||||
##################################
|
||||
# enhancement methods (IInstaller)
|
||||
##################################
|
||||
def supported_enhancements(self): # pylint: disable=no-self-use
|
||||
"""Returns currently supported enhancements."""
|
||||
return ['redirect', 'staple-ocsp']
|
||||
return ['redirect', 'ensure-http-header', 'staple-ocsp']
|
||||
|
||||
def enhance(self, domain, enhancement, options=None):
|
||||
"""Enhance configuration.
|
||||
|
|
@ -647,13 +652,80 @@ class NginxConfigurator(common.Installer):
|
|||
test_redirect_block = _test_block_from_block(_redirect_block_for_domain(domain))
|
||||
return vhost.contains_list(test_redirect_block)
|
||||
|
||||
def _set_http_header(self, domain, header_substring):
|
||||
"""Enables header identified by header_substring on domain.
|
||||
|
||||
If the vhost is listening plaintextishly, separates out the relevant
|
||||
directives into a new server block, and only add header directive to
|
||||
HTTPS block.
|
||||
|
||||
:param str domain: the domain to enable header for.
|
||||
:param str header_substring: String to uniquely identify a header.
|
||||
e.g. Strict-Transport-Security, Upgrade-Insecure-Requests
|
||||
:returns: Success
|
||||
:raises .errors.PluginError: If no viable HTTPS host can be created or
|
||||
set with header header_substring.
|
||||
"""
|
||||
vhosts = self.choose_vhosts(domain)
|
||||
if not vhosts:
|
||||
raise errors.PluginError(
|
||||
"Unable to find corresponding HTTPS host for enhancement.")
|
||||
for vhost in vhosts:
|
||||
if vhost.has_header(header_substring):
|
||||
raise errors.PluginEnhancementAlreadyPresent(
|
||||
"Existing %s header" % (header_substring))
|
||||
|
||||
# if there is no separate SSL block, break the block into two and
|
||||
# choose the SSL block.
|
||||
if vhost.ssl and any([not addr.ssl for addr in vhost.addrs]):
|
||||
_, vhost = self._split_block(vhost)
|
||||
|
||||
header_directives = [
|
||||
['\n ', 'add_header', ' ', header_substring, ' '] +
|
||||
constants.HEADER_ARGS[header_substring],
|
||||
['\n']]
|
||||
self.parser.add_server_directives(vhost, header_directives)
|
||||
|
||||
def _add_redirect_block(self, vhost, domain):
|
||||
"""Add redirect directive to vhost
|
||||
"""
|
||||
redirect_block = _redirect_block_for_domain(domain)
|
||||
|
||||
self.parser.add_server_directives(
|
||||
vhost, redirect_block, replace=False, insert_at_top=True)
|
||||
vhost, redirect_block, insert_at_top=True)
|
||||
|
||||
def _split_block(self, vhost, only_directives=None):
|
||||
"""Splits this "virtual host" (i.e. this nginx server block) into
|
||||
separate HTTP and HTTPS blocks.
|
||||
|
||||
:param vhost: The server block to break up into two.
|
||||
:param list only_directives: If this exists, only duplicate these directives
|
||||
when splitting the block.
|
||||
:type vhost: :class:`~certbot_nginx.obj.VirtualHost`
|
||||
:returns: tuple (http_vhost, https_vhost)
|
||||
:rtype: tuple of type :class:`~certbot_nginx.obj.VirtualHost`
|
||||
"""
|
||||
http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives)
|
||||
|
||||
def _ssl_match_func(directive):
|
||||
return 'ssl' in directive
|
||||
|
||||
def _ssl_config_match_func(directive):
|
||||
return self.mod_ssl_conf in directive
|
||||
|
||||
def _no_ssl_match_func(directive):
|
||||
return 'ssl' not in directive
|
||||
|
||||
# remove all ssl addresses and related directives from the new block
|
||||
for directive in self.SSL_DIRECTIVES:
|
||||
self.parser.remove_server_directives(http_vhost, directive)
|
||||
self.parser.remove_server_directives(http_vhost, 'listen', match_func=_ssl_match_func)
|
||||
self.parser.remove_server_directives(http_vhost, 'include',
|
||||
match_func=_ssl_config_match_func)
|
||||
|
||||
# remove all non-ssl addresses from the existing block
|
||||
self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func)
|
||||
return http_vhost, vhost
|
||||
|
||||
def _enable_redirect(self, domain, unused_options):
|
||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||
|
|
@ -694,28 +766,15 @@ class NginxConfigurator(common.Installer):
|
|||
:param `~obj.Vhost` vhost: vhost to enable redirect for
|
||||
"""
|
||||
|
||||
new_vhost = None
|
||||
http_vhost = None
|
||||
if vhost.ssl:
|
||||
new_vhost = self.parser.duplicate_vhost(vhost,
|
||||
only_directives=['listen', 'server_name'])
|
||||
|
||||
def _ssl_match_func(directive):
|
||||
return 'ssl' in directive
|
||||
|
||||
def _no_ssl_match_func(directive):
|
||||
return 'ssl' not in directive
|
||||
|
||||
# remove all ssl addresses from the new block
|
||||
self.parser.remove_server_directives(new_vhost, 'listen', match_func=_ssl_match_func)
|
||||
|
||||
# remove all non-ssl addresses from the existing block
|
||||
self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func)
|
||||
http_vhost, _ = self._split_block(vhost, ['listen', 'server_name'])
|
||||
|
||||
# Add this at the bottom to get the right order of directives
|
||||
return_404_directive = [['\n ', 'return', ' ', '404']]
|
||||
self.parser.add_server_directives(new_vhost, return_404_directive, replace=False)
|
||||
self.parser.add_server_directives(http_vhost, return_404_directive)
|
||||
|
||||
vhost = new_vhost
|
||||
vhost = http_vhost
|
||||
|
||||
if self._has_certbot_redirect(vhost, domain):
|
||||
logger.info("Traffic on port %s already redirecting to ssl in %s",
|
||||
|
|
@ -763,7 +822,7 @@ class NginxConfigurator(common.Installer):
|
|||
|
||||
try:
|
||||
self.parser.add_server_directives(vhost,
|
||||
stapling_directives, replace=False)
|
||||
stapling_directives)
|
||||
except errors.MisconfigurationError as error:
|
||||
logger.debug(error)
|
||||
raise errors.PluginError("An error occurred while enabling OCSP "
|
||||
|
|
@ -837,7 +896,7 @@ class NginxConfigurator(common.Installer):
|
|||
raise errors.PluginError(
|
||||
"Unable to run %s -V" % self.conf('ctl'))
|
||||
|
||||
version_regex = re.compile(r"nginx/([0-9\.]*)", re.IGNORECASE)
|
||||
version_regex = re.compile(r"nginx version: ([^/]+)/([0-9\.]*)", re.IGNORECASE)
|
||||
version_matches = version_regex.findall(text)
|
||||
|
||||
sni_regex = re.compile(r"TLS SNI support enabled", re.IGNORECASE)
|
||||
|
|
@ -854,7 +913,12 @@ class NginxConfigurator(common.Installer):
|
|||
if not sni_matches:
|
||||
raise errors.PluginError("Nginx build doesn't support SNI")
|
||||
|
||||
nginx_version = tuple([int(i) for i in version_matches[0].split(".")])
|
||||
product_name, product_version = version_matches[0]
|
||||
if product_name != 'nginx':
|
||||
logger.warning("NGINX derivative %s is not officially supported by"
|
||||
" certbot", product_name)
|
||||
|
||||
nginx_version = tuple([int(i) for i in product_version.split(".")])
|
||||
|
||||
# nginx < 0.8.48 uses machine hostname as default server_name instead of
|
||||
# the empty string
|
||||
|
|
|
|||
|
|
@ -44,3 +44,7 @@ def os_constant(key):
|
|||
:return: value of constant for active os
|
||||
"""
|
||||
return CLI_DEFAULTS[key]
|
||||
|
||||
HSTS_ARGS = ['\"max-age=31536000\"', ' ', 'always']
|
||||
|
||||
HEADER_ARGS = {'Strict-Transport-Security': HSTS_ARGS}
|
||||
|
|
|
|||
|
|
@ -159,16 +159,22 @@ class NginxHttp01(common.ChallengePerformer):
|
|||
document_root = os.path.join(
|
||||
self.configurator.config.work_dir, "http_01_nonexistent")
|
||||
|
||||
block.extend([['server_name', ' ', achall.domain],
|
||||
['root', ' ', document_root],
|
||||
self._location_directive_for_achall(achall)
|
||||
])
|
||||
# TODO: do we want to return something else if they otherwise access this block?
|
||||
return [['server'], block]
|
||||
|
||||
def _location_directive_for_achall(self, achall):
|
||||
validation = achall.validation(achall.account_key)
|
||||
validation_path = self._get_validation_path(achall)
|
||||
|
||||
block.extend([['server_name', ' ', achall.domain],
|
||||
['root', ' ', document_root],
|
||||
[['location', ' ', '=', ' ', validation_path],
|
||||
[['default_type', ' ', 'text/plain'],
|
||||
['return', ' ', '200', ' ', validation]]]])
|
||||
# TODO: do we want to return something else if they otherwise access this block?
|
||||
return [['server'], block]
|
||||
location_directive = [['location', ' ', '=', ' ', validation_path],
|
||||
[['default_type', ' ', 'text/plain'],
|
||||
['return', ' ', '200', ' ', validation]]]
|
||||
return location_directive
|
||||
|
||||
|
||||
def _make_or_mod_server_block(self, achall):
|
||||
"""Modifies a server block to respond to a challenge.
|
||||
|
|
@ -191,17 +197,12 @@ class NginxHttp01(common.ChallengePerformer):
|
|||
vhost = vhosts[0]
|
||||
|
||||
# Modify existing server block
|
||||
validation = achall.validation(achall.account_key)
|
||||
validation_path = self._get_validation_path(achall)
|
||||
|
||||
location_directive = [[['location', ' ', '=', ' ', validation_path],
|
||||
[['default_type', ' ', 'text/plain'],
|
||||
['return', ' ', '200', ' ', validation]]]]
|
||||
location_directive = [self._location_directive_for_achall(achall)]
|
||||
|
||||
self.configurator.parser.add_server_directives(vhost,
|
||||
location_directive, replace=False)
|
||||
location_directive)
|
||||
|
||||
rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)',
|
||||
' ', '$1', ' ', 'break']]
|
||||
self.configurator.parser.add_server_directives(vhost,
|
||||
rewrite_directive, replace=False, insert_at_top=True)
|
||||
rewrite_directive, insert_at_top=True)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import six
|
|||
|
||||
from certbot.plugins import common
|
||||
|
||||
REDIRECT_DIRECTIVES = ['return', 'rewrite']
|
||||
ADD_HEADER_DIRECTIVE = 'add_header'
|
||||
|
||||
class Addr(common.Addr):
|
||||
r"""Represents an Nginx address, i.e. what comes after the 'listen'
|
||||
|
|
@ -29,6 +29,8 @@ class Addr(common.Addr):
|
|||
:param str port: port number or "\*" or ""
|
||||
:param bool ssl: Whether the directive includes 'ssl'
|
||||
:param bool default: Whether the directive includes 'default_server'
|
||||
:param bool default: Whether this is an IPv6 address
|
||||
:param bool ipv6only: Whether the directive includes 'ipv6only=on'
|
||||
|
||||
"""
|
||||
UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0')
|
||||
|
|
@ -88,6 +90,8 @@ class Addr(common.Addr):
|
|||
ssl = True
|
||||
elif nextpart == 'default_server':
|
||||
default = True
|
||||
elif nextpart == 'default':
|
||||
default = True
|
||||
elif nextpart == "ipv6only=on":
|
||||
ipv6only = True
|
||||
|
||||
|
|
@ -198,6 +202,14 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
tuple(self.addrs), tuple(self.names),
|
||||
self.ssl, self.enabled))
|
||||
|
||||
def has_header(self, header_name):
|
||||
"""Determine if this server block has a particular header set.
|
||||
:param str header_name: The name of the header to check for, e.g.
|
||||
'Strict-Transport-Security'
|
||||
"""
|
||||
found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name)
|
||||
return found is not None
|
||||
|
||||
def contains_list(self, test):
|
||||
"""Determine if raw server block contains test list at top level
|
||||
"""
|
||||
|
|
@ -233,3 +245,19 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
|||
addrs=", ".join(str(addr) for addr in self.addrs),
|
||||
names=", ".join(self.names),
|
||||
https="Yes" if self.ssl else "No"))
|
||||
|
||||
def _find_directive(directives, directive_name, match_content=None):
|
||||
"""Find a directive of type directive_name in directives. If match_content is given,
|
||||
Searches for `match_content` in the directive arguments.
|
||||
"""
|
||||
if not directives or isinstance(directives, six.string_types) or len(directives) == 0:
|
||||
return None
|
||||
|
||||
# If match_content is None, just match on directive type. Otherwise, match on
|
||||
# both directive type -and- the content!
|
||||
if directives[0] == directive_name and \
|
||||
(match_content is None or match_content in directives):
|
||||
return directives
|
||||
|
||||
matches = (_find_directive(line, directive_name, match_content) for line in directives)
|
||||
return next((m for m in matches if m is not None), None)
|
||||
|
|
|
|||
|
|
@ -276,15 +276,13 @@ class NginxParser(object):
|
|||
|
||||
return False
|
||||
|
||||
def add_server_directives(self, vhost, directives, replace, insert_at_top=False):
|
||||
"""Add or replace directives in the server block identified by vhost.
|
||||
def add_server_directives(self, vhost, directives, insert_at_top=False):
|
||||
"""Add directives to the server block identified by vhost.
|
||||
|
||||
This method modifies vhost to be fully consistent with the new directives.
|
||||
|
||||
..note :: If replace is True and the directive already exists, the first
|
||||
instance will be replaced. Otherwise, the directive is added.
|
||||
..note :: If replace is False nothing gets added if an identical
|
||||
block exists already.
|
||||
..note :: It's an error to try and add a nonrepeatable directive that already
|
||||
exists in the config block with a conflicting value.
|
||||
|
||||
..todo :: Doesn't match server blocks whose server_name directives are
|
||||
split across multiple conf files.
|
||||
|
|
@ -292,13 +290,34 @@ class NginxParser(object):
|
|||
:param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
|
||||
whose information we use to match on
|
||||
:param list directives: The directives to add
|
||||
:param bool replace: Whether to only replace existing directives
|
||||
:param bool insert_at_top: True if the directives need to be inserted at the top
|
||||
of the server block instead of the bottom
|
||||
|
||||
"""
|
||||
self._modify_server_directives(vhost,
|
||||
functools.partial(_add_directives, directives, replace, insert_at_top))
|
||||
functools.partial(_add_directives, directives, insert_at_top))
|
||||
|
||||
def update_or_add_server_directives(self, vhost, directives, insert_at_top=False):
|
||||
"""Add or replace directives in the server block identified by vhost.
|
||||
|
||||
This method modifies vhost to be fully consistent with the new directives.
|
||||
|
||||
..note :: When a directive with the same name already exists in the
|
||||
config block, the first instance will be replaced. Otherwise, the directive
|
||||
will be appended/prepended to the config block as in add_server_directives.
|
||||
|
||||
..todo :: Doesn't match server blocks whose server_name directives are
|
||||
split across multiple conf files.
|
||||
|
||||
:param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
|
||||
whose information we use to match on
|
||||
:param list directives: The directives to add
|
||||
:param bool insert_at_top: True if the directives need to be inserted at the top
|
||||
of the server block instead of the bottom
|
||||
|
||||
"""
|
||||
self._modify_server_directives(vhost,
|
||||
functools.partial(_update_or_add_directives, directives, insert_at_top))
|
||||
|
||||
def remove_server_directives(self, vhost, directive_name, match_func=None):
|
||||
"""Remove all directives of type directive_name.
|
||||
|
|
@ -335,13 +354,14 @@ class NginxParser(object):
|
|||
except errors.MisconfigurationError as err:
|
||||
raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err)))
|
||||
|
||||
def duplicate_vhost(self, vhost_template, delete_default=False, only_directives=None):
|
||||
def duplicate_vhost(self, vhost_template, remove_singleton_listen_params=False,
|
||||
only_directives=None):
|
||||
"""Duplicate the vhost in the configuration files.
|
||||
|
||||
:param :class:`~certbot_nginx.obj.VirtualHost` vhost_template: The vhost
|
||||
whose information we copy
|
||||
:param bool delete_default: If we should remove default_server
|
||||
from listen directives in the block.
|
||||
:param bool remove_singleton_listen_params: If we should remove parameters
|
||||
from listen directives in the block that can only be used once per address
|
||||
:param list only_directives: If it exists, only duplicate the named directives. Only
|
||||
looks at first level of depth; does not expand includes.
|
||||
|
||||
|
|
@ -368,15 +388,21 @@ class NginxParser(object):
|
|||
|
||||
enclosing_block.append(raw_in_parsed)
|
||||
new_vhost.path[-1] = len(enclosing_block) - 1
|
||||
if delete_default:
|
||||
if remove_singleton_listen_params:
|
||||
for addr in new_vhost.addrs:
|
||||
addr.default = False
|
||||
addr.ipv6only = False
|
||||
for directive in enclosing_block[new_vhost.path[-1]][1]:
|
||||
if (len(directive) > 0 and directive[0] == 'listen'
|
||||
and 'default_server' in directive):
|
||||
del directive[directive.index('default_server')]
|
||||
if len(directive) > 0 and directive[0] == 'listen':
|
||||
if 'default_server' in directive:
|
||||
del directive[directive.index('default_server')]
|
||||
if 'default' in directive:
|
||||
del directive[directive.index('default')]
|
||||
if 'ipv6only=on' in directive:
|
||||
del directive[directive.index('ipv6only=on')]
|
||||
return new_vhost
|
||||
|
||||
|
||||
def _parse_ssl_options(ssl_options):
|
||||
if ssl_options is not None:
|
||||
try:
|
||||
|
|
@ -523,32 +549,23 @@ def _is_ssl_on_directive(entry):
|
|||
len(entry) == 2 and entry[0] == 'ssl' and
|
||||
entry[1] == 'on')
|
||||
|
||||
def _add_directives(directives, replace, insert_at_top, block):
|
||||
"""Adds or replaces directives in a config block.
|
||||
|
||||
When replace=False, it's an error to try and add a nonrepeatable directive that already
|
||||
exists in the config block with a conflicting value.
|
||||
|
||||
When replace=True and a directive with the same name already exists in the
|
||||
config block, the first instance will be replaced. Otherwise, the directive
|
||||
will be added to the config block.
|
||||
|
||||
..todo :: Find directives that are in included files.
|
||||
|
||||
:param list directives: The new directives.
|
||||
:param bool replace: Described above.
|
||||
:param bool insert_at_top: Described above.
|
||||
:param list block: The block to replace in
|
||||
|
||||
"""
|
||||
def _add_directives(directives, insert_at_top, block):
|
||||
"""Adds directives to a config block."""
|
||||
for directive in directives:
|
||||
_add_directive(block, directive, replace, insert_at_top)
|
||||
_add_directive(block, directive, insert_at_top)
|
||||
if block and '\n' not in block[-1]: # could be " \n " or ["\n"] !
|
||||
block.append(nginxparser.UnspacedList('\n'))
|
||||
|
||||
def _update_or_add_directives(directives, insert_at_top, block):
|
||||
"""Adds or replaces directives in a config block."""
|
||||
for directive in directives:
|
||||
_update_or_add_directive(block, directive, insert_at_top)
|
||||
if block and '\n' not in block[-1]: # could be " \n " or ["\n"] !
|
||||
block.append(nginxparser.UnspacedList('\n'))
|
||||
|
||||
|
||||
INCLUDE = 'include'
|
||||
REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'location', 'rewrite'])
|
||||
REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'rewrite'])
|
||||
COMMENT = ' managed by Certbot'
|
||||
COMMENT_BLOCK = [' ', '#', COMMENT]
|
||||
|
||||
|
|
@ -600,28 +617,20 @@ def _find_location(block, directive_name, match_func=None):
|
|||
return next((index for index, line in enumerate(block) \
|
||||
if line and line[0] == directive_name and (match_func is None or match_func(line))), None)
|
||||
|
||||
def _add_directive(block, directive, replace, insert_at_top):
|
||||
"""Adds or replaces a single directive in a config block.
|
||||
def _is_whitespace_or_comment(directive):
|
||||
"""Is this directive either a whitespace or comment directive?"""
|
||||
return len(directive) == 0 or directive[0] == '#'
|
||||
|
||||
See _add_directives for more documentation.
|
||||
|
||||
"""
|
||||
directive = nginxparser.UnspacedList(directive)
|
||||
def is_whitespace_or_comment(directive):
|
||||
"""Is this directive either a whitespace or comment directive?"""
|
||||
return len(directive) == 0 or directive[0] == '#'
|
||||
if is_whitespace_or_comment(directive):
|
||||
def _add_directive(block, directive, insert_at_top):
|
||||
if not isinstance(directive, nginxparser.UnspacedList):
|
||||
directive = nginxparser.UnspacedList(directive)
|
||||
if _is_whitespace_or_comment(directive):
|
||||
# whitespace or comment
|
||||
block.append(directive)
|
||||
return
|
||||
|
||||
location = _find_location(block, directive[0])
|
||||
|
||||
if replace:
|
||||
if location is not None:
|
||||
block[location] = directive
|
||||
comment_directive(block, location)
|
||||
return
|
||||
# Append or prepend directive. Fail if the name is not a repeatable directive name,
|
||||
# and there is already a copy of that directive with a different value
|
||||
# in the config file.
|
||||
|
|
@ -646,7 +655,7 @@ def _add_directive(block, directive, replace, insert_at_top):
|
|||
for included_directive in included_directives:
|
||||
included_dir_loc = _find_location(block, included_directive[0])
|
||||
included_dir_name = included_directive[0]
|
||||
if not is_whitespace_or_comment(included_directive) \
|
||||
if not _is_whitespace_or_comment(included_directive) \
|
||||
and not can_append(included_dir_loc, included_dir_name):
|
||||
if block[included_dir_loc] != included_directive:
|
||||
raise errors.MisconfigurationError(err_fmt.format(included_directive,
|
||||
|
|
@ -667,6 +676,30 @@ def _add_directive(block, directive, replace, insert_at_top):
|
|||
elif block[location] != directive:
|
||||
raise errors.MisconfigurationError(err_fmt.format(directive, block[location]))
|
||||
|
||||
def _update_directive(block, directive, location):
|
||||
block[location] = directive
|
||||
comment_directive(block, location)
|
||||
|
||||
def _update_or_add_directive(block, directive, insert_at_top):
|
||||
if not isinstance(directive, nginxparser.UnspacedList):
|
||||
directive = nginxparser.UnspacedList(directive)
|
||||
if _is_whitespace_or_comment(directive):
|
||||
# whitespace or comment
|
||||
block.append(directive)
|
||||
return
|
||||
|
||||
location = _find_location(block, directive[0])
|
||||
|
||||
# we can update directive
|
||||
if location is not None:
|
||||
_update_directive(block, directive, location)
|
||||
return
|
||||
|
||||
_add_directive(block, directive, insert_at_top)
|
||||
|
||||
def _is_certbot_comment(directive):
|
||||
return '#' in directive and COMMENT in directive
|
||||
|
||||
def _remove_directives(directive_name, match_func, block):
|
||||
"""Removes directives of name directive_name from a config block if match_func matches.
|
||||
"""
|
||||
|
|
@ -674,6 +707,9 @@ def _remove_directives(directive_name, match_func, block):
|
|||
location = _find_location(block, directive_name, match_func=match_func)
|
||||
if location is None:
|
||||
return
|
||||
# if the directive was made by us, remove the comment following
|
||||
if location + 1 < len(block) and _is_certbot_comment(block[location + 1]):
|
||||
del block[location + 1]
|
||||
del block[location]
|
||||
|
||||
def _apply_global_addr_ssl(addr_to_ssl, parsed_server):
|
||||
|
|
@ -707,7 +743,7 @@ def _parse_server_raw(server):
|
|||
if addr.ssl:
|
||||
parsed_server['ssl'] = True
|
||||
elif directive[0] == 'server_name':
|
||||
parsed_server['names'].update(directive[1:])
|
||||
parsed_server['names'].update(x.strip('"\'') for x in directive[1:])
|
||||
elif _is_ssl_on_directive(directive):
|
||||
parsed_server['ssl'] = True
|
||||
apply_ssl_to_all_addrs = True
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
"globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com"]))
|
||||
|
||||
def test_supported_enhancements(self):
|
||||
self.assertEqual(['redirect', 'staple-ocsp'],
|
||||
self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'],
|
||||
self.config.supported_enhancements())
|
||||
|
||||
def test_enhance(self):
|
||||
|
|
@ -113,8 +113,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
None, [0])
|
||||
self.config.parser.add_server_directives(
|
||||
mock_vhost,
|
||||
[['listen', ' ', '5001', ' ', 'ssl']],
|
||||
replace=False)
|
||||
[['listen', ' ', '5001', ' ', 'ssl']])
|
||||
self.config.save()
|
||||
|
||||
# pylint: disable=protected-access
|
||||
|
|
@ -206,9 +205,9 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
"example/chain.pem",
|
||||
None)
|
||||
|
||||
@mock.patch('certbot_nginx.parser.NginxParser.add_server_directives')
|
||||
def test_deploy_cert_raise_on_add_error(self, mock_add_server_directives):
|
||||
mock_add_server_directives.side_effect = errors.MisconfigurationError()
|
||||
@mock.patch('certbot_nginx.parser.NginxParser.update_or_add_server_directives')
|
||||
def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives):
|
||||
mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError()
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
self.config.deploy_cert,
|
||||
|
|
@ -510,6 +509,54 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
['return', '404'], ['#', ' managed by Certbot'], [], [], []]]],
|
||||
generated_conf)
|
||||
|
||||
def test_split_for_headers(self):
|
||||
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
|
||||
self.config.deploy_cert(
|
||||
"example.org",
|
||||
"example/cert.pem",
|
||||
"example/key.pem",
|
||||
"example/chain.pem",
|
||||
"example/fullchain.pem")
|
||||
self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security")
|
||||
generated_conf = self.config.parser.parsed[example_conf]
|
||||
self.assertEqual(
|
||||
[[['server'], [
|
||||
['server_name', '.example.com'],
|
||||
['server_name', 'example.*'], [],
|
||||
['listen', '5001', 'ssl'], ['#', ' managed by Certbot'],
|
||||
['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'],
|
||||
['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'],
|
||||
['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'],
|
||||
['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'],
|
||||
[], [],
|
||||
['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'],
|
||||
['#', ' managed by Certbot'],
|
||||
[], []]],
|
||||
[['server'], [
|
||||
['listen', '69.50.225.155:9000'],
|
||||
['listen', '127.0.0.1'],
|
||||
['server_name', '.example.com'],
|
||||
['server_name', 'example.*'],
|
||||
[], [], []]]],
|
||||
generated_conf)
|
||||
|
||||
def test_http_header_hsts(self):
|
||||
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
|
||||
self.config.enhance("www.example.com", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always']
|
||||
generated_conf = self.config.parser.parsed[example_conf]
|
||||
self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
|
||||
|
||||
def test_http_header_hsts_twice(self):
|
||||
self.config.enhance("www.example.com", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
self.assertRaises(
|
||||
errors.PluginEnhancementAlreadyPresent,
|
||||
self.config.enhance, "www.example.com",
|
||||
"ensure-http-header", "Strict-Transport-Security")
|
||||
|
||||
|
||||
@mock.patch('certbot_nginx.obj.VirtualHost.contains_list')
|
||||
def test_certbot_redirect_exists(self, mock_contains_list):
|
||||
# Test that we add no redirect statement if there is already a
|
||||
|
|
@ -592,7 +639,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
self.assertEqual([[['server'],
|
||||
[['listen', 'myhost', 'default_server'],
|
||||
['listen', 'otherhost', 'default_server'],
|
||||
['server_name', 'www.example.org'],
|
||||
['server_name', '"www.example.org"'],
|
||||
[['location', '/'],
|
||||
[['root', 'html'],
|
||||
['index', 'index.html', 'index.htm']]]]],
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class AddrTest(unittest.TestCase):
|
|||
self.addr5 = Addr.fromstring("myhost")
|
||||
self.addr6 = Addr.fromstring("80 default_server spdy")
|
||||
self.addr7 = Addr.fromstring("unix:/var/run/nginx.sock")
|
||||
self.addr8 = Addr.fromstring("*:80 default ssl")
|
||||
|
||||
def test_fromstring(self):
|
||||
self.assertEqual(self.addr1.get_addr(), "192.168.1.1")
|
||||
|
|
@ -46,6 +47,8 @@ class AddrTest(unittest.TestCase):
|
|||
self.assertFalse(self.addr6.ssl)
|
||||
self.assertTrue(self.addr6.default)
|
||||
|
||||
self.assertTrue(self.addr8.default)
|
||||
|
||||
self.assertEqual(None, self.addr7)
|
||||
|
||||
def test_str(self):
|
||||
|
|
@ -55,6 +58,7 @@ class AddrTest(unittest.TestCase):
|
|||
self.assertEqual(str(self.addr4), "*:80 default_server ssl")
|
||||
self.assertEqual(str(self.addr5), "myhost")
|
||||
self.assertEqual(str(self.addr6), "80 default_server")
|
||||
self.assertEqual(str(self.addr8), "*:80 default_server ssl")
|
||||
|
||||
def test_to_string(self):
|
||||
self.assertEqual(self.addr1.to_string(), "192.168.1.1")
|
||||
|
|
@ -77,7 +81,8 @@ class AddrTest(unittest.TestCase):
|
|||
from certbot_nginx.obj import Addr
|
||||
any_addresses = ("0.0.0.0:80 default_server ssl",
|
||||
"80 default_server ssl",
|
||||
"*:80 default_server ssl")
|
||||
"*:80 default_server ssl",
|
||||
"80 default ssl")
|
||||
for first, second in itertools.combinations(any_addresses, 2):
|
||||
self.assertEqual(Addr.fromstring(first), Addr.fromstring(second))
|
||||
|
||||
|
|
@ -143,6 +148,15 @@ class VirtualHostTest(unittest.TestCase):
|
|||
"filp",
|
||||
set([Addr.fromstring("localhost")]), False, False,
|
||||
set(['localhost']), raw4, [])
|
||||
raw_has_hsts = [
|
||||
['listen', '69.50.225.155:9000'],
|
||||
['server_name', 'return.com'],
|
||||
['add_header', 'always', 'set', 'Strict-Transport-Security', '\"max-age=31536000\"'],
|
||||
]
|
||||
self.vhost_has_hsts = VirtualHost(
|
||||
"filep",
|
||||
set([Addr.fromstring("localhost")]), False, False,
|
||||
set(['localhost']), raw_has_hsts, [])
|
||||
|
||||
def test_eq(self):
|
||||
from certbot_nginx.obj import Addr
|
||||
|
|
@ -162,6 +176,12 @@ class VirtualHostTest(unittest.TestCase):
|
|||
'enabled: False'])
|
||||
self.assertEqual(stringified, str(self.vhost1))
|
||||
|
||||
def test_has_header(self):
|
||||
self.assertTrue(self.vhost_has_hsts.has_header('Strict-Transport-Security'))
|
||||
self.assertFalse(self.vhost_has_hsts.has_header('Bogus-Header'))
|
||||
self.assertFalse(self.vhost1.has_header('Strict-Transport-Security'))
|
||||
self.assertFalse(self.vhost1.has_header('Bogus-Header'))
|
||||
|
||||
def test_contains_list(self):
|
||||
from certbot_nginx.obj import VirtualHost
|
||||
from certbot_nginx.obj import Addr
|
||||
|
|
|
|||
|
|
@ -191,6 +191,31 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
['server_name', '*.www.foo.com', '*.www.example.com']]
|
||||
self.assertTrue(nparser.has_ssl_on_directive(mock_vhost))
|
||||
|
||||
|
||||
def test_remove_server_directives(self):
|
||||
nparser = parser.NginxParser(self.config_path)
|
||||
mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'),
|
||||
None, None, None,
|
||||
set(['localhost',
|
||||
r'~^(www\.)?(example|bar)\.']),
|
||||
None, [10, 1, 9])
|
||||
example_com = nparser.abs_path('sites-enabled/example.com')
|
||||
names = set(['.example.com', 'example.*'])
|
||||
mock_vhost.filep = example_com
|
||||
mock_vhost.names = names
|
||||
mock_vhost.path = [0]
|
||||
nparser.add_server_directives(mock_vhost,
|
||||
[['foo', 'bar'], ['ssl_certificate',
|
||||
'/etc/ssl/cert2.pem']])
|
||||
nparser.remove_server_directives(mock_vhost, 'foo')
|
||||
nparser.remove_server_directives(mock_vhost, 'ssl_certificate')
|
||||
self.assertEqual(nparser.parsed[example_com],
|
||||
[[['server'], [['listen', '69.50.225.155:9000'],
|
||||
['listen', '127.0.0.1'],
|
||||
['server_name', '.example.com'],
|
||||
['server_name', 'example.*'],
|
||||
[]]]])
|
||||
|
||||
def test_add_server_directives(self):
|
||||
nparser = parser.NginxParser(self.config_path)
|
||||
mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'),
|
||||
|
|
@ -200,8 +225,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
None, [10, 1, 9])
|
||||
nparser.add_server_directives(mock_vhost,
|
||||
[['foo', 'bar'], ['\n ', 'ssl_certificate', ' ',
|
||||
'/etc/ssl/cert.pem']],
|
||||
replace=False)
|
||||
'/etc/ssl/cert.pem']])
|
||||
ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem')
|
||||
dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')])
|
||||
self.assertEqual(1, len(re.findall(ssl_re, dump)))
|
||||
|
|
@ -213,10 +237,8 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
mock_vhost.path = [0]
|
||||
nparser.add_server_directives(mock_vhost,
|
||||
[['foo', 'bar'], ['ssl_certificate',
|
||||
'/etc/ssl/cert2.pem']],
|
||||
replace=False)
|
||||
nparser.add_server_directives(mock_vhost, [['foo', 'bar']],
|
||||
replace=False)
|
||||
'/etc/ssl/cert2.pem']])
|
||||
nparser.add_server_directives(mock_vhost, [['foo', 'bar']])
|
||||
from certbot_nginx.parser import COMMENT
|
||||
self.assertEqual(nparser.parsed[example_com],
|
||||
[[['server'], [['listen', '69.50.225.155:9000'],
|
||||
|
|
@ -238,8 +260,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
nparser.add_server_directives,
|
||||
mock_vhost,
|
||||
[['foo', 'bar'],
|
||||
['ssl_certificate', '/etc/ssl/cert2.pem']],
|
||||
replace=False)
|
||||
['ssl_certificate', '/etc/ssl/cert2.pem']])
|
||||
|
||||
def test_comment_is_repeatable(self):
|
||||
nparser = parser.NginxParser(self.config_path)
|
||||
|
|
@ -249,12 +270,10 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
set(['.example.com', 'example.*']),
|
||||
None, [0])
|
||||
nparser.add_server_directives(mock_vhost,
|
||||
[['\n ', '#', ' ', 'what a nice comment']],
|
||||
replace=False)
|
||||
[['\n ', '#', ' ', 'what a nice comment']])
|
||||
nparser.add_server_directives(mock_vhost,
|
||||
[['\n ', 'include', ' ',
|
||||
nparser.abs_path('comment_in_file.conf')]],
|
||||
replace=False)
|
||||
nparser.abs_path('comment_in_file.conf')]])
|
||||
from certbot_nginx.parser import COMMENT
|
||||
self.assertEqual(nparser.parsed[example_com],
|
||||
[[['server'], [['listen', '69.50.225.155:9000'],
|
||||
|
|
@ -273,8 +292,8 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
target = set(['.example.com', 'example.*'])
|
||||
filep = nparser.abs_path('sites-enabled/example.com')
|
||||
mock_vhost = obj.VirtualHost(filep, None, None, None, target, None, [0])
|
||||
nparser.add_server_directives(
|
||||
mock_vhost, [['server_name', 'foobar.com']], replace=True)
|
||||
nparser.update_or_add_server_directives(
|
||||
mock_vhost, [['server_name', 'foobar.com']])
|
||||
from certbot_nginx.parser import COMMENT
|
||||
self.assertEqual(
|
||||
nparser.parsed[filep],
|
||||
|
|
@ -284,8 +303,8 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
['server_name', 'example.*'], []
|
||||
]]])
|
||||
mock_vhost.names = set(['foobar.com', 'example.*'])
|
||||
nparser.add_server_directives(
|
||||
mock_vhost, [['ssl_certificate', 'cert.pem']], replace=True)
|
||||
nparser.update_or_add_server_directives(
|
||||
mock_vhost, [['ssl_certificate', 'cert.pem']])
|
||||
self.assertEqual(
|
||||
nparser.parsed[filep],
|
||||
[[['server'], [['listen', '69.50.225.155:9000'],
|
||||
|
|
@ -411,7 +430,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
|
||||
vhosts = nparser.get_vhosts()
|
||||
default = [x for x in vhosts if 'default' in x.filep][0]
|
||||
new_vhost = nparser.duplicate_vhost(default, delete_default=True)
|
||||
new_vhost = nparser.duplicate_vhost(default, remove_singleton_listen_params=True)
|
||||
nparser.filedump(ext='')
|
||||
|
||||
# check properties of new vhost
|
||||
|
|
@ -429,6 +448,28 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
|
|||
self.assertEqual(len(default.raw), len(new_vhost_parsed.raw))
|
||||
self.assertTrue(next(iter(default.addrs)).super_eq(next(iter(new_vhost_parsed.addrs))))
|
||||
|
||||
def test_duplicate_vhost_remove_ipv6only(self):
|
||||
nparser = parser.NginxParser(self.config_path)
|
||||
|
||||
vhosts = nparser.get_vhosts()
|
||||
ipv6ssl = [x for x in vhosts if 'ipv6ssl' in x.filep][0]
|
||||
new_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=True)
|
||||
nparser.filedump(ext='')
|
||||
|
||||
for addr in new_vhost.addrs:
|
||||
self.assertFalse(addr.ipv6only)
|
||||
|
||||
identical_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=False)
|
||||
nparser.filedump(ext='')
|
||||
|
||||
called = False
|
||||
for addr in identical_vhost.addrs:
|
||||
if addr.ipv6:
|
||||
self.assertTrue(addr.ipv6only)
|
||||
called = True
|
||||
self.assertTrue(called)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
server {
|
||||
listen myhost default_server;
|
||||
listen otherhost default_server;
|
||||
server_name www.example.org;
|
||||
server_name "www.example.org";
|
||||
|
||||
location / {
|
||||
root html;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.22.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ http {
|
|||
|
||||
server {
|
||||
# IPv4.
|
||||
listen 5002;
|
||||
listen 5002 $default_server;
|
||||
# IPv6.
|
||||
listen [::]:5002 default ipv6only=on;
|
||||
listen [::]:5002 $default_server;
|
||||
server_name nginx.wtf nginx2.wtf;
|
||||
|
||||
root $root/webroot;
|
||||
|
|
@ -62,5 +62,36 @@ http {
|
|||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 5002;
|
||||
listen [::]:5002;
|
||||
server_name nginx3.wtf;
|
||||
|
||||
root $root/webroot;
|
||||
|
||||
location /.well-known/ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 8082;
|
||||
listen [::]:8082;
|
||||
server_name nginx4.wtf nginx5.wtf;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 5002;
|
||||
listen [::]:5002;
|
||||
listen 5001 ssl;
|
||||
listen [::]:5001 ssl;
|
||||
if (\$scheme != "https") {
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
server_name nginx6.wtf nginx7.wtf;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh -xe
|
||||
#!/bin/bash -xe
|
||||
# prerequisite: apt-get install --no-install-recommends nginx-light openssl
|
||||
|
||||
. ./tests/integration/_common.sh
|
||||
|
|
@ -6,13 +6,15 @@
|
|||
export PATH="/usr/sbin:$PATH" # /usr/sbin/nginx
|
||||
nginx_root="$root/nginx"
|
||||
mkdir $nginx_root
|
||||
original=$(root="$nginx_root" ./certbot-nginx/tests/boulder-integration.conf.sh)
|
||||
nginx_conf="$nginx_root/nginx.conf"
|
||||
echo "$original" > $nginx_conf
|
||||
|
||||
reload_nginx () {
|
||||
original=$(root="$nginx_root" ./certbot-nginx/tests/boulder-integration.conf.sh)
|
||||
nginx_conf="$nginx_root/nginx.conf"
|
||||
echo "$original" > $nginx_conf
|
||||
|
||||
killall nginx || true
|
||||
nginx -c $nginx_root/nginx.conf
|
||||
killall nginx || true
|
||||
nginx -c $nginx_root/nginx.conf
|
||||
}
|
||||
|
||||
certbot_test_nginx () {
|
||||
certbot_test \
|
||||
|
|
@ -32,10 +34,30 @@ test_deployment_and_rollback() {
|
|||
diff -q <(echo "$original") $nginx_conf
|
||||
}
|
||||
|
||||
export default_server="default_server"
|
||||
reload_nginx
|
||||
certbot_test_nginx --domains nginx.wtf run
|
||||
test_deployment_and_rollback nginx.wtf
|
||||
certbot_test_nginx --domains nginx2.wtf --preferred-challenges http
|
||||
test_deployment_and_rollback nginx2.wtf
|
||||
# Overlapping location block and server-block-level return 301
|
||||
certbot_test_nginx --domains nginx3.wtf --preferred-challenges http
|
||||
test_deployment_and_rollback nginx3.wtf
|
||||
# No matching server block; default_server exists
|
||||
certbot_test_nginx --domains nginx4.wtf --preferred-challenges http
|
||||
test_deployment_and_rollback nginx4.wtf
|
||||
# No matching server block; default_server does not exist
|
||||
export default_server=""
|
||||
reload_nginx
|
||||
if nginx -c $nginx_root/nginx.conf -T 2>/dev/null | grep "default_server"; then
|
||||
echo "Failed to remove default_server"
|
||||
exit 1
|
||||
fi
|
||||
certbot_test_nginx --domains nginx5.wtf --preferred-challenges http
|
||||
test_deployment_and_rollback nginx5.wtf
|
||||
# Mutiple domains, mix of matching and not
|
||||
certbot_test_nginx --domains nginx6.wtf,nginx7.wtf --preferred-challenges http
|
||||
test_deployment_and_rollback nginx6.wtf
|
||||
|
||||
# note: not reached if anything above fails, hence "killall" at the
|
||||
# top
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Certbot client."""
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '0.22.0.dev0'
|
||||
__version__ = '0.24.0.dev0'
|
||||
|
|
|
|||
|
|
@ -69,14 +69,15 @@ class AuthHandler(object):
|
|||
|
||||
# While there are still challenges remaining...
|
||||
while self._has_challenges(aauthzrs):
|
||||
resp = self._solve_challenges(aauthzrs)
|
||||
logger.info("Waiting for verification...")
|
||||
if config.debug_challenges:
|
||||
notify('Challenges loaded. Press continue to submit to CA. '
|
||||
'Pass "-v" for more info about challenges.', pause=True)
|
||||
with error_handler.ExitHandler(self._cleanup_challenges, aauthzrs):
|
||||
resp = self._solve_challenges(aauthzrs)
|
||||
logger.info("Waiting for verification...")
|
||||
if config.debug_challenges:
|
||||
notify('Challenges loaded. Press continue to submit to CA. '
|
||||
'Pass "-v" for more info about challenges.', pause=True)
|
||||
|
||||
# Send all Responses - this modifies achalls
|
||||
self._respond(aauthzrs, resp, best_effort)
|
||||
# Send all Responses - this modifies achalls
|
||||
self._respond(aauthzrs, resp, best_effort)
|
||||
|
||||
# Just make sure all decisions are complete.
|
||||
self.verify_authzr_complete(aauthzrs)
|
||||
|
|
@ -118,14 +119,13 @@ class AuthHandler(object):
|
|||
"""Get Responses for challenges from authenticators."""
|
||||
resp = []
|
||||
all_achalls = self._get_all_achalls(aauthzrs)
|
||||
with error_handler.ErrorHandler(self._cleanup_challenges, all_achalls):
|
||||
try:
|
||||
if all_achalls:
|
||||
resp = self.auth.perform(all_achalls)
|
||||
except errors.AuthorizationError:
|
||||
logger.critical("Failure in setting up challenges.")
|
||||
logger.info("Attempting to clean up outstanding challenges...")
|
||||
raise
|
||||
try:
|
||||
if all_achalls:
|
||||
resp = self.auth.perform(all_achalls)
|
||||
except errors.AuthorizationError:
|
||||
logger.critical("Failure in setting up challenges.")
|
||||
logger.info("Attempting to clean up outstanding challenges...")
|
||||
raise
|
||||
|
||||
assert len(resp) == len(all_achalls)
|
||||
|
||||
|
|
@ -147,13 +147,10 @@ class AuthHandler(object):
|
|||
"""
|
||||
# TODO: chall_update is a dirty hack to get around acme-spec #105
|
||||
chall_update = dict()
|
||||
active_achalls = self._send_responses(aauthzrs, resp, chall_update)
|
||||
self._send_responses(aauthzrs, resp, chall_update)
|
||||
|
||||
# Check for updated status...
|
||||
try:
|
||||
self._poll_challenges(aauthzrs, chall_update, best_effort)
|
||||
finally:
|
||||
self._cleanup_challenges(aauthzrs, active_achalls)
|
||||
self._poll_challenges(aauthzrs, chall_update, best_effort)
|
||||
|
||||
def _send_responses(self, aauthzrs, resps, chall_update):
|
||||
"""Send responses and make sure errors are handled.
|
||||
|
|
@ -161,6 +158,13 @@ class AuthHandler(object):
|
|||
:param aauthzrs: authorizations and the selected annotated challenges
|
||||
to try and perform
|
||||
:type aauthzrs: `list` of `AnnotatedAuthzr`
|
||||
:param resps: challenge responses from the authenticator where
|
||||
each response at index i corresponds to the annotated
|
||||
challenge at index i in the list returned by
|
||||
:func:`_get_all_achalls`
|
||||
:type resps: `collections.abc.Iterable` of
|
||||
:class:`~acme.challenges.ChallengeResponse` or `False` or
|
||||
`None`
|
||||
:param dict chall_update: parameter that is updated to hold
|
||||
aauthzr index to list of outstanding solved annotated challenges
|
||||
|
||||
|
|
@ -185,7 +189,7 @@ class AuthHandler(object):
|
|||
return active_achalls
|
||||
|
||||
def _poll_challenges(self, aauthzrs, chall_update,
|
||||
best_effort, min_sleep=3, max_rounds=15):
|
||||
best_effort, min_sleep=3, max_rounds=30):
|
||||
"""Wait for all challenge results to be determined."""
|
||||
indices_to_check = set(chall_update.keys())
|
||||
comp_indices = set()
|
||||
|
|
@ -287,19 +291,19 @@ class AuthHandler(object):
|
|||
chall_prefs.extend(plugin_pref)
|
||||
return chall_prefs
|
||||
|
||||
def _cleanup_challenges(self, aauthzrs, achall_list=None):
|
||||
def _cleanup_challenges(self, aauthzrs, achalls=None):
|
||||
"""Cleanup challenges.
|
||||
|
||||
If achall_list is not provided, cleanup all achallenges.
|
||||
:param aauthzrs: authorizations and their selected annotated
|
||||
challenges
|
||||
:type aauthzrs: `list` of `AnnotatedAuthzr`
|
||||
:param achalls: annotated challenges to cleanup
|
||||
:type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge`
|
||||
|
||||
"""
|
||||
logger.info("Cleaning up challenges")
|
||||
|
||||
if achall_list is None:
|
||||
if achalls is None:
|
||||
achalls = self._get_all_achalls(aauthzrs)
|
||||
else:
|
||||
achalls = achall_list
|
||||
|
||||
if achalls:
|
||||
self.auth.cleanup(achalls)
|
||||
for achall in achalls:
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ CLI_DEFAULTS = dict(
|
|||
dns_route53=False
|
||||
|
||||
)
|
||||
STAGING_URI = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
STAGING_URI = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
# The set of reasons for revoking a certificate is defined in RFC 5280 in
|
||||
# section 5.3.1. The reasons that users are allowed to submit are restricted to
|
||||
|
|
@ -135,13 +135,13 @@ RENEWER_DEFAULTS = dict(
|
|||
"""Defaults for renewer script."""
|
||||
|
||||
|
||||
ENHANCEMENTS = ["redirect", "http-header", "ocsp-stapling", "spdy"]
|
||||
ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling", "spdy"]
|
||||
"""List of possible :class:`certbot.interfaces.IInstaller`
|
||||
enhancements.
|
||||
|
||||
List of expected options parameters:
|
||||
- redirect: None
|
||||
- http-header: TODO
|
||||
- ensure-http-header: name of header (i.e. Strict-Transport-Security)
|
||||
- ocsp-stapling: certificate chain file path
|
||||
- spdy: TODO
|
||||
|
||||
|
|
|
|||
|
|
@ -445,5 +445,5 @@ def cert_and_chain_from_fullchain(fullchain_pem):
|
|||
"""
|
||||
cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, fullchain_pem)).decode()
|
||||
chain = fullchain_pem[len(cert):]
|
||||
chain = fullchain_pem[len(cert):].lstrip()
|
||||
return (cert, chain)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ if os.name != "nt":
|
|||
if signal.getsignal(signal_code) != signal.SIG_IGN:
|
||||
_SIGNALS.append(signal_code)
|
||||
|
||||
|
||||
class ErrorHandler(object):
|
||||
"""Context manager for running code that must be cleaned up on failure.
|
||||
|
||||
|
|
@ -55,6 +54,7 @@ class ErrorHandler(object):
|
|||
|
||||
"""
|
||||
def __init__(self, func=None, *args, **kwargs):
|
||||
self.call_on_regular_exit = False
|
||||
self.body_executed = False
|
||||
self.funcs = []
|
||||
self.prev_handlers = {}
|
||||
|
|
@ -70,8 +70,11 @@ class ErrorHandler(object):
|
|||
self.body_executed = True
|
||||
retval = False
|
||||
# SystemExit is ignored to properly handle forks that don't exec
|
||||
if exec_type in (None, SystemExit):
|
||||
if exec_type is SystemExit:
|
||||
return retval
|
||||
elif exec_type is None:
|
||||
if not self.call_on_regular_exit:
|
||||
return retval
|
||||
elif exec_type is errors.SignalExit:
|
||||
logger.debug("Encountered signals: %s", self.received_signals)
|
||||
retval = True
|
||||
|
|
@ -136,3 +139,15 @@ class ErrorHandler(object):
|
|||
for signum in self.received_signals:
|
||||
logger.debug("Calling signal %s", signum)
|
||||
os.kill(os.getpid(), signum)
|
||||
|
||||
class ExitHandler(ErrorHandler):
|
||||
"""Context manager for running code that must be cleaned up.
|
||||
|
||||
Subclass of ErrorHandler, with the same usage and parameters.
|
||||
In addition to cleaning up on all signals, also cleans up on
|
||||
regular exit.
|
||||
"""
|
||||
def __init__(self, func=None, *args, **kwargs):
|
||||
ErrorHandler.__init__(self, func, *args, **kwargs)
|
||||
self.call_on_regular_exit = True
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,10 @@ class NotSupportedError(PluginError):
|
|||
"""Certbot Plugin function not supported error."""
|
||||
|
||||
|
||||
class PluginStorageError(PluginError):
|
||||
"""Certbot Plugin Storage error."""
|
||||
|
||||
|
||||
class StandaloneBindError(Error):
|
||||
"""Standalone plugin bind error."""
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import logging.handlers
|
|||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from acme import messages
|
||||
|
|
@ -148,7 +147,6 @@ def setup_log_file_handler(config, logfile, fmt):
|
|||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ from certbot import interfaces
|
|||
from certbot import reverter
|
||||
from certbot import util
|
||||
|
||||
from certbot.plugins.storage import PluginStorage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -99,7 +101,6 @@ class Plugin(object):
|
|||
def conf(self, var):
|
||||
"""Find a configuration value for variable ``var``."""
|
||||
return getattr(self.config, self.dest(var))
|
||||
# other
|
||||
|
||||
|
||||
class Installer(Plugin):
|
||||
|
|
@ -110,6 +111,7 @@ class Installer(Plugin):
|
|||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Installer, self).__init__(*args, **kwargs)
|
||||
self.storage = PluginStorage(self.config, self.name)
|
||||
self.reverter = reverter.Reverter(self.config)
|
||||
|
||||
def add_to_checkpoint(self, save_files, save_notes, temporary=False):
|
||||
|
|
|
|||
117
certbot/plugins/storage.py
Normal file
117
certbot/plugins/storage.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""Plugin storage class."""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from certbot import errors
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PluginStorage(object):
|
||||
"""Class implementing storage functionality for plugins"""
|
||||
|
||||
def __init__(self, config, classkey):
|
||||
"""Initializes PluginStorage object storing required configuration
|
||||
options.
|
||||
|
||||
:param .configuration.NamespaceConfig config: Configuration object
|
||||
:param str classkey: class name to use as root key in storage file
|
||||
|
||||
"""
|
||||
|
||||
self._config = config
|
||||
self._classkey = classkey
|
||||
self._initialized = False
|
||||
self._data = None
|
||||
self._storagepath = None
|
||||
|
||||
def _initialize_storage(self):
|
||||
"""Initializes PluginStorage data and reads current state from the disk
|
||||
if the storage json exists."""
|
||||
|
||||
self._storagepath = os.path.join(self._config.config_dir, ".pluginstorage.json")
|
||||
self._load()
|
||||
self._initialized = True
|
||||
|
||||
def _load(self):
|
||||
"""Reads PluginStorage content from the disk to a dict structure
|
||||
|
||||
:raises .errors.PluginStorageError: when unable to open or read the file
|
||||
"""
|
||||
data = dict()
|
||||
filedata = ""
|
||||
try:
|
||||
with open(self._storagepath, 'r') as fh:
|
||||
filedata = fh.read()
|
||||
except IOError as e:
|
||||
errmsg = "Could not read PluginStorage data file: {0} : {1}".format(
|
||||
self._storagepath, str(e))
|
||||
if os.path.isfile(self._storagepath):
|
||||
# Only error out if file exists, but cannot be read
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
try:
|
||||
data = json.loads(filedata)
|
||||
except ValueError:
|
||||
if not filedata:
|
||||
logger.debug("Plugin storage file %s was empty, no values loaded",
|
||||
self._storagepath)
|
||||
else:
|
||||
errmsg = "PluginStorage file {0} is corrupted.".format(
|
||||
self._storagepath)
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
self._data = data
|
||||
|
||||
def save(self):
|
||||
"""Saves PluginStorage content to disk
|
||||
|
||||
:raises .errors.PluginStorageError: when unable to serialize the data
|
||||
or write it to the filesystem
|
||||
"""
|
||||
if not self._initialized:
|
||||
errmsg = "Unable to save, no values have been added to PluginStorage."
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
|
||||
try:
|
||||
serialized = json.dumps(self._data)
|
||||
except TypeError as e:
|
||||
errmsg = "Could not serialize PluginStorage data: {0}".format(
|
||||
str(e))
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
try:
|
||||
with os.fdopen(os.open(self._storagepath,
|
||||
os.O_WRONLY | os.O_CREAT, 0o600), 'w') as fh:
|
||||
fh.write(serialized)
|
||||
except IOError as e:
|
||||
errmsg = "Could not write PluginStorage data to file {0} : {1}".format(
|
||||
self._storagepath, str(e))
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
|
||||
def put(self, key, value):
|
||||
"""Put configuration value to PluginStorage
|
||||
|
||||
:param str key: Key to store the value to
|
||||
:param value: Data to store
|
||||
"""
|
||||
if not self._initialized:
|
||||
self._initialize_storage()
|
||||
|
||||
if not self._classkey in self._data.keys():
|
||||
self._data[self._classkey] = dict()
|
||||
self._data[self._classkey][key] = value
|
||||
|
||||
def fetch(self, key):
|
||||
"""Get configuration value from PluginStorage
|
||||
|
||||
:param str key: Key to get value from the storage
|
||||
|
||||
:raises KeyError: If the key doesn't exist in the storage
|
||||
"""
|
||||
if not self._initialized:
|
||||
self._initialize_storage()
|
||||
|
||||
return self._data[self._classkey][key]
|
||||
117
certbot/plugins/storage_test.py
Normal file
117
certbot/plugins/storage_test.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""Tests for certbot.plugins.storage.PluginStorage"""
|
||||
import json
|
||||
import mock
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot.plugins import common
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
class PluginStorageTest(test_util.ConfigTestCase):
|
||||
"""Test for certbot.plugins.storage.PluginStorage"""
|
||||
|
||||
def setUp(self):
|
||||
super(PluginStorageTest, self).setUp()
|
||||
self.plugin_cls = common.Installer
|
||||
os.mkdir(self.config.config_dir)
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
self.plugin = self.plugin_cls(config=self.config, name="mockplugin")
|
||||
|
||||
def test_load_errors_cant_read(self):
|
||||
with open(os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json"), "w") as fh:
|
||||
fh.write("dummy")
|
||||
# When unable to read file that exists
|
||||
mock_open = mock.mock_open()
|
||||
mock_open.side_effect = IOError
|
||||
self.plugin.storage.storagepath = os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json")
|
||||
with mock.patch("six.moves.builtins.open", mock_open):
|
||||
with mock.patch('os.path.isfile', return_value=True):
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin.storage._load) # pylint: disable=protected-access
|
||||
|
||||
def test_load_errors_empty(self):
|
||||
with open(os.path.join(self.config.config_dir, ".pluginstorage.json"), "w") as fh:
|
||||
fh.write('')
|
||||
with mock.patch("certbot.plugins.storage.logger.debug") as mock_log:
|
||||
# Should not error out but write a debug log line instead
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
nocontent = self.plugin_cls(self.config, "mockplugin")
|
||||
self.assertRaises(KeyError,
|
||||
nocontent.storage.fetch, "value")
|
||||
self.assertTrue(mock_log.called)
|
||||
self.assertTrue("no values loaded" in mock_log.call_args[0][0])
|
||||
|
||||
def test_load_errors_corrupted(self):
|
||||
with open(os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json"), "w") as fh:
|
||||
fh.write('invalid json')
|
||||
with mock.patch("certbot.plugins.storage.logger.error") as mock_log:
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
corrupted = self.plugin_cls(self.config, "mockplugin")
|
||||
self.assertRaises(errors.PluginError,
|
||||
corrupted.storage.fetch,
|
||||
"value")
|
||||
self.assertTrue("is corrupted" in mock_log.call_args[0][0])
|
||||
|
||||
def test_save_errors_cant_serialize(self):
|
||||
with mock.patch("certbot.plugins.storage.logger.error") as mock_log:
|
||||
# Set data as something that can't be serialized
|
||||
self.plugin.storage._initialized = True # pylint: disable=protected-access
|
||||
self.plugin.storage.storagepath = "/tmp/whatever"
|
||||
self.plugin.storage._data = self.plugin_cls # pylint: disable=protected-access
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin.storage.save)
|
||||
self.assertTrue("Could not serialize" in mock_log.call_args[0][0])
|
||||
|
||||
def test_save_errors_unable_to_write_file(self):
|
||||
mock_open = mock.mock_open()
|
||||
mock_open.side_effect = IOError
|
||||
with mock.patch("os.open", mock_open):
|
||||
with mock.patch("certbot.plugins.storage.logger.error") as mock_log:
|
||||
self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access
|
||||
self.plugin.storage._initialized = True # pylint: disable=protected-access
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin.storage.save)
|
||||
self.assertTrue("Could not write" in mock_log.call_args[0][0])
|
||||
|
||||
def test_save_uninitialized(self):
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin_cls(self.config, "x").storage.save)
|
||||
|
||||
def test_namespace_isolation(self):
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
plugin1 = self.plugin_cls(self.config, "first")
|
||||
plugin2 = self.plugin_cls(self.config, "second")
|
||||
plugin1.storage.put("first_key", "first_value")
|
||||
self.assertRaises(KeyError,
|
||||
plugin2.storage.fetch, "first_key")
|
||||
self.assertRaises(KeyError,
|
||||
plugin2.storage.fetch, "first")
|
||||
self.assertEqual(plugin1.storage.fetch("first_key"), "first_value")
|
||||
|
||||
|
||||
def test_saved_state(self):
|
||||
self.plugin.storage.put("testkey", "testvalue")
|
||||
# Write to disk
|
||||
self.plugin.storage.save()
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
another = self.plugin_cls(self.config, "mockplugin")
|
||||
self.assertEqual(another.storage.fetch("testkey"), "testvalue")
|
||||
|
||||
with open(os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json"), 'r') as fh:
|
||||
psdata = fh.read()
|
||||
psjson = json.loads(psdata)
|
||||
self.assertTrue("mockplugin" in psjson.keys())
|
||||
self.assertEqual(len(psjson), 1)
|
||||
self.assertEqual(psjson["mockplugin"]["testkey"], "testvalue")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
@ -278,6 +278,43 @@ class HandleAuthorizationsTest(unittest.TestCase):
|
|||
self.assertRaises(
|
||||
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
|
||||
|
||||
def test_perform_error(self):
|
||||
self.mock_auth.perform.side_effect = errors.AuthorizationError
|
||||
|
||||
authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=True)
|
||||
mock_order = mock.MagicMock(authorizations=[authzr])
|
||||
self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
|
||||
|
||||
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
|
||||
self.assertEqual(
|
||||
self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
|
||||
|
||||
@mock.patch("certbot.auth_handler.AuthHandler._respond")
|
||||
def test_respond_error(self, mock_respond):
|
||||
authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)]
|
||||
mock_order = mock.MagicMock(authorizations=authzrs)
|
||||
mock_respond.side_effect = errors.AuthorizationError
|
||||
|
||||
self.assertRaises(
|
||||
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
|
||||
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
|
||||
self.assertEqual(
|
||||
self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
|
||||
|
||||
@mock.patch("certbot.auth_handler.AuthHandler._poll_challenges")
|
||||
@mock.patch("certbot.auth_handler.AuthHandler.verify_authzr_complete")
|
||||
def test_incomplete_authzr_error(self, mock_verify, mock_poll):
|
||||
authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)]
|
||||
mock_order = mock.MagicMock(authorizations=authzrs)
|
||||
mock_verify.side_effect = errors.AuthorizationError
|
||||
mock_poll.side_effect = self._validate_all
|
||||
|
||||
self.assertRaises(
|
||||
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
|
||||
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
|
||||
self.assertEqual(
|
||||
self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
|
||||
|
||||
def _validate_all(self, aauthzrs, unused_1, unused_2):
|
||||
for i, aauthzr in enumerate(aauthzrs):
|
||||
azr = aauthzr.authzr
|
||||
|
|
|
|||
|
|
@ -380,10 +380,12 @@ class CertAndChainFromFullchainTest(unittest.TestCase):
|
|||
cert_pem = CERT.decode()
|
||||
chain_pem = cert_pem + SS_CERT.decode()
|
||||
fullchain_pem = cert_pem + chain_pem
|
||||
spacey_fullchain_pem = cert_pem + u'\n' + chain_pem
|
||||
from certbot.crypto_util import cert_and_chain_from_fullchain
|
||||
cert_out, chain_out = cert_and_chain_from_fullchain(fullchain_pem)
|
||||
self.assertEqual(cert_out, cert_pem)
|
||||
self.assertEqual(chain_out, chain_pem)
|
||||
for fullchain in (fullchain_pem, spacey_fullchain_pem):
|
||||
cert_out, chain_out = cert_and_chain_from_fullchain(fullchain)
|
||||
self.assertEqual(cert_out, cert_pem)
|
||||
self.assertEqual(chain_out, chain_pem)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def send_signal(signum):
|
|||
|
||||
|
||||
class ErrorHandlerTest(unittest.TestCase):
|
||||
"""Tests for certbot.error_handler."""
|
||||
"""Tests for certbot.error_handler.ErrorHandler."""
|
||||
|
||||
def setUp(self):
|
||||
from certbot import error_handler
|
||||
|
|
@ -47,6 +47,7 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
self.handler = error_handler.ErrorHandler(self.init_func,
|
||||
*self.init_args,
|
||||
**self.init_kwargs)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.signals = error_handler._SIGNALS
|
||||
|
||||
|
|
@ -113,6 +114,33 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
pass
|
||||
self.assertFalse(self.init_func.called)
|
||||
|
||||
def test_regular_exit(self):
|
||||
func = mock.MagicMock()
|
||||
self.handler.register(func)
|
||||
with self.handler:
|
||||
pass
|
||||
self.init_func.assert_not_called()
|
||||
func.assert_not_called()
|
||||
|
||||
|
||||
class ExitHandlerTest(ErrorHandlerTest):
|
||||
"""Tests for certbot.error_handler.ExitHandler."""
|
||||
|
||||
def setUp(self):
|
||||
from certbot import error_handler
|
||||
super(ExitHandlerTest, self).setUp()
|
||||
self.handler = error_handler.ExitHandler(self.init_func,
|
||||
*self.init_args,
|
||||
**self.init_kwargs)
|
||||
|
||||
def test_regular_exit(self):
|
||||
func = mock.MagicMock()
|
||||
self.handler.register(func)
|
||||
with self.handler:
|
||||
pass
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
func.assert_called_once_with()
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase):
|
|||
handler.close()
|
||||
|
||||
self.assertEqual(handler.level, logging.DEBUG)
|
||||
self.assertEqual(handler.formatter.converter, time.gmtime)
|
||||
self.assertEqual(handler.formatter.converter, time.localtime)
|
||||
|
||||
expected_path = os.path.join(self.config.logs_dir, log_file)
|
||||
self.assertEqual(log_path, expected_path)
|
||||
|
|
|
|||
|
|
@ -726,7 +726,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
self.test_rc.configuration["renewalparams"] = {}
|
||||
rp = self.test_rc.configuration["renewalparams"]
|
||||
self.assertEqual(self.test_rc.is_test_cert, False)
|
||||
rp["server"] = "https://acme-staging.api.letsencrypt.org/directory"
|
||||
rp["server"] = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
self.assertEqual(self.test_rc.is_test_cert, True)
|
||||
rp["server"] = "https://staging.someotherca.com/directory"
|
||||
self.assertEqual(self.test_rc.is_test_cert, True)
|
||||
|
|
|
|||
2
certbot/tests/testdata/sample-renewal.conf
vendored
2
certbot/tests/testdata/sample-renewal.conf
vendored
|
|
@ -61,7 +61,7 @@ chain_path = /home/ubuntu/letsencrypt/chain.pem
|
|||
break_my_certs = False
|
||||
standalone = True
|
||||
manual = False
|
||||
server = https://acme-staging.api.letsencrypt.org/directory
|
||||
server = https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
standalone_supported_challenges = "tls-sni-01,http-01"
|
||||
webroot = False
|
||||
os_packages_only = False
|
||||
|
|
|
|||
|
|
@ -604,7 +604,7 @@ def enforce_domain_sanity(domain):
|
|||
def is_wildcard_domain(domain):
|
||||
""""Is domain a wildcard domain?
|
||||
|
||||
:param damain: domain to check
|
||||
:param domain: domain to check
|
||||
:type domain: `bytes` or `str` or `unicode`
|
||||
|
||||
:returns: True if domain is a wildcard, otherwise, False
|
||||
|
|
|
|||
|
|
@ -3,3 +3,7 @@
|
|||
|
||||
.. automodule:: certbot.constants
|
||||
:members:
|
||||
:exclude-members: SSL_DHPARAMS_SRC
|
||||
|
||||
.. autodata:: SSL_DHPARAMS_SRC
|
||||
:annotation: = '/path/to/certbot/ssl-dhparams.pem'
|
||||
|
|
|
|||
|
|
@ -107,8 +107,8 @@ optional arguments:
|
|||
case, and to know when to deprecate support for past
|
||||
Python versions and flags. If you wish to hide this
|
||||
information from the Let's Encrypt server, set this to
|
||||
"". (default: CertbotACMEClient/0.21.1 (certbot;
|
||||
darwin 10.13.3) Authenticator/XXX Installer/YYY
|
||||
"". (default: CertbotACMEClient/0.23.0 (certbot;
|
||||
darwin 10.13.4) Authenticator/XXX Installer/YYY
|
||||
(SUBCOMMAND; flags: FLAGS) Py/2.7.14). The flags
|
||||
encoded in the user agent are: --duplicate, --force-
|
||||
renew, --allow-subset-of-names, -n, and whether any
|
||||
|
|
@ -200,7 +200,7 @@ testing:
|
|||
--test-cert, --staging
|
||||
Use the staging server to obtain or revoke test
|
||||
(invalid) certificates; equivalent to --server https
|
||||
://acme-staging.api.letsencrypt.org/directory
|
||||
://acme-staging-v02.api.letsencrypt.org/directory
|
||||
(default: False)
|
||||
--debug Show tracebacks in case of errors, and allow certbot-
|
||||
auto execution on experimental platforms (default:
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ running:
|
|||
|
||||
If you're on macOS, we recommend you skip the rest of this section and instead
|
||||
run Certbot in Docker. You can find instructions for how to do this :ref:`here
|
||||
<docker>`. If you're running on Linux, you can run the following commands to
|
||||
<docker-dev>`. If you're running on Linux, you can run the following commands to
|
||||
install dependencies and set up a virtual environment where you can run
|
||||
Certbot. You will need to repeat this when Certbot's dependencies change or when
|
||||
a new plugin is introduced.
|
||||
|
|
@ -43,7 +43,7 @@ each shell where you're working:
|
|||
.. code-block:: shell
|
||||
|
||||
source ./venv/bin/activate
|
||||
export SERVER=https://acme-staging.api.letsencrypt.org/directory
|
||||
export SERVER=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
source tests/integration/_common.sh
|
||||
|
||||
After that, your shell will be using the virtual environment, your copy of
|
||||
|
|
@ -376,8 +376,11 @@ commands:
|
|||
This should generate documentation in the ``docs/_build/html``
|
||||
directory.
|
||||
|
||||
.. note:: If you skipped the "Getting Started" instructions above,
|
||||
run ``pip install -e ".[docs]"`` to install Certbot's docs extras modules.
|
||||
|
||||
.. _docker:
|
||||
|
||||
.. _docker-dev:
|
||||
|
||||
Running the client with Docker
|
||||
==============================
|
||||
|
|
@ -443,10 +446,10 @@ For squeeze you will need to:
|
|||
FreeBSD
|
||||
-------
|
||||
|
||||
Packages can be installed on FreeBSD using ``pkg``,
|
||||
or any other port-management tool (``portupgrade``, ``portmanager``, etc.)
|
||||
from the pre-built package or can be built and installed from ports.
|
||||
Either way will ensure proper installation of all the dependencies required
|
||||
Packages can be installed on FreeBSD using ``pkg``,
|
||||
or any other port-management tool (``portupgrade``, ``portmanager``, etc.)
|
||||
from the pre-built package or can be built and installed from ports.
|
||||
Either way will ensure proper installation of all the dependencies required
|
||||
for the package.
|
||||
|
||||
FreeBSD by default uses ``tcsh``. In order to activate virtualenv (see
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ Disable and remove the swapfile once the virtual environment is constructed::
|
|||
user@webserver:~$ sudo swapoff /tmp/swapfile
|
||||
user@webserver:~$ sudo rm /tmp/swapfile
|
||||
|
||||
.. _docker-user:
|
||||
|
||||
Running with Docker
|
||||
-------------------
|
||||
|
||||
|
|
@ -115,13 +117,17 @@ these make much sense to you, you should definitely use the
|
|||
certbot-auto_ method, which enables you to use installer plugins
|
||||
that cover both of those hard topics.
|
||||
|
||||
If you're still not convinced and have decided to use this method,
|
||||
from the server that the domain you're requesting a certficate for resolves
|
||||
to, `install Docker`_, then issue the following command:
|
||||
If you're still not convinced and have decided to use this method, from
|
||||
the server that the domain you're requesting a certficate for resolves
|
||||
to, `install Docker`_, then issue a command like the one found below. If
|
||||
you are using Certbot with the :ref:`Standalone` plugin, you will need
|
||||
to make the port it uses accessible from outside of the container by
|
||||
including something like ``-p 80:80`` or ``-p 443:443`` on the command
|
||||
line before ``certbot/certbot``.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo docker run -it --rm -p 443:443 -p 80:80 --name certbot \
|
||||
sudo docker run -it --rm --name certbot \
|
||||
-v "/etc/letsencrypt:/etc/letsencrypt" \
|
||||
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
|
||||
certbot/certbot certonly
|
||||
|
|
@ -131,6 +137,19 @@ Running Certbot with the ``certonly`` command will obtain a certificate and plac
|
|||
within Docker, you must install the certificate manually according to the procedure
|
||||
recommended by the provider of your webserver.
|
||||
|
||||
There are also Docker images for each of Certbot's DNS plugins available
|
||||
at https://hub.docker.com/u/certbot which automate doing domain
|
||||
validation over DNS for popular providers. To use one, just replace
|
||||
``certbot/certbot`` in the command above with the name of the image you
|
||||
want to use. For example, to use Certbot's plugin for Amazon Route 53,
|
||||
you'd use ``certbot/dns-route53``. You may also need to add flags to
|
||||
Certbot and/or mount additional directories to provide access to your
|
||||
DNS API credentials as specified in the :ref:`DNS plugin documentation
|
||||
<dns_plugins>`. If you would like to obtain a wildcard certificate from
|
||||
Let's Encrypt's ACMEv2 server, you'll need to include ``--server
|
||||
https://acme-v02.api.letsencrypt.org/directory`` on the command line as
|
||||
well.
|
||||
|
||||
For more information about the layout
|
||||
of the ``/etc/letsencrypt`` directory, see :ref:`where-certs`.
|
||||
|
||||
|
|
@ -187,10 +206,11 @@ want to use the Apache plugin, it has to be installed separately:
|
|||
emerge -av app-crypt/certbot
|
||||
emerge -av app-crypt/certbot-apache
|
||||
|
||||
When using the Apache plugin, you will run into a "cannot find a cert or key
|
||||
directive" error if you're sporting the default Gentoo ``httpd.conf``.
|
||||
You can fix this by commenting out two lines in ``/etc/apache2/httpd.conf``
|
||||
as follows:
|
||||
When using the Apache plugin, you will run into a "cannot find an
|
||||
SSLCertificateFile directive" or "cannot find an SSLCertificateKeyFile
|
||||
directive for certificate" error if you're sporting the default Gentoo
|
||||
``httpd.conf``. You can fix this by commenting out two lines in
|
||||
``/etc/apache2/httpd.conf`` as follows:
|
||||
|
||||
Change
|
||||
|
||||
|
|
@ -240,4 +260,3 @@ whole process is described in the :doc:`contributing`.
|
|||
e.g. ``sudo python setup.py install``, ``sudo pip install``, ``sudo
|
||||
./venv/bin/...``. These modes of operation might corrupt your operating
|
||||
system and are **not supported** by the Certbot team!
|
||||
|
||||
|
|
|
|||
100
docs/using.rst
100
docs/using.rst
|
|
@ -45,7 +45,7 @@ a combination_ of distinct authenticator and installer plugins.
|
|||
Plugin Auth Inst Notes Challenge types (and port)
|
||||
=========== ==== ==== =============================================================== =============================
|
||||
apache_ Y Y | Automates obtaining and installing a certificate with Apache tls-sni-01_ (443)
|
||||
| 2.4 on Debian-based distributions with ``libaugeas0`` 1.0+.
|
||||
| 2.4 on OSes with ``libaugeas0`` 1.0+.
|
||||
webroot_ Y N | Obtains a certificate by writing to the webroot directory of http-01_ (80)
|
||||
| an already running webserver.
|
||||
nginx_ Y Y | Automates obtaining and installing a certificate with Nginx. tls-sni-01_ (443)
|
||||
|
|
@ -54,12 +54,19 @@ standalone_ Y N | Uses a "standalone" webserver to obtain a certificate.
|
|||
| Requires port 80 or 443 to be available. This is useful on tls-sni-01_ (443)
|
||||
| systems with no webserver, or when direct integration with
|
||||
| the local webserver is not supported or not desired.
|
||||
|dns_plugs| Y N | This category of plugins automates obtaining a certificate by dns-01_ (53)
|
||||
| modifying DNS records to prove you have control over a
|
||||
| domain. Doing domain validation in this way is
|
||||
| the only way to obtain wildcard certificates from Let's
|
||||
| Encrypt.
|
||||
manual_ Y N | Helps you obtain a certificate by giving you instructions to http-01_ (80),
|
||||
| perform domain validation yourself. Additionally allows you dns-01_ (53) or
|
||||
| to specify scripts to automate the validation task in a tls-sni-01_ (443)
|
||||
| customized way.
|
||||
=========== ==== ==== =============================================================== =============================
|
||||
|
||||
.. |dns_plugs| replace:: :ref:`DNS plugins <dns_plugins>`
|
||||
|
||||
Under the hood, plugins use one of several ACME protocol challenges_ to
|
||||
prove you control a domain. The options are http-01_ (which uses port 80),
|
||||
tls-sni-01_ (port 443) and dns-01_ (requiring configuration of a DNS server on
|
||||
|
|
@ -80,7 +87,7 @@ Apache
|
|||
|
||||
The Apache plugin currently requires an OS with augeas version 1.0; currently `it
|
||||
supports
|
||||
<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/constants.py>`_
|
||||
<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/entrypoint.py>`_
|
||||
modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin.
|
||||
This automates both obtaining *and* installing certificates on an Apache
|
||||
webserver. To specify this plugin on the command line, simply include
|
||||
|
|
@ -141,6 +148,8 @@ the ``--nginx`` flag on the commandline.
|
|||
|
||||
certbot --nginx
|
||||
|
||||
.. _standalone:
|
||||
|
||||
Standalone
|
||||
----------
|
||||
|
||||
|
|
@ -164,6 +173,33 @@ the Internet on the specified port using each requested domain name.
|
|||
.. note:: The ``--standalone-supported-challenges`` option has been
|
||||
deprecated since ``certbot`` version 0.9.0.
|
||||
|
||||
.. _dns_plugins:
|
||||
|
||||
DNS Plugins
|
||||
-----------
|
||||
|
||||
If you'd like to obtain a wildcard certificate from Let's Encrypt or run
|
||||
``certbot`` on a machine other than your target webserver, you can use one of
|
||||
Certbot's DNS plugins.
|
||||
|
||||
These plugins are still in the process of being packaged
|
||||
by many distributions and cannot currently be installed with ``certbot-auto``.
|
||||
If, however, you are comfortable installing the certificates yourself,
|
||||
you can run these plugins with :ref:`Docker <docker-user>`.
|
||||
|
||||
Once installed, you can find documentation on how to use each plugin at:
|
||||
|
||||
* `certbot-dns-cloudflare <https://certbot-dns-cloudflare.readthedocs.io>`_
|
||||
* `certbot-dns-cloudxns <https://certbot-dns-cloudxns.readthedocs.io>`_
|
||||
* `certbot-dns-digitalocean <https://certbot-dns-digitalocean.readthedocs.io>`_
|
||||
* `certbot-dns-dnsimple <https://certbot-dns-dnsimple.readthedocs.io>`_
|
||||
* `certbot-dns-dnsmadeeasy <https://certbot-dns-dnsmadeeasy.readthedocs.io>`_
|
||||
* `certbot-dns-google <https://certbot-dns-google.readthedocs.io>`_
|
||||
* `certbot-dns-luadns <https://certbot-dns-luadns.readthedocs.io>`_
|
||||
* `certbot-dns-nsone <https://certbot-dns-nsone.readthedocs.io>`_
|
||||
* `certbot-dns-rfc2136 <https://certbot-dns-rfc2136.readthedocs.io>`_
|
||||
* `certbot-dns-route53 <https://certbot-dns-route53.readthedocs.io>`_
|
||||
|
||||
Manual
|
||||
------
|
||||
|
||||
|
|
@ -516,6 +552,12 @@ can run on a regular basis, like every week or every day). In that case,
|
|||
you are likely to want to use the ``-q`` or ``--quiet`` quiet flag to
|
||||
silence all output except errors.
|
||||
|
||||
.. seealso:: Many of the certbot clients obtained through a
|
||||
distribution come with automatic renewal out of the box,
|
||||
such as Debian and Ubuntu versions installed through `apt`,
|
||||
CentOS/RHEL 7 through EPEL, etc. See `Automated Renewals`_
|
||||
for more details.
|
||||
|
||||
If you are manually renewing all of your certificates, the
|
||||
``--force-renewal`` flag may be helpful; it causes the expiration time of
|
||||
the certificate(s) to be ignored when considering renewal, and attempts to
|
||||
|
|
@ -611,6 +653,31 @@ The following commands could be used to specify where these files are located::
|
|||
sed -i 's,/etc/letsencrypt/live/example.com,/home/user/me/certbot,g' /etc/letsencrypt/renewal/example.com.conf
|
||||
certbot update_symlinks
|
||||
|
||||
Automated Renewals
|
||||
------------------
|
||||
|
||||
Many Linux distributions provide automated renewal when you use the
|
||||
packages installed through their system package manager. The
|
||||
following table is an *incomplete* list of distributions which do so,
|
||||
as well as their methods for doing so.
|
||||
|
||||
If you are not sure whether or not your system has this already
|
||||
automated, refer to your distribution's documentation, or check your
|
||||
system's crontab (typically in `/etc/crontab/` and `/etc/cron.*/*` and
|
||||
systemd timers (`systemctl list-timers`).
|
||||
|
||||
.. csv-table:: Distributions with Automated Renewal
|
||||
:header: "Distribution Name", "Distribution Version", "Automation Method"
|
||||
|
||||
"CentOS", "EPEL 7", "systemd"
|
||||
"Debian", "jessie", "cron, systemd"
|
||||
"Debian", "stretch", "cron, systemd"
|
||||
"Debian", "testing/sid", "cron, systemd"
|
||||
"Fedora", "26", "systemd"
|
||||
"Fedora", "27", "systemd"
|
||||
"RHEL", "EPEL 7", "systemd"
|
||||
"Ubuntu", "17.10", "cron, systemd"
|
||||
"Ubuntu", "certbot PPA", "cron, systemd"
|
||||
|
||||
.. _where-certs:
|
||||
|
||||
|
|
@ -801,6 +868,27 @@ Example usage for DNS-01 (Cloudflare API v4) (for example purposes only, do not
|
|||
|
||||
.. _lock-files:
|
||||
|
||||
Changing the ACME Server
|
||||
========================
|
||||
|
||||
By default, Certbot uses Let's Encrypt's initial production server at
|
||||
https://acme-v01.api.letsencrypt.org/. You can tell Certbot to use a
|
||||
different CA by providing ``--server`` on the command line or in a
|
||||
:ref:`configuration file <config-file>` with the URL of the server's
|
||||
ACME directory. For example, if you would like to use Let's Encrypt's
|
||||
new ACMEv2 server, you would add ``--server
|
||||
https://acme-v02.api.letsencrypt.org/directory`` to the command line.
|
||||
Certbot will automatically select which version of the ACME protocol to
|
||||
use based on the contents served at the provided URL.
|
||||
|
||||
If you use ``--server`` to specify an ACME CA that implements a newer
|
||||
version of the spec, you may be able to obtain a certificate for a
|
||||
wildcard domain. Some CAs (such as Let's Encrypt) require that domain
|
||||
validation for wildcard domains must be done through modifications to
|
||||
DNS records which means that the dns-01_ challenge type must be used. To
|
||||
see a list of Certbot plugins that support this challenge type and how
|
||||
to use them, see plugins_.
|
||||
|
||||
Lock Files
|
||||
==========
|
||||
|
||||
|
|
@ -831,7 +919,7 @@ Certbot accepts a global configuration file that applies its options to all invo
|
|||
of Certbot. Certificate specific configuration choices should be set in the ``.conf``
|
||||
files that can be found in ``/etc/letsencrypt/renewal``.
|
||||
|
||||
By default no cli.ini file is created, after creating one
|
||||
By default no cli.ini file is created, after creating one
|
||||
it is possible to specify the location of this configuration file with
|
||||
``certbot-auto --config cli.ini`` (or shorter ``-c cli.ini``). An
|
||||
example configuration file is shown below:
|
||||
|
|
@ -867,6 +955,12 @@ the oldest one to make room for new logs. The number of subsequent logs can be
|
|||
changed by passing the desired number to the command line flag
|
||||
``--max-log-backups``.
|
||||
|
||||
.. note:: Some distributions, including Debian and Ubuntu, disable
|
||||
certbot's internal log rotation in favor of a more traditional
|
||||
logrotate script. If you are using a distribution's packages and
|
||||
want to alter the log rotation, check `/etc/logrotate.d/` for a
|
||||
certbot rotation script.
|
||||
|
||||
.. _command-line:
|
||||
|
||||
Certbot command-line options
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Always use the staging/testing server - avoids rate limiting
|
||||
server = https://acme-staging.api.letsencrypt.org/directory
|
||||
server = https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
|
||||
# This is an example configuration file for developers
|
||||
config-dir = /tmp/le/conf
|
||||
|
|
|
|||
104
letsencrypt-auto
104
letsencrypt-auto
|
|
@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
|
|||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
|
||||
LE_AUTO_VERSION="0.21.1"
|
||||
LE_AUTO_VERSION="0.23.0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -47,6 +47,7 @@ Help for certbot itself cannot be provided until it is installed.
|
|||
--no-bootstrap do not install OS dependencies
|
||||
--no-self-upgrade do not download updates
|
||||
--os-packages-only install OS dependencies and exit
|
||||
--install-only install certbot, upgrade if needed, and exit
|
||||
-v, --verbose provide more output
|
||||
-q, --quiet provide only update/error output;
|
||||
implies --non-interactive
|
||||
|
|
@ -60,6 +61,8 @@ for arg in "$@" ; do
|
|||
DEBUG=1;;
|
||||
--os-packages-only)
|
||||
OS_PACKAGES_ONLY=1;;
|
||||
--install-only)
|
||||
INSTALL_ONLY=1;;
|
||||
--no-self-upgrade)
|
||||
# Do not upgrade this script (also prevents client upgrades, because each
|
||||
# copy of the script pins a hash of the python client)
|
||||
|
|
@ -246,7 +249,7 @@ DeprecationBootstrap() {
|
|||
fi
|
||||
}
|
||||
|
||||
MIN_PYTHON_VERSION="2.6"
|
||||
MIN_PYTHON_VERSION="2.7"
|
||||
MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//')
|
||||
# Sets LE_PYTHON to Python version string and PYVER to the first two
|
||||
# digits of the python version
|
||||
|
|
@ -1196,24 +1199,24 @@ letsencrypt==0.7.0 \
|
|||
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
|
||||
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
|
||||
|
||||
certbot==0.21.1 \
|
||||
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
|
||||
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
|
||||
acme==0.21.1 \
|
||||
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
|
||||
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
|
||||
certbot-apache==0.21.1 \
|
||||
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
|
||||
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
|
||||
certbot-nginx==0.21.1 \
|
||||
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
|
||||
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
|
||||
#!/usr/bin/env python
|
||||
"""A small script that can act as a trust root for installing pip 8
|
||||
"""A small script that can act as a trust root for installing pip >=8
|
||||
|
||||
Embed this in your project, and your VCS checkout is all you have to trust. In
|
||||
a post-peep era, this lets you claw your way to a hash-checking version of pip,
|
||||
|
|
@ -1237,6 +1240,7 @@ anything goes wrong, it will exit with a non-zero status code.
|
|||
from __future__ import print_function
|
||||
from distutils.version import StrictVersion
|
||||
from hashlib import sha256
|
||||
from os import environ
|
||||
from os.path import join
|
||||
from pipes import quote
|
||||
from shutil import rmtree
|
||||
|
|
@ -1270,14 +1274,14 @@ except ImportError:
|
|||
from urllib.parse import urlparse # 3.4
|
||||
|
||||
|
||||
__version__ = 1, 3, 0
|
||||
__version__ = 1, 5, 1
|
||||
PIP_VERSION = '9.0.1'
|
||||
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
|
||||
|
||||
|
||||
# wheel has a conditional dependency on argparse:
|
||||
maybe_argparse = (
|
||||
[('https://pypi.python.org/packages/18/dd/'
|
||||
'e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
|
||||
[('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
|
||||
'argparse-1.4.0.tar.gz',
|
||||
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
|
||||
if version_info < (2, 7, 0) else [])
|
||||
|
|
@ -1285,18 +1289,14 @@ maybe_argparse = (
|
|||
|
||||
PACKAGES = maybe_argparse + [
|
||||
# Pip has no dependencies, as it vendors everything:
|
||||
('https://pypi.python.org/packages/11/b6/'
|
||||
'abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
|
||||
'pip-{0}.tar.gz'
|
||||
.format(PIP_VERSION),
|
||||
('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
|
||||
'pip-{0}.tar.gz'.format(PIP_VERSION),
|
||||
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
|
||||
# This version of setuptools has only optional dependencies:
|
||||
('https://pypi.python.org/packages/69/65/'
|
||||
'4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
|
||||
'setuptools-20.2.2.tar.gz',
|
||||
'24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'),
|
||||
('https://pypi.python.org/packages/c9/1d/'
|
||||
'bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
|
||||
('59/88/2f3990916931a5de6fa9706d6d75eb32ee8b78627bb2abaab7ed9e6d0622/'
|
||||
'setuptools-29.0.1.tar.gz',
|
||||
'b539118819a4857378398891fa5366e090690e46b3e41421a1e07d6e9fd8feb0'),
|
||||
('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
|
||||
'wheel-0.29.0.tar.gz',
|
||||
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
|
||||
]
|
||||
|
|
@ -1317,12 +1317,13 @@ def hashed_download(url, temp, digest):
|
|||
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
|
||||
# authenticity has only privacy (not arbitrary code execution)
|
||||
# implications, since we're checking hashes.
|
||||
def opener():
|
||||
def opener(using_https=True):
|
||||
opener = build_opener(HTTPSHandler())
|
||||
# Strip out HTTPHandler to prevent MITM spoof:
|
||||
for handler in opener.handlers:
|
||||
if isinstance(handler, HTTPHandler):
|
||||
opener.handlers.remove(handler)
|
||||
if using_https:
|
||||
# Strip out HTTPHandler to prevent MITM spoof:
|
||||
for handler in opener.handlers:
|
||||
if isinstance(handler, HTTPHandler):
|
||||
opener.handlers.remove(handler)
|
||||
return opener
|
||||
|
||||
def read_chunks(response, chunk_size):
|
||||
|
|
@ -1332,8 +1333,9 @@ def hashed_download(url, temp, digest):
|
|||
break
|
||||
yield chunk
|
||||
|
||||
response = opener().open(url)
|
||||
path = join(temp, urlparse(url).path.split('/')[-1])
|
||||
parsed_url = urlparse(url)
|
||||
response = opener(using_https=parsed_url.scheme == 'https').open(url)
|
||||
path = join(temp, parsed_url.path.split('/')[-1])
|
||||
actual_hash = sha256()
|
||||
with open(path, 'wb') as file:
|
||||
for chunk in read_chunks(response, 4096):
|
||||
|
|
@ -1346,6 +1348,24 @@ def hashed_download(url, temp, digest):
|
|||
return path
|
||||
|
||||
|
||||
def get_index_base():
|
||||
"""Return the URL to the dir containing the "packages" folder.
|
||||
|
||||
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
|
||||
end if it's there; that is likely to give us the right dir.
|
||||
|
||||
"""
|
||||
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
|
||||
if env_var:
|
||||
SIMPLE = '/simple'
|
||||
if env_var.endswith(SIMPLE):
|
||||
return env_var[:-len(SIMPLE)]
|
||||
else:
|
||||
return env_var
|
||||
else:
|
||||
return DEFAULT_INDEX_BASE
|
||||
|
||||
|
||||
def main():
|
||||
pip_version = StrictVersion(check_output(['pip', '--version'])
|
||||
.decode('utf-8').split()[1])
|
||||
|
|
@ -1353,11 +1373,13 @@ def main():
|
|||
if pip_version >= min_pip_version:
|
||||
return 0
|
||||
has_pip_cache = pip_version >= StrictVersion('6.0')
|
||||
|
||||
index_base = get_index_base()
|
||||
temp = mkdtemp(prefix='pipstrap-')
|
||||
try:
|
||||
downloads = [hashed_download(url, temp, digest)
|
||||
for url, digest in PACKAGES]
|
||||
downloads = [hashed_download(index_base + '/packages/' + path,
|
||||
temp,
|
||||
digest)
|
||||
for path, digest in PACKAGES]
|
||||
check_output('pip install --no-index --no-deps -U ' +
|
||||
# Disable cache since we're not using it and it otherwise
|
||||
# sometimes throws permission warnings:
|
||||
|
|
@ -1428,6 +1450,12 @@ UNLIKELY_EOF
|
|||
|
||||
say "Installation succeeded."
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_ONLY" = 1 ]; then
|
||||
say "Certbot is installed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
"$VENV_BIN/letsencrypt" "$@"
|
||||
|
||||
else
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ RUN yum install -y python-pip sudo
|
|||
COPY ./pieces/pipstrap.py /opt
|
||||
RUN /opt/pipstrap.py
|
||||
# Pin pytest version for increased stability
|
||||
RUN pip install pytest==3.2.5
|
||||
RUN pip install pytest==3.2.5 six==1.10.0
|
||||
|
||||
# Add an unprivileged user:
|
||||
RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ RUN apt-get update && \
|
|||
COPY ./pieces/pipstrap.py /opt
|
||||
RUN /opt/pipstrap.py
|
||||
# Pin pytest version for increased stability
|
||||
RUN pip install pytest==3.2.5
|
||||
RUN pip install pytest==3.2.5 six==1.10.0
|
||||
|
||||
# Let that user sudo:
|
||||
RUN sed -i.bkp -e \
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ RUN apt-get update && \
|
|||
COPY ./pieces/pipstrap.py /opt
|
||||
RUN /opt/pipstrap.py
|
||||
# Pin pytest version for increased stability
|
||||
RUN pip install pytest==3.2.5
|
||||
RUN pip install pytest==3.2.5 six==1.10.0
|
||||
|
||||
RUN mkdir -p /home/lea/certbot
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ RUN apt-get update && \
|
|||
COPY ./pieces/pipstrap.py /opt
|
||||
RUN /opt/pipstrap.py
|
||||
# Pin pytest version for increased stability
|
||||
RUN pip install pytest==3.2.5
|
||||
RUN pip install pytest==3.2.5 six==1.10.0
|
||||
|
||||
# Let that user sudo:
|
||||
RUN sed -i.bkp -e \
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlpqMlYACgkQTRfJlc2X
|
||||
dfKHfQgAnZQJ34jFoVqEodT0EjvkFKZif4V/zXTsVwTHn107BcLCpH/9gjANrSo3
|
||||
JpvseH2q0odhOAZA4rZKH4Geh+5fsUl3Ew9YB28RXeyqEfCATUqPq6q+jAi55SLc
|
||||
a064Ux5N7eOIh9gxvpDKBeSFD0eNB8IDtPQhUspr+WnoycawrJHNGawL8WIfrWY3
|
||||
0ZPF981iPCWCdN3woDP9wHA2QtBClAk2pQ1aMgdkK9r/QLO+DY92xmT/Uu4ik2jR
|
||||
zv+QplsQLftjD+bRar5R9jiCWV5phPqrOF3ypMiU0K5bsnrZfGBzBcoEyfKuB+UR
|
||||
F/j/631OC6yLRasr+xcL1gc+SCryfA==
|
||||
=tkZT
|
||||
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlrFS/EACgkQTRfJlc2X
|
||||
dfK+rQf8DcKY5bMi5eJnwwAlui6WIyWSrf1KAKt09tEGZSHQ1fcyCPrGVhk7VVDg
|
||||
NJ1/XiYBquPW+7mYUcHrIRsiKYbTUcmVjyqP6tZd67IxRH9ToNqBzA6kq99T+IPd
|
||||
iTGdczHMSPcxM6/Fa5PYMHXy2+ctTr/8+gnsxth9QfcM62Yd6ecfqIdoId3vk9Aw
|
||||
UBMENZhUasIvgZDWuow+1XVZ/DAmdvj2Xl/E3sA9i2ArREJhkhVegtdrHkwSY+Hm
|
||||
MKfZGqNVse6ZAF/8YdEVBum0OngMMs63DwucwFxmw5DqWtmnXm6awLNW/LQ/3R5L
|
||||
xuKjcVaAT1h5TgIyRT6opH8JBKmLpg==
|
||||
=Ouj4
|
||||
-----END PGP SIGNATURE-----
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
|
|||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
|
||||
LE_AUTO_VERSION="0.22.0.dev0"
|
||||
LE_AUTO_VERSION="0.24.0.dev0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -1199,18 +1199,18 @@ letsencrypt==0.7.0 \
|
|||
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
|
||||
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
|
||||
|
||||
certbot==0.21.1 \
|
||||
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
|
||||
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
|
||||
acme==0.21.1 \
|
||||
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
|
||||
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
|
||||
certbot-apache==0.21.1 \
|
||||
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
|
||||
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
|
||||
certbot-nginx==0.21.1 \
|
||||
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
|
||||
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,12 +1,12 @@
|
|||
certbot==0.21.1 \
|
||||
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
|
||||
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
|
||||
acme==0.21.1 \
|
||||
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
|
||||
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
|
||||
certbot-apache==0.21.1 \
|
||||
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
|
||||
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
|
||||
certbot-nginx==0.21.1 \
|
||||
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
|
||||
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
Run these locally by saying... ::
|
||||
|
||||
./build.py && docker build -t lea . && docker run --rm -t -i lea
|
||||
./build.py && docker build -t lea . -f Dockerfile.<distro> && docker run --rm -t -i lea
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from threading import Thread
|
|||
from unittest import TestCase
|
||||
|
||||
from pytest import mark
|
||||
from six.moves import xrange # pylint: disable=redefined-builtin
|
||||
|
||||
|
||||
@mark.skip
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
[pytest]
|
||||
addopts = --quiet
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue