integration: use python3 (#4582)
Python 2 is over in 1 month 4 days: https://pythonclock.org/ This rolls forward most of the changes in #4313. The original change was rolled back in #4323 because it broke `docker-compose up`. This change fixes those original issues by (a) making sure `requests` is installed and (b) sourcing a virtualenv containing the `requests` module before running start.py. Other notable changes in this: - Certbot has changed the developer instructions to install specific packages rather than rely on `letsencrypt-auto --os-packages-only`, so we follow suit. - Python3 now has a `bytes` type that is used in some places that used to provide `str`, and all `str` are now Unicode. That means going from `bytes` to `str` and back requires explicit `.decode()` and `.encode()`. - Moved from urllib2 to requests in many places.
This commit is contained in:
parent
211985eae7
commit
1146eecac3
|
@ -2,13 +2,16 @@ version: '3'
|
||||||
services:
|
services:
|
||||||
boulder:
|
boulder:
|
||||||
# To minimize fetching this should be the same version used below
|
# To minimize fetching this should be the same version used below
|
||||||
image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.13.2}:2019-10-17
|
image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.13.2}:2019-11-18
|
||||||
environment:
|
environment:
|
||||||
FAKE_DNS: 10.77.77.77
|
FAKE_DNS: 10.77.77.77
|
||||||
PKCS11_PROXY_SOCKET: tcp://boulder-hsm:5657
|
PKCS11_PROXY_SOCKET: tcp://boulder-hsm:5657
|
||||||
BOULDER_CONFIG_DIR: test/config
|
BOULDER_CONFIG_DIR: test/config
|
||||||
GO111MODULE: "on"
|
GO111MODULE: "on"
|
||||||
GOFLAGS: "-mod=vendor"
|
GOFLAGS: "-mod=vendor"
|
||||||
|
# This is required so Python doesn't throw an error when printing
|
||||||
|
# non-ASCII to stdout.
|
||||||
|
PYTHONIOENCODING: "utf-8"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/go/src/github.com/letsencrypt/boulder
|
- .:/go/src/github.com/letsencrypt/boulder
|
||||||
- ./.gocache:/root/.cache/go-build
|
- ./.gocache:/root/.cache/go-build
|
||||||
|
@ -55,7 +58,7 @@ services:
|
||||||
working_dir: /go/src/github.com/letsencrypt/boulder
|
working_dir: /go/src/github.com/letsencrypt/boulder
|
||||||
bhsm:
|
bhsm:
|
||||||
# To minimize fetching this should be the same version used above
|
# To minimize fetching this should be the same version used above
|
||||||
image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.13.2}:2019-10-17
|
image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.13.2}:2019-11-18
|
||||||
environment:
|
environment:
|
||||||
PKCS11_DAEMON_SOCKET: tcp://0.0.0.0:5657
|
PKCS11_DAEMON_SOCKET: tcp://0.0.0.0:5657
|
||||||
command: /usr/local/bin/pkcs11-daemon /usr/lib/softhsm/libsofthsm2.so
|
command: /usr/local/bin/pkcs11-daemon /usr/lib/softhsm/libsofthsm2.so
|
||||||
|
@ -82,7 +85,7 @@ services:
|
||||||
logging:
|
logging:
|
||||||
driver: none
|
driver: none
|
||||||
netaccess:
|
netaccess:
|
||||||
image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.13.2}:2019-10-17
|
image: letsencrypt/boulder-tools-go${TRAVIS_GO_VERSION:-1.13.2}:2019-11-18
|
||||||
environment:
|
environment:
|
||||||
GO111MODULE: "on"
|
GO111MODULE: "on"
|
||||||
GOFLAGS: "-mod=vendor"
|
GOFLAGS: "-mod=vendor"
|
||||||
|
|
4
start.py
4
start.py
|
@ -25,8 +25,8 @@ try:
|
||||||
startservers.check()
|
startservers.check()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print "\nstopping servers."
|
print("\nstopping servers.")
|
||||||
except OSError, v:
|
except OSError as v:
|
||||||
# Ignore EINTR, which happens when we get SIGTERM or SIGINT (i.e. when
|
# Ignore EINTR, which happens when we get SIGTERM or SIGINT (i.e. when
|
||||||
# someone hits Ctrl-C after running docker-compose up or start.py.
|
# someone hits Ctrl-C after running docker-compose up or start.py.
|
||||||
if v.errno != errno.EINTR:
|
if v.errno != errno.EINTR:
|
||||||
|
|
4
test.sh
4
test.sh
|
@ -103,9 +103,9 @@ if [[ "$RUN" =~ "integration" ]] ; then
|
||||||
args+=("--filter" "${INT_FILTER}")
|
args+=("--filter" "${INT_FILTER}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source ${CERTBOT_PATH:-/certbot}/${VENV_NAME:-venv}/bin/activate
|
source ${CERTBOT_PATH:-/certbot}/${VENV_NAME:-venv3}/bin/activate
|
||||||
DIRECTORY=http://boulder:4000/directory \
|
DIRECTORY=http://boulder:4000/directory \
|
||||||
python2 test/integration-test.py --chisel --gotest "${args[@]}"
|
python3 test/integration-test.py --chisel --gotest "${args[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test that just ./start.py works, which is a proxy for testing that
|
# Test that just ./start.py works, which is a proxy for testing that
|
||||||
|
|
|
@ -9,4 +9,5 @@ RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
|
||||||
WORKDIR $GOPATH
|
WORKDIR $GOPATH
|
||||||
|
|
||||||
ADD build.sh /tmp/build.sh
|
ADD build.sh /tmp/build.sh
|
||||||
|
ADD requirements.txt /tmp/requirements.txt
|
||||||
RUN /tmp/build.sh
|
RUN /tmp/build.sh
|
||||||
|
|
|
@ -16,14 +16,21 @@ apt-get install -y --no-install-recommends \
|
||||||
ruby \
|
ruby \
|
||||||
ruby-dev \
|
ruby-dev \
|
||||||
rsyslog \
|
rsyslog \
|
||||||
python3-venv \
|
|
||||||
softhsm \
|
softhsm \
|
||||||
build-essential \
|
build-essential \
|
||||||
cmake \
|
cmake \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libseccomp-dev \
|
libseccomp-dev \
|
||||||
opensc \
|
opensc \
|
||||||
unzip
|
unzip \
|
||||||
|
python3-dev \
|
||||||
|
python3-venv \
|
||||||
|
gcc \
|
||||||
|
libaugeas0 \
|
||||||
|
libssl-dev \
|
||||||
|
libffi-dev \
|
||||||
|
ca-certificates \
|
||||||
|
openssl
|
||||||
|
|
||||||
curl -L https://github.com/google/protobuf/releases/download/v3.6.1/protoc-3.6.1-linux-x86_64.zip -o /tmp/protoc.zip
|
curl -L https://github.com/google/protobuf/releases/download/v3.6.1/protoc-3.6.1-linux-x86_64.zip -o /tmp/protoc.zip
|
||||||
unzip /tmp/protoc.zip -d /usr/local/protoc
|
unzip /tmp/protoc.zip -d /usr/local/protoc
|
||||||
|
@ -48,8 +55,9 @@ go get \
|
||||||
|
|
||||||
git clone https://github.com/certbot/certbot /certbot
|
git clone https://github.com/certbot/certbot /certbot
|
||||||
cd /certbot
|
cd /certbot
|
||||||
./letsencrypt-auto --os-packages-only
|
./tools/venv3.py
|
||||||
./tools/venv.py
|
source venv3/bin/activate
|
||||||
|
pip install -r /tmp/requirements.txt
|
||||||
cd -
|
cd -
|
||||||
|
|
||||||
# Install pkcs11-proxy. Checked out commit was master HEAD at time
|
# Install pkcs11-proxy. Checked out commit was master HEAD at time
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import urllib2
|
|
||||||
import json
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
class ChallTestServer:
|
class ChallTestServer:
|
||||||
"""
|
"""
|
||||||
|
@ -41,9 +41,10 @@ class ChallTestServer:
|
||||||
self._baseURL = url
|
self._baseURL = url
|
||||||
|
|
||||||
def _postURL(self, url, body):
|
def _postURL(self, url, body):
|
||||||
return urllib2.urlopen(
|
response = requests.post(
|
||||||
url,
|
url,
|
||||||
data=json.dumps(body)).read()
|
data=json.dumps(body))
|
||||||
|
return response.text
|
||||||
|
|
||||||
def _URL(self, path):
|
def _URL(self, path):
|
||||||
urlPath = self._paths.get(path, None)
|
urlPath = self._paths.get(path, None)
|
||||||
|
|
|
@ -14,7 +14,7 @@ import socket
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import urllib2
|
import requests
|
||||||
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
@ -73,9 +73,9 @@ def update_email(client, email):
|
||||||
email.
|
email.
|
||||||
"""
|
"""
|
||||||
if client is None:
|
if client is None:
|
||||||
raise NoClientError("update_email requires a valid acme.Client argument")
|
raise(NoClientError("update_email requires a valid acme.Client argument"))
|
||||||
if email is None:
|
if email is None:
|
||||||
raise EmailRequiredError("update_email requires an email argument")
|
raise(EmailRequiredError("update_email requires an email argument"))
|
||||||
if not email.startswith("mailto:"):
|
if not email.startswith("mailto:"):
|
||||||
email = "mailto:"+ email
|
email = "mailto:"+ email
|
||||||
acct = client.account
|
acct = client.account
|
||||||
|
@ -86,7 +86,7 @@ def get_chall(authz, typ):
|
||||||
for chall_body in authz.body.challenges:
|
for chall_body in authz.body.challenges:
|
||||||
if isinstance(chall_body.chall, typ):
|
if isinstance(chall_body.chall, typ):
|
||||||
return chall_body
|
return chall_body
|
||||||
raise Exception("No %s challenge found" % typ)
|
raise(Exception("No %s challenge found" % typ))
|
||||||
|
|
||||||
class ValidationError(Exception):
|
class ValidationError(Exception):
|
||||||
"""An error that occurs during challenge validation."""
|
"""An error that occurs during challenge validation."""
|
||||||
|
@ -110,9 +110,9 @@ def issue(client, authzs, cert_output=None):
|
||||||
csr = OpenSSL.crypto.X509Req()
|
csr = OpenSSL.crypto.X509Req()
|
||||||
csr.add_extensions([
|
csr.add_extensions([
|
||||||
OpenSSL.crypto.X509Extension(
|
OpenSSL.crypto.X509Extension(
|
||||||
'subjectAltName',
|
'subjectAltName'.encode(),
|
||||||
critical=False,
|
critical=False,
|
||||||
value=', '.join('DNS:' + d for d in domains).encode()
|
value=(', '.join('DNS:' + d for d in domains)).encode()
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
csr.set_pubkey(pkey)
|
csr.set_pubkey(pkey)
|
||||||
|
@ -126,19 +126,21 @@ def issue(client, authzs, cert_output=None):
|
||||||
# If we get a PollError, pick the first failed authz and turn it into a more
|
# If we get a PollError, pick the first failed authz and turn it into a more
|
||||||
# useful ValidationError that contains details we can look for in tests.
|
# useful ValidationError that contains details we can look for in tests.
|
||||||
for authz in error.updated:
|
for authz in error.updated:
|
||||||
updated_authz = json.loads(urllib2.urlopen(authz.uri).read())
|
r = requests.get(authz.uri)
|
||||||
|
r.raise_for_status()
|
||||||
|
updated_authz = r.json()
|
||||||
domain = authz.body.identifier.value,
|
domain = authz.body.identifier.value,
|
||||||
for c in updated_authz['challenges']:
|
for c in updated_authz['challenges']:
|
||||||
if 'error' in c:
|
if 'error' in c:
|
||||||
err = c['error']
|
err = c['error']
|
||||||
raise ValidationError(domain, err['type'], err['detail'])
|
raise(ValidationError(domain, err['type'], err['detail']))
|
||||||
# If none of the authz's had an error, just re-raise.
|
# If none of the authz's had an error, just re-raise.
|
||||||
raise
|
raise
|
||||||
if cert_output is not None:
|
if cert_output is not None:
|
||||||
pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||||
cert_resource.body)
|
cert_resource.body)
|
||||||
with open(cert_output, 'w') as f:
|
with open(cert_output, 'w') as f:
|
||||||
f.write(pem)
|
f.write(pem.decode())
|
||||||
return cert_resource
|
return cert_resource
|
||||||
|
|
||||||
def http_01_answer(client, chall_body):
|
def http_01_answer(client, chall_body):
|
||||||
|
@ -223,7 +225,7 @@ def auth_and_issue(domains, chall_type="dns-01", email=None, cert_output=None, c
|
||||||
elif chall_type == "tls-alpn-01":
|
elif chall_type == "tls-alpn-01":
|
||||||
cleanup = do_tlsalpn_challenges(client, authzs)
|
cleanup = do_tlsalpn_challenges(client, authzs)
|
||||||
else:
|
else:
|
||||||
raise Exception("invalid challenge type %s" % chall_type)
|
raise(Exception("invalid challenge type %s" % chall_type))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cert_resource = issue(client, authzs, cert_output)
|
cert_resource = issue(client, authzs, cert_output)
|
||||||
|
@ -250,15 +252,15 @@ def expect_problem(problem_type, func):
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
if not ok:
|
if not ok:
|
||||||
raise Exception('Expected %s, got no error' % problem_type)
|
raise(Exception('Expected %s, got no error' % problem_type))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
domains = sys.argv[1:]
|
domains = sys.argv[1:]
|
||||||
if len(domains) == 0:
|
if len(domains) == 0:
|
||||||
print __doc__
|
print(__doc__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
try:
|
try:
|
||||||
auth_and_issue(domains)
|
auth_and_issue(domains)
|
||||||
except messages.Error, e:
|
except messages.Error as e:
|
||||||
print e
|
print(e)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
@ -15,7 +15,6 @@ import sys
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import urllib2
|
|
||||||
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
@ -189,10 +188,10 @@ if __name__ == "__main__":
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
domains = sys.argv[1:]
|
domains = sys.argv[1:]
|
||||||
if len(domains) == 0:
|
if len(domains) == 0:
|
||||||
print __doc__
|
print(__doc__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
try:
|
try:
|
||||||
auth_and_issue(domains)
|
auth_and_issue(domains)
|
||||||
except messages.Error, e:
|
except messages.Error as e:
|
||||||
print e
|
print(e)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
@ -51,7 +51,11 @@ if [ -n "${PKCS11_PROXY_SOCKET:-}" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $# -eq 0 ]]; then
|
if [[ $# -eq 0 ]]; then
|
||||||
exec ./start.py
|
# the activate script touches PS1, which is undefined, so we have to relax
|
||||||
|
# the "fail on undefined" setting here.
|
||||||
|
set +u
|
||||||
|
source ${CERTBOT_PATH:-/certbot}/${VENV_NAME:-venv3}/bin/activate
|
||||||
|
exec python3 ./start.py
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec $@
|
exec $@
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#!/usr/bin/env python2.7
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import urllib2
|
import urllib
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
|
import requests
|
||||||
import socket
|
import socket
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -38,20 +38,20 @@ def random_domain():
|
||||||
return "rand.%x.xyz" % random.randrange(2**32)
|
return "rand.%x.xyz" % random.randrange(2**32)
|
||||||
|
|
||||||
def run(cmd, **kwargs):
|
def run(cmd, **kwargs):
|
||||||
return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, **kwargs)
|
return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, **kwargs).decode()
|
||||||
|
|
||||||
def fetch_ocsp(request_bytes, url):
|
def fetch_ocsp(request_bytes, url):
|
||||||
"""Fetch an OCSP response using POST, GET, and GET with URL encoding.
|
"""Fetch an OCSP response using POST, GET, and GET with URL encoding.
|
||||||
|
|
||||||
Returns a tuple of the responses.
|
Returns a tuple of the responses.
|
||||||
"""
|
"""
|
||||||
ocsp_req_b64 = base64.b64encode(request_bytes)
|
ocsp_req_b64 = base64.b64encode(request_bytes).decode()
|
||||||
|
|
||||||
# Make the OCSP request three different ways: by POST, by GET, and by GET with
|
# Make the OCSP request three different ways: by POST, by GET, and by GET with
|
||||||
# URL-encoded parameters. All three should have an identical response.
|
# URL-encoded parameters. All three should have an identical response.
|
||||||
get_response = urllib2.urlopen("%s/%s" % (url, ocsp_req_b64)).read()
|
get_response = requests.get("%s/%s" % (url, ocsp_req_b64)).content
|
||||||
get_encoded_response = urllib2.urlopen("%s/%s" % (url, urllib2.quote(ocsp_req_b64, safe = ""))).read()
|
get_encoded_response = requests.get("%s/%s" % (url, urllib.parse.quote(ocsp_req_b64, safe = ""))).content
|
||||||
post_response = urllib2.urlopen("%s/" % (url), request_bytes).read()
|
post_response = requests.post("%s/" % (url), data=request_bytes).content
|
||||||
|
|
||||||
return (post_response, get_response, get_encoded_response)
|
return (post_response, get_response, get_encoded_response)
|
||||||
|
|
||||||
|
@ -61,13 +61,13 @@ def make_ocsp_req(cert_file, issuer_file):
|
||||||
# First generate the OCSP request in DER form
|
# First generate the OCSP request in DER form
|
||||||
run("openssl ocsp -no_nonce -issuer %s -cert %s -reqout %s" % (
|
run("openssl ocsp -no_nonce -issuer %s -cert %s -reqout %s" % (
|
||||||
issuer_file, cert_file, ocsp_req_file))
|
issuer_file, cert_file, ocsp_req_file))
|
||||||
with open(ocsp_req_file) as f:
|
with open(ocsp_req_file, mode='rb') as f:
|
||||||
ocsp_req = f.read()
|
ocsp_req = f.read()
|
||||||
return ocsp_req
|
return ocsp_req
|
||||||
|
|
||||||
def ocsp_verify(cert_file, issuer_file, ocsp_response):
|
def ocsp_verify(cert_file, issuer_file, ocsp_response):
|
||||||
ocsp_resp_file = os.path.join(tempdir, "ocsp.resp")
|
ocsp_resp_file = os.path.join(tempdir, "ocsp.resp")
|
||||||
with open(ocsp_resp_file, "w") as f:
|
with open(ocsp_resp_file, "wb") as f:
|
||||||
f.write(ocsp_response)
|
f.write(ocsp_response)
|
||||||
output = run("openssl ocsp -no_nonce -issuer %s -cert %s \
|
output = run("openssl ocsp -no_nonce -issuer %s -cert %s \
|
||||||
-verify_other %s -CAfile test/test-root.pem \
|
-verify_other %s -CAfile test/test-root.pem \
|
||||||
|
@ -76,8 +76,8 @@ def ocsp_verify(cert_file, issuer_file, ocsp_response):
|
||||||
# also look for the string "Response Verify Failure"
|
# also look for the string "Response Verify Failure"
|
||||||
verify_failure = "Response Verify Failure"
|
verify_failure = "Response Verify Failure"
|
||||||
if re.search(verify_failure, output):
|
if re.search(verify_failure, output):
|
||||||
print output
|
print(output)
|
||||||
raise Exception("OCSP verify failure")
|
raise(Exception("OCSP verify failure"))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def verify_ocsp(cert_file, issuer_file, url, status):
|
def verify_ocsp(cert_file, issuer_file, url, status):
|
||||||
|
@ -87,28 +87,28 @@ def verify_ocsp(cert_file, issuer_file, url, status):
|
||||||
# Verify all responses are the same
|
# Verify all responses are the same
|
||||||
for resp in responses:
|
for resp in responses:
|
||||||
if resp != responses[0]:
|
if resp != responses[0]:
|
||||||
raise Exception("OCSP responses differed: %s vs %s" %(
|
raise(Exception("OCSP responses differed: %s vs %s" %(
|
||||||
base64.b64encode(responses[0]), base64.b64encode(resp)))
|
base64.b64encode(responses[0]), base64.b64encode(resp))))
|
||||||
|
|
||||||
# Check response is for the correct certificate and is correct
|
# Check response is for the correct certificate and is correct
|
||||||
# status
|
# status
|
||||||
resp = responses[0]
|
resp = responses[0]
|
||||||
verify_output = ocsp_verify(cert_file, issuer_file, resp)
|
verify_output = ocsp_verify(cert_file, issuer_file, resp)
|
||||||
if not re.search("%s: %s" % (cert_file, status), verify_output):
|
if not re.search("%s: %s" % (cert_file, status), verify_output):
|
||||||
print verify_output
|
print(verify_output)
|
||||||
raise Exception("OCSP response wasn't '%s'" % status)
|
raise(Exception("OCSP response wasn't '%s'" % status))
|
||||||
|
|
||||||
def reset_akamai_purges():
|
def reset_akamai_purges():
|
||||||
urllib2.urlopen("http://localhost:6789/debug/reset-purges", "{}")
|
requests.post("http://localhost:6789/debug/reset-purges", data="{}")
|
||||||
|
|
||||||
def verify_akamai_purge():
|
def verify_akamai_purge():
|
||||||
deadline = time.time() + 0.25
|
deadline = time.time() + 0.25
|
||||||
while True:
|
while True:
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
if time.time() > deadline:
|
if time.time() > deadline:
|
||||||
raise Exception("Timed out waiting for Akamai purge")
|
raise(Exception("Timed out waiting for Akamai purge"))
|
||||||
response = urllib2.urlopen("http://localhost:6789/debug/get-purges")
|
response = requests.get("http://localhost:6789/debug/get-purges")
|
||||||
purgeData = json.load(response)
|
purgeData = response.json()
|
||||||
if len(purgeData["V3"]) is not 1:
|
if len(purgeData["V3"]) is not 1:
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
@ -150,7 +150,7 @@ def waitport(port, prog, perTickCheck=None):
|
||||||
return True
|
return True
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
if e.errno == errno.ECONNREFUSED:
|
if e.errno == errno.ECONNREFUSED:
|
||||||
print "Waiting for debug port %d (%s)" % (port, prog)
|
print("Waiting for debug port %d (%s)" % (port, prog))
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
raise Exception("timed out waiting for debug port %d (%s)" % (port, prog))
|
raise(Exception("timed out waiting for debug port %d (%s)" % (port, prog)))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python2.7
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
This file contains basic infrastructure for running the integration test cases.
|
This file contains basic infrastructure for running the integration test cases.
|
||||||
|
@ -60,13 +60,13 @@ def run_expired_authz_purger():
|
||||||
tool = "expired-authz-purger2"
|
tool = "expired-authz-purger2"
|
||||||
out = get_future_output("./bin/expired-authz-purger2 --single-run --config cmd/expired-authz-purger2/config.json", target_time)
|
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:
|
if 'via FAKECLOCK' not in out:
|
||||||
raise Exception("%s was not built with `integration` build tag" % (tool))
|
raise(Exception("%s was not built with `integration` build tag" % (tool)))
|
||||||
if num is None:
|
if num is None:
|
||||||
return
|
return
|
||||||
expected_output = 'deleted %d expired authorizations' % (num)
|
expected_output = 'deleted %d expired authorizations' % (num)
|
||||||
if expected_output not in out:
|
if expected_output not in out:
|
||||||
raise Exception("%s did not print '%s'. Output:\n%s" % (
|
raise(Exception("%s did not print '%s'. Output:\n%s" % (
|
||||||
tool, expected_output, out))
|
tool, expected_output, out)))
|
||||||
|
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ def run_janitor():
|
||||||
def get_stat_line(port, stat):
|
def get_stat_line(port, stat):
|
||||||
url = "http://localhost:%d/metrics" % port
|
url = "http://localhost:%d/metrics" % port
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
for l in response.content.split("\n"):
|
for l in response.text.split("\n"):
|
||||||
if l.strip().startswith(stat):
|
if l.strip().startswith(stat):
|
||||||
return l
|
return l
|
||||||
return None
|
return None
|
||||||
|
@ -120,7 +120,7 @@ def run_janitor():
|
||||||
def stat_value(line):
|
def stat_value(line):
|
||||||
parts = line.split(" ")
|
parts = line.split(" ")
|
||||||
if len(parts) != 2:
|
if len(parts) != 2:
|
||||||
raise Exception("stat line {0} was missing required parts".format(line))
|
raise(Exception("stat line {0} was missing required parts".format(line)))
|
||||||
return parts[1]
|
return parts[1]
|
||||||
|
|
||||||
# Wait for the janitor to finish its work. The easiest way to tell this
|
# Wait for the janitor to finish its work. The easiest way to tell this
|
||||||
|
@ -129,7 +129,7 @@ def run_janitor():
|
||||||
attempts = 0
|
attempts = 0
|
||||||
while True:
|
while True:
|
||||||
if attempts > 5:
|
if attempts > 5:
|
||||||
raise Exception("timed out waiting for janitor workbatch counts to stabilize")
|
raise(Exception("timed out waiting for janitor workbatch counts to stabilize"))
|
||||||
|
|
||||||
certStatusWorkBatch = get_stat_line(8014, statline("workbatch", "certificateStatus"))
|
certStatusWorkBatch = get_stat_line(8014, statline("workbatch", "certificateStatus"))
|
||||||
certsWorkBatch = get_stat_line(8014, statline("workbatch", "certificates"))
|
certsWorkBatch = get_stat_line(8014, statline("workbatch", "certificates"))
|
||||||
|
@ -166,7 +166,7 @@ def run_janitor():
|
||||||
|
|
||||||
for l in [certStatusDeletes, certsDeletes, certsPerNameDeletes, ordersDeletes]:
|
for l in [certStatusDeletes, certsDeletes, certsPerNameDeletes, ordersDeletes]:
|
||||||
if stat_value(l) == "0":
|
if stat_value(l) == "0":
|
||||||
raise Exception("Expected a non-zero number of deletes to be performed. Found {0}".format(l))
|
raise(Exception("Expected a non-zero number of deletes to be performed. Found {0}".format(l)))
|
||||||
|
|
||||||
# Check that all error stats are empty
|
# Check that all error stats are empty
|
||||||
errorStats = [
|
errorStats = [
|
||||||
|
@ -178,7 +178,7 @@ def run_janitor():
|
||||||
for eStat in errorStats:
|
for eStat in errorStats:
|
||||||
actual = get_stat_line(8014, eStat)
|
actual = get_stat_line(8014, eStat)
|
||||||
if actual is not None:
|
if actual is not None:
|
||||||
raise Exception("Expected to find no error stat lines but found {0}\n".format(eStat))
|
raise(Exception("Expected to find no error stat lines but found {0}\n".format(eStat)))
|
||||||
|
|
||||||
# Terminate the janitor
|
# Terminate the janitor
|
||||||
p.terminate()
|
p.terminate()
|
||||||
|
@ -221,9 +221,9 @@ def test_stats():
|
||||||
def expect_stat(port, stat):
|
def expect_stat(port, stat):
|
||||||
url = "http://localhost:%d/metrics" % port
|
url = "http://localhost:%d/metrics" % port
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
if not stat in response.content:
|
if not stat in response.text:
|
||||||
print(response.content)
|
print(response.content)
|
||||||
raise Exception("%s not present in %s" % (stat, url))
|
raise(Exception("%s not present in %s" % (stat, url)))
|
||||||
expect_stat(8000, "\nresponse_time_count{")
|
expect_stat(8000, "\nresponse_time_count{")
|
||||||
expect_stat(8000, "\ngo_goroutines ")
|
expect_stat(8000, "\ngo_goroutines ")
|
||||||
expect_stat(8000, '\ngrpc_client_handling_seconds_count{grpc_method="NewRegistration",grpc_service="ra.RegistrationAuthority",grpc_type="unary"} ')
|
expect_stat(8000, '\ngrpc_client_handling_seconds_count{grpc_method="NewRegistration",grpc_service="ra.RegistrationAuthority",grpc_type="unary"} ')
|
||||||
|
@ -251,27 +251,27 @@ def main():
|
||||||
test_case_filter="", skip_setup=False)
|
test_case_filter="", skip_setup=False)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if not (args.run_certbot or args.run_chisel or args.run_loadtest or args.custom is not None):
|
if not (args.run_certbot or args.run_chisel or args.custom is not None):
|
||||||
raise Exception("must run at least one of the letsencrypt or chisel tests with --certbot, --chisel, or --custom")
|
raise(Exception("must run at least one of the letsencrypt or chisel tests with --certbot, --chisel, or --custom"))
|
||||||
|
|
||||||
if not args.test_case_filter:
|
if not args.test_case_filter:
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
|
|
||||||
six_months_ago = now+datetime.timedelta(days=-30*6)
|
six_months_ago = now+datetime.timedelta(days=-30*6)
|
||||||
if not startservers.start(race_detection=True, fakeclock=fakeclock(six_months_ago)):
|
if not startservers.start(race_detection=True, fakeclock=fakeclock(six_months_ago)):
|
||||||
raise Exception("startservers failed (mocking six months ago)")
|
raise(Exception("startservers failed (mocking six months ago)"))
|
||||||
v1_integration.caa_client = caa_client = chisel.make_client()
|
v1_integration.caa_client = caa_client = chisel.make_client()
|
||||||
setup_six_months_ago()
|
setup_six_months_ago()
|
||||||
startservers.stop()
|
startservers.stop()
|
||||||
|
|
||||||
twenty_days_ago = now+datetime.timedelta(days=-20)
|
twenty_days_ago = now+datetime.timedelta(days=-20)
|
||||||
if not startservers.start(race_detection=True, fakeclock=fakeclock(twenty_days_ago)):
|
if not startservers.start(race_detection=True, fakeclock=fakeclock(twenty_days_ago)):
|
||||||
raise Exception("startservers failed (mocking twenty days ago)")
|
raise(Exception("startservers failed (mocking twenty days ago)"))
|
||||||
setup_twenty_days_ago()
|
setup_twenty_days_ago()
|
||||||
startservers.stop()
|
startservers.stop()
|
||||||
|
|
||||||
if not startservers.start(race_detection=True, fakeclock=None):
|
if not startservers.start(race_detection=True, fakeclock=None):
|
||||||
raise Exception("startservers failed")
|
raise(Exception("startservers failed"))
|
||||||
|
|
||||||
if args.run_chisel:
|
if args.run_chisel:
|
||||||
run_chisel(args.test_case_filter)
|
run_chisel(args.test_case_filter)
|
||||||
|
@ -304,7 +304,7 @@ def main():
|
||||||
run_loadtest()
|
run_loadtest()
|
||||||
|
|
||||||
if not startservers.check():
|
if not startservers.check():
|
||||||
raise Exception("startservers.check failed")
|
raise(Exception("startservers.check failed"))
|
||||||
|
|
||||||
check_slow_queries()
|
check_slow_queries()
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ def check_balance():
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
metrics = requests.get("http://%s/metrics" % address)
|
metrics = requests.get("http://%s/metrics" % address)
|
||||||
if not "grpc_server_handled_total" in metrics.text:
|
if not "grpc_server_handled_total" in metrics.text:
|
||||||
raise Exception("no gRPC traffic processed by %s; load balancing problem?"
|
raise(Exception("no gRPC traffic processed by %s; load balancing problem?")
|
||||||
% address)
|
% address)
|
||||||
|
|
||||||
def run_cert_checker():
|
def run_cert_checker():
|
||||||
|
@ -400,7 +400,7 @@ if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
raise Exception("%s. Output:\n%s" % (e, e.output))
|
raise(Exception("%s. Output:\n%s" % (e, e.output)))
|
||||||
|
|
||||||
@atexit.register
|
@atexit.register
|
||||||
def stop():
|
def stop():
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import atexit
|
import atexit
|
||||||
import BaseHTTPServer
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
|
@ -98,7 +97,7 @@ def start(race_detection, fakeclock):
|
||||||
print(e)
|
print(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print "All servers running. Hit ^C to kill."
|
print("All servers running. Hit ^C to kill.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check():
|
def check():
|
||||||
|
@ -116,9 +115,9 @@ def check():
|
||||||
else:
|
else:
|
||||||
busted.append(p)
|
busted.append(p)
|
||||||
if busted:
|
if busted:
|
||||||
print "\n\nThese processes exited early (check above for their output):"
|
print("\n\nThese processes exited early (check above for their output):")
|
||||||
for p in busted:
|
for p in busted:
|
||||||
print "\t'%s' with pid %d exited %d" % (p.cmd, p.pid, p.returncode)
|
print("\t'%s' with pid %d exited %d" % (p.cmd, p.pid, p.returncode))
|
||||||
processes = stillok
|
processes = stillok
|
||||||
return not busted
|
return not busted
|
||||||
|
|
||||||
|
@ -129,7 +128,7 @@ def startChallSrv():
|
||||||
"""
|
"""
|
||||||
global challSrvProcess
|
global challSrvProcess
|
||||||
if challSrvProcess is not None:
|
if challSrvProcess is not None:
|
||||||
raise Exception("startChallSrv called more than once")
|
raise(Exception("startChallSrv called more than once"))
|
||||||
|
|
||||||
# NOTE(@cpu): We specify explicit bind addresses for -https01 and
|
# NOTE(@cpu): We specify explicit bind addresses for -https01 and
|
||||||
# --tlsalpn01 here to allow HTTPS HTTP-01 responses on 5001 for on interface
|
# --tlsalpn01 here to allow HTTPS HTTP-01 responses on 5001 for on interface
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/env python2.7
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
@ -7,7 +6,6 @@ import random
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
import urllib2
|
|
||||||
|
|
||||||
import startservers
|
import startservers
|
||||||
|
|
||||||
|
@ -40,7 +38,7 @@ def rand_http_chall(client):
|
||||||
for c in authz.body.challenges:
|
for c in authz.body.challenges:
|
||||||
if isinstance(c.chall, challenges.HTTP01):
|
if isinstance(c.chall, challenges.HTTP01):
|
||||||
return d, c.chall
|
return d, c.chall
|
||||||
raise Exception("No HTTP-01 challenge found for random domain authz")
|
raise(Exception("No HTTP-01 challenge found for random domain authz"))
|
||||||
|
|
||||||
def test_http_challenge_loop_redirect():
|
def test_http_challenge_loop_redirect():
|
||||||
client = chisel.make_client()
|
client = chisel.make_client()
|
||||||
|
@ -158,7 +156,7 @@ def test_http_challenge_http_redirect():
|
||||||
# There should have been at least two GET requests made to the
|
# There should have been at least two GET requests made to the
|
||||||
# challtestsrv. There may have been more if remote VAs were configured.
|
# challtestsrv. There may have been more if remote VAs were configured.
|
||||||
if len(history) < 2:
|
if len(history) < 2:
|
||||||
raise Exception("Expected at least 2 HTTP request events on challtestsrv, found {1}".format(len(history)))
|
raise(Exception("Expected at least 2 HTTP request events on challtestsrv, found {1}".format(len(history))))
|
||||||
|
|
||||||
initialRequests = []
|
initialRequests = []
|
||||||
redirectedRequests = []
|
redirectedRequests = []
|
||||||
|
@ -166,7 +164,7 @@ def test_http_challenge_http_redirect():
|
||||||
for request in history:
|
for request in history:
|
||||||
# All requests should have been over HTTP
|
# All requests should have been over HTTP
|
||||||
if request['HTTPS'] is True:
|
if request['HTTPS'] is True:
|
||||||
raise Exception("Expected all requests to be HTTP")
|
raise(Exception("Expected all requests to be HTTP"))
|
||||||
# Initial requests should have the expected initial HTTP-01 URL for the challenge
|
# Initial requests should have the expected initial HTTP-01 URL for the challenge
|
||||||
if request['URL'] == challengePath:
|
if request['URL'] == challengePath:
|
||||||
initialRequests.append(request)
|
initialRequests.append(request)
|
||||||
|
@ -175,15 +173,15 @@ def test_http_challenge_http_redirect():
|
||||||
elif request['URL'] == redirectPath:
|
elif request['URL'] == redirectPath:
|
||||||
redirectedRequests.append(request)
|
redirectedRequests.append(request)
|
||||||
else:
|
else:
|
||||||
raise Exception("Unexpected request URL {0} in challtestsrv history: {1}".format(request['URL'], request))
|
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.
|
# There should have been at least 1 initial HTTP-01 validation request.
|
||||||
if len(initialRequests) < 1:
|
if len(initialRequests) < 1:
|
||||||
raise Exception("Expected {0} initial HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(initialRequests)))
|
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
|
# There should have been at least 1 redirected HTTP request for each VA
|
||||||
if len(redirectedRequests) < 1:
|
if len(redirectedRequests) < 1:
|
||||||
raise Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests)))
|
raise(Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests))))
|
||||||
|
|
||||||
def test_http_challenge_https_redirect():
|
def test_http_challenge_https_redirect():
|
||||||
client = chisel.make_client()
|
client = chisel.make_client()
|
||||||
|
@ -220,7 +218,7 @@ def test_http_challenge_https_redirect():
|
||||||
|
|
||||||
# There should have been at least two GET requests made to the challtestsrv by the VA
|
# There should have been at least two GET requests made to the challtestsrv by the VA
|
||||||
if len(history) < 2:
|
if len(history) < 2:
|
||||||
raise Exception("Expected 2 HTTP request events on challtestsrv, found {0}".format(len(history)))
|
raise(Exception("Expected 2 HTTP request events on challtestsrv, found {0}".format(len(history))))
|
||||||
|
|
||||||
initialRequests = []
|
initialRequests = []
|
||||||
redirectedRequests = []
|
redirectedRequests = []
|
||||||
|
@ -234,26 +232,26 @@ def test_http_challenge_https_redirect():
|
||||||
elif request['URL'] == redirectPath:
|
elif request['URL'] == redirectPath:
|
||||||
redirectedRequests.append(request)
|
redirectedRequests.append(request)
|
||||||
else:
|
else:
|
||||||
raise Exception("Unexpected request URL {0} in challtestsrv history: {1}".format(request['URL'], request))
|
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.
|
# There should have been at least 1 initial HTTP-01 validation request.
|
||||||
if len(initialRequests) < 1:
|
if len(initialRequests) < 1:
|
||||||
raise Exception("Expected {0} initial HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(initialRequests)))
|
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
|
# All initial requests should have been over HTTP
|
||||||
for r in initialRequests:
|
for r in initialRequests:
|
||||||
if r['HTTPS'] is True:
|
if r['HTTPS'] is True:
|
||||||
raise Exception("Expected all initial requests to be HTTP")
|
raise(Exception("Expected all initial requests to be HTTP"))
|
||||||
|
|
||||||
# There should have been at least 1 redirected HTTP request for each VA
|
# There should have been at least 1 redirected HTTP request for each VA
|
||||||
if len(redirectedRequests) < 1:
|
if len(redirectedRequests) < 1:
|
||||||
raise Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests)))
|
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
|
# All the redirected requests should have been over HTTPS with the correct
|
||||||
# SNI value
|
# SNI value
|
||||||
for r in redirectedRequests:
|
for r in redirectedRequests:
|
||||||
if r['HTTPS'] is False:
|
if r['HTTPS'] is False:
|
||||||
raise Exception("Expected all redirected requests to be HTTPS")
|
raise(Exception("Expected all redirected requests to be HTTPS"))
|
||||||
elif r['ServerName'] != d:
|
elif r['ServerName'] != d:
|
||||||
raise Exception("Expected all redirected requests to have ServerName {0} got \"{1}\"".format(d, r['ServerName']))
|
raise(Exception("Expected all redirected requests to have ServerName {0} got \"{1}\"".format(d, r['ServerName'])))
|
||||||
|
|
||||||
class SlowHTTPRequestHandler(BaseHTTPRequestHandler):
|
class SlowHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
|
@ -315,7 +313,7 @@ def test_http_challenge_timeout():
|
||||||
# present the timeout is 20s so adding 2s of padding = 22s)
|
# present the timeout is 20s so adding 2s of padding = 22s)
|
||||||
expectedDuration = 22
|
expectedDuration = 22
|
||||||
if delta.total_seconds() == 0 or delta.total_seconds() > expectedDuration:
|
if delta.total_seconds() == 0 or delta.total_seconds() > expectedDuration:
|
||||||
raise Exception("expected timeout to occur in under {0} seconds. Took {1}".format(expectedDuration, delta.total_seconds()))
|
raise(Exception("expected timeout to occur in under {0} seconds. Took {1}".format(expectedDuration, delta.total_seconds())))
|
||||||
|
|
||||||
def test_tls_alpn_challenge():
|
def test_tls_alpn_challenge():
|
||||||
# Pick two random domains
|
# Pick two random domains
|
||||||
|
@ -339,7 +337,7 @@ def test_issuer():
|
||||||
of length exactly 1.
|
of length exactly 1.
|
||||||
"""
|
"""
|
||||||
certr, authzs = auth_and_issue([random_domain()])
|
certr, authzs = auth_and_issue([random_domain()])
|
||||||
cert = urllib2.urlopen(certr.uri).read()
|
cert = requests.get(certr.uri).content
|
||||||
# In the future the chain URI will use HTTPS so include the root certificate
|
# In the future the chain URI will use HTTPS so include the root certificate
|
||||||
# for the WFE's PKI. Note: We use the requests library here so we honor the
|
# for the WFE's PKI. Note: We use the requests library here so we honor the
|
||||||
# REQUESTS_CA_BUNDLE passed by test.sh.
|
# REQUESTS_CA_BUNDLE passed by test.sh.
|
||||||
|
@ -382,7 +380,7 @@ def test_ct_submission():
|
||||||
def submissions(group):
|
def submissions(group):
|
||||||
count = 0
|
count = 0
|
||||||
for log in group:
|
for log in group:
|
||||||
count += int(urllib2.urlopen(log + "?hostnames=%s" % hostname).read())
|
count += int(requests.get(log + "?hostnames=%s" % hostname).text)
|
||||||
return count
|
return count
|
||||||
|
|
||||||
auth_and_issue([hostname])
|
auth_and_issue([hostname])
|
||||||
|
@ -392,29 +390,29 @@ def test_ct_submission():
|
||||||
|
|
||||||
for i in range(len(log_groups)):
|
for i in range(len(log_groups)):
|
||||||
if got[i] < expected[i]:
|
if got[i] < expected[i]:
|
||||||
raise Exception("For log group %d, got %d submissions, expected %d." %
|
raise(Exception("For log group %d, got %d submissions, expected %d." %
|
||||||
(i, got[i], expected[i]))
|
(i, got[i], expected[i])))
|
||||||
|
|
||||||
def test_expiration_mailer():
|
def test_expiration_mailer():
|
||||||
email_addr = "integration.%x@letsencrypt.org" % random.randrange(2**16)
|
email_addr = "integration.%x@letsencrypt.org" % random.randrange(2**16)
|
||||||
cert, _ = auth_and_issue([random_domain()], email=email_addr)
|
cert, _ = auth_and_issue([random_domain()], email=email_addr)
|
||||||
# Check that the expiration mailer sends a reminder
|
# Check that the expiration mailer sends a reminder
|
||||||
expiry = datetime.datetime.strptime(cert.body.get_notAfter(), '%Y%m%d%H%M%SZ')
|
expiry = datetime.datetime.strptime(cert.body.get_notAfter().decode(), '%Y%m%d%H%M%SZ')
|
||||||
no_reminder = expiry + datetime.timedelta(days=-31)
|
no_reminder = expiry + datetime.timedelta(days=-31)
|
||||||
first_reminder = expiry + datetime.timedelta(days=-13)
|
first_reminder = expiry + datetime.timedelta(days=-13)
|
||||||
last_reminder = expiry + datetime.timedelta(days=-2)
|
last_reminder = expiry + datetime.timedelta(days=-2)
|
||||||
|
|
||||||
urllib2.urlopen("http://localhost:9381/clear", data='')
|
requests.post("http://localhost:9381/clear", data='')
|
||||||
print get_future_output('./bin/expiration-mailer --config %s/expiration-mailer.json' %
|
print(get_future_output('./bin/expiration-mailer --config %s/expiration-mailer.json' %
|
||||||
config_dir, no_reminder)
|
config_dir, no_reminder))
|
||||||
print get_future_output('./bin/expiration-mailer --config %s/expiration-mailer.json' %
|
print(get_future_output('./bin/expiration-mailer --config %s/expiration-mailer.json' %
|
||||||
config_dir, first_reminder)
|
config_dir, first_reminder))
|
||||||
print get_future_output('./bin/expiration-mailer --config %s/expiration-mailer.json' %
|
print(get_future_output('./bin/expiration-mailer --config %s/expiration-mailer.json' %
|
||||||
config_dir, last_reminder)
|
config_dir, last_reminder))
|
||||||
resp = urllib2.urlopen("http://localhost:9381/count?to=%s" % email_addr)
|
resp = requests.get("http://localhost:9381/count?to=%s" % email_addr)
|
||||||
mailcount = int(resp.read())
|
mailcount = int(resp.text)
|
||||||
if mailcount != 2:
|
if mailcount != 2:
|
||||||
raise Exception("\nExpiry mailer failed: expected 2 emails, got %d" % mailcount)
|
raise(Exception("\nExpiry mailer failed: expected 2 emails, got %d" % mailcount))
|
||||||
|
|
||||||
def test_revoke_by_account():
|
def test_revoke_by_account():
|
||||||
client = chisel.make_client()
|
client = chisel.make_client()
|
||||||
|
@ -450,13 +448,13 @@ def test_recheck_caa():
|
||||||
recheck CAA and reject the request.
|
recheck CAA and reject the request.
|
||||||
"""
|
"""
|
||||||
if len(caa_recheck_authzs) == 0:
|
if len(caa_recheck_authzs) == 0:
|
||||||
raise Exception("CAA authzs not prepared for test_caa")
|
raise(Exception("CAA authzs not prepared for test_caa"))
|
||||||
domains = []
|
domains = []
|
||||||
for a in caa_recheck_authzs:
|
for a in caa_recheck_authzs:
|
||||||
response = requests.get(a.uri)
|
response = requests.get(a.uri)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise Exception("Unexpected response for CAA authz: ",
|
raise(Exception("Unexpected response for CAA authz: ",
|
||||||
response.status_code)
|
response.status_code))
|
||||||
domain = a.body.identifier.value
|
domain = a.body.identifier.value
|
||||||
domains.append(domain)
|
domains.append(domain)
|
||||||
challSrv.add_caa_issue(domain, ";")
|
challSrv.add_caa_issue(domain, ";")
|
||||||
|
@ -524,11 +522,11 @@ def test_account_update():
|
||||||
result = chisel.update_email(client, email=email)
|
result = chisel.update_email(client, email=email)
|
||||||
# We expect one contact in the result
|
# We expect one contact in the result
|
||||||
if len(result.body.contact) != 1:
|
if len(result.body.contact) != 1:
|
||||||
raise Exception("\nUpdate account failed: expected one contact in result, got 0")
|
raise(Exception("\nUpdate account failed: expected one contact in result, got 0"))
|
||||||
# We expect it to be the email we just updated to
|
# We expect it to be the email we just updated to
|
||||||
actual = result.body.contact[0]
|
actual = result.body.contact[0]
|
||||||
if actual != "mailto:"+email:
|
if actual != "mailto:"+email:
|
||||||
raise Exception("\nUpdate account failed: expected contact %s, got %s" % (email, actual))
|
raise(Exception("\nUpdate account failed: expected contact %s, got %s" % (email, actual)))
|
||||||
|
|
||||||
def test_renewal_exemption():
|
def test_renewal_exemption():
|
||||||
"""
|
"""
|
||||||
|
@ -584,13 +582,13 @@ def test_admin_revoker_cert():
|
||||||
|
|
||||||
def test_sct_embedding():
|
def test_sct_embedding():
|
||||||
certr, authzs = auth_and_issue([random_domain()])
|
certr, authzs = auth_and_issue([random_domain()])
|
||||||
certBytes = urllib2.urlopen(certr.uri).read()
|
certBytes = requests.get(certr.uri).content
|
||||||
cert = x509.load_der_x509_certificate(certBytes, default_backend())
|
cert = x509.load_der_x509_certificate(certBytes, default_backend())
|
||||||
|
|
||||||
# make sure there is no poison extension
|
# make sure there is no poison extension
|
||||||
try:
|
try:
|
||||||
cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3"))
|
cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3"))
|
||||||
raise Exception("certificate contains CT poison extension")
|
raise(Exception("certificate contains CT poison extension"))
|
||||||
except x509.ExtensionNotFound:
|
except x509.ExtensionNotFound:
|
||||||
# do nothing
|
# do nothing
|
||||||
pass
|
pass
|
||||||
|
@ -599,18 +597,18 @@ def test_sct_embedding():
|
||||||
try:
|
try:
|
||||||
sctList = cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.2"))
|
sctList = cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.2"))
|
||||||
except x509.ExtensionNotFound:
|
except x509.ExtensionNotFound:
|
||||||
raise Exception("certificate doesn't contain SCT list extension")
|
raise(Exception("certificate doesn't contain SCT list extension"))
|
||||||
if len(sctList.value) != 2:
|
if len(sctList.value) != 2:
|
||||||
raise Exception("SCT list contains wrong number of SCTs")
|
raise(Exception("SCT list contains wrong number of SCTs"))
|
||||||
for sct in sctList.value:
|
for sct in sctList.value:
|
||||||
if sct.version != x509.certificate_transparency.Version.v1:
|
if sct.version != x509.certificate_transparency.Version.v1:
|
||||||
raise Exception("SCT contains wrong version")
|
raise(Exception("SCT contains wrong version"))
|
||||||
if sct.entry_type != x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE:
|
if sct.entry_type != x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE:
|
||||||
raise Exception("SCT contains wrong entry type")
|
raise(Exception("SCT contains wrong entry type"))
|
||||||
delta = sct.timestamp - datetime.datetime.now()
|
delta = sct.timestamp - datetime.datetime.now()
|
||||||
if abs(delta) > datetime.timedelta(hours=1):
|
if abs(delta) > datetime.timedelta(hours=1):
|
||||||
raise Exception("Delta between SCT timestamp and now was too great "
|
raise(Exception("Delta between SCT timestamp and now was too great "
|
||||||
"%s vs %s (%s)" % (sct.timestamp, datetime.datetime.now(), delta))
|
"%s vs %s (%s)" % (sct.timestamp, datetime.datetime.now(), delta)))
|
||||||
|
|
||||||
def test_auth_deactivation():
|
def test_auth_deactivation():
|
||||||
client = chisel.make_client(None)
|
client = chisel.make_client(None)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/env python2.7
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Integration test cases for ACMEv2 as implemented by boulder-wfe2.
|
Integration test cases for ACMEv2 as implemented by boulder-wfe2.
|
||||||
|
@ -67,7 +66,7 @@ def rand_http_chall(client):
|
||||||
for c in a.body.challenges:
|
for c in a.body.challenges:
|
||||||
if isinstance(c.chall, challenges.HTTP01):
|
if isinstance(c.chall, challenges.HTTP01):
|
||||||
return d, c.chall
|
return d, c.chall
|
||||||
raise Exception("No HTTP-01 challenge found for random domain authz")
|
raise(Exception("No HTTP-01 challenge found for random domain authz"))
|
||||||
|
|
||||||
def check_challenge_dns_err(chalType):
|
def check_challenge_dns_err(chalType):
|
||||||
"""
|
"""
|
||||||
|
@ -104,20 +103,20 @@ def check_challenge_dns_err(chalType):
|
||||||
elif chalType == "tls-alpn-01":
|
elif chalType == "tls-alpn-01":
|
||||||
c = chisel2.get_chall(authzr, challenges.TLSALPN01)
|
c = chisel2.get_chall(authzr, challenges.TLSALPN01)
|
||||||
else:
|
else:
|
||||||
raise Exception("Invalid challenge type requested: {0}".format(challType))
|
raise(Exception("Invalid challenge type requested: {0}".format(challType)))
|
||||||
|
|
||||||
# The failed challenge's error should match expected
|
# The failed challenge's error should match expected
|
||||||
error = c.error
|
error = c.error
|
||||||
if error is None or error.typ != "urn:ietf:params:acme:error:{0}".format(expectedProbType):
|
if error is None or error.typ != "urn:ietf:params:acme:error:{0}".format(expectedProbType):
|
||||||
raise Exception("Expected {0} prob, got {1}".format(expectedProbType, error.typ))
|
raise(Exception("Expected {0} prob, got {1}".format(expectedProbType, error.typ)))
|
||||||
if not expectedProbRegex.match(error.detail):
|
if not expectedProbRegex.match(error.detail):
|
||||||
raise Exception("Prob detail did not match expectedProbRegex, got \"{0}\"".format(error.detail))
|
raise(Exception("Prob detail did not match expectedProbRegex, got \"{0}\"".format(error.detail)))
|
||||||
finally:
|
finally:
|
||||||
challSrv.remove_servfail_response(d)
|
challSrv.remove_servfail_response(d)
|
||||||
|
|
||||||
# If there was no exception that means something went wrong. The test should fail.
|
# If there was no exception that means something went wrong. The test should fail.
|
||||||
if failed is False:
|
if failed is False:
|
||||||
raise Exception("No problem generated issuing for broken DNS identifier")
|
raise(Exception("No problem generated issuing for broken DNS identifier"))
|
||||||
|
|
||||||
def test_http_challenge_dns_err():
|
def test_http_challenge_dns_err():
|
||||||
"""
|
"""
|
||||||
|
@ -171,9 +170,9 @@ def test_http_challenge_broken_redirect():
|
||||||
c = chisel2.get_chall(authzr, challenges.HTTP01)
|
c = chisel2.get_chall(authzr, challenges.HTTP01)
|
||||||
error = c.error
|
error = c.error
|
||||||
if error is None or error.typ != "urn:ietf:params:acme:error:connection":
|
if error is None or error.typ != "urn:ietf:params:acme:error:connection":
|
||||||
raise Exception("Expected connection prob, got %s" % (error.__str__()))
|
raise(Exception("Expected connection prob, got %s" % (error.__str__())))
|
||||||
if error.detail != expectedError:
|
if error.detail != expectedError:
|
||||||
raise Exception("Expected prob detail %s, got %s" % (expectedError, error.detail))
|
raise(Exception("Expected prob detail %s, got %s" % (expectedError, error.detail)))
|
||||||
|
|
||||||
challSrv.remove_http_redirect(challengePath)
|
challSrv.remove_http_redirect(challengePath)
|
||||||
|
|
||||||
|
@ -317,7 +316,7 @@ def test_http_challenge_http_redirect():
|
||||||
# There should have been at least two GET requests made to the
|
# There should have been at least two GET requests made to the
|
||||||
# challtestsrv. There may have been more if remote VAs were configured.
|
# challtestsrv. There may have been more if remote VAs were configured.
|
||||||
if len(history) < 2:
|
if len(history) < 2:
|
||||||
raise Exception("Expected at least 2 HTTP request events on challtestsrv, found {1}".format(len(history)))
|
raise(Exception("Expected at least 2 HTTP request events on challtestsrv, found {1}".format(len(history))))
|
||||||
|
|
||||||
initialRequests = []
|
initialRequests = []
|
||||||
redirectedRequests = []
|
redirectedRequests = []
|
||||||
|
@ -325,7 +324,7 @@ def test_http_challenge_http_redirect():
|
||||||
for request in history:
|
for request in history:
|
||||||
# All requests should have been over HTTP
|
# All requests should have been over HTTP
|
||||||
if request['HTTPS'] is True:
|
if request['HTTPS'] is True:
|
||||||
raise Exception("Expected all requests to be HTTP")
|
raise(Exception("Expected all requests to be HTTP"))
|
||||||
# Initial requests should have the expected initial HTTP-01 URL for the challenge
|
# Initial requests should have the expected initial HTTP-01 URL for the challenge
|
||||||
if request['URL'] == challengePath:
|
if request['URL'] == challengePath:
|
||||||
initialRequests.append(request)
|
initialRequests.append(request)
|
||||||
|
@ -334,15 +333,15 @@ def test_http_challenge_http_redirect():
|
||||||
elif request['URL'] == redirectPath:
|
elif request['URL'] == redirectPath:
|
||||||
redirectedRequests.append(request)
|
redirectedRequests.append(request)
|
||||||
else:
|
else:
|
||||||
raise Exception("Unexpected request URL {0} in challtestsrv history: {1}".format(request['URL'], request))
|
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.
|
# There should have been at least 1 initial HTTP-01 validation request.
|
||||||
if len(initialRequests) < 1:
|
if len(initialRequests) < 1:
|
||||||
raise Exception("Expected {0} initial HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(initialRequests)))
|
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
|
# There should have been at least 1 redirected HTTP request for each VA
|
||||||
if len(redirectedRequests) < 1:
|
if len(redirectedRequests) < 1:
|
||||||
raise Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests)))
|
raise(Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests))))
|
||||||
|
|
||||||
def test_http_challenge_https_redirect():
|
def test_http_challenge_https_redirect():
|
||||||
client = chisel2.make_client()
|
client = chisel2.make_client()
|
||||||
|
@ -377,7 +376,7 @@ def test_http_challenge_https_redirect():
|
||||||
error = chall.error
|
error = chall.error
|
||||||
if error:
|
if error:
|
||||||
problems.append(error.__str__())
|
problems.append(error.__str__())
|
||||||
raise Exception("validation problem: %s" % "; ".join(problems))
|
raise(Exception("validation problem: %s" % "; ".join(problems)))
|
||||||
|
|
||||||
challSrv.remove_http_redirect(challengePath)
|
challSrv.remove_http_redirect(challengePath)
|
||||||
challSrv.remove_a_record(d)
|
challSrv.remove_a_record(d)
|
||||||
|
@ -387,7 +386,7 @@ def test_http_challenge_https_redirect():
|
||||||
|
|
||||||
# There should have been at least two GET requests made to the challtestsrv by the VA
|
# There should have been at least two GET requests made to the challtestsrv by the VA
|
||||||
if len(history) < 2:
|
if len(history) < 2:
|
||||||
raise Exception("Expected 2 HTTP request events on challtestsrv, found {0}".format(len(history)))
|
raise(Exception("Expected 2 HTTP request events on challtestsrv, found {0}".format(len(history))))
|
||||||
|
|
||||||
initialRequests = []
|
initialRequests = []
|
||||||
redirectedRequests = []
|
redirectedRequests = []
|
||||||
|
@ -401,30 +400,30 @@ def test_http_challenge_https_redirect():
|
||||||
elif request['URL'] == redirectPath:
|
elif request['URL'] == redirectPath:
|
||||||
redirectedRequests.append(request)
|
redirectedRequests.append(request)
|
||||||
else:
|
else:
|
||||||
raise Exception("Unexpected request URL {0} in challtestsrv history: {1}".format(request['URL'], request))
|
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.
|
# There should have been at least 1 initial HTTP-01 validation request.
|
||||||
if len(initialRequests) < 1:
|
if len(initialRequests) < 1:
|
||||||
raise Exception("Expected {0} initial HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(initialRequests)))
|
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
|
# All initial requests should have been over HTTP
|
||||||
for r in initialRequests:
|
for r in initialRequests:
|
||||||
if r['HTTPS'] is True:
|
if r['HTTPS'] is True:
|
||||||
raise Exception("Expected all initial requests to be HTTP, got %s" % r)
|
raise(Exception("Expected all initial requests to be HTTP, got %s" % r))
|
||||||
|
|
||||||
# There should have been at least 1 redirected HTTP request for each VA
|
# There should have been at least 1 redirected HTTP request for each VA
|
||||||
if len(redirectedRequests) < 1:
|
if len(redirectedRequests) < 1:
|
||||||
raise Exception("Expected {0} redirected HTTP-01 request events on challtestsrv, found {1}".format(validation_attempts, len(redirectedRequests)))
|
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
|
# All the redirected requests should have been over HTTPS with the correct
|
||||||
# SNI value
|
# SNI value
|
||||||
for r in redirectedRequests:
|
for r in redirectedRequests:
|
||||||
if r['HTTPS'] is False:
|
if r['HTTPS'] is False:
|
||||||
raise Exception("Expected all redirected requests to be HTTPS")
|
raise(Exception("Expected all redirected requests to be HTTPS"))
|
||||||
# TODO(@cpu): The following ServerName test will fail with config-next
|
# TODO(@cpu): The following ServerName test will fail with config-next
|
||||||
# until https://github.com/letsencrypt/boulder/issues/3969 is fixed.
|
# until https://github.com/letsencrypt/boulder/issues/3969 is fixed.
|
||||||
if CONFIG_NEXT:
|
if CONFIG_NEXT:
|
||||||
return
|
return
|
||||||
elif r['ServerName'] != d:
|
elif r['ServerName'] != d:
|
||||||
raise Exception("Expected all redirected requests to have ServerName {0} got \"{1}\"".format(d, r['ServerName']))
|
raise(Exception("Expected all redirected requests to have ServerName {0} got \"{1}\"".format(d, r['ServerName'])))
|
||||||
|
|
||||||
def test_tls_alpn_challenge():
|
def test_tls_alpn_challenge():
|
||||||
# Pick two random domains
|
# Pick two random domains
|
||||||
|
@ -453,8 +452,8 @@ def test_overlapping_wildcard():
|
||||||
authzs = order.authorizations
|
authzs = order.authorizations
|
||||||
|
|
||||||
if len(authzs) != 2:
|
if len(authzs) != 2:
|
||||||
raise Exception("order for %s had %d authorizations, expected 2" %
|
raise(Exception("order for %s had %d authorizations, expected 2" %
|
||||||
(domains, len(authzs)))
|
(domains, len(authzs))))
|
||||||
|
|
||||||
cleanup = chisel2.do_dns_challenges(client, authzs)
|
cleanup = chisel2.do_dns_challenges(client, authzs)
|
||||||
try:
|
try:
|
||||||
|
@ -519,8 +518,8 @@ def test_wildcard_authz_reuse():
|
||||||
# We expect all of the returned authorizations to be pending status
|
# We expect all of the returned authorizations to be pending status
|
||||||
for authz in order.authorizations:
|
for authz in order.authorizations:
|
||||||
if authz.body.status != Status("pending"):
|
if authz.body.status != Status("pending"):
|
||||||
raise Exception("order for %s included a non-pending authorization (status: %s) from a previous HTTP-01 order" %
|
raise(Exception("order for %s included a non-pending authorization (status: %s) from a previous HTTP-01 order" %
|
||||||
((domains), str(authz.body.status)))
|
((domains), str(authz.body.status))))
|
||||||
|
|
||||||
def test_bad_overlap_wildcard():
|
def test_bad_overlap_wildcard():
|
||||||
chisel2.expect_problem("urn:ietf:params:acme:error:malformed",
|
chisel2.expect_problem("urn:ietf:params:acme:error:malformed",
|
||||||
|
@ -569,20 +568,20 @@ def test_order_reuse_failed_authz():
|
||||||
# If the poll ended and an authz's status isn't invalid then we reached the
|
# If the poll ended and an authz's status isn't invalid then we reached the
|
||||||
# deadline, fail the test
|
# deadline, fail the test
|
||||||
if not authzFailed:
|
if not authzFailed:
|
||||||
raise Exception("timed out waiting for order %s to become invalid" % firstOrderURI)
|
raise(Exception("timed out waiting for order %s to become invalid" % firstOrderURI))
|
||||||
|
|
||||||
# Make another order with the same domains
|
# Make another order with the same domains
|
||||||
order = client.new_order(csr_pem)
|
order = client.new_order(csr_pem)
|
||||||
|
|
||||||
# It should not be the same order as before
|
# It should not be the same order as before
|
||||||
if order.uri == firstOrderURI:
|
if order.uri == firstOrderURI:
|
||||||
raise Exception("new-order for %s returned a , now-invalid, order" % domains)
|
raise(Exception("new-order for %s returned a , now-invalid, order" % domains))
|
||||||
|
|
||||||
# We expect all of the returned authorizations to be pending status
|
# We expect all of the returned authorizations to be pending status
|
||||||
for authz in order.authorizations:
|
for authz in order.authorizations:
|
||||||
if authz.body.status != Status("pending"):
|
if authz.body.status != Status("pending"):
|
||||||
raise Exception("order for %s included a non-pending authorization (status: %s) from a previous order" %
|
raise(Exception("order for %s included a non-pending authorization (status: %s) from a previous order" %
|
||||||
((domains), str(authz.body.status)))
|
((domains), str(authz.body.status))))
|
||||||
|
|
||||||
# We expect the new order can be fulfilled
|
# We expect the new order can be fulfilled
|
||||||
cleanup = chisel2.do_http_challenges(client, order.authorizations)
|
cleanup = chisel2.do_http_challenges(client, order.authorizations)
|
||||||
|
@ -683,12 +682,13 @@ def test_revoke_by_privkey():
|
||||||
|
|
||||||
def test_sct_embedding():
|
def test_sct_embedding():
|
||||||
order = chisel2.auth_and_issue([random_domain()])
|
order = chisel2.auth_and_issue([random_domain()])
|
||||||
cert = x509.load_pem_x509_certificate(str(order.fullchain_pem), default_backend())
|
print(order.fullchain_pem.encode())
|
||||||
|
cert = x509.load_pem_x509_certificate(order.fullchain_pem.encode(), default_backend())
|
||||||
|
|
||||||
# make sure there is no poison extension
|
# make sure there is no poison extension
|
||||||
try:
|
try:
|
||||||
cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3"))
|
cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3"))
|
||||||
raise Exception("certificate contains CT poison extension")
|
raise(Exception("certificate contains CT poison extension"))
|
||||||
except x509.ExtensionNotFound:
|
except x509.ExtensionNotFound:
|
||||||
# do nothing
|
# do nothing
|
||||||
pass
|
pass
|
||||||
|
@ -697,14 +697,14 @@ def test_sct_embedding():
|
||||||
try:
|
try:
|
||||||
sctList = cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.2"))
|
sctList = cert.extensions.get_extension_for_oid(x509.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.2"))
|
||||||
except x509.ExtensionNotFound:
|
except x509.ExtensionNotFound:
|
||||||
raise Exception("certificate doesn't contain SCT list extension")
|
raise(Exception("certificate doesn't contain SCT list extension"))
|
||||||
if len(sctList.value) != 2:
|
if len(sctList.value) != 2:
|
||||||
raise Exception("SCT list contains wrong number of SCTs")
|
raise(Exception("SCT list contains wrong number of SCTs"))
|
||||||
for sct in sctList.value:
|
for sct in sctList.value:
|
||||||
if sct.version != x509.certificate_transparency.Version.v1:
|
if sct.version != x509.certificate_transparency.Version.v1:
|
||||||
raise Exception("SCT contains wrong version")
|
raise(Exception("SCT contains wrong version"))
|
||||||
if sct.entry_type != x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE:
|
if sct.entry_type != x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE:
|
||||||
raise Exception("SCT contains wrong entry type")
|
raise(Exception("SCT contains wrong entry type"))
|
||||||
|
|
||||||
def test_only_return_existing_reg():
|
def test_only_return_existing_reg():
|
||||||
client = chisel2.uninitialized_client()
|
client = chisel2.uninitialized_client()
|
||||||
|
@ -723,7 +723,7 @@ def test_only_return_existing_reg():
|
||||||
})
|
})
|
||||||
resp = client.net.post(client.directory['newAccount'], acct, acme_version=2)
|
resp = client.net.post(client.directory['newAccount'], acct, acme_version=2)
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
raise Exception("incorrect response returned for onlyReturnExisting")
|
raise(Exception("incorrect response returned for onlyReturnExisting"))
|
||||||
|
|
||||||
other_client = chisel2.uninitialized_client()
|
other_client = chisel2.uninitialized_client()
|
||||||
newAcct = extendedAcct({
|
newAcct = extendedAcct({
|
||||||
|
@ -770,7 +770,7 @@ def BouncerHTTPRequestHandler(redirect, guestlist):
|
||||||
self.log_message("BouncerHandler UA {0} has no requests on the Guestlist. Sending request to the curb".format(ua))
|
self.log_message("BouncerHandler UA {0} has no requests on the Guestlist. Sending request to the curb".format(ua))
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(b'(• ◡ •) <( VIPs only! )')
|
self.wfile.write(u'(• ◡ •) <( VIPs only! )')
|
||||||
|
|
||||||
BouncerHandler.guestlist = guestlist
|
BouncerHandler.guestlist = guestlist
|
||||||
BouncerHandler.redirect = redirect
|
BouncerHandler.redirect = redirect
|
||||||
|
@ -810,7 +810,7 @@ def multiva_setup(client, guestlist, domain=None):
|
||||||
if isinstance(c.chall, challenges.HTTP01):
|
if isinstance(c.chall, challenges.HTTP01):
|
||||||
chall = c.chall
|
chall = c.chall
|
||||||
if chall is None:
|
if chall is None:
|
||||||
raise Exception("No HTTP-01 challenge found for random domain authz")
|
raise(Exception("No HTTP-01 challenge found for random domain authz"))
|
||||||
|
|
||||||
token = chall.encode("token")
|
token = chall.encode("token")
|
||||||
|
|
||||||
|
@ -894,16 +894,16 @@ def test_http_multiva_threshold_fail():
|
||||||
# test needs to unpack an `acme_errors.ValidationError` on its own. It
|
# test needs to unpack an `acme_errors.ValidationError` on its own. It
|
||||||
# might be possible to clean this up in the future.
|
# might be possible to clean this up in the future.
|
||||||
if len(e.failed_authzrs) != 1:
|
if len(e.failed_authzrs) != 1:
|
||||||
raise Exception("expected one failed authz, found {0}".format(len(e.failed_authzrs)))
|
raise(Exception("expected one failed authz, found {0}".format(len(e.failed_authzrs))))
|
||||||
challs = e.failed_authzrs[0].body.challenges
|
challs = e.failed_authzrs[0].body.challenges
|
||||||
httpChall = None
|
httpChall = None
|
||||||
for chall_body in challs:
|
for chall_body in challs:
|
||||||
if isinstance(chall_body.chall, challenges.HTTP01):
|
if isinstance(chall_body.chall, challenges.HTTP01):
|
||||||
httpChall = chall_body
|
httpChall = chall_body
|
||||||
if httpChall is None:
|
if httpChall is None:
|
||||||
raise Exception("no HTTP-01 challenge in failed authz")
|
raise(Exception("no HTTP-01 challenge in failed authz"))
|
||||||
if httpChall.error.typ != "urn:ietf:params:acme:error:unauthorized":
|
if httpChall.error.typ != "urn:ietf:params:acme:error:unauthorized":
|
||||||
raise Exception("expected unauthorized prob, found {0}".format(httpChall.error.typ))
|
raise(Exception("expected unauthorized prob, found {0}".format(httpChall.error.typ)))
|
||||||
finally:
|
finally:
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
@ -945,7 +945,7 @@ def test_http_multiva_threshold_fail_account_disabled():
|
||||||
# Find the numeric ID it was assigned by the ACME server
|
# Find the numeric ID it was assigned by the ACME server
|
||||||
acctURI = client.net.account.uri
|
acctURI = client.net.account.uri
|
||||||
if len(acctURI.split("/")) < 1:
|
if len(acctURI.split("/")) < 1:
|
||||||
raise Exception("invalid account URI for newly registered account: {0}".format(acctURI))
|
raise(Exception("invalid account URI for newly registered account: {0}".format(acctURI)))
|
||||||
acctID = acctURI.split("/")[-1:][0]
|
acctID = acctURI.split("/")[-1:][0]
|
||||||
|
|
||||||
def run_query(query):
|
def run_query(query):
|
||||||
|
@ -1043,7 +1043,7 @@ def wait_for_tcp_server(addr, port):
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
try:
|
try:
|
||||||
sock.connect((addr, port))
|
sock.connect((addr, port))
|
||||||
sock.sendall("\n")
|
sock.sendall(b"\n")
|
||||||
return
|
return
|
||||||
except socket.error:
|
except socket.error:
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
@ -1085,9 +1085,9 @@ def test_http2_http01_challenge():
|
||||||
c = chisel2.get_chall(authzr, challenges.HTTP01)
|
c = chisel2.get_chall(authzr, challenges.HTTP01)
|
||||||
error = c.error
|
error = c.error
|
||||||
if error is None or error.typ != "urn:ietf:params:acme:error:connection":
|
if error is None or error.typ != "urn:ietf:params:acme:error:connection":
|
||||||
raise Exception("Expected connection prob, got %s" % (error.__str__()))
|
raise(Exception("Expected connection prob, got %s" % (error.__str__())))
|
||||||
if not error.detail.endswith(expectedError):
|
if not error.detail.endswith(expectedError):
|
||||||
raise Exception("Expected prob detail ending in %s, got %s" % (expectedError, error.detail))
|
raise(Exception("Expected prob detail ending in %s, got %s" % (expectedError, error.detail)))
|
||||||
finally:
|
finally:
|
||||||
server.shutdown()
|
server.shutdown()
|
||||||
server.server_close()
|
server.server_close()
|
||||||
|
@ -1121,26 +1121,26 @@ def test_new_order_policy_errs():
|
||||||
if e.detail != 'Error creating new order :: Cannot issue for "between-addr.in-addr.arpa": The ACME server refuses to issue a certificate for this domain name, because it is forbidden by policy (and 1 more problems. Refer to sub-problems for more information.)':
|
if e.detail != 'Error creating new order :: Cannot issue for "between-addr.in-addr.arpa": The ACME server refuses to issue a certificate for this domain name, because it is forbidden by policy (and 1 more problems. Refer to sub-problems for more information.)':
|
||||||
raise(Exception('Order problem detail did not match expected'))
|
raise(Exception('Order problem detail did not match expected'))
|
||||||
if not ok:
|
if not ok:
|
||||||
raise Exception('Expected problem, got no error')
|
raise(Exception('Expected problem, got no error'))
|
||||||
|
|
||||||
def test_long_san_no_cn():
|
def test_long_san_no_cn():
|
||||||
try:
|
try:
|
||||||
chisel2.auth_and_issue([''.join(random.choice(string.ascii_uppercase) for x in range(61)) + ".com"])
|
chisel2.auth_and_issue([''.join(random.choice(string.ascii_uppercase) for x in range(61)) + ".com"])
|
||||||
# if we get to this raise the auth_and_issue call didn't fail, so fail the test
|
# if we get to this raise the auth_and_issue call didn't fail, so fail the test
|
||||||
raise Exception("Issuance didn't fail when the only SAN in a certificate was longer than the max CN length")
|
raise(Exception("Issuance didn't fail when the only SAN in a certificate was longer than the max CN length"))
|
||||||
except messages.Error as e:
|
except messages.Error as e:
|
||||||
if e.typ != "urn:ietf:params:acme:error:badCSR":
|
if e.typ != "urn:ietf:params:acme:error:badCSR":
|
||||||
raise Exception('Expected malformed type problem, got {0}'.format(e.typ))
|
raise(Exception('Expected malformed type problem, got {0}'.format(e.typ)))
|
||||||
if e.detail != 'Error finalizing order :: issuing precertificate: CSR doesn\'t contain a SAN short enough to fit in CN':
|
if e.detail != 'Error finalizing order :: issuing precertificate: CSR doesn\'t contain a SAN short enough to fit in CN':
|
||||||
raise Exception('Problem detail did not match expected')
|
raise(Exception('Problem detail did not match expected'))
|
||||||
|
|
||||||
def test_delete_unused_challenges():
|
def test_delete_unused_challenges():
|
||||||
order = chisel2.auth_and_issue([random_domain()], chall_type="dns-01")
|
order = chisel2.auth_and_issue([random_domain()], chall_type="dns-01")
|
||||||
a = order.authorizations[0]
|
a = order.authorizations[0]
|
||||||
if len(a.body.challenges) != 1:
|
if len(a.body.challenges) != 1:
|
||||||
raise Exception("too many challenges (%d) left after validation" % len(a.body.challenges))
|
raise(Exception("too many challenges (%d) left after validation" % len(a.body.challenges)))
|
||||||
if not isinstance(a.body.challenges[0].chall, challenges.DNS01):
|
if not isinstance(a.body.challenges[0].chall, challenges.DNS01):
|
||||||
raise Exception("wrong challenge type left after validation")
|
raise(Exception("wrong challenge type left after validation"))
|
||||||
|
|
||||||
# intentionally fail a challenge
|
# intentionally fail a challenge
|
||||||
client = chisel2.make_client()
|
client = chisel2.make_client()
|
||||||
|
@ -1154,10 +1154,10 @@ def test_delete_unused_challenges():
|
||||||
break
|
break
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if len(a.body.challenges) != 1:
|
if len(a.body.challenges) != 1:
|
||||||
raise Exception("too many challenges (%d) left after failed validation" %
|
raise(Exception("too many challenges (%d) left after failed validation" %
|
||||||
len(a.body.challenges))
|
len(a.body.challenges)))
|
||||||
if not isinstance(a.body.challenges[0].chall, challenges.DNS01):
|
if not isinstance(a.body.challenges[0].chall, challenges.DNS01):
|
||||||
raise Exception("wrong challenge type left after validation")
|
raise(Exception("wrong challenge type left after validation"))
|
||||||
|
|
||||||
def test_auth_deactivation_v2():
|
def test_auth_deactivation_v2():
|
||||||
client = chisel2.make_client(None)
|
client = chisel2.make_client(None)
|
||||||
|
@ -1165,12 +1165,12 @@ def test_auth_deactivation_v2():
|
||||||
order = client.new_order(csr_pem)
|
order = client.new_order(csr_pem)
|
||||||
resp = client.deactivate_authorization(order.authorizations[0])
|
resp = client.deactivate_authorization(order.authorizations[0])
|
||||||
if resp.body.status is not messages.STATUS_DEACTIVATED:
|
if resp.body.status is not messages.STATUS_DEACTIVATED:
|
||||||
raise Exception("unexpected authorization status")
|
raise(Exception("unexpected authorization status"))
|
||||||
|
|
||||||
order = chisel2.auth_and_issue([random_domain()], client=client)
|
order = chisel2.auth_and_issue([random_domain()], client=client)
|
||||||
resp = client.deactivate_authorization(order.authorizations[0])
|
resp = client.deactivate_authorization(order.authorizations[0])
|
||||||
if resp.body.status is not messages.STATUS_DEACTIVATED:
|
if resp.body.status is not messages.STATUS_DEACTIVATED:
|
||||||
raise Exception("unexpected authorization status")
|
raise(Exception("unexpected authorization status"))
|
||||||
|
|
||||||
|
|
||||||
def check_ocsp_basic_oid(cert_file, issuer_file, url):
|
def check_ocsp_basic_oid(cert_file, issuer_file, url):
|
||||||
|
@ -1189,8 +1189,8 @@ def check_ocsp_basic_oid(cert_file, issuer_file, url):
|
||||||
expected = bytearray.fromhex("06 09 2B 06 01 05 05 07 30 01 01")
|
expected = bytearray.fromhex("06 09 2B 06 01 05 05 07 30 01 01")
|
||||||
for resp in responses:
|
for resp in responses:
|
||||||
if not expected in bytearray(resp):
|
if not expected in bytearray(resp):
|
||||||
raise Exception("Did not receive successful OCSP response: %s doesn't contain %s" %
|
raise(Exception("Did not receive successful OCSP response: %s doesn't contain %s" %
|
||||||
(base64.b64encode(resp), base64.b64encode(expected)))
|
(base64.b64encode(resp), base64.b64encode(expected))))
|
||||||
|
|
||||||
expired_cert_name = ""
|
expired_cert_name = ""
|
||||||
@register_six_months_ago
|
@register_six_months_ago
|
||||||
|
@ -1213,17 +1213,19 @@ def ocsp_exp_unauth_setup():
|
||||||
|
|
||||||
def test_ocsp_exp_unauth():
|
def test_ocsp_exp_unauth():
|
||||||
tries = 0
|
tries = 0
|
||||||
|
if expired_cert_name == "":
|
||||||
|
raise Exception("ocsp_exp_unauth_setup didn't run")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
verify_ocsp(expired_cert_name, "test/test-ca2.pem", "http://localhost:4002", "XXX")
|
verify_ocsp(expired_cert_name, "test/test-ca2.pem", "http://localhost:4002", "XXX")
|
||||||
raise Exception("Unexpected return from verify_ocsp")
|
raise(Exception("Unexpected return from verify_ocsp"))
|
||||||
except subprocess.CalledProcessError as cpe:
|
except subprocess.CalledProcessError as cpe:
|
||||||
if cpe.output == 'Responder Error: unauthorized (6)\n':
|
if cpe.output == b'Responder Error: unauthorized (6)\n':
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if tries is 5:
|
if tries is 5:
|
||||||
raise Exception("timed out waiting for unauthorized OCSP response for expired certificate")
|
raise(Exception("timed out waiting for unauthorized OCSP response for expired certificate"))
|
||||||
tries += 1
|
tries += 1
|
||||||
time.sleep(0.25)
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
@ -1247,13 +1249,13 @@ def test_blocked_key_account():
|
||||||
terms_of_service_agreed=True))
|
terms_of_service_agreed=True))
|
||||||
except acme_errors.Error as e:
|
except acme_errors.Error as e:
|
||||||
if e.typ != "urn:ietf:params:acme:error:badPublicKey":
|
if e.typ != "urn:ietf:params:acme:error:badPublicKey":
|
||||||
raise Exception("problem did not have correct error type, had {0}".format(e.typ))
|
raise(Exception("problem did not have correct error type, had {0}".format(e.typ)))
|
||||||
if e.detail != "public key is forbidden":
|
if e.detail != "public key is forbidden":
|
||||||
raise Exception("problem did not have correct error detail, had {0}".format(e.detail))
|
raise(Exception("problem did not have correct error detail, had {0}".format(e.detail)))
|
||||||
testPass = True
|
testPass = True
|
||||||
|
|
||||||
if testPass is False:
|
if testPass is False:
|
||||||
raise Exception("expected account creation to fail with Error when using blocked key")
|
raise(Exception("expected account creation to fail with Error when using blocked key"))
|
||||||
|
|
||||||
def test_blocked_key_cert():
|
def test_blocked_key_cert():
|
||||||
# Only config-next has a blocked keys file configured.
|
# Only config-next has a blocked keys file configured.
|
||||||
|
@ -1276,13 +1278,13 @@ def test_blocked_key_cert():
|
||||||
order = client.poll_and_finalize(order)
|
order = client.poll_and_finalize(order)
|
||||||
except acme_errors.Error as e:
|
except acme_errors.Error as e:
|
||||||
if e.typ != "urn:ietf:params:acme:error:badPublicKey":
|
if e.typ != "urn:ietf:params:acme:error:badPublicKey":
|
||||||
raise Exception("problem did not have correct error type, had {0}".format(e.typ))
|
raise(Exception("problem did not have correct error type, had {0}".format(e.typ)))
|
||||||
if e.detail != "Error finalizing order :: invalid public key in CSR: public key is forbidden":
|
if e.detail != "Error finalizing order :: invalid public key in CSR: public key is forbidden":
|
||||||
raise Exception("problem did not have correct error detail, had {0}".format(e.detail))
|
raise(Exception("problem did not have correct error detail, had {0}".format(e.detail)))
|
||||||
testPass = True
|
testPass = True
|
||||||
|
|
||||||
if testPass is False:
|
if testPass is False:
|
||||||
raise Exception("expected cert creation to fail with Error when using blocked key")
|
raise(Exception("expected cert creation to fail with Error when using blocked key"))
|
||||||
|
|
||||||
def run(cmd, **kwargs):
|
def run(cmd, **kwargs):
|
||||||
return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, **kwargs)
|
return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, **kwargs)
|
||||||
|
|
Loading…
Reference in New Issue