MockServer 9.2.0 on AWS User Guide
Overview
This guide covers launching and using the MockServer 9.2.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
MockServer mocks the HTTP and HTTPS APIs your code depends on. You tell it what to answer with expectations (a request matcher and the response to send), point your application or test suite at it, and then verify which requests your code actually sent. It can also act as a proxy that records, forwards and inspects real traffic, and it has a live web dashboard. Tests then run against a fast, predictable fake of a third-party API that is slow, rate limited, costly or not built yet.
The cloudimg image runs MockServer as a private service:
- HTTPS from the first boot. nginx serves MockServer at
https://<public-ip>/with a certificate generated on your instance's first boot. Port 80 only redirects to HTTPS. - Two separate locks. Your mocked API (every path outside
/mockserver/) needs this instance's API key in anX-Cloudimg-Keyheader. MockServer's control plane (/mockserver/...: creating expectations, verifying, retrieving) and the dashboard need the admin login, which MockServer checks too. Without them nginx answers401and MockServer never sees the request. - A private proxy. MockServer's own port,
1080(control plane, mocked API and proxy in one), listens on127.0.0.1only. You reach it through an SSH tunnel or from tests that run on the instance; it is never an open relay. - A TLS CA that is unique to your instance. To answer HTTPS through its proxy, MockServer signs certificates with a CA. MockServer's built-in CA has its private key published inside the MockServer jar, so the image does not use it: first boot generates a new CA on your instance (Step 9). Trust only this per-instance CA in your test clients: its certificate (PEM) is
/etc/mockserver/mockserver-ca.crt. Never trust MockServer's default CA (CN=www.mockserver.com). - No default login. The image contains no key, password, certificate or host name. First boot writes them to
/var/lib/cloudimg/mockserver-credentials.txt(root only). - MockServer cannot reach inside your AWS account. Forwarded requests, the proxy and remote OpenAPI or JSON schema references make MockServer open connections for you. It is blocked at the kernel level from the EC2 instance metadata service (IPv4 and IPv6), from every private and link-local range and from this instance's other loopback services, and MockServer's own private-network block is on as well. Public addresses keep working.
- MockServer runs locked in. It runs as its own unprivileged Java service user with no capabilities, a read-only file system apart from its state directory, a memory cap, and no way to run any program but Java. Templates cannot load Java classes, JavaScript templates do not run, and the endpoints that would stop the server, open new ports, write files or load extensions are refused.
- Pinned. The official MockServer 9.2.0 jar from Maven Central, checked against its SHA-256 checksum, on Ubuntu's own OpenJDK 21 runtime.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: MockServer 9.2.0 (/opt/mockserver/), OpenJDK 21 (headless runtime), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. Settings and the startup expectations are in /etc/mockserver/; the build record is in /usr/share/doc/cloudimg-mockserver/.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
- A security group that allows TCP 22 (SSH and the proxy tunnel), TCP 443 (MockServer over HTTPS) and TCP 80 (redirects to HTTPS). Open them to your own networks only. Do not open port 1080: it is not listening on the network, and the proxy is reached through SSH.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick MockServer 9.2.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, a security group as above and a subnet.
With the AWS CLI (replace the values in angle brackets; <your-mgmt-cidr> is the network you connect from, for example 203.0.113.0/24):
aws ec2 create-security-group --region <region> --vpc-id <your-vpc> \
--group-name mockserver --description "MockServer: SSH and HTTPS from my network"
for p in 22 80 443; do
aws ec2 authorize-security-group-ingress --region <region> --group-id <your-security-group> \
--protocol tcp --port $p --cidr <your-mgmt-cidr>
done
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=mockserver}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| MockServer 9.2.0 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot takes about a minute: it generates the certificate, the API key, the admin password and the TLS CA, then starts MockServer and nginx. Check that everything is running:
systemctl is-active mockserver nginx mockserver-firstboot mockserver-egress-guard
sudo test -f /var/lib/cloudimg/mockserver-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network; MockServer (port 1080) listens on loopback:
sudo ss -lntp | awk 'NR>1 && $4 !~ /^127\.0\.0\.5[34]/ {print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:1080 users:(("java"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
Step 4: Get your URL and credentials
Run sudo cat /var/lib/cloudimg/mockserver-credentials.txt to see your settings. The file is readable by root only and holds these values, all generated for this instance:
sudo sed -n 's/^\(MOCKSERVER_[A-Z0-9_]*\)=.*/\1/p' /var/lib/cloudimg/mockserver-credentials.txt
MOCKSERVER_URL
MOCKSERVER_HOST
MOCKSERVER_DASHBOARD_URL
MOCKSERVER_ADMIN_USER
MOCKSERVER_ADMIN_PASSWORD
MOCKSERVER_GATE_HEADER
MOCKSERVER_API_KEY
MOCKSERVER_LOCAL_ADDRESS
MOCKSERVER_CA_CERT
MOCKSERVER_CA_SHA256_FINGERPRINT
MOCKSERVER_TLS_CERT
MOCKSERVER_TLS_SHA256_FINGERPRINT
MOCKSERVER_URLishttps://<public-ip>/. Every path outside/mockserver/is your mocked API; sendMOCKSERVER_API_KEYin theX-Cloudimg-Keyheader (named byMOCKSERVER_GATE_HEADER).MOCKSERVER_ADMIN_USER(admin) andMOCKSERVER_ADMIN_PASSWORDare the login for the control plane (https://<public-ip>/mockserver/...) and the dashboard (MOCKSERVER_DASHBOARD_URL).MOCKSERVER_LOCAL_ADDRESSis MockServer itself,127.0.0.1:1080on the instance, which is also its proxy.MOCKSERVER_CA_CERTandMOCKSERVER_CA_SHA256_FINGERPRINTare this instance's TLS CA (Step 9).MOCKSERVER_TLS_CERTandMOCKSERVER_TLS_SHA256_FINGERPRINTare the certificate nginx serves on port 443. It is self-signed: when a client or browser reports it, compare its SHA-256 fingerprint withMOCKSERVER_TLS_SHA256_FINGERPRINTbefore you trust the connection. Both fingerprints are the SHA-256 of the certificate in DER form, as lowercase hex.
The instance's private IP address and its EC2 DNS names are served too, so clients inside your VPC can use them.
Step 5: Talk to MockServer
The commands in this guide run on the instance. Save these helper functions once; they read the root-only credentials file with sudo each time and hand the secrets to curl through file descriptors, so no key or password lands in your shell history or the process list:
cat > ~/mockserver-env.sh <<'EOF'
CF=/var/lib/cloudimg/mockserver-credentials.txt
H=$(sudo sed -n 's/^MOCKSERVER_HOST=//p' $CF)
# ctl: MockServer's control plane (/mockserver/...) through nginx, with the admin login
ctl() { curl -s --cacert /etc/ssl/mockserver/mockserver.crt --resolve "$H:443:127.0.0.1" \
-K <(sudo sed -n 's/^MOCKSERVER_ADMIN_PASSWORD=\(.*\)/user = "admin:\1"/p' $CF) "$@"; }
# mock: your mocked API (every other path) through nginx, with this instance's API key
mock() { curl -s --cacert /etc/ssl/mockserver/mockserver.crt --resolve "$H:443:127.0.0.1" \
-K <(sudo sed -n 's/^MOCKSERVER_API_KEY=\(.*\)/header = "X-Cloudimg-Key: \1"/p' $CF) "$@"; }
# pxy: a request through MockServer's proxy on the instance, trusting only this instance's CA
pxy() { curl -s -x http://127.0.0.1:1080 --cacert /etc/mockserver/mockserver-ca.crt "$@"; }
EOF
Ask MockServer for its status. jq -e prints the answer and fails if it is not what you expect:
. ~/mockserver-env.sh
ctl -X PUT "https://$H/mockserver/status" | jq -e 'select(.version == "9.2.0" and .ports == [1080])'
{
"artifactId": "mockserver-core",
"gitHash": "789142c1",
"groupId": "org.mock-server",
"ports": [
1080
],
"version": "9.2.0"
}
Without the login, nginx answers 401 with a Basic challenge; without the key, the mocked API answers 401 with its own CloudimgKey challenge:
. ~/mockserver-env.sh
C=(curl -s --cacert /etc/ssl/mockserver/mockserver.crt --resolve "$H:443:127.0.0.1")
"${C[@]}" -o /dev/null -w 'no login: %{http_code} %header{www-authenticate}\n' -X PUT "https://$H/mockserver/status"
"${C[@]}" -o /dev/null -w 'no key: %{http_code} %header{www-authenticate}\n' "https://$H/cloudimg-sample/widgets"
no login: 401 Basic realm="MockServer (cloudimg)", charset="UTF-8"
no key: 401 CloudimgKey realm="MockServer", header="X-Cloudimg-Key"
Step 6: Call the sample API
The image starts with three sample expectations for a made-up widgets API under /cloudimg-sample/ (loaded at every start from /etc/mockserver/initialization.json; a copy is in /usr/share/doc/cloudimg-mockserver/cloudimg-sample-expectations.json). Every answer below comes from MockServer:
. ~/mockserver-env.sh
mock "https://$H/cloudimg-sample/widgets" | jq -e 'select(.[0].name == "Acme Sprocket")'
mock "https://$H/cloudimg-sample/widgets/7" | jq -e 'select(.id == 7)'
mock -H 'Content-Type: application/json' -d '{"name":"Gear"}' "https://$H/cloudimg-sample/widgets" | jq -e 'select(.created == true)'
mock -o /dev/null -w 'no expectation for this path: %{http_code}\n' "https://$H/nothing/here"
[
{
"id": 1,
"name": "Acme Sprocket",
"color": "blue"
},
{
"id": 2,
"name": "Acme Cog",
"color": "green"
}
]
{
"id": 7,
"method": "GET",
"servedBy": "velocity template"
}
{
"id": 3,
"created": true
}
no expectation for this path: 404
The second answer is a Velocity template: MockServer copies the {id} path parameter into the response. A request that matches no expectation gets 404.
Step 7: Create, verify and clear your own expectations
An expectation is a JSON document with an httpRequest matcher and an httpResponse. Create one with PUT /mockserver/expectation, call it, then check how often your code called it with PUT /mockserver/verify (202 means the verification passed, 406 that it failed and why). This example mocks a small Todo API:
. ~/mockserver-env.sh
cd "$(mktemp -d)"
cat > todo-expectation.json <<'JSON'
{
"id": "todo-list",
"httpRequest": {"method": "GET", "path": "/todos"},
"httpResponse": {
"statusCode": 200,
"headers": {"Content-Type": ["application/json"]},
"body": {"type": "JSON", "json": [{"id": 1, "title": "Write the expectation", "done": true}]}
}
}
JSON
ctl -X PUT -H 'Content-Type: application/json' --data-binary @todo-expectation.json "https://$H/mockserver/expectation" | jq -e '.[0].id'
mock "https://$H/todos" | jq -e 'select(.[0].title == "Write the expectation")'
mock -o /dev/null "https://$H/todos"
ctl -X PUT -H 'Content-Type: application/json' -d '{"httpRequest":{"path":"/todos"},"times":{"atLeast":2}}' -o /dev/null -w 'verify at least 2 calls: %{http_code}\n' "https://$H/mockserver/verify"
ctl -X PUT -H 'Content-Type: application/json' -d '{"httpRequest":{"path":"/todos"},"times":{"atLeast":5}}' -o /dev/null -w 'verify at least 5 calls: %{http_code}\n' "https://$H/mockserver/verify"
"todo-list"
[
{
"id": 1,
"title": "Write the expectation",
"done": true
}
]
verify at least 2 calls: 202
verify at least 5 calls: 406
Without -o /dev/null a failed verification explains itself: how many matching requests MockServer recorded and the closest requests that did not match.
MockServer records every request it receives. Retrieve them to see exactly what your code sent. nginx removes the X-Cloudimg-Key header before the request reaches MockServer, so the key is never recorded:
. ~/mockserver-env.sh
ctl -X PUT -H 'Content-Type: application/json' -d '{"path":"/todos"}' "https://$H/mockserver/retrieve?type=REQUESTS&format=JSON" | jq -e 'length >= 2'
ctl -X PUT -H 'Content-Type: application/json' -d '{"path":"/todos"}' "https://$H/mockserver/retrieve?type=REQUESTS&format=JSON" | jq -e '[.[].headers | keys[] | ascii_downcase] | any(. == "x-cloudimg-key") | not'
true
true
Remove one expectation by its id with PUT /mockserver/clear?type=EXPECTATIONS; the sample expectations stay:
. ~/mockserver-env.sh
ctl -X PUT -H 'Content-Type: application/json' -d '{"id":"todo-list"}' -o /dev/null -w 'clear: %{http_code}\n' "https://$H/mockserver/clear?type=EXPECTATIONS"
mock -o /dev/null -w 'after clear: %{http_code}\n' "https://$H/todos"
ctl -X PUT "https://$H/mockserver/retrieve?type=ACTIVE_EXPECTATIONS" | jq -r '.[].id'
clear: 200
after clear: 404
cloudimg-sample-list-widgets
cloudimg-sample-get-widget
cloudimg-sample-create-widget
MockServer keeps expectations and recorded requests in memory. PUT /mockserver/reset removes everything, the samples included, and a restart reloads /etc/mockserver/initialization.json. To keep your own expectations across restarts, add them to that file (sudo install -m 0644 my-expectations.json /etc/mockserver/initialization.json, a JSON array of expectations) and run sudo systemctl restart mockserver. Request bodies are limited to 10 MB. See the MockServer documentation for request matchers, response templates, delays, callbacks and OpenAPI import.
Step 8: Open the dashboard
MockServer's dashboard shows the log, the active expectations, the requests it received and the requests it proxied, live. Open MOCKSERVER_DASHBOARD_URL (https://<public-ip>/mockserver/dashboard) in your browser and sign in as admin with MOCKSERVER_ADMIN_PASSWORD. The browser warns about the self-signed certificate: check that its SHA-256 fingerprint matches MOCKSERVER_TLS_SHA256_FINGERPRINT before you continue (or use your own domain and certificate, Step 15).

Open Dashboard shows the live view. The log on the left includes a forwarded request that MockServer's private-network block refused (Step 10):

Click an expectation to see its JSON:

The dashboard only works on this instance's own address: other web sites cannot drive it, and its "Proxied Requests" hint about sending proxy traffic to port 443 does not apply here. The proxy is 127.0.0.1:1080 through SSH (Step 11).
Step 9: HTTPS through the proxy and your instance's CA
Point an application at MockServer as its HTTP proxy and MockServer answers with your expectations, even for host names that do not exist. For HTTPS, MockServer acts as a man in the middle: it makes a certificate for the host your client asked for and signs it with a certificate authority (CA). Your client accepts that certificate only if it trusts the CA.
The CA is unique to your instance. MockServer's built-in CA is the same for every MockServer user and its private key is published inside the MockServer jar, so a client that trusts it would accept a certificate for any web site from anyone. This image never uses it, and MockServer refuses to start with it. First boot generates a new CA on your instance (RSA 3072, valid for 10 years, allowed to sign server certificates only):
- The certificate (public, safe to copy) is
/etc/mockserver/mockserver-ca.crt. Its SHA-256 fingerprint isMOCKSERVER_CA_SHA256_FINGERPRINTin the credentials file. - The private key never leaves the instance. It is
/etc/mockserver/ca/ca.key, mode 0600, owned by themockserverservice user, inside a directory only that user and root can open. It is not in the credentials file, nothing prints it, and you never need to copy it. The per-host certificates MockServer signs with it are kept in memory only.
openssl x509 -in /etc/mockserver/mockserver-ca.crt -noout -subject -ext basicConstraints | sed -E 's/CA [0-9a-f]{8}$/CA <id>/'
sudo stat -c '%n %a %U:%G' /etc/mockserver/ca/ca.key /etc/mockserver/ca
sudo grep -c 'PRIVATE KEY' /var/lib/cloudimg/mockserver-credentials.txt || true
subject=O = cloudimg MockServer, CN = MockServer per-instance CA <id>
X509v3 Basic Constraints: critical
CA:TRUE, pathlen:0
/etc/mockserver/ca/ca.key 600 mockserver:mockserver
/etc/mockserver/ca 750 root:mockserver
0
Send requests for the sample host api.cloudimg-sample.test (which does not exist) through the proxy, over plain HTTP, over HTTPS verified against this CA only (--cacert, with the host name checked), and over HTTP/2:
. ~/mockserver-env.sh
pxy http://api.cloudimg-sample.test/cloudimg-sample/widgets | jq -e 'select(.[0].name == "Acme Sprocket")' >/dev/null && echo "http: sample"
pxy https://api.cloudimg-sample.test/cloudimg-sample/widgets | jq -e 'select(.[1].name == "Acme Cog")' >/dev/null && echo "https: sample"
pxy --http2 -o /dev/null -w 'https over HTTP/%{http_version}: %{http_code}\n' https://api.cloudimg-sample.test/cloudimg-sample/widgets
http: sample
https: sample
https over HTTP/2: 200
mockserver-ca-presented shows which CA MockServer actually presents. It opens TLS connections through the proxy and directly, reads the certificate chain MockServer sends, and checks that the issuing CA is this instance's CA and not MockServer's public default CA:
sudo mockserver-ca-presented
MOCKSERVER_CA_INSTANCE_SHA256=bd3e725e24e1cb1784796058a93ad79e90de439f3dd58989e8a63e6d7b348698
MOCKSERVER_CA_PRESENTED_PROXY_SHA256=bd3e725e24e1cb1784796058a93ad79e90de439f3dd58989e8a63e6d7b348698
MOCKSERVER_CA_PRESENTED_DIRECT_SHA256=bd3e725e24e1cb1784796058a93ad79e90de439f3dd58989e8a63e6d7b348698
MOCKSERVER_CA_PROOF_OK
The fingerprint differs on every instance.
What the proxy does with requests it has no expectation for. A plain HTTP request gets 404. An HTTPS request through the proxy (an HTTP CONNECT tunnel) is forwarded to the real host, so MockServer works as a recording proxy for HTTPS: you see the traffic in the dashboard and in retrieve. The outbound fence (Step 10) still applies, so only public addresses are reachable:
. ~/mockserver-env.sh
pxy -o /dev/null -w 'unmatched plain http: %{http_code}\n' http://example.com/
pxy -o /dev/null -w 'unmatched https, forwarded: %{http_code}\n' https://example.com/
unmatched plain http: 404
unmatched https, forwarded: 200
Trust the CA in your test clients
Trust the CA only on machines and tools you use for testing, and remove it when you finish. First copy the certificate to your machine and check its fingerprint against MOCKSERVER_CA_SHA256_FINGERPRINT:
scp -i <your-key.pem> ubuntu@<public-ip>:/etc/mockserver/mockserver-ca.crt ./mockserver-ca.crt
openssl x509 -in mockserver-ca.crt -outform der | sha256sum
- curl and most command-line tools: pass the file, as above:
curl --cacert mockserver-ca.crt .... Many tools also read an environment variable:REQUESTS_CA_BUNDLE=mockserver-ca.crtfor Pythonrequests,NODE_EXTRA_CA_CERTS=mockserver-ca.crtfor Node.js,SSL_CERT_FILEfor many others. For Java, import it into a test trust store (keytool -importcert -alias mockserver -file mockserver-ca.crt -keystore mockserver-test.jks) and start the JVM with-Djavax.net.ssl.trustStore=mockserver-test.jks. - The system trust store (a Linux test machine or CI runner; on Ubuntu and Debian), named after the instance so you can remove it later:
sudo cp mockserver-ca.crt /usr/local/share/ca-certificates/mockserver-<public-ip>.crt
sudo update-ca-certificates
# when you are done:
sudo rm /usr/local/share/ca-certificates/mockserver-<public-ip>.crt && sudo update-ca-certificates --fresh
On macOS, add it to the System keychain with Keychain Access (or sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain mockserver-ca.crt); on Windows, import it into Trusted Root Certification Authorities with certutil -addstore -f Root mockserver-ca.crt from an administrator prompt.
* A browser: in Firefox open Settings > Privacy & Security > Certificates > View Certificates > Authorities > Import, choose mockserver-ca.crt and tick Trust this CA to identify websites. Then set the browser's HTTP and HTTPS proxy to 127.0.0.1 port 1080 through the SSH tunnel in Step 11. Use a separate browser profile for testing.
If you ever think the CA has been exposed, or when you hand the instance to someone else, replace it with sudo mockserver-mint-ca (Step 14) and remove the old certificate from your trust stores.
Step 10: Forward to a real API, and the outbound fence
An expectation can forward a request to a real server instead of answering it, for example to put MockServer in front of a real API so you can record and inspect the traffic. This one forwards /example to https://example.com/:
. ~/mockserver-env.sh
ctl -X PUT -H 'Content-Type: application/json' -o /dev/null -w 'forward expectation: %{http_code}\n' \
-d '{"id":"example-forward","httpRequest":{"path":"/example"},"httpOverrideForwardedRequest":{"httpRequest":{"path":"/","headers":{"Host":["example.com"]},"secure":true,"socketAddress":{"host":"example.com","port":443,"scheme":"HTTPS"}}}}' \
"https://$H/mockserver/expectation"
mock "https://$H/example" | grep -o '<title>[^<]*</title>'
ctl -X PUT -H 'Content-Type: application/json' -o /dev/null -w 'clear: %{http_code}\n' -d '{"id":"example-forward"}' "https://$H/mockserver/clear?type=EXPECTATIONS"
forward expectation: 201
<title>Example Domain</title>
clear: 200
A forward to the EC2 instance metadata service, to a private address in your VPC or to another service on this instance never connects. MockServer's own private-network block refuses it (the WARN ... blocked by SSRF policy line in the dashboard log above), and underneath it the kernel refuses every connection from MockServer's service account to those ranges:
. ~/mockserver-env.sh
ctl -X PUT -H 'Content-Type: application/json' -o /dev/null -w 'metadata forward expectation: %{http_code}\n' \
-d '{"id":"imds-forward","httpRequest":{"path":"/imds"},"httpOverrideForwardedRequest":{"httpRequest":{"path":"/latest/meta-data/","headers":{"Host":["169.254.169.254"]},"socketAddress":{"host":"169.254.169.254","port":80,"scheme":"HTTP"}}}}' \
"https://$H/mockserver/expectation"
mock -o /dev/null -m 20 -w 'forwarded to the metadata service: %{http_code}\n' "https://$H/imds"
ctl -X PUT -H 'Content-Type: application/json' -o /dev/null -w 'clear: %{http_code}\n' -d '{"id":"imds-forward"}' "https://$H/mockserver/clear?type=EXPECTATIONS"
metadata forward expectation: 201
forwarded to the metadata service: 502
clear: 200
The fence is visible in MockServer's unit and in the nftables rules for its service account (the metadata addresses 169.254.169.254 and fd00:ec2::254 fall inside the listed ranges; MockServer may reach only the local DNS resolver and its own port on loopback):
systemctl show -p IPAddressDeny --value mockserver | tr ' ' '\n' | sort
sudo nft list table inet cloudimg_mockserver_egress | grep -oE '(oifname "lo"|ip6? daddr \{).*(accept|reject.*)' | sed -E 's/counter packets [0-9]+ bytes [0-9]+ //'
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
168.63.129.16/32
169.254.0.0/16
172.16.0.0/12
192.0.0.0/24
192.168.0.0/16
198.18.0.0/15
224.0.0.0/4
240.0.0.0/4
64:ff9b::/96
::/127
fc00::/7
fe80::/10
ff00::/8
oifname "lo" ip daddr 127.0.0.53 udp dport 53 accept
oifname "lo" ip daddr 127.0.0.53 tcp dport 53 accept
oifname "lo" ip daddr 127.0.0.1 tcp dport 1080 accept
oifname "lo" ct state new reject
ip daddr { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.168.0.0/16, 198.18.0.0/15, 224.0.0.0/3 } reject with icmp port-unreachable
ip6 daddr { ::/127, 64:ff9b::/96, fc00::/7, fe80::/10, ff00::/8 } reject with icmpv6 port-unreachable
To mock or record an API that lives on a private address (inside your VPC), run that test from a MockServer you control the network policy of; this image is built to never reach into the network it runs in.
Step 11: Use MockServer from your own machine
Your mocked API and the control plane are served on port 443. Copy the server certificate, check its fingerprint against MOCKSERVER_TLS_SHA256_FINGERPRINT, and keep the key and the password out of your shell history by putting them in curl config files:
scp -i <your-key.pem> ubuntu@<public-ip>:/etc/ssl/mockserver/mockserver.crt ./mockserver-server.crt
openssl x509 -in mockserver-server.crt -outform der | sha256sum
printf 'header = "X-Cloudimg-Key: %s"\n' '<MOCKSERVER_API_KEY>' > mockserver-key.cfg && chmod 600 mockserver-key.cfg
printf 'user = "admin:%s"\n' '<MOCKSERVER_ADMIN_PASSWORD>' > mockserver-admin.cfg && chmod 600 mockserver-admin.cfg
curl --cacert mockserver-server.crt -K mockserver-key.cfg https://<public-ip>/cloudimg-sample/widgets
curl --cacert mockserver-server.crt -K mockserver-admin.cfg -X PUT https://<public-ip>/mockserver/status
MockServer's Java, Node.js and other client libraries take a host, port and these headers. Most test frameworks let you set a default header (the key) and HTTP Basic credentials (the login) on their HTTP client.
The proxy is not open to the network. Forward it over SSH, then point your client or test suite at 127.0.0.1:1080 as its HTTP and HTTPS proxy, trusting this instance's CA (Step 9):
ssh -i <your-key.pem> -N -L 1080:127.0.0.1:1080 ubuntu@<public-ip> &
curl -x http://127.0.0.1:1080 --cacert mockserver-ca.crt https://api.cloudimg-sample.test/cloudimg-sample/widgets
Through the tunnel you reach MockServer directly, so control-plane calls to http://127.0.0.1:1080/mockserver/... need the admin login (MockServer checks it itself) but mocked API calls need no key. Anyone who can SSH to the instance can use the tunnel, so keep SSH restricted to your own networks.
Step 12: What the front door refuses
Without the key every mocked API path answers 401, and a wrong key gets the same 401 (there is no lockout to trip, and the key is 192 random bits). The control plane needs the login, not the key. With the login, the endpoints that would stop MockServer, open a port or change its settings are refused, another web site cannot create expectations, TRACE and compressed bodies are refused, bodies are limited to 10 MB, plain HTTP only redirects and unknown host names get no answer:
. ~/mockserver-env.sh
C=(curl -s --cacert /etc/ssl/mockserver/mockserver.crt --resolve "$H:443:127.0.0.1")
"${C[@]}" -o /dev/null -w 'wrong key: %{http_code}\n' -K <(printf 'header = "X-Cloudimg-Key: %s"\n' "$(openssl rand -hex 24)") "https://$H/cloudimg-sample/widgets"
mock -o /dev/null -w 'control plane with the key but no login: %{http_code}\n' -X PUT "https://$H/mockserver/status"
ctl -o /dev/null -w 'stop: %{http_code}\n' -X PUT "https://$H/mockserver/stop"
ctl -o /dev/null -w 'bind a new port: %{http_code}\n' -X PUT -d '{"ports":[2000]}' "https://$H/mockserver/bind"
ctl -o /dev/null -w 'change the configuration: %{http_code}\n' -X PUT -H 'Content-Type: application/json' -d '{}' "https://$H/mockserver/configuration"
ctl -o /dev/null -w 'read the configuration: %{http_code}\n' "https://$H/mockserver/configuration"
ctl -o /dev/null -w 'cross-site expectation: %{http_code}\n' -X PUT -H 'Sec-Fetch-Site: cross-site' -H 'Content-Type: application/json' -d '{}' "https://$H/mockserver/expectation"
ctl -o /dev/null -w 'other origin: %{http_code}\n' -X PUT -H 'Origin: https://evil.example' -H 'Content-Type: application/json' -d '{}' "https://$H/mockserver/expectation"
mock -o /dev/null -w 'TRACE: %{http_code}\n' -X TRACE "https://$H/cloudimg-sample/widgets"
mock -o /dev/null -w 'compressed body: %{http_code}\n' -H 'Content-Encoding: gzip' -H 'Content-Type: application/json' -d 'x' "https://$H/cloudimg-sample/widgets"
head -c 11000000 /dev/zero | mock -o /dev/null -w 'an 11 MB body: %{http_code}\n' -H 'Content-Type: application/octet-stream' --data-binary @- "https://$H/cloudimg-sample/widgets"
curl -s -o /dev/null -w 'plain http: %{http_code} %header{location}\n' --resolve "$H:80:127.0.0.1" "http://$H/cloudimg-sample/widgets"
curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' --resolve "other.example:443:127.0.0.1" https://other.example/ || true
wrong key: 401
control plane with the key but no login: 401
stop: 403
bind a new port: 403
change the configuration: 403
read the configuration: 200
cross-site expectation: 403
other origin: 403
TRACE: 405
compressed body: 415
an 11 MB body: 413
plain http: 301 https://<public-ip>/cloudimg-sample/widgets
unknown host name: 000
nginx logs every request with its status and its path only: no query string, no headers and no credentials.
Step 13: Run the self-test
The image ships a self-test that checks every refusal of the front door (including 25 wrong keys followed by the right one), the admin login at nginx and at MockServer, the refused endpoints, the sample API, HTTPS through the proxy against this instance's CA, the CA MockServer presents, the dashboard, the listening ports and the certificate:
sudo /usr/local/sbin/mockserver-selftest
MOCKSERVER_SELFTEST_OK
Step 14: Rotate the key, the password or the CA
Each helper generates a new secret, puts it in force and writes it to the credentials file; none of them prints a secret. A new API key works at once and the old one stops:
sudo mockserver-rotate-key
mockserver-rotate-key: a new API key is in force; read it with: sudo grep MOCKSERVER_API_KEY= /var/lib/cloudimg/mockserver-credentials.txt
A new admin password restarts MockServer, so the expectations and recorded requests in memory are replaced by those in initialization.json (save yours first with PUT /mockserver/retrieve?type=ACTIVE_EXPECTATIONS):
sudo mockserver-rotate-password
mockserver-rotate-password: the admin password is in force; read it with: sudo grep MOCKSERVER_ADMIN_PASSWORD= /var/lib/cloudimg/mockserver-credentials.txt
A new CA also restarts MockServer. Replace the old certificate in your trust stores with the new /etc/mockserver/mockserver-ca.crt:
sudo mockserver-mint-ca | sed -E 's/[0-9a-f]{64}$/<new-fingerprint>/'
sudo mockserver-ca-presented | tail -n 1
mockserver-mint-ca: CA SHA-256 fingerprint (DER) <new-fingerprint>
MOCKSERVER_CA_PROOF_OK
Step 15: Use your own domain name
Point a DNS record at the instance, then set the name. The helper generates a new certificate for it (and the instance's addresses), pins nginx to those names (other host names get no answer) and prints where MockServer is now served:
sudo mockserver-set-hostname <your-domain>
To use a CA-issued certificate instead, replace /etc/ssl/mockserver/mockserver.crt and /etc/ssl/mockserver/mockserver.key with your own (keep the key root only, mode 0600) and run sudo systemctl reload nginx. If the instance's public IP changes after a stop and start, run sudo mockserver-set-hostname with the new address, or use an Elastic IP.
Step 16: Security notes
The server certificate generated at first boot is a server certificate only: it cannot sign other certificates. Only the instance CA can, and it can sign server certificates only (pathlen:0).
openssl x509 -in /etc/ssl/mockserver/mockserver.crt -noout -ext basicConstraints,extendedKeyUsage
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Extended Key Usage:
TLS Web Server Authentication
MockServer's public default CA. The official MockServer jar contains the certificate and private key of MockServer's public default CA (CN=www.mockserver.com), as every copy of MockServer does. They are public, the image never uses them (MockServer always starts with this instance's CA and refuses to start with the default one), and that CA is not trusted anywhere on the instance, neither in the system trust store nor in Java's. The jar is the unmodified file from Maven Central, whose SHA-256 the build record /usr/share/doc/cloudimg-mockserver/BUILD-INFO.txt lists. Check it at any time:
cd / && sha256sum -c /usr/share/doc/cloudimg-mockserver/SHA256SUMS
opt/mockserver/mockserver-netty-9.2.0-jar-with-dependencies.jar: OK
opt/mockserver/LICENSE: OK
Known advisories. The build record lists the vulnerability scans of the jar and its bundled libraries at build time, and the MockServer security advisories fixed in 9.2.0:
grep -oE 'GHSA-[a-z0-9-]+' /usr/share/doc/cloudimg-mockserver/BUILD-INFO.txt
GHSA-7pwj-xvc2-hfpc
GHSA-xcr9-3j63-6cgq
GHSA-52vf-hgvq-8pr3
GHSA-qrq7-fxf6-47p7
GHSA-jg86-hv4g-8h78
Step 17: Keep it up to date
Ubuntu security updates, OpenJDK included, install automatically. To apply everything now:
sudo apt-get update && sudo apt-get upgrade -y
New MockServer releases arrive as new versions of the AWS Marketplace listing: launch the new version and copy your expectations from /etc/mockserver/initialization.json across.
Support
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
MockServer is developed by James D Bloom and contributors and released under the Apache License 2.0. cloudimg is not affiliated with the MockServer project. The other versions on this listing are on the developer and API tools guide.