def test_expired_authz_purger(): def expect(target_time, num, table): out = get_future_output("./bin/expired-authz-purger --config cmd/expired-authz-purger/config.json", target_time) if 'via FAKECLOCK' not in out: raise Exception("expired-authz-purger was not built with `integration` build tag") if num is None: return expected_output = 'Deleted a total of %d expired authorizations from %s' % (num, table) if expected_output not in out: raise Exception("expired-authz-purger did not print '%s'. Output:\n%s" % ( expected_output, out)) now = datetime.datetime.utcnow() # Run the purger once to clear out any backlog so we have a clean slate. expect(now+datetime.timedelta(days=+365), None, "") # Make an authz, but don't attempt its challenges. chisel.make_client().request_domain_challenges("eap-test.com") # Run the authz twice: Once immediate, expecting nothing to be purged, and # once as if it were the future, expecting one purged authz. after_grace_period = now + datetime.timedelta(days=+14, minutes=+3) expect(now, 0, "pendingAuthorizations") expect(after_grace_period, 1, "pendingAuthorizations") auth_and_issue([random_domain()]) after_grace_period = now + datetime.timedelta(days=+67, minutes=+3) expect(now, 0, "authz") expect(after_grace_period, 1, "authz")
def run_expired_authz_purger(): # Note: This test must be run after all other tests that depend on # authorizations added to the database during setup # (e.g. test_expired_authzs_404). def expect(target_time, num, table): tool = "expired-authz-purger2" out = get_future_output("./bin/expired-authz-purger2 --single-run --config cmd/expired-authz-purger2/config.json", target_time) if 'via FAKECLOCK' not in out: raise Exception("%s was not built with `integration` build tag" % (tool)) if num is None: return expected_output = 'deleted %d expired authorizations' % (num) if expected_output not in out: raise Exception("%s did not print '%s'. Output:\n%s" % ( tool, expected_output, out)) now = datetime.datetime.utcnow() # Run the purger once to clear out any backlog so we have a clean slate. expect(now+datetime.timedelta(days=+365), None, "") # Make an authz, but don't attempt its challenges. chisel.make_client().request_domain_challenges("eap-test.com") # Run the authz twice: Once immediate, expecting nothing to be purged, and # once as if it were the future, expecting one purged authz. after_grace_period = now + datetime.timedelta(days=+14, minutes=+3) expect(now, 0, "pendingAuthorizations") expect(after_grace_period, 1, "pendingAuthorizations") auth_and_issue([random_domain()]) after_grace_period = now + datetime.timedelta(days=+67, minutes=+3) expect(now, 0, "authz") expect(after_grace_period, 1, "authz")
def test_expired_authz_purger(): def expect(target_time, num): expected_output = '' if num is not None: expected_output = 'Deleted a total of %d expired pending authorizations' % num try: out = get_future_output("./bin/expired-authz-purger --config cmd/expired-authz-purger/config.json --yes", target_time) if expected_output not in out: print(("expired-authz-purger did not print '%s'. " + "Maybe not built with `integration` build tag? Output:\n%s") % ( expected_output, out)) die(ExitStatus.NodeFailure) except subprocess.CalledProcessError as e: print("\nFailed to run authz purger: %s" % e) die(ExitStatus.NodeFailure) now = datetime.datetime.utcnow() # Run the purger once to clear out any backlog so we have a clean slate. expect(now, None) # Make an authz, but don't attempt its challenges. chisel.make_client().request_domain_challenges("eap-test.com") # Run the authz twice: Once immediate, expecting nothing to be purged, and # once as if it were the future, expecting one purged authz. after_grace_period = now + datetime.timedelta(days=+14, minutes=+3) expect(now, 0) expect(after_grace_period, 1)
def test_caa_extensions(): goodCAA = "happy-hacker-ca.invalid" client = chisel.make_client() caa_account_uri = client.account.uri caa_records = [ {"domain": "accounturi.good-caa-reserved.com", "value":"{0}; accounturi={1}".format(goodCAA, caa_account_uri)}, {"domain": "dns-01-only.good-caa-reserved.com", "value": "{0}; validationmethods=dns-01".format(goodCAA)}, {"domain": "http-01-only.good-caa-reserved.com", "value": "{0}; validationmethods=http-01".format(goodCAA)}, {"domain": "dns-01-or-http01.good-caa-reserved.com", "value": "{0}; validationmethods=dns-01,http-01".format(goodCAA)}, ] for policy in caa_records: challSrv.add_caa_issue(policy["domain"], policy["value"]) # TODO(@4a6f656c): Once the `CAAValidationMethods` feature flag is enabled by # default, remove this early return. if not CONFIG_NEXT: return chisel.expect_problem("urn:acme:error:caa", lambda: auth_and_issue(["dns-01-only.good-caa-reserved.com"], chall_type="http-01")) chisel.expect_problem("urn:acme:error:caa", lambda: auth_and_issue(["http-01-only.good-caa-reserved.com"], chall_type="dns-01")) # Note: the additional names are to avoid rate limiting... auth_and_issue(["dns-01-only.good-caa-reserved.com", "www.dns-01-only.good-caa-reserved.com"], chall_type="dns-01") auth_and_issue(["http-01-only.good-caa-reserved.com", "www.http-01-only.good-caa-reserved.com"], chall_type="http-01") auth_and_issue(["dns-01-or-http-01.good-caa-reserved.com", "dns-01-only.good-caa-reserved.com"], chall_type="dns-01") auth_and_issue(["dns-01-or-http-01.good-caa-reserved.com", "http-01-only.good-caa-reserved.com"], chall_type="http-01") # CAA should fail with an arbitrary account, but succeed with the CAA client. chisel.expect_problem("urn:acme:error:caa", lambda: auth_and_issue(["accounturi.good-caa-reserved.com"])) auth_and_issue(["accounturi.good-caa-reserved.com"], client=client)
def test_http_challenge_http_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Calculate its keyauth so we can add it in a special non-standard location # for the redirect result resp = chall.response(client.key) keyauth = resp.key_authorization challSrv.add_http01_response("http-redirect", keyauth) # Create a HTTP redirect from the challenge's validation path to some other # token path where we have registered the key authorization. challengePath = "/.well-known/acme-challenge/{0}".format(token) redirectPath = "/.well-known/acme-challenge/http-redirect?params=are&important=to¬=lose" challSrv.add_http_redirect( challengePath, "http://{0}{1}".format(d, redirectPath)) # Issuing should succeed auth_and_issue([d], client=client, chall_type="http-01") # Cleanup the redirects challSrv.remove_http_redirect(challengePath) challSrv.remove_http01_response("http-redirect") history = challSrv.http_request_history(d) challSrv.clear_http_request_history(d) # There should have been at least two GET requests made to the # challtestsrv. There may have been more if remote VAs were configured. if len(history) < 2: raise Exception("Expected at least 2 HTTP request events on challtestsrv, found {1}".format(len(history))) initialRequests = [] redirectedRequests = [] for request in history: # All requests should have been over HTTP if request['HTTPS'] is True: raise Exception("Expected all requests to be HTTP") # Initial requests should have the expected initial HTTP-01 URL for the challenge if request['URL'] == challengePath: initialRequests.append(request) # Redirected requests should have the expected redirect path URL with all # its parameters elif request['URL'] == redirectPath: redirectedRequests.append(request) else: raise Exception("Unexpected request URL {0} in challtestsrv history: {1}".format(request['URL'], request)) # There should have been at least 1 initial HTTP-01 validation request. if len(initialRequests) < 1: raise Exception("Expected {0} initial HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(initialRequests))) # There should have been at least 1 redirected HTTP request for each VA if len(redirectedRequests) < 1: raise Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests)))
def test_revoke_by_account(): cert_file_pem = os.path.join(tempdir, "revokeme.pem") client = chisel.make_client() cert, _ = auth_and_issue([random_domain()], client=client) client.revoke(cert.body) wait_for_ocsp_revoked(cert_file_pem, "test/test-ca2.pem", ee_ocsp_url) return 0
def caa_recheck_setup(): global caa_recheck_client caa_recheck_client = chisel.make_client() # Issue a certificate with the clock set back, and save the authzs to check # later that they are valid (200). They should however require rechecking for # CAA purposes. _, authzs = auth_and_issue([random_domain()], client=caa_recheck_client) for a in authzs: caa_recheck_authzs.append(a)
def setup_twenty_days_ago(): """Do any setup that needs to happen 20 day in the past, for tests that will run in the 'present'. """ # Issue a certificate with the clock set back, and save the authzs to check # later that they are valid (200). They should however require rechecking for # CAA purposes. global caa_client caa_client = chisel.make_client() global caa_authzs _, caa_authzs = auth_and_issue(["recheck.good-caa-reserved.com"], client=caa_client)
def test_auth_deactivation(): client = chisel.make_client(None) auth = client.request_domain_challenges(random_domain()) resp = client.deactivate_authorization(auth) if resp.body.status is not messages.STATUS_DEACTIVATED: raise Exception("unexpected authorization status") _, auth = auth_and_issue([random_domain()], client=client) resp = client.deactivate_authorization(auth[0]) if resp.body.status is not messages.STATUS_DEACTIVATED: raise Exception("unexpected authorization status")
def test_revoke_by_account(): client = chisel.make_client() cert, _ = auth_and_issue([random_domain()], client=client) client.revoke(cert.body, 0) cert_file_pem = os.path.join(tempdir, "revokeme.pem") with open(cert_file_pem, "w") as f: f.write(OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert.body.wrapped).decode()) ee_ocsp_url = "http://localhost:4002" wait_for_ocsp_revoked(cert_file_pem, "test/test-ca2.pem", ee_ocsp_url) return 0
def caa_recheck_setup(): global caa_recheck_client caa_recheck_client = chisel.make_client() # Issue a certificate with the clock set back, and save the authzs to check # later that they are valid (200). They should however require rechecking for # CAA purposes. numNames = 10 # Generate numNames subdomains of a random domain base_domain = random_domain() domains = ["{0}.{1}".format(str(n), base_domain) for n in range(numNames)] _, authzs = auth_and_issue(domains, client=caa_recheck_client) for a in authzs: caa_recheck_authzs.append(a)
def test_revoke_by_account(): client = chisel.make_client() cert_file = temppath('test_revoke_by_account.pem') cert, _ = auth_and_issue([random_domain()], client=client, cert_output=cert_file.name) reset_akamai_purges() client.revoke(cert.body, 0) verify_ocsp(cert_file.name, "/tmp/intermediate-cert-sm2-a.pem", "http://localhost:4002", "revoked") verify_akamai_purge()
def test_admin_revoker_authz(): # Make an authz, but don't attempt its challenges. authz_resource = chisel.make_client().request_domain_challenges("ar-auth-test.com") url = authz_resource.uri # Revoke authorization by domain output = run( "./bin/admin-revoker auth-revoke --config %s/admin-revoker.json ar-auth-test.com" % (default_config_dir)) if not output.rstrip().endswith("Revoked 1 pending authorizations and 0 final authorizations"): raise Exception("admin-revoker didn't revoke the expected number of pending and finalized authorizations") # Check authorization has actually been revoked response = urllib2.urlopen(url) data = json.loads(response.read()) if data['status'] != "revoked": raise Exception("Authorization wasn't revoked")
def test_http_challenge_https_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to an HTTPS # address with the same path. challengePath = "/.well-known/acme-challenge/{0}".format(token) add_http_redirect(challengePath, "https://{0}{1}".format(d, challengePath)) auth_and_issue([d], client=client, chall_type="http-01") remove_http_redirect(challengePath)
def test_revoke_by_account(): client = chisel.make_client() cert, _ = auth_and_issue([random_domain()], client=client) reset_akamai_purges() client.revoke(cert.body, 0) cert_file_pem = os.path.join(tempdir, "revokeme.pem") with open(cert_file_pem, "w") as f: f.write( OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert.body.wrapped).decode()) ee_ocsp_url = "http://localhost:4002" verify_ocsp(cert_file_pem, "/tmp/intermediate-cert-rsa-a.pem", ee_ocsp_url, "revoked") verify_akamai_purge() return 0
def test_revoke_by_account(): client = chisel.make_client() cert, _ = auth_and_issue([random_domain()], client=client) reset_akamai_purges() client.revoke(cert.body, 0) cert_file_pem = os.path.join(tempdir, "revokeme.pem") with open(cert_file_pem, "w") as f: f.write(OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert.body.wrapped).decode()) ee_ocsp_url = "http://localhost:4002" if default_config_dir.startswith("test/config-next"): verify_revocation(cert_file_pem, "test/test-ca2.pem", ee_ocsp_url) else: wait_for_ocsp_revoked(cert_file_pem, "test/test-ca2.pem", ee_ocsp_url) verify_akamai_purge() return 0
def test_account_update(): """ Create a new ACME client/account with one contact email. Then update the account to a different contact emails. """ emails=("*****@*****.**", "*****@*****.**", "*****@*****.**") client = chisel.make_client(email=emails[0]) for email in emails[1:]: result = chisel.update_email(client, email=email) # We expect one contact in the result if len(result.body.contact) != 1: raise Exception("\nUpdate account failed: expected one contact in result, got 0") # We expect it to be the email we just updated to actual = result.body.contact[0] if actual != "mailto:"+email: raise Exception("\nUpdate account failed: expected contact %s, got %s" % (email, actual))
def test_http_challenge_loop_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to itself challengePath = "/.well-known/acme-challenge/{0}".format(token) add_http_redirect(challengePath, "http://{0}{1}".format(d, challengePath)) # Issuing for the the name should fail because of the challenge domains's # redirect loop. chisel.expect_problem( "urn:acme:error:connection", lambda: auth_and_issue([d], client=client, chall_type="http-01")) remove_http_redirect(challengePath)
def test_http_challenge_loop_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to itself challengePath = "/.well-known/acme-challenge/{0}".format(token) challSrv.add_http_redirect( challengePath, "http://{0}{1}".format(d, challengePath)) # Issuing for the the name should fail because of the challenge domains's # redirect loop. chisel.expect_problem("urn:acme:error:connection", lambda: auth_and_issue([d], client=client, chall_type="http-01")) challSrv.remove_http_redirect(challengePath)
def test_http_challenge_badhost_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to a # non public hostname. challengePath = "/.well-known/acme-challenge/{0}".format(token) challSrv.add_http_redirect(challengePath, "https://example.lan{0}".format(challengePath)) # Issuing for the name should cause a connection error because the redirect # domain name is an not end in IANA registered TLD. chisel.expect_problem( "urn:acme:error:connection", lambda: auth_and_issue([d], client=client, chall_type="http-01")) challSrv.remove_http_redirect(challengePath)
def test_admin_revoker_authz(): # Make an authz, but don't attempt its challenges. authz_resource = chisel.make_client().request_domain_challenges("ar-auth-test.com") url = authz_resource.uri # Revoke authorization by domain try: output = subprocess.check_output( "./bin/admin-revoker auth-revoke --config %s/admin-revoker.json ar-auth-test.com" % (default_config_dir), shell=True) except subprocess.CalledProcessError as e: print("Failed to revoke authorization: %s", e) die(ExitStatus.RevokerFailure) if not output.rstrip().endswith("Revoked 1 pending authorizations and 0 final authorizations"): print("admin-revoker didn't revoke the expected number of pending and finalized authorizations") die(ExitStatus.RevokerFailure) # Check authorization has actually been revoked response = urllib2.urlopen(url) data = json.loads(response.read()) if data['status'] != "revoked": print("Authorization wasn't revoked") die(ExitStatus.RevokerFailure)
def test_http_challenge_badproto_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to whacky # non-http/https protocol URL. challengePath = "/.well-known/acme-challenge/{0}".format(token) add_http_redirect(challengePath, "gopher://{0}{1}".format(d, challengePath)) # Issuing for the name should cause a connection error because the redirect # URL an invalid protocol scheme. chisel.expect_problem( "urn:acme:error:connection", lambda: auth_and_issue([d], client=client, chall_type="http-01")) remove_http_redirect(challengePath)
def test_http_challenge_badhost_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to a bare IP # hostname. challengePath = "/.well-known/acme-challenge/{0}".format(token) add_http_redirect(challengePath, "https://127.0.0.1{0}".format(challengePath)) # Issuing for the name should cause a connection error because the redirect # domain name is an IP address. chisel.expect_problem( "urn:acme:error:connection", lambda: auth_and_issue([d], client=client, chall_type="http-01")) remove_http_redirect(challengePath)
def test_http_challenge_badhost_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to a bare IP # hostname. challengePath = "/.well-known/acme-challenge/{0}".format(token) challSrv.add_http_redirect( challengePath, "https://127.0.0.1{0}".format(challengePath)) # Issuing for the name should cause a connection error because the redirect # domain name is an IP address. chisel.expect_problem("urn:acme:error:connection", lambda: auth_and_issue([d], client=client, chall_type="http-01")) challSrv.remove_http_redirect(challengePath)
def test_http_challenge_badproto_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to whacky # non-http/https protocol URL. challengePath = "/.well-known/acme-challenge/{0}".format(token) challSrv.add_http_redirect( challengePath, "gopher://{0}{1}".format(d, challengePath)) # Issuing for the name should cause a connection error because the redirect # URL an invalid protocol scheme. chisel.expect_problem("urn:acme:error:connection", lambda: auth_and_issue([d], client=client, chall_type="http-01")) challSrv.remove_http_redirect(challengePath)
def test_http_challenge_http_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Calculate its keyauth so we can add it in a special non-standard location # for the redirect result resp = chall.response(client.key) keyauth = resp.key_authorization add_http01_response("http-redirect", keyauth) # Create a HTTP redirect from the challenge's validation path to some other # token path where we have registered the key authorization. challengePath = "/.well-known/acme-challenge/{0}".format(token) add_http_redirect( challengePath, "http://{0}/.well-known/acme-challenge/http-redirect".format(d)) auth_and_issue([d], client=client, chall_type="http-01") remove_http_redirect(challengePath) remove_http01_response("http-redirect")
def test_http_challenge_https_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a HTTP redirect from the challenge's validation path to an HTTPS # address with the same path. challengePath = "/.well-known/acme-challenge/{0}".format(token) add_http_redirect(challengePath, "https://{0}{1}".format(d, challengePath)) # Also add an A record for the domain pointing to the interface that the # HTTPS HTTP-01 challtestsrv is bound. urllib2.urlopen("{0}/add-a".format(challsrv_url_base), data=json.dumps({ "host": d, "addresses": ["10.77.77.77"], })).read() auth_and_issue([d], client=client, chall_type="http-01") remove_http_redirect(challengePath)
def main(): parser = argparse.ArgumentParser(description='Run integration tests') parser.add_argument('--all', dest="run_all", action="store_true", help="run all of the clients' integration tests") parser.add_argument('--certbot', dest='run_certbot', action='store_true', help="run the certbot integration tests") parser.add_argument('--chisel', dest="run_chisel", action="store_true", help="run integration tests using chisel") parser.add_argument('--load', dest="run_loadtest", action="store_true", help="run load-generator") parser.add_argument('--filter', dest="test_case_filter", action="store", help="Regex filter for test cases") parser.add_argument('--skip-setup', dest="skip_setup", action="store_true", help="skip integration test setup") # allow any ACME client to run custom command for integration # testing (without having to implement its own busy-wait loop) parser.add_argument('--custom', metavar="CMD", help="run custom command") parser.set_defaults(run_all=False, run_certbot=False, run_chisel=False, run_loadtest=False, test_case_filter="", skip_setup=False) args = parser.parse_args() if not (args.run_all or args.run_certbot or args.run_chisel or args.run_loadtest or args.custom is not None): raise Exception( "must run at least one of the letsencrypt or chisel tests with --all, --certbot, --chisel, --load or --custom" ) if not args.skip_setup: now = datetime.datetime.utcnow() seventy_days_ago = now + datetime.timedelta(days=-70) if not startservers.start(race_detection=True, fakeclock=fakeclock(seventy_days_ago)): raise Exception("startservers failed (mocking seventy days ago)") setup_seventy_days_ago() global caa_client caa_client = chisel.make_client() startservers.stop() now = datetime.datetime.utcnow() twenty_days_ago = now + datetime.timedelta(days=-20) if not startservers.start(race_detection=True, fakeclock=fakeclock(twenty_days_ago)): raise Exception("startservers failed (mocking twenty days ago)") setup_twenty_days_ago() startservers.stop() caa_acount_uri = caa_client.account.uri if caa_client is not None else None if not startservers.start(race_detection=True, account_uri=caa_acount_uri): raise Exception("startservers failed") if not args.skip_setup: setup_zero_days_ago() if args.run_all or args.run_chisel: run_chisel(args.test_case_filter) if args.run_all or args.run_certbot: run_client_tests() if args.run_all or args.run_loadtest: run_loadtest() if args.custom: run(args.custom) run_cert_checker() check_balance() run_expired_authz_purger() if not startservers.check(): raise Exception("startservers.check failed") global exit_status exit_status = 0
def main(): parser = argparse.ArgumentParser(description='Run integration tests') parser.add_argument('--all', dest="run_all", action="store_true", help="run all of the clients' integration tests") parser.add_argument('--certbot', dest='run_certbot', action='store_true', help="run the certbot integration tests") parser.add_argument('--chisel', dest="run_chisel", action="store_true", help="run integration tests using chisel") parser.add_argument('--load', dest="run_loadtest", action="store_true", help="run load-generator") parser.add_argument('--filter', dest="test_case_filter", action="store", help="Regex filter for test cases") parser.add_argument('--skip-setup', dest="skip_setup", action="store_true", help="skip integration test setup") # allow any ACME client to run custom command for integration # testing (without having to implement its own busy-wait loop) parser.add_argument('--custom', metavar="CMD", help="run custom command") parser.set_defaults(run_all=False, run_certbot=False, run_chisel=False, run_loadtest=False, test_case_filter="", skip_setup=False) args = parser.parse_args() if not (args.run_all or args.run_certbot or args.run_chisel or args.run_loadtest or args.custom is not None): raise Exception("must run at least one of the letsencrypt or chisel tests with --all, --certbot, --chisel, --load or --custom") if not args.skip_setup: now = datetime.datetime.utcnow() seventy_days_ago = now+datetime.timedelta(days=-70) if not startservers.start(race_detection=True, fakeclock=fakeclock(seventy_days_ago)): raise Exception("startservers failed (mocking seventy days ago)") setup_seventy_days_ago() global caa_client caa_client = chisel.make_client() startservers.stop() now = datetime.datetime.utcnow() twenty_days_ago = now+datetime.timedelta(days=-20) if not startservers.start(race_detection=True, fakeclock=fakeclock(twenty_days_ago)): raise Exception("startservers failed (mocking twenty days ago)") setup_twenty_days_ago() startservers.stop() caa_account_uri = caa_client.account.uri if caa_client is not None else None if not startservers.start(race_detection=True, account_uri=caa_account_uri): raise Exception("startservers failed") if not args.skip_setup: setup_zero_days_ago() setup_mock_dns(caa_account_uri) if args.run_all or args.run_chisel: run_chisel(args.test_case_filter) if args.run_all or args.run_certbot: run_client_tests() if args.run_all or args.run_loadtest: run_loadtest() if args.custom: run(args.custom) run_cert_checker() check_balance() run_expired_authz_purger() if not startservers.check(): raise Exception("startservers.check failed") global exit_status exit_status = 0
def test_http_challenge_https_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Calculate its keyauth so we can add it in a special non-standard location # for the redirect result resp = chall.response(client.key) keyauth = resp.key_authorization challSrv.add_http01_response("https-redirect", keyauth) # Create a HTTP redirect from the challenge's validation path to an HTTPS # path with some parameters challengePath = "/.well-known/acme-challenge/{0}".format(token) redirectPath = "/.well-known/acme-challenge/https-redirect?params=are&important=to¬=lose" challSrv.add_http_redirect( challengePath, "https://{0}{1}".format(d, redirectPath)) # Also add an A record for the domain pointing to the interface that the # HTTPS HTTP-01 challtestsrv is bound. challSrv.add_a_record(d, ["10.77.77.77"]) auth_and_issue([d], client=client, chall_type="http-01") challSrv.remove_http_redirect(challengePath) challSrv.remove_a_record(d) history = challSrv.http_request_history(d) challSrv.clear_http_request_history(d) # There should have been at least two GET requests made to the challtestsrv by the VA if len(history) < 2: raise Exception("Expected 2 HTTP request events on challtestsrv, found {0}".format(len(history))) initialRequests = [] redirectedRequests = [] for request in history: # Initial requests should have the expected initial HTTP-01 URL for the challenge if request['URL'] == challengePath: initialRequests.append(request) # Redirected requests should have the expected redirect path URL with all # its parameters elif request['URL'] == redirectPath: redirectedRequests.append(request) else: raise Exception("Unexpected request URL {0} in challtestsrv history: {1}".format(request['URL'], request)) # There should have been at least 1 initial HTTP-01 validation request. if len(initialRequests) < 1: raise Exception("Expected {0} initial HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(initialRequests))) # All initial requests should have been over HTTP for r in initialRequests: if r['HTTPS'] is True: raise Exception("Expected all initial requests to be HTTP") # There should have been at least 1 redirected HTTP request for each VA if len(redirectedRequests) < 1: raise Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests))) # All the redirected requests should have been over HTTPS with the correct # SNI value for r in redirectedRequests: if r['HTTPS'] is False: raise Exception("Expected all redirected requests to be HTTPS") elif r['ServerName'] != d: raise Exception("Expected all redirected requests to have ServerName {0} got \"{1}\"".format(d, r['ServerName']))
def test_http_challenge_https_redirect(): client = chisel.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Calculate its keyauth so we can add it in a special non-standard location # for the redirect result resp = chall.response(client.key) keyauth = resp.key_authorization challSrv.add_http01_response("https-redirect", keyauth) # Create a HTTP redirect from the challenge's validation path to an HTTPS # path with some parameters challengePath = "/.well-known/acme-challenge/{0}".format(token) redirectPath = "/.well-known/acme-challenge/https-redirect?params=are&important=to¬=lose" challSrv.add_http_redirect(challengePath, "https://{0}{1}".format(d, redirectPath)) # Also add an A record for the domain pointing to the interface that the # HTTPS HTTP-01 challtestsrv is bound. challSrv.add_a_record(d, ["10.77.77.77"]) auth_and_issue([d], client=client, chall_type="http-01") challSrv.remove_http_redirect(challengePath) challSrv.remove_a_record(d) history = challSrv.http_request_history(d) challSrv.clear_http_request_history(d) # There should have been at least two GET requests made to the challtestsrv by the VA if len(history) < 2: raise (Exception( "Expected 2 HTTP request events on challtestsrv, found {0}".format( len(history)))) initialRequests = [] redirectedRequests = [] for request in history: # Initial requests should have the expected initial HTTP-01 URL for the challenge if request['URL'] == challengePath: initialRequests.append(request) # Redirected requests should have the expected redirect path URL with all # its parameters elif request['URL'] == redirectPath: redirectedRequests.append(request) else: raise (Exception( "Unexpected request URL {0} in challtestsrv history: {1}". format(request['URL'], request))) # There should have been at least 1 initial HTTP-01 validation request. if len(initialRequests) < 1: raise (Exception( "Expected {0} initial HTTP-01 request events on challtestsrv, found {1}" .format(validation_attempts, len(initialRequests)))) # All initial requests should have been over HTTP for r in initialRequests: if r['HTTPS'] is True: raise (Exception("Expected all initial requests to be HTTP")) # There should have been at least 1 redirected HTTP request for each VA if len(redirectedRequests) < 1: raise (Exception( "Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}" .format(validation_attempts, len(redirectedRequests)))) # All the redirected requests should have been over HTTPS with the correct # SNI value for r in redirectedRequests: if r['HTTPS'] is False: raise (Exception("Expected all redirected requests to be HTTPS")) elif r['ServerName'] != d: raise (Exception( "Expected all redirected requests to have ServerName {0} got \"{1}\"" .format(d, r['ServerName'])))
def main(): parser = argparse.ArgumentParser(description='Run integration tests') parser.add_argument('--certbot', dest='run_certbot', action='store_true', help="run the certbot integration tests") parser.add_argument('--chisel', dest="run_chisel", action="store_true", help="run integration tests using chisel") parser.add_argument('--filter', dest="test_case_filter", action="store", help="Regex filter for test cases") # allow any ACME client to run custom command for integration # testing (without having to implement its own busy-wait loop) parser.add_argument('--custom', metavar="CMD", help="run custom command") parser.set_defaults(run_certbot=False, run_chisel=False, test_case_filter="", skip_setup=False) args = parser.parse_args() if not (args.run_certbot or args.run_chisel or args.run_loadtest or args.custom is not None): raise Exception( "must run at least one of the letsencrypt or chisel tests with --certbot, --chisel, or --custom" ) if not args.test_case_filter: now = datetime.datetime.utcnow() six_months_ago = now + datetime.timedelta(days=-30 * 6) if not startservers.start(race_detection=True, fakeclock=fakeclock(six_months_ago)): raise Exception("startservers failed (mocking six months ago)") v1_integration.caa_client = caa_client = chisel.make_client() setup_six_months_ago() startservers.stop() twenty_days_ago = now + datetime.timedelta(days=-20) if not startservers.start(race_detection=True, fakeclock=fakeclock(twenty_days_ago)): raise Exception("startservers failed (mocking twenty days ago)") setup_twenty_days_ago() startservers.stop() if not startservers.start(race_detection=True): raise Exception("startservers failed") if args.run_chisel: run_chisel(args.test_case_filter) if args.run_certbot: run_client_tests() run_go_tests() if args.custom: run(args.custom) # Skip the last-phase checks when the test case filter is one, because that # means we want to quickly iterate on a single test case. if not args.test_case_filter: run_cert_checker() check_balance() if not CONFIG_NEXT: run_expired_authz_purger() # Run the boulder-janitor. This should happen after all other tests because # it runs with the fake clock set to the future and deletes rows that may # otherwise be referenced by tests. run_janitor() # Run the load-generator last. run_loadtest will stop the # pebble-challtestsrv before running the load-generator and will not restart # it. run_loadtest() if not startservers.check(): raise Exception("startservers.check failed") global exit_status exit_status = 0
def main(): parser = argparse.ArgumentParser(description='Run integration tests') parser.add_argument('--all', dest="run_all", action="store_true", help="run all of the clients' integration tests") parser.add_argument('--certbot', dest='run_certbot', action='store_true', help="run the certbot integration tests") parser.add_argument('--chisel', dest="run_chisel", action="store_true", help="run integration tests using chisel") parser.add_argument('--load', dest="run_loadtest", action="store_true", help="run load-generator") parser.add_argument('--filter', dest="test_case_filter", action="store", help="Regex filter for test cases") parser.add_argument('--skip-setup', dest="skip_setup", action="store_true", help="skip integration test setup") # allow any ACME client to run custom command for integration # testing (without having to implement its own busy-wait loop) parser.add_argument('--custom', metavar="CMD", help="run custom command") parser.set_defaults(run_all=False, run_certbot=False, run_chisel=False, run_loadtest=False, test_case_filter="", skip_setup=False) args = parser.parse_args() if not (args.run_all or args.run_certbot or args.run_chisel or args.run_loadtest or args.custom is not None): raise Exception( "must run at least one of the letsencrypt or chisel tests with --all, --certbot, --chisel, --load or --custom" ) caa_client = None if not args.skip_setup: now = datetime.datetime.utcnow() seventy_days_ago = now + datetime.timedelta(days=-70) if not startservers.start(race_detection=True, fakeclock=fakeclock(seventy_days_ago)): raise Exception("startservers failed (mocking seventy days ago)") setup_seventy_days_ago() v1_integration.caa_client = caa_client = chisel.make_client() startservers.stop() now = datetime.datetime.utcnow() twenty_days_ago = now + datetime.timedelta(days=-20) if not startservers.start(race_detection=True, fakeclock=fakeclock(twenty_days_ago)): raise Exception("startservers failed (mocking twenty days ago)") setup_twenty_days_ago() startservers.stop() if not startservers.start(race_detection=True): raise Exception("startservers failed") if not args.skip_setup: setup_zero_days_ago() if args.run_all or args.run_chisel: run_chisel(args.test_case_filter) if args.run_all or args.run_certbot: run_client_tests() if args.custom: run(args.custom) run_cert_checker() # Skip load-balancing check when test case filter is on, since that usually # means there's a single issuance and we don't expect every RPC backend to get # traffic. if not args.test_case_filter: check_balance() if not CONFIG_NEXT: run_expired_authz_purger() # Run the load-generator last. run_loadtest will stop the # pebble-challtestsrv before running the load-generator and will not restart # it. if args.run_all or args.run_loadtest: run_loadtest() if not startservers.check(): raise Exception("startservers.check failed") global exit_status exit_status = 0