Compare commits

...

4 Commits

Author SHA1 Message Date
Brad Warren
6b90b89c0c various fixes/improvements 2020-12-21 14:18:34 -08:00
Brad Warren
1f4536d061 add missing paren 2020-12-21 12:46:53 -08:00
Brad Warren
e2acb14fc2 add --http-01-port to run_acme_server 2020-12-21 12:45:44 -08:00
Brad Warren
562610fd7e switch from external boulder to local pebble 2020-12-21 12:29:17 -08:00
12 changed files with 82 additions and 175 deletions

View File

@@ -35,7 +35,8 @@ class ACMEServer(object):
ACMEServer is also a context manager, and so can be used to ensure ACME server is
started/stopped upon context enter/exit.
"""
def __init__(self, acme_server, nodes, http_proxy=True, stdout=False, dns_server=None):
def __init__(self, acme_server, nodes, http_proxy=True, stdout=False,
dns_server=None, http_01_port=DEFAULT_HTTP_01_PORT):
"""
Create an ACMEServer instance.
:param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble)
@@ -43,6 +44,8 @@ class ACMEServer(object):
:param bool http_proxy: if False do not start the HTTP proxy
:param bool stdout: if True stream all subprocesses stdout to standard stdout
:param str dns_server: if set, Pebble/Boulder will use it to resolve domains
:param int http_01_port: port to use for http-01 validation; currently
only supported for pebble without an HTTP proxy
"""
self._construct_acme_xdist(acme_server, nodes)
@@ -52,6 +55,11 @@ class ACMEServer(object):
self._processes = [] # type: List[subprocess.Popen]
self._stdout = sys.stdout if stdout else open(os.devnull, 'w')
self._dns_server = dns_server
self._http_01_port = http_01_port
if http_01_port != DEFAULT_HTTP_01_PORT:
if self._acme_type != 'pebble' or self._proxy:
raise ValueError('setting http_01_port is not currently supported '
'with boulder or the HTTP proxy')
def start(self):
"""Start the test stack"""
@@ -134,7 +142,8 @@ class ACMEServer(object):
def _prepare_pebble_server(self):
"""Configure and launch the Pebble server"""
print('=> Starting pebble instance deployment...')
pebble_path, challtestsrv_path, pebble_config_path = pebble_artifacts.fetch(self._workspace)
pebble_artifacts_rv = pebble_artifacts.fetch(self._workspace, self._http_01_port)
pebble_path, challtestsrv_path, pebble_config_path = pebble_artifacts_rv
# Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid
# nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment.
@@ -223,7 +232,7 @@ class ACMEServer(object):
print('=> Configuring the HTTP proxy...')
mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port)
for node, port in self.acme_xdist['http_port'].items()}
command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)]
command = [sys.executable, proxy.__file__, str(DEFAULT_HTTP_01_PORT), json.dumps(mapping)]
self._launch_process(command)
print('=> Finished configuring the HTTP proxy.')
@@ -251,11 +260,14 @@ def main():
help='specify the DNS server as `IP:PORT` to use by '
'Pebble; if not specified, a local mock DNS server will be used to '
'resolve domains to localhost.')
parser.add_argument('--http-01-port', type=int, default=DEFAULT_HTTP_01_PORT,
help='specify the port to use for http-01 validation; '
'this is currently only supported for Pebble.')
args = parser.parse_args()
acme_server = ACMEServer(
args.server_type, [], http_proxy=False, stdout=True,
dns_server=args.dns_server
dns_server=args.dns_server, http_01_port=args.http_01_port,
)
try:

View File

@@ -127,7 +127,7 @@ def main():
# Default config is pebble
directory_url = os.environ.get('SERVER', PEBBLE_DIRECTORY_URL)
http_01_port = int(os.environ.get('HTTP_01_PORT', HTTP_01_PORT))
http_01_port = int(os.environ.get('HTTP_01_PORT', DEFAULT_HTTP_01_PORT))
tls_alpn_01_port = int(os.environ.get('TLS_ALPN_01_PORT', TLS_ALPN_01_PORT))
# Execution of certbot in a self-contained workspace

View File

@@ -1,5 +1,5 @@
"""Some useful constants to use throughout certbot-ci integration tests"""
HTTP_01_PORT = 5002
DEFAULT_HTTP_01_PORT = 5002
TLS_ALPN_01_PORT = 5001
CHALLTESTSRV_PORT = 8055
BOULDER_V1_DIRECTORY_URL = 'http://localhost:4000/directory'

View File

@@ -7,19 +7,19 @@ import stat
import pkg_resources
import requests
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
from certbot_integration_tests.utils.constants import DEFAULT_HTTP_01_PORT, MOCK_OCSP_SERVER_PORT
PEBBLE_VERSION = 'v2.3.0'
ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets')
def fetch(workspace):
def fetch(workspace, http_01_port=DEFAULT_HTTP_01_PORT):
# pylint: disable=missing-function-docstring
suffix = 'linux-amd64' if os.name != 'nt' else 'windows-amd64.exe'
pebble_path = _fetch_asset('pebble', suffix)
challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix)
pebble_config_path = _build_pebble_config(workspace)
pebble_config_path = _build_pebble_config(workspace, http_01_port)
return pebble_path, challtestsrv_path, pebble_config_path
@@ -38,7 +38,7 @@ def _fetch_asset(asset, suffix):
return asset_path
def _build_pebble_config(workspace):
def _build_pebble_config(workspace, http_01_port):
config_path = os.path.join(workspace, 'pebble-config.json')
with open(config_path, 'w') as file_h:
file_h.write(json.dumps({
@@ -47,7 +47,7 @@ def _build_pebble_config(workspace):
'managementListenAddress': '0.0.0.0:15000',
'certificate': os.path.join(ASSETS_PATH, 'cert.pem'),
'privateKey': os.path.join(ASSETS_PATH, 'key.pem'),
'httpPort': 5002,
'httpPort': http_01_port,
'tlsPort': 5001,
'ocspResponderURL': 'http://127.0.0.1:{0}'.format(MOCK_OCSP_SERVER_PORT),
},

View File

@@ -1,7 +1,6 @@
# letstest
Simple AWS testfarm scripts for certbot client testing
- Configures (canned) boulder server
- Launches EC2 instances with a given list of AMIs for different distros
- Copies certbot repo and puts it on the instances
- Runs certbot tests (bash scripts) on all of these
@@ -56,11 +55,6 @@ It will take a minute for these instances to shut down and become available agai
A folder named `letest-<timestamp>` is also created with a log file from each instance of the test and a file named "results" containing the output above.
The tests take quite a while to run.
Also, the way all of the tests work is to check if there is already a boulder server running and if not start one. The boulder server is left running between tests,
and there are known issues if two instances of boulder attempt to be started. After starting your first test, wait until you see "Found existing boulder server:" or if you see output
about creating a boulder server, wait a minute before starting the 2nd test. You only have to do this after starting your first session of tests or after running
the `aws ec2 terminate-instances` command above.
## Scripts
Example scripts are in the 'scripts' directory, these are just bash scripts that have a few parameters passed
to them at runtime via environment variables. test_apache2.sh is a useful reference.
@@ -73,5 +67,4 @@ See:
- https://docs.aws.amazon.com/cli/latest/userguide/cli-ec2-keypairs.html
Main repos:
- https://github.com/letsencrypt/boulder
- https://github.com/letsencrypt/letsencrypt

View File

@@ -1,4 +1,7 @@
# These images are located in us-east-1.
#
# All machines must currently use x86_64 since Pebble does not currently
# publish images for other architectures.
targets:
#-----------------------------------------------------------------------------
@@ -30,12 +33,6 @@ targets:
type: ubuntu
virt: hvm
user: admin
- ami: ami-0dcd54b7d2fff584f
name: debian10_arm64
type: ubuntu
virt: hvm
user: admin
machine_type: a1.medium
- ami: ami-003f19e0e687de1cd
name: debian9
type: ubuntu

View File

@@ -1,7 +1,6 @@
"""
Certbot Integration Test Tool
- Configures (canned) boulder server
- Launches EC2 instances with a given list of AMIs for different distros
- Copies certbot repo and puts it on the instances
- Runs certbot tests (bash scripts) on all of these
@@ -81,12 +80,6 @@ parser.add_argument('--saveinstances',
parser.add_argument('--alt_pip',
default='',
help="server from which to pull candidate release packages")
parser.add_argument('--killboulder',
action='store_true',
help="do not leave a persistent boulder server running")
parser.add_argument('--boulderonly',
action='store_true',
help="only make a boulder server")
cl_args = parser.parse_args()
# Credential Variables
@@ -98,7 +91,6 @@ PROFILE = None if cl_args.aws_profile == 'SET_BY_ENV' else cl_args.aws_profile
# Globals
#-------------------------------------------------------------------------------
BOULDER_AMI = 'ami-072a9534772bec854' # premade shared boulder AMI 18.04LTS us-east-1
SECURITY_GROUP_NAME = 'certbot-security-group'
SENTINEL = None #queue kill signal
SUBNET_NAME = 'certbot-subnet'
@@ -133,10 +125,6 @@ def make_security_group(vpc):
mysg = vpc.create_security_group(GroupName=SECURITY_GROUP_NAME,
Description='security group for automated testing')
mysg.authorize_ingress(IpProtocol="tcp", CidrIp="0.0.0.0/0", FromPort=22, ToPort=22)
mysg.authorize_ingress(IpProtocol="tcp", CidrIp="0.0.0.0/0", FromPort=80, ToPort=80)
mysg.authorize_ingress(IpProtocol="tcp", CidrIp="0.0.0.0/0", FromPort=443, ToPort=443)
# for boulder wfe (http) server
mysg.authorize_ingress(IpProtocol="tcp", CidrIp="0.0.0.0/0", FromPort=4000, ToPort=4000)
# for mosh
mysg.authorize_ingress(IpProtocol="udp", CidrIp="0.0.0.0/0", FromPort=60000, ToPort=61000)
return mysg
@@ -193,23 +181,6 @@ def _get_block_device_mappings(ec2_client, ami_id):
# Helper Routines
#-------------------------------------------------------------------------------
def block_until_http_ready(urlstring, wait_time=10, timeout=240):
"Blocks until server at urlstring can respond to http requests"
server_ready = False
t_elapsed = 0
while not server_ready and t_elapsed < timeout:
try:
sys.stdout.write('.')
sys.stdout.flush()
req = urllib_request.Request(urlstring)
response = urllib_request.urlopen(req)
#if response.code == 200:
server_ready = True
except urllib_error.URLError:
pass
time.sleep(wait_time)
t_elapsed += wait_time
def block_until_ssh_open(ipstring, wait_time=10, timeout=120):
"Blocks until server at ipstring has an open port 22"
reached = False
@@ -288,26 +259,15 @@ def deploy_script(cxn, scriptpath, *args):
args_str = ' '.join(args)
cxn.run('./'+scriptfile+' '+args_str)
def run_boulder(cxn):
boulder_path = '$GOPATH/src/github.com/letsencrypt/boulder'
cxn.run('cd %s && sudo docker-compose up -d' % boulder_path)
def config_and_launch_boulder(cxn, instance):
# yes, we're hardcoding the gopath. it's a predetermined AMI.
with cxn.prefix('export GOPATH=/home/ubuntu/gopath'):
deploy_script(cxn, 'scripts/boulder_config.sh')
run_boulder(cxn)
def install_and_launch_certbot(cxn, instance, boulder_url, target, log_dir):
def install_and_launch_certbot(cxn, instance, target, log_dir):
local_repo_to_remote(cxn, log_dir)
# This needs to be like this, I promise. 1) The env argument to run doesn't work.
# See https://github.com/fabric/fabric/issues/1744. 2) prefix() sticks an && between
# the commands, so it needs to be exports rather than no &&s in between for the script subshell.
with cxn.prefix('export BOULDER_URL=%s && export PUBLIC_IP=%s && export PRIVATE_IP=%s && '
with cxn.prefix('export PUBLIC_IP=%s && export PRIVATE_IP=%s && '
'export PUBLIC_HOSTNAME=%s && export PIP_EXTRA_INDEX_URL=%s && '
'export OS_TYPE=%s' %
(boulder_url,
instance.public_ip_address,
(instance.public_ip_address,
instance.private_ip_address,
instance.public_dns_name,
cl_args.alt_pip,
@@ -344,7 +304,7 @@ def create_client_instance(ec2_client, target, security_group_id, subnet_id, sel
self_destruct=self_destruct)
def test_client_process(fab_config, inqueue, outqueue, boulder_url, log_dir):
def test_client_process(fab_config, inqueue, outqueue, log_dir):
cur_proc = mp.current_process()
for inreq in iter(inqueue.get, SENTINEL):
ii, instance_id, target = inreq
@@ -366,7 +326,7 @@ def test_client_process(fab_config, inqueue, outqueue, boulder_url, log_dir):
with Connection(host_string, config=fab_config) as cxn:
try:
install_and_launch_certbot(cxn, instance, boulder_url, target, log_dir)
install_and_launch_certbot(cxn, instance, target, log_dir)
outqueue.put((ii, target, Status.PASS))
print("%s - %s SUCCESS"%(target['ami'], target['name']))
except:
@@ -385,15 +345,13 @@ def test_client_process(fab_config, inqueue, outqueue, boulder_url, log_dir):
pass
def cleanup(cl_args, instances, targetlist, boulder_server, log_dir):
def cleanup(cl_args, instances, targetlist, log_dir):
print('Logs in ', log_dir)
# If lengths of instances and targetlist aren't equal, instances failed to
# start before running tests so leaving instances running for debugging
# isn't very useful. Let's cleanup after ourselves instead.
if len(instances) != len(targetlist) or not cl_args.saveinstances:
print('Terminating EC2 Instances')
if cl_args.killboulder:
boulder_server.terminate()
for instance in instances:
instance.terminate()
else:
@@ -483,70 +441,18 @@ def main():
security_group_id = make_security_group(vpc).id
time.sleep(30)
boulder_preexists = False
boulder_servers = ec2_client.instances.filter(Filters=[
{'Name': 'tag:Name', 'Values': ['le-boulderserver']},
{'Name': 'instance-state-name', 'Values': ['running']}])
boulder_server = next(iter(boulder_servers), None)
print("Requesting Instances...")
if boulder_server:
print("Found existing boulder server:", boulder_server)
boulder_preexists = True
else:
print("Can't find a boulder server, starting one...")
# If we want to kill boulder on shutdown, have it self-destruct in case
# cleanup fails.
self_destruct = cl_args.killboulder
boulder_server = make_instance(ec2_client,
'le-boulderserver',
BOULDER_AMI,
KEYNAME,
machine_type='t2.micro',
#machine_type='t2.medium',
security_group_id=security_group_id,
subnet_id=subnet_id,
self_destruct=self_destruct)
instances = []
try:
if not cl_args.boulderonly:
print("Creating instances: ", end="")
# If we want to preserve instances, do not have them self-destruct.
self_destruct = not cl_args.saveinstances
for target in targetlist:
instances.append(
create_client_instance(ec2_client, target,
security_group_id, subnet_id,
self_destruct)
)
print()
# Configure and launch boulder server
#-------------------------------------------------------------------------------
print("Waiting on Boulder Server")
boulder_server = block_until_instance_ready(boulder_server)
print(" server %s"%boulder_server)
# host_string defines the ssh user and host for connection
host_string = "ubuntu@%s"%boulder_server.public_ip_address
print("Boulder Server at (SSH):", host_string)
if not boulder_preexists:
print("Configuring and Launching Boulder")
with Connection(host_string, config=fab_config) as boulder_cxn:
config_and_launch_boulder(boulder_cxn, boulder_server)
# blocking often unnecessary, but cheap EC2 VMs can get very slow
block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address,
wait_time=10, timeout=500)
boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address
print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address)
print("Boulder Server at (EC2 private ip): %s"%boulder_url)
if cl_args.boulderonly:
sys.exit(0)
print("Creating instances: ", end="")
# If we want to preserve instances, do not have them self-destruct.
self_destruct = not cl_args.saveinstances
for target in targetlist:
instances.append(
create_client_instance(ec2_client, target,
security_group_id, subnet_id,
self_destruct)
)
print()
# Install and launch client scripts in parallel
#-------------------------------------------------------------------------------
@@ -564,7 +470,7 @@ def main():
# initiate process execution
client_process_args=(fab_config, inqueue, outqueue, boulder_url, log_dir)
client_process_args=(fab_config, inqueue, outqueue, log_dir)
for i in range(num_processes):
p = mp.Process(target=test_client_process, args=client_process_args)
jobs.append(p)
@@ -615,7 +521,7 @@ def main():
sys.exit(1)
finally:
cleanup(cl_args, instances, targetlist, boulder_server, log_dir)
cleanup(cl_args, instances, targetlist, log_dir)
if __name__ == '__main__':

View File

@@ -1,24 +0,0 @@
#!/bin/bash -x
# Configures and Launches Boulder Server installed on
# us-east-1 ami-072a9534772bec854 bouldertestserver3 (boulder commit b24fe7c3ea4)
# fetch instance data from EC2 metadata service
public_host=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-hostname)
public_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-ipv4)
private_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/local-ipv4)
# set to public DNS resolver
resolver_ip=8.8.8.8
resolver=$resolver_ip':53'
# modifies integration testing boulder setup for local AWS VPC network
# connections instead of localhost
cd $GOPATH/src/github.com/letsencrypt/boulder
# change test ports to real
sed -i '/httpPort/ s/5002/80/' ./test/config/va.json
sed -i '/httpsPort/ s/5001/443/' ./test/config/va.json
sed -i '/tlsPort/ s/5001/443/' ./test/config/va.json
# set dns resolver
sed -i 's/"127.0.0.1:8053",/"'$resolver'"/' ./test/config/va.json
sed -i 's/"127.0.0.1:8054"//' ./test/config/va.json

