Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Exclude files that don't belong in the Docker image
.git
.gitignore
.dockerignore
README.md
*.tar
*.tar.gz
.vscode/
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.zip
*.tar
*.tar.gz

# Editor
.vscode/

# Claude Code internal
.claude/
62 changes: 47 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
FROM node:20-alpine AS mitre-builder

WORKDIR /build
COPY scripts/generate-mitre-techniques.mjs /build/scripts/generate-mitre-techniques.mjs
RUN node /build/scripts/generate-mitre-techniques.mjs /build/mitre-techniques.json

FROM node:22-alpine AS navigator-builder

RUN apk add --no-cache git
WORKDIR /build
RUN git clone --depth 1 https://github.com/mitre-attack/attack-navigator.git
WORKDIR /build/attack-navigator/nav-app
RUN npm install
RUN npm run build -- --configuration production --base-href /attack-navigator/ --deploy-url /attack-navigator/

FROM alpine:3.19

# ── Install Apache only — no Python, no extras ────────────────────────────
RUN apk add --no-cache apache2
# ── Install Apache, Python3 and SQLite CLI ────────────────────────────────
RUN apk add --no-cache apache2 python3 sqlite

# ── Enable mod_cgi ────────────────────────────────────────────────────────
RUN sed -i 's/#LoadModule cgi_module/LoadModule cgi_module/' /etc/apache2/httpd.conf
Expand All @@ -11,33 +26,50 @@ RUN sed -i 's/^Listen 80$/Listen 8080/' /etc/apache2/
sed -i 's/#ServerName www.example.com:80/ServerName localhost/' /etc/apache2/httpd.conf && \
sed -i 's/Options Indexes FollowSymLinks/Options FollowSymLinks/' /etc/apache2/httpd.conf

# ── CGI directory config ──────────────────────────────────────────────────
# ── CGI directory config + SOP static serving ────────────────────────────
RUN echo '<Directory "/var/www/localhost/cgi-bin">' >> /etc/apache2/conf.d/cgi.conf && \
echo ' AllowOverride None' >> /etc/apache2/conf.d/cgi.conf && \
echo ' Options +ExecCGI' >> /etc/apache2/conf.d/cgi.conf && \
echo ' AddHandler cgi-script .sh' >> /etc/apache2/conf.d/cgi.conf && \
echo ' AddHandler cgi-script .sh .py' >> /etc/apache2/conf.d/cgi.conf && \
echo ' Require all granted' >> /etc/apache2/conf.d/cgi.conf && \
echo '</Directory>' >> /etc/apache2/conf.d/cgi.conf && \
echo 'ScriptAlias /cgi-bin/ /var/www/localhost/cgi-bin/' >> /etc/apache2/conf.d/cgi.conf
echo 'ScriptAlias /cgi-bin/ /var/www/localhost/cgi-bin/' >> /etc/apache2/conf.d/cgi.conf && \
echo 'PassEnv SIEM_TOOL_1 SIEM_TOOL_2 SIEM_TOOL_3 SIEM_TOOL_4 SIEM_TOOL_5' >> /etc/apache2/conf.d/cgi.conf && \
echo 'Alias /docs/ /data/docs/' >> /etc/apache2/conf.d/cgi.conf && \
echo '<Directory "/data/docs">' >> /etc/apache2/conf.d/cgi.conf && \
echo ' AllowOverride None' >> /etc/apache2/conf.d/cgi.conf && \
echo ' Options -Indexes' >> /etc/apache2/conf.d/cgi.conf && \
echo ' Require all granted' >> /etc/apache2/conf.d/cgi.conf && \
echo '</Directory>' >> /etc/apache2/conf.d/cgi.conf

