Integration test for ACMEv2 (#3298)
This commit is contained in:
parent
bdad6ddc4e
commit
b369818ad6
|
|
@ -39,6 +39,7 @@ matrix:
|
||||||
- env: RUN="unit"
|
- env: RUN="unit"
|
||||||
- env: RUN="unit-next" BOULDER_CONFIG_DIR="test/config-next"
|
- env: RUN="unit-next" BOULDER_CONFIG_DIR="test/config-next"
|
||||||
- env: RUN="coverage"
|
- env: RUN="coverage"
|
||||||
|
- env: RUN="acme-v2" BOULDER_CONFIG_DIR="test/config-next"
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env: RUN="coverage"
|
- env: RUN="coverage"
|
||||||
|
|
|
||||||
12
test.sh
12
test.sh
|
|
@ -188,6 +188,18 @@ if [[ "$RUN" =~ "integration" ]] ; then
|
||||||
end_context #integration
|
end_context #integration
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$RUN" =~ "acme-v2" ]] ; then
|
||||||
|
# If you're developing against a local Certbot repo, edit docker-compose.yml
|
||||||
|
# to mount it as a volume under /certbot, and run tests with
|
||||||
|
# docker-compose run -e RUN=acme-v2 -e CERTBOT_REPO=/certbot boulder ./test.sh
|
||||||
|
CERTBOT_REPO=${CERTBOT_REPO:-https://github.com/certbot/certbot}
|
||||||
|
CERTBOT_DIR=$(mktemp -d -t certbotXXXX)
|
||||||
|
git clone $CERTBOT_REPO $CERTBOT_DIR
|
||||||
|
(cd $CERTBOT_DIR ; git checkout acme-v2-integration; ./tools/venv.sh)
|
||||||
|
source $CERTBOT_DIR/venv/bin/activate
|
||||||
|
REQUESTS_CA_BUNDLE=test/wfe.pem DIRECTORY=https://boulder:4431/directory run python2 test/integration-test-v2.py
|
||||||
|
fi
|
||||||
|
|
||||||
# Run godep-restore (happens only in Travis) to check that the hashes in
|
# Run godep-restore (happens only in Travis) to check that the hashes in
|
||||||
# Godeps.json really exist in the remote repo and match what we have.
|
# Godeps.json really exist in the remote repo and match what we have.
|
||||||
if [[ "$RUN" =~ "godep-restore" ]] ; then
|
if [[ "$RUN" =~ "godep-restore" ]] ; then
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,12 @@ from cryptography import x509
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
|
import josepy
|
||||||
|
|
||||||
from acme import challenges
|
from acme import challenges
|
||||||
from acme import client as acme_client
|
from acme import client as acme_client
|
||||||
|
from acme import crypto_util as acme_crypto_util
|
||||||
from acme import errors as acme_errors
|
from acme import errors as acme_errors
|
||||||
from acme import jose
|
|
||||||
from acme import messages
|
from acme import messages
|
||||||
from acme import standalone
|
from acme import standalone
|
||||||
|
|
||||||
|
|
@ -36,17 +37,18 @@ logger = logging.getLogger()
|
||||||
logger.setLevel(int(os.getenv('LOGLEVEL', 0)))
|
logger.setLevel(int(os.getenv('LOGLEVEL', 0)))
|
||||||
|
|
||||||
DIRECTORY = os.getenv('DIRECTORY', 'http://localhost:4001/directory')
|
DIRECTORY = os.getenv('DIRECTORY', 'http://localhost:4001/directory')
|
||||||
|
ACCEPTABLE_TOS = "https://boulder:4431/terms/v7"
|
||||||
|
|
||||||
def make_client(email=None):
|
def make_client(email=None):
|
||||||
"""Build an acme.Client and register a new account with a random key."""
|
"""Build an acme.Client and register a new account with a random key."""
|
||||||
key = jose.JWKRSA(key=rsa.generate_private_key(65537, 2048, default_backend()))
|
key = josepy.JWKRSA(key=rsa.generate_private_key(65537, 2048, default_backend()))
|
||||||
|
|
||||||
net = acme_client.ClientNetwork(key, verify_ssl=False,
|
net = acme_client.ClientNetwork(key, acme_version=2,
|
||||||
user_agent="Boulder integration tester")
|
user_agent="Boulder integration tester")
|
||||||
|
|
||||||
client = acme_client.Client(DIRECTORY, key=key, net=net)
|
client = acme_client.Client(DIRECTORY, key=key, net=net, acme_version=2)
|
||||||
tos = client.directory.meta.terms_of_service
|
tos = client.directory.meta.terms_of_service
|
||||||
if tos is not None and "Do%20what%20thou%20wilt" in tos:
|
if tos == ACCEPTABLE_TOS:
|
||||||
net.account = client.register(messages.NewRegistration.from_data(email=email,
|
net.account = client.register(messages.NewRegistration.from_data(email=email,
|
||||||
terms_of_service_agreed=True))
|
terms_of_service_agreed=True))
|
||||||
else:
|
else:
|
||||||
|
|
@ -70,10 +72,10 @@ class ValidationError(Exception):
|
||||||
return "%s: %s: %s" % (self.domain, self.problem_type, self.detail)
|
return "%s: %s: %s" % (self.domain, self.problem_type, self.detail)
|
||||||
|
|
||||||
def make_csr(domains):
|
def make_csr(domains):
|
||||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
|
key = OpenSSL.crypto.PKey()
|
||||||
return x509.CertificateSigningRequestBuilder().add_extension(
|
key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
|
||||||
x509.SubjectAlternativeName([x509.DNSName(d) for d in domains], critical=False)
|
pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
|
||||||
).sign(key, hashes.SHA256(), default_backend()).public_bytes(serialization.Encoding.PEM)
|
return acme_crypto_util.make_csr(pem, domains, False)
|
||||||
|
|
||||||
def issue(client, authzs, cert_output=None):
|
def issue(client, authzs, cert_output=None):
|
||||||
"""Given a list of authzs that are being processed by the server,
|
"""Given a list of authzs that are being processed by the server,
|
||||||
|
|
@ -129,12 +131,7 @@ def auth_and_issue(domains, chall_type="http-01", email=None, cert_output=None,
|
||||||
raise Exception("invalid challenge type %s" % chall_type)
|
raise Exception("invalid challenge type %s" % chall_type)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
order = client.poll_order_and_request_issuance(order)
|
||||||
order, response = client.poll_order(order)
|
|
||||||
print order.to_json()
|
|
||||||
if order.body.status != "pending":
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
finally:
|
finally:
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env python2.7
|
||||||
|
"""
|
||||||
|
Integration test for ACMEv2 as implemented by boulder-wfe2.
|
||||||
|
|
||||||
|
Currently (December 2017) this depends on the acme-v2-integration branch of
|
||||||
|
Certbot, while we wait on landing some of our changes in master.
|
||||||
|
"""
|
||||||
|
import atexit
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import startservers
|
||||||
|
|
||||||
|
import chisel2
|
||||||
|
from chisel2 import auth_and_issue
|
||||||
|
|
||||||
|
exit_status = 1
|
||||||
|
tempdir = tempfile.mkdtemp()
|
||||||
|
|
||||||
|
def random_domain():
|
||||||
|
"""Generate a random domain for testing (to avoid rate limiting)."""
|
||||||
|
return "rand.%x.xyz" % random.randrange(2**32)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not startservers.start(race_detection=True):
|
||||||
|
raise Exception("startservers failed")
|
||||||
|
|
||||||
|
test_multidomain()
|
||||||
|
|
||||||
|
if not startservers.check():
|
||||||
|
raise Exception("startservers.check failed")
|
||||||
|
|
||||||
|
global exit_status
|
||||||
|
exit_status = 0
|
||||||
|
|
||||||
|
def test_multidomain():
|
||||||
|
auth_and_issue([random_domain(), random_domain()])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise Exception("%s. Output:\n%s" % (e, e.output))
|
||||||
|
|
||||||
|
@atexit.register
|
||||||
|
def stop():
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(tempdir)
|
||||||
|
if exit_status == 0:
|
||||||
|
print("\n\nSUCCESS")
|
||||||
|
else:
|
||||||
|
print("\n\nFAILURE")
|
||||||
Loading…
Reference in New Issue