View File

@@ -1,8 +0,0 @@
#!/bin/bash -x
# Check out special branch until latest docker changes land in Boulder master.
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
cd $BOULDERPATH
FAKE_DNS=$(ifconfig docker0 | grep "inet addr:" | cut -d: -f2 | awk '{ print $1}')
sed -i "s/FAKE_DNS: .*/FAKE_DNS: $FAKE_DNS/" docker-compose.yml
docker-compose up -d

View File

@@ -7,7 +7,7 @@ if [ "$OS_TYPE" = "ubuntu" ]
then
CONFFILE=/etc/apache2/sites-available/000-default.conf
sudo apt-get update
sudo apt-get -y --no-upgrade install apache2 #curl
sudo apt-get -y --no-upgrade install apache2 curl
sudo apt-get -y install realpath # needed for test-apache-conf
# For apache 2.4, set up ServerName
sudo sed -i '/ServerName/ s/#ServerName/ServerName/' $CONFFILE
@@ -64,11 +64,41 @@ if [ $? -ne 0 ] ; then
exit 1
fi
tools/venv3.py -e acme[dev] -e certbot[dev,docs] -e certbot-apache
tools/venv3.py -e acme[dev] -e certbot[dev,docs] -e certbot-apache -e certbot-ci
PEBBLE_LOGS="acme_server.log"
PEBBLE_URL="https://localhost:14000/dir"
# We configure Pebble to use port 80 for http-01 validation rather than an
# alternate port because:
# 1) It allows us to test with Apache configurations that are more realistic
# and closer to the default configuration on various OSes.
# 2) As of writing this, Certbot's Apache plugin requires there to be an
# existing virtual host for the port used for http-01 validation.
venv3/bin/run_acme_server --http-01-port 80 > "${PEBBLE_LOGS}" 2>&1 &
sudo "venv3/bin/certbot" -v --debug --text --agree-tos \
DumpPebbleLogs() {
if [ -f "${PEBBLE_LOGS}" ] ; then
echo "Pebble's logs were:"
cat "${PEBBLE_LOGS}"
fi
}
for n in $(seq 1 150) ; do
if curl --insecure "${PEBBLE_URL}" 2>/dev/null; then
break
else
echo "waiting for pebble"
sleep 1
fi
done
if ! curl --insecure "${PEBBLE_URL}" 2>/dev/null; then
echo "timed out waiting for pebble to start"
DumpPebbleLogs
exit 1
fi
sudo "venv3/bin/certbot" -v --debug --text --agree-tos --no-verify-ssl \
--renew-by-default --redirect --register-unsafely-without-email \
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL
--domain "${PUBLIC_HOSTNAME}" --server "${PEBBLE_URL}"
if [ $? -ne 0 ] ; then
FAIL=1
fi
@@ -90,7 +120,7 @@ fi
if [ "$OS_TYPE" = "ubuntu" ] ; then
export SERVER="$BOULDER_URL"
export SERVER="${PEBBLE_URL}"
"venv3/bin/tox" -e apacheconftest
else
echo Not running hackish apache tests on $OS_TYPE
@@ -102,5 +132,6 @@ fi
# return error if any of the subtests failed
if [ "$FAIL" = 1 ] ; then
DumpPebbleLogs
exit 1
fi

View File

@@ -1,7 +1,7 @@
#!/bin/bash -xe
set -o pipefail
# $OS_TYPE $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL
# $OS_TYPE $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME
# are dynamically set at execution
cd letsencrypt

View File

@@ -1,7 +1,7 @@
#!/bin/bash -x
set -eo pipefail
# $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL are dynamically set at execution
# $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME are dynamically set at execution
# with curl, instance metadata available from EC2 metadata service:
#public_host=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-hostname)