Deployment guide

IRIS Web

Videos

Overview: DFIR-IRIS on Ubuntu 24.04 LTS on Azure: overview (watch on YouTube)

Launch and log in: DFIR-IRIS on Ubuntu 24.04 LTS on Azure: launch and log in (watch on YouTube)

Deep Dive: DFIR-IRIS on Ubuntu 24.04 LTS on Azure: deep dive (watch on YouTube)

Overview

DFIR-IRIS is an open source collaborative platform for digital forensics and incident response teams. It gives analysts a shared case workspace with assets, indicators of compromise, a structured investigation timeline, evidence and datastore management, tasks and notes. An incident is worked in one place with a full audit trail. A complete REST API and a pluggable module system let you drive it from your existing tooling.

This SecureAnyCloud image runs DFIR-IRIS v2.4.29 in a dedicated Python virtual environment, served by gunicorn with the eventlet worker. The application is bound to loopback and reverse-proxied behind nginx, which terminates TLS. PostgreSQL holds durable state, and RabbitMQ drives a Celery worker for module pipelines and report generation.

Every secret is generated uniquely on the first boot of each instance, so nothing ships with a known password. This includes the administrator password, the REST API key, the database and broker passwords, the Flask secret key and password salt, and the TLS certificate.

What you get:

  • DFIR-IRIS v2.4.29 from the official source tag at /opt/iris-web, served by gunicorn (eventlet)
  • nginx TLS reverse proxy on port 443, a port 80 redirect, and an unauthenticated static /healthz on both for load balancer probes
  • PostgreSQL 16 holding the iris_db and iris_tasks databases with per-VM application roles
  • RabbitMQ bound to loopback as the Celery broker, with a per-VM broker user and the default guest account removed
  • A Celery worker (iris-worker.service) for modules, pipelines and report generation
  • A single administrator account created automatically at first boot with a per-VM password and REST API key
  • A per-VM self-signed TLS certificate carrying the instance address in its SAN
  • iris-web-firstboot.service generating every secret, building the database and provisioning the administrator
  • DFIR-IRIS, the worker, nginx, PostgreSQL and RabbitMQ enabled and auto-starting on boot
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key, and a VNet with a subnet.

Standard_B2s (2 vCPU, 4 GB RAM) is a comfortable starting size for a small team. Move up to a D2s or D4s size for larger teams or heavy module and reporting workloads.

Open inbound ports 443 and 80 on the network security group, plus 22 to your management network. PostgreSQL, RabbitMQ and the gunicorn application all listen only on loopback and are never exposed to the network.

1

Deploy and connect

Deploy the image from the Azure Marketplace. Then connect over SSH, using the admin username you chose at deployment time:

ssh azureuser@<vm-ip>

First boot generates every secret and builds the database. It normally completes within a minute or two of the VM reaching the running state. Confirm that it has finished before you go further:

sudo systemctl is-active iris-web-firstboot.service ls -l /var/lib/sac/data/iris-web-firstboot.done

is-active reports active and the sentinel file exists once first boot is complete.

2

Verify the services

Five services make up the appliance. Check that all five report active:

sudo systemctl is-active iris-web iris-worker nginx postgresql rabbitmq-server
3

Confirm the network exposure

Only nginx faces the network. The application, PostgreSQL, RabbitMQ, the Erlang distribution port and the epmd port mapper are all bound to loopback:

sudo ss -tlnp | grep -E ':80 |:443 |:8000 |:5432 |:5672 '

You should see nginx on 0.0.0.0:80 and 0.0.0.0:443, and gunicorn, PostgreSQL and RabbitMQ on 127.0.0.1 only.

The unauthenticated health endpoint is served over plain HTTP for load balancer probes. Everything else redirects to TLS:

curl -s http://127.0.0.1/healthz
curl -s -o /dev/null -w 'http root -> %{http_code}\n' http://127.0.0.1/

The first returns ok and the second reports 301.

4

Read the per VM credentials

Every secret is unique to this instance and is written to a root only file:

sudo cat /root/iris-web-credentials.txt

The IRIS_URL line records the address the instance resolved for itself at first boot. On a VM with a public IP, browse to that public IP or to whatever DNS name you point at the VM. DFIR-IRIS derives its URLs from the request host, so it renders correctly on any address you reach it on.

