deepstream.io 10.2.4 on AWS User Guide
Overview
This guide covers launching and using the deepstream.io 10.2.4 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg streaming and messaging listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
deepstream.io is an open source realtime server. Clients connect over a WebSocket and share records (JSON documents that stay in sync on every client that subscribes to them), events (publish and subscribe), RPCs (one client calls a procedure another client provides) and presence (which users are online). An HTTP API offers the same operations to servers and scripts that do not keep a connection open.
What the cloudimg image adds:
- deepstream.io 10.2.4, the release that closes five upstream security advisories. 10.1.x and earlier could be crashed by one unauthenticated WebSocket frame, frozen with a crafted listen pattern, and let clients bypass permission rules through bulk record reads, record updates and listen responses. 10.2.4 fixes all of them.
- TLS only. deepstream listens on
127.0.0.1:6020and nothing else. nginx serves the WebSocket endpoint atwss://<host>/deepstreamand the HTTP API athttps://<host>/apion port 443; port 80 only redirects. - Authentication that is on, with no default account. Upstream's sample configuration accepts anyone (
auth: none) and its sample user file holds the published loginuserA/password. This image uses file authentication only, ships an empty user list, and generates anadminlogin unique to your instance at first boot. Passwords are stored only as PBKDF2-HMAC-SHA512 digests (210,000 iterations, a random salt per password); the password itself never appears on a command line. - Least privilege for every user you add. The
adminrole has full access. Any other role can readpublic/*records, read and write its ownusers/<name>/*records and subscribe topublic/*events, and nothing else, until you widen the rules. - A WebSocket Origin guard. deepstream itself accepts a WebSocket upgrade from any web page. nginx refuses an upgrade (and an HTTP API call) whose
Originis another site before it reaches deepstream, so a page on another site cannot use a visitor's browser to talk to your server. Clients that send noOrigin(Node.js, Java, mobile apps, scripts) are not affected; you allow your own web application's origin with one command. - Exact routes, rate limits, sandboxing. deepstream matches its HTTP paths by prefix; nginx serves only the two exact paths and answers 404 for everything else, including the health check, which stays on the instance. WebSocket connections and API calls are rate limited per client address. deepstream runs as an unprivileged user with systemd sandboxing and cannot reach the instance metadata service.
- HTTPS from the first boot. The certificate is generated on your instance for its own names and addresses. It is an end-entity certificate: it can only vouch for this instance and can never sign other certificates. Install a CA-issued certificate with one command (Step 12).
- A pinned software supply chain. deepstream and its 136 npm dependencies are installed from a committed lockfile with every package's integrity hash checked and no install scripts run; Node.js is the official release, checked against its signed checksums. Ubuntu 24.04 LTS is fully patched at build time with unattended security upgrades on, and SSH accepts keys only.
What is on the image: deepstream.io 10.2.4 (@deepstream/server) and the official JavaScript client @deepstream/client 7.1.1 in /opt/deepstream, Node.js 26.13.0 LTS in /opt/node (also on the PATH as node and npm), nginx 1.24 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent. No compiler toolchain is installed. deepstream keeps records in memory (no storage plugin is configured), so records live as long as the server runs.
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 with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH) from your admin network and TCP 443 (WSS and HTTPS) from your clients, plus TCP 80 if you want plain HTTP requests redirected to HTTPS. There is nothing else to open: deepstream's own port (6020) listens on the loopback address only and refuses connections from other machines even if a security group allows them.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick deepstream.io 10.2.4 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (or larger for many concurrent connections), your key pair and a security group as above.
With the AWS CLI (replace the values in angle brackets):
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=deepstream}]'
First boot takes about a minute after the instance is running: the boot service generates the admin login and the certificate, then deepstream and nginx start.
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| deepstream.io 10.2.4 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services and the listening set
systemctl is-active deepstream-metadata-names deepstream-firstboot deepstream nginx
sudo test -f /var/lib/cloudimg/deepstream-firstboot.done && echo FIRSTBOOT_DONE
All four units are active and the first boot marker is present:
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on network addresses; deepstream listens on 127.0.0.1:6020:
sudo ss -Hlnt | awk '{print $4}' | grep -v '^127.0.0.5' | sort
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:6020
[::]:22
[::]:443
[::]:80
Step 4: Get the URLs, the admin login and the certificate
The credentials file is readable by root only. It holds the WebSocket and HTTP API URLs, the admin user and password, and the SHA-256 fingerprint of this instance's certificate:
sudo cat /root/deepstream-credentials.txt
# deepstream.io 10.2.4 on Ubuntu 24.04 - generated on first boot by deepstream-firstboot.service.
# The admin login is unique to this instance; none was baked into the image. Keep this file secret: the admin role
# has full access to every record, event, RPC and presence. deepstream stores the password only as a PBKDF2-SHA512
# digest (/etc/deepstream/users.yml). Clients connect over TLS only:
# WebSocket wss://<public-ip>/deepstream HTTP API https://<public-ip>/api (POST, JSON)
# The certificate is self signed (minted on this instance): compare its SHA-256 fingerprint with
# DEEPSTREAM_TLS_CERT_SHA256 below and pin it in your client (Node: socketOptions.ca), or install a CA-issued
# certificate with sudo deepstream-install-cert <chain.pem> <key.pem>
# Add users (password on stdin, stored hashed): sudo deepstream-user add <name> <role>
# Allow a browser app's origin: sudo deepstream-allow-origin add https://app.example.com
# Change the served name: sudo deepstream-set-hostname <dns-name> Re-verify: sudo deepstream-selfcheck
DEEPSTREAM_URL=wss://<public-ip>/deepstream
DEEPSTREAM_HTTP_URL=https://<public-ip>/api
DEEPSTREAM_HOST=<public-ip>
DEEPSTREAM_ADMIN_USER=admin
DEEPSTREAM_ADMIN_PASSWORD=<DEEPSTREAM_ADMIN_PASSWORD>
DEEPSTREAM_TLS_CERT_SHA256=<sha256-fingerprint>
DEEPSTREAM_TLS_CERT_FILE=/etc/cloudimg/deepstream-tls.pem
# User guide: https://www.cloudimg.co.uk/guides/deepstream-io-10-2-4-on-ubuntu-24-04-aws/
The URLs, the host and the user (without the password):
sudo grep -E '^DEEPSTREAM_(URL|HTTP_URL|HOST|ADMIN_USER|TLS_CERT_FILE)=' /root/deepstream-credentials.txt
DEEPSTREAM_URL=wss://<public-ip>/deepstream
DEEPSTREAM_HTTP_URL=https://<public-ip>/api
DEEPSTREAM_HOST=<public-ip>
DEEPSTREAM_ADMIN_USER=admin
DEEPSTREAM_TLS_CERT_FILE=/etc/cloudimg/deepstream-tls.pem
To print just the password on the instance:
sudo awk -F= '$1=="DEEPSTREAM_ADMIN_PASSWORD" {print $2}' /root/deepstream-credentials.txt
The host is the instance's public IP address from the EC2 instance metadata (the private address when the instance has no public one). If you stop and start the instance without an Elastic IP, the boot service picks up the new address and issues a new certificate for it (unless you installed your own, Step 12).
Step 5: Check the certificate and pin it in your client
The certificate is self signed and unique to this instance, so no client trusts it by default; a client that has not been told about it refuses the connection. Do not add it to an operating-system or browser trust store. Instead, compare its fingerprint and then pin it in the one client that connects to this server, for that connection only.
The certificate's fingerprint, names and constraints, read on the instance:
openssl x509 -in /etc/cloudimg/deepstream-tls.pem -noout -fingerprint -sha256
openssl x509 -in /etc/cloudimg/deepstream-tls.pem -noout -ext subjectAltName,basicConstraints,keyUsage,extendedKeyUsage
sha256 Fingerprint=<sha256-fingerprint>
X509v3 Subject Alternative Name:
IP Address:<public-ip>, DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<private-ip>, DNS:ip-<private-ip-dashed>.ec2.internal, IP Address:127.0.0.1, DNS:localhost
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
Copy the certificate file (it is public, not a secret) to the machine your client runs on and check that its fingerprint matches DEEPSTREAM_TLS_CERT_SHA256:
scp -i <your-key.pem> ubuntu@<public-ip>:/etc/cloudimg/deepstream-tls.pem ./deepstream-tls.pem
openssl x509 -in deepstream-tls.pem -noout -fingerprint -sha256
Then give that file to your client as the only certificate it trusts for this server: in Node.js the client's socketOptions: { ca } (Step 6), in other runtimes the equivalent per-connection trust setting. Browsers cannot pin a certificate for one site: for browser clients, install a CA-issued certificate for a DNS name you own (Step 12).
Step 6: Connect a Node.js client
Clients log in with a user name and password; the password should come from a file or the environment, never from the command line. This example uses the official client, logs in over wss:// with the instance certificate pinned for this connection, and then uses a record, an event, an RPC and presence. On the instance it connects to wss://localhost/deepstream (the certificate also names localhost), using the copy of the client library in /opt/deepstream:
install -d -m 0700 ~/ds-demo
( umask 077; sudo awk -F= '$1=="DEEPSTREAM_ADMIN_PASSWORD" {print $2}' /root/deepstream-credentials.txt > ~/ds-demo/admin.pw )
cat > ~/ds-demo/app.js <<'EOF'
// deepstream.io client demo: log in over wss:// with this instance's certificate pinned for this connection only,
// then use records, events, RPC and presence. The password is read from a file, never the command line.
const fs = require('fs')
const { DeepstreamClient } = require('@deepstream/client')
const url = process.env.DEEPSTREAM_URL || 'wss://localhost/deepstream'
const ca = fs.readFileSync(process.env.DEEPSTREAM_CA_FILE || '/etc/cloudimg/deepstream-tls.pem')
const password = fs.readFileSync(process.env.DEEPSTREAM_PASSWORD_FILE, 'utf8').trim()
const client = new DeepstreamClient(url, { socketOptions: { ca } })
client.on('error', (err, event) => console.error('deepstream error:', event, err))
client.login({ username: process.env.DEEPSTREAM_USER || 'admin', password }, async (ok, data) => {
if (!ok) { console.error('login refused:', JSON.stringify(data)); process.exit(1) }
console.log('logged in as', process.env.DEEPSTREAM_USER || 'admin', JSON.stringify(data))
// Records: synchronised JSON documents
const record = client.record.getRecord('demo/settings')
await record.whenReady()
await record.setWithAck({ theme: 'dark', updated: new Date().toISOString().slice(0, 10) })
console.log('record demo/settings =', JSON.stringify(record.get()))
// Events: publish / subscribe
const got = new Promise((resolve) => client.event.subscribe('demo/news', resolve))
client.event.emit('demo/news', { headline: 'hello from deepstream' })
console.log('event demo/news received', JSON.stringify(await got))
// RPC: request / response
client.rpc.provide('demo/add', (data, response) => response.send(data.a + data.b))
await new Promise((resolve) => setTimeout(resolve, 500)) // let the provider registration reach the server
console.log('rpc demo/add(2, 3) =', await client.rpc.make('demo/add', { a: 2, b: 3 }))
// Presence: who is online
client.presence.getAll(['admin'], (err, users) => {
console.log('presence', JSON.stringify(users))
client.rpc.unprovide('demo/add'); record.discard(); client.close()
})
})
EOF
cd ~/ds-demo && DEEPSTREAM_PASSWORD_FILE=~/ds-demo/admin.pw NODE_PATH=/opt/deepstream/node_modules node app.js
logged in as admin {"role":"admin"}
record demo/settings = {"theme":"dark","updated":"2026-10-29"}
event demo/news received {"headline":"hello from deepstream"}
rpc demo/add(2, 3) = 5
presence {"admin":true}
From your own machine, install the same client (npm install @deepstream/client@7.1.1), save the password in a file only you can read (chmod 600), and run the same program against the public URL with the certificate you copied in Step 5:
DEEPSTREAM_URL=wss://<public-ip>/deepstream DEEPSTREAM_CA_FILE=./deepstream-tls.pem DEEPSTREAM_PASSWORD_FILE=./admin.pw node app.js
A wrong password is refused with INVALID_AUTHENTICATION_DETAILS, a missing user name or password with missing authentication parameter. Each connection may try to log in at most three times and is closed after 20 seconds without a login.
Step 7: Records, events, RPC and presence
- Records (
client.record.getRecord(name)) are JSON documents. Every client subscribed to a record receives each change;setWithAckresolves once the server accepted the write, andclient.record.snapshot(name)reads the current value once. Records are kept in deepstream's memory: configure one of deepstream's storage connectors (in/etc/deepstream/config.yml) if they must survive a restart. - Events (
client.event.subscribe/emit) are fire-and-forget messages to every subscriber of a name. - RPCs (
client.rpc.provide/make) route a request to one client that provides the procedure and return its answer. - Presence (
client.presence.getAll,subscribe) reports which user names are connected.
Names are free-form strings; the / separated prefixes are what the permission rules match (Step 9).
Step 8: The HTTP API
The HTTP API runs the same operations without a WebSocket: POST a JSON body with authData and a list of operations to https://<host>/api. Write the body to a file so the password never appears on a command line, and pin the certificate with --cacert:
cd ~/ds-demo
( umask 077
printf '{"authData":{"username":"admin","password":"%s"},"body":[{"topic":"record","action":"write","recordName":"demo/http","data":{"source":"http api"}}]}' "$(cat ~/ds-demo/admin.pw)" > write.json
printf '{"authData":{"username":"admin","password":"%s"},"body":[{"topic":"record","action":"read","recordName":"demo/http"},{"topic":"event","action":"emit","eventName":"demo/news","data":{"headline":"via http"}}]}' "$(cat ~/ds-demo/admin.pw)" > read.json )
curl -s --cacert /etc/cloudimg/deepstream-tls.pem -H 'Content-Type: application/json' --data-binary @write.json https://localhost/api; echo
curl -s --cacert /etc/cloudimg/deepstream-tls.pem -H 'Content-Type: application/json' --data-binary @read.json https://localhost/api; echo
{"result":"SUCCESS","body":[{"success":true}]}
{"result":"SUCCESS","body":[{"version":1,"data":{"source":"http api"},"success":true},{"success":true}]}
Send each write and the read that depends on it as separate requests: operations in one request run in parallel. Without valid authData the API answers 401:
printf '{"body":[{"topic":"record","action":"read","recordName":"demo/http"}]}' > ~/ds-demo/anon.json
curl -s -w ' (HTTP %{http_code})\n' --cacert /etc/cloudimg/deepstream-tls.pem -H 'Content-Type: application/json' --data-binary @"$HOME/ds-demo/anon.json" https://localhost/api
Unsuccessful authentication attempt.
(HTTP 401)
The API accepts only POST with a Content-Type: application/json body (other methods 405, other body types 415), and a browser request from another site is refused 403 (Step 10). From another machine use DEEPSTREAM_HTTP_URL from the credentials file with --cacert ./deepstream-tls.pem.
Step 9: Add users and scope their permissions
Users live in /etc/deepstream/users.yml, each with a PBKDF2 digest of its password and a role. Manage them with deepstream-user, which reads the password on standard input (never the command line), stores only the digest and lists names and roles only. deepstream reads the user list at start, so restart it afterwards:
( umask 077; openssl rand -hex 16 > alice.pw )
sudo deepstream-user add alice app < alice.pw
sudo systemctl restart deepstream
sudo deepstream-user list
deepstream-user: added alice (role app); restart deepstream to apply
admin role=admin
alice role=app
sudo deepstream-user remove <name> removes a user (it refuses to remove the last admin). The role decides what a user may do. /etc/deepstream/permissions.yml gives the admin role full access and every other role least privilege:
sudo sed -n '/^record:/,/^event:/p' /etc/deepstream/permissions.yml
record:
"*":
create: "user.data.role === 'admin'"
write: "user.data.role === 'admin'"
read: "user.data.role === 'admin'"
delete: "user.data.role === 'admin'"
listen: "user.data.role === 'admin'"
notify: "user.data.role === 'admin'"
"public/*":
create: "user.data.role === 'admin'"
write: "user.data.role === 'admin'"
read: true
delete: "user.data.role === 'admin'"
listen: "user.data.role === 'admin'"
notify: "user.data.role === 'admin'"
"users/$owner/*":
create: "user.id === $owner || user.data.role === 'admin'"
write: "user.id === $owner || user.data.role === 'admin'"
read: "user.id === $owner || user.data.role === 'admin'"
delete: "user.id === $owner || user.data.role === 'admin'"
listen: "user.data.role === 'admin'"
notify: "user.data.role === 'admin'"
event:
So alice (role app) may write users/alice/... and read public/..., and everything else is denied. Logged in as alice with the same client as in Step 6 (an admin had written public/notice first):
write users/alice/profile -> ok
read public/notice -> {"text":"maintenance at 22:00"}
write public/notice -> MESSAGE_DENIED
read demo/settings -> MESSAGE_DENIED
rpc demo/add -> MESSAGE_DENIED
To grant more, add a rule for the name prefix your application uses (the most specific matching pattern decides), for example a chat/* record prefix every app user may read and write:
"chat/*":
create: "user.data.role === 'app' || user.data.role === 'admin'"
write: "user.data.role === 'app' || user.data.role === 'admin'"
read: "user.data.role === 'app' || user.data.role === 'admin'"
delete: "user.data.role === 'admin'"
listen: "user.data.role === 'admin'"
notify: "user.data.role === 'admin'"
Edit the file with sudoedit /etc/deepstream/permissions.yml, then sudo systemctl restart deepstream. Rules are deepstream Valve expressions: user.id is the user name and user.data its serverData from users.yml; see the deepstream documentation for the data, oldData and path variables they can use.
Step 10: Browser applications and the Origin guard
A browser sends an Origin header with every WebSocket upgrade. nginx lets an upgrade through when there is no Origin (a server-side or mobile client) or when it is one of this instance's own https:// origins, and refuses any other site with 403 before deepstream sees it:
H="$(sudo sed -n 's/^DEEPSTREAM_HOST=//p' /root/deepstream-credentials.txt)"
ws() { curl -s -m 3 -o /dev/null -w '%{http_code}\n' --cacert /etc/cloudimg/deepstream-tls.pem --http1.1 -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' --resolve "$H:443:127.0.0.1" "$@" "https://$H/deepstream"; }
echo "no Origin (a server-side client): $(ws)"
echo "Origin https://$H (this instance): $(ws -H "Origin: https://$H")"
echo "Origin https://evil.example (another site): $(ws -H 'Origin: https://evil.example')"
no Origin (a server-side client): 101
Origin https://<public-ip> (this instance): 101
Origin https://evil.example (another site): 403
(101 is the WebSocket upgrade.) If your web application is served from its own site, allow its origin; the change is saved and applied at once:
sudo deepstream-allow-origin add https://app.example.com
sudo deepstream-allow-origin list
sudo deepstream-allow-origin remove https://app.example.com
Browsers do not let a page pin a certificate, so browser clients need a CA-issued certificate for a DNS name (Step 12).
Step 11: What the front door refuses
nginx serves exactly two paths. deepstream itself matches its HTTP paths by prefix (/apiX and /api/x would reach its API); nginx answers 404 for every other path, and deepstream's health check is reachable on the instance only (http://127.0.0.1:6020/health-check):
cd ~/ds-demo
for p in /health-check /apiX /api/x; do echo "$p -> $(curl -s -o /dev/null -w '%{http_code}' --cacert /etc/cloudimg/deepstream-tls.pem -H 'Content-Type: application/json' --data-binary @write.json https://localhost$p)"; done
echo "http://localhost/deepstream -> $(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://localhost/deepstream)"
echo "health check on the instance -> $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:6020/health-check)"
/health-check -> 404
/apiX -> 404
/api/x -> 404
http://localhost/deepstream -> 301 https://<public-ip>/deepstream
health check on the instance -> 200
Also refused: requests for a host name the instance does not serve (the connection is dropped), ; path parameters (400), and, per client address, more than 60 new WebSocket connections a minute (a burst of 40 is allowed) or 120 HTTP API calls a minute (burst 60), which get 429. The limits are in /etc/nginx/sites-available/cloudimg-deepstream (limit_req_zone) if your clients connect through one shared address.
Step 12: Use your own domain and a CA-issued certificate
Point a DNS name at the instance (an Elastic IP keeps the address stable), then make it the served name; this issues a new self-signed certificate for it:
sudo deepstream-set-hostname deepstream.example.com
To serve a certificate from your CA (or Let's Encrypt) for that name, which browsers and clients trust without pinning, install the chain (server certificate first) and its unencrypted private key:
sudo deepstream-install-cert /path/to/fullchain.pem /path/to/privkey.pem deepstream.example.com
The boot-time renewal never replaces a certificate you installed; sudo deepstream-set-hostname <name> goes back to a self-signed one. Both commands update the URLs and the fingerprint in the credentials file.
Step 13: The instance metadata block
deepstream, nginx and the boot service may not connect to the EC2 instance metadata service (169.254.169.254) or its IPv6 and IPv4-mapped forms. Only a small boot unit reads the instance's own names and addresses for the certificate, before deepstream starts:
for u in deepstream nginx deepstream-firstboot; do printf '%s: ' "$u"; systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
deepstream: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128
nginx: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128
deepstream-firstboot: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128
The self-check below proves it from inside deepstream's own service: a connection to the metadata service from there never completes, while a loopback connection does. deepstream also runs as the unprivileged deepstream user, cannot read /root, the TLS key or the boot state, and can write only its empty state directory.
Step 14: Re-verify the appliance
deepstream-selfcheck re-checks the whole appliance: the units and files, the certificate, the front door (redirect, Origin guard, refusals, exact routes), the HTTP API, the official client over wss:// with the certificate pinned (the refusal matrix and a record, event, RPC and presence round trip), the listening set and the metadata block. It reads the admin password from the credentials file, never from a command line:
sudo deepstream-selfcheck
ok units active; credentials file, TLS key and deepstream config present with their permissions; the config gate accepts the configuration
ok TLS: the served certificate is this instance's end-entity certificate (CA:FALSE, serverAuth; SHA-256 <fingerprint>...)
ok front door: TLS only (80 -> 301), unknown Host dropped; WebSocket no-Origin 101, own Origin 101, foreign / null / http Origin 403 (before the upgrade), non-WebSocket 426, upgrade elsewhere 403; HTTP API anonymous / userA / wrong password 401 (deepstream), admin 200 (a record written, read back and deleted), foreign Origin 403, text/plain and form 415, GET / OPTIONS / PUT 405, ';' 400; exact routes only: /apiX /api/ /api/x /api/auth /deepstreamX /deepstream/ /health-check /health-checkX / 404; CORS stripped
admin login accepted over wss://<public-ip>/deepstream (clientData role admin; positive control)
anonymous refused: {"error":"missing authentication parameter: username or/and password"}
missing password refused: {"error":"missing authentication parameter: username or/and password"}
upstream sample userA/"password" refused: "INVALID_AUTHENTICATION_DETAILS"
wrong password refused: "INVALID_AUTHENTICATION_DETAILS"
unknown user refused: "INVALID_AUTHENTICATION_DETAILS"
event: connection B -> deepstream -> connection A (nonce matched)
record: update by B delivered to A's subscription (nonce matched)
RPC: A called the procedure B provides and got B's answer
presence: admin reported online
ok client (@deepstream/client 7.1.1, certificate pinned for this connection): refusal matrix exact, admin login accepted; event, record, RPC and presence delivered between two connections
ok public listeners 22/80/443 only; deepstream on 127.0.0.1:6020 only (health check answers there)
ok from inside deepstream.service's cgroup: 169.254.169.254 unreachable (curl v4 and v4-mapped; Node.js timeout), loopback control 200
SELFCHECK OK
Step 15: Logs and service management
deepstream logs to the journal without colour codes. It logs connections and logins, never the data a client logged in with:
sudo journalctl -u deepstream -n 6 -o cat --no-pager
CLIENT_DISCONNECTED | admin
INCOMING_CONNECTION | from undefined (127.0.0.1)
AUTH_SUCCESSFUL | admin
CLIENT_DISCONNECTED | admin
INCOMING_CONNECTION | from undefined (::1)
CLIENT_DISCONNECTED | null
The configuration is /etc/deepstream/config.yml. Before deepstream starts, deepstream-config-gate checks it and refuses to start the server (the reason is in journalctl -u deepstream) if it would weaken the appliance: authentication other than hashed file authentication, a user without a digest, a bind other than 127.0.0.1:6020, another connection endpoint (text or JSON WebSocket, MQTT), logging of failed login data, the HTTP token endpoint, telemetry, a plugin directory, or ${...} environment expansion.
sudo systemctl restart deepstream
sudo systemctl status deepstream
Security notes
- Keep
/root/deepstream-credentials.txtsecret and give each application its own user with the narrowest role that works (Step 9); theadminrole can read and change everything. - deepstream stores records in memory only on this image; anything clients write is lost when deepstream restarts unless you configure a storage connector.
- Restrict the security group's port 443 to the networks your clients connect from, and port 22 to your admin network.
What changed since the Azure image (deepstream.io 10.1.4)
The cloudimg Azure Marketplace image of deepstream.io ships 10.1.4 behind plain HTTP on port 80 with md5 password digests at 100 iterations. This AWS image runs 10.2.4 (which fixes GHSA-rv32-v8hf-rxqg, GHSA-9r5v-rrr7-v476, GHSA-g4qj-f7jp-76c6, GHSA-v448-hpx6-fm45 and GHSA-j55p-v9rg-r9f2), serves only WSS and HTTPS, hashes with PBKDF2-SHA512 at 210,000 iterations, refuses foreign-origin WebSocket upgrades, gives non-admin users least privilege and blocks the instance metadata service.
Support
cloudimg provides support for this image by email at support@cloudimg.co.uk. deepstream.io is open source software under the MIT licence; this image is not affiliated with or endorsed by the deepstream.io project.