Security AWS

OWASP Threat Dragon 2.6.2 on AWS User Guide

| Product: OWASP Threat Dragon 2.6.2 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the OWASP Threat Dragon 2.6.2 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg SIEM and Threat Detection listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

OWASP Threat Dragon is the OWASP Foundation's threat modelling application. You draw a data-flow diagram of the system you are designing (processes, data stores, external actors, the data flows between them and the trust boundaries they cross), record the threats against each element using STRIDE, LINDDUN, CIA, DIE or PLOT4ai, give each threat a severity, a status and a mitigation, and produce a threat model report. A model is a plain JSON file, so it can live next to the code it describes.

This image runs the Threat Dragon web application (the td.server back end serving the td.vue single-page front end), built from the upstream v2.6.2 tag, in local session mode: you open threat models from files on your computer and save them back to files, and no third-party account is involved. The cloudimg image adds:

  • A login unique to your instance, and no default credential. In local session mode Threat Dragon has no account store of its own, so the whole application sits behind a login (user admin) whose password is generated on your instance at first boot and written to a file only root can read. Nothing answers without it except a health check.
  • Per-instance keys. Threat Dragon's encryption key and both of its token signing keys are generated on your instance at first boot, kept in a file only root can read, and handed to the application by systemd. No OAuth client ID or secret ships in the image.
  • HTTPS from the first boot. nginx serves Threat Dragon on port 443 with a certificate generated on your instance; port 80 redirects to HTTPS. Requests for a host name the instance does not serve get no answer.
  • No application port. Threat Dragon listens on a local socket that only nginx can open, not on a network port, and runs as its own unprivileged user under a locked down systemd unit.
  • Hardened defaults. Threat Dragon and nginx are blocked from the instance metadata service and the AMI requires IMDSv2. The client address comes from the connection, never from a header a client can forge, and Threat Dragon is always given the configured host name, never the one a request claims. A browser request that changes something, or opens a WebSocket, from another web site is refused.
  • A patched dependency tree. 2.6.2 is the latest Threat Dragon release. The server's production dependencies take the same overrides the upstream project has since added on its main branch plus compatible security updates, and the single-page application is built after compatible security updates, so neither carries a known high or critical advisory at build time. The audit results are on the image in /usr/share/doc/cloudimg/.
  • A current, patchable Node.js. Node.js 24 (the version upstream builds and tests with) comes from NodeSource's own package archive, whose signing key is pinned by its fingerprint, and unattended upgrades install new Node.js 24 releases from it along with Ubuntu's security updates.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: OWASP Threat Dragon 2.6.2 in /opt/threat-dragon, Node.js 24, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. Threat models are kept in your browser session and in the files you save, so there is no database.

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 443 (Threat Dragon) and TCP 80 (redirects to HTTPS) from the people who will use it, and TCP 22 (SSH) from your own networks only.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick OWASP Threat Dragon 2.6.2 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above. The diagram editing happens in the browser, so the server needs little: a smaller instance works for a few users.

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=threat-dragon}]'

Step 2: Connect to your instance

Version SSH user
OWASP Threat Dragon 2.6.2 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

The first boot takes a few seconds. Then the first boot unit, Threat Dragon and nginx are active:

systemctl is-active threat-dragon-firstboot threat-dragon nginx
active
active
active

nginx is the only web listener. Threat Dragon has no network port: it listens on a socket in /run/threat-dragon, which only nginx can open:

sudo ss -tlnp | awk 'NR==1 || /nginx|sshd/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
sudo ls -l /run/threat-dragon/
State Local Peer
LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
total 0
srwxrwx--- 1 threatdragon threatdragon 0 Oct 20 13:21 app.sock

The versions:

python3 -c 'import json; print("Threat Dragon", json.load(open("/opt/threat-dragon/td.server/package.json"))["version"])'; node --version
Threat Dragon 2.6.2
v24.22.0

Step 4: Get your URL and password

The first boot wrote the address (your instance's public IP address by default), the user name and the password generated on this instance to /var/lib/cloudimg/threat-dragon-credentials.txt. Show everything but the password:

sudo sed -E 's/^(THREAT_DRAGON_PASSWORD=).*/\1<hidden: run sudo cat to see it>/' /var/lib/cloudimg/threat-dragon-credentials.txt
# OWASP Threat Dragon 2.6.2 - generated on first boot by threat-dragon-firstboot.service.
# Unique to this instance (not baked into the image); keep it safe.
THREAT_DRAGON_URL=https://<public-ip>/
THREAT_DRAGON_USERNAME=admin
THREAT_DRAGON_PASSWORD=<hidden: run sudo cat to see it>

# The browser asks for this login first, then Threat Dragon opens; choose "Local Session" to work on threat
# models that you open from and save to files on your own computer.
# Change the password:   sudo threat-dragon-set-password            (prompts)
# Change the host name:  sudo threat-dragon-set-host <dns-name>

Then read the password itself:

sudo cat /var/lib/cloudimg/threat-dragon-credentials.txt

Threat Dragon's own keys are in a file only root can read (systemd reads it and passes the values to the application). Only the names are shown here:

sudo stat -c '%n %a %U:%G' /etc/threat-dragon/threat-dragon.env /var/lib/cloudimg/threat-dragon-credentials.txt
sudo grep -oE '^[A-Z_]+=' /etc/threat-dragon/threat-dragon.env
/etc/threat-dragon/threat-dragon.env 600 root:root
/var/lib/cloudimg/threat-dragon-credentials.txt 600 root:root
NODE_ENV=
PORT=
SERVER_API_PROTOCOL=
LOG_LEVEL=
ENCRYPTION_KEYS=
ENCRYPTION_JWT_SIGNING_KEY=
ENCRYPTION_JWT_REFRESH_SIGNING_KEY=

Step 5: Sign in

Open https://<public-ip>/ in your browser. The certificate is generated on your instance and is not signed by a public authority, so the browser warns once; accept it (or use your own certificate, Step 9). The browser then asks for a user name and password: enter admin and the password from Step 4. Threat Dragon's front page opens; choose Login to Local Session.

The OWASP Threat Dragon 2.6.2 front page with the Login to Local Session button

The dashboard offers to open an existing threat model from a file, create a new empty one, explore a sample model, or start from a template.

The Threat Dragon dashboard with Open an existing threat model, Create a new empty threat model, Explore a sample threat model and Create model from a Template

Step 6: Work on a threat model

Choose Explore a sample threat model and pick Three Tier Web Application. A model opens on its summary: owner, reviewer, contributors, a high level description of the system and its diagrams.

The Three Tier Web Application sample model summary with its owner, reviewer, description and the Three Tier Web App diagram, and the Edit, Report, Manage and Close Model buttons

Click the diagram to open the editor. Drag processes, stores, actors, data flows and trust boundaries from the stencil on the left onto the canvas, connect them, and select an element to edit its Properties and its Threats. New Threat adds a threat to the selected element: choose the threat type, then give it a title, a severity, a status, a description and the mitigation. Here the PostgresSQL store carries two information disclosure threats from the STRIDE model:

The Threat Dragon diagram editor showing Web UI, Web Service and PostgresSQL inside the Internet and Public Cloud trust boundaries, with PostgresSQL selected and its two Store Sensitive Data threats listed

Report on the model page renders the whole model as a document: the summary counts of open and mitigated threats by severity, every diagram and every threat with its mitigation. Use Print to save it as a PDF.

The Threat Dragon threat model report for Three Tier Web Application with its executive summary and the threat counts

Step 7: Save and open your models

In local session mode the model you are editing lives in your browser tab. Save in the editor writes it to a JSON file on your computer (the browser asks where to save it, or downloads it); keep it with your project, for example in its source repository. Open an existing threat model on the dashboard loads such a file again. Nothing is stored on the instance, so there is nothing to back up on the server; keep your model files safe.

Step 8: Check the security model

The health check answers without a login; the application answers 401 without it; a host name the instance does not serve gets no answer; plain HTTP redirects to HTTPS:

H=$(sed -nE 's/^set \$td_host "([^"]+)";$/\1/p' /etc/nginx/threat-dragon-host.conf)
curl -s -o /dev/null -w 'health (port 80): %{http_code}\n' http://127.0.0.1/health
curl -ks -o /dev/null -w 'app without the login: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -ks -o /dev/null -w 'unknown Host: %{http_code}\n' --resolve "evil.example:443:127.0.0.1" https://evil.example/ ; echo "(000 = connection closed with no response)"
curl -s -o /dev/null -w 'http -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
health (port 80): 200
app without the login: 401
unknown Host: 000
(000 = connection closed with no response)
http -> 301 https://<public-ip>/

Threat Dragon and nginx cannot reach the instance metadata service (and the AMI only answers IMDSv2 requests):

for u in threat-dragon nginx; do printf '%s: %s\n' $u "$(systemctl show -p IPAddressDeny --value $u.service)"; done
threat-dragon: fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32
nginx: fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

The whole check at once, as the image's own self-test runs it (the health check, the login refused without a password and with a wrong one and accepted with the instance password, Threat Dragon answering through the login, no network port for the application):

sudo /usr/local/sbin/threat-dragon-selftest
OK: health 200/200, login 401/401/200, /healthz + /api/config through the gate, app on its unix socket only

The dependency audits recorded at build time for the server and for the single-page application (the one moderate advisory is a missing bounds check in the uuid library's v3, v5 and v6 functions when a caller passes its own buffer):

python3 -c 'import json
for t in ("server", "spa"): print(t, json.load(open(f"/usr/share/doc/cloudimg/threat-dragon-{t}-npm-audit.json"))["metadata"]["vulnerabilities"])'
server {'info': 0, 'low': 0, 'moderate': 0, 'high': 0, 'critical': 0, 'total': 0}
spa {'info': 0, 'low': 0, 'moderate': 1, 'high': 0, 'critical': 0, 'total': 1}

Step 9: Your own DNS name and certificate

Point a DNS name at the instance (an Elastic IP keeps the address stable), then make it the name Threat Dragon is served under. This regenerates the instance certificate for that name and updates nginx and the credentials file:

sudo threat-dragon-set-host threat-dragon.example.com

To use a certificate from your own authority instead, replace /etc/nginx/ssl/threat-dragon.crt and /etc/nginx/ssl/threat-dragon.key (keep the key 0600 root:root) and run sudo systemctl reload nginx. For a public name you can also put the instance behind an Application Load Balancer with an AWS Certificate Manager certificate, forwarding to HTTPS port 443.

Step 10: Change the password

sudo threat-dragon-set-password

It prompts twice, stores the new password as a bcrypt hash (the password never appears on a command line or in a log), updates /var/lib/cloudimg/threat-dragon-credentials.txt and reloads nginx. Everyone who uses this instance signs in with this one login; give it only to the people who should see the application.

Step 11: Optional: keep models in a git provider

Threat Dragon can also sign users in with GitHub, GitLab, Bitbucket or Google and read and write models in a repository or a Drive folder. No provider is configured in this image. To add one, register an OAuth application with the provider using the callback URL https://<your-host>/api/oauth/return, add its settings to the instance environment file as the Threat Dragon configuration documentation describes, and restart the service:

sudo nano /etc/threat-dragon/threat-dragon.env      # e.g. GITHUB_CLIENT_ID=... and GITHUB_CLIENT_SECRET=...
sudo systemctl restart threat-dragon

The provider then appears on the front page next to the local session. The instance login stays in front of the application; Threat Dragon's own provider sessions are checked by Threat Dragon with the instance's signing key.

Step 12: Server components

Component Where Notes
OWASP Threat Dragon 2.6.2 /opt/threat-dragon, unit threat-dragon.service runs as threatdragon, socket /run/threat-dragon/app.sock, logs in /var/log/threat-dragon and the journal
Node.js 24 /usr/bin/node from NodeSource's archive, kept updated by unattended upgrades
Keys /etc/threat-dragon/threat-dragon.env 0600 root:root, generated at first boot
nginx /etc/nginx/sites-available/cloudimg-threat-dragon TLS 1.2/1.3 on 443, 80 redirects, host in /etc/nginx/threat-dragon-host.conf, login in /etc/nginx/threat-dragon.htpasswd
First boot threat-dragon-firstboot.service keys, login, certificate, /var/lib/cloudimg/threat-dragon-credentials.txt
Licences and audits /usr/share/doc/cloudimg/ the Apache 2.0 licence, attribution and the build-time dependency audits

Step 13: Security recommendations

  • Restrict TCP 443 and TCP 80 to the networks of the people who use Threat Dragon and TCP 22 to your administration networks.
  • Keep the instance patched (unattended upgrades are on; reboot when /var/run/reboot-required appears).
  • Change the password if it has been shared more widely than intended.
  • Use your own DNS name and certificate for production.
  • Keep your saved model files under version control.

Support and licensing

cloudimg provides support for this image by email at support@cloudimg.co.uk. OWASP Threat Dragon is licensed under the Apache License 2.0; the licence text is on the image in /usr/share/doc/cloudimg/licenses/. OWASP and Threat Dragon are trademarks of the OWASP Foundation; this image is packaged by cloudimg and is not affiliated with or endorsed by the OWASP Foundation.