diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..5eee84cce --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto eol=lf + +# special files +*.bat text eol=crlf +*.jpeg binary +*.jpg binary +*.png binary diff --git a/.pylintrc b/.pylintrc index 268d61ec6..92dde98c0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -38,8 +38,9 @@ load-plugins=linter_plugin # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=fixme,locally-disabled,abstract-class-not-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name -# abstract-class-not-used cannot be disabled locally (at least in pylint 1.4.1) +disable=fixme,locally-disabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes +# abstract-class-not-used cannot be disabled locally (at least in +# pylint 1.4.1), same for abstract-class-little-used [REPORTS] diff --git a/.travis.yml b/.travis.yml index ff8a1038a..8586c3840 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ env: - GOPATH=/tmp/go - PATH=$GOPATH/bin:$PATH matrix: + - TOXENV=py26 BOULDER_INTEGRATION=1 - TOXENV=py27 BOULDER_INTEGRATION=1 - TOXENV=lint - TOXENV=cover @@ -38,7 +39,6 @@ addons: mariadb: "10.0" apt: packages: # keep in sync with bootstrap/ubuntu.sh and Boulder - - lsb-release - python - python-dev - python-virtualenv diff --git a/MANIFEST.in b/MANIFEST.in index dfec1c3c9..fa0d16e0d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,4 +6,5 @@ include LICENSE.txt include linter_plugin.py include letsencrypt/DISCLAIMER recursive-include docs * +recursive-include examples * recursive-include letsencrypt/tests/testdata * diff --git a/README.rst b/README.rst index 43ecd413c..d3e89c939 100644 --- a/README.rst +++ b/README.rst @@ -76,23 +76,23 @@ server automatically!:: Current Features ---------------- -* web servers supported: +* Supports multiple web servers: - apache/2.x (tested and working on Ubuntu Linux) - nginx/0.8.48+ (under development) - - standalone (runs its own webserver to prove you control the domain) + - standalone (runs its own simple webserver to prove you control a domain) -* the private key is generated locally on your system -* can talk to the Let's Encrypt (demo) CA or optionally to other ACME - compliant services -* can get domain-validated (DV) certificates -* can revoke certificates -* adjustable RSA key bitlength (2048 (default), 4096, ...) -* optionally can install a http->https redirect, so your site effectively +* The private key is generated locally on your system. +* Can talk to the Let's Encrypt (demo) CA or optionally to other ACME + compliant services. +* Can get domain-validated (DV) certificates. +* Can revoke certificates. +* Adjustable RSA key bit-length (2048 (default), 4096, ...). +* Can optionally install a http -> https redirect, so your site effectively runs https only (Apache only) -* fully automated -* configuration changes are logged and can be reverted using the CLI -* text and ncurses UI +* Fully automated. +* Configuration changes are logged and can be reverted. +* Text and ncurses UI. * Free and Open Source Software, made with Python. @@ -110,7 +110,7 @@ Documentation: https://letsencrypt.readthedocs.org Software project: https://github.com/letsencrypt/letsencrypt -Notes for developers: CONTRIBUTING.md_ +Notes for developers: https://letsencrypt.readthedocs.org/en/latest/contributing.html Main Website: https://letsencrypt.org/ @@ -123,4 +123,3 @@ email to client-dev+subscribe@letsencrypt.org) .. _Freenode: https://freenode.net .. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev -.. _CONTRIBUTING.md: https://github.com/letsencrypt/letsencrypt/blob/master/CONTRIBUTING.md diff --git a/Vagrantfile b/Vagrantfile index d54e4ea23..a2759440c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,14 +4,11 @@ # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" -# Setup instructions from docs/using.rst +# Setup instructions from docs/contributing.rst $ubuntu_setup_script = <%d\xbd%\xe1T\xdd\xaa0\x18\xde' @@ -659,14 +634,12 @@ class DNSTest(unittest.TestCase): class DNSResponseTest(unittest.TestCase): def setUp(self): - self.key = jose.JWKRSA(key=KEY) - from acme.challenges import DNS self.chall = DNS(token=jose.b64decode( b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA")) self.validation = jose.JWS.sign( payload=self.chall.json_dumps(sort_keys=True).encode(), - key=self.key, alg=jose.RS256) + key=KEY, alg=jose.RS256) from acme.challenges import DNSResponse self.msg = DNSResponse(validation=self.validation) @@ -694,7 +667,7 @@ class DNSResponseTest(unittest.TestCase): def test_check_validation(self): self.assertTrue( - self.msg.check_validation(self.chall, self.key.public_key())) + self.msg.check_validation(self.chall, KEY.public_key())) if __name__ == '__main__': diff --git a/acme/acme/jose/jwk.py b/acme/acme/jose/jwk.py index 74fa72319..4d07229b3 100644 --- a/acme/acme/jose/jwk.py +++ b/acme/acme/jose/jwk.py @@ -35,7 +35,7 @@ class JWK(json_util.TypedJSONObjectWithFields): _thumbprint_json_dumps_params = { # "no whitespace or line breaks before or after any syntactic # elements" - 'indent': 0, + 'indent': None, 'separators': (',', ':'), # "members ordered lexicographically by the Unicode [UNICODE] # code points of the member names" @@ -47,6 +47,8 @@ class JWK(json_util.TypedJSONObjectWithFields): https://tools.ietf.org/html/rfc7638 + :returns bytes: + """ digest = hashes.Hash(hash_function(), backend=default_backend()) digest.update(json.dumps( diff --git a/acme/acme/jose/jwk_test.py b/acme/acme/jose/jwk_test.py index d8a7410e8..eea5793bf 100644 --- a/acme/acme/jose/jwk_test.py +++ b/acme/acme/jose/jwk_test.py @@ -1,4 +1,5 @@ """Tests for acme.jose.jwk.""" +import binascii import unittest from acme import test_util @@ -40,8 +41,9 @@ class JWKTestBaseMixin(object): class JWKOctTest(unittest.TestCase, JWKTestBaseMixin): """Tests for acme.jose.jwk.JWKOct.""" - thumbprint = (b"=,\xdd;I\x1a+i\x02x\x8a\x12?06IM\xc2\x80" - b"\xe4\xc3\x1a\xfc\x89\xf3)'\xce\xccm\xfd5") + thumbprint = (b"\xf3\xe7\xbe\xa8`\xd2\xdap\xe9}\x9c\xce>" + b"\xd0\xfcI\xbe\xcd\x92'\xd4o\x0e\xf41\xea" + b"\x8e(\x8a\xb2i\x1c") def setUp(self): from acme.jose.jwk import JWKOct @@ -71,8 +73,8 @@ class JWKRSATest(unittest.TestCase, JWKTestBaseMixin): """Tests for acme.jose.jwk.JWKRSA.""" # pylint: disable=too-many-instance-attributes - thumbprint = (b'\x08\xfa1\x87\x1d\x9b6H/*\x1eW\xc2\xe3\xf6P' - b'\xefs\x0cKB\x87\xcf\x85yO\x045\x0e\x91\x80\x0b') + thumbprint = (b'\x83K\xdc#3\x98\xca\x98\xed\xcb\x80\x80<\x0c' + b'\xf0\x95\xb9H\xb2*l\xbd$\xe5&|O\x91\xd4 \xb0Y') def setUp(self): from acme.jose.jwk import JWKRSA @@ -168,6 +170,22 @@ class JWKRSATest(unittest.TestCase, JWKTestBaseMixin): self.assertRaises(errors.DeserializationError, JWK.from_json, {'kty': 'RSA', 'e': 'AQAB', 'n': '1'}) + def test_thumbprint_go_jose(self): + # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk.go#L155 + # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L331-L344 + # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L384 + from acme.jose.jwk import JWKRSA + key = JWKRSA.json_loads("""{ + "kty": "RSA", + "kid": "bilbo.baggins@hobbiton.example", + "use": "sig", + "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw", + "e": "AQAB" +}""") + self.assertEqual( + binascii.hexlify(key.thumbprint()), + b"f63838e96077ad1fc01c3f8405774dedc0641f558ebb4b40dccf5f9b6d66a932") + if __name__ == '__main__': unittest.main() # pragma: no cover diff --git a/acme/acme/jose/jws.py b/acme/acme/jose/jws.py index 61a3b5aea..1a073e17d 100644 --- a/acme/acme/jose/jws.py +++ b/acme/acme/jose/jws.py @@ -104,7 +104,7 @@ class Header(json_util.JSONObjectWithFields): .. todo:: Supports only "jwk" header parameter lookup. :returns: (Public) key found in the header. - :rtype: :class:`acme.jose.jwk.JWK` + :rtype: .JWK :raises acme.jose.errors.Error: if key could not be found @@ -194,8 +194,7 @@ class Signature(json_util.JSONObjectWithFields): def verify(self, payload, key=None): """Verify. - :param key: Key used for verification. - :type key: :class:`acme.jose.jwk.JWK` + :param JWK key: Key used for verification. """ key = self.combined.find_key() if key is None else key @@ -208,8 +207,7 @@ class Signature(json_util.JSONObjectWithFields): protect=frozenset(), **kwargs): """Sign. - :param key: Key for signature. - :type key: :class:`acme.jose.jwk.JWK` + :param JWK key: Key for signature. """ assert isinstance(key, alg.kty) diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py index d2d859bc5..6c1c4f596 100644 --- a/acme/acme/messages_test.py +++ b/acme/acme/messages_test.py @@ -278,7 +278,7 @@ class AuthorizationTest(unittest.TestCase): self.challbs = ( ChallengeBody( uri='http://challb1', status=STATUS_VALID, - chall=challenges.SimpleHTTP(token=b'IlirfxKKXAsHtmzK29Pj8A')), + chall=challenges.HTTP01(token=b'IlirfxKKXAsHtmzK29Pj8A')), ChallengeBody(uri='http://challb2', status=STATUS_VALID, chall=challenges.DNS( token=b'DGyRejmCefe7v4NfDGDKfA')), diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 49759bc07..1466671e3 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -4,7 +4,6 @@ import collections import functools import logging import os -import socket import sys import six @@ -50,62 +49,35 @@ class ACMEServerMixin: # pylint: disable=old-style-class server_version = "ACME client standalone challenge solver" allow_reuse_address = True - def __init__(self): - self._stopped = False - - def serve_forever2(self): - """Serve forever, until other thread calls `shutdown2`.""" - logger.debug("Starting server at %s:%d...", - *self.socket.getsockname()[:2]) - while not self._stopped: - self.handle_request() - - def shutdown2(self): - """Shutdown server loop from `serve_forever2`.""" - self._stopped = True - - # dummy request to terminate last server_forever2.handle_request() - sock = socket.socket() - try: - sock.connect(self.socket.getsockname()) - except socket.error: - pass # thread is probably already finished - finally: - sock.close() - - self.server_close() - class DVSNIServer(TLSServer, ACMEServerMixin): """DVSNI Server.""" def __init__(self, server_address, certs): - ACMEServerMixin.__init__(self) TLSServer.__init__( self, server_address, socketserver.BaseRequestHandler, certs=certs) -class SimpleHTTPServer(BaseHTTPServer.HTTPServer, ACMEServerMixin): - """SimpleHTTP Server.""" +class HTTP01Server(BaseHTTPServer.HTTPServer, ACMEServerMixin): + """HTTP01 Server.""" def __init__(self, server_address, resources): - ACMEServerMixin.__init__(self) BaseHTTPServer.HTTPServer.__init__( - self, server_address, SimpleHTTPRequestHandler.partial_init( + self, server_address, HTTP01RequestHandler.partial_init( simple_http_resources=resources)) -class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - """SimpleHTTP challenge handler. +class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """HTTP01 challenge handler. Adheres to the stdlib's `socketserver.BaseRequestHandler` interface. - :ivar set simple_http_resources: A set of `SimpleHTTPResource` + :ivar set simple_http_resources: A set of `HTTP01Resource` objects. TODO: better name? """ - SimpleHTTPResource = collections.namedtuple( - "SimpleHTTPResource", "chall response validation") + HTTP01Resource = collections.namedtuple( + "HTTP01Resource", "chall response validation") def __init__(self, *args, **kwargs): self.simple_http_resources = kwargs.pop("simple_http_resources", set()) @@ -114,7 +86,7 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): # pylint: disable=invalid-name,missing-docstring if self.path == "/": self.handle_index() - elif self.path.startswith("/" + challenges.SimpleHTTP.URI_ROOT_PATH): + elif self.path.startswith("/" + challenges.HTTP01.URI_ROOT_PATH): self.handle_simple_http_resource() else: self.handle_404() @@ -134,15 +106,15 @@ class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.wfile.write(b"404") def handle_simple_http_resource(self): - """Handle SimpleHTTP provisioned resources.""" + """Handle HTTP01 provisioned resources.""" for resource in self.simple_http_resources: if resource.chall.path == self.path: - logger.debug("Serving SimpleHTTP with token %r", + logger.debug("Serving HTTP01 with token %r", resource.chall.encode("token")) self.send_response(http_client.OK) - self.send_header("Content-type", resource.response.CONTENT_TYPE) + self.send_header("Content-type", resource.chall.CONTENT_TYPE) self.end_headers() - self.wfile.write(resource.validation.json_dumps().encode()) + self.wfile.write(resource.validation.encode()) return else: # pylint: disable=useless-else-on-loop logger.debug("No resources to serve") diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index 349581a3d..85ef6ab14 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -1,7 +1,6 @@ """Tests for acme.standalone.""" import os import shutil -import socket import threading import tempfile import time @@ -29,54 +28,6 @@ class TLSServerTest(unittest.TestCase): server.server_close() # pylint: disable=no-member -class ACMEServerMixinTest(unittest.TestCase): - """Tests for acme.standalone.ACMEServerMixin.""" - - def setUp(self): - from acme.standalone import ACMEServerMixin - - class _MockHandler(socketserver.BaseRequestHandler): - # pylint: disable=missing-docstring,no-member,no-init - - def handle(self): - self.request.sendall(b"DONE") - - class _MockServer(socketserver.TCPServer, ACMEServerMixin): - def __init__(self, *args, **kwargs): - socketserver.TCPServer.__init__(self, *args, **kwargs) - ACMEServerMixin.__init__(self) - - self.server = _MockServer(("", 0), _MockHandler) - - def _busy_wait(self): # pragma: no cover - # This function is used to avoid race conditions in tests, but - # not all of the functionality is always used, hence "no - # cover" - while True: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - # pylint: disable=no-member - sock.connect(self.server.socket.getsockname()) - except socket.error: - pass - else: - sock.recv(4) # wait until handle_request is actually called - break - finally: - sock.close() - time.sleep(1) - - def test_serve_shutdown(self): - thread = threading.Thread(target=self.server.serve_forever2) - thread.start() - self._busy_wait() - self.server.shutdown2() - - def test_shutdown2_not_running(self): - self.server.shutdown2() - self.server.shutdown2() - - class DVSNIServerTest(unittest.TestCase): """Test for acme.standalone.DVSNIServer.""" @@ -89,42 +40,38 @@ class DVSNIServerTest(unittest.TestCase): from acme.standalone import DVSNIServer self.server = DVSNIServer(("", 0), certs=self.certs) # pylint: disable=no-member - self.thread = threading.Thread(target=self.server.handle_request) + self.thread = threading.Thread(target=self.server.serve_forever) self.thread.start() def tearDown(self): - self.server.shutdown2() + self.server.shutdown() # pylint: disable=no-member self.thread.join() - def test_init(self): - # pylint: disable=protected-access - self.assertFalse(self.server._stopped) - - def test_dvsni(self): + def test_it(self): host, port = self.server.socket.getsockname()[:2] - cert = crypto_util.probe_sni(b'localhost', host=host, port=port) + cert = crypto_util.probe_sni(b'localhost', host=host, port=port, timeout=1) self.assertEqual(jose.ComparableX509(cert), jose.ComparableX509(self.certs[b'localhost'][1])) -class SimpleHTTPServerTest(unittest.TestCase): - """Tests for acme.standalone.SimpleHTTPServer.""" +class HTTP01ServerTest(unittest.TestCase): + """Tests for acme.standalone.HTTP01Server.""" def setUp(self): self.account_key = jose.JWK.load( test_util.load_vector('rsa1024_key.pem')) self.resources = set() - from acme.standalone import SimpleHTTPServer - self.server = SimpleHTTPServer(('', 0), resources=self.resources) + from acme.standalone import HTTP01Server + self.server = HTTP01Server(('', 0), resources=self.resources) # pylint: disable=no-member self.port = self.server.socket.getsockname()[1] - self.thread = threading.Thread(target=self.server.handle_request) + self.thread = threading.Thread(target=self.server.serve_forever) self.thread.start() def tearDown(self): - self.server.shutdown2() + self.server.shutdown() # pylint: disable=no-member self.thread.join() def test_index(self): @@ -139,25 +86,24 @@ class SimpleHTTPServerTest(unittest.TestCase): 'http://localhost:{0}/foo'.format(self.port), verify=False) self.assertEqual(response.status_code, http_client.NOT_FOUND) - def _test_simple_http(self, add): - chall = challenges.SimpleHTTP(token=(b'x' * 16)) - response = challenges.SimpleHTTPResponse(tls=False) + def _test_http01(self, add): + chall = challenges.HTTP01(token=(b'x' * 16)) + response, validation = chall.response_and_validation(self.account_key) - from acme.standalone import SimpleHTTPRequestHandler - resource = SimpleHTTPRequestHandler.SimpleHTTPResource( - chall=chall, response=response, validation=response.gen_validation( - chall, self.account_key)) + from acme.standalone import HTTP01RequestHandler + resource = HTTP01RequestHandler.HTTP01Resource( + chall=chall, response=response, validation=validation) if add: self.resources.add(resource) return resource.response.simple_verify( resource.chall, 'localhost', self.account_key.public_key(), port=self.port) - def test_simple_http_found(self): - self.assertTrue(self._test_simple_http(add=True)) + def test_http01_found(self): + self.assertTrue(self._test_http01(add=True)) - def test_simple_http_not_found(self): - self.assertFalse(self._test_simple_http(add=False)) + def test_http01_not_found(self): + self.assertFalse(self._test_http01(add=False)) class TestSimpleDVSNIServer(unittest.TestCase): diff --git a/acme/acme/testdata/README b/acme/acme/testdata/README index 11bca55e5..dfe3f5405 100644 --- a/acme/acme/testdata/README +++ b/acme/acme/testdata/README @@ -4,12 +4,12 @@ to use appropriate extension for vector filenames: .pem for PEM and The following command has been used to generate test keys: - for x in 256 512 1024; do openssl genrsa -out rsa${k}_key.pem $k; done + for x in 256 512 1024 2048; do openssl genrsa -out rsa${k}_key.pem $k; done and for the CSR: - openssl req -key rsa512_key.pem -new -subj '/CN=example.com' -outform DER > csr.der + openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der and for the certificate: - openssl req -key rsa512_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der + openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der diff --git a/acme/acme/testdata/cert.der b/acme/acme/testdata/cert.der index 5f1018505..ab231982f 100644 Binary files a/acme/acme/testdata/cert.der and b/acme/acme/testdata/cert.der differ diff --git a/acme/acme/testdata/csr.der b/acme/acme/testdata/csr.der index adc29ff18..d43ac85a1 100644 Binary files a/acme/acme/testdata/csr.der and b/acme/acme/testdata/csr.der differ diff --git a/acme/acme/testdata/rsa2048_key.pem b/acme/acme/testdata/rsa2048_key.pem new file mode 100644 index 000000000..33efd3467 --- /dev/null +++ b/acme/acme/testdata/rsa2048_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA8HwZMHeImB/iM8/n8CTCR4KeYQB2gLGO3v8xLms+PWH3Zbxc +dVtEn25Y34scIh+iOuEXBcSBalBddLHKBGVN3nCfmpupoLm52xgRG44q9OWODpg4 +FSi4afqVw2agMx0RHi0v3GVcdpqB83UW42kK1ESZHUuq7mxLg8u3IMYZFm6Amsf+ +YQjBbDNn8NczJOFhsExP2EdM5ykgM1Om8aqTqqPMgPub68/r4Sym+BjLnvRq5Qtz +h/jCfOBIIpAwg3lj7l8OyE3kkD3ALtuiuminNUqLHEkUaLq/Xiv8V8mvnrhG7h3Q ++L1Xc707P0dz5YM5XxTMhmUE1cae/lQ0KbNrpwIDAQABAoIBAAiDXCDrGlrIRimv +YnaN1pLRfOnSKl/D6VrbjdIm2b0yip9/W4aMBJHgRiUjt4s9s3CCJ1585lftIGHR +KWWecHM/aWb/u7GE4Z9v6qsfDUY+GhlKKjIVjvGxfTu9lk446TI4R0l2DR/luFP2 +ASlrvoZlJ0ZyN0rZapLv0zvFx32Tukd+3rcMmXfHl7aRGMZG1YTKNmBJ4d9iJ6cP +HG3fgSzLQMPLNO/20MzbXdREG5FNQtwaMuFnIcVbtMCvc/71lQQEfANMLCUweEed +YWGOjgDeh+731nJsopel+2TSTgnf5VhcFrgChZZdqeKvP+HbXjTE2VkWo7BrzoM7 +xICYBwECgYEA/ZF/JOjZfwIMUdikv8vldJzRMdFryl4NJWnh4NeThNOEpUcpxnyK +wyMnnQaGJa51u9EEnzl0sZ2h2ODjD6KFpz6fkWaVRq5SWalVPAoKZGaoPZV3IUOI +8Tm0xkXho+A/FUUEcxCLME+3V9EdPfHaVRJOrbfDyxvNhsj4w9F0aAkCgYEA8sp7 +XTrolOknJGv4Qt1w6gcm5+cMtLaRfi8ZHPHujl2x9eWE8/s2818az7jc0Xr/G4HQ +NeU+3Es4BblEckSHmhUZhx26cZgkLSIIDofEtaEc6u8CyWfxsWvn3l4T3kMdeSLC +9UoLk59AH2tkMIh8vzV8LSisLJa341lMdgryQi8CgYAlJKr7PSCe+i3Tz2hSsAts +iYwbQBIKErzaPihYRzvUuSc1DreP26535y5mUg5UdrnISVXj/Qaa/fw3SLn6EFSD +qyi0o9I6CE8H00YpBU+AZYk/fCV3Oe1VaJ6SbKog1zhmZTXBpSq+aO7ybi9aY5MX +4xajW8fSeMAifk3yYTwsAQKBgErcEcOCOVpItU/uloKPYpRWFjHktK83p46fmP+q +vOJak1d9KExOBfhuN4caucNBSE1D7l3fzE0CSEjDgg41gRYKMW/Ow8DopybfWlqY +lBdokNEDVvmgug35dmnC2h9q1DiYdkJJTV57+Lp3U1H/k28lX59Q7h1lb1eDHic7 +YszzAoGBAOx05dhOiYbzAJSTQu3oBHFn4mTYIqCcDO6cQrEJwPKAq7mAhT0yOk9N +CrqRV/1aes665829cyTwcAZl6nqbzHv5XjX5+g6vmooCb4oCkq49rumHjoQdrX8D +RR5b+Spkc1jo4rctCcExzSkgo+K5N3oBVYznecje7O7Z0/qiJE/8 +-----END RSA PRIVATE KEY----- diff --git a/acme/docs/.gitignore b/acme/docs/.gitignore new file mode 100644 index 000000000..ba65b13af --- /dev/null +++ b/acme/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/acme/docs/Makefile b/acme/docs/Makefile new file mode 100644 index 000000000..79de9c0a3 --- /dev/null +++ b/acme/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/acme-python.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/acme-python.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/acme-python" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/acme-python" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/acme/docs/_static/.gitignore b/acme/docs/_static/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/acme/docs/_templates/.gitignore b/acme/docs/_templates/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/acme/docs/api.rst b/acme/docs/api.rst new file mode 100644 index 000000000..c874d8470 --- /dev/null +++ b/acme/docs/api.rst @@ -0,0 +1,8 @@ +================= +API Documentation +================= + +.. toctree:: + :glob: + + api/* diff --git a/acme/docs/api/challenges.rst b/acme/docs/api/challenges.rst new file mode 100644 index 000000000..7bc084fe9 --- /dev/null +++ b/acme/docs/api/challenges.rst @@ -0,0 +1,5 @@ +Challenges +---------- + +.. automodule:: acme.challenges + :members: diff --git a/acme/docs/api/client.rst b/acme/docs/api/client.rst new file mode 100644 index 000000000..d0ab89eb2 --- /dev/null +++ b/acme/docs/api/client.rst @@ -0,0 +1,5 @@ +Client +------ + +.. automodule:: acme.client + :members: diff --git a/acme/docs/api/errors.rst b/acme/docs/api/errors.rst new file mode 100644 index 000000000..644ff0e71 --- /dev/null +++ b/acme/docs/api/errors.rst @@ -0,0 +1,5 @@ +Errors +------ + +.. automodule:: acme.errors + :members: diff --git a/acme/docs/api/fields.rst b/acme/docs/api/fields.rst new file mode 100644 index 000000000..c224f7fef --- /dev/null +++ b/acme/docs/api/fields.rst @@ -0,0 +1,5 @@ +Fields +------ + +.. automodule:: acme.fields + :members: diff --git a/acme/docs/api/jose.rst b/acme/docs/api/jose.rst new file mode 100644 index 000000000..0f7dd627c --- /dev/null +++ b/acme/docs/api/jose.rst @@ -0,0 +1,10 @@ +JOSE +---- + +.. automodule:: acme.jose + :members: + +.. toctree:: + :glob: + + jose/* diff --git a/acme/docs/api/jose/base64.rst b/acme/docs/api/jose/base64.rst new file mode 100644 index 000000000..a40f8b11b --- /dev/null +++ b/acme/docs/api/jose/base64.rst @@ -0,0 +1,5 @@ +JOSE Base64 +----------- + +.. automodule:: acme.jose.b64 + :members: diff --git a/acme/docs/api/jose/errors.rst b/acme/docs/api/jose/errors.rst new file mode 100644 index 000000000..60d9b5c78 --- /dev/null +++ b/acme/docs/api/jose/errors.rst @@ -0,0 +1,5 @@ +Errors +------ + +.. automodule:: acme.jose.errors + :members: diff --git a/acme/docs/api/jose/interfaces.rst b/acme/docs/api/jose/interfaces.rst new file mode 100644 index 000000000..7b72b364f --- /dev/null +++ b/acme/docs/api/jose/interfaces.rst @@ -0,0 +1,5 @@ +Interfaces +---------- + +.. automodule:: acme.jose.interfaces + :members: diff --git a/acme/docs/api/jose/json_util.rst b/acme/docs/api/jose/json_util.rst new file mode 100644 index 000000000..f06edead2 --- /dev/null +++ b/acme/docs/api/jose/json_util.rst @@ -0,0 +1,5 @@ +JSON utilities +-------------- + +.. automodule:: acme.jose.json_util + :members: diff --git a/acme/docs/api/jose/jwa.rst b/acme/docs/api/jose/jwa.rst new file mode 100644 index 000000000..c858a5d1d --- /dev/null +++ b/acme/docs/api/jose/jwa.rst @@ -0,0 +1,5 @@ +JSON Web Algorithms +------------------- + +.. automodule:: acme.jose.jwa + :members: diff --git a/acme/docs/api/jose/jwk.rst b/acme/docs/api/jose/jwk.rst new file mode 100644 index 000000000..8e6bbe13f --- /dev/null +++ b/acme/docs/api/jose/jwk.rst @@ -0,0 +1,5 @@ +JSON Web Key +------------ + +.. automodule:: acme.jose.jwk + :members: diff --git a/acme/docs/api/jose/jws.rst b/acme/docs/api/jose/jws.rst new file mode 100644 index 000000000..15335bf8f --- /dev/null +++ b/acme/docs/api/jose/jws.rst @@ -0,0 +1,5 @@ +JSON Web Signature +------------------ + +.. automodule:: acme.jose.jws + :members: diff --git a/acme/docs/api/jose/util.rst b/acme/docs/api/jose/util.rst new file mode 100644 index 000000000..41cbbcc31 --- /dev/null +++ b/acme/docs/api/jose/util.rst @@ -0,0 +1,5 @@ +Utilities +--------- + +.. automodule:: acme.jose.util + :members: diff --git a/acme/docs/api/messages.rst b/acme/docs/api/messages.rst new file mode 100644 index 000000000..0374a72c8 --- /dev/null +++ b/acme/docs/api/messages.rst @@ -0,0 +1,5 @@ +Messages +-------- + +.. automodule:: acme.messages + :members: diff --git a/acme/docs/api/other.rst b/acme/docs/api/other.rst new file mode 100644 index 000000000..eb27a5d53 --- /dev/null +++ b/acme/docs/api/other.rst @@ -0,0 +1,5 @@ +Other ACME objects +------------------ + +.. automodule:: acme.other + :members: diff --git a/acme/docs/api/standalone.rst b/acme/docs/api/standalone.rst new file mode 100644 index 000000000..a65e5df2a --- /dev/null +++ b/acme/docs/api/standalone.rst @@ -0,0 +1,5 @@ +Standalone +---------- + +.. automodule:: acme.standalone + :members: diff --git a/acme/docs/conf.py b/acme/docs/conf.py new file mode 100644 index 000000000..1448aaea3 --- /dev/null +++ b/acme/docs/conf.py @@ -0,0 +1,312 @@ +# -*- coding: utf-8 -*- +# +# acme-python documentation build configuration file, created by +# sphinx-quickstart on Sun Oct 18 13:38:06 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + + +here = os.path.abspath(os.path.dirname(__file__)) + + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'sphinxcontrib.programoutput', +] + +autodoc_member_order = 'bysource' +autodoc_default_flags = ['show-inheritance', 'private-members'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'acme-python' +copyright = u'2015-2015, Let\'s Encrypt Project' +author = u'Let\'s Encrypt Project' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0' +# The full version, including alpha/beta/rc tags. +release = '0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'py:obj' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'acme-pythondoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'acme-python.tex', u'acme-python Documentation', + u'Let\'s Encrypt Project', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'acme-python', u'acme-python Documentation', + [author], 1), + ('man/jws', 'jws', u'jws script documentation', [project], 1), +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'acme-python', u'acme-python Documentation', + author, 'acme-python', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), +} diff --git a/acme/docs/index.rst b/acme/docs/index.rst new file mode 100644 index 000000000..a200808da --- /dev/null +++ b/acme/docs/index.rst @@ -0,0 +1,32 @@ +.. acme-python documentation master file, created by + sphinx-quickstart on Sun Oct 18 13:38:06 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to acme-python's documentation! +======================================= + +Contents: + +.. toctree:: + :maxdepth: 2 + + api + +.. automodule:: acme + :members: + + +Example client: + +.. include:: ../examples/example_client.py + :code: python + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/acme/docs/make.bat b/acme/docs/make.bat new file mode 100644 index 000000000..781185977 --- /dev/null +++ b/acme/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\acme-python.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\acme-python.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/man/jws.rst b/acme/docs/man/jws.rst similarity index 100% rename from docs/man/jws.rst rename to acme/docs/man/jws.rst diff --git a/examples/acme_client.py b/acme/examples/example_client.py similarity index 91% rename from examples/acme_client.py rename to acme/examples/example_client.py index eacfb8ab8..b4b5ad010 100644 --- a/examples/acme_client.py +++ b/acme/examples/example_client.py @@ -15,16 +15,16 @@ from acme import jose logging.basicConfig(level=logging.DEBUG) -NEW_REG_URL = 'https://www.letsencrypt-demo.org/acme/new-reg' +DIRECTORY_URL = 'https://acme-staging.api.letsencrypt.org/directory' BITS = 2048 # minimum for Boulder DOMAIN = 'example1.com' # example.com is ignored by Boulder # generate_private_key requires cryptography>=0.5 key = jose.JWKRSA(key=rsa.generate_private_key( public_exponent=65537, - key_size=2048, + key_size=BITS, backend=default_backend())) -acme = client.Client(NEW_REG_URL, key) +acme = client.Client(DIRECTORY_URL, key) regr = acme.register() logging.info('Auto-accepting TOS: %s', regr.terms_of_service) diff --git a/acme/examples/standalone/README b/acme/examples/standalone/README index 89bc5d74e..8bb0fad97 100644 --- a/acme/examples/standalone/README +++ b/acme/examples/standalone/README @@ -1,2 +1,2 @@ python -m acme.standalone -p 1234 -curl -k https://localhost:1234 \ No newline at end of file +curl -k https://localhost:1234 diff --git a/acme/readthedocs.org.requirements.txt b/acme/readthedocs.org.requirements.txt new file mode 100644 index 000000000..65e6c7cf3 --- /dev/null +++ b/acme/readthedocs.org.requirements.txt @@ -0,0 +1,10 @@ +# 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[docs] diff --git a/acme/setup.py b/acme/setup.py index 6448b7fe9..a6551a023 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -32,6 +32,12 @@ if sys.version_info < (2, 7): else: install_requires.append('mock') +docs_extras = [ + 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags + 'sphinx_rtd_theme', + 'sphinxcontrib-programoutput', +] + testing_extras = [ 'nose', 'tox', @@ -41,7 +47,7 @@ testing_extras = [ setup( name='acme', version=version, - description='ACME protocol implementation', + description='ACME protocol implementation in Python', url='https://github.com/letsencrypt/letsencrypt', author="Let's Encrypt Project", author_email='client-dev@letsencrypt.org', @@ -52,7 +58,11 @@ setup( 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], @@ -61,6 +71,7 @@ setup( include_package_data=True, install_requires=install_requires, extras_require={ + 'docs': docs_extras, 'testing': testing_extras, }, entry_points={ diff --git a/bootstrap/_arch_common.sh b/bootstrap/_arch_common.sh new file mode 100755 index 000000000..f66067ffb --- /dev/null +++ b/bootstrap/_arch_common.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# Tested with: +# - ArchLinux (x86_64) +# +# "python-virtualenv" is Python3, but "python2-virtualenv" provides +# only "virtualenv2" binary, not "virtualenv" necessary in +# ./bootstrap/dev/_common_venv.sh + +deps=" + git + python2 + python-virtualenv + gcc + dialog + augeas + openssl + libffi + ca-certificates + pkg-config +" + +missing=$(pacman -T $deps) + +if [ "$missing" ]; then + pacman -S --needed $missing +fi diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 687f30d09..71144ce40 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -1,57 +1,50 @@ #!/bin/sh -# Tested with: -# - Ubuntu: -# - 12.04 (x64, Travis) -# - 14.04 (x64, Vagrant) -# - 14.10 (x64) -# - Debian: -# - 6.0.10 "squeeze" (x64) -# - 7.8 "wheezy" (x64) -# - 8.0 "jessie" (x64) -# - Raspbian: -# - 7.8 (armhf) +# Current version tested with: +# +# - Ubuntu +# - 14.04 (x64) +# - 15.04 (x64) +# - Debian +# - 7.9 "wheezy" (x64) +# - sid (2015-10-21) (x64) +# Past versions tested with: +# +# - Debian 8.0 "jessie" (x64) +# - Raspbian 7.8 (armhf) -# virtualenv binary can be found in different packages depending on -# distro version (#346) -newer () { - apt-get install -y lsb-release --no-install-recommends - distro=$(lsb_release -si) - # 6.0.10 => 60, 14.04 => 1404 - # TODO: in sid version==unstable - version=$(lsb_release -sr | awk -F '.' '{print $1 $2}') - if [ "$distro" = "Ubuntu" -a "$version" -ge 1410 ] - then - return 0; - elif [ "$distro" = "Debian" -a "$version" -ge 80 ] - then - return 0; - else - return 1; - fi -} +# Believed not to work: +# +# - Debian 6.0.10 "squeeze" (x64) apt-get update -# you can force newer if lsb_release is not available (e.g. Docker -# debian:jessie base image) -if [ "$1" = "newer" ] || newer -then +# virtualenv binary can be found in different packages depending on +# distro version (#346) + +virtualenv= +if apt-cache show virtualenv > /dev/null ; then virtualenv="virtualenv" -else - virtualenv="python-virtualenv" fi +if apt-cache show python-virtualenv > /dev/null ; then + virtualenv="$virtualenv python-virtualenv" +fi apt-get install -y --no-install-recommends \ - git-core \ + git \ python \ python-dev \ - "$virtualenv" \ + $virtualenv \ gcc \ dialog \ libaugeas0 \ libssl-dev \ libffi-dev \ ca-certificates \ + +if ! which virtualenv > /dev/null ; then + echo Failed to install a working \"virtualenv\" command, exiting + exit 1 +fi diff --git a/bootstrap/_gentoo_common.sh b/bootstrap/_gentoo_common.sh new file mode 100755 index 000000000..a718db7ff --- /dev/null +++ b/bootstrap/_gentoo_common.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +PACKAGES="dev-vcs/git + dev-lang/python:2.7 + dev-python/virtualenv + dev-util/dialog + app-admin/augeas + dev-libs/openssl + dev-libs/libffi + app-misc/ca-certificates + virtual/pkgconfig" + +case "$PACKAGE_MANAGER" in + (paludis) + cave resolve --keep-targets if-possible $PACKAGES -x + ;; + (pkgcore) + pmerge --noreplace $PACKAGES + ;; + (portage|*) + emerge --noreplace $PACKAGES + ;; +esac diff --git a/bootstrap/_rpm_common.sh b/bootstrap/_rpm_common.sh index 3fd0f59f9..26b91b8c4 100755 --- a/bootstrap/_rpm_common.sh +++ b/bootstrap/_rpm_common.sh @@ -21,7 +21,6 @@ $tool install -y \ python \ python-devel \ python-virtualenv \ - python-devel \ gcc \ dialog \ augeas-libs \ diff --git a/bootstrap/archlinux.sh b/bootstrap/archlinux.sh deleted file mode 100755 index 884262f0b..000000000 --- a/bootstrap/archlinux.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -# "python-virtualenv" is Python3, but "python2-virtualenv" provides -# only "virtualenv2" binary, not "virtualenv" necessary in -# ./bootstrap/dev/_common_venv.sh -pacman -S --needed \ - git \ - python2 \ - python-virtualenv \ - gcc \ - dialog \ - augeas \ - openssl \ - libffi \ - ca-certificates \ diff --git a/bootstrap/archlinux.sh b/bootstrap/archlinux.sh new file mode 120000 index 000000000..c5c9479f7 --- /dev/null +++ b/bootstrap/archlinux.sh @@ -0,0 +1 @@ +_arch_common.sh \ No newline at end of file diff --git a/bootstrap/dev/_venv_common.sh b/bootstrap/dev/_venv_common.sh index 2d84dc39b..d07f38ed8 100755 --- a/bootstrap/dev/_venv_common.sh +++ b/bootstrap/dev/_venv_common.sh @@ -21,5 +21,6 @@ pip install -U setuptools pip install -U pip pip install "$@" +set +x echo "Please run the following command to activate developer environment:" echo "source $VENV_NAME/bin/activate" diff --git a/bootstrap/gentoo.sh b/bootstrap/gentoo.sh new file mode 120000 index 000000000..125d6a592 --- /dev/null +++ b/bootstrap/gentoo.sh @@ -0,0 +1 @@ +_gentoo_common.sh \ No newline at end of file diff --git a/bootstrap/install-deps.sh b/bootstrap/install-deps.sh new file mode 100755 index 000000000..3cb0fc274 --- /dev/null +++ b/bootstrap/install-deps.sh @@ -0,0 +1,43 @@ +#!/bin/sh -e +# +# Install OS dependencies. In the glorious future, letsencrypt-auto will +# source this... + +if test "`id -u`" -ne "0" ; then + SUDO=sudo +else + SUDO= +fi + +BOOTSTRAP=`dirname $0` +if [ ! -f $BOOTSTRAP/debian.sh ] ; then + echo "Cannot find the letsencrypt bootstrap scripts in $BOOTSTRAP" + exit 1 +fi +if [ -f /etc/debian_version ] ; then + echo "Bootstrapping dependencies for Debian-based OSes..." + $SUDO $BOOTSTRAP/_deb_common.sh +elif [ -f /etc/arch-release ] ; then + echo "Bootstrapping dependencies for Archlinux..." + $SUDO $BOOTSTRAP/archlinux.sh +elif [ -f /etc/redhat-release ] ; then + echo "Bootstrapping dependencies for RedHat-based OSes..." + $SUDO $BOOTSTRAP/_rpm_common.sh +elif [ -f /etc/gentoo-release ] ; then + echo "Bootstrapping dependencies for Gentoo-based OSes..." + $SUDO $BOOTSTRAP/_gentoo_common.sh +elif uname | grep -iq FreeBSD ; then + echo "Bootstrapping dependencies for FreeBSD..." + $SUDO $BOOTSTRAP/freebsd.sh +elif uname | grep -iq Darwin ; then + echo "Bootstrapping dependencies for Mac OS X..." + echo "WARNING: Mac support is very experimental at present..." + $BOOTSTRAP/mac.sh +else + echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!" + echo + echo "You will need to bootstrap, configure virtualenv, and run a pip install manually" + echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites" + echo "for more info" + exit 1 +fi diff --git a/bootstrap/manjaro.sh b/bootstrap/manjaro.sh new file mode 120000 index 000000000..c5c9479f7 --- /dev/null +++ b/bootstrap/manjaro.sh @@ -0,0 +1 @@ +_arch_common.sh \ No newline at end of file diff --git a/bootstrap/venv.sh b/bootstrap/venv.sh index ce31e6703..6ca082446 100755 --- a/bootstrap/venv.sh +++ b/bootstrap/venv.sh @@ -25,9 +25,9 @@ pip install -U letsencrypt letsencrypt-apache # letsencrypt-nginx echo echo "Congratulations, Let's Encrypt has been successfully installed/updated!" echo -echo -n "Your prompt should now be prepended with ($VENV_NAME). Next " -echo -n "time, if the prompt is different, 'source' this script again " -echo -n "before running 'letsencrypt'." +printf "%s" "Your prompt should now be prepended with ($VENV_NAME). Next " +printf "time, if the prompt is different, 'source' this script again " +printf "before running 'letsencrypt'." echo echo echo "You can now run 'letsencrypt --help'." diff --git a/docs/.gitignore b/docs/.gitignore index 69fa449dd..ba65b13af 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1 @@ -_build/ +/_build/ diff --git a/docs/api/plugins/standalone.rst b/docs/api/plugins/standalone.rst index 99e5ea2f8..f5b9d9c24 100644 --- a/docs/api/plugins/standalone.rst +++ b/docs/api/plugins/standalone.rst @@ -3,9 +3,3 @@ .. automodule:: letsencrypt.plugins.standalone :members: - -:mod:`letsencrypt.plugins.standalone.authenticator` -=================================================== - -.. automodule:: letsencrypt.plugins.standalone.authenticator - :members: diff --git a/docs/ciphers.rst b/docs/ciphers.rst new file mode 100644 index 000000000..12c403d09 --- /dev/null +++ b/docs/ciphers.rst @@ -0,0 +1,211 @@ +============ +Ciphersuites +============ + +.. contents:: Table of Contents + :local: + + +.. _ciphersuites: + +Introduction +============ + +Autoupdates +----------- + +Within certain limits, TLS server software can choose what kind of +cryptography to use when a client connects. These choices can affect +security, compatibility, and performance in complex ways. Most of +these options are independent of a particular certificate. The Let's +Encrypt client tries to provide defaults that we think are most useful +to our users. + +As described below, the Let's Encrypt client will default to modifying +server software's cryptographic settings to keep these up-to-date with +what we think are appropriate defaults when new versions of the Let's +Encrypt client are installed (for example, by an operating system package +manager). + +When this feature is implemented, this document will be updated +to describe how to disable these automatic changes. + + +Cryptographic choices +--------------------- + +Software that uses cryptography must inevitably make choices about what +kind of cryptography to use and how. These choices entail assumptions +about how well particular cryptographic mechanisms resist attack, and what +trade-offs are available and appropriate. The choices are constrained +by compatibility issues (in order to interoperate with other software, +an implementation must agree to use cryptographic mechanisms that the +other side also supports) and protocol issues (cryptographic mechanisms +must be specified in protocols and there must be a way to agree to use +them in a particular context). + +The best choices for a particular application may change over time in +response to new research, new standardization events, changes in computer +hardware, and changes in the prevalence of legacy software. Much important +research on cryptanalysis and cryptographic vulnerabilities is unpublished +because many researchers have been working in the interest of improving +some entities' communications security while weakening, or failing to +improve, others' security. But important information that improves our +understanding of the state of the art is published regularly. + +When enabling TLS support in a compatible web server (which is a separate +step from obtaining a certificate), Let's Encrypt has the ability to +update that web server's TLS configuration. Again, this is *different +from the cryptographic particulars of the certificate itself*; the +certificate as of the initial release will be RSA-signed using one of +Let's Encrypt's 2048-bit RSA keys, and will describe the subscriber's +RSA public key ("subject public key") of at least 2048 bits, which is +used for key establishment. + +Note that the subscriber's RSA public key can be used in a wide variety +of key establishment methods, most of which do not use RSA directly +for key exchange, but only for authenticating the server! For example, +in DHE and ECDHE key exchanges, the subject public key is just used to +sign other parameters for authentication. You do not have to "use RSA" +for other purposes just because you're using an RSA key for authentication. + +The certificate doesn't specify other cryptographic or ciphersuite +particulars; for example, it doesn't say whether or not parties should +use a particular symmetric algorithm like 3DES, or what cipher modes +they should use. All of these details are negotiated between client +and server independent of the content of the ciphersuite. The +Let's Encrypt project hopes to provide useful defaults that reflect +good security choices with respect to the publicly-known state of the +art. However, the Let's Encrypt certificate authority does *not* +dictate end-users' security policy, and any site is welcome to change +its preferences in accordance with its own policy or its administrators' +preferences, and use different cryptographic mechanisms or parameters, +or a different priority order, than the defaults provided by the Let's +Encrypt client. + +If you don't use the Let's Encrypt client to configure your server +directly, because the client doesn't integrate with your server software +or because you chose not to use this integration, then the cryptographic +defaults haven't been modified, and the cryptography chosen by the server +will still be whatever the default for your software was. For example, +if you obtain a certificate using *standalone* mode and then manually +install it in an IMAP or LDAP server, your cryptographic settings will +not be modified by the client in any way. + + +Sources of defaults +------------------- + +Initially, the Let's Encrypt client will configure users' servers to +use the cryptographic defaults recommended by the Mozilla project. +These settings are well-reasoned recommendations that carefully +consider client software compatibility. They are described at + +https://wiki.mozilla.org/Security/Server_Side_TLS + +and the version implemented by the Let's Encrypt client will be the +version that was most current as of the release date of each client +version. Mozilla offers three seperate sets of cryptographic options, +which trade off security and compatibility differently. These are +referred to as as the "Modern", "Intermediate", and "Old" configurations +(in order from most secure to least secure, and least-backwards compatible +to most-backwards compatible). The client will follow the Mozilla defaults +for the *Intermediate* configuration by default, at least with regards to +ciphersuites and TLS versions. Mozilla's web site describes which client +software will be compatible with each configuration. You can also use +the Qualys SSL Labs site, which the Let's Encrypt software will suggest +when installing a certificate, to test your server and see whether it +will be compatible with particular software versions. + +It will be possible to ask the Let's Encrypt client to instead apply +(and track) Modern or Old configurations. + +The Let's Encrypt project expects to follow the Mozilla recommendations +in the future as those recommendations are updated. (For example, some +users have proposed prioritizing a new ciphersuite known as ``0xcc13`` +which uses the ChaCha and Poly1305 algorithms, and which is already +implemented by the Chrome browser. Mozilla has delayed recommending +``0xcc13`` over compatibility and standardization concerns, but is likely +to recommend it in the future once these concerns have been addressed. At +that point, the Let's Encrypt client would likely follow the Mozilla +recommendations and favor the use of this ciphersuite as well.) + +The Let's Encrypt project may deviate from the Mozilla recommendations +in the future if good cause is shown and we believe our users' +priorities would be well-served by doing so. In general, please address +relevant proposals for changing priorities to the Mozilla security +team first, before asking the Let's Encrypt project to change the +client's priorities. The Mozilla security team is likely to have more +resources and expertise to bring to bear on evaluating reasons why its +recommendations should be updated. + +The Let's Encrpyt project will entertain proposals to create a *very* +small number of alternative configurations (apart from Modern, +Intermediate, and Old) that there's reason to believe would be widely +used by sysadmins; this would usually be a preferable course to modifying +an existing configuration. For example, if many sysadmins want their +servers configured to track a different expert recommendation, Let's +Encrypt could add an option to do so. + + +Resources for recommendations +----------------------------- + +In the course of considering how to handle this issue, we received +recommendations with sources of expert guidance on ciphersuites and other +cryptographic parameters. We're grateful to everyone who contributed +suggestions. The recommendations we received are available at + +https://github.com/letsencrypt/letsencrypt/wiki/Ciphersuite-guidance + +Let's Encrypt client users are welcome to review these authorities to +better inform their own cryptographic parameter choices. We also +welcome suggestions of other resources to add to this list. Please keep +in mind that different recommendations may reflect different priorities +or evaluations of trade-offs, especially related to compatibility! + + +Changing your settings +---------------------- + +This will probably look something like + +..code-block: shell + + letsencrypt --cipher-recommendations mozilla-secure + letsencrypt --cipher-recommendations mozilla-intermediate + letsencrypt --cipher-recommendations mozilla-old + +to track Mozilla's *Secure*, *Intermediate*, or *Old* recommendations, +and + +..code-block: shell + + letsencrypt --update-ciphers on + +to enable updating ciphers with each new Let's Encrypt client release, +or + +..code-block: shell + + letsencrypt --update-ciphers off + +to disable automatic configuration updates. These features have not yet +been implemented and this syntax may change then they are implemented. + + +TODO +---- + +The status of this feature is tracked as part of issue #1123 in our +bug tracker. + +https://github.com/letsencrypt/letsencrypt/issues/1123 + +Prior to implementation of #1123, the client does not actually modify +ciphersuites (this is intended to be implemented as a "configuration +enhancement", but the only configuration enhancement implemented +so far is redirecting HTTP requests to HTTPS in web servers, the +"redirect" enhancement). The changes here would probably be either a new +"ciphersuite" enhancement in each plugin that provides an installer, +or a family of enhancements, one per selectable ciphersuite configuration. diff --git a/docs/conf.py b/docs/conf.py index e2b360a6e..62a7cea07 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,13 +17,6 @@ import os import re import sys -import mock - - -# http://docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules -# c.f. #262 -sys.modules.update( - (mod_name, mock.MagicMock()) for mod_name in ['augeas']) here = os.path.abspath(os.path.dirname(__file__)) @@ -36,13 +29,11 @@ with codecs.open(init_fn, encoding='utf8') as fd: # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) -for pkg in 'acme', 'letsencrypt-apache', 'letsencrypt-nginx': - sys.path.insert(0, os.path.abspath(os.path.join(here, '..', pkg))) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -74,7 +65,7 @@ master_doc = 'index' # General information about the project. project = u'Let\'s Encrypt' -copyright = u'2014, Let\'s Encrypt Project' +copyright = u'2014-2015, Let\'s Encrypt Project' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -126,6 +117,9 @@ pygments_style = 'sphinx' # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + # -- Options for HTML output ---------------------------------------------- @@ -289,7 +283,6 @@ man_pages = [ [project], 1), ('man/letsencrypt-renewer', 'letsencrypt-renewer', u'letsencrypt-renewer script documentation', [project], 1), - ('man/jws', 'jws', u'jws script documentation', [project], 1), ] # If true, show URL addresses after external links. @@ -320,7 +313,7 @@ texinfo_documents = [ #texinfo_no_detailmenu = False -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} - -todo_include_todos = True +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), +} diff --git a/docs/contributing.rst b/docs/contributing.rst index 6b6550c71..a3baa7bc5 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -1,46 +1,64 @@ -============ -Contributing -============ +=============== +Developer Guide +=============== + +.. contents:: Table of Contents + :local: + .. _hacking: Hacking ======= -All changes in your pull request **must** have 100% unit test coverage, pass -our `integration`_ tests, **and** be compliant with the -:ref:`coding style `. +Running a local copy of the client +---------------------------------- - -Bootstrap ---------- - -Start by :ref:`installing Let's Encrypt prerequisites -`. Then run: +Running the client in developer mode from your local tree is a little +different than running ``letsencrypt-auto``. To get set up, do these things +once: .. code-block:: shell + git clone https://github.com/letsencrypt/letsencrypt + cd letsencrypt + ./bootstrap/install-deps.sh ./bootstrap/dev/venv.sh -Activate the virtualenv: +Then in each shell where you're working on the client, do: .. code-block:: shell - source ./$VENV_NAME/bin/activate + source ./venv/bin/activate -This step should prepend you prompt with ``($VENV_NAME)`` and save you -from typing ``./$VENV_NAME/bin/...``. It is also required to run some -of the `testing`_ tools. Virtualenv can be disabled at any time by -typing ``deactivate``. More information can be found in `virtualenv -documentation`_. +After that, your shell will be using the virtual environment, and you run the +client by typing: -Note that packages are installed in so called *editable mode*, in -which any source code changes in the current working directory are -"live" and no further ``./bootstrap/dev/venv.sh`` or ``pip install -...`` invocations are necessary while developing. +.. code-block:: shell -.. _`virtualenv documentation`: https://virtualenv.pypa.io + letsencrypt +Activating a shell in this way makes it easier to run unit tests +with ``tox`` and integration tests, as described below. To reverse this, you +can type ``deactivate``. More information can be found in the `virtualenv docs`_. + +.. _`virtualenv docs`: https://virtualenv.pypa.io + +Find issues to work on +---------------------- + +You can find the open issues in the `github issue tracker`_. Comparatively +easy ones are marked `Good Volunteer Task`_. If you're starting work on +something, post a comment to let others know and seek feedback on your plan +where appropriate. + +Once you've got a working branch, you can open a pull request. All changes in +your pull request must have thorough unit test coverage, pass our +`integration`_ tests, and be compliant with the :ref:`coding style +`. + +.. _github issue tracker: https://github.com/letsencrypt/letsencrypt/issues +.. _Good Volunteer Task: https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+Volunteer+Task%22 Testing ------- @@ -64,8 +82,14 @@ The following tools are there to help you: but you won't get TAB completion... -Integration -~~~~~~~~~~~ +.. _integration: + +Integration testing with the boulder CA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Generally it is sufficient to open a pull request and let Github and Travis run +integration tests for you. + Mac OS X users: Run `./tests/mac-bootstrap.sh` instead of `boulder-start.sh` to install dependencies, configure the environment, and start boulder. @@ -84,68 +108,13 @@ run (in a separate terminal):: If you would like to test `letsencrypt_nginx` plugin (highly encouraged) make sure to install prerequisites as listed in -``letsencrypt-nginx/tests/boulder-integration.sh``: - -.. include:: ../letsencrypt-nginx/tests/boulder-integration.sh - :start-line: 1 - :end-line: 2 - :code: shell - -and rerun the integration tests suite. +``letsencrypt-nginx/tests/boulder-integration.sh`` and rerun +the integration tests suite. .. _Boulder: https://github.com/letsencrypt/boulder .. _Go: https://golang.org -Vagrant -------- - -If you are a Vagrant user, Let's Encrypt comes with a Vagrantfile that -automates setting up a development environment in an Ubuntu 14.04 -LTS VM. To set it up, simply run ``vagrant up``. The repository is -synced to ``/vagrant``, so you can get started with: - -.. code-block:: shell - - vagrant ssh - cd /vagrant - ./venv/bin/pip install -r requirements.txt .[dev,docs,testing] - sudo ./venv/bin/letsencrypt - -Support for other Linux distributions coming soon. - -.. note:: - Unfortunately, Python distutils and, by extension, setup.py and - tox, use hard linking quite extensively. Hard linking is not - supported by the default sync filesystem in Vagrant. As a result, - all actions with these commands are *significantly slower* in - Vagrant. One potential fix is to `use NFS`_ (`related issue`_). - -.. _use NFS: http://docs.vagrantup.com/v2/synced-folders/nfs.html -.. _related issue: https://github.com/ClusterHQ/flocker/issues/516 - - -Docker ------- - -OSX users will probably find it easiest to set up a Docker container for -development. Let's Encrypt comes with a Dockerfile (``Dockerfile-dev``) -for doing so. To use Docker on OSX, install and setup docker-machine using the -instructions at https://docs.docker.com/installation/mac/. - -To build the development Docker image:: - - docker build -t letsencrypt -f Dockerfile-dev . - -Now run tests inside the Docker image: - -.. code-block:: shell - - docker run -it letsencrypt bash - cd src - tox -e py27 - - Code components and layout ========================== @@ -177,12 +146,13 @@ which implement bindings to alternative UI libraries. Authenticators -------------- -Authenticators are plugins designed to solve challenges received from +Authenticators are plugins designed to prove that this client deserves a +certificate for some domain name by solving challenges received from the ACME server. From the protocol, there are essentially two different types of challenges. Challenges that must be solved by individual plugins in order to satisfy domain validation (subclasses of `~.DVChallenge`, i.e. `~.challenges.DVSNI`, -`~.challenges.SimpleHTTPS`, `~.challenges.DNS`) and continuity specific +`~.challenges.HTTP01`, `~.challenges.DNS`) and continuity specific challenges (subclasses of `~.ContinuityChallenge`, i.e. `~.challenges.RecoveryToken`, `~.challenges.RecoveryContact`, `~.challenges.ProofOfPossession`). Continuity challenges are @@ -201,17 +171,25 @@ in a separate branch). Installer --------- -Installers classes exist to actually setup the certificate and be able -to enhance the configuration. (Turn on HSTS, redirect to HTTPS, -etc). You can indicate your abilities through the -:meth:`~.IInstaller.supported_enhancements` call. We currently only -have one Installer written (still developing), `~.ApacheConfigurator`. +Installers plugins exist to actually setup the certificate in a server, +possibly tweak the security configuration to make it more correct and secure +(Fix some mixed content problems, turn on HSTS, redirect to HTTPS, etc). +Installer plugins tell the main client about their abilities to do the latter +via the :meth:`~.IInstaller.supported_enhancements` call. We currently +have two Installers in the tree, the `~.ApacheConfigurator`. and the +`~.NginxConfigurator`. External projects have made some progress toward +support for IIS, Icecast and Plesk. -Installers and Authenticators will oftentimes be the same -class/object. Installers and Authenticators are kept separate because +Installers and Authenticators will oftentimes be the same class/object +(because for instance both tasks can be performed by a webserver like nginx) +though this is not always the case (the standalone plugin is an authenticator +that listens on port 443, but it cannot install certs; a postfix plugin would +be an installer but not an authenticator). + +Installers and Authenticators are kept separate because it should be possible to use the `~.StandaloneAuthenticator` (it sets up its own Python server to perform challenges) with a program that -cannot solve challenges itself. (Imagine MTA installers). +cannot solve challenges itself (Such as MTA installers). Installer Development @@ -233,6 +211,27 @@ We currently offer a pythondialog and "text" mode for displays. Display plugins implement the `~letsencrypt.interfaces.IDisplay` interface. +.. _dev-plugin: + +Writing your own plugin +======================= + +Let's Encrypt client supports dynamic discovery of plugins through the +`setuptools entry points`_. This way you can, for example, create a +custom implementation of `~letsencrypt.interfaces.IAuthenticator` or +the `~letsencrypt.interfaces.IInstaller` without having to merge it +with the core upstream source code. An example is provided in +``examples/plugins/`` directory. + +.. warning:: Please be aware though that as this client is still in a + developer-preview stage, the API may undergo a few changes. If you + believe the plugin will be beneficial to the community, please + consider submitting a pull request to the repo and we will update + it with any necessary API changes. + +.. _`setuptools entry points`: + https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins + .. _coding-style: @@ -297,10 +296,62 @@ commands: This should generate documentation in the ``docs/_build/html`` directory. + +Other methods for running the client +==================================== + +Vagrant +------- + +If you are a Vagrant user, Let's Encrypt comes with a Vagrantfile that +automates setting up a development environment in an Ubuntu 14.04 +LTS VM. To set it up, simply run ``vagrant up``. The repository is +synced to ``/vagrant``, so you can get started with: + +.. code-block:: shell + + vagrant ssh + cd /vagrant + sudo ./venv/bin/letsencrypt + +Support for other Linux distributions coming soon. + +.. note:: + Unfortunately, Python distutils and, by extension, setup.py and + tox, use hard linking quite extensively. Hard linking is not + supported by the default sync filesystem in Vagrant. As a result, + all actions with these commands are *significantly slower* in + Vagrant. One potential fix is to `use NFS`_ (`related issue`_). + +.. _use NFS: http://docs.vagrantup.com/v2/synced-folders/nfs.html +.. _related issue: https://github.com/ClusterHQ/flocker/issues/516 + + +Docker +------ + +OSX users will probably find it easiest to set up a Docker container for +development. Let's Encrypt comes with a Dockerfile (``Dockerfile-dev``) +for doing so. To use Docker on OSX, install and setup docker-machine using the +instructions at https://docs.docker.com/installation/mac/. + +To build the development Docker image:: + + docker build -t letsencrypt -f Dockerfile-dev . + +Now run tests inside the Docker image: + +.. code-block:: shell + + docker run -it letsencrypt bash + cd src + tox -e py27 + + .. _prerequisites: -Notes on OS depedencies -======================= +Notes on OS dependencies +======================== OS level dependencies are managed by scripts in ``bootstrap``. Some notes are provided here mainly for the :ref:`developers ` reference. diff --git a/docs/index.rst b/docs/index.rst index b076b45c6..68289d760 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,13 +7,12 @@ Welcome to the Let's Encrypt client documentation! intro using contributing - plugins + packaging .. toctree:: :maxdepth: 1 api - pkgs Indices and tables diff --git a/docs/make.bat b/docs/make.bat index e83ba8cb5..198e864c3 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,263 +1,263 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\LetsEncrypt.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\LetsEncrypt.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\LetsEncrypt.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\LetsEncrypt.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/packaging.rst b/docs/packaging.rst new file mode 100644 index 000000000..5f09b65fa --- /dev/null +++ b/docs/packaging.rst @@ -0,0 +1,6 @@ +=============== +Packaging Guide +=============== + +Documentation can be found at +https://github.com/letsencrypt/letsencrypt/wiki/Packaging. diff --git a/docs/pkgs.rst b/docs/pkgs.rst deleted file mode 100644 index 2e1b18dfb..000000000 --- a/docs/pkgs.rst +++ /dev/null @@ -1,14 +0,0 @@ -======== -Packages -======== - -.. note:: It is planned to distribute `acme` and plugins separately as - described in `#358`_. For the time being those packages are bundled - together into a single repo, and single documentation. - -.. _`#358`: https://github.com/letsencrypt/letsencrypt/issues/358 - -.. toctree:: - :glob: - - pkgs/** diff --git a/docs/pkgs/acme/index.rst b/docs/pkgs/acme/index.rst deleted file mode 100644 index 23c5a3284..000000000 --- a/docs/pkgs/acme/index.rst +++ /dev/null @@ -1,56 +0,0 @@ -:mod:`acme` -=========== - -.. contents:: - -.. automodule:: acme - :members: - - -Client ------- - -.. automodule:: acme.client - :members: - - -Messages --------- - -.. automodule:: acme.messages - :members: - - -Challenges ----------- - -.. automodule:: acme.challenges - :members: - - -Other ACME objects ------------------- - -.. automodule:: acme.other - :members: - - -Fields ------- - -.. automodule:: acme.fields - :members: - - -Errors ------- - -.. automodule:: acme.errors - :members: - - -Standalone ----------- - -.. automodule:: acme.standalone - :members: diff --git a/docs/pkgs/acme/jose.rst b/docs/pkgs/acme/jose.rst deleted file mode 100644 index fa3a0e9bb..000000000 --- a/docs/pkgs/acme/jose.rst +++ /dev/null @@ -1,67 +0,0 @@ -:mod:`acme.jose` -================ - -.. contents:: - -.. automodule:: acme.jose - :members: - - -JSON Web Algorithms -------------------- - -.. automodule:: acme.jose.jwa - :members: - - -JSON Web Key ------------- - -.. automodule:: acme.jose.jwk - :members: - - -JSON Web Signature ------------------- - -.. automodule:: acme.jose.jws - :members: - - -Implementation details ----------------------- - - -Interfaces -~~~~~~~~~~ - -.. automodule:: acme.jose.interfaces - :members: - - -Errors -~~~~~~ - -.. automodule:: acme.jose.errors - :members: - - -JSON utilities -~~~~~~~~~~~~~~ - -.. automodule:: acme.jose.json_util - :members: - - -JOSE Base64 -~~~~~~~~~~~ - -.. automodule:: acme.jose.b64 - :members: - - -Utilities -~~~~~~~~~ - -.. automodule:: acme.jose.util - :members: diff --git a/docs/pkgs/letsencrypt_apache.rst b/docs/pkgs/letsencrypt_apache.rst deleted file mode 100644 index c4e7e8e6e..000000000 --- a/docs/pkgs/letsencrypt_apache.rst +++ /dev/null @@ -1,42 +0,0 @@ -:mod:`letsencrypt_apache` -------------------------- - -.. automodule:: letsencrypt_apache - :members: - -:mod:`letsencrypt_apache.configurator` -====================================== - -.. automodule:: letsencrypt_apache.configurator - :members: - -:mod:`letsencrypt_apache.display_ops` -===================================== - -.. automodule:: letsencrypt_apache.display_ops - :members: - -:mod:`letsencrypt_apache.dvsni` -=============================== - -.. automodule:: letsencrypt_apache.dvsni - :members: - -:mod:`letsencrypt_apache.obj` -============================= - -.. automodule:: letsencrypt_apache.obj - :members: - -:mod:`letsencrypt_apache.parser` -================================ - -.. automodule:: letsencrypt_apache.parser - :members: - - -:mod:`letsencrypt_apache.augeas_configurator` -============================================= - -.. automodule:: letsencrypt_apache.augeas_configurator - :members: diff --git a/docs/pkgs/letsencrypt_nginx.rst b/docs/pkgs/letsencrypt_nginx.rst deleted file mode 100644 index 03114b685..000000000 --- a/docs/pkgs/letsencrypt_nginx.rst +++ /dev/null @@ -1,35 +0,0 @@ -:mod:`letsencrypt_nginx` ------------------------- - -.. automodule:: letsencrypt_nginx - :members: - -:mod:`letsencrypt_nginx.configurator` -===================================== - -.. automodule:: letsencrypt_nginx.configurator - :members: - -:mod:`letsencrypt_nginx.dvsni` -============================== - -.. automodule:: letsencrypt_nginx.dvsni - :members: - -:mod:`letsencrypt_nginx.obj` -============================ - -.. automodule:: letsencrypt_nginx.obj - :members: - -:mod:`letsencrypt_nginx.parser` -=============================== - -.. automodule:: letsencrypt_nginx.parser - :members: - -:mod:`letsencrypt_nginx.nginxparser` -==================================== - -.. automodule:: letsencrypt_nginx.nginxparser - :members: diff --git a/docs/plugins.rst b/docs/plugins.rst deleted file mode 100644 index 33d63e84f..000000000 --- a/docs/plugins.rst +++ /dev/null @@ -1,19 +0,0 @@ -======= -Plugins -======= - -Let's Encrypt client supports dynamic discovery of plugins through the -`setuptools entry points`_. This way you can, for example, create a -custom implementation of `~letsencrypt.interfaces.IAuthenticator` or -the `~letsencrypt.interfaces.IInstaller` without having to merge it -with the core upstream source code. An example is provided in -``examples/plugins/`` directory. - -.. warning:: Please be aware though that as this client is still in a - developer-preview stage, the API may undergo a few changes. If you - believe the plugin will be beneficial to the community, please - consider submitting a pull request to the repo and we will update - it with any necessary API changes. - -.. _`setuptools entry points`: - https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins diff --git a/docs/using.rst b/docs/using.rst index 879ea49c4..eb8c31595 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -1,60 +1,254 @@ -============================== -Using the Let's Encrypt client -============================== +========== +User Guide +========== +.. contents:: Table of Contents + :local: -Getting the code -================ +.. _installation: -Please `install Git`_ and run the following commands: +Installation +============ + +.. _letsencrypt-auto: + +letsencrypt-auto +---------------- + +``letsencrypt-auto`` is a wrapper which installs some dependencies +from your OS standard package repositories (e.g using `apt-get` or +`yum`), and for other dependencies it sets up a virtualized Python +environment with packages downloaded from PyPI [#venv]_. It also +provides automated updates. + +Firstly, please `install Git`_ and run the following commands: .. code-block:: shell git clone https://github.com/letsencrypt/letsencrypt cd letsencrypt -Alternatively you could `download the ZIP archive`_ and extract the -snapshot of our repository, but it's strongly recommended to use the -above method instead. +.. warning:: Alternatively you could `download the ZIP archive`_ and + extract the snapshot of our repository, but it's strongly + recommended to use the above method instead. .. _`install Git`: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git .. _`download the ZIP archive`: https://github.com/letsencrypt/letsencrypt/archive/master.zip - -Installation and Usage -====================== - To install and run the client you just need to type: .. code-block:: shell ./letsencrypt-auto -(Once letsencrypt is packaged by distributions, the command will just be -``letsencrypt``. ``letsencrypt-auto`` is a wrapper which installs virtualized -dependencies and provides automated updates during the beta program) - -.. warning:: Please do **not** use ``python setup.py install`` or ``sudo pip install`. - Those mode of operation might corrupt your operating system and is - **not supported** by the Let's Encrypt team! - -The ``letsencrypt`` commandline tool has a builtin help: +Throughout the documentation, whenever you see references to +``letsencrypt`` script/binary, you can substitute in +``letsencrypt-auto``. For example, to get the help you would type: .. code-block:: shell - ./letsencrypt-auto --help + ./letsencrypt-auto --help +Running with Docker +------------------- + +Docker_ is an amazingly simple and quick way to obtain a +certificate. However, this mode of operation is unable to install +certificates or configure your webserver, because our installer +plugins cannot reach it from inside the Docker container. + +You should definitely read the :ref:`where-certs` section, in order to +know how to manage the certs +manually. https://github.com/letsencrypt/letsencrypt/wiki/Ciphersuite-guidance +provides some information about recommended ciphersuites. If none of +these make much sense to you, you should definitely use the +letsencrypt-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 cert for resolves +to, `install Docker`_, then issue the following command: + +.. code-block:: shell + + sudo docker run -it --rm -p 443:443 -p 80:80 --name letsencrypt \ + -v "/etc/letsencrypt:/etc/letsencrypt" \ + -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ + quay.io/letsencrypt/letsencrypt:latest auth + +and follow the instructions (note that ``auth`` command is explicitly +used - no installer plugins involved). Your new cert will be available +in ``/etc/letsencrypt/live`` on the host. + +.. _Docker: https://docker.com +.. _`install Docker`: https://docs.docker.com/userguide/ + + +Distro packages +--------------- + +Unfortunately, this is an ongoing effort. If you'd like to package +Let's Encrypt client for your distribution of choice please have a +look at the :doc:`packaging`. + + +From source +----------- + +Installation from source is only supported for developers and the +whole process is described in the :doc:`contributing`. + +.. warning:: Please do **not** use ``python setup.py install`` or + ``python pip install .``. Please do **not** attempt the + installation commands as superuser/root and/or without virtual + environment, 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 + Let's Encrypt team! + + +Comparison of different methods +------------------------------- + +Unless you have a very specific requirements, we kindly ask you to use +the letsencrypt-auto_ method. It's the fastest, the most thoroughly +tested and the most reliable way of getting our software and the free +SSL certificates! + + +Plugins +======= + +Officially supported plugins: + +========== = = ================================================================ +Plugin A I Notes and status +========== = = ================================================================ +standalone Y N Very stable. Uses port 80 (force by + ``--standalone-supported-challenges http-01``) or 443 + (force by ``--standalone-supported-challenges dvsni``). +apache Y Y Alpha. Automates Apache installation, works fairly well but on + Debian-based distributions only for now. +webroot Y N Works with already running webserver, by writing necessary files + to the disk (``--webroot-path`` should be pointed to your + ``public_html``). Currently, when multiple domains are specified + (`-d`), they must all use the same web root path. +manual Y N Hidden from standard UI, use with ``-a manual``. Requires to + copy and paste commands into a new terminal session. Allows to + run client on machine different than target webserver, e.g. your + laptop. +nginx Y Y Very experimental. Not included in letsencrypt-auto_. +========== = = ================================================================ + +Third party plugins are listed at +https://github.com/letsencrypt/letsencrypt/wiki/Plugins. If +that's not enough, you can always :ref:`write your own plugin +`. + + +Renewal +======= + +.. note:: Let's Encrypt CA issues short lived certificates (90 + days). Make sure you renew the certificates at least once in 3 + months. + +In order to renew certificates simply call the ``letsencrypt`` (or +letsencrypt-auto_) again, and use the same values when prompted. You +can automate it slightly by passing necessary flags on the CLI (see +`--help all`), or even further using the :ref:`config-file`. If you're +sure that UI doesn't prompt for any details you can add the command to +``crontab`` (make it less than every 90 days to avoid problems, say +every month). + +Please note that the CA will send notification emails to the address +you provide if you do not renew certificates that are about to expire. + +Let's Encrypt is working hard on automating the renewal process. Until +the tool is ready, we are sorry for the inconvenience! + + +.. _where-certs: + +Where are my certificates? +========================== + +First of all, we encourage you to use Apache or nginx installers, both +which perform the certificate managemant automatically. If, however, +you prefer to manage everything by hand, this section provides +information on where to find necessary files. + +All generated keys and issued certificates can be found in +``/etc/letsencrypt/live/$domain``. Rather than copying, please point +your (web) server configuration directly to those files (or create +symlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated +with the latest necessary files. + +.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys`` + contain all previous keys and certificates, while + ``/etc/letsencrypt/live`` symlinks to the latest versions. + +The following files are available: + +``privkey.pem`` + Private key for the certificate. + + .. warning:: This **must be kept secret at all times**! Never share + it with anyone, including Let's Encrypt developers. You cannot + put it into safe, however - your server still needs to access + this file in order for SSL/TLS to work. + + This is what Apache needs for `SSLCertificateKeyFile + `_, + and nginx for `ssl_certificate_key + `_. + +``cert.pem`` + Server certificate only. + + This is what Apache needs for `SSLCertificateFile + `_. + +``chain.pem`` + All certificates that need to be served by the browser **excluding** + server certificate, i.e. root and intermediate certificates only. + + This is what Apache needs for `SSLCertificateChainFile + `_. + +``fullchain.pem`` + All certificates, **including** server certificate. This is + concatenation of ``chain.pem`` and ``cert.pem``. + + This is what nginx needs for `ssl_certificate + `_. + + +For both chain files, all certificates are ordered from root (primary +certificate) towards leaf. + +Please note, that **you must use** either ``chain.pem`` or +``fullchain.pem``. In case of webservers, using only ``cert.pem``, +will cause nasty errors served through the browsers! + +.. note:: All files are PEM-encoded (as the filename suffix + suggests). If you need other format, such as DER or PFX, then you + could convert using ``openssl``, but this means you will not + benefit from automatic renewal_! + + +.. _config-file: + Configuration file ------------------- +================== It is possible to specify configuration file with -``letsencrypt-auto --config cli.ini`` (or shorter ``-c cli.ini``). For -instance, if you are a contributor, you might find the following -handy: +``letsencrypt-auto --config cli.ini`` (or shorter ``-c cli.ini``). An +example configuration file is shown below: -.. include:: ../examples/dev-cli.ini +.. include:: ../examples/cli.ini :code: ini By default, the following locations are searched: @@ -67,22 +261,30 @@ By default, the following locations are searched: .. keep it up to date with constants.py -Running with Docker -=================== +Getting help +============ -Docker_ is another way to quickly obtain testing certs. From the -server that the domain your requesting a cert for resolves to, -`install Docker`_, issue the following command: +If you're having problems you can chat with us on `IRC (#letsencrypt @ +Freenode) `_ or +get support on our `forums `_. -.. code-block:: shell +If you find a bug in the software, please do report it in our `issue +tracker +`_. Remember to +give us us as much information as possible: - sudo docker run -it --rm -p 443:443 --name letsencrypt \ - -v "/etc/letsencrypt:/etc/letsencrypt" \ - -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ - quay.io/letsencrypt/letsencrypt:latest auth +- copy and paste exact command line used and the output (though mind + that the latter might include some personally identifiable + information, including your email and domains) +- copy and paste logs from ``/var/log/letsencrypt`` (though mind they + also might contain personally identifiable information) +- copy and paste ``letsencrypt --version`` output +- your operating system, including specific version +- specify which installation_ method you've chosen -and follow the instructions. Your new cert will be available in -``/etc/letsencrypt/certs``. -.. _Docker: https://docker.com -.. _`install Docker`: https://docs.docker.com/userguide/ +.. rubric:: Footnotes + +.. [#venv] By using this virtualized Python environment (`virtualenv + `_) we don't pollute the main + OS space with packages from PyPI! diff --git a/examples/cli.ini b/examples/cli.ini new file mode 100644 index 000000000..34fb8ab02 --- /dev/null +++ b/examples/cli.ini @@ -0,0 +1,24 @@ +# This is an example of the kind of things you can do in a configuration file. +# All flags used by the client can be configured here. Run Let's Encrypt with +# "--help" to learn more about the available options. + +# Use a 4096 bit RSA key instead of 2048 +rsa-key-size = 4096 + +# Always use the staging/testing server +server = https://acme-staging.api.letsencrypt.org/directory + +# Uncomment and update to register with the specified e-mail address +# email = foo@example.com + +# Uncomment to use a text interface instead of ncurses +# text = True + +# Uncomment to use the standalone authenticator on port 443 +# authenticator = standalone +# standalone-supported-challenges = dvsni + +# Uncomment to use the webroot authenticator. Replace webroot-path with the +# path to the public_html / webroot folder being served by your web server. +# authenticator = webroot +# webroot-path = /usr/share/nginx/html diff --git a/letsencrypt-apache/MANIFEST.in b/letsencrypt-apache/MANIFEST.in index 0cedae323..15d5af8cd 100644 --- a/letsencrypt-apache/MANIFEST.in +++ b/letsencrypt-apache/MANIFEST.in @@ -1,5 +1,6 @@ include LICENSE.txt include README.rst +recursive-include docs * recursive-include letsencrypt_apache/tests/testdata * include letsencrypt_apache/options-ssl-apache.conf include letsencrypt_apache/httpd.aug diff --git a/letsencrypt-apache/docs/.gitignore b/letsencrypt-apache/docs/.gitignore new file mode 100644 index 000000000..ba65b13af --- /dev/null +++ b/letsencrypt-apache/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/letsencrypt-apache/docs/Makefile b/letsencrypt-apache/docs/Makefile new file mode 100644 index 000000000..9bf5154fe --- /dev/null +++ b/letsencrypt-apache/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/letsencrypt-apache.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/letsencrypt-apache.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/letsencrypt-apache" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/letsencrypt-apache" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/letsencrypt-apache/docs/_static/.gitignore b/letsencrypt-apache/docs/_static/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letsencrypt-apache/docs/_templates/.gitignore b/letsencrypt-apache/docs/_templates/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letsencrypt-apache/docs/api.rst b/letsencrypt-apache/docs/api.rst new file mode 100644 index 000000000..8668ec5d8 --- /dev/null +++ b/letsencrypt-apache/docs/api.rst @@ -0,0 +1,8 @@ +================= +API Documentation +================= + +.. toctree:: + :glob: + + api/** diff --git a/letsencrypt-apache/docs/api/augeas_configurator.rst b/letsencrypt-apache/docs/api/augeas_configurator.rst new file mode 100644 index 000000000..3b1821e3d --- /dev/null +++ b/letsencrypt-apache/docs/api/augeas_configurator.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_apache.augeas_configurator` +--------------------------------------------- + +.. automodule:: letsencrypt_apache.augeas_configurator + :members: diff --git a/letsencrypt-apache/docs/api/configurator.rst b/letsencrypt-apache/docs/api/configurator.rst new file mode 100644 index 000000000..2ed613286 --- /dev/null +++ b/letsencrypt-apache/docs/api/configurator.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_apache.configurator` +-------------------------------------- + +.. automodule:: letsencrypt_apache.configurator + :members: diff --git a/letsencrypt-apache/docs/api/display_ops.rst b/letsencrypt-apache/docs/api/display_ops.rst new file mode 100644 index 000000000..59ff9d15e --- /dev/null +++ b/letsencrypt-apache/docs/api/display_ops.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_apache.display_ops` +------------------------------------- + +.. automodule:: letsencrypt_apache.display_ops + :members: diff --git a/letsencrypt-apache/docs/api/dvsni.rst b/letsencrypt-apache/docs/api/dvsni.rst new file mode 100644 index 000000000..945771db8 --- /dev/null +++ b/letsencrypt-apache/docs/api/dvsni.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_apache.dvsni` +------------------------------- + +.. automodule:: letsencrypt_apache.dvsni + :members: diff --git a/letsencrypt-apache/docs/api/obj.rst b/letsencrypt-apache/docs/api/obj.rst new file mode 100644 index 000000000..969293ca1 --- /dev/null +++ b/letsencrypt-apache/docs/api/obj.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_apache.obj` +----------------------------- + +.. automodule:: letsencrypt_apache.obj + :members: diff --git a/letsencrypt-apache/docs/api/parser.rst b/letsencrypt-apache/docs/api/parser.rst new file mode 100644 index 000000000..0c998e06c --- /dev/null +++ b/letsencrypt-apache/docs/api/parser.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_apache.parser` +-------------------------------- + +.. automodule:: letsencrypt_apache.parser + :members: diff --git a/letsencrypt-apache/docs/conf.py b/letsencrypt-apache/docs/conf.py new file mode 100644 index 000000000..ddbf09262 --- /dev/null +++ b/letsencrypt-apache/docs/conf.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +# +# letsencrypt-apache documentation build configuration file, created by +# sphinx-quickstart on Sun Oct 18 13:39:26 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +import mock + + +# http://docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules +# c.f. #262 +sys.modules.update( + (mod_name, mock.MagicMock()) for mod_name in ['augeas']) + +here = os.path.abspath(os.path.dirname(__file__)) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +autodoc_member_order = 'bysource' +autodoc_default_flags = ['show-inheritance', 'private-members'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'letsencrypt-apache' +copyright = u'2014-2015, Let\'s Encrypt Project' +author = u'Let\'s Encrypt Project' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0' +# The full version, including alpha/beta/rc tags. +release = '0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'py:obj' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'letsencrypt-apachedoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'letsencrypt-apache.tex', u'letsencrypt-apache Documentation', + u'Let\'s Encrypt Project', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'letsencrypt-apache', u'letsencrypt-apache Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'letsencrypt-apache', u'letsencrypt-apache Documentation', + author, 'letsencrypt-apache', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), + 'letsencrypt': ('https://letsencrypt.readthedocs.org/en/latest/', None), +} diff --git a/letsencrypt-apache/docs/index.rst b/letsencrypt-apache/docs/index.rst new file mode 100644 index 000000000..f968ccbef --- /dev/null +++ b/letsencrypt-apache/docs/index.rst @@ -0,0 +1,31 @@ +.. letsencrypt-apache documentation master file, created by + sphinx-quickstart on Sun Oct 18 13:39:26 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to letsencrypt-apache's documentation! +============================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + +.. toctree:: + :maxdepth: 1 + + api + + +.. automodule:: letsencrypt_apache + :members: + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/letsencrypt-apache/docs/make.bat b/letsencrypt-apache/docs/make.bat new file mode 100644 index 000000000..62a54fd2c --- /dev/null +++ b/letsencrypt-apache/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\letsencrypt-apache.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\letsencrypt-apache.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/letsencrypt-apache/letsencrypt_apache/dvsni.py b/letsencrypt-apache/letsencrypt_apache/dvsni.py index c6c41dc51..ed88bf8a7 100644 --- a/letsencrypt-apache/letsencrypt_apache/dvsni.py +++ b/letsencrypt-apache/letsencrypt_apache/dvsni.py @@ -20,7 +20,7 @@ class ApacheDvsni(common.Dvsni): larger array. ApacheDvsni is capable of solving many challenges at once which causes an indexing issue within ApacheConfigurator who must return all responses in order. Imagine ApacheConfigurator - maintaining state about where all of the SimpleHTTP Challenges, + maintaining state about where all of the http-01 Challenges, Dvsni Challenges belong in the response array. This is an optional utility. diff --git a/letsencrypt-apache/letsencrypt_apache/parser.py b/letsencrypt-apache/letsencrypt_apache/parser.py index 0a3643064..ec5211ae4 100644 --- a/letsencrypt-apache/letsencrypt_apache/parser.py +++ b/letsencrypt-apache/letsencrypt_apache/parser.py @@ -122,7 +122,7 @@ class ApacheParser(object): """ try: proc = subprocess.Popen( - [ctl, "-D", "DUMP_RUN_CFG"], + [ctl, "-t", "-D", "DUMP_RUN_CFG"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() diff --git a/letsencrypt-apache/readthedocs.org.requirements.txt b/letsencrypt-apache/readthedocs.org.requirements.txt new file mode 100644 index 000000000..7855b5ce2 --- /dev/null +++ b/letsencrypt-apache/readthedocs.org.requirements.txt @@ -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 letsencrypt-apache[docs] diff --git a/letsencrypt-apache/setup.py b/letsencrypt-apache/setup.py index 626e700b2..e4dd11935 100644 --- a/letsencrypt-apache/setup.py +++ b/letsencrypt-apache/setup.py @@ -20,6 +20,11 @@ if sys.version_info < (2, 7): else: install_requires.append('mock') +docs_extras = [ + 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags + 'sphinx_rtd_theme', +] + setup( name='letsencrypt-apache', version=version, @@ -36,6 +41,7 @@ setup( 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', @@ -48,6 +54,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, + extras_require={ + 'docs': docs_extras, + }, entry_points={ 'letsencrypt.plugins': [ 'apache = letsencrypt_apache.configurator:ApacheConfigurator', diff --git a/letsencrypt-auto b/letsencrypt-auto index e7c9d6737..d163998aa 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -1,5 +1,8 @@ #!/bin/sh -e # +# A script to run the latest release version of the Let's Encrypt in a +# virtual environment +# # Installs and updates the letencrypt virtualenv, and runs letsencrypt # using that virtual environment. This allows the client to function decently # without requiring specific versions of its dependencies from the operating @@ -38,9 +41,15 @@ then elif [ -f /etc/arch-release ] ; then echo "Bootstrapping dependencies for Archlinux..." $SUDO $BOOTSTRAP/archlinux.sh + elif [ -f /etc/manjaro-release ] ; then + echo "Bootstrapping dependencies for Manjaro Linux..." + $SUDO $BOOTSTRAP/manjaro.sh elif [ -f /etc/redhat-release ] ; then echo "Bootstrapping dependencies for RedHat-based OSes..." $SUDO $BOOTSTRAP/_rpm_common.sh + elif [ -f /etc/gentoo-release ] ; then + echo "Bootstrapping dependencies for Gentoo-based OSes..." + $SUDO $BOOTSTRAP/_gentoo_common.sh elif uname | grep -iq FreeBSD ; then echo "Bootstrapping dependencies for FreeBSD..." $SUDO $BOOTSTRAP/freebsd.sh @@ -64,7 +73,7 @@ then fi fi -echo -n "Updating letsencrypt and virtual environment dependencies..." +printf "Updating letsencrypt and virtual environment dependencies..." if [ "$VERBOSE" = 1 ] ; then echo $VENV_BIN/pip install -U setuptools @@ -77,15 +86,15 @@ if [ "$VERBOSE" = 1 ] ; then fi else $VENV_BIN/pip install -U setuptools > /dev/null - echo -n . + printf . $VENV_BIN/pip install -U pip > /dev/null - echo -n . + printf . # nginx is buggy / disabled for now... $VENV_BIN/pip install -U letsencrypt > /dev/null - echo -n . + printf . $VENV_BIN/pip install -U letsencrypt-apache > /dev/null if $VENV_BIN/pip freeze | grep -q letsencrypt-nginx ; then - echo -n . + printf . $VENV_BIN/pip install -U letsencrypt-nginx > /dev/null fi echo diff --git a/letsencrypt-compatibility-test/MANIFEST.in b/letsencrypt-compatibility-test/MANIFEST.in index 4d346a5d0..24d777841 100644 --- a/letsencrypt-compatibility-test/MANIFEST.in +++ b/letsencrypt-compatibility-test/MANIFEST.in @@ -1,5 +1,6 @@ include LICENSE.txt include README.rst +recursive-include docs * include letsencrypt_compatibility_test/configurators/apache/a2enmod.sh include letsencrypt_compatibility_test/configurators/apache/a2dismod.sh include letsencrypt_compatibility_test/configurators/apache/Dockerfile diff --git a/letsencrypt-compatibility-test/docs/.gitignore b/letsencrypt-compatibility-test/docs/.gitignore new file mode 100644 index 000000000..ba65b13af --- /dev/null +++ b/letsencrypt-compatibility-test/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/letsencrypt-compatibility-test/docs/Makefile b/letsencrypt-compatibility-test/docs/Makefile new file mode 100644 index 000000000..90582a59b --- /dev/null +++ b/letsencrypt-compatibility-test/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/letsencrypt-compatibility-test.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/letsencrypt-compatibility-test.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/letsencrypt-compatibility-test" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/letsencrypt-compatibility-test" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/letsencrypt-compatibility-test/docs/_static/.gitignore b/letsencrypt-compatibility-test/docs/_static/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letsencrypt-compatibility-test/docs/_templates/.gitignore b/letsencrypt-compatibility-test/docs/_templates/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letsencrypt-compatibility-test/docs/api.rst b/letsencrypt-compatibility-test/docs/api.rst new file mode 100644 index 000000000..8668ec5d8 --- /dev/null +++ b/letsencrypt-compatibility-test/docs/api.rst @@ -0,0 +1,8 @@ +================= +API Documentation +================= + +.. toctree:: + :glob: + + api/** diff --git a/docs/pkgs/letsencrypt_compatibility_test.rst b/letsencrypt-compatibility-test/docs/api/index.rst similarity index 100% rename from docs/pkgs/letsencrypt_compatibility_test.rst rename to letsencrypt-compatibility-test/docs/api/index.rst diff --git a/letsencrypt-compatibility-test/docs/conf.py b/letsencrypt-compatibility-test/docs/conf.py new file mode 100644 index 000000000..7e9f0d5a4 --- /dev/null +++ b/letsencrypt-compatibility-test/docs/conf.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +# +# letsencrypt-compatibility-test documentation build configuration file, created by +# sphinx-quickstart on Sun Oct 18 13:40:53 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + + +here = os.path.abspath(os.path.dirname(__file__)) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'repoze.sphinx.autointerface', +] + +autodoc_member_order = 'bysource' +autodoc_default_flags = ['show-inheritance', 'private-members'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'letsencrypt-compatibility-test' +copyright = u'2014-2015, Let\'s Encrypt Project' +author = u'Let\'s Encrypt Project' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0' +# The full version, including alpha/beta/rc tags. +release = '0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'py:obj' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'letsencrypt-compatibility-testdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'letsencrypt-compatibility-test.tex', u'letsencrypt-compatibility-test Documentation', + u'Let\'s Encrypt Project', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'letsencrypt-compatibility-test', u'letsencrypt-compatibility-test Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'letsencrypt-compatibility-test', u'letsencrypt-compatibility-test Documentation', + author, 'letsencrypt-compatibility-test', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), + 'letsencrypt': ('https://letsencrypt.readthedocs.org/en/latest/', None), + 'letsencrypt-apache': ('https://letsencrypt-apache.readthedocs.org/en/latest/', None), + 'letsencrypt-nginx': ('https://letsencrypt-nginx.readthedocs.org/en/latest/', None), +} diff --git a/letsencrypt-compatibility-test/docs/index.rst b/letsencrypt-compatibility-test/docs/index.rst new file mode 100644 index 000000000..df57ee6e6 --- /dev/null +++ b/letsencrypt-compatibility-test/docs/index.rst @@ -0,0 +1,27 @@ +.. letsencrypt-compatibility-test documentation master file, created by + sphinx-quickstart on Sun Oct 18 13:40:53 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to letsencrypt-compatibility-test's documentation! +========================================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + +.. toctree:: + :maxdepth: 1 + + api + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/letsencrypt-compatibility-test/docs/make.bat b/letsencrypt-compatibility-test/docs/make.bat new file mode 100644 index 000000000..c75269bdc --- /dev/null +++ b/letsencrypt-compatibility-test/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\letsencrypt-compatibility-test.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\letsencrypt-compatibility-test.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/letsencrypt-compatibility-test/readthedocs.org.requirements.txt b/letsencrypt-compatibility-test/readthedocs.org.requirements.txt new file mode 100644 index 000000000..957a8a157 --- /dev/null +++ b/letsencrypt-compatibility-test/readthedocs.org.requirements.txt @@ -0,0 +1,13 @@ +# 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 letsencrypt-apache +-e letsencrypt-compatibility-test[docs] diff --git a/letsencrypt-compatibility-test/setup.py b/letsencrypt-compatibility-test/setup.py index 2e70fd1d7..c791d51c4 100644 --- a/letsencrypt-compatibility-test/setup.py +++ b/letsencrypt-compatibility-test/setup.py @@ -9,7 +9,6 @@ version = '0.1.0.dev0' install_requires = [ 'letsencrypt=={0}'.format(version), 'letsencrypt-apache=={0}'.format(version), - 'letsencrypt-nginx=={0}'.format(version), 'docker-py', 'zope.interface', ] @@ -19,6 +18,12 @@ if sys.version_info < (2, 7): else: install_requires.append('mock') +docs_extras = [ + 'repoze.sphinx.autointerface', + 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags + 'sphinx_rtd_theme', +] + setup( name='letsencrypt-compatibility-test', version=version, @@ -33,6 +38,7 @@ setup( 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', @@ -41,6 +47,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, + extras_require={ + 'docs': docs_extras, + }, entry_points={ 'console_scripts': [ 'letsencrypt-compatibility-test = letsencrypt_compatibility_test.test_driver:main', diff --git a/letsencrypt-nginx/MANIFEST.in b/letsencrypt-nginx/MANIFEST.in index c4bd67735..912d624d9 100644 --- a/letsencrypt-nginx/MANIFEST.in +++ b/letsencrypt-nginx/MANIFEST.in @@ -1,4 +1,5 @@ include LICENSE.txt include README.rst +recursive-include docs * recursive-include letsencrypt_nginx/tests/testdata * include letsencrypt_nginx/options-ssl-nginx.conf diff --git a/letsencrypt-nginx/docs/.gitignore b/letsencrypt-nginx/docs/.gitignore new file mode 100644 index 000000000..ba65b13af --- /dev/null +++ b/letsencrypt-nginx/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/letsencrypt-nginx/docs/Makefile b/letsencrypt-nginx/docs/Makefile new file mode 100644 index 000000000..3a3828235 --- /dev/null +++ b/letsencrypt-nginx/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/letsencrypt-nginx.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/letsencrypt-nginx.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/letsencrypt-nginx" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/letsencrypt-nginx" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/letsencrypt-nginx/docs/_static/.gitignore b/letsencrypt-nginx/docs/_static/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letsencrypt-nginx/docs/_templates/.gitignore b/letsencrypt-nginx/docs/_templates/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letsencrypt-nginx/docs/api.rst b/letsencrypt-nginx/docs/api.rst new file mode 100644 index 000000000..8668ec5d8 --- /dev/null +++ b/letsencrypt-nginx/docs/api.rst @@ -0,0 +1,8 @@ +================= +API Documentation +================= + +.. toctree:: + :glob: + + api/** diff --git a/letsencrypt-nginx/docs/api/dvsni.rst b/letsencrypt-nginx/docs/api/dvsni.rst new file mode 100644 index 000000000..4f5f9d7e3 --- /dev/null +++ b/letsencrypt-nginx/docs/api/dvsni.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_nginx.dvsni` +------------------------------ + +.. automodule:: letsencrypt_nginx.dvsni + :members: diff --git a/letsencrypt-nginx/docs/api/nginxparser.rst b/letsencrypt-nginx/docs/api/nginxparser.rst new file mode 100644 index 000000000..e55bda0b1 --- /dev/null +++ b/letsencrypt-nginx/docs/api/nginxparser.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_nginx.nginxparser` +------------------------------------ + +.. automodule:: letsencrypt_nginx.nginxparser + :members: diff --git a/letsencrypt-nginx/docs/api/obj.rst b/letsencrypt-nginx/docs/api/obj.rst new file mode 100644 index 000000000..418b87cf7 --- /dev/null +++ b/letsencrypt-nginx/docs/api/obj.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_nginx.obj` +---------------------------- + +.. automodule:: letsencrypt_nginx.obj + :members: diff --git a/letsencrypt-nginx/docs/api/parser.rst b/letsencrypt-nginx/docs/api/parser.rst new file mode 100644 index 000000000..6582263ef --- /dev/null +++ b/letsencrypt-nginx/docs/api/parser.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt_nginx.parser` +------------------------------- + +.. automodule:: letsencrypt_nginx.parser + :members: diff --git a/letsencrypt-nginx/docs/conf.py b/letsencrypt-nginx/docs/conf.py new file mode 100644 index 000000000..cdb3490a0 --- /dev/null +++ b/letsencrypt-nginx/docs/conf.py @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- +# +# letsencrypt-nginx documentation build configuration file, created by +# sphinx-quickstart on Sun Oct 18 13:39:39 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + + +here = os.path.abspath(os.path.dirname(__file__)) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +autodoc_member_order = 'bysource' +autodoc_default_flags = ['show-inheritance', 'private-members'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'letsencrypt-nginx' +copyright = u'2014-2015, Let\'s Encrypt Project' +author = u'Let\'s Encrypt Project' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0' +# The full version, including alpha/beta/rc tags. +release = '0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'py:obj' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'letsencrypt-nginxdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'letsencrypt-nginx.tex', u'letsencrypt-nginx Documentation', + u'Let\'s Encrypt Project', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'letsencrypt-nginx', u'letsencrypt-nginx Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'letsencrypt-nginx', u'letsencrypt-nginx Documentation', + author, 'letsencrypt-nginx', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), + 'letsencrypt': ('https://letsencrypt.readthedocs.org/en/latest/', None), +} diff --git a/letsencrypt-nginx/docs/index.rst b/letsencrypt-nginx/docs/index.rst new file mode 100644 index 000000000..e4f8f715f --- /dev/null +++ b/letsencrypt-nginx/docs/index.rst @@ -0,0 +1,31 @@ +.. letsencrypt-nginx documentation master file, created by + sphinx-quickstart on Sun Oct 18 13:39:39 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to letsencrypt-nginx's documentation! +============================================= + +Contents: + +.. toctree:: + :maxdepth: 2 + + +.. toctree:: + :maxdepth: 1 + + api + + +.. automodule:: letsencrypt_nginx + :members: + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/letsencrypt-nginx/docs/make.bat b/letsencrypt-nginx/docs/make.bat new file mode 100644 index 000000000..eb19a3fb5 --- /dev/null +++ b/letsencrypt-nginx/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\letsencrypt-nginx.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\letsencrypt-nginx.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/letsencrypt-nginx/letsencrypt_nginx/dvsni.py b/letsencrypt-nginx/letsencrypt_nginx/dvsni.py index 9ac2fcd7c..662f10889 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/dvsni.py +++ b/letsencrypt-nginx/letsencrypt_nginx/dvsni.py @@ -26,7 +26,7 @@ class NginxDvsni(common.Dvsni): larger array. NginxDvsni is capable of solving many challenges at once which causes an indexing issue within NginxConfigurator who must return all responses in order. Imagine NginxConfigurator - maintaining state about where all of the SimpleHTTP Challenges, + maintaining state about where all of the http-01 Challenges, Dvsni Challenges belong in the response array. This is an optional utility. diff --git a/letsencrypt-nginx/letsencrypt_nginx/parser.py b/letsencrypt-nginx/letsencrypt_nginx/parser.py index fc8ed95f1..19483821a 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/parser.py +++ b/letsencrypt-nginx/letsencrypt_nginx/parser.py @@ -491,5 +491,5 @@ def _add_directives(block, directives, replace=False): changed = True if not changed: raise errors.MisconfigurationError( - 'LetsEncrypt expected directive for %s in the Nginx ' + 'Let\'s Encrypt expected directive for %s in the Nginx ' 'config but did not find it.' % directive[0]) diff --git a/letsencrypt-nginx/readthedocs.org.requirements.txt b/letsencrypt-nginx/readthedocs.org.requirements.txt new file mode 100644 index 000000000..3b55df408 --- /dev/null +++ b/letsencrypt-nginx/readthedocs.org.requirements.txt @@ -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 letsencrypt-nginx[docs] diff --git a/letsencrypt-nginx/setup.py b/letsencrypt-nginx/setup.py index a37b8222b..a669ad841 100644 --- a/letsencrypt-nginx/setup.py +++ b/letsencrypt-nginx/setup.py @@ -20,6 +20,11 @@ if sys.version_info < (2, 7): else: install_requires.append('mock') +docs_extras = [ + 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags + 'sphinx_rtd_theme', +] + setup( name='letsencrypt-nginx', version=version, @@ -36,6 +41,7 @@ setup( 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', @@ -48,6 +54,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, + extras_require={ + 'docs': docs_extras, + }, entry_points={ 'letsencrypt.plugins': [ 'nginx = letsencrypt_nginx.configurator:NginxConfigurator', diff --git a/letsencrypt/achallenges.py b/letsencrypt/achallenges.py index e86f51a3f..f08c6a396 100644 --- a/letsencrypt/achallenges.py +++ b/letsencrypt/achallenges.py @@ -45,6 +45,15 @@ class AnnotatedChallenge(jose.ImmutableMap): return getattr(self.challb, name) +class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge): + """Client annotated `KeyAuthorizationChallenge` challenge.""" + __slots__ = ('challb', 'domain', 'account_key') + + def response_and_validation(self): + """Generate response and validation.""" + return self.challb.chall.response_and_validation(self.account_key) + + class DVSNI(AnnotatedChallenge): """Client annotated "dvsni" ACME challenge. @@ -76,31 +85,6 @@ class DVSNI(AnnotatedChallenge): return response, cert, key -class SimpleHTTP(AnnotatedChallenge): - """Client annotated "simpleHttp" ACME challenge.""" - __slots__ = ('challb', 'domain', 'account_key') - acme_type = challenges.SimpleHTTP - - def gen_response_and_validation(self, tls): - """Generates a SimpleHTTP response and validation. - - :param bool tls: True if TLS should be used - - :returns: ``(response, validation)`` tuple, where ``response`` is - an instance of `acme.challenges.SimpleHTTPResponse` and - ``validation`` is an instance of - `acme.challenges.SimpleHTTPProvisionedResource`. - :rtype: tuple - - """ - response = challenges.SimpleHTTPResponse(tls=tls) - - validation = response.gen_validation( - self.challb.chall, self.account_key) - logger.debug("Simple HTTP validation payload: %s", validation.payload) - return response, validation - - class DNS(AnnotatedChallenge): """Client annotated "dns" ACME challenge.""" __slots__ = ('challb', 'domain') diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index 2da053f8c..11019daac 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -347,9 +347,6 @@ def challb_to_achall(challb, account_key, domain): if isinstance(chall, challenges.DVSNI): return achallenges.DVSNI( challb=challb, domain=domain, account_key=account_key) - elif isinstance(chall, challenges.SimpleHTTP): - return achallenges.SimpleHTTP( - challb=challb, domain=domain, account_key=account_key) elif isinstance(chall, challenges.DNS): return achallenges.DNS(challb=challb, domain=domain) elif isinstance(chall, challenges.RecoveryContact): @@ -358,7 +355,9 @@ def challb_to_achall(challb, account_key, domain): elif isinstance(chall, challenges.ProofOfPossession): return achallenges.ProofOfPossession( challb=challb, domain=domain) - + elif isinstance(chall, challenges.KeyAuthorizationChallenge): + return achallenges.KeyAuthorizationAnnotatedChallenge( + challb=challb, domain=domain, account_key=account_key) else: raise errors.Error( "Received unsupported challenge of type: %s", chall.typ) @@ -486,29 +485,29 @@ def is_preferred(offered_challb, satisfied, _ERROR_HELP_COMMON = ( "To fix these errors, please make sure that your domain name was entered " - "correctly and the DNS A/AAAA record(s) for that domain contains the " + "correctly and the DNS A record(s) for that domain contain(s) the " "right IP address.") _ERROR_HELP = { "connection": _ERROR_HELP_COMMON + " Additionally, please check that your computer " - "has publicly routable IP address and no firewalls are preventing the " - "server from communicating with the client.", + "has a publicly routable IP address and that no firewalls are preventing " + "the server from communicating with the client.", "dnssec": _ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for " - "your domain, please ensure the signature is valid.", + "your domain, please ensure that the signature is valid.", "malformed": "To fix these errors, please make sure that you did not provide any " - "invalid information to the client and try running Let's Encrypt " + "invalid information to the client, and try running Let's Encrypt " "again.", "serverInternal": "Unfortunately, an error on the ACME server prevented you from completing " "authorization. Please try again later.", "tls": - _ERROR_HELP_COMMON + " Additionally, please check that you have an up " - "to date TLS configuration that allows the server to communicate with " - "the Let's Encrypt client.", + _ERROR_HELP_COMMON + " Additionally, please check that you have an " + "up-to-date TLS configuration that allows the server to communicate " + "with the Let's Encrypt client.", "unauthorized": _ERROR_HELP_COMMON, "unknownHost": _ERROR_HELP_COMMON, } diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 1b396b0b8..9f532f002 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -12,7 +12,6 @@ import time import traceback import configargparse -import configobj import OpenSSL import zope.component import zope.interface.exceptions @@ -54,37 +53,52 @@ SHORT_USAGE = """ The Let's Encrypt agent can obtain and install HTTPS/TLS/SSL certificates. By default, it will attempt to use a webserver both for obtaining and installing -the cert. """ - -# This is the short help for letsencrypt --help, where we disable argparse -# altogether -USAGE = SHORT_USAGE + """Major SUBCOMMANDS are: +the cert. Major SUBCOMMANDS are: (default) run Obtain & install a cert in your current webserver - auth Authenticate & obtain cert, but do not install it + certonly Obtain cert, but do not install it (aka "auth") install Install a previously obtained cert in a server revoke Revoke a previously obtained certificate rollback Rollback server configuration changes made during install config_changes Show changes made to server config during installation -Choice of server for authentication/installation: +""" - --apache Use the Apache plugin for authentication & installation - --nginx Use the Nginx plugin for authentication & installation +# This is the short help for letsencrypt --help, where we disable argparse +# altogether +USAGE = SHORT_USAGE + """Choice of server plugins for obtaining and installing cert: + + %s --standalone Run a standalone webserver for authentication - OR: - --authenticator standalone --installer nginx + %s + +OR use different servers to obtain (authenticate) the cert and then install it: + + --authenticator standalone --installer apache More detailed help: -h, --help [topic] print this message, or detailed help on a topic; the available topics are: - all, apache, automation, manual, nginx, paths, security, testing, or any of - the subcommands + all, automation, paths, security, testing, or any of the subcommands or + plugins (certonly, install, nginx, apache, standalone, etc) """ +def usage_strings(plugins): + """Make usage strings late so that plugins can be initialised late""" + if "nginx" in plugins: + nginx_doc = "--nginx Use the Nginx plugin for authentication & installation" + else: + nginx_doc = "(nginx support is experimental, buggy, and not installed by default)" + if "apache" in plugins: + apache_doc = "--apache Use the Apache plugin for authentication & installation" + else: + apache_doc = "(the apache plugin is not installed)" + return USAGE % (apache_doc, nginx_doc), SHORT_USAGE + + def _find_domains(args, installer): if args.domains is None: domains = display_ops.choose_names(installer) @@ -167,25 +181,21 @@ def _init_le_client(args, config, authenticator, installer): return client.Client(config, acc, authenticator, installer, acme=acme) -def _find_duplicative_certs(domains, config, renew_config): +def _find_duplicative_certs(config, domains): """Find existing certs that duplicate the request.""" identical_names_cert, subset_names_cert = None, None - configs_dir = renew_config.renewal_configs_dir + cli_config = configuration.RenewerConfiguration(config) + configs_dir = cli_config.renewal_configs_dir # Verify the directory is there le_util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) - cli_config = configuration.RenewerConfiguration(config) for renewal_file in os.listdir(configs_dir): try: full_path = os.path.join(configs_dir, renewal_file) - rc_config = configobj.ConfigObj(renew_config.renewer_config_file) - rc_config.merge(configobj.ConfigObj(full_path)) - rc_config.filename = full_path - candidate_lineage = storage.RenewableCert( - rc_config, config_opts=None, cli_config=cli_config) - except (configobj.ConfigObjError, CertStorageError, IOError): + candidate_lineage = storage.RenewableCert(full_path, cli_config) + except (CertStorageError, IOError): logger.warning("Renewal configuration file %s is broken. " "Skipping.", full_path) continue @@ -217,7 +227,7 @@ def _treat_as_renewal(config, domains): # kind of certificate to be obtained with renewal = False.) if not config.duplicate: ident_names_cert, subset_names_cert = _find_duplicative_certs( - domains, config, configuration.RenewerConfiguration(config)) + config, domains) # I am not sure whether that correctly reads the systemwide # configuration file. question = None @@ -267,16 +277,31 @@ def _treat_as_renewal(config, domains): return None -def _report_new_cert(cert_path): - """Reports the creation of a new certificate to the user.""" +def _report_new_cert(cert_path, fullchain_path): + """Reports the creation of a new certificate to the user. + + :param str cert_path: path to cert + :param str fullchain_path: path to full chain + + """ expiry = crypto_util.notAfter(cert_path).date() reporter_util = zope.component.getUtility(interfaces.IReporter) - reporter_util.add_message("Congratulations! Your certificate has been " - "saved at {0} and will expire on {1}. To obtain " - "a new version of the certificate in the " - "future, simply run Let's Encrypt again.".format( - cert_path, expiry), - reporter_util.MEDIUM_PRIORITY) + if fullchain_path: + # Print the path to fullchain.pem because that's what modern webservers + # (Nginx and Apache2.4) will want. + and_chain = "and chain have" + path = fullchain_path + else: + # Unless we're in .csr mode and there really isn't one + and_chain = "has " + path = cert_path + # XXX Perhaps one day we could detect the presence of known old webservers + # and say something more informative here. + msg = ("Congratulations! Your certificate {0} been saved at {1}." + " Your cert will expire on {2}. To obtain a new version of the " + "certificate in the future, simply run Let's Encrypt again." + .format(and_chain, path, expiry)) + reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY) def _auth_from_domains(le_client, config, domains, plugins): @@ -304,7 +329,7 @@ def _auth_from_domains(le_client, config, domains, plugins): if not lineage: raise Error("Certificate could not be obtained") - _report_new_cert(lineage.cert) + _report_new_cert(lineage.cert, lineage.fullchain) return lineage @@ -312,8 +337,8 @@ def _auth_from_domains(le_client, config, domains, plugins): def set_configurator(previously, now): """ Setting configurators multiple ways is okay, as long as they all agree - :param string previously: previously identified request for the installer/authenticator - :param string requested: the request currently being processed + :param str previously: previously identified request for the installer/authenticator + :param str requested: the request currently being processed """ if now is None: # we're not actually setting anything @@ -329,9 +354,9 @@ def diagnose_configurator_problem(cfg_type, requested, plugins): """ Raise the most helpful error message about a plugin being unavailable - :param string cfg_type: either "installer" or "authenticator" - :param string requested: the plugin that was requested - :param PluginRegistry plugins: available plugins + :param str cfg_type: either "installer" or "authenticator" + :param str requested: the plugin that was requested + :param .PluginsRegistry plugins: available plugins :raises error.PluginSelectionError: if there was a problem """ @@ -341,16 +366,17 @@ def diagnose_configurator_problem(cfg_type, requested, plugins): msg = "The requested {0} plugin does not appear to be installed".format(requested) else: msg = ("The {0} plugin is not working; there may be problems with " - "your existing configuration").format(requested) + "your existing configuration.\nThe error was: {1!r}" + .format(requested, plugins[requested].problem)) elif cfg_type == "installer": if os.path.exists("/etc/debian_version"): # Debian... installers are at least possible msg = ('No installers seem to be present and working on your system; ' - 'fix that or try running letsencrypt with the "auth" command') + 'fix that or try running letsencrypt with the "certonly" command') else: # XXX update this logic as we make progress on #788 and nginx support msg = ('No installers are available on your OS yet; try running ' - '"letsencrypt-auto auth" to get a cert you can install manually') + '"letsencrypt-auto certonly" to get a cert you can install manually') else: msg = "{0} could not be determined or is not installed".format(cfg_type) raise PluginSelectionError(msg) @@ -365,7 +391,7 @@ def choose_configurator_plugins(args, config, plugins, verb): # Which plugins do we need? need_inst = need_auth = (verb == "run") - if verb == "auth": + if verb == "certonly": need_auth = True if verb == "install": need_inst = True @@ -426,6 +452,7 @@ def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-lo le_client.deploy_certificate( domains, lineage.privkey, lineage.cert, lineage.chain, lineage.fullchain) + le_client.enhance_config(domains, args.redirect) if len(lineage.available_versions("cert")) == 1: @@ -434,7 +461,7 @@ def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-lo display_ops.success_renewal(domains) -def auth(args, config, plugins): +def obtaincert(args, config, plugins): """Authenticate & obtain cert, but do not install it.""" if args.domains is not None and args.csr is not None: @@ -444,7 +471,7 @@ def auth(args, config, plugins): try: # installers are used in auth mode to determine domain names - installer, authenticator = choose_configurator_plugins(args, config, plugins, "auth") + installer, authenticator = choose_configurator_plugins(args, config, plugins, "certonly") except PluginSelectionError, e: return e.message @@ -455,9 +482,9 @@ def auth(args, config, plugins): if args.csr is not None: certr, chain = le_client.obtain_certificate_from_csr(le_util.CSR( file=args.csr[0], data=args.csr[1], form="der")) - cert_path, _ = le_client.save_certificate( - certr, chain, args.cert_path, args.chain_path) - _report_new_cert(cert_path) + cert_path, _, cert_fullchain = le_client.save_certificate( + certr, chain, args.cert_path, args.chain_path, args.fullchain_path) + _report_new_cert(cert_path, cert_fullchain) else: domains = _find_domains(args, installer) _auth_from_domains(le_client, config, domains, plugins) @@ -468,7 +495,8 @@ def install(args, config, plugins): # XXX: Update for renewer/RenewableCert try: - installer, _ = choose_configurator_plugins(args, config, plugins, "auth") + installer, _ = choose_configurator_plugins(args, config, + plugins, "install") except PluginSelectionError, e: return e.message @@ -521,7 +549,6 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print logger.debug("Filtered plugins: %r", filtered) if not args.init and not args.prepare: - print str(filtered) return filtered.init(config) @@ -529,13 +556,11 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print logger.debug("Verified plugins: %r", verified) if not args.prepare: - print str(verified) return verified.prepare() available = verified.available() logger.debug("Prepared plugins: %s", available) - print str(available) def read_file(filename, mode="rb"): @@ -594,11 +619,23 @@ class HelpfulArgumentParser(object): 'letsencrypt --help security' for security options. """ + + # Maps verbs/subcommands to the functions that implement them + VERBS = {"auth": obtaincert, "certonly": obtaincert, + "config_changes": config_changes, "everything": run, + "install": install, "plugins": plugins_cmd, + "revoke": revoke, "rollback": rollback, "run": run} + + # List of topics for which additional help can be provided + HELP_TOPICS = ["all", "security", + "paths", "automation", "testing"] + VERBS.keys() + def __init__(self, args, plugins): plugin_names = [name for name, _p in plugins.iteritems()] - self.help_topics = HELP_TOPICS + plugin_names + [None] + self.help_topics = self.HELP_TOPICS + plugin_names + [None] + usage, short_usage = usage_strings(plugins) self.parser = configargparse.ArgParser( - usage=SHORT_USAGE, + usage=short_usage, formatter_class=argparse.ArgumentDefaultsHelpFormatter, args_for_setting_config_path=["-c", "--config"], default_config_files=flag_default("config_files")) @@ -607,39 +644,55 @@ class HelpfulArgumentParser(object): self.parser._add_config_file_help = False # pylint: disable=protected-access self.silent_parser = SilentParser(self.parser) - self.verb = None - self.args = self.preprocess_args(args) + self.args = args + self.determine_verb() help1 = self.prescan_for_flag("-h", self.help_topics) help2 = self.prescan_for_flag("--help", self.help_topics) assert max(True, "a") == "a", "Gravity changed direction" - help_arg = max(help1, help2) - if help_arg is True: + self.help_arg = max(help1, help2) + if self.help_arg is True: # just --help with no topic; avoid argparse altogether - print USAGE + print usage sys.exit(0) - self.visible_topics = self.determine_help_topics(help_arg) + self.visible_topics = self.determine_help_topics(self.help_arg) #print self.visible_topics self.groups = {} # elements are added by .add_group() - def preprocess_args(self, args): - """Work around some limitations in argparse. + def parse_args(self): + """Parses command line arguments and returns the result. + + :returns: parsed command line arguments + :rtype: argparse.Namespace - Currently: add the default verb "run" as a default, and ensure that the - subcommand / verb comes last. """ - if "-h" in args or "--help" in args: + parsed_args = self.parser.parse_args(self.args) + parsed_args.func = self.VERBS[self.verb] + + return parsed_args + + def determine_verb(self): + """Determines the verb/subcommand provided by the user. + + This function works around some of the limitations of argparse. + + """ + if "-h" in self.args or "--help" in self.args: # all verbs double as help arguments; don't get them confused self.verb = "help" - return args + return - for i, token in enumerate(args): - if token in VERBS: - reordered = args[:i] + args[(i + 1):] + [args[i]] - self.verb = token - return reordered + for i, token in enumerate(self.args): + if token in self.VERBS: + verb = token + if verb == "auth": + verb = "certonly" + if verb == "everything": + verb = "run" + self.verb = verb + self.args.pop(i) + return self.verb = "run" - return args + ["run"] def prescan_for_flag(self, flag, possible_arguments): """Checks cli input for flags. @@ -720,6 +773,10 @@ class HelpfulArgumentParser(object): """ # topics maps each topic to whether it should be documented by # argparse on the command line + if chosen_topic == "auth": + chosen_topic = "certonly" + if chosen_topic == "everything": + chosen_topic = "run" if chosen_topic == "all": return dict([(t, True) for t in self.help_topics]) elif not chosen_topic: @@ -728,8 +785,16 @@ class HelpfulArgumentParser(object): return dict([(t, t == chosen_topic) for t in self.help_topics]) -def create_parser(plugins, args): - """Create parser.""" +def prepare_and_parse_args(plugins, args): + """Returns parsed command line arguments. + + :param .PluginsRegistry plugins: available plugins + :param list args: command line arguments with the program name removed + + :returns: parsed command line arguments + :rtype: argparse.Namespace + + """ helpful = HelpfulArgumentParser(args, plugins) # --help is automatically provided by argparse @@ -783,73 +848,52 @@ def create_parser(plugins, args): "testing", "--no-verify-ssl", action="store_true", help=config_help("no_verify_ssl"), default=flag_default("no_verify_ssl")) - helpful.add( # TODO: apache plugin does NOT respect it (#479) + helpful.add( "testing", "--dvsni-port", type=int, default=flag_default("dvsni_port"), help=config_help("dvsni_port")) - helpful.add("testing", "--simple-http-port", type=int, - help=config_help("simple_http_port")) + helpful.add("testing", "--http-01-port", dest="http01_port", type=int, + help=config_help("http01_port")) helpful.add_group( "security", description="Security parameters & server settings") helpful.add( - "security", "-B", "--rsa-key-size", type=int, metavar="N", + "security", "--rsa-key-size", type=int, metavar="N", default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) - # TODO: resolve - assumes binary logic while client.py assumes ternary. helpful.add( - "security", "-r", "--redirect", action="store_true", + "security", "--redirect", action="store_true", help="Automatically redirect all HTTP traffic to HTTPS for the newly " - "authenticated vhost.") + "authenticated vhost.", dest="redirect", default=None) + helpful.add( + "security", "--no-redirect", action="store_false", + help="Do not automatically redirect all HTTP traffic to HTTPS for the newly " + "authenticated vhost.", dest="redirect", default=None) helpful.add( "security", "--strict-permissions", action="store_true", help="Require that all configuration files are owned by the current " "user; only needed if your config is somewhere unsafe like /tmp/") + _create_subparsers(helpful) _paths_parser(helpful) # _plugins_parsing should be the last thing to act upon the main # parser (--help should display plugin-specific options last) _plugins_parsing(helpful, plugins) - _create_subparsers(helpful) - return helpful.parser, helpful.args - - -# For now unfortunately this constant just needs to match the code below; -# there isn't an elegant way to autogenerate it in time. -VERBS = ["run", "auth", "install", "revoke", "rollback", "config_changes", "plugins"] -HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + VERBS + return helpful.parse_args() def _create_subparsers(helpful): - subparsers = helpful.parser.add_subparsers(metavar="SUBCOMMAND") - - def add_subparser(name): # pylint: disable=missing-docstring - if name == "plugins": - func = plugins_cmd - else: - func = eval(name) # pylint: disable=eval-used - h = func.__doc__.splitlines()[0] - subparser = subparsers.add_parser(name, help=h, description=func.__doc__) - subparser.set_defaults(func=func) - return subparser - - # the order of add_subparser() calls is important: it defines the - # order in which subparser names will be displayed in --help - # these add_subparser objects return objects to which arguments could be - # attached, but they have annoying arg ordering constrains so we use - # groups instead: https://github.com/letsencrypt/letsencrypt/issues/820 - for v in VERBS: - add_subparser(v) - - helpful.add_group("auth", description="Options for modifying how a cert is obtained") + helpful.add_group("certonly", description="Options for modifying how a cert is obtained") helpful.add_group("install", description="Options for modifying how a cert is deployed") helpful.add_group("revoke", description="Options for revocation of certs") helpful.add_group("rollback", description="Options for reverting config changes") helpful.add_group("plugins", description="Plugin options") - helpful.add("auth", + helpful.add("certonly", "--csr", type=read_file, - help="Path to a Certificate Signing Request (CSR) in DER format.") + help="Path to a Certificate Signing Request (CSR) in DER" + " format; note that the .csr file *must* contain a Subject" + " Alternative Name field for each domain you want certified.") helpful.add("rollback", "--checkpoints", type=int, metavar="N", default=flag_default("rollback_checkpoints"), @@ -869,24 +913,32 @@ def _create_subparsers(helpful): def _paths_parser(helpful): add = helpful.add verb = helpful.verb + if verb == "help": + verb = helpful.help_arg helpful.add_group( "paths", description="Arguments changing execution paths & servers") - cph = "Path to where cert is saved (with auth), installed (with install --csr) or revoked." - if verb == "auth": - add("paths", "--cert-path", default=flag_default("auth_cert_path"), help=cph) + cph = "Path to where cert is saved (with auth --csr), installed from or revoked." + section = "paths" + if verb in ("install", "revoke", "certonly"): + section = verb + if verb == "certonly": + add(section, "--cert-path", default=flag_default("auth_cert_path"), help=cph) elif verb == "revoke": - add("paths", "--cert-path", type=read_file, required=True, help=cph) + add(section, "--cert-path", type=read_file, required=True, help=cph) else: - add("paths", "--cert-path", help=cph, required=(verb == "install")) + add(section, "--cert-path", help=cph, required=(verb == "install")) + section = "paths" + if verb in ("install", "revoke"): + section = verb # revoke --key-path reads a file, install --key-path takes a string - add("paths", "--key-path", type=((verb == "revoke" and read_file) or str), + add(section, "--key-path", type=((verb == "revoke" and read_file) or str), required=(verb == "install"), help="Path to private key for cert creation or revocation (if account key is missing)") default_cp = None - if verb == "auth": + if verb == "certonly": default_cp = flag_default("auth_chain_path") add("paths", "--fullchain-path", default=default_cp, help="Accompanying path to a full certificate chain (cert plus chain).") @@ -1030,8 +1082,7 @@ def main(cli_args=sys.argv[1:]): # note: arg parser internally handles --help (and exits afterwards) plugins = plugins_disco.PluginsRegistry.find_all() - parser, tweaked_cli_args = create_parser(plugins, cli_args) - args = parser.parse_args(tweaked_cli_args) + args = prepare_and_parse_args(plugins, cli_args) config = configuration.NamespaceConfig(args) zope.component.provideUtility(config) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 937526bbb..6eb33f3fe 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -258,8 +258,8 @@ class Client(object): params, config, cli_config) return lineage - def save_certificate(self, certr, chain_cert, cert_path, chain_path): - # pylint: disable=no-self-use + def save_certificate(self, certr, chain_cert, + cert_path, chain_path, fullchain_path): """Saves the certificate received from the ACME server. :param certr: ACME "certificate" resource. @@ -268,24 +268,23 @@ class Client(object): :param list chain_cert: :param str cert_path: Candidate path to a certificate. :param str chain_path: Candidate path to a certificate chain. + :param str fullchain_path: Candidate path to a full cert chain. - :returns: cert_path, chain_path (absolute paths to the actual files) + :returns: cert_path, chain_path, and fullchain_path as absolute + paths to the actual files :rtype: `tuple` of `str` :raises IOError: If unable to find room to write the cert files """ - for path in cert_path, chain_path: + for path in cert_path, chain_path, fullchain_path: le_util.make_or_verify_dir( os.path.dirname(path), 0o755, os.geteuid(), self.config.strict_permissions) - # try finally close - cert_chain_abspath = None - cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) - # TODO: Except cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body) + cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) try: cert_file.write(cert_pem) finally: @@ -293,22 +292,15 @@ class Client(object): logger.info("Server issued certificate; certificate written to %s", act_cert_path) + cert_chain_abspath = None + fullchain_abspath = None if chain_cert: - chain_file, act_chain_path = le_util.unique_file( - chain_path, 0o644) - # TODO: Except chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert) - try: - chain_file.write(chain_pem) - finally: - chain_file.close() + cert_chain_abspath = _save_chain(chain_pem, chain_path) + fullchain_abspath = _save_chain(cert_pem + chain_pem, + fullchain_path) - logger.info("Cert chain written to %s", act_chain_path) - - # This expects a valid chain file - cert_chain_abspath = os.path.abspath(act_chain_path) - - return os.path.abspath(act_cert_path), cert_chain_abspath + return os.path.abspath(act_cert_path), cert_chain_abspath, fullchain_abspath def deploy_certificate(self, domains, privkey_path, cert_path, chain_path, fullchain_path): @@ -334,7 +326,7 @@ class Client(object): key_path=os.path.abspath(privkey_path), chain_path=chain_path, fullchain_path=fullchain_path) - self.installer.save() + self.installer.save() # needed by the Apache plugin self.installer.save("Deployed Let's Encrypt Certificate") # sites may have been enabled / final cleanup @@ -466,3 +458,25 @@ def view_config_changes(config): rev = reverter.Reverter(config) rev.recovery_routine() rev.view_config_changes() + + +def _save_chain(chain_pem, chain_path): + """Saves chain_pem at a unique path based on chain_path. + + :param str chain_pem: certificate chain in PEM format + :param str chain_path: candidate path for the cert chain + + :returns: absolute path to saved cert chain + :rtype: str + + """ + chain_file, act_chain_path = le_util.unique_file(chain_path, 0o644) + try: + chain_file.write(chain_pem) + finally: + chain_file.close() + + logger.info("Cert chain written to %s", act_chain_path) + + # This expects a valid chain file + return os.path.abspath(act_chain_path) diff --git a/letsencrypt/colored_logging.py b/letsencrypt/colored_logging.py index 170da0b38..443364ddd 100644 --- a/letsencrypt/colored_logging.py +++ b/letsencrypt/colored_logging.py @@ -17,7 +17,12 @@ class StreamHandler(logging.StreamHandler): """ def __init__(self, stream=None): - super(StreamHandler, self).__init__(stream) + if sys.version_info < (2, 7): + # pragma: no cover + # pylint: disable=non-parent-init-called + logging.StreamHandler.__init__(self, stream) + else: + super(StreamHandler, self).__init__(stream) self.colored = (sys.stderr.isatty() if stream is None else stream.isatty()) self.red_level = logging.WARNING @@ -31,10 +36,10 @@ class StreamHandler(logging.StreamHandler): :rtype: str """ - output = super(StreamHandler, self).format(record) + out = (logging.StreamHandler.format(self, record) + if sys.version_info < (2, 7) + else super(StreamHandler, self).format(record)) if self.colored and record.levelno >= self.red_level: - return ''.join((le_util.ANSI_SGR_RED, - output, - le_util.ANSI_SGR_RESET)) + return ''.join((le_util.ANSI_SGR_RED, out, le_util.ANSI_SGR_RESET)) else: - return output + return out diff --git a/letsencrypt/configuration.py b/letsencrypt/configuration.py index f72005233..b604651e9 100644 --- a/letsencrypt/configuration.py +++ b/letsencrypt/configuration.py @@ -37,9 +37,9 @@ class NamespaceConfig(object): def __init__(self, namespace): self.namespace = namespace - if self.simple_http_port == self.dvsni_port: + if self.http01_port == self.dvsni_port: raise errors.Error( - "Trying to run SimpleHTTP and DVSNI " + "Trying to run http-01 and DVSNI " "on the same port ({0})".format(self.dvsni_port)) def __getattr__(self, name): @@ -78,11 +78,11 @@ class NamespaceConfig(object): self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) @property - def simple_http_port(self): # pylint: disable=missing-docstring - if self.namespace.simple_http_port is not None: - return self.namespace.simple_http_port + def http01_port(self): # pylint: disable=missing-docstring + if self.namespace.http01_port is not None: + return self.namespace.http01_port else: - return challenges.SimpleHTTPResponse.PORT + return challenges.HTTP01Response.PORT class RenewerConfiguration(object): diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 362009ec6..15f8c53f0 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -41,7 +41,7 @@ RENEWER_DEFAULTS = dict( EXCLUSIVE_CHALLENGES = frozenset([frozenset([ - challenges.DVSNI, challenges.SimpleHTTP])]) + challenges.DVSNI, challenges.HTTP01])]) """Mutually exclusive challenges.""" diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 8bf714c88..498b01683 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -223,7 +223,7 @@ class IConfig(zope.interface.Interface): "Port number to perform DVSNI challenge. " "Boulder in testing mode defaults to 5001.") - simple_http_port = zope.interface.Attribute( + http01_port = zope.interface.Attribute( "Port used in the SimpleHttp challenge.") diff --git a/letsencrypt/plugins/disco.py b/letsencrypt/plugins/disco.py index 5a41fda88..9ed6ac596 100644 --- a/letsencrypt/plugins/disco.py +++ b/letsencrypt/plugins/disco.py @@ -120,6 +120,13 @@ class PluginEntryPoint(object): """Is plugin misconfigured?""" return isinstance(self._prepared, errors.MisconfigurationError) + @property + def problem(self): + """Return the Exception raised during plugin setup, or None if all is well""" + if isinstance(self._prepared, Exception): + return self._prepared + return None + @property def available(self): """Is plugin available, i.e. prepared or misconfigured?""" diff --git a/letsencrypt/plugins/disco_test.py b/letsencrypt/plugins/disco_test.py index 293c2b913..41d8cd5fe 100644 --- a/letsencrypt/plugins/disco_test.py +++ b/letsencrypt/plugins/disco_test.py @@ -51,7 +51,7 @@ class PluginEntryPointTest(unittest.TestCase): def test_description(self): self.assertEqual( - "Automatically configure and run a simple webserver", + "Automatically use a temporary webserver", self.plugin_ep.description) def test_description_with_name(self): @@ -70,6 +70,7 @@ class PluginEntryPointTest(unittest.TestCase): self.assertFalse(self.plugin_ep.prepared) self.assertFalse(self.plugin_ep.misconfigured) self.assertFalse(self.plugin_ep.available) + self.assertTrue(self.plugin_ep.problem is None) self.assertTrue(self.plugin_ep.entry_point is EP_SA) self.assertEqual("sa", self.plugin_ep.name) @@ -133,6 +134,8 @@ class PluginEntryPointTest(unittest.TestCase): errors.MisconfigurationError)) self.assertTrue(self.plugin_ep.prepared) self.assertTrue(self.plugin_ep.misconfigured) + self.assertTrue(isinstance(self.plugin_ep.problem, + errors.MisconfigurationError)) self.assertTrue(self.plugin_ep.available) def test_prepare_no_installation(self): diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index cb6c9af7e..a2a2f7f34 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -26,18 +26,19 @@ logger = logging.getLogger(__name__) class Authenticator(common.Plugin): """Manual Authenticator. - This plugin requires user's manual intervention in setting up a HTTP - server for solving SimpleHTTP challenges and thus does not need to be - run as a privilidged process. Alternatively shows instructions on how - to use Python's built-in HTTP server. + This plugin requires user's manual intervention in setting up a HTTP + server for solving http-01 challenges and thus does not need to be + run as a privileged process. Alternatively shows instructions on how + to use Python's built-in HTTP server. .. todo:: Support for `~.challenges.DVSNI`. + """ zope.interface.implements(interfaces.IAuthenticator) zope.interface.classProvides(interfaces.IPluginFactory) hidden = True - description = "Manually configure and run a simple Python webserver" + description = "Manually configure an HTTP server" MESSAGE_TEMPLATE = """\ Make sure your web server displays the following content at @@ -51,6 +52,15 @@ If you don't have HTTP server configured, you can run the following command on the target server (as root): {command} +""" + + # a disclaimer about your current IP being transmitted to Let's Encrypt's servers. + IP_DISCLAIMER = """\ +NOTE: The IP of this machine will be publicly logged as having requested this certificate. \ +If you're running letsencrypt in manual mode on a machine that is not your server, \ +please ensure you're okay with that. + +Are you OK with your IP being logged? """ # "cd /tmp/letsencrypt" makes sure user doesn't serve /root, @@ -59,9 +69,9 @@ command on the target server (as root): # anything recursively under the cwd CMD_TEMPLATE = """\ -mkdir -p {root}/public_html/{response.URI_ROOT_PATH} +mkdir -p {root}/public_html/{achall.URI_ROOT_PATH} cd {root}/public_html -echo -n {validation} > {response.URI_ROOT_PATH}/{encoded_token} +printf "%s" {validation} > {achall.URI_ROOT_PATH}/{encoded_token} # run only once per server: $(command -v python2 || command -v python2.7 || command -v python2.6) -c \\ "import BaseHTTPServer, SimpleHTTPServer; \\ @@ -86,14 +96,14 @@ s.serve_forever()" """ def more_info(self): # pylint: disable=missing-docstring,no-self-use return ("This plugin requires user's manual intervention in setting " - "up a HTTP server for solving SimpleHTTP challenges and thus " - "does not need to be run as a privilidged process. " + "up an HTTP server for solving http-01 challenges and thus " + "does not need to be run as a privileged process. " "Alternatively shows instructions on how to use Python's " "built-in HTTP server.") def get_chall_pref(self, domain): # pylint: disable=missing-docstring,no-self-use,unused-argument - return [challenges.SimpleHTTP] + return [challenges.HTTP01] def perform(self, achalls): # pylint: disable=missing-docstring responses = [] @@ -121,27 +131,26 @@ s.serve_forever()" """ # same path for each challenge response would be easier for # users, but will not work if multiple domains point at the # same server: default command doesn't support virtual hosts - response, validation = achall.gen_response_and_validation( - tls=False) # SimpleHTTP TLS is dead: ietf-wg-acme/acme#7 + response, validation = achall.response_and_validation() - port = (response.port if self.config.simple_http_port is None - else int(self.config.simple_http_port)) + port = (response.port if self.config.http01_port is None + else int(self.config.http01_port)) command = self.CMD_TEMPLATE.format( root=self._root, achall=achall, response=response, - validation=pipes.quote(validation.json_dumps()), + # TODO(kuba): pipes still necessary? + validation=pipes.quote(validation), encoded_token=achall.chall.encode("token"), - ct=response.CONTENT_TYPE, port=port) + ct=achall.CONTENT_TYPE, port=port) if self.conf("test-mode"): logger.debug("Test mode. Executing the manual command: %s", command) - # sh shipped with OS X does't support echo -n - executable = "/bin/bash" if sys.platform == "darwin" else None + # sh shipped with OS X does't support echo -n, but supports printf try: self._httpd = subprocess.Popen( command, # don't care about setting stdout and stderr, # we're in test mode anyway shell=True, - executable=executable, + executable=None, # "preexec_fn" is UNIX specific, but so is "command" preexec_fn=os.setsid) except OSError as error: # ValueError should not happen! @@ -155,14 +164,18 @@ s.serve_forever()" """ if self._httpd.poll() is not None: raise errors.Error("Couldn't execute manual command") else: + if not zope.component.getUtility(interfaces.IDisplay).yesno( + self.IP_DISCLAIMER, "Yes", "No"): + raise errors.PluginError("Must agree to IP logging to proceed") + self._notify_and_wait(self.MESSAGE_TEMPLATE.format( - validation=validation.json_dumps(), response=response, - uri=response.uri(achall.domain, achall.challb.chall), - ct=response.CONTENT_TYPE, command=command)) + validation=validation, response=response, + uri=achall.chall.uri(achall.domain), + ct=achall.CONTENT_TYPE, command=command)) if response.simple_verify( achall.chall, achall.domain, - achall.account_key.public_key(), self.config.simple_http_port): + achall.account_key.public_key(), self.config.http01_port): return response else: logger.error( diff --git a/letsencrypt/plugins/manual_test.py b/letsencrypt/plugins/manual_test.py index 8cfff1cc5..a9281902f 100644 --- a/letsencrypt/plugins/manual_test.py +++ b/letsencrypt/plugins/manual_test.py @@ -23,13 +23,13 @@ class AuthenticatorTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.manual import Authenticator self.config = mock.MagicMock( - simple_http_port=8080, manual_test_mode=False) + http01_port=8080, manual_test_mode=False) self.auth = Authenticator(config=self.config, name="manual") - self.achalls = [achallenges.SimpleHTTP( - challb=acme_util.SIMPLE_HTTP_P, domain="foo.com", account_key=KEY)] + self.achalls = [achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, domain="foo.com", account_key=KEY)] config_test_mode = mock.MagicMock( - simple_http_port=8080, manual_test_mode=True) + http01_port=8080, manual_test_mode=True) self.auth_test_mode = Authenticator( config=config_test_mode, name="manual") @@ -43,13 +43,15 @@ class AuthenticatorTest(unittest.TestCase): def test_perform_empty(self): self.assertEqual([], self.auth.perform([])) + @mock.patch("letsencrypt.plugins.manual.zope.component.getUtility") @mock.patch("letsencrypt.plugins.manual.sys.stdout") - @mock.patch("acme.challenges.SimpleHTTPResponse.simple_verify") + @mock.patch("acme.challenges.HTTP01Response.simple_verify") @mock.patch("__builtin__.raw_input") - def test_perform(self, mock_raw_input, mock_verify, mock_stdout): + def test_perform(self, mock_raw_input, mock_verify, mock_stdout, mock_interaction): mock_verify.return_value = True + mock_interaction().yesno.return_value = True - resp = challenges.SimpleHTTPResponse(tls=False) + resp = self.achalls[0].response(KEY) self.assertEqual([resp], self.auth.perform(self.achalls)) self.assertEqual(1, mock_raw_input.call_count) mock_verify.assert_called_with( @@ -61,6 +63,15 @@ class AuthenticatorTest(unittest.TestCase): mock_verify.return_value = False self.assertEqual([None], self.auth.perform(self.achalls)) + @mock.patch("letsencrypt.plugins.manual.zope.component.getUtility") + @mock.patch("letsencrypt.plugins.manual.Authenticator._notify_and_wait") + def test_disagree_with_ip_logging(self, mock_notify, mock_interaction): + mock_interaction().yesno.return_value = False + mock_notify.side_effect = errors.Error("Exception not raised, \ + continued execution even after disagreeing with IP logging") + + self.assertRaises(errors.PluginError, self.auth.perform, self.achalls) + @mock.patch("letsencrypt.plugins.manual.subprocess.Popen", autospec=True) def test_perform_test_command_oserror(self, mock_popen): mock_popen.side_effect = OSError @@ -78,7 +89,7 @@ class AuthenticatorTest(unittest.TestCase): @mock.patch("letsencrypt.plugins.manual.socket.socket") @mock.patch("letsencrypt.plugins.manual.time.sleep", autospec=True) - @mock.patch("acme.challenges.SimpleHTTPResponse.simple_verify", + @mock.patch("acme.challenges.HTTP01Response.simple_verify", autospec=True) @mock.patch("letsencrypt.plugins.manual.subprocess.Popen", autospec=True) def test_perform_test_mode(self, mock_popen, mock_verify, mock_sleep, diff --git a/letsencrypt/plugins/standalone.py b/letsencrypt/plugins/standalone.py index 6fcf62194..5041091e4 100644 --- a/letsencrypt/plugins/standalone.py +++ b/letsencrypt/plugins/standalone.py @@ -14,7 +14,6 @@ from acme import challenges from acme import crypto_util as acme_crypto_util from acme import standalone as acme_standalone -from letsencrypt import achallenges from letsencrypt import errors from letsencrypt import interfaces @@ -33,7 +32,7 @@ class ServerManager(object): `acme.crypto_util.SSLSocket.certs` and `acme.crypto_util.SSLSocket.simple_http_resources` respectively. All created servers share the same certificates and resources, so if - you're running both TLS and non-TLS instances, SimpleHTTP handlers + you're running both TLS and non-TLS instances, HTTP01 handlers will serve the same URLs! """ @@ -52,13 +51,13 @@ class ServerManager(object): :param int port: Port to run the server on. :param challenge_type: Subclass of `acme.challenges.Challenge`, - either `acme.challenge.SimpleHTTP` or `acme.challenges.DVSNI`. + either `acme.challenge.HTTP01` or `acme.challenges.DVSNI`. :returns: Server instance. :rtype: ACMEServerMixin """ - assert challenge_type in (challenges.DVSNI, challenges.SimpleHTTP) + assert challenge_type in (challenges.DVSNI, challenges.HTTP01) if port in self._instances: return self._instances[port].server @@ -66,13 +65,15 @@ class ServerManager(object): try: if challenge_type is challenges.DVSNI: server = acme_standalone.DVSNIServer(address, self.certs) - else: # challenges.SimpleHTTP - server = acme_standalone.SimpleHTTPServer( + else: # challenges.HTTP01 + server = acme_standalone.HTTP01Server( address, self.simple_http_resources) except socket.error as error: raise errors.StandaloneBindError(error, port) - thread = threading.Thread(target=server.serve_forever2) + thread = threading.Thread( + # pylint: disable=no-member + target=server.serve_forever) thread.start() # if port == 0, then random free port on OS is taken @@ -90,7 +91,7 @@ class ServerManager(object): instance = self._instances[port] logger.debug("Stopping server at %s:%d...", *instance.server.socket.getsockname()[:2]) - instance.server.shutdown2() + instance.server.shutdown() instance.thread.join() del self._instances[port] @@ -108,7 +109,7 @@ class ServerManager(object): in six.iteritems(self._instances)) -SUPPORTED_CHALLENGES = set([challenges.DVSNI, challenges.SimpleHTTP]) +SUPPORTED_CHALLENGES = set([challenges.DVSNI, challenges.HTTP01]) def supported_challenges_validator(data): @@ -137,22 +138,22 @@ class Authenticator(common.Plugin): """Standalone Authenticator. This authenticator creates its own ephemeral TCP listener on the - necessary port in order to respond to incoming DVSNI and SimpleHTTP + necessary port in order to respond to incoming DVSNI and HTTP01 challenges from the certificate authority. Therefore, it does not rely on any existing server program. """ zope.interface.implements(interfaces.IAuthenticator) zope.interface.classProvides(interfaces.IPluginFactory) - description = "Automatically configure and run a simple webserver" + description = "Automatically use a temporary webserver" def __init__(self, *args, **kwargs): super(Authenticator, self).__init__(*args, **kwargs) - # one self-signed key for all DVSNI and SimpleHTTP certificates + # one self-signed key for all DVSNI and HTTP01 certificates self.key = OpenSSL.crypto.PKey() self.key.generate_key(OpenSSL.crypto.TYPE_RSA, bits=2048) - # TODO: generate only when the first SimpleHTTP challenge is solved + # TODO: generate only when the first HTTP01 challenge is solved self.simple_http_cert = acme_crypto_util.gen_ss_cert( self.key, domains=["temp server"]) @@ -180,11 +181,20 @@ class Authenticator(common.Plugin): return set(challenges.Challenge.TYPES[name] for name in self.conf("supported-challenges").split(",")) + @property + def _necessary_ports(self): + necessary_ports = set() + if challenges.HTTP01 in self.supported_challenges: + necessary_ports.add(self.config.http01_port) + if challenges.DVSNI in self.supported_challenges: + necessary_ports.add(self.config.dvsni_port) + return necessary_ports + def more_info(self): # pylint: disable=missing-docstring return("This authenticator creates its own ephemeral TCP listener " - "on the necessary port in order to respond to incoming DVSNI " - "and SimpleHTTP challenges from the certificate authority. " - "Therefore, it does not rely on any existing server program.") + "on the necessary port in order to respond to incoming DVSNI " + "and HTTP01 challenges from the certificate authority. " + "Therefore, it does not rely on any existing server program.") def prepare(self): # pylint: disable=missing-docstring pass @@ -196,8 +206,7 @@ class Authenticator(common.Plugin): return chall_pref def perform(self, achalls): # pylint: disable=missing-docstring - if any(util.already_listening(port) for port in - (self.config.dvsni_port, self.config.simple_http_port)): + if any(util.already_listening(port) for port in self._necessary_ports): raise errors.MisconfigurationError( "At least one of the (possibly) required ports is " "already taken.") @@ -227,13 +236,12 @@ class Authenticator(common.Plugin): responses = [] for achall in achalls: - if isinstance(achall, achallenges.SimpleHTTP): + if isinstance(achall.chall, challenges.HTTP01): server = self.servers.run( - self.config.simple_http_port, challenges.SimpleHTTP) - response, validation = achall.gen_response_and_validation( - tls=False) + self.config.http01_port, challenges.HTTP01) + response, validation = achall.response_and_validation() self.simple_http_resources.add( - acme_standalone.SimpleHTTPRequestHandler.SimpleHTTPResource( + acme_standalone.HTTP01RequestHandler.HTTP01Resource( chall=achall.chall, response=response, validation=validation)) cert = self.simple_http_cert diff --git a/letsencrypt/plugins/standalone_test.py b/letsencrypt/plugins/standalone_test.py index 0ccdccb1f..15da04417 100644 --- a/letsencrypt/plugins/standalone_test.py +++ b/letsencrypt/plugins/standalone_test.py @@ -43,12 +43,12 @@ class ServerManagerTest(unittest.TestCase): self._test_run_stop(challenges.DVSNI) def test_run_stop_simplehttp(self): - self._test_run_stop(challenges.SimpleHTTP) + self._test_run_stop(challenges.HTTP01) def test_run_idempotent(self): - server = self.mgr.run(port=0, challenge_type=challenges.SimpleHTTP) + server = self.mgr.run(port=0, challenge_type=challenges.HTTP01) port = server.socket.getsockname()[1] # pylint: disable=no-member - server2 = self.mgr.run(port=port, challenge_type=challenges.SimpleHTTP) + server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01) self.assertEqual(self.mgr.running(), {port: server}) self.assertTrue(server is server2) self.mgr.stop(port) @@ -60,7 +60,7 @@ class ServerManagerTest(unittest.TestCase): port = some_server.getsockname()[1] self.assertRaises( errors.StandaloneBindError, self.mgr.run, port, - challenge_type=challenges.SimpleHTTP) + challenge_type=challenges.HTTP01) self.assertEqual(self.mgr.running(), {}) @@ -74,9 +74,9 @@ class SupportedChallengesValidatorTest(unittest.TestCase): def test_correct(self): self.assertEqual("dvsni", self._call("dvsni")) - self.assertEqual("simpleHttp", self._call("simpleHttp")) - self.assertEqual("dvsni,simpleHttp", self._call("dvsni,simpleHttp")) - self.assertEqual("simpleHttp,dvsni", self._call("simpleHttp,dvsni")) + self.assertEqual("http-01", self._call("http-01")) + self.assertEqual("dvsni,http-01", self._call("dvsni,http-01")) + self.assertEqual("http-01,dvsni", self._call("http-01,dvsni")) def test_unrecognized(self): assert "foo" not in challenges.Challenge.TYPES @@ -91,26 +91,32 @@ class AuthenticatorTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone import Authenticator - self.config = mock.MagicMock(dvsni_port=1234, simple_http_port=4321, - standalone_supported_challenges="dvsni,simpleHttp") + self.config = mock.MagicMock( + dvsni_port=1234, http01_port=4321, + standalone_supported_challenges="dvsni,http-01") self.auth = Authenticator(self.config, name="standalone") def test_supported_challenges(self): self.assertEqual(self.auth.supported_challenges, - set([challenges.DVSNI, challenges.SimpleHTTP])) + set([challenges.DVSNI, challenges.HTTP01])) def test_more_info(self): self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) def test_get_chall_pref(self): self.assertEqual(set(self.auth.get_chall_pref(domain=None)), - set([challenges.DVSNI, challenges.SimpleHTTP])) + set([challenges.DVSNI, challenges.HTTP01])) @mock.patch("letsencrypt.plugins.standalone.util") - def test_perform_misconfiguration(self, mock_util): - mock_util.already_listening.return_value = True - self.assertRaises(errors.MisconfigurationError, self.auth.perform, []) - mock_util.already_listening.assert_called_once_with(1234) + def test_perform_alredy_listening(self, mock_util): + for chall, port in ((challenges.DVSNI.typ, 1234), + (challenges.HTTP01.typ, 4321)): + mock_util.already_listening.return_value = True + self.config.standalone_supported_challenges = chall + self.assertRaises( + errors.MisconfigurationError, self.auth.perform, []) + mock_util.already_listening.assert_called_once_with(port) + mock_util.already_listening.reset_mock() @mock.patch("letsencrypt.plugins.standalone.zope.component.getUtility") def test_perform(self, unused_mock_get_utility): @@ -147,8 +153,8 @@ class AuthenticatorTest(unittest.TestCase): def test_perform2(self): domain = b'localhost' key = jose.JWK.load(test_util.load_vector('rsa512_key.pem')) - simple_http = achallenges.SimpleHTTP( - challb=acme_util.SIMPLE_HTTP_P, domain=domain, account_key=key) + simple_http = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, domain=domain, account_key=key) dvsni = achallenges.DVSNI( challb=acme_util.DVSNI_P, domain=domain, account_key=key) @@ -162,11 +168,11 @@ class AuthenticatorTest(unittest.TestCase): self.assertTrue(isinstance(responses, list)) self.assertEqual(2, len(responses)) - self.assertTrue(isinstance(responses[0], challenges.SimpleHTTPResponse)) + self.assertTrue(isinstance(responses[0], challenges.HTTP01Response)) self.assertTrue(isinstance(responses[1], challenges.DVSNIResponse)) self.assertEqual(self.auth.servers.run.mock_calls, [ - mock.call(4321, challenges.SimpleHTTP), + mock.call(4321, challenges.HTTP01), mock.call(1234, challenges.DVSNI), ]) self.assertEqual(self.auth.served, { @@ -176,8 +182,8 @@ class AuthenticatorTest(unittest.TestCase): self.assertEqual(1, len(self.auth.simple_http_resources)) self.assertEqual(2, len(self.auth.certs)) self.assertEqual(list(self.auth.simple_http_resources), [ - acme_standalone.SimpleHTTPRequestHandler.SimpleHTTPResource( - acme_util.SIMPLE_HTTP, responses[0], mock.ANY)]) + acme_standalone.HTTP01RequestHandler.HTTP01Resource( + acme_util.HTTP01, responses[0], mock.ANY)]) def test_cleanup(self): self.auth.servers = mock.Mock() diff --git a/letsencrypt/plugins/webroot.py b/letsencrypt/plugins/webroot.py index f11325f57..8fe89fd9f 100644 --- a/letsencrypt/plugins/webroot.py +++ b/letsencrypt/plugins/webroot.py @@ -1,4 +1,43 @@ -"""Webroot plugin.""" +"""Webroot plugin. + +Content-Type +------------ + +This plugin requires your webserver to use a specific `Content-Type` +header in the HTTP response. + +Apache2 +~~~~~~~ + +.. note:: Instructions written and tested for Debian Jessie. Other + operating systems might use something very similar, but you might + still need to readjust some commands. + +Create ``/etc/apache2/conf-available/letsencrypt-simplehttp.conf``, with +the following contents:: + + + + Header set Content-Type "application/jose+json" + + + +and then run ``a2enmod headers; a2enconf letsencrypt``; depending on the +output you will have to either ``service apache2 restart`` or ``service +apache2 reload``. + +nginx +~~~~~ + +Use the following snippet in your ``server{...}`` stanza:: + + location ~ /.well-known/acme-challenge/(.*) { + default_type application/jose+json; + } + +and reload your daemon. + +""" import errno import logging import os @@ -23,7 +62,7 @@ class Authenticator(common.Plugin): description = "Webroot Authenticator" MORE_INFO = """\ -Authenticator plugin that performs SimpleHTTP challenge by saving +Authenticator plugin that performs http-01 challenge by saving necessary validation resources to appropriate paths on the file system. It expects that there is some other HTTP server configured to serve all files under specified web root ({0}).""" @@ -37,7 +76,7 @@ to serve all files under specified web root ({0}).""" def get_chall_pref(self, domain): # pragma: no cover # pylint: disable=missing-docstring,no-self-use,unused-argument - return [challenges.SimpleHTTP] + return [challenges.HTTP01] def __init__(self, *args, **kwargs): super(Authenticator, self).__init__(*args, **kwargs) @@ -51,8 +90,7 @@ to serve all files under specified web root ({0}).""" if not os.path.isdir(path): raise errors.PluginError( path + " does not exist or is not a directory") - self.full_root = os.path.join( - path, challenges.SimpleHTTPResponse.URI_ROOT_PATH) + self.full_root = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH) logger.debug("Creating root challenges validation dir at %s", self.full_root) @@ -61,7 +99,7 @@ to serve all files under specified web root ({0}).""" except OSError as exception: if exception.errno != errno.EEXIST: raise errors.PluginError( - "Couldn't create root for SimpleHTTP " + "Couldn't create root for http-01 " "challenge responses: {0}", exception) def perform(self, achalls): # pylint: disable=missing-docstring @@ -72,11 +110,11 @@ to serve all files under specified web root ({0}).""" return os.path.join(self.full_root, achall.chall.encode("token")) def _perform_single(self, achall): - response, validation = achall.gen_response_and_validation(tls=False) + response, validation = achall.response_and_validation() path = self._path_for_achall(achall) logger.debug("Attempting to save validation to %s", path) with open(path, "w") as validation_file: - validation_file.write(validation.json_dumps()) + validation_file.write(validation.encode()) return response def cleanup(self, achalls): # pylint: disable=missing-docstring diff --git a/letsencrypt/plugins/webroot_test.py b/letsencrypt/plugins/webroot_test.py index d8c0e2aa2..aa8f16e38 100644 --- a/letsencrypt/plugins/webroot_test.py +++ b/letsencrypt/plugins/webroot_test.py @@ -6,6 +6,7 @@ import unittest import mock +from acme import challenges from acme import jose from letsencrypt import achallenges @@ -21,8 +22,8 @@ KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) class AuthenticatorTest(unittest.TestCase): """Tests for letsencrypt.plugins.webroot.Authenticator.""" - achall = achallenges.SimpleHTTP( - challb=acme_util.SIMPLE_HTTP_P, domain=None, account_key=KEY) + achall = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, domain=None, account_key=KEY) def setUp(self): from letsencrypt.plugins.webroot import Authenticator @@ -70,9 +71,11 @@ class AuthenticatorTest(unittest.TestCase): self.assertEqual(1, len(responses)) self.assertTrue(os.path.exists(self.validation_path)) with open(self.validation_path) as validation_f: - validation = jose.JWS.json_loads(validation_f.read()) - self.assertTrue(responses[0].check_validation( - validation, self.achall.chall, KEY.public_key())) + validation = validation_f.read() + self.assertTrue( + challenges.KeyAuthorizationChallengeResponse( + key_authorization=validation).verify( + self.achall.chall, KEY.public_key())) self.auth.cleanup([self.achall]) self.assertFalse(os.path.exists(self.validation_path)) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 33492f344..40e49702a 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -12,7 +12,6 @@ import logging import os import sys -import configobj import OpenSSL import zope.component @@ -77,7 +76,7 @@ def renew(cert, old_version): # was an int, not a str) config.rsa_key_size = int(config.rsa_key_size) config.dvsni_port = int(config.dvsni_port) - config.namespace.simple_http_port = int(config.namespace.simple_http_port) + config.namespace.http01_port = int(config.namespace.http01_port) zope.component.provideUtility(config) try: authenticator = plugins[renewalparams["authenticator"]] @@ -142,7 +141,7 @@ def _create_parser(): return _paths_parser(parser) -def main(config=None, cli_args=sys.argv[1:]): +def main(cli_args=sys.argv[1:]): """Main function for autorenewer script.""" # TODO: Distinguish automated invocation from manual invocation, # perhaps by looking at sys.argv[0] and inhibiting automated @@ -150,6 +149,11 @@ def main(config=None, cli_args=sys.argv[1:]): # turned it off. (The boolean parameter should probably be # called renewer_enabled.) + # TODO: When we have a more elaborate renewer command line, we will + # presumably also be able to specify a config file on the + # command line, which, if provided, should take precedence over + # te default config files + zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) args = _create_parser().parse_args(cli_args) @@ -160,27 +164,12 @@ def main(config=None, cli_args=sys.argv[1:]): cli_config = configuration.RenewerConfiguration(args) - config = storage.config_with_defaults(config) - # Now attempt to read the renewer config file and augment or replace - # the renewer defaults with any options contained in that file. If - # renewer_config_file is undefined or if the file is nonexistent or - # empty, this .merge() will have no effect. TODO: when we have a more - # elaborate renewer command line, we will presumably also be able to - # specify a config file on the command line, which, if provided, should - # take precedence over this one. - config.merge(configobj.ConfigObj(cli_config.renewer_config_file)) # Ensure that all of the needed folders have been created before continuing le_util.make_or_verify_dir(cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) - for i in os.listdir(cli_config.renewal_configs_dir): - print "Processing", i - if not i.endswith(".conf"): - continue - rc_config = configobj.ConfigObj(cli_config.renewer_config_file) - rc_config.merge(configobj.ConfigObj( - os.path.join(cli_config.renewal_configs_dir, i))) - rc_config.filename = os.path.join(cli_config.renewal_configs_dir, i) + for renewal_file in os.listdir(cli_config.renewal_configs_dir): + print "Processing", renewal_file try: # TODO: Before trying to initialize the RenewableCert object, # we could check here whether the combination of the config @@ -190,7 +179,7 @@ def main(config=None, cli_args=sys.argv[1:]): # RenewableCert object for this cert at all, which could # dramatically improve performance for large deployments # where autorenewal is widely turned off. - cert = storage.RenewableCert(rc_config, cli_config=cli_config) + cert = storage.RenewableCert(renewal_file, cli_config) except errors.CertStorageError: # This indicates an invalid renewal configuration file, such # as one missing a required parameter (in the future, perhaps diff --git a/letsencrypt/storage.py b/letsencrypt/storage.py index 12411ffb4..52be94f68 100644 --- a/letsencrypt/storage.py +++ b/letsencrypt/storage.py @@ -2,7 +2,6 @@ import datetime import os import re -import time import configobj import parsedatetime @@ -11,6 +10,7 @@ import pytz from letsencrypt import constants from letsencrypt import crypto_util from letsencrypt import errors +from letsencrypt import error_handler from letsencrypt import le_util ALL_FOUR = ("cert", "privkey", "chain", "fullchain") @@ -23,8 +23,8 @@ def config_with_defaults(config=None): return defaults_copy -def parse_time_interval(interval, textparser=parsedatetime.Calendar()): - """Parse the time specified time interval. +def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()): + """Parse the time specified time interval, and add it to the base_time The interval can be in the English-language format understood by parsedatetime, e.g., '10 days', '3 weeks', '6 months', '9 hours', or @@ -32,15 +32,19 @@ def parse_time_interval(interval, textparser=parsedatetime.Calendar()): hours'. If an integer is found with no associated unit, it is interpreted by default as a number of days. + :param datetime.datetime base_time: The time to be added with the interval. :param str interval: The time interval to parse. - :returns: The interpretation of the time interval. - :rtype: :class:`datetime.timedelta`""" + :returns: The base_time plus the interpretation of the time interval. + :rtype: :class:`datetime.datetime`""" if interval.strip().isdigit(): interval += " days" - return datetime.timedelta(0, time.mktime(textparser.parse( - interval, time.localtime(0))[0])) + + # try to use the same timezone, but fallback to UTC + tzinfo = base_time.tzinfo or pytz.UTC + + return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0] class RenewableCert(object): # pylint: disable=too-many-instance-attributes @@ -78,55 +82,50 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes renewal configuration file and/or systemwide defaults. """ - def __init__(self, configfile, config_opts=None, cli_config=None): + def __init__(self, config_filename, cli_config): """Instantiate a RenewableCert object from an existing lineage. - :param configobj.ConfigObj configfile: an already-parsed - ConfigObj object made from reading the renewal config file + :param str config_filename: the path to the renewal config file that defines this lineage. - - :param configobj.ConfigObj config_opts: systemwide defaults for - renewal properties not otherwise specified in the individual - renewal config file. - :param .RenewerConfiguration cli_config: + :param .RenewerConfiguration: parsed command line arguments :raises .CertStorageError: if the configuration file's name didn't end in ".conf", or the file is missing or broken. - :raises TypeError: if the provided renewal configuration isn't a - ConfigObj object. """ self.cli_config = cli_config - if isinstance(configfile, configobj.ConfigObj): - if not os.path.basename(configfile.filename).endswith(".conf"): - raise errors.CertStorageError( - "renewal config file name must end in .conf") - self.lineagename = os.path.basename( - configfile.filename)[:-len(".conf")] - else: - raise TypeError("RenewableCert config must be ConfigObj object") + if not config_filename.endswith(".conf"): + raise errors.CertStorageError( + "renewal config file name must end in .conf") + self.lineagename = os.path.basename( + config_filename[:-len(".conf")]) # self.configuration should be used to read parameters that # may have been chosen based on default values from the # systemwide renewal configuration; self.configfile should be # used to make and save changes. - self.configfile = configfile + try: + self.configfile = configobj.ConfigObj(config_filename) + except configobj.ConfigObjError: + raise errors.CertStorageError( + "error parsing {0}".format(config_filename)) # TODO: Do we actually use anything from defaults and do we want to # read further defaults from the systemwide renewal configuration # file at this stage? - self.configuration = config_with_defaults(config_opts) - self.configuration.merge(self.configfile) + self.configuration = config_with_defaults(self.configfile) if not all(x in self.configuration for x in ALL_FOUR): raise errors.CertStorageError( "renewal config file {0} is missing a required " - "file reference".format(configfile)) + "file reference".format(self.configfile)) self.cert = self.configuration["cert"] self.privkey = self.configuration["privkey"] self.chain = self.configuration["chain"] self.fullchain = self.configuration["fullchain"] + self._fix_symlinks() + def _consistent(self): """Are the files associated with this lineage self-consistent? @@ -203,6 +202,40 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes # happen as a result of random tampering by a sysadmin, or # filesystem errors, or crashes.) + def _previous_symlinks(self): + """Returns the kind and path of all symlinks used in recovery. + + :returns: list of (kind, symlink) tuples + :rtype: list + + """ + previous_symlinks = [] + for kind in ALL_FOUR: + link_dir = os.path.dirname(getattr(self, kind)) + link_base = "previous_{0}.pem".format(kind) + previous_symlinks.append((kind, os.path.join(link_dir, link_base))) + + return previous_symlinks + + def _fix_symlinks(self): + """Fixes symlinks in the event of an incomplete version update. + + If there is no problem with the current symlinks, this function + has no effect. + + """ + previous_symlinks = self._previous_symlinks() + if all(os.path.exists(link[1]) for link in previous_symlinks): + for kind, previous_link in previous_symlinks: + current_link = getattr(self, kind) + if os.path.lexists(current_link): + os.unlink(current_link) + os.symlink(os.readlink(previous_link), current_link) + + for _, link in previous_symlinks: + if os.path.exists(link): + os.unlink(link) + def current_target(self, kind): """Returns full path to which the specified item currently points. @@ -374,10 +407,19 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes def update_all_links_to(self, version): """Change all member objects to point to the specified version. - :param int version: the desired version""" + :param int version: the desired version - for kind in ALL_FOUR: - self._update_link_to(kind, version) + """ + with error_handler.ErrorHandler(self._fix_symlinks): + previous_links = self._previous_symlinks() + for kind, link in previous_links: + os.symlink(self.current_target(kind), link) + + for kind in ALL_FOUR: + self._update_link_to(kind, version) + + for _, link in previous_links: + os.unlink(link) def names(self, version=None): """What are the subject names of this certificate? @@ -426,11 +468,9 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes if self.has_pending_deployment(): interval = self.configuration.get("deploy_before_expiry", "5 days") - autodeploy_interval = parse_time_interval(interval) expiry = crypto_util.notAfter(self.current_target("cert")) - now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC) - remaining = expiry - now - if remaining < autodeploy_interval: + now = pytz.UTC.fromutc(datetime.datetime.utcnow()) + if expiry < add_time_interval(now, interval): return True return False @@ -493,12 +533,10 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes # Renewals on the basis of expiry time interval = self.configuration.get("renew_before_expiry", "10 days") - autorenew_interval = parse_time_interval(interval) expiry = crypto_util.notAfter(self.version( "cert", self.latest_common_version())) - now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC) - remaining = expiry - now - if remaining < autorenew_interval: + now = pytz.UTC.fromutc(datetime.datetime.utcnow()) + if expiry < add_time_interval(now, interval): return True return False @@ -532,6 +570,8 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes :param configobj.ConfigObj config: renewal configuration defaults, affecting, for example, the locations of the directories where the associated files will be saved + :param .RenewerConfiguration cli_config: parsed command line + arguments :returns: the newly-created RenewalCert object :rtype: :class:`storage.renewableCert`""" @@ -601,7 +641,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes # TODO: add human-readable comments explaining other available # parameters new_config.write() - return cls(new_config, config, cli_config) + return cls(new_config.filename, cli_config) def save_successor(self, prior_version, new_cert, new_privkey, new_chain): """Save new cert and chain as a successor of a prior version. diff --git a/letsencrypt/tests/acme_util.py b/letsencrypt/tests/acme_util.py index 235810435..300eb453b 100644 --- a/letsencrypt/tests/acme_util.py +++ b/letsencrypt/tests/acme_util.py @@ -12,7 +12,7 @@ from letsencrypt.tests import test_util KEY = test_util.load_rsa_private_key('rsa512_key.pem') # Challenges -SIMPLE_HTTP = challenges.SimpleHTTP( +HTTP01 = challenges.HTTP01( token="evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA") DVSNI = challenges.DVSNI( token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA")) @@ -41,7 +41,7 @@ POP = challenges.ProofOfPossession( ) ) -CHALLENGES = [SIMPLE_HTTP, DVSNI, DNS, RECOVERY_CONTACT, POP] +CHALLENGES = [HTTP01, DVSNI, DNS, RECOVERY_CONTACT, POP] DV_CHALLENGES = [chall for chall in CHALLENGES if isinstance(chall, challenges.DVChallenge)] CONT_CHALLENGES = [chall for chall in CHALLENGES @@ -80,12 +80,12 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name # Pending ChallengeBody objects DVSNI_P = chall_to_challb(DVSNI, messages.STATUS_PENDING) -SIMPLE_HTTP_P = chall_to_challb(SIMPLE_HTTP, messages.STATUS_PENDING) +HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING) DNS_P = chall_to_challb(DNS, messages.STATUS_PENDING) RECOVERY_CONTACT_P = chall_to_challb(RECOVERY_CONTACT, messages.STATUS_PENDING) POP_P = chall_to_challb(POP, messages.STATUS_PENDING) -CHALLENGES_P = [SIMPLE_HTTP_P, DVSNI_P, DNS_P, RECOVERY_CONTACT_P, POP_P] +CHALLENGES_P = [HTTP01_P, DVSNI_P, DNS_P, RECOVERY_CONTACT_P, POP_P] DV_CHALLENGES_P = [challb for challb in CHALLENGES_P if isinstance(challb.chall, challenges.DVChallenge)] CONT_CHALLENGES_P = [ diff --git a/letsencrypt/tests/auth_handler_test.py b/letsencrypt/tests/auth_handler_test.py index 18ee56081..7be37c91e 100644 --- a/letsencrypt/tests/auth_handler_test.py +++ b/letsencrypt/tests/auth_handler_test.py @@ -9,21 +9,13 @@ from acme import challenges from acme import client as acme_client from acme import messages +from letsencrypt import achallenges from letsencrypt import errors from letsencrypt import le_util from letsencrypt.tests import acme_util -TRANSLATE = { - "dvsni": "DVSNI", - "simpleHttp": "SimpleHTTP", - "dns": "DNS", - "recoveryContact": "RecoveryContact", - "proofOfPossession": "ProofOfPossession", -} - - class ChallengeFactoryTest(unittest.TestCase): # pylint: disable=protected-access @@ -283,6 +275,22 @@ class PollChallengesTest(unittest.TestCase): return (new_authzr, "response") +class ChallbToAchallTest(unittest.TestCase): + """Tests for letsencrypt.auth_handler.challb_to_achall.""" + + def _call(self, challb): + from letsencrypt.auth_handler import challb_to_achall + return challb_to_achall(challb, "account_key", "domain") + + def test_it(self): + self.assertEqual( + self._call(acme_util.HTTP01_P), + achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.HTTP01_P, account_key="account_key", + domain="domain"), + ) + + class GenChallengePathTest(unittest.TestCase): """Tests for letsencrypt.auth_handler.gen_challenge_path. @@ -301,8 +309,8 @@ class GenChallengePathTest(unittest.TestCase): return gen_challenge_path(challbs, preferences, combinations) def test_common_case(self): - """Given DVSNI and SimpleHTTP with appropriate combos.""" - challbs = (acme_util.DVSNI_P, acme_util.SIMPLE_HTTP_P) + """Given DVSNI and HTTP01 with appropriate combos.""" + challbs = (acme_util.DVSNI_P, acme_util.HTTP01_P) prefs = [challenges.DVSNI] combos = ((0,), (1,)) @@ -317,7 +325,7 @@ class GenChallengePathTest(unittest.TestCase): challbs = (acme_util.POP_P, acme_util.RECOVERY_CONTACT_P, acme_util.DVSNI_P, - acme_util.SIMPLE_HTTP_P) + acme_util.HTTP01_P) prefs = [challenges.ProofOfPossession, challenges.DVSNI] combos = acme_util.gen_combos(challbs) self.assertEqual(self._call(challbs, prefs, combos), (0, 2)) @@ -329,12 +337,12 @@ class GenChallengePathTest(unittest.TestCase): challbs = (acme_util.RECOVERY_CONTACT_P, acme_util.POP_P, acme_util.DVSNI_P, - acme_util.SIMPLE_HTTP_P, + acme_util.HTTP01_P, acme_util.DNS_P) # Typical webserver client that can do everything except DNS # Attempted to make the order realistic prefs = [challenges.ProofOfPossession, - challenges.SimpleHTTP, + challenges.HTTP01, challenges.DVSNI, challenges.RecoveryContact] combos = acme_util.gen_combos(challbs) @@ -403,8 +411,8 @@ class IsPreferredTest(unittest.TestCase): def _call(cls, chall, satisfied): from letsencrypt.auth_handler import is_preferred return is_preferred(chall, satisfied, exclusive_groups=frozenset([ - frozenset([challenges.DVSNI, challenges.SimpleHTTP]), - frozenset([challenges.DNS, challenges.SimpleHTTP]), + frozenset([challenges.DVSNI, challenges.HTTP01]), + frozenset([challenges.DNS, challenges.HTTP01]), ])) def test_empty_satisfied(self): @@ -413,7 +421,7 @@ class IsPreferredTest(unittest.TestCase): def test_mutually_exclusvie(self): self.assertFalse( self._call( - acme_util.DVSNI_P, frozenset([acme_util.SIMPLE_HTTP_P]))) + acme_util.DVSNI_P, frozenset([acme_util.HTTP01_P]))) def test_mutually_exclusive_same_type(self): self.assertTrue( @@ -425,16 +433,14 @@ class ReportFailedChallsTest(unittest.TestCase): # pylint: disable=protected-access def setUp(self): - from letsencrypt import achallenges - kwargs = { - "chall": acme_util.SIMPLE_HTTP, + "chall": acme_util.HTTP01, "uri": "uri", "status": messages.STATUS_INVALID, "error": messages.Error(typ="tls", detail="detail"), } - self.simple_http = achallenges.SimpleHTTP( + self.http01 = achallenges.KeyAuthorizationAnnotatedChallenge( # pylint: disable=star-args challb=messages.ChallengeBody(**kwargs), domain="example.com", @@ -458,7 +464,7 @@ class ReportFailedChallsTest(unittest.TestCase): def test_same_error_and_domain(self, mock_zope): from letsencrypt import auth_handler - auth_handler._report_failed_challs([self.simple_http, self.dvsni_same]) + auth_handler._report_failed_challs([self.http01, self.dvsni_same]) call_list = mock_zope().add_message.call_args_list self.assertTrue(len(call_list) == 1) self.assertTrue("Domains: example.com\n" in call_list[0][0][0]) @@ -467,7 +473,7 @@ class ReportFailedChallsTest(unittest.TestCase): def test_different_errors_and_domains(self, mock_zope): from letsencrypt import auth_handler - auth_handler._report_failed_challs([self.simple_http, self.dvsni_diff]) + auth_handler._report_failed_challs([self.http01, self.dvsni_diff]) self.assertTrue(mock_zope().add_message.call_count == 2) diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index 9d9164f24..d6d056777 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -60,39 +60,66 @@ class CLITest(unittest.TestCase): return ret, None, stderr, client def test_no_flags(self): - with mock.patch('letsencrypt.cli.run') as mock_run: + with MockedVerb("run") as mock_run: self._call([]) self.assertEqual(1, mock_run.call_count) + def _help_output(self, args): + "Run a help command, and return the help string for scrutiny" + output = StringIO.StringIO() + with mock.patch('letsencrypt.cli.sys.stdout', new=output): + self.assertRaises(SystemExit, self._call_stdout, args) + out = output.getvalue() + return out + def test_help(self): self.assertRaises(SystemExit, self._call, ['--help']) self.assertRaises(SystemExit, self._call, ['--help', 'all']) - output = StringIO.StringIO() - with mock.patch('letsencrypt.cli.sys.stdout', new=output): - self.assertRaises(SystemExit, self._call_stdout, ['--help', 'all']) - out = output.getvalue() - self.assertTrue("--configurator" in out) - self.assertTrue("how a cert is deployed" in out) - self.assertTrue("--manual-test-mode" in out) - output.truncate(0) - self.assertRaises(SystemExit, self._call_stdout, ['-h', 'nginx']) - out = output.getvalue() - if "nginx" in disco.PluginsRegistry.find_all(): - # may be false while building distributions without plugins - self.assertTrue("--nginx-ctl" in out) - self.assertTrue("--manual-test-mode" not in out) - self.assertTrue("--checkpoints" not in out) - output.truncate(0) - self.assertRaises(SystemExit, self._call_stdout, ['--help', 'plugins']) - out = output.getvalue() - self.assertTrue("--manual-test-mode" not in out) - self.assertTrue("--prepare" in out) - self.assertTrue("Plugin options" in out) - output.truncate(0) - self.assertRaises(SystemExit, self._call_stdout, ['-h']) - out = output.getvalue() - from letsencrypt import cli - self.assertTrue(cli.USAGE in out) + plugins = disco.PluginsRegistry.find_all() + out = self._help_output(['--help', 'all']) + self.assertTrue("--configurator" in out) + self.assertTrue("how a cert is deployed" in out) + self.assertTrue("--manual-test-mode" in out) + + out = self._help_output(['-h', 'nginx']) + if "nginx" in plugins: + # may be false while building distributions without plugins + self.assertTrue("--nginx-ctl" in out) + self.assertTrue("--manual-test-mode" not in out) + self.assertTrue("--checkpoints" not in out) + + out = self._help_output(['-h']) + if "nginx" in plugins: + self.assertTrue("Use the Nginx plugin" in out) + else: + self.assertTrue("(nginx support is experimental" in out) + + out = self._help_output(['--help', 'plugins']) + self.assertTrue("--manual-test-mode" not in out) + self.assertTrue("--prepare" in out) + self.assertTrue("Plugin options" in out) + + out = self._help_output(['--help', 'install']) + self.assertTrue("--cert-path" in out) + self.assertTrue("--key-path" in out) + + out = self._help_output(['--help', 'revoke']) + self.assertTrue("--cert-path" in out) + self.assertTrue("--key-path" in out) + + out = self._help_output(['-h', 'config_changes']) + self.assertTrue("--cert-path" not in out) + self.assertTrue("--key-path" not in out) + + out = self._help_output(['-h']) + from letsencrypt import cli + self.assertTrue(cli.usage_strings(plugins)[0] in out) + + @mock.patch('letsencrypt.cli.display_ops') + def test_installer_selection(self, mock_display_ops): + self._call(['install', '--domain', 'foo.bar', '--cert-path', 'cert', + '--key-path', 'key', '--chain-path', 'chain']) + self.assertEqual(mock_display_ops.pick_installer.call_count, 1) def test_configurator_selection(self): real_plugins = disco.PluginsRegistry.find_all() @@ -115,6 +142,12 @@ class CLITest(unittest.TestCase): # (we can only do that if letsencrypt-nginx is actually present) ret, _, _, _ = self._call(args) self.assertTrue("The nginx plugin is not working" in ret) + self.assertTrue("Could not find configuration root" in ret) + self.assertTrue("NoInstallationError" in ret) + + with MockedVerb("certonly") as mock_certonly: + self._call(["auth", "--standalone"]) + self.assertEqual(1, mock_certonly.call_count) def test_rollback(self): _, _, _, client = self._call(['rollback']) @@ -135,24 +168,24 @@ class CLITest(unittest.TestCase): for r in xrange(len(flags)))): self._call(['plugins'] + list(args)) - def test_auth_bad_args(self): - ret, _, _, _ = self._call(['-d', 'foo.bar', 'auth', '--csr', CSR]) + def test_certonly_bad_args(self): + ret, _, _, _ = self._call(['-d', 'foo.bar', 'certonly', '--csr', CSR]) self.assertEqual(ret, '--domains and --csr are mutually exclusive') - ret, _, _, _ = self._call(['-a', 'bad_auth', 'auth']) + ret, _, _, _ = self._call(['-a', 'bad_auth', 'certonly']) self.assertEqual(ret, 'The requested bad_auth plugin does not appear to be installed') @mock.patch('letsencrypt.crypto_util.notAfter') @mock.patch('letsencrypt.cli.zope.component.getUtility') - def test_auth_new_request_success(self, mock_get_utility, mock_notAfter): + def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter): cert_path = '/etc/letsencrypt/live/foo.bar' date = '1970-01-01' mock_notAfter().date.return_value = date - mock_lineage = mock.MagicMock(cert=cert_path) + mock_lineage = mock.MagicMock(cert=cert_path, fullchain=cert_path) mock_client = mock.MagicMock() mock_client.obtain_and_enroll_certificate.return_value = mock_lineage - self._auth_new_request_common(mock_client) + self._certonly_new_request_common(mock_client) self.assertEqual( mock_client.obtain_and_enroll_certificate.call_count, 1) self.assertTrue( @@ -160,26 +193,27 @@ class CLITest(unittest.TestCase): self.assertTrue( date in mock_get_utility().add_message.call_args[0][0]) - def test_auth_new_request_failure(self): + def test_certonly_new_request_failure(self): mock_client = mock.MagicMock() mock_client.obtain_and_enroll_certificate.return_value = False self.assertRaises(errors.Error, - self._auth_new_request_common, mock_client) + self._certonly_new_request_common, mock_client) - def _auth_new_request_common(self, mock_client): + def _certonly_new_request_common(self, mock_client): with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal: mock_renewal.return_value = None with mock.patch('letsencrypt.cli._init_le_client') as mock_init: mock_init.return_value = mock_client - self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth']) + self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly']) @mock.patch('letsencrypt.cli.zope.component.getUtility') @mock.patch('letsencrypt.cli._treat_as_renewal') @mock.patch('letsencrypt.cli._init_le_client') - def test_auth_renewal(self, mock_init, mock_renewal, mock_get_utility): - cert_path = '/etc/letsencrypt/live/foo.bar' + def test_certonly_renewal(self, mock_init, mock_renewal, mock_get_utility): + cert_path = '/etc/letsencrypt/live/foo.bar/cert.pem' + chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem' - mock_lineage = mock.MagicMock(cert=cert_path) + mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path) mock_cert = mock.MagicMock(body='body') mock_key = mock.MagicMock(pem='pem_key') mock_renewal.return_value = mock_lineage @@ -189,37 +223,38 @@ class CLITest(unittest.TestCase): mock_init.return_value = mock_client with mock.patch('letsencrypt.cli.OpenSSL'): with mock.patch('letsencrypt.cli.crypto_util'): - self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth']) + self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly']) mock_client.obtain_certificate.assert_called_once_with(['foo.bar']) self.assertEqual(mock_lineage.save_successor.call_count, 1) mock_lineage.update_all_links_to.assert_called_once_with( mock_lineage.latest_common_version()) self.assertTrue( - cert_path in mock_get_utility().add_message.call_args[0][0]) + chain_path in mock_get_utility().add_message.call_args[0][0]) @mock.patch('letsencrypt.crypto_util.notAfter') @mock.patch('letsencrypt.cli.display_ops.pick_installer') @mock.patch('letsencrypt.cli.zope.component.getUtility') @mock.patch('letsencrypt.cli._init_le_client') - def test_auth_csr(self, mock_init, mock_get_utility, - mock_pick_installer, mock_notAfter): - cert_path = '/etc/letsencrypt/live/foo.bar' + def test_certonly_csr(self, mock_init, mock_get_utility, + mock_pick_installer, mock_notAfter): + cert_path = '/etc/letsencrypt/live/blahcert.pem' date = '1970-01-01' mock_notAfter().date.return_value = date mock_client = mock.MagicMock() mock_client.obtain_certificate_from_csr.return_value = ('certr', 'chain') - mock_client.save_certificate.return_value = cert_path, None + mock_client.save_certificate.return_value = cert_path, None, None mock_init.return_value = mock_client installer = 'installer' self._call( - ['-a', 'standalone', '-i', installer, 'auth', '--csr', CSR, - '--cert-path', cert_path, '--chain-path', '/']) + ['-a', 'standalone', '-i', installer, 'certonly', '--csr', CSR, + '--cert-path', cert_path, '--fullchain-path', '/', + '--chain-path', '/']) self.assertEqual(mock_pick_installer.call_args[0][1], installer) mock_client.save_certificate.assert_called_once_with( - 'certr', 'chain', cert_path, '/') + 'certr', 'chain', cert_path, '/', '/') self.assertTrue( cert_path in mock_get_utility().add_message.call_args[0][0]) self.assertTrue( @@ -344,28 +379,58 @@ class DuplicativeCertsTest(renewer_test.BaseRenewableCertTest): f.write(test_cert) # No overlap at all - result = _find_duplicative_certs(['wow.net', 'hooray.org'], - self.config, self.cli_config) + result = _find_duplicative_certs( + self.cli_config, ['wow.net', 'hooray.org']) self.assertEqual(result, (None, None)) # Totally identical - result = _find_duplicative_certs(['example.com', 'www.example.com'], - self.config, self.cli_config) + result = _find_duplicative_certs( + self.cli_config, ['example.com', 'www.example.com']) self.assertTrue(result[0].configfile.filename.endswith('example.org.conf')) self.assertEqual(result[1], None) # Superset - result = _find_duplicative_certs(['example.com', 'www.example.com', - 'something.new'], self.config, - self.cli_config) + result = _find_duplicative_certs( + self.cli_config, ['example.com', 'www.example.com', 'something.new']) self.assertEqual(result[0], None) self.assertTrue(result[1].configfile.filename.endswith('example.org.conf')) # Partial overlap doesn't count - result = _find_duplicative_certs(['example.com', 'something.new'], - self.config, self.cli_config) + result = _find_duplicative_certs( + self.cli_config, ['example.com', 'something.new']) self.assertEqual(result, (None, None)) +class MockedVerb(object): + """Simple class that can be used for mocking out verbs/subcommands. + + Storing a dictionary of verbs and the functions that implement them + in letsencrypt.cli makes mocking much more complicated. This class + can be used as a simple context manager for mocking out verbs in CLI + tests. For example: + + with MockedVerb("run") as mock_run: + self._call([]) + self.assertEqual(1, mock_run.call_count) + + """ + def __init__(self, verb_name): + from letsencrypt import cli + + self.verb_dict = cli.HelpfulArgumentParser.VERBS + self.verb_func = None + self.verb_name = verb_name + + def __enter__(self): + self.verb_func = self.verb_dict[self.verb_name] + mocked_func = mock.MagicMock() + self.verb_dict[self.verb_name] = mocked_func + + return mocked_func + + def __exit__(self, unused_type, unused_value, unused_trace): + self.verb_dict[self.verb_name] = self.verb_func + + if __name__ == '__main__': unittest.main() # pragma: no cover diff --git a/letsencrypt/tests/client_test.py b/letsencrypt/tests/client_test.py index 0f8b2df61..b2ebe74f0 100644 --- a/letsencrypt/tests/client_test.py +++ b/letsencrypt/tests/client_test.py @@ -120,18 +120,22 @@ class ClientTest(unittest.TestCase): os.chmod(tmp_path, 0o755) # TODO: really?? certr = mock.MagicMock(body=test_util.load_cert(certs[0])) - cert1 = test_util.load_cert(certs[1]) - cert2 = test_util.load_cert(certs[2]) + chain_cert = [test_util.load_cert(certs[1]), + test_util.load_cert(certs[2])] candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem") candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem") + candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem") - cert_path, chain_path = self.client.save_certificate( - certr, [cert1, cert2], candidate_cert_path, candidate_chain_path) + cert_path, chain_path, fullchain_path = self.client.save_certificate( + certr, chain_cert, candidate_cert_path, candidate_chain_path, + candidate_fullchain_path) self.assertEqual(os.path.dirname(cert_path), os.path.dirname(candidate_cert_path)) self.assertEqual(os.path.dirname(chain_path), os.path.dirname(candidate_chain_path)) + self.assertEqual(os.path.dirname(fullchain_path), + os.path.dirname(candidate_fullchain_path)) with open(cert_path, "r") as cert_file: cert_contents = cert_file.read() diff --git a/letsencrypt/tests/configuration_test.py b/letsencrypt/tests/configuration_test.py index 44bccb577..c7e227ee5 100644 --- a/letsencrypt/tests/configuration_test.py +++ b/letsencrypt/tests/configuration_test.py @@ -14,7 +14,7 @@ class NamespaceConfigTest(unittest.TestCase): self.namespace = mock.MagicMock( config_dir='/tmp/config', work_dir='/tmp/foo', foo='bar', server='https://acme-server.org:443/new', - dvsni_port=1234, simple_http_port=4321) + dvsni_port=1234, http01_port=4321) from letsencrypt.configuration import NamespaceConfig self.config = NamespaceConfig(self.namespace) @@ -54,10 +54,10 @@ class NamespaceConfigTest(unittest.TestCase): self.assertEqual(self.config.key_dir, '/tmp/config/keys') self.assertEqual(self.config.temp_checkpoint_dir, '/tmp/foo/t') - def test_simple_http_port(self): - self.assertEqual(4321, self.config.simple_http_port) - self.namespace.simple_http_port = None - self.assertEqual(80, self.config.simple_http_port) + def test_http01_port(self): + self.assertEqual(4321, self.config.http01_port) + self.namespace.http01_port = None + self.assertEqual(80, self.config.http01_port) class RenewerConfigurationTest(unittest.TestCase): diff --git a/letsencrypt/tests/crypto_util_test.py b/letsencrypt/tests/crypto_util_test.py index f0b8c0e4d..1a9f39572 100644 --- a/letsencrypt/tests/crypto_util_test.py +++ b/letsencrypt/tests/crypto_util_test.py @@ -228,9 +228,8 @@ class CertLoaderTest(unittest.TestCase): def test_load_invalid_cert(self): from letsencrypt.crypto_util import pyopenssl_load_certificate bad_cert_data = CERT.replace("BEGIN CERTIFICATE", "ASDFASDFASDF!!!") - - with self.assertRaises(errors.Error): - pyopenssl_load_certificate(bad_cert_data) + self.assertRaises( + errors.Error, pyopenssl_load_certificate, bad_cert_data) class NotBeforeTest(unittest.TestCase): diff --git a/letsencrypt/tests/renewer_test.py b/letsencrypt/tests/renewer_test.py index 3599d8ad3..05d7e123d 100644 --- a/letsencrypt/tests/renewer_test.py +++ b/letsencrypt/tests/renewer_test.py @@ -1,5 +1,6 @@ """Tests for letsencrypt.renewer.""" import datetime +import pytz import os import tempfile import shutil @@ -62,11 +63,11 @@ class BaseRenewableCertTest(unittest.TestCase): kind + ".pem") config.filename = os.path.join(self.tempdir, "renewal", "example.org.conf") + config.write() self.config = config self.defaults = configobj.ConfigObj() - self.test_rc = storage.RenewableCert( - self.config, self.defaults, self.cli_config) + self.test_rc = storage.RenewableCert(config.filename, self.cli_config) def tearDown(self): shutil.rmtree(self.tempdir) @@ -98,34 +99,32 @@ class RenewableCertTests(BaseRenewableCertTest): def test_renewal_bad_config(self): """Test that the RenewableCert constructor will complain if - the renewal configuration file doesn't end in ".conf" or if it - isn't a ConfigObj.""" + the renewal configuration file doesn't end in ".conf" + + """ from letsencrypt import storage - defaults = configobj.ConfigObj() - config = configobj.ConfigObj() - # These files don't exist and aren't created here; the point of the test - # is to confirm that the constructor rejects them outright because of - # the configfile's name. - for kind in ALL_FOUR: - config["cert"] = "nonexistent_" + kind + ".pem" - config.filename = "nonexistent_sillyfile" - self.assertRaises( - errors.CertStorageError, storage.RenewableCert, config, defaults) - self.assertRaises(TypeError, storage.RenewableCert, "fun", defaults) + broken = os.path.join(self.tempdir, "broken.conf") + with open(broken, "w") as f: + f.write("[No closing bracket for you!") + self.assertRaises(errors.CertStorageError, storage.RenewableCert, + broken, self.cli_config) + os.unlink(broken) + self.assertRaises(errors.CertStorageError, storage.RenewableCert, + "fun", self.cli_config) def test_renewal_incomplete_config(self): """Test that the RenewableCert constructor will complain if the renewal configuration file is missing a required file element.""" from letsencrypt import storage - defaults = configobj.ConfigObj() config = configobj.ConfigObj() config["cert"] = "imaginary_cert.pem" # Here the required privkey is missing. config["chain"] = "imaginary_chain.pem" config["fullchain"] = "imaginary_fullchain.pem" - config.filename = "imaginary_config.conf" - self.assertRaises( - errors.CertStorageError, storage.RenewableCert, config, defaults) + config.filename = os.path.join(self.tempdir, "imaginary_config.conf") + config.write() + self.assertRaises(errors.CertStorageError, storage.RenewableCert, + config.filename, self.cli_config) def test_consistent(self): # pylint: disable=too-many-statements,protected-access @@ -289,7 +288,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertEqual("cert8.pem", os.path.basename(self.test_rc.version("cert", 8))) - def test_update_all_links_to(self): + def test_update_all_links_to_success(self): for ver in xrange(1, 6): for kind in ALL_FOUR: where = getattr(self.test_rc, kind) @@ -307,6 +306,39 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertEqual(ver, self.test_rc.current_version(kind)) self.assertEqual(self.test_rc.latest_common_version(), 5) + def test_update_all_links_to_partial_failure(self): + def unlink_or_raise(path, real_unlink=os.unlink): + # pylint: disable=missing-docstring + basename = os.path.basename(path) + if "fullchain" in basename and basename.startswith("prev"): + raise ValueError + else: + real_unlink(path) + + self._write_out_ex_kinds() + with mock.patch("letsencrypt.storage.os.unlink") as mock_unlink: + mock_unlink.side_effect = unlink_or_raise + self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12) + + for kind in ALL_FOUR: + self.assertEqual(self.test_rc.current_version(kind), 12) + + def test_update_all_links_to_full_failure(self): + def unlink_or_raise(path, real_unlink=os.unlink): + # pylint: disable=missing-docstring + if "fullchain" in os.path.basename(path): + raise ValueError + else: + real_unlink(path) + + self._write_out_ex_kinds() + with mock.patch("letsencrypt.storage.os.unlink") as mock_unlink: + mock_unlink.side_effect = unlink_or_raise + self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12) + + for kind in ALL_FOUR: + self.assertEqual(self.test_rc.current_version(kind), 11) + def test_has_pending_deployment(self): for ver in xrange(1, 6): for kind in ALL_FOUR: @@ -591,18 +623,47 @@ class RenewableCertTests(BaseRenewableCertTest): # OCSP server to test against. self.assertFalse(self.test_rc.ocsp_revoked()) - def test_parse_time_interval(self): + def test_add_time_interval(self): from letsencrypt import storage - # XXX: I'm not sure if intervals related to years and months - # take account of the current date (if so, some of these - # may fail in the future, like in leap years or even in - # months of different lengths!) - intended = {"": 0, "17 days": 17, "23": 23, "1 month": 31, - "7 weeks": 49, "1 year 1 day": 366, "1 year-1 day": 364, - "4 years": 1461} - for time in intended: - self.assertEqual(storage.parse_time_interval(time), - datetime.timedelta(intended[time])) + + # this month has 30 days, and the next year is a leap year + time_1 = pytz.UTC.fromutc(datetime.datetime(2003, 11, 20, 11, 59, 21)) + + # this month has 31 days, and the next year is not a leap year + time_2 = pytz.UTC.fromutc(datetime.datetime(2012, 10, 18, 21, 31, 16)) + + # in different time zone (GMT+8) + time_3 = pytz.timezone('Asia/Shanghai').fromutc( + datetime.datetime(2015, 10, 26, 22, 25, 41)) + + intended = { + (time_1, ""): time_1, + (time_2, ""): time_2, + (time_3, ""): time_3, + (time_1, "17 days"): time_1 + datetime.timedelta(17), + (time_2, "17 days"): time_2 + datetime.timedelta(17), + (time_1, "30"): time_1 + datetime.timedelta(30), + (time_2, "30"): time_2 + datetime.timedelta(30), + (time_1, "7 weeks"): time_1 + datetime.timedelta(49), + (time_2, "7 weeks"): time_2 + datetime.timedelta(49), + # 1 month is always 30 days, no matter which month it is + (time_1, "1 month"): time_1 + datetime.timedelta(30), + (time_2, "1 month"): time_2 + datetime.timedelta(31), + # 1 year could be 365 or 366 days, depends on the year + (time_1, "1 year"): time_1 + datetime.timedelta(366), + (time_2, "1 year"): time_2 + datetime.timedelta(365), + (time_1, "1 year 1 day"): time_1 + datetime.timedelta(367), + (time_2, "1 year 1 day"): time_2 + datetime.timedelta(366), + (time_1, "1 year-1 day"): time_1 + datetime.timedelta(365), + (time_2, "1 year-1 day"): time_2 + datetime.timedelta(364), + (time_1, "4 years"): time_1 + datetime.timedelta(1461), + (time_2, "4 years"): time_2 + datetime.timedelta(1461), + } + + for parameters, excepted in intended.items(): + base_time, interval = parameters + self.assertEqual(storage.add_time_interval(base_time, interval), + excepted) @mock.patch("letsencrypt.renewer.plugins_disco") @mock.patch("letsencrypt.account.AccountFileStorage") @@ -628,7 +689,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.test_rc.configfile["renewalparams"]["server"] = "acme.example.com" self.test_rc.configfile["renewalparams"]["authenticator"] = "fake" self.test_rc.configfile["renewalparams"]["dvsni_port"] = "4430" - self.test_rc.configfile["renewalparams"]["simple_http_port"] = "1234" + self.test_rc.configfile["renewalparams"]["http01_port"] = "1234" self.test_rc.configfile["renewalparams"]["account"] = "abcde" mock_auth = mock.MagicMock() mock_pd.PluginsRegistry.find_all.return_value = {"apache": mock_auth} @@ -667,10 +728,6 @@ class RenewableCertTests(BaseRenewableCertTest): mock_rc_instance.should_autorenew.return_value = True mock_rc_instance.latest_common_version.return_value = 10 mock_rc.return_value = mock_rc_instance - with open(os.path.join(self.cli_config.renewal_configs_dir, - "README"), "w") as f: - f.write("This is a README file to make sure that the renewer is") - f.write("able to correctly ignore files that don't end in .conf.") with open(os.path.join(self.cli_config.renewal_configs_dir, "example.org.conf"), "w") as f: # This isn't actually parsed in this test; we have a separate @@ -682,7 +739,7 @@ class RenewableCertTests(BaseRenewableCertTest): "example.com.conf"), "w") as f: f.write("cert = cert.pem\nprivkey = privkey.pem\n") f.write("chain = chain.pem\nfullchain = fullchain.pem\n") - renewer.main(self.defaults, cli_args=self._common_cli_args()) + renewer.main(cli_args=self._common_cli_args()) self.assertEqual(mock_rc.call_count, 2) self.assertEqual(mock_rc_instance.update_all_links_to.call_count, 2) self.assertEqual(mock_notify.notify.call_count, 4) @@ -695,7 +752,7 @@ class RenewableCertTests(BaseRenewableCertTest): mock_happy_instance.should_autorenew.return_value = False mock_happy_instance.latest_common_version.return_value = 10 mock_rc.return_value = mock_happy_instance - renewer.main(self.defaults, cli_args=self._common_cli_args()) + renewer.main(cli_args=self._common_cli_args()) self.assertEqual(mock_rc.call_count, 4) self.assertEqual(mock_happy_instance.update_all_links_to.call_count, 0) self.assertEqual(mock_notify.notify.call_count, 4) @@ -706,7 +763,7 @@ class RenewableCertTests(BaseRenewableCertTest): with open(os.path.join(self.cli_config.renewal_configs_dir, "bad.conf"), "w") as f: f.write("incomplete = configfile\n") - renewer.main(self.defaults, cli_args=self._common_cli_args()) + renewer.main(cli_args=self._common_cli_args()) # The errors.CertStorageError is caught inside and nothing happens. diff --git a/letshelp-letsencrypt/MANIFEST.in b/letshelp-letsencrypt/MANIFEST.in index 96c1d7ba5..6ea55a950 100644 --- a/letshelp-letsencrypt/MANIFEST.in +++ b/letshelp-letsencrypt/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE.txt include README.rst +recursive-include docs * recursive-include letshelp_letsencrypt/testdata * diff --git a/letshelp-letsencrypt/docs/.gitignore b/letshelp-letsencrypt/docs/.gitignore new file mode 100644 index 000000000..ba65b13af --- /dev/null +++ b/letshelp-letsencrypt/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/letshelp-letsencrypt/docs/Makefile b/letshelp-letsencrypt/docs/Makefile new file mode 100644 index 000000000..8e742d837 --- /dev/null +++ b/letshelp-letsencrypt/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/letshelp-letsencrypt.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/letshelp-letsencrypt.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/letshelp-letsencrypt" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/letshelp-letsencrypt" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/letshelp-letsencrypt/docs/_static/.gitignore b/letshelp-letsencrypt/docs/_static/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letshelp-letsencrypt/docs/_templates/.gitignore b/letshelp-letsencrypt/docs/_templates/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/letshelp-letsencrypt/docs/api.rst b/letshelp-letsencrypt/docs/api.rst new file mode 100644 index 000000000..8668ec5d8 --- /dev/null +++ b/letshelp-letsencrypt/docs/api.rst @@ -0,0 +1,8 @@ +================= +API Documentation +================= + +.. toctree:: + :glob: + + api/** diff --git a/docs/pkgs/letshelp_letsencrypt.rst b/letshelp-letsencrypt/docs/api/index.rst similarity index 100% rename from docs/pkgs/letshelp_letsencrypt.rst rename to letshelp-letsencrypt/docs/api/index.rst diff --git a/letshelp-letsencrypt/docs/conf.py b/letshelp-letsencrypt/docs/conf.py new file mode 100644 index 000000000..206b0b9e2 --- /dev/null +++ b/letshelp-letsencrypt/docs/conf.py @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- +# +# letshelp-letsencrypt documentation build configuration file, created by +# sphinx-quickstart on Sun Oct 18 13:40:19 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + + +here = os.path.abspath(os.path.dirname(__file__)) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath(os.path.join(here, '..'))) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +autodoc_member_order = 'bysource' +autodoc_default_flags = ['show-inheritance', 'private-members'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'letshelp-letsencrypt' +copyright = u'2014-2015, Let\'s Encrypt Project' +author = u'Let\'s Encrypt Project' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0' +# The full version, including alpha/beta/rc tags. +release = '0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'py:obj' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'letshelp-letsencryptdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'letshelp-letsencrypt.tex', u'letshelp-letsencrypt Documentation', + u'Let\'s Encrypt Project', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'letshelp-letsencrypt', u'letshelp-letsencrypt Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'letshelp-letsencrypt', u'letshelp-letsencrypt Documentation', + author, 'letshelp-letsencrypt', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), + 'letsencrypt': ('https://letsencrypt.readthedocs.org/en/latest/', None), +} diff --git a/letshelp-letsencrypt/docs/index.rst b/letshelp-letsencrypt/docs/index.rst new file mode 100644 index 000000000..6b67a2e1f --- /dev/null +++ b/letshelp-letsencrypt/docs/index.rst @@ -0,0 +1,27 @@ +.. letshelp-letsencrypt documentation master file, created by + sphinx-quickstart on Sun Oct 18 13:40:19 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to letshelp-letsencrypt's documentation! +================================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + + +.. toctree:: + :maxdepth: 1 + + api + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/letshelp-letsencrypt/docs/make.bat b/letshelp-letsencrypt/docs/make.bat new file mode 100644 index 000000000..006f7825d --- /dev/null +++ b/letshelp-letsencrypt/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\letshelp-letsencrypt.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\letshelp-letsencrypt.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/letshelp-letsencrypt/readthedocs.org.requirements.txt b/letshelp-letsencrypt/readthedocs.org.requirements.txt new file mode 100644 index 000000000..898d2716e --- /dev/null +++ b/letshelp-letsencrypt/readthedocs.org.requirements.txt @@ -0,0 +1,10 @@ +# 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 letshelp-letsencrypt[docs] diff --git a/letshelp-letsencrypt/setup.py b/letshelp-letsencrypt/setup.py index a83fc8843..04b879e14 100644 --- a/letshelp-letsencrypt/setup.py +++ b/letshelp-letsencrypt/setup.py @@ -14,6 +14,11 @@ if sys.version_info < (2, 7): else: install_requires.append('mock') +docs_extras = [ + 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags + 'sphinx_rtd_theme', +] + setup( name='letshelp-letsencrypt', version=version, @@ -29,6 +34,7 @@ setup( 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', @@ -41,6 +47,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, + extras_require={ + 'docs': docs_extras, + }, entry_points={ 'console_scripts': [ 'letshelp-letsencrypt-apache = letshelp_letsencrypt.apache:main', diff --git a/readthedocs.org.requirements.txt b/readthedocs.org.requirements.txt index 3c3a3c576..94a81e788 100644 --- a/readthedocs.org.requirements.txt +++ b/readthedocs.org.requirements.txt @@ -9,7 +9,3 @@ -e acme -e .[docs] --e letsencrypt-apache --e letsencrypt-nginx --e letsencrypt-compatibility-test --e letshelp-letsencrypt diff --git a/setup.py b/setup.py index d4fe1eda0..40c6ac16c 100644 --- a/setup.py +++ b/setup.py @@ -99,6 +99,7 @@ setup( 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh index 7c0ee8fea..18a996926 100755 --- a/tests/boulder-integration.sh +++ b/tests/boulder-integration.sh @@ -28,7 +28,7 @@ common() { } common --domains le1.wtf --standalone-supported-challenges dvsni auth -common --domains le2.wtf --standalone-supported-challenges simpleHttp run +common --domains le2.wtf --standalone-supported-challenges http-01 run common -a manual -d le.wtf auth export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \ diff --git a/tests/boulder-start.sh b/tests/boulder-start.sh index 051c832f2..47c1b6278 100755 --- a/tests/boulder-start.sh +++ b/tests/boulder-start.sh @@ -37,5 +37,7 @@ wget https://github.com/jsha/boulder-tools/raw/master/goose.gz && \ zcat goose.gz > $GOPATH/bin/goose && \ chmod +x $GOPATH/bin/goose ./test/create_db.sh +# listenbuddy is needed for ./start.py +go get github.com/jsha/listenbuddy ./start.py & # Hopefully start.py bootstraps before integration test is started... diff --git a/tests/integration/_common.sh b/tests/integration/_common.sh index 1dffaa4e3..cd894fd10 100755 --- a/tests/integration/_common.sh +++ b/tests/integration/_common.sh @@ -16,10 +16,11 @@ letsencrypt_test () { --server "${SERVER:-http://localhost:4000/directory}" \ --no-verify-ssl \ --dvsni-port 5001 \ - --simple-http-port 5002 \ + --http-01-port 5002 \ --manual-test-mode \ $store_flags \ --text \ + --no-redirect \ --agree-dev-preview \ --agree-tos \ --email "" \ diff --git a/tools/deps.sh b/tools/deps.sh index 28bfdaff5..6fb2bf63b 100755 --- a/tools/deps.sh +++ b/tools/deps.sh @@ -2,9 +2,9 @@ # # Find all Python imports. # -# ./deps.sh letsencrypt -# ./deps.sh acme -# ./deps.sh letsencrypt-apache +# ./tools/deps.sh letsencrypt +# ./tools/deps.sh acme +# ./tools/deps.sh letsencrypt-apache # ... # # Manually compare the output with deps in setup.py. diff --git a/tox.ini b/tox.ini index febf1a2d0..8ea3e518f 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ # acme and letsencrypt are not yet on pypi, so when Tox invokes # "install *.zip", it will not find deps skipsdist = true -envlist = py27,cover,lint +envlist = py26,py27,py33,py34,cover,lint # nosetest -v => more verbose output, allows to detect busy waiting # loops, especially on Travis @@ -48,6 +48,7 @@ commands = ./tox.cover.sh [testenv:lint] +# recent versions of pylint do not support Python 2.6 (#97, #187) basepython = python2.7 # separating into multiple invocations disables cross package # duplicate code checking; if one of the commands fails, others will