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-next" BOULDER_CONFIG_DIR="test/config-next"
 | 
			
		||||
    - env: RUN="coverage"
 | 
			
		||||
    - env: RUN="acme-v2" BOULDER_CONFIG_DIR="test/config-next"
 | 
			
		||||
  fast_finish: true
 | 
			
		||||
  allow_failures:
 | 
			
		||||
    - env: RUN="coverage"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								test.sh
								
								
								
								
							
							
						
						
									
										12
									
								
								test.sh
								
								
								
								
							| 
						 | 
				
			
			@ -188,6 +188,18 @@ if [[ "$RUN" =~ "integration" ]] ; then
 | 
			
		|||
  end_context #integration
 | 
			
		||||
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
 | 
			
		||||
# Godeps.json really exist in the remote repo and match what we have.
 | 
			
		||||
if [[ "$RUN" =~ "godep-restore" ]] ; then
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,11 +23,12 @@ from cryptography import x509
 | 
			
		|||
from cryptography.hazmat.primitives import hashes
 | 
			
		||||
 | 
			
		||||
import OpenSSL
 | 
			
		||||
import josepy
 | 
			
		||||
 | 
			
		||||
from acme import challenges
 | 
			
		||||
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 jose
 | 
			
		||||
from acme import messages
 | 
			
		||||
from acme import standalone
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,17 +37,18 @@ logger = logging.getLogger()
 | 
			
		|||
logger.setLevel(int(os.getenv('LOGLEVEL', 0)))
 | 
			
		||||
 | 
			
		||||
DIRECTORY = os.getenv('DIRECTORY', 'http://localhost:4001/directory')
 | 
			
		||||
ACCEPTABLE_TOS = "https://boulder:4431/terms/v7"
 | 
			
		||||
 | 
			
		||||
def make_client(email=None):
 | 
			
		||||
    """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")
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    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,
 | 
			
		||||
            terms_of_service_agreed=True))
 | 
			
		||||
    else:
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +72,10 @@ class ValidationError(Exception):
 | 
			
		|||
        return "%s: %s: %s" % (self.domain, self.problem_type, self.detail)
 | 
			
		||||
 | 
			
		||||
def make_csr(domains):
 | 
			
		||||
    key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
 | 
			
		||||
    return x509.CertificateSigningRequestBuilder().add_extension(
 | 
			
		||||
        x509.SubjectAlternativeName([x509.DNSName(d) for d in domains], critical=False)
 | 
			
		||||
    ).sign(key, hashes.SHA256(), default_backend()).public_bytes(serialization.Encoding.PEM)
 | 
			
		||||
    key = OpenSSL.crypto.PKey()
 | 
			
		||||
    key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
 | 
			
		||||
    pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
 | 
			
		||||
    return acme_crypto_util.make_csr(pem, domains, False)
 | 
			
		||||
 | 
			
		||||
def issue(client, authzs, cert_output=None):
 | 
			
		||||
    """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)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        while True:
 | 
			
		||||
            order, response = client.poll_order(order)
 | 
			
		||||
            print order.to_json()
 | 
			
		||||
            if order.body.status != "pending":
 | 
			
		||||
                break
 | 
			
		||||
            time.sleep(1)
 | 
			
		||||
        order = client.poll_order_and_request_issuance(order)
 | 
			
		||||
    finally:
 | 
			
		||||
        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