# ── Logs → stdout/stderr so docker logs works ─────────────────────────────
RUN rm -rf /var/www/localhost/htdocs/* && \
ln -sf /proc/self/fd/1 /var/log/apache2/access.log && \
ln -sf /proc/self/fd/2 /var/log/apache2/error.log

# ── Copy app ──────────────────────────────────────────────────────────────
COPY index.html /var/www/localhost/htdocs/index.html
COPY cgi-bin/save_playbook.sh /var/www/localhost/cgi-bin/save_playbook.sh
COPY cgi-bin/load_playbooks.sh /var/www/localhost/cgi-bin/load_playbooks.sh
COPY cgi-bin/delete_playbook.sh /var/www/localhost/cgi-bin/delete_playbook.sh

RUN chmod +x /var/www/localhost/cgi-bin/*.sh
COPY app/index.html /var/www/localhost/htdocs/index.html
COPY app/style.css /var/www/localhost/htdocs/style.css
COPY app/app.js /var/www/localhost/htdocs/app.js
COPY app/playbooks/ /var/www/localhost/htdocs/playbooks/
COPY --from=mitre-builder /build/mitre-techniques.json /var/www/localhost/htdocs/playbooks/mitre-techniques.json
COPY --from=navigator-builder /build/attack-navigator/nav-app/dist/browser/ /var/www/localhost/htdocs/attack-navigator/
COPY app/cgi-bin/init_db.py /var/www/localhost/cgi-bin/init_db.py
COPY app/cgi-bin/load_playbooks.py /var/www/localhost/cgi-bin/load_playbooks.py
COPY app/cgi-bin/save_playbook.py /var/www/localhost/cgi-bin/save_playbook.py
COPY app/cgi-bin/update_playbook.py /var/www/localhost/cgi-bin/update_playbook.py
COPY app/cgi-bin/delete_playbook.py /var/www/localhost/cgi-bin/delete_playbook.py
COPY app/cgi-bin/load_sops.py /var/www/localhost/cgi-bin/load_sops.py
COPY app/cgi-bin/upload_sop.py /var/www/localhost/cgi-bin/upload_sop.py
COPY app/cgi-bin/delete_sop.py /var/www/localhost/cgi-bin/delete_sop.py
COPY app/cgi-bin/get_config.sh /var/www/localhost/cgi-bin/get_config.sh
COPY entrypoint.sh /entrypoint.sh

# ── Persistent playbook storage — Docker named volume mounted at /playbooks
RUN mkdir -p /playbooks && chown apache:apache /playbooks
RUN chmod +x /entrypoint.sh /var/www/localhost/cgi-bin/*.sh /var/www/localhost/cgi-bin/*.py

VOLUME ["/playbooks"]
# ── Persistent data volume ────────────────────────────────────────────────
RUN mkdir -p /data/docs && chown -R apache:apache /data

EXPOSE 8080

CMD ["httpd", "-D", "FOREGROUND"]
CMD ["/entrypoint.sh"]
103 changes: 25 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,41 @@ Alpine + Apache serving the SOC Incident Response Playbook Library.
Custom playbooks created via the web form are persisted as JSON files
on a named Docker volume at `/playbooks` inside the container.

## What's in this version (v3.0)

- 36 fully expanded playbooks from the CPT IR Playbook document
- 122 Splunk queries across detection, containment, eradication, and recovery phases
- Full step detail: each step shows a title, explanatory context, and where
applicable a ready-to-use Splunk search
- Analyst playbook creator — saves to the Docker volume, shared across all browsers
- Sidebar navigation with live search and category filtering
- DNS attack playbook (tunnelling, DGA/fast-flux, amplification, rebinding)
- Base alert procedure (intake → triage → decision → documentation)

## Stack

| Component | Detail |
|-------------|----------------------------------------------|
| Base image | alpine:3.19 |
| Web server | Apache httpd (apache2 package only) |
| Backend | Three Apache CGI shell scripts |
| Persistence | Docker named volume → `/playbooks` in container |
| Port | 8080 |
| Image size | ~10–12 MB |

## Project structure

```
soc-playbooks/
├── Dockerfile
├── docker-compose.yml
├── index.html ← full SPA (36 playbooks, 122 Splunk queries)
├── cgi-bin/
│ ├── save_playbook.sh ← POST: writes /playbooks/<id>.json
│ ├── load_playbooks.sh ← GET: returns all playbooks as JSON array
│ └── delete_playbook.sh ← POST: removes /playbooks/<id>.json
└── README.md
```
![PlayBook_Example](example.png)

## Quick start

```bash
docker compose up -d

# View logs
docker compose logs -f

# Stop (volume data preserved)
docker compose down
```

Access at: **http://localhost:8080**

## Active tool tabs

The UI shows five query/tool tabs configured with `SIEM_TOOL_1` through
`SIEM_TOOL_5` in `docker-compose.yml`. This project defaults to the priority
SOC stack: Sysmon XML, OSQuery SQL, Velociraptor VQL, Elastic EQL, and Elastic
Detection Rules. Other supported values include `splunk`, `kql`,
`security_onion`, `qradar`, `sigma`, `carbon_black`, `chronicle`,
`crowdstrike`, `defender`, `opensearch`, and `logrhythm`.

## MITRE ATT&CK group playbooks

Default threat-group playbooks are generated from the public MITRE ATT&CK
Enterprise STIX dataset. To refresh missing group playbooks and append them to
the default manifest, run:

```bash
python scripts/generate_mitre_group_playbooks.py
```

The generator creates one `Threat Groups` playbook per MITRE intrusion-set/group
ID (`Gxxxx`) and does not duplicate entries that already exist in the manifest.

## Volume management

```bash
Expand Down Expand Up @@ -81,51 +66,13 @@ docker compose up -d --build
# The playbook-data volume is untouched — custom playbooks survive the rebuild
```

## Nginx reverse proxy integration

Add to your existing nginx config:

```nginx
location /playbooks/ {
proxy_pass http://soc-playbooks:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
```

## Add to an existing Docker Compose stack (e.g. HOL stack)

```yaml
soc-playbooks:
build: ./soc-playbooks
image: soc-playbooks:latest
container_name: soc-playbooks
restart: unless-stopped
volumes:
- playbook-data:/playbooks
networks:
- your_existing_network # share with nginx proxy

volumes:
playbook-data:
driver: local
```

## Air-gapped deployment

```bash
# Export on internet-connected host
docker save soc-playbooks:latest | gzip > soc-playbooks-v3.tar.gz
docker save soc-playbooks:latest | gzip > soc-playbooks-v4.tar.gz

# On the air-gapped host
docker load < soc-playbooks-v3.tar.gz
docker load < soc-playbooks-v4.tar.gz
docker compose up -d
```

## Migrating to Gitea (future)

This container uses Apache CGI for persistence. If you move to a locally
hosted Gitea instance, the three CGI functions in index.html are replaced
with Gitea Contents API calls — see the project documentation for the
migration guide.
Loading