Check the version, the health endpoint and the TLS certificate that was generated for this VM:

curl -s http://127.0.0.1/healthz
curl -sk https://127.0.0.1/api/versions
sudo openssl x509 -in /etc/iris/tls/iris.crt -noout -subject -dates

Issue the certificate for your public address

First boot puts the address Azure reports into the TLS certificate and the IRIS_URL line. With a Standard SKU public IP (the Azure default), the current image records the VM's private address (10.0.0.x). The certificate therefore does not name the public IP, and API clients that trust iris.crt (curl --cacert, Python requests) refuse it there. Check:

sudo grep '^IRIS_URL=' /root/iris-web-credentials.txt

If it shows a 10. address, issue a new certificate for the public IP on the VM. Azure's instance metadata service reports the public IP to the VM itself, so nothing leaves the VM:

PUBLIC_IP=$(curl -s -H Metadata:true 'http://169.254.169.254/metadata/loadbalancer?api-version=2020-10-01' | grep -o '"frontendIpAddress":"[^"]*"' | head -1 | cut -d'"' -f4)
PRIV=$(hostname -I | awk '{print $1}'); echo "public $PUBLIC_IP, private $PRIV"
sudo openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \
  -keyout /etc/iris/tls/iris.key -out /etc/iris/tls/iris.crt \
  -subj "/CN=$PUBLIC_IP/O=sac/OU=DFIR-IRIS appliance" \
  -addext "subjectAltName=IP:$PUBLIC_IP,IP:$PRIV,IP:127.0.0.1,DNS:localhost"
sudo chmod 0600 /etc/iris/tls/iris.key
sudo sed -i "s/\b$PRIV\b/$PUBLIC_IP/g" /root/iris-web-credentials.txt
sudo systemctl reload nginx

For a DNS name, set PUBLIC_IP to the name and write DNS:$PUBLIC_IP in place of IP:$PUBLIC_IP. Your passwords and API key do not change. The certificate is still self-signed until you replace it (Step 9).

5

Sign in

Open https://<vm-ip>/ in a browser. The certificate is self-signed and generated for this VM, so your browser warns on the first visit. Accept the warning, or replace the certificate as shown in Step 9.

Sign in as administrator with the IRIS_ADMIN_PASSWORD value from the credentials file.

After you sign in, you land on the dashboard, which summarizes open cases, tasks and alerts.

A single case, #1 - Initial Demo, is created by DFIR-IRIS itself so the interface is never empty. The cases list is under Manage cases.

6

Create your first case

From the dashboard, choose Create new case. Alternatively, open Manage cases and select Add case. Fill in a case name, a description, the customer and your SOC reference, then save. The screenshots in this guide were produced with these values:

  • Case name: Ransomware outbreak - finance VLAN
  • Description: Suspected ransomware detected on the finance VLAN following an EDR alert. Scoping impacted hosts and collecting volatile evidence.
  • Customer: IrisInitialClient
  • SOC ID: SOC-2026-0042

Once the case is created, its summary is the hub for the whole investigation, with tabs for notes, assets, indicators, the timeline, the graph, tasks and evidence.

7

Record indicators, assets and evidence

Inside the case, use the IOC tab and Add IOC to record indicators. The examples shown here are an ip-any indicator 198.51.100.51 described as a C2 beacon, and an md5 indicator for the dropped encryptor, both at TLP amber.

Use the Assets tab and Add asset to record affected hosts, for example a Windows - Computer asset named FIN-WKSTN-014 described as patient zero.

Use the Evidence tab and Register Evidence to record collected artifacts with their size and hash. This is what preserves chain of custody.

8

Use the REST API

DFIR-IRIS exposes a full REST API authenticated with the per-VM key from the credentials file. List the cases on the instance:

IRIS_KEY="<IRIS_ADMIN_API_KEY>" curl -sk -H "Authorization: Bearer ${IRIS_KEY}" https://127.0.0.1/manage/cases/list | python3 -m json.tool | head -20

Create a case, then add an indicator to it:

IRIS_KEY="<IRIS_ADMIN_API_KEY>"
CASE_ID=$(curl -sk -H "Authorization: Bearer ${IRIS_KEY}" -H 'Content-Type: application/json' \
  -X POST https://127.0.0.1/manage/cases/add \
  -d '{"case_name":"Phishing triage","case_description":"Reported credential phishing campaign.","case_customer":1,"case_soc_id":"SOC-2026-0043"}' \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["data"]["case_id"])')
echo "created case ${CASE_ID}" curl -sk -H "Authorization: Bearer ${IRIS_KEY}" -H 'Content-Type: application/json' \
  -X POST "https://127.0.0.1/case/ioc/add?cid=${CASE_ID}" \
  -d '{"ioc_value":"203.0.113.24","ioc_type_id":76,"ioc_tlp_id":2,"ioc_description":"Phishing landing page host","ioc_tags":"phishing"}' \
  | python3 -m json.tool | head -12

Authentication is enforced server-side. The identical request without a key is refused:

curl -sk -o /dev/null -w 'unauthenticated case creation -> %{http_code}\n' \
  -H 'Content-Type: application/json' -X POST https://127.0.0.1/manage/cases/add \
  -d '{"case_name":"should be refused","case_description":"no credential","case_customer":1,"case_soc_id":"NOPE"}'

This reports 401.

9

Replace the TLS certificate

The appliance generates a self-signed certificate for the instance at first boot. Replace it with a certificate issued by your own CA or a public CA, then reload nginx:

sudo ls -l /etc/iris/tls/

Copy your certificate to /etc/iris/tls/iris.crt and your private key to /etc/iris/tls/iris.key. Keep the key as 0600 root:root, then run sudo nginx -t && sudo systemctl reload nginx.

10

Components

The main components and their locations:

  • Application source: /opt/iris-web/source
  • Python 3.9 virtual environment: /opt/iris-web/venv
  • Environment and secrets: /etc/iris/iris.env (0640 root:iris)
  • TLS certificate and key: /etc/iris/tls/
  • Uploads, templates, backups: /home/iris/
  • Per-VM credentials: /root/iris-web-credentials.txt (0600 root:root)
  • nginx site: /etc/nginx/sites-available/sac-iris-web
  • Web service: iris-web.service
  • Celery worker: iris-worker.service
  • First-boot service: iris-web-firstboot.service

Useful commands:

sudo systemctl status iris-web --no-pager | head -12 sudo journalctl -u iris-worker --no-pager -n 10
11

Security notes

Every secret on this instance is generated at first boot and is unique to it. There is no default administrator password and no shared key.

The image also corrects four issues present in the upstream project. All four matter for a platform that holds forensic evidence:

  • Development TLS private keys are not shipped. The upstream repository commits a working root CA private key, whose certificate remains valid until 2032, along with a development server key, and its sample environment points nginx at them. Anyone holding that CA key could mint certificates trusted by any deployment that installed it. Neither key is present in this image. A fresh keypair is generated per VM instead.
  • No upstream default secrets are in use. The upstream sample environment ships a syntactically valid Flask secret key and password salt, so a careless deployment runs with a publicly known session key. This image generates both per VM and fails the build if either default string is present.
  • Nothing but nginx faces the network. Upstream binds the application, PostgreSQL and RabbitMQ to all interfaces and relies on the container network for isolation, which does not exist in a native install. Here, all three are bound to loopback.
  • The case Assets tab renders correctly for assets created through the REST API. In upstream v2.4.29, the assets table crashes while rendering if an asset has no analysis status. This is exactly what the REST API produces when analysis_status_id is omitted, and it leaves that case's Assets tab loading forever. This image includes a null guard so that API-created assets display normally with an Unspecified status.

The administrator password is recorded in the credentials file only. The image deliberately provisions the administrator before the service starts, so the password is never written to the system journal.

Recommended next steps:

  • Replace the TLS certificate (Step 9).
  • Create individual analyst accounts under Manage rather than sharing the administrator login.
  • Review the access control groups.
  • Restrict inbound access to your analyst networks with a network security group rule.

Licensing

DFIR-IRIS is distributed under the GNU Lesser General Public License v3.0 or later. This is a repackaged open source software product, with additional charges for SecureAnyCloud support services. DFIR-IRIS is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.

Deploy on Azure

Launch DFIR-IRIS on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure