mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 19:02:52 +02:00
Fixing merge conflicts.
This commit is contained in:
+3
-1
@@ -17,9 +17,11 @@ letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64
|
|||||||
|
|
||||||
/.vagrant
|
/.vagrant
|
||||||
|
|
||||||
|
tags
|
||||||
|
|
||||||
# editor temporary files
|
# editor temporary files
|
||||||
*~
|
*~
|
||||||
*.swp
|
*.sw?
|
||||||
\#*#
|
\#*#
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
|||||||
+12
@@ -34,6 +34,8 @@ matrix:
|
|||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: TOXENV=apacheconftest
|
env: TOXENV=apacheconftest
|
||||||
sudo: required
|
sudo: required
|
||||||
|
- python: "2.7"
|
||||||
|
env: TOXENV=nginxroundtrip
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: TOXENV=py27 BOULDER_INTEGRATION=1
|
env: TOXENV=py27 BOULDER_INTEGRATION=1
|
||||||
sudo: true
|
sudo: true
|
||||||
@@ -53,6 +55,16 @@ matrix:
|
|||||||
services: docker
|
services: docker
|
||||||
before_install:
|
before_install:
|
||||||
addons:
|
addons:
|
||||||
|
- sudo: required
|
||||||
|
env: TOXENV=apache_compat
|
||||||
|
services: docker
|
||||||
|
before_install:
|
||||||
|
addons:
|
||||||
|
- sudo: required
|
||||||
|
env: TOXENV=nginx_compat
|
||||||
|
services: docker
|
||||||
|
before_install:
|
||||||
|
addons:
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: TOXENV=cover
|
env: TOXENV=cover
|
||||||
- python: "3.3"
|
- python: "3.3"
|
||||||
|
|||||||
+12
-17
@@ -1,18 +1,12 @@
|
|||||||
.. notice for github users
|
.. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin
|
||||||
|
|
||||||
Disclaimer
|
Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identify of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server.
|
||||||
==========
|
|
||||||
|
|
||||||
Certbot (previously, the Let's Encrypt client) is **BETA SOFTWARE**. It
|
Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment.
|
||||||
contains plenty of bugs and rough edges, and should be tested thoroughly in
|
|
||||||
staging environments before use on production systems.
|
|
||||||
|
|
||||||
For more information regarding the status of the project, please see
|
How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide <https://certbot.eff.org>`_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access <https://certbot.eff.org/faq/#does-certbot-require-root-privileges>`_ to your web server to run Certbot.
|
||||||
https://letsencrypt.org. Be sure to checkout the
|
|
||||||
`Frequently Asked Questions (FAQ) <https://community.letsencrypt.org/t/frequently-asked-questions-faq/26#topic-title>`_.
|
|
||||||
|
|
||||||
About Certbot
|
If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issues by Let’s Encrypt.
|
||||||
==============================
|
|
||||||
|
|
||||||
Certbot is a fully-featured, extensible client for the Let's
|
Certbot is a fully-featured, extensible client for the Let's
|
||||||
Encrypt CA (or any other CA that speaks the `ACME
|
Encrypt CA (or any other CA that speaks the `ACME
|
||||||
@@ -121,7 +115,7 @@ email to client-dev+subscribe@letsencrypt.org)
|
|||||||
:alt: Docker Repository on Quay.io
|
:alt: Docker Repository on Quay.io
|
||||||
|
|
||||||
.. _`installation instructions`:
|
.. _`installation instructions`:
|
||||||
https://letsencrypt.readthedocs.org/en/latest/using.html
|
https://letsencrypt.readthedocs.org/en/latest/using.html#getting-certbot
|
||||||
|
|
||||||
.. _watch demo video: https://www.youtube.com/watch?v=Gas_sSB-5SU
|
.. _watch demo video: https://www.youtube.com/watch?v=Gas_sSB-5SU
|
||||||
|
|
||||||
@@ -142,9 +136,12 @@ but for most users who want to avoid running an ACME client as root, either
|
|||||||
The Apache plugin currently requires a Debian-based OS with augeas version
|
The Apache plugin currently requires a Debian-based OS with augeas version
|
||||||
1.0; this includes Ubuntu 12.04+ and Debian 7+.
|
1.0; this includes Ubuntu 12.04+ and Debian 7+.
|
||||||
|
|
||||||
|
.. Do not modify this comment unless you know what you're doing. tag:intro-end
|
||||||
|
|
||||||
|
.. Do not modify this comment unless you know what you're doing. tag:features-begin
|
||||||
|
|
||||||
Current Features
|
Current Features
|
||||||
================
|
=====================
|
||||||
|
|
||||||
* Supports multiple web servers:
|
* Supports multiple web servers:
|
||||||
|
|
||||||
@@ -168,8 +165,6 @@ Current Features
|
|||||||
command line.
|
command line.
|
||||||
* Free and Open Source Software, made with Python.
|
* Free and Open Source Software, made with Python.
|
||||||
|
|
||||||
|
.. Do not modify this comment unless you know what you're doing. tag:features-end
|
||||||
|
|
||||||
.. _Freenode: https://webchat.freenode.net?channels=%23letsencrypt
|
For extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide <https://certbot.eff.org/docs/contributing.html>`_.
|
||||||
.. _OFTC: https://webchat.oftc.net?channels=%23certbot
|
|
||||||
.. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev
|
|
||||||
.. _certbot.eff.org: https://certbot.eff.org/
|
|
||||||
|
|||||||
+71
-4
@@ -14,7 +14,6 @@ from acme import crypto_util
|
|||||||
from acme import fields
|
from acme import fields
|
||||||
from acme import jose
|
from acme import jose
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -206,6 +205,74 @@ class KeyAuthorizationChallenge(_TokenChallenge):
|
|||||||
self.validation(account_key, *args, **kwargs))
|
self.validation(account_key, *args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
@ChallengeResponse.register
|
||||||
|
class DNS01Response(KeyAuthorizationChallengeResponse):
|
||||||
|
"""ACME dns-01 challenge response."""
|
||||||
|
typ = "dns-01"
|
||||||
|
|
||||||
|
def simple_verify(self, chall, domain, account_public_key):
|
||||||
|
"""Simple verify.
|
||||||
|
|
||||||
|
:param challenges.DNS01 chall: Corresponding challenge.
|
||||||
|
:param unicode domain: Domain name being verified.
|
||||||
|
:param JWK account_public_key: Public key for the key pair
|
||||||
|
being authorized.
|
||||||
|
|
||||||
|
:returns: ``True`` iff validation with the TXT records resolved from a
|
||||||
|
DNS server is successful.
|
||||||
|
:rtype: bool
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.verify(chall, account_public_key):
|
||||||
|
logger.debug("Verification of key authorization in response failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
validation_domain_name = chall.validation_domain_name(domain)
|
||||||
|
validation = chall.validation(account_public_key)
|
||||||
|
logger.debug("Verifying %s at %s...", chall.typ, validation_domain_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from acme import dns_resolver
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
raise errors.Error("Local validation for 'dns-01' challenges "
|
||||||
|
"requires 'dnspython'")
|
||||||
|
txt_records = dns_resolver.txt_records_for_name(validation_domain_name)
|
||||||
|
exists = validation in txt_records
|
||||||
|
if not exists:
|
||||||
|
logger.debug("Key authorization from response (%r) doesn't match "
|
||||||
|
"any DNS response in %r", self.key_authorization,
|
||||||
|
txt_records)
|
||||||
|
return exists
|
||||||
|
|
||||||
|
|
||||||
|
@Challenge.register # pylint: disable=too-many-ancestors
|
||||||
|
class DNS01(KeyAuthorizationChallenge):
|
||||||
|
"""ACME dns-01 challenge."""
|
||||||
|
response_cls = DNS01Response
|
||||||
|
typ = response_cls.typ
|
||||||
|
|
||||||
|
LABEL = "_acme-challenge"
|
||||||
|
"""Label clients prepend to the domain name being validated."""
|
||||||
|
|
||||||
|
def validation(self, account_key, **unused_kwargs):
|
||||||
|
"""Generate validation.
|
||||||
|
|
||||||
|
:param JWK account_key:
|
||||||
|
:rtype: unicode
|
||||||
|
|
||||||
|
"""
|
||||||
|
return jose.b64encode(hashlib.sha256(self.key_authorization(
|
||||||
|
account_key).encode("utf-8")).digest()).decode()
|
||||||
|
|
||||||
|
def validation_domain_name(self, name):
|
||||||
|
"""Domain name for TXT validation record.
|
||||||
|
|
||||||
|
:param unicode name: Domain name being validated.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return "{0}.{1}".format(self.LABEL, name)
|
||||||
|
|
||||||
|
|
||||||
@ChallengeResponse.register
|
@ChallengeResponse.register
|
||||||
class HTTP01Response(KeyAuthorizationChallengeResponse):
|
class HTTP01Response(KeyAuthorizationChallengeResponse):
|
||||||
"""ACME http-01 challenge response."""
|
"""ACME http-01 challenge response."""
|
||||||
@@ -231,8 +298,8 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
|||||||
being authorized.
|
being authorized.
|
||||||
:param int port: Port used in the validation.
|
:param int port: Port used in the validation.
|
||||||
|
|
||||||
:returns: ``True`` iff validation is successful, ``False``
|
:returns: ``True`` iff validation with the files currently served by the
|
||||||
otherwise.
|
HTTP server is successful.
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -410,7 +477,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
|
|||||||
|
|
||||||
|
|
||||||
:returns: ``True`` iff client's control of the domain has been
|
:returns: ``True`` iff client's control of the domain has been
|
||||||
verified, ``False`` otherwise.
|
verified.
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -77,6 +77,93 @@ class KeyAuthorizationChallengeResponseTest(unittest.TestCase):
|
|||||||
self.assertFalse(response.verify(self.chall, KEY.public_key()))
|
self.assertFalse(response.verify(self.chall, KEY.public_key()))
|
||||||
|
|
||||||
|
|
||||||
|
class DNS01ResponseTest(unittest.TestCase):
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from acme.challenges import DNS01Response
|
||||||
|
self.msg = DNS01Response(key_authorization=u'foo')
|
||||||
|
self.jmsg = {
|
||||||
|
'resource': 'challenge',
|
||||||
|
'type': 'dns-01',
|
||||||
|
'keyAuthorization': u'foo',
|
||||||
|
}
|
||||||
|
|
||||||
|
from acme.challenges import DNS01
|
||||||
|
self.chall = DNS01(token=(b'x' * 16))
|
||||||
|
self.response = self.chall.response(KEY)
|
||||||
|
|
||||||
|
def test_to_partial_json(self):
|
||||||
|
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
||||||
|
|
||||||
|
def test_from_json(self):
|
||||||
|
from acme.challenges import DNS01Response
|
||||||
|
self.assertEqual(self.msg, DNS01Response.from_json(self.jmsg))
|
||||||
|
|
||||||
|
def test_from_json_hashable(self):
|
||||||
|
from acme.challenges import DNS01Response
|
||||||
|
hash(DNS01Response.from_json(self.jmsg))
|
||||||
|
|
||||||
|
def test_simple_verify_bad_key_authorization(self):
|
||||||
|
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
|
||||||
|
self.response.simple_verify(self.chall, "local", key2.public_key())
|
||||||
|
|
||||||
|
@mock.patch("acme.dns_resolver.txt_records_for_name")
|
||||||
|
def test_simple_verify_good_validation(self, mock_resolver):
|
||||||
|
mock_resolver.return_value = [self.chall.validation(KEY.public_key())]
|
||||||
|
self.assertTrue(self.response.simple_verify(
|
||||||
|
self.chall, "local", KEY.public_key()))
|
||||||
|
mock_resolver.assert_called_once_with(
|
||||||
|
self.chall.validation_domain_name("local"))
|
||||||
|
|
||||||
|
@mock.patch("acme.dns_resolver.txt_records_for_name")
|
||||||
|
def test_simple_verify_good_validation_multiple_txts(self, mock_resolver):
|
||||||
|
mock_resolver.return_value = [
|
||||||
|
"!", self.chall.validation(KEY.public_key())]
|
||||||
|
self.assertTrue(self.response.simple_verify(
|
||||||
|
self.chall, "local", KEY.public_key()))
|
||||||
|
mock_resolver.assert_called_once_with(
|
||||||
|
self.chall.validation_domain_name("local"))
|
||||||
|
|
||||||
|
@mock.patch("acme.dns_resolver.txt_records_for_name")
|
||||||
|
def test_simple_verify_bad_validation(self, mock_dns):
|
||||||
|
mock_dns.return_value = ["!"]
|
||||||
|
self.assertFalse(self.response.simple_verify(
|
||||||
|
self.chall, "local", KEY.public_key()))
|
||||||
|
|
||||||
|
|
||||||
|
class DNS01Test(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from acme.challenges import DNS01
|
||||||
|
self.msg = DNS01(token=jose.decode_b64jose(
|
||||||
|
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA'))
|
||||||
|
self.jmsg = {
|
||||||
|
'type': 'dns-01',
|
||||||
|
'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA',
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_validation_domain_name(self):
|
||||||
|
self.assertEqual('_acme-challenge.www.example.com',
|
||||||
|
self.msg.validation_domain_name('www.example.com'))
|
||||||
|
|
||||||
|
def test_validation(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk",
|
||||||
|
self.msg.validation(KEY))
|
||||||
|
|
||||||
|
def test_to_partial_json(self):
|
||||||
|
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
||||||
|
|
||||||
|
def test_from_json(self):
|
||||||
|
from acme.challenges import DNS01
|
||||||
|
self.assertEqual(self.msg, DNS01.from_json(self.jmsg))
|
||||||
|
|
||||||
|
def test_from_json_hashable(self):
|
||||||
|
from acme.challenges import DNS01
|
||||||
|
hash(DNS01.from_json(self.jmsg))
|
||||||
|
|
||||||
|
|
||||||
class HTTP01ResponseTest(unittest.TestCase):
|
class HTTP01ResponseTest(unittest.TestCase):
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""DNS Resolver for ACME client.
|
||||||
|
Required only for local validation of 'dns-01' challenges.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import dns.resolver
|
||||||
|
import dns.exception
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def txt_records_for_name(name):
|
||||||
|
"""Resolve the name and return the TXT records.
|
||||||
|
|
||||||
|
:param unicode name: Domain name being verified.
|
||||||
|
|
||||||
|
:returns: A list of txt records, if empty the name could not be resolved
|
||||||
|
:rtype: list of unicode
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
dns_response = dns.resolver.query(name, 'TXT')
|
||||||
|
except dns.resolver.NXDOMAIN as error:
|
||||||
|
return []
|
||||||
|
except dns.exception.DNSException as error:
|
||||||
|
logger.error("Error resolving %s: %s", name, str(error))
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [txt_rec.decode("utf-8") for rdata in dns_response
|
||||||
|
for txt_rec in rdata.strings]
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Tests for acme.dns_resolver."""
|
||||||
|
import unittest
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from acme import dns_resolver
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dns
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
dns = None
|
||||||
|
|
||||||
|
|
||||||
|
def create_txt_response(name, txt_records):
|
||||||
|
"""
|
||||||
|
Returns an RRSet containing the 'txt_records' as the result of a DNS
|
||||||
|
query for 'name'.
|
||||||
|
|
||||||
|
This takes advantage of the fact that an Answer object mostly behaves
|
||||||
|
like an RRset.
|
||||||
|
"""
|
||||||
|
return dns.rrset.from_text_list(name, 60, "IN", "TXT", txt_records)
|
||||||
|
|
||||||
|
|
||||||
|
class TxtRecordsForNameTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||||
|
def test_txt_records_for_name_with_single_response(self, mock_dns):
|
||||||
|
mock_dns.return_value = create_txt_response('name', ['response'])
|
||||||
|
self.assertEqual(['response'],
|
||||||
|
dns_resolver.txt_records_for_name('name'))
|
||||||
|
|
||||||
|
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||||
|
def test_txt_records_for_name_with_multiple_responses(self, mock_dns):
|
||||||
|
mock_dns.return_value = create_txt_response(
|
||||||
|
'name', ['response1', 'response2'])
|
||||||
|
self.assertEqual(['response1', 'response2'],
|
||||||
|
dns_resolver.txt_records_for_name('name'))
|
||||||
|
|
||||||
|
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||||
|
def test_txt_records_for_name_domain_not_found(self, mock_dns):
|
||||||
|
mock_dns.side_effect = dns.resolver.NXDOMAIN
|
||||||
|
self.assertEquals([], dns_resolver.txt_records_for_name('name'))
|
||||||
|
|
||||||
|
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||||
|
def test_txt_records_for_name_domain_other_error(self, mock_dns):
|
||||||
|
mock_dns.side_effect = dns.exception.DNSException
|
||||||
|
self.assertEquals([], dns_resolver.txt_records_for_name('name'))
|
||||||
|
|
||||||
|
def run(self, result=None):
|
||||||
|
if dns is None: # pragma: no cover
|
||||||
|
print(self, "... SKIPPING, no dnspython available")
|
||||||
|
return
|
||||||
|
super(TxtRecordsForNameTest, self).run(result)
|
||||||
@@ -35,6 +35,11 @@ if sys.version_info < (2, 7):
|
|||||||
else:
|
else:
|
||||||
install_requires.append('mock')
|
install_requires.append('mock')
|
||||||
|
|
||||||
|
# dnspython 1.12 is required to support both Python 2 and Python 3.
|
||||||
|
dns_extras = [
|
||||||
|
'dnspython>=1.12',
|
||||||
|
]
|
||||||
|
|
||||||
dev_extras = [
|
dev_extras = [
|
||||||
'nose',
|
'nose',
|
||||||
'pep8',
|
'pep8',
|
||||||
@@ -76,6 +81,7 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
extras_require={
|
extras_require={
|
||||||
|
'dns': dns_extras,
|
||||||
'dev': dev_extras,
|
'dev': dev_extras,
|
||||||
'docs': docs_extras,
|
'docs': docs_extras,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -538,6 +538,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
is_ssl = True
|
is_ssl = True
|
||||||
|
|
||||||
filename = get_file_path(self.aug.get("/augeas/files%s/path" % get_file_path(path)))
|
filename = get_file_path(self.aug.get("/augeas/files%s/path" % get_file_path(path)))
|
||||||
|
if filename is None:
|
||||||
|
return None
|
||||||
|
|
||||||
if self.conf("handle-sites"):
|
if self.conf("handle-sites"):
|
||||||
is_enabled = self.is_site_enabled(filename)
|
is_enabled = self.is_site_enabled(filename)
|
||||||
else:
|
else:
|
||||||
@@ -1801,25 +1804,25 @@ def get_file_path(vhost_path):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Strip off /files
|
# Strip off /files/
|
||||||
avail_fp = vhost_path[6:]
|
try:
|
||||||
# This can be optimized...
|
if vhost_path.startswith("/files/"):
|
||||||
while True:
|
avail_fp = vhost_path[7:].split("/")
|
||||||
# Cast all to lowercase to be case insensitive
|
else:
|
||||||
find_if = avail_fp.lower().find("/ifmodule")
|
return None
|
||||||
if find_if != -1:
|
except AttributeError:
|
||||||
avail_fp = avail_fp[:find_if]
|
# If we recieved a None path
|
||||||
continue
|
return None
|
||||||
find_vh = avail_fp.lower().find("/virtualhost")
|
|
||||||
if find_vh != -1:
|
last_good = ""
|
||||||
avail_fp = avail_fp[:find_vh]
|
# Loop through the path parts and validate after every addition
|
||||||
continue
|
for p in avail_fp:
|
||||||
find_macro = avail_fp.lower().find("/macro")
|
cur_path = last_good+"/"+p
|
||||||
if find_macro != -1:
|
if os.path.exists(cur_path):
|
||||||
avail_fp = avail_fp[:find_macro]
|
last_good = cur_path
|
||||||
continue
|
else:
|
||||||
break
|
break
|
||||||
return avail_fp
|
return last_good
|
||||||
|
|
||||||
|
|
||||||
def install_ssl_options_conf(options_ssl):
|
def install_ssl_options_conf(options_ssl):
|
||||||
|
|||||||
@@ -2,7 +2,23 @@
|
|||||||
import pkg_resources
|
import pkg_resources
|
||||||
from certbot import util
|
from certbot import util
|
||||||
|
|
||||||
|
CLI_DEFAULTS_DEFAULT = dict(
|
||||||
|
server_root="/etc/apache2",
|
||||||
|
vhost_root="/etc/apache2/sites-available",
|
||||||
|
vhost_files="*",
|
||||||
|
version_cmd=['apache2ctl', '-v'],
|
||||||
|
define_cmd=['apache2ctl', '-t', '-D', 'DUMP_RUN_CFG'],
|
||||||
|
restart_cmd=['apache2ctl', 'graceful'],
|
||||||
|
conftest_cmd=['apache2ctl', 'configtest'],
|
||||||
|
enmod=None,
|
||||||
|
dismod=None,
|
||||||
|
le_vhost_ext="-le-ssl.conf",
|
||||||
|
handle_mods=False,
|
||||||
|
handle_sites=False,
|
||||||
|
challenge_location="/etc/apache2",
|
||||||
|
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||||
|
"certbot_apache", "options-ssl-apache.conf")
|
||||||
|
)
|
||||||
CLI_DEFAULTS_DEBIAN = dict(
|
CLI_DEFAULTS_DEBIAN = dict(
|
||||||
server_root="/etc/apache2",
|
server_root="/etc/apache2",
|
||||||
vhost_root="/etc/apache2/sites-available",
|
vhost_root="/etc/apache2/sites-available",
|
||||||
@@ -71,7 +87,25 @@ CLI_DEFAULTS_DARWIN = dict(
|
|||||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||||
"certbot_apache", "options-ssl-apache.conf")
|
"certbot_apache", "options-ssl-apache.conf")
|
||||||
)
|
)
|
||||||
|
CLI_DEFAULTS_SUSE = dict(
|
||||||
|
server_root="/etc/apache2",
|
||||||
|
vhost_root="/etc/apache2/vhosts.d",
|
||||||
|
vhost_files="*.conf",
|
||||||
|
version_cmd=['apache2ctl', '-v'],
|
||||||
|
define_cmd=['apache2ctl', '-t', '-D', 'DUMP_RUN_CFG'],
|
||||||
|
restart_cmd=['apache2ctl', 'graceful'],
|
||||||
|
conftest_cmd=['apache2ctl', 'configtest'],
|
||||||
|
enmod="a2enmod",
|
||||||
|
dismod="a2dismod",
|
||||||
|
le_vhost_ext="-le-ssl.conf",
|
||||||
|
handle_mods=False,
|
||||||
|
handle_sites=False,
|
||||||
|
challenge_location="/etc/apache2/vhosts.d",
|
||||||
|
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||||
|
"certbot_apache", "options-ssl-apache.conf")
|
||||||
|
)
|
||||||
CLI_DEFAULTS = {
|
CLI_DEFAULTS = {
|
||||||
|
"default": CLI_DEFAULTS_DEFAULT,
|
||||||
"debian": CLI_DEFAULTS_DEBIAN,
|
"debian": CLI_DEFAULTS_DEBIAN,
|
||||||
"ubuntu": CLI_DEFAULTS_DEBIAN,
|
"ubuntu": CLI_DEFAULTS_DEBIAN,
|
||||||
"centos": CLI_DEFAULTS_CENTOS,
|
"centos": CLI_DEFAULTS_CENTOS,
|
||||||
@@ -83,6 +117,8 @@ CLI_DEFAULTS = {
|
|||||||
"gentoo": CLI_DEFAULTS_GENTOO,
|
"gentoo": CLI_DEFAULTS_GENTOO,
|
||||||
"gentoo base system": CLI_DEFAULTS_GENTOO,
|
"gentoo base system": CLI_DEFAULTS_GENTOO,
|
||||||
"darwin": CLI_DEFAULTS_DARWIN,
|
"darwin": CLI_DEFAULTS_DARWIN,
|
||||||
|
"opensuse": CLI_DEFAULTS_SUSE,
|
||||||
|
"suse": CLI_DEFAULTS_SUSE,
|
||||||
}
|
}
|
||||||
"""CLI defaults."""
|
"""CLI defaults."""
|
||||||
|
|
||||||
@@ -115,13 +151,36 @@ HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS,
|
|||||||
|
|
||||||
|
|
||||||
def os_constant(key):
|
def os_constant(key):
|
||||||
"""Get a constant value for operating system
|
"""
|
||||||
|
Get a constant value for operating system
|
||||||
|
|
||||||
:param key: name of cli constant
|
:param key: name of cli constant
|
||||||
:return: value of constant for active os
|
:return: value of constant for active os
|
||||||
"""
|
"""
|
||||||
|
|
||||||
os_info = util.get_os_info()
|
os_info = util.get_os_info()
|
||||||
try:
|
try:
|
||||||
constants = CLI_DEFAULTS[os_info[0].lower()]
|
constants = CLI_DEFAULTS[os_info[0].lower()]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
constants = CLI_DEFAULTS["debian"]
|
constants = os_like_constants()
|
||||||
|
if not constants:
|
||||||
|
constants = CLI_DEFAULTS["default"]
|
||||||
return constants[key]
|
return constants[key]
|
||||||
|
|
||||||
|
|
||||||
|
def os_like_constants():
|
||||||
|
"""
|
||||||
|
Try to get constants for distribution with
|
||||||
|
similar layout and configuration, indicated by
|
||||||
|
/etc/os-release variable "LIKE"
|
||||||
|
|
||||||
|
:returns: Constants dictionary
|
||||||
|
:rtype: `dict`
|
||||||
|
"""
|
||||||
|
|
||||||
|
os_like = util.get_systemd_os_like()
|
||||||
|
if os_like:
|
||||||
|
for os_name in os_like:
|
||||||
|
if os_name in CLI_DEFAULTS.keys():
|
||||||
|
return CLI_DEFAULTS[os_name]
|
||||||
|
return {}
|
||||||
|
|||||||
@@ -125,6 +125,12 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||||||
self.assertTrue("google.com" in names)
|
self.assertTrue("google.com" in names)
|
||||||
self.assertTrue("certbot.demo" in names)
|
self.assertTrue("certbot.demo" in names)
|
||||||
|
|
||||||
|
def test_get_bad_path(self):
|
||||||
|
from certbot_apache.configurator import get_file_path
|
||||||
|
self.assertEqual(get_file_path(None), None)
|
||||||
|
self.assertEqual(get_file_path("nonexistent"), None)
|
||||||
|
self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access
|
||||||
|
|
||||||
def test_bad_servername_alias(self):
|
def test_bad_servername_alias(self):
|
||||||
ssl_vh1 = obj.VirtualHost(
|
ssl_vh1 = obj.VirtualHost(
|
||||||
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
|
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
|
||||||
|
|||||||
@@ -25,3 +25,20 @@ class ConstantsTest(unittest.TestCase):
|
|||||||
os_info.return_value = ('Nonexistent Linux', '', '')
|
os_info.return_value = ('Nonexistent Linux', '', '')
|
||||||
self.assertEqual(constants.os_constant("vhost_root"),
|
self.assertEqual(constants.os_constant("vhost_root"),
|
||||||
"/etc/apache2/sites-available")
|
"/etc/apache2/sites-available")
|
||||||
|
|
||||||
|
@mock.patch("certbot.util.get_os_info")
|
||||||
|
def test_get_default_constants(self, os_info):
|
||||||
|
os_info.return_value = ('Nonexistent Linux', '', '')
|
||||||
|
with mock.patch("certbot.util.get_systemd_os_like") as os_like:
|
||||||
|
# Get defaults
|
||||||
|
os_like.return_value = False
|
||||||
|
c_hm = constants.os_constant("handle_mods")
|
||||||
|
c_sr = constants.os_constant("server_root")
|
||||||
|
self.assertFalse(c_hm)
|
||||||
|
self.assertEqual(c_sr, "/etc/apache2")
|
||||||
|
# Use darwin as like test target
|
||||||
|
os_like.return_value = ["something", "nonexistent", "darwin"]
|
||||||
|
d_vr = constants.os_constant("vhost_root")
|
||||||
|
d_em = constants.os_constant("enmod")
|
||||||
|
self.assertFalse(d_em)
|
||||||
|
self.assertEqual(d_vr, "/etc/apache2/other")
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
FROM debian:jessie
|
||||||
|
MAINTAINER Brad Warren <bmw@eff.org>
|
||||||
|
|
||||||
|
# no need to mkdir anything:
|
||||||
|
# https://docs.docker.com/reference/builder/#copy
|
||||||
|
# If <dest> doesn't exist, it is created along with all missing
|
||||||
|
# directories in its path.
|
||||||
|
|
||||||
|
# TODO: Install non-default Python versions for tox.
|
||||||
|
# TODO: Install Apache/Nginx for plugin development.
|
||||||
|
COPY certbot-auto /opt/certbot/src/certbot-auto
|
||||||
|
RUN /opt/certbot/src/certbot-auto -n --os-packages-only
|
||||||
|
|
||||||
|
# the above is not likely to change, so by putting it further up the
|
||||||
|
# Dockerfile we make sure we cache as much as possible
|
||||||
|
|
||||||
|
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini pep8.travis.sh .pep8 .pylintrc /opt/certbot/src/
|
||||||
|
|
||||||
|
# all above files are necessary for setup.py, however, package source
|
||||||
|
# code directory has to be copied separately to a subdirectory...
|
||||||
|
# https://docs.docker.com/reference/builder/#copy: "If <src> is a
|
||||||
|
# directory, the entire contents of the directory are copied,
|
||||||
|
# including filesystem metadata. Note: The directory itself is not
|
||||||
|
# copied, just its contents." Order again matters, three files are far
|
||||||
|
# more likely to be cached than the whole project directory
|
||||||
|
|
||||||
|
COPY certbot /opt/certbot/src/certbot/
|
||||||
|
COPY acme /opt/certbot/src/acme/
|
||||||
|
COPY certbot-apache /opt/certbot/src/certbot-apache/
|
||||||
|
COPY certbot-nginx /opt/certbot/src/certbot-nginx/
|
||||||
|
COPY certbot-compatibility-test /opt/certbot/src/certbot-compatibility-test/
|
||||||
|
|
||||||
|
RUN virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
|
||||||
|
/opt/certbot/venv/bin/pip install -U setuptools && \
|
||||||
|
/opt/certbot/venv/bin/pip install -U pip && \
|
||||||
|
/opt/certbot/venv/bin/pip install \
|
||||||
|
-e /opt/certbot/src/acme \
|
||||||
|
-e /opt/certbot/src \
|
||||||
|
-e /opt/certbot/src/certbot-apache \
|
||||||
|
-e /opt/certbot/src/certbot-nginx \
|
||||||
|
-e /opt/certbot/src/certbot-compatibility-test \
|
||||||
|
-e /opt/certbot/src[dev,docs]
|
||||||
|
|
||||||
|
# install in editable mode (-e) to save space: it's not possible to
|
||||||
|
# "rm -rf /opt/certbot/src" (it's stays in the underlaying image);
|
||||||
|
# this might also help in debugging: you can "docker run --entrypoint
|
||||||
|
# bash" and investigate, apply patches, etc.
|
||||||
|
|
||||||
|
WORKDIR /opt/certbot/src/certbot-compatibility-test/certbot_compatibility_test/testdata
|
||||||
|
|
||||||
|
ENV PATH /opt/certbot/venv/bin:$PATH
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
FROM certbot-compatibility-test
|
||||||
|
MAINTAINER Brad Warren <bmw@eff.org>
|
||||||
|
|
||||||
|
RUN apt-get install apache2 -y
|
||||||
|
|
||||||
|
ENTRYPOINT [ "certbot-compatibility-test", "-p", "apache" ]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
FROM certbot-compatibility-test
|
||||||
|
MAINTAINER Brad Warren <bmw@eff.org>
|
||||||
|
|
||||||
|
RUN apt-get install nginx -y
|
||||||
|
|
||||||
|
ENTRYPOINT [ "certbot-compatibility-test", "-p", "nginx" ]
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
FROM httpd
|
|
||||||
MAINTAINER Brad Warren <bradmw@umich.edu>
|
|
||||||
|
|
||||||
RUN mkdir /var/run/apache2
|
|
||||||
|
|
||||||
ENV APACHE_RUN_USER=daemon \
|
|
||||||
APACHE_RUN_GROUP=daemon \
|
|
||||||
APACHE_PID_FILE=/usr/local/apache2/logs/httpd.pid \
|
|
||||||
APACHE_RUN_DIR=/var/run/apache2 \
|
|
||||||
APACHE_LOCK_DIR=/var/lock \
|
|
||||||
APACHE_LOG_DIR=/usr/local/apache2/logs
|
|
||||||
|
|
||||||
COPY certbot-compatibility-test/certbot_compatibility_test/configurators/apache/a2enmod.sh /usr/local/bin/
|
|
||||||
COPY certbot-compatibility-test/certbot_compatibility_test/configurators/apache/a2dismod.sh /usr/local/bin/
|
|
||||||
COPY certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem /usr/local/apache2/conf/
|
|
||||||
COPY certbot-compatibility-test/certbot_compatibility_test/testdata/empty_cert.pem /usr/local/apache2/conf/
|
|
||||||
|
|
||||||
# Note: this only exposes the port to other docker containers. You
|
|
||||||
# still have to bind to 443@host at runtime.
|
|
||||||
EXPOSE 443
|
|
||||||
+3
-42
@@ -1,5 +1,4 @@
|
|||||||
"""Provides a common base for Apache proxies"""
|
"""Provides a common base for Apache proxies"""
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -17,10 +16,6 @@ from certbot_compatibility_test import util
|
|||||||
from certbot_compatibility_test.configurators import common as configurators_common
|
from certbot_compatibility_test.configurators import common as configurators_common
|
||||||
|
|
||||||
|
|
||||||
APACHE_VERSION_REGEX = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
|
|
||||||
APACHE_COMMANDS = ["apachectl", "a2enmod", "a2dismod"]
|
|
||||||
|
|
||||||
|
|
||||||
@zope.interface.implementer(interfaces.IConfiguratorProxy)
|
@zope.interface.implementer(interfaces.IConfiguratorProxy)
|
||||||
class Proxy(configurators_common.Proxy):
|
class Proxy(configurators_common.Proxy):
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
@@ -32,37 +27,25 @@ class Proxy(configurators_common.Proxy):
|
|||||||
self.le_config.apache_le_vhost_ext = "-le-ssl.conf"
|
self.le_config.apache_le_vhost_ext = "-le-ssl.conf"
|
||||||
|
|
||||||
self.modules = self.server_root = self.test_conf = self.version = None
|
self.modules = self.server_root = self.test_conf = self.version = None
|
||||||
self._apache_configurator = self._all_names = self._test_names = None
|
|
||||||
patch = mock.patch(
|
patch = mock.patch(
|
||||||
"certbot_apache.configurator.display_ops.select_vhost")
|
"certbot_apache.configurator.display_ops.select_vhost")
|
||||||
mock_display = patch.start()
|
mock_display = patch.start()
|
||||||
mock_display.side_effect = le_errors.PluginError(
|
mock_display.side_effect = le_errors.PluginError(
|
||||||
"Unable to determine vhost")
|
"Unable to determine vhost")
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
"""Wraps the Apache Configurator methods"""
|
|
||||||
method = getattr(self._apache_configurator, name, None)
|
|
||||||
if callable(method):
|
|
||||||
return method
|
|
||||||
else:
|
|
||||||
raise AttributeError()
|
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
"""Loads the next configuration for the plugin to test"""
|
"""Loads the next configuration for the plugin to test"""
|
||||||
|
|
||||||
config = super(Proxy, self).load_config()
|
config = super(Proxy, self).load_config()
|
||||||
self._all_names, self._test_names = _get_names(config)
|
self._all_names, self._test_names = _get_names(config)
|
||||||
|
|
||||||
server_root = _get_server_root(config)
|
server_root = _get_server_root(config)
|
||||||
# with open(os.path.join(config, "config_file")) as f:
|
|
||||||
# config_file = os.path.join(server_root, f.readline().rstrip())
|
|
||||||
shutil.rmtree("/etc/apache2")
|
shutil.rmtree("/etc/apache2")
|
||||||
shutil.copytree(server_root, "/etc/apache2", symlinks=True)
|
shutil.copytree(server_root, "/etc/apache2", symlinks=True)
|
||||||
|
|
||||||
self._prepare_configurator()
|
self._prepare_configurator()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.check_call("apachectl -k start".split())
|
subprocess.check_call("apachectl -k restart".split())
|
||||||
except errors.Error:
|
except errors.Error:
|
||||||
raise errors.Error(
|
raise errors.Error(
|
||||||
"Apache failed to load {0} before tests started".format(
|
"Apache failed to load {0} before tests started".format(
|
||||||
@@ -78,38 +61,16 @@ class Proxy(configurators_common.Proxy):
|
|||||||
# An alias
|
# An alias
|
||||||
self.le_config.apache_handle_modules = self.le_config.apache_handle_mods
|
self.le_config.apache_handle_modules = self.le_config.apache_handle_mods
|
||||||
|
|
||||||
self._apache_configurator = configurator.ApacheConfigurator(
|
self._configurator = configurator.ApacheConfigurator(
|
||||||
config=configuration.NamespaceConfig(self.le_config),
|
config=configuration.NamespaceConfig(self.le_config),
|
||||||
name="apache")
|
name="apache")
|
||||||
self._apache_configurator.prepare()
|
self._configurator.prepare()
|
||||||
|
|
||||||
def cleanup_from_tests(self):
|
def cleanup_from_tests(self):
|
||||||
"""Performs any necessary cleanup from running plugin tests"""
|
"""Performs any necessary cleanup from running plugin tests"""
|
||||||
super(Proxy, self).cleanup_from_tests()
|
super(Proxy, self).cleanup_from_tests()
|
||||||
mock.patch.stopall()
|
mock.patch.stopall()
|
||||||
|
|
||||||
def get_all_names_answer(self):
|
|
||||||
"""Returns the set of domain names that the plugin should find"""
|
|
||||||
if self._all_names:
|
|
||||||
return self._all_names
|
|
||||||
else:
|
|
||||||
raise errors.Error("No configuration file loaded")
|
|
||||||
|
|
||||||
def get_testable_domain_names(self):
|
|
||||||
"""Returns the set of domain names that can be tested against"""
|
|
||||||
if self._test_names:
|
|
||||||
return self._test_names
|
|
||||||
else:
|
|
||||||
return {"example.com"}
|
|
||||||
|
|
||||||
def deploy_cert(self, domain, cert_path, key_path, chain_path=None,
|
|
||||||
fullchain_path=None):
|
|
||||||
"""Installs cert"""
|
|
||||||
cert_path, key_path, chain_path = self.copy_certs_and_keys(
|
|
||||||
cert_path, key_path, chain_path)
|
|
||||||
self._apache_configurator.deploy_cert(
|
|
||||||
domain, cert_path, key_path, chain_path, fullchain_path)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_server_root(config):
|
def _get_server_root(config):
|
||||||
"""Returns the server root directory in config"""
|
"""Returns the server root directory in config"""
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from certbot import constants
|
from certbot import constants
|
||||||
|
from certbot_compatibility_test import errors
|
||||||
from certbot_compatibility_test import util
|
from certbot_compatibility_test import util
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +32,18 @@ class Proxy(object):
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.http_port = 80
|
self.http_port = 80
|
||||||
self.https_port = 443
|
self.https_port = 443
|
||||||
|
self._configurator = self._all_names = self._test_names = None
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"""Wraps the configurator methods"""
|
||||||
|
if self._configurator is None:
|
||||||
|
raise AttributeError()
|
||||||
|
|
||||||
|
method = getattr(self._configurator, name, None)
|
||||||
|
if callable(method):
|
||||||
|
return method
|
||||||
|
else:
|
||||||
|
raise AttributeError()
|
||||||
|
|
||||||
def has_more_configs(self):
|
def has_more_configs(self):
|
||||||
"""Returns true if there are more configs to test"""
|
"""Returns true if there are more configs to test"""
|
||||||
@@ -63,3 +76,25 @@ class Proxy(object):
|
|||||||
chain = None
|
chain = None
|
||||||
|
|
||||||
return cert, key, chain
|
return cert, key, chain
|
||||||
|
|
||||||
|
def get_all_names_answer(self):
|
||||||
|
"""Returns the set of domain names that the plugin should find"""
|
||||||
|
if self._all_names:
|
||||||
|
return self._all_names
|
||||||
|
else:
|
||||||
|
raise errors.Error("No configuration file loaded")
|
||||||
|
|
||||||
|
def get_testable_domain_names(self):
|
||||||
|
"""Returns the set of domain names that can be tested against"""
|
||||||
|
if self._test_names:
|
||||||
|
return self._test_names
|
||||||
|
else:
|
||||||
|
return {"example.com"}
|
||||||
|
|
||||||
|
def deploy_cert(self, domain, cert_path, key_path, chain_path=None,
|
||||||
|
fullchain_path=None):
|
||||||
|
"""Installs cert"""
|
||||||
|
cert_path, key_path, chain_path = self.copy_certs_and_keys(
|
||||||
|
cert_path, key_path, chain_path)
|
||||||
|
self._configurator.deploy_cert(
|
||||||
|
domain, cert_path, key_path, chain_path, fullchain_path)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Certbot compatibility test Nginx configurators"""
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
"""Provides a common base for Nginx proxies"""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import zope.interface
|
||||||
|
|
||||||
|
from certbot import configuration
|
||||||
|
from certbot_nginx import configurator
|
||||||
|
from certbot_nginx import constants
|
||||||
|
from certbot_compatibility_test import errors
|
||||||
|
from certbot_compatibility_test import interfaces
|
||||||
|
from certbot_compatibility_test import util
|
||||||
|
from certbot_compatibility_test.configurators import common as configurators_common
|
||||||
|
|
||||||
|
|
||||||
|
@zope.interface.implementer(interfaces.IConfiguratorProxy)
|
||||||
|
class Proxy(configurators_common.Proxy):
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
"""A common base for Nginx test configurators"""
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
"""Initializes the plugin with the given command line args"""
|
||||||
|
super(Proxy, self).__init__(args)
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
"""Loads the next configuration for the plugin to test"""
|
||||||
|
config = super(Proxy, self).load_config()
|
||||||
|
self._all_names, self._test_names = _get_names(config)
|
||||||
|
|
||||||
|
server_root = _get_server_root(config)
|
||||||
|
|
||||||
|
# XXX: Deleting all of this is kind of scary unless the test
|
||||||
|
# instances really each have a complete configuration!
|
||||||
|
shutil.rmtree("/etc/nginx")
|
||||||
|
shutil.copytree(server_root, "/etc/nginx", symlinks=True)
|
||||||
|
|
||||||
|
self._prepare_configurator()
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.check_call("service nginx reload".split())
|
||||||
|
except errors.Error:
|
||||||
|
raise errors.Error(
|
||||||
|
"Nginx failed to load {0} before tests started".format(
|
||||||
|
config))
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def _prepare_configurator(self):
|
||||||
|
"""Prepares the Nginx plugin for testing"""
|
||||||
|
for k in constants.CLI_DEFAULTS.keys():
|
||||||
|
setattr(self.le_config, "nginx_" + k, constants.os_constant(k))
|
||||||
|
|
||||||
|
conf = configuration.NamespaceConfig(self.le_config)
|
||||||
|
zope.component.provideUtility(conf)
|
||||||
|
self._configurator = configurator.NginxConfigurator(
|
||||||
|
config=conf, name="nginx")
|
||||||
|
self._configurator.prepare()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_server_root(config):
|
||||||
|
"""Returns the server root directory in config"""
|
||||||
|
subdirs = [
|
||||||
|
name for name in os.listdir(config)
|
||||||
|
if os.path.isdir(os.path.join(config, name))]
|
||||||
|
|
||||||
|
if len(subdirs) != 1:
|
||||||
|
raise errors.Error("Malformed configuration directory {0}".format(config))
|
||||||
|
|
||||||
|
return os.path.join(config, subdirs[0].rstrip())
|
||||||
|
|
||||||
|
|
||||||
|
def _get_names(config):
|
||||||
|
"""Returns all and testable domain names in config"""
|
||||||
|
all_names = set()
|
||||||
|
for root, _dirs, files in os.walk(config):
|
||||||
|
for this_file in files:
|
||||||
|
for line in open(os.path.join(root, this_file)):
|
||||||
|
if line.strip().startswith("server_name"):
|
||||||
|
names = line.partition("server_name")[2].rpartition(";")[0]
|
||||||
|
for n in names.split():
|
||||||
|
all_names.add(n)
|
||||||
|
non_ip_names = set(n for n in all_names if not util.IP_REGEX.match(n))
|
||||||
|
return all_names, non_ip_names
|
||||||
@@ -22,7 +22,8 @@ from certbot_compatibility_test import errors
|
|||||||
from certbot_compatibility_test import util
|
from certbot_compatibility_test import util
|
||||||
from certbot_compatibility_test import validator
|
from certbot_compatibility_test import validator
|
||||||
|
|
||||||
from certbot_compatibility_test.configurators.apache import common
|
from certbot_compatibility_test.configurators.apache import common as a_common
|
||||||
|
from certbot_compatibility_test.configurators.nginx import common as n_common
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION = """
|
DESCRIPTION = """
|
||||||
@@ -32,7 +33,7 @@ tests that the plugin supports are performed.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGINS = {"apache": common.Proxy}
|
PLUGINS = {"apache": a_common.Proxy, "nginx": n_common.Proxy}
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -143,7 +144,7 @@ def test_deploy_cert(plugin, temp_dir, domains):
|
|||||||
|
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
try:
|
try:
|
||||||
plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path)
|
plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path, cert_path)
|
||||||
plugin.save() # Needed by the Apache plugin
|
plugin.save() # Needed by the Apache plugin
|
||||||
except le_errors.Error as error:
|
except le_errors.Error as error:
|
||||||
logger.error("Plugin failed to deploy ceritificate for %s:", domain)
|
logger.error("Plugin failed to deploy ceritificate for %s:", domain)
|
||||||
|
|||||||
Binary file not shown.
@@ -160,9 +160,9 @@ class NginxConfigurator(common.Plugin):
|
|||||||
stapling_directives = []
|
stapling_directives = []
|
||||||
if self.version >= (1, 3, 7):
|
if self.version >= (1, 3, 7):
|
||||||
stapling_directives = [
|
stapling_directives = [
|
||||||
['\n', 'ssl_trusted_certificate', ' ', chain_path],
|
['\n ', 'ssl_trusted_certificate', ' ', chain_path],
|
||||||
['\n', 'ssl_stapling', ' ', 'on'],
|
['\n ', 'ssl_stapling', ' ', 'on'],
|
||||||
['\n', 'ssl_stapling_verify', ' ', 'on'], ['\n']]
|
['\n ', 'ssl_stapling_verify', ' ', 'on'], ['\n']]
|
||||||
|
|
||||||
if len(stapling_directives) != 0 and not chain_path:
|
if len(stapling_directives) != 0 and not chain_path:
|
||||||
raise errors.PluginError(
|
raise errors.PluginError(
|
||||||
@@ -337,10 +337,10 @@ class NginxConfigurator(common.Plugin):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
snakeoil_cert, snakeoil_key = self._get_snakeoil_paths()
|
snakeoil_cert, snakeoil_key = self._get_snakeoil_paths()
|
||||||
ssl_block = [['\n', 'listen', ' ', '{0} ssl'.format(self.config.tls_sni_01_port)],
|
ssl_block = [['\n ', 'listen', ' ', '{0} ssl'.format(self.config.tls_sni_01_port)],
|
||||||
['\n', 'ssl_certificate', ' ', snakeoil_cert],
|
['\n ', 'ssl_certificate', ' ', snakeoil_cert],
|
||||||
['\n', 'ssl_certificate_key', ' ', snakeoil_key],
|
['\n ', 'ssl_certificate_key', ' ', snakeoil_key],
|
||||||
['\n', 'include', ' ', self.parser.loc["ssl_options"]]]
|
['\n ', 'include', ' ', self.parser.loc["ssl_options"]]]
|
||||||
self.parser.add_server_directives(
|
self.parser.add_server_directives(
|
||||||
vhost.filep, vhost.names, ssl_block, replace=False)
|
vhost.filep, vhost.names, ssl_block, replace=False)
|
||||||
vhost.ssl = True
|
vhost.ssl = True
|
||||||
@@ -401,9 +401,10 @@ class NginxConfigurator(common.Plugin):
|
|||||||
:type unused_options: Not Available
|
:type unused_options: Not Available
|
||||||
"""
|
"""
|
||||||
redirect_block = [[
|
redirect_block = [[
|
||||||
['if', '($scheme != "https")'],
|
['\n ', 'if', ' ', '($scheme != "https") '],
|
||||||
[['return', '301 https://$host$request_uri']]
|
[['\n ', 'return', ' ', '301 https://$host$request_uri'],
|
||||||
]]
|
'\n ']
|
||||||
|
], ['\n']]
|
||||||
self.parser.add_server_directives(
|
self.parser.add_server_directives(
|
||||||
vhost.filep, vhost.names, redirect_block, replace=False)
|
vhost.filep, vhost.names, redirect_block, replace=False)
|
||||||
logger.info("Redirecting all traffic to ssl in %s", vhost.filep)
|
logger.info("Redirecting all traffic to ssl in %s", vhost.filep)
|
||||||
|
|||||||
@@ -16,3 +16,17 @@ MOD_SSL_CONF_SRC = pkg_resources.resource_filename(
|
|||||||
"certbot_nginx", "options-ssl-nginx.conf")
|
"certbot_nginx", "options-ssl-nginx.conf")
|
||||||
"""Path to the nginx mod_ssl config file found in the Certbot
|
"""Path to the nginx mod_ssl config file found in the Certbot
|
||||||
distribution."""
|
distribution."""
|
||||||
|
|
||||||
|
def os_constant(key):
|
||||||
|
# XXX TODO: In the future, this could return different constants
|
||||||
|
# based on what OS we are running under. To see an
|
||||||
|
# approach to how to handle different OSes, see the
|
||||||
|
# apache version of this file. Currently, we do not
|
||||||
|
# actually have any OS-specific constants on Nginx.
|
||||||
|
"""
|
||||||
|
Get a constant value for operating system
|
||||||
|
|
||||||
|
:param key: name of cli constant
|
||||||
|
:return: value of constant for active os
|
||||||
|
"""
|
||||||
|
return CLI_DEFAULTS[key]
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||||||
|
|
||||||
def test_redirect_enhance(self):
|
def test_redirect_enhance(self):
|
||||||
expected = [
|
expected = [
|
||||||
['if', '($scheme != "https")'],
|
['if', '($scheme != "https") '],
|
||||||
[['return', '301 https://$host$request_uri']]
|
[['return', '301 https://$host$request_uri']]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -91,10 +91,10 @@ class NginxTlsSni01(common.TLSSNI01):
|
|||||||
# Add the 'include' statement for the challenges if it doesn't exist
|
# Add the 'include' statement for the challenges if it doesn't exist
|
||||||
# already in the main config
|
# already in the main config
|
||||||
included = False
|
included = False
|
||||||
include_directive = ['include', ' ', self.challenge_conf]
|
include_directive = ['\n', 'include', ' ', self.challenge_conf]
|
||||||
root = self.configurator.parser.loc["root"]
|
root = self.configurator.parser.loc["root"]
|
||||||
|
|
||||||
bucket_directive = ['server_names_hash_bucket_size', ' ', '128']
|
bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128']
|
||||||
|
|
||||||
main = self.configurator.parser.parsed[root]
|
main = self.configurator.parser.parsed[root]
|
||||||
for key, body in main:
|
for key, body in main:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""ACME AuthHandler."""
|
"""ACME AuthHandler."""
|
||||||
import itertools
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import six
|
||||||
import zope.component
|
import zope.component
|
||||||
|
|
||||||
from acme import challenges
|
from acme import challenges
|
||||||
@@ -141,7 +141,7 @@ class AuthHandler(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
active_achalls = []
|
active_achalls = []
|
||||||
for achall, resp in itertools.izip(achalls, resps):
|
for achall, resp in six.moves.zip(achalls, resps):
|
||||||
# This line needs to be outside of the if block below to
|
# This line needs to be outside of the if block below to
|
||||||
# ensure failed challenges are cleaned up correctly
|
# ensure failed challenges are cleaned up correctly
|
||||||
active_achalls.append(achall)
|
active_achalls.append(achall)
|
||||||
@@ -472,7 +472,7 @@ def _report_failed_challs(failed_achalls):
|
|||||||
problems.setdefault(achall.error.typ, []).append(achall)
|
problems.setdefault(achall.error.typ, []).append(achall)
|
||||||
|
|
||||||
reporter = zope.component.getUtility(interfaces.IReporter)
|
reporter = zope.component.getUtility(interfaces.IReporter)
|
||||||
for achalls in problems.itervalues():
|
for achalls in six.itervalues(problems):
|
||||||
reporter.add_message(
|
reporter.add_message(
|
||||||
_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY)
|
_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY)
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -343,8 +343,10 @@ class HelpfulArgumentParser(object):
|
|||||||
self.determine_verb()
|
self.determine_verb()
|
||||||
help1 = self.prescan_for_flag("-h", self.help_topics)
|
help1 = self.prescan_for_flag("-h", self.help_topics)
|
||||||
help2 = self.prescan_for_flag("--help", self.help_topics)
|
help2 = self.prescan_for_flag("--help", self.help_topics)
|
||||||
assert max(True, "a") == "a", "Gravity changed direction"
|
if isinstance(help1, bool) and isinstance(help2, bool):
|
||||||
self.help_arg = max(help1, help2)
|
self.help_arg = help1 or help2
|
||||||
|
else:
|
||||||
|
self.help_arg = help1 if isinstance(help1, str) else help2
|
||||||
if self.help_arg is True:
|
if self.help_arg is True:
|
||||||
# just --help with no topic; avoid argparse altogether
|
# just --help with no topic; avoid argparse altogether
|
||||||
print(usage)
|
print(usage)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ CLI_DEFAULTS = dict(
|
|||||||
os.path.join(os.environ.get("XDG_CONFIG_HOME", "~/.config"),
|
os.path.join(os.environ.get("XDG_CONFIG_HOME", "~/.config"),
|
||||||
"letsencrypt", "cli.ini"),
|
"letsencrypt", "cli.ini"),
|
||||||
],
|
],
|
||||||
verbose_count=-(logging.INFO / 10),
|
verbose_count=-int(logging.INFO / 10),
|
||||||
server="https://acme-v01.api.letsencrypt.org/directory",
|
server="https://acme-v01.api.letsencrypt.org/directory",
|
||||||
rsa_key_size=2048,
|
rsa_key_size=2048,
|
||||||
rollback_checkpoints=1,
|
rollback_checkpoints=1,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import collections
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
import six
|
||||||
|
|
||||||
import zope.interface
|
import zope.interface
|
||||||
import zope.interface.verify
|
import zope.interface.verify
|
||||||
@@ -194,12 +195,12 @@ class PluginsRegistry(collections.Mapping):
|
|||||||
def init(self, config):
|
def init(self, config):
|
||||||
"""Initialize all plugins in the registry."""
|
"""Initialize all plugins in the registry."""
|
||||||
return [plugin_ep.init(config) for plugin_ep
|
return [plugin_ep.init(config) for plugin_ep
|
||||||
in self._plugins.itervalues()]
|
in six.itervalues(self._plugins)]
|
||||||
|
|
||||||
def filter(self, pred):
|
def filter(self, pred):
|
||||||
"""Filter plugins based on predicate."""
|
"""Filter plugins based on predicate."""
|
||||||
return type(self)(dict((name, plugin_ep) for name, plugin_ep
|
return type(self)(dict((name, plugin_ep) for name, plugin_ep
|
||||||
in self._plugins.iteritems() if pred(plugin_ep)))
|
in six.iteritems(self._plugins) if pred(plugin_ep)))
|
||||||
|
|
||||||
def visible(self):
|
def visible(self):
|
||||||
"""Filter plugins based on visibility."""
|
"""Filter plugins based on visibility."""
|
||||||
@@ -216,7 +217,7 @@ class PluginsRegistry(collections.Mapping):
|
|||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
"""Prepare all plugins in the registry."""
|
"""Prepare all plugins in the registry."""
|
||||||
return [plugin_ep.prepare() for plugin_ep in self._plugins.itervalues()]
|
return [plugin_ep.prepare() for plugin_ep in six.itervalues(self._plugins)]
|
||||||
|
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Filter plugins based on availability."""
|
"""Filter plugins based on availability."""
|
||||||
@@ -238,7 +239,7 @@ class PluginsRegistry(collections.Mapping):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# use list instead of set because PluginEntryPoint is not hashable
|
# use list instead of set because PluginEntryPoint is not hashable
|
||||||
candidates = [plugin_ep for plugin_ep in self._plugins.itervalues()
|
candidates = [plugin_ep for plugin_ep in six.itervalues(self._plugins)
|
||||||
if plugin_ep.initialized and plugin_ep.init() is plugin]
|
if plugin_ep.initialized and plugin_ep.init() is plugin]
|
||||||
assert len(candidates) <= 1
|
assert len(candidates) <= 1
|
||||||
if candidates:
|
if candidates:
|
||||||
@@ -249,7 +250,7 @@ class PluginsRegistry(collections.Mapping):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{0}({1})".format(
|
return "{0}({1})".format(
|
||||||
self.__class__.__name__, ','.join(
|
self.__class__.__name__, ','.join(
|
||||||
repr(p_ep) for p_ep in self._plugins.itervalues()))
|
repr(p_ep) for p_ep in six.itervalues(self._plugins)))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if not self._plugins:
|
if not self._plugins:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import six
|
||||||
import zope.component
|
import zope.component
|
||||||
import zope.interface
|
import zope.interface
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@ s.serve_forever()" """
|
|||||||
#answer = zope.component.getUtility(interfaces.IDisplay).notification(
|
#answer = zope.component.getUtility(interfaces.IDisplay).notification(
|
||||||
# message=message, height=25, pause=True)
|
# message=message, height=25, pause=True)
|
||||||
sys.stdout.write(message)
|
sys.stdout.write(message)
|
||||||
raw_input("Press ENTER to continue")
|
six.moves.input("Press ENTER to continue")
|
||||||
|
|
||||||
def cleanup(self, achalls):
|
def cleanup(self, achalls):
|
||||||
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def pick_plugin(config, default, plugins, question, ifaces):
|
|||||||
else:
|
else:
|
||||||
return plugin_ep.init()
|
return plugin_ep.init()
|
||||||
elif len(prepared) == 1:
|
elif len(prepared) == 1:
|
||||||
plugin_ep = prepared.values()[0]
|
plugin_ep = list(prepared.values())[0]
|
||||||
logger.debug("Single candidate plugin: %s", plugin_ep)
|
logger.debug("Single candidate plugin: %s", plugin_ep)
|
||||||
if plugin_ep.misconfigured:
|
if plugin_ep.misconfigured:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
NAME="SystemdOS"
|
NAME="SystemdOS"
|
||||||
VERSION="42.42.42 LTS, Unreal"
|
VERSION="42.42.42 LTS, Unreal"
|
||||||
ID=systemdos
|
ID=systemdos
|
||||||
ID_LIKE=debian
|
ID_LIKE="something nonexistent debian"
|
||||||
VERSION_ID="42"
|
VERSION_ID="42"
|
||||||
HOME_URL="http://www.example.com/"
|
HOME_URL="http://www.example.com/"
|
||||||
SUPPORT_URL="http://help.example.com/"
|
SUPPORT_URL="http://help.example.com/"
|
||||||
|
|||||||
@@ -359,6 +359,15 @@ class OsInfoTest(unittest.TestCase):
|
|||||||
with mock.patch('os.path.isfile', return_value=False):
|
with mock.patch('os.path.isfile', return_value=False):
|
||||||
self.assertEqual(get_systemd_os_info(), ("", ""))
|
self.assertEqual(get_systemd_os_info(), ("", ""))
|
||||||
|
|
||||||
|
def test_systemd_os_release_like(self):
|
||||||
|
from certbot.util import get_systemd_os_like
|
||||||
|
|
||||||
|
with mock.patch('os.path.isfile', return_value=True):
|
||||||
|
id_likes = get_systemd_os_like(test_util.vector_path(
|
||||||
|
"os-release"))
|
||||||
|
self.assertEqual(len(id_likes), 3)
|
||||||
|
self.assertTrue("debian" in id_likes)
|
||||||
|
|
||||||
@mock.patch("certbot.util.subprocess.Popen")
|
@mock.patch("certbot.util.subprocess.Popen")
|
||||||
def test_non_systemd_os_info(self, popen_mock):
|
def test_non_systemd_os_info(self, popen_mock):
|
||||||
from certbot.util import (get_os_info, get_python_os_info,
|
from certbot.util import (get_os_info, get_python_os_info,
|
||||||
|
|||||||
@@ -268,6 +268,19 @@ def get_systemd_os_info(filepath="/etc/os-release"):
|
|||||||
return (os_name, os_version)
|
return (os_name, os_version)
|
||||||
|
|
||||||
|
|
||||||
|
def get_systemd_os_like(filepath="/etc/os-release"):
|
||||||
|
"""
|
||||||
|
Get a list of strings that indicate the distribution likeness to
|
||||||
|
other distributions.
|
||||||
|
|
||||||
|
:param str filepath: File path of os-release file
|
||||||
|
:returns: List of distribution acronyms
|
||||||
|
:rtype: `list` of `str`
|
||||||
|
"""
|
||||||
|
|
||||||
|
return _get_systemd_os_release_var("ID_LIKE", filepath).split(" ")
|
||||||
|
|
||||||
|
|
||||||
def _get_systemd_os_release_var(varname, filepath="/etc/os-release"):
|
def _get_systemd_os_release_var(varname, filepath="/etc/os-release"):
|
||||||
"""
|
"""
|
||||||
Get single value from systemd /etc/os-release
|
Get single value from systemd /etc/os-release
|
||||||
@@ -409,6 +422,9 @@ def enforce_domain_sanity(domain):
|
|||||||
else:
|
else:
|
||||||
raise errors.ConfigurationError(str(error_fmt).format(domain))
|
raise errors.ConfigurationError(str(error_fmt).format(domain))
|
||||||
|
|
||||||
|
if six.PY3:
|
||||||
|
domain = domain.decode('ascii')
|
||||||
|
|
||||||
# Remove trailing dot
|
# Remove trailing dot
|
||||||
domain = domain[:-1] if domain.endswith('.') else domain
|
domain = domain[:-1] if domain.endswith('.') else domain
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Developer Guide
|
|||||||
:local:
|
:local:
|
||||||
|
|
||||||
|
|
||||||
.. _hacking:
|
.. _getting_started:
|
||||||
|
|
||||||
Hacking
|
Getting Started
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Running a local copy of the client
|
Running a local copy of the client
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ Welcome to the Certbot documentation!
|
|||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
intro
|
intro
|
||||||
|
install
|
||||||
using
|
using
|
||||||
contributing
|
contributing
|
||||||
packaging
|
packaging
|
||||||
|
resources
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
=====================
|
||||||
|
Quick Installation
|
||||||
|
=====================
|
||||||
|
|
||||||
|
If ``certbot`` (or ``letsencrypt``) is packaged for your Unix OS (visit
|
||||||
|
certbot.eff.org_ to find out), you can install it
|
||||||
|
from there, and run it by typing ``certbot`` (or ``letsencrypt``). Because
|
||||||
|
not all operating systems have packages yet, we provide a temporary solution
|
||||||
|
via the ``certbot-auto`` wrapper script, which obtains some dependencies from
|
||||||
|
your OS and puts others in a python virtual environment::
|
||||||
|
|
||||||
|
user@webserver:~$ wget https://dl.eff.org/certbot-auto
|
||||||
|
user@webserver:~$ chmod a+x ./certbot-auto
|
||||||
|
user@webserver:~$ ./certbot-auto --help
|
||||||
|
|
||||||
|
.. hint:: The certbot-auto download is protected by HTTPS, which is pretty good, but if you'd like to
|
||||||
|
double check the integrity of the ``certbot-auto`` script, you can use these steps for verification before running it::
|
||||||
|
|
||||||
|
user@server:~$ wget -N https://dl.eff.org/certbot-auto.asc
|
||||||
|
user@server:~$ gpg2 --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2
|
||||||
|
user@server:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc certbot-auto
|
||||||
|
|
||||||
|
And for full command line help, you can type::
|
||||||
|
|
||||||
|
./certbot-auto --help all
|
||||||
|
|
||||||
|
``certbot-auto`` updates to the latest client release automatically. And
|
||||||
|
since ``certbot-auto`` is a wrapper to ``certbot``, it accepts exactly
|
||||||
|
the same command line flags and arguments. More details about this script and
|
||||||
|
other installation methods can be found `in the User Guide
|
||||||
|
<https://certbot.eff.org/docs/using.html#installation>`_.
|
||||||
|
|
||||||
|
.. _certbot.eff.org: https://certbot.eff.org/
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
=====================
|
=====================
|
||||||
README / Introduction
|
Introduction
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
.. include:: ../README.rst
|
.. include:: ../README.rst
|
||||||
.. include:: ../CHANGES.rst
|
:start-after: tag:intro-begin
|
||||||
|
:end-before: tag:intro-end
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
=====================
|
||||||
|
Resources
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Documentation: https://certbot.eff.org/docs
|
||||||
|
|
||||||
|
Software project: https://github.com/certbot/certbot
|
||||||
|
|
||||||
|
Notes for developers: https://certbot.eff.org/docs/contributing.html
|
||||||
|
|
||||||
|
Main Website: https://letsencrypt.org/
|
||||||
|
|
||||||
|
Let's Encrypt FAQ: https://community.letsencrypt.org/t/frequently-asked-questions-faq/26#topic-title
|
||||||
|
|
||||||
|
IRC Channel: #letsencrypt on `Freenode`_ or #certbot on `OFTC`_
|
||||||
|
|
||||||
|
Community: https://community.letsencrypt.org
|
||||||
|
|
||||||
|
ACME spec: http://ietf-wg-acme.github.io/acme/
|
||||||
|
|
||||||
|
ACME working area in github: https://github.com/ietf-wg-acme/acme
|
||||||
|
|
||||||
|
|
||||||
|
Mailing list: `client-dev`_ (to subscribe without a Google account, send an
|
||||||
|
email to client-dev+subscribe@letsencrypt.org)
|
||||||
|
|
||||||
|
|build-status| |coverage| |docs| |container|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. |build-status| image:: https://travis-ci.org/certbot/certbot.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/certbot/certbot
|
||||||
|
:alt: Travis CI status
|
||||||
|
|
||||||
|
.. |coverage| image:: https://coveralls.io/repos/certbot/certbot/badge.svg?branch=master
|
||||||
|
:target: https://coveralls.io/r/certbot/certbot
|
||||||
|
:alt: Coverage status
|
||||||
|
|
||||||
|
.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/
|
||||||
|
:target: https://readthedocs.org/projects/letsencrypt/
|
||||||
|
:alt: Documentation status
|
||||||
|
|
||||||
|
.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status
|
||||||
|
:target: https://quay.io/repository/letsencrypt/letsencrypt
|
||||||
|
:alt: Docker Repository on Quay.io
|
||||||
|
|
||||||
|
.. _`installation instructions`:
|
||||||
|
https://letsencrypt.readthedocs.org/en/latest/using.html
|
||||||
|
|
||||||
|
.. _watch demo video: https://www.youtube.com/watch?v=Gas_sSB-5SU
|
||||||
|
|
||||||
|
.. _Freenode: https://webchat.freenode.net?channels=%23letsencrypt
|
||||||
|
.. _OFTC: https://webchat.oftc.net?channels=%23certbot
|
||||||
|
.. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev
|
||||||
@@ -7,6 +7,26 @@ User Guide
|
|||||||
|
|
||||||
.. _installation:
|
.. _installation:
|
||||||
|
|
||||||
|
System Requirements
|
||||||
|
===================
|
||||||
|
|
||||||
|
The Let's Encrypt Client presently only runs on Unix-ish OSes that include
|
||||||
|
Python 2.6 or 2.7; Python 3.x support will hopefully be added in the future. The
|
||||||
|
client requires root access in order to write to ``/etc/letsencrypt``,
|
||||||
|
``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to bind to ports 80 and 443
|
||||||
|
(if you use the ``standalone`` plugin) and to read and modify webserver
|
||||||
|
configurations (if you use the ``apache`` or ``nginx`` plugins). If none of
|
||||||
|
these apply to you, it is theoretically possible to run without root privileges,
|
||||||
|
but for most users who want to avoid running an ACME client as root, either
|
||||||
|
`letsencrypt-nosudo <https://github.com/diafygi/letsencrypt-nosudo>`_ or
|
||||||
|
`simp_le <https://github.com/kuba/simp_le>`_ are more appropriate choices.
|
||||||
|
|
||||||
|
The Apache plugin currently requires OS with augeas version 1.0; currently `it
|
||||||
|
supports
|
||||||
|
<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/constants.py>`_
|
||||||
|
modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin.
|
||||||
|
|
||||||
|
|
||||||
Getting Certbot
|
Getting Certbot
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ install_requires = [
|
|||||||
'parsedatetime>=1.3', # Calendar.parseDT
|
'parsedatetime>=1.3', # Calendar.parseDT
|
||||||
'PyOpenSSL',
|
'PyOpenSSL',
|
||||||
'pyrfc3339',
|
'pyrfc3339',
|
||||||
'python2-pythondialog>=3.2.2rc1', # Debian squeeze support, cf. #280
|
|
||||||
'pytz',
|
'pytz',
|
||||||
# For pkg_resources. >=1.0 so pip resolves it to a version cryptography
|
# For pkg_resources. >=1.0 so pip resolves it to a version cryptography
|
||||||
# will tolerate; see #2599:
|
# will tolerate; see #2599:
|
||||||
@@ -52,6 +51,12 @@ install_requires = [
|
|||||||
'zope.interface',
|
'zope.interface',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Debian squeeze support, cf. #280
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
install_requires.append('python2-pythondialog>=3.2.2rc1')
|
||||||
|
else:
|
||||||
|
install_requires.append('pythondialog>=3.2.2rc1')
|
||||||
|
|
||||||
# env markers in extras_require cause problems with older pip: #517
|
# env markers in extras_require cause problems with older pip: #517
|
||||||
# Keep in sync with conditional_requirements.py.
|
# Keep in sync with conditional_requirements.py.
|
||||||
if sys.version_info < (2, 7):
|
if sys.version_info < (2, 7):
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ set -xe
|
|||||||
# Check out special branch until latest docker changes land in Boulder master.
|
# Check out special branch until latest docker changes land in Boulder master.
|
||||||
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
|
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
|
||||||
cd $BOULDERPATH
|
cd $BOULDERPATH
|
||||||
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
|
FAKE_DNS=$(ifconfig docker0 | grep "inet addr:" | cut -d: -f2 | awk '{ print $1}')
|
||||||
|
sed -i "s/FAKE_DNS: .*/FAKE_DNS: $FAKE_DNS/" docker-compose.yml
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
# Check out special branch until latest docker changes land in Boulder master.
|
# Check out special branch until latest docker changes land in Boulder master.
|
||||||
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
|
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
|
||||||
cd $BOULDERPATH
|
cd $BOULDERPATH
|
||||||
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
|
FAKE_DNS=$(ifconfig docker0 | grep "inet addr:" | cut -d: -f2 | awk '{ print $1}')
|
||||||
|
sed -i "s/FAKE_DNS: .*/FAKE_DNS: $FAKE_DNS/" docker-compose.yml
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ envlist = py{26,33,34,35},cover,lint
|
|||||||
# packages installed separately to ensure that downstream deps problems
|
# packages installed separately to ensure that downstream deps problems
|
||||||
# are detected, c.f. #1002
|
# are detected, c.f. #1002
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dev]
|
pip install -e acme[dns,dev]
|
||||||
nosetests -v acme
|
nosetests -v acme
|
||||||
pip install -e .[dev]
|
pip install -e .[dev]
|
||||||
nosetests -v certbot
|
nosetests -v certbot
|
||||||
@@ -35,26 +35,27 @@ deps =
|
|||||||
py{26,27}-oldest: psutil==2.1.0
|
py{26,27}-oldest: psutil==2.1.0
|
||||||
py{26,27}-oldest: PyOpenSSL==0.13
|
py{26,27}-oldest: PyOpenSSL==0.13
|
||||||
py{26,27}-oldest: python2-pythondialog==3.2.2rc1
|
py{26,27}-oldest: python2-pythondialog==3.2.2rc1
|
||||||
|
py{26,27}-oldest: dnspython>=1.12
|
||||||
|
|
||||||
[testenv:py33]
|
[testenv:py33]
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dev]
|
pip install -e acme[dns,dev]
|
||||||
nosetests -v acme
|
nosetests -v acme
|
||||||
|
|
||||||
[testenv:py34]
|
[testenv:py34]
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dev]
|
pip install -e acme[dns,dev]
|
||||||
nosetests -v acme
|
nosetests -v acme
|
||||||
|
|
||||||
[testenv:py35]
|
[testenv:py35]
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dev]
|
pip install -e acme[dns,dev]
|
||||||
nosetests -v acme
|
nosetests -v acme
|
||||||
|
|
||||||
[testenv:cover]
|
[testenv:cover]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
|
pip install -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
|
||||||
./tox.cover.sh
|
./tox.cover.sh
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
@@ -64,7 +65,7 @@ basepython = python2.7
|
|||||||
# duplicate code checking; if one of the commands fails, others will
|
# duplicate code checking; if one of the commands fails, others will
|
||||||
# continue, but tox return code will reflect previous error
|
# continue, but tox return code will reflect previous error
|
||||||
commands =
|
commands =
|
||||||
pip install -q -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
|
pip install -q -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
|
||||||
./pep8.travis.sh
|
./pep8.travis.sh
|
||||||
pylint --reports=n --rcfile=.pylintrc certbot
|
pylint --reports=n --rcfile=.pylintrc certbot
|
||||||
pylint --reports=n --rcfile=acme/.pylintrc acme/acme
|
pylint --reports=n --rcfile=acme/.pylintrc acme/acme
|
||||||
@@ -79,6 +80,10 @@ commands =
|
|||||||
pip install -e acme -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
|
pip install -e acme -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
|
||||||
{toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test --debian-modules
|
{toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test --debian-modules
|
||||||
|
|
||||||
|
[testenv:nginxroundtrip]
|
||||||
|
commands =
|
||||||
|
pip install -e acme[dev] -e .[dev] -e certbot-nginx
|
||||||
|
python certbot-compatibility-test/nginx/roundtrip.py certbot-compatibility-test/nginx/nginx-roundtrip-testdata
|
||||||
|
|
||||||
[testenv:le_auto]
|
[testenv:le_auto]
|
||||||
# At the moment, this tests under Python 2.7 only, as only that version is
|
# At the moment, this tests under Python 2.7 only, as only that version is
|
||||||
@@ -89,3 +94,21 @@ commands =
|
|||||||
whitelist_externals =
|
whitelist_externals =
|
||||||
docker
|
docker
|
||||||
passenv = DOCKER_*
|
passenv = DOCKER_*
|
||||||
|
|
||||||
|
[testenv:apache_compat]
|
||||||
|
commands =
|
||||||
|
docker build -t certbot-compatibility-test -f certbot-compatibility-test/Dockerfile .
|
||||||
|
docker build -t apache-compat -f certbot-compatibility-test/Dockerfile-apache .
|
||||||
|
docker run --rm -it apache-compat -c apache.tar.gz -vvvv
|
||||||
|
whitelist_externals =
|
||||||
|
docker
|
||||||
|
passenv = DOCKER_*
|
||||||
|
|
||||||
|
[testenv:nginx_compat]
|
||||||
|
commands =
|
||||||
|
docker build -t certbot-compatibility-test -f certbot-compatibility-test/Dockerfile .
|
||||||
|
docker build -t nginx-compat -f certbot-compatibility-test/Dockerfile-nginx .
|
||||||
|
docker run --rm -it nginx-compat -c nginx.tar.gz -vvvv
|
||||||
|
whitelist_externals =
|
||||||
|
docker
|
||||||
|
passenv = DOCKER_*
|
||||||
|
|||||||
Reference in New Issue
Block a user