4 minutes
Enabling HTTPS in mountebank
The Why
Most of the inter-service communication is switching to, or has already switched to a secure mode. This is especially important if your objective is to achieve any compliance certification. While this is great in production, it makes testing a but difficult. Based on the availability of PKI in the company, our integration testing environment may run in a secure mode. We may be able to generate self-signed certificates for our local development environment but it requires turning off hostname verification, which isn’t a great idea.
Mountebank has rich support for HTTPS and we should be able to modify our previous remote mock setup to support communication to a dependency over https.
Generating certificates
Generating local CA
You will need to establish a local CA to start using certificates for the development environment. We will use a config file to generate certificates.
Here’s a config file for our local CA. Save it as ca.cnf
to a ca-certs
directory.
[ req ]
default_bits = 2048
default_md = sha256
prompt = no
encrypt_key = no
distinguished_name = ca_dn
x509_extensions = ca_extensions
[ ca_dn ]
C = US
O = Local Development CA
CN = dev-ca.example.com
[ ca_extensions ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = critical,CA:true
keyUsage = cRLSign,keyCertSign
And commands using openssl
to generate the CA certificate using ca.cnf
from above.
# Export a password to use for the CA key.
export CA_KEY_PWD="<some-password>"
# Generate a private key for the CA
openssl genrsa -out ./ca-cert/ca-key.pem -passout pass:$CA_KEY_PWD
# Generate the CA cert using the config file above
openssl req -new -x509 -sha256 -days 365 \
-nodes -key ./ca-cert/ca-key.pem \
-out ./ca-cert/ca-cert.pem \
-config ./ca-cert/ca.cnf \
-passin pass:$CA_KEY_PWD
Generating mountebank certificate
We will use our “Local Development CA” to obtain a certificate for our mountebank server in mb-cert
directory.
The configuration for the server
[ req ]
default_bits = 2048
default_md = sha256
prompt = no
encrypt_key = no
distinguished_name = mb_dn
req_extensions = req_extensions
[ mb_dn ]
C = US
O = Local Mountebank Server
CN = mountebank.example.com
[ req_extensions ]
subjectAltName = @alt_names
subjectKeyIdentifier = hash
keyUsage = critical, digitalSignature, keyEncipherment
# This allows the certificate to be used as a server certificate
extendedKeyUsage = serverAuth
# This section allows to define alternate names for the mountebank server
# Ideally, this will match the network aliases in your docker-compose file
# A client doing hostname validation will match its expected name to one of
# names defined here
[ alt_names ]
DNS.1 = mountebank.example.com
DNS.2 = auth.example.com
DNS.3 = telemetry.example.com
And a script to generate a server certificate
# Export a password to use for the mountebank key.
export MB_KEY_PWD="<mb-password>"
# Generate a private key for the mountebank server
openssl genrsa -out ./mb-cert/mb-key.pem -passout pass:$MB_KEY_PWD
# Create a new certificate signing request using the config above
openssl req -new -key ./mb-cert/mb-key.pem -sha256 \
-out ./mb-cert/mb-cert.csr \
-config ./mb-cert/mb.cnf \
-passin pass:$MB_KEY_PWD
# As a CA, sign the certificate request and issue a new certificate
# allowing the desired extensions
openssl x509 -req -days 365 -sha256 -in ./mb-cert/mb-cert.csr \
-CA ./ca-cert/ca-cert.pem -CAkey ./ca-cert/ca-key.pem \
-extfile ./mb-cert/mb.cnf -extensions req_extensions \
-set_serial 1 -out ./mb-cert/mb-cert.pem
# We need to remove newlines from the private key and certificate
# so they can be loaded them in our imposter.
cat ./mb-cert/mb-key.pem | awk '{printf "%s\\n", $0}' > ./mb-cert/mb-key-flat.pem
cat ./mb-cert/mb-cert.pem | awk '{printf "%s\\n", $0}' > ./mb-cert/mb-cert-flat.pem
Putting it all together
Updating the docker-compose file
We will mount the mb-certs
directory as a volume so mountebank can load them.
version: '3'
services:
account_service:
image: example/account_service:latest
container_name: account_service
volumes:
- ./logs:/app/logs
ports:
- "443:443"
networks:
account_service_network:
ipv4_address: 172.1.1.5
mountebank:
build:
context: account_service/mocks
dockerfile: Dockerfile
container_name: mountebank
ports:
- "2525:2525"
volumes:
- ./account_service/mocks/imposters.ejs:/mountebank/imposters.ejs
- ./mb-certs:/mountebank/certs # Mount the certs directory
networks:
account_service_network:
ipv4_address: 172.1.1.6
aliases:
- auth.example.com
networks:
account_service_network:
driver: bridge
ipam:
driver: default
config:
-
subnet: 172.1.1.0/24
Updating the imposter
Here’s a new imposter that uses the https
protocol and uses the new certificate we just created.
{
"port": 443,
"protocol": "https",
"key": "<% include /mountebank/certs/mb-key-flat.pem %>",
"cert": "<% include /mountebank/certs/mb-cert-flat.pem %>",
"stubs": [{
"responses": [{
"is": {
"statusCode": 200,
"body": { "auth_status": "true" }
}
}],
"predicates": [{
"equals": {
"path": "/v1/",
"method": "POST",
"headers": {
"Host": "auth.example.com",
"Content-Type": "application/json"
}
}
}]
}]
}
So far we have:
- Created a local CA and generated required certificates
- Updated the docker container configuration to make the certificates available to mountebank
- Created a new imposter that offers a server certificate with all possible remote dependency names
Wrap
It is certainly possible to mock secure endpoints of our dependencies without introducing any insecure option in our code base. Mountebank combined with the right certificates would also allow us to test dependencies that require a client certificate.
developmenttestingdockerdocker-composemountebankremote mockssoamicroservicesssltlsmtlsservice oriented architecture
790 Words
2019-03-17 22:16 +0000