From 321553eabc40949cc56ca108ae4a1555c00d4d1b Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 6 Nov 2025 18:46:10 -0600 Subject: [PATCH 01/47] feat: completely rearrange all the docker files, update README, and add more documentation --- README.md | 225 +++--------------- configs/rest-api/template.env | 66 ----- docker/README.md | 3 + .../example-setup/compose.certs.yaml | 4 +- .../example-setup/compose.dev.yaml | 6 +- .../example-setup/compose.yaml | 19 +- .../configs}/frontend/nginx.conf | 0 .../rest-api/rest-api-service-config.json | 0 .../configs/rest-api/template.env | 138 +++++++++++ .../example-setup/configs}/taxii/README.md | 0 .../configs}/taxii/config/template.env | 4 +- .../example-setup/configs}/taxii/nginx.conf | 2 +- docker/example-setup/template.env | 33 +++ docs/configuration.md | 94 ++++++++ docs/database-backups.md | 69 ++++++ docs/deployment.md | 57 +++++ docs/troubleshooting.md | 20 ++ template.env | 22 -- 18 files changed, 469 insertions(+), 293 deletions(-) delete mode 100644 configs/rest-api/template.env create mode 100644 docker/README.md rename compose.certs.yaml => docker/example-setup/compose.certs.yaml (87%) rename compose.dev.yaml => docker/example-setup/compose.dev.yaml (53%) rename compose.yaml => docker/example-setup/compose.yaml (77%) rename {configs => docker/example-setup/configs}/frontend/nginx.conf (100%) rename {configs => docker/example-setup/configs}/rest-api/rest-api-service-config.json (100%) create mode 100644 docker/example-setup/configs/rest-api/template.env rename {configs => docker/example-setup/configs}/taxii/README.md (100%) rename {configs => docker/example-setup/configs}/taxii/config/template.env (98%) rename {configs => docker/example-setup/configs}/taxii/nginx.conf (98%) create mode 100644 docker/example-setup/template.env create mode 100644 docs/configuration.md create mode 100644 docs/database-backups.md create mode 100644 docs/deployment.md create mode 100644 docs/troubleshooting.md delete mode 100644 template.env diff --git a/README.md b/README.md index 175aaee..9b620f4 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,64 @@ # ATT&CK Workbench Deployment -This repository contains deployment files for the ATT&CK Workbench, a web application for editing ATT&CK data represented in STIX. It is composed of a frontend SPA, a backend REST API, and a database. Optionally, you can deploy a "sidecar service" that makes your Workbench data available over a TAXII 2.1 API. +This repository contains deployment files for the ATT&CK Workbench, a web application for editing ATT&CK data represented in STIX. +It is composed of a frontend Single Page App (SPA), a backend REST API, and a database. +Optionally, you can deploy a "sidecar service" that makes your Workbench data available over a TAXII 2.1 API. -## Deployment Options - -### Docker Compose - -The ATT&CK Workbench can be deployed using Docker Compose with two different configurations: - -#### 1. Using Pre-built Images (Recommended) +## Quick Start -Use `compose.yaml` to pull pre-built images directly from GitHub Container Registry (GHCR): +### Deploy with Docker Compose ```bash -# Deploy with pre-built images -docker compose up -d - -# Deploy with TAXII server -docker compose --profile with-taxii up -d - -# Stop the deployment -docker compose down -``` +# Clone this repository +git clone https://github.com/mitre-attack/attack-workbench-deployment.git +cd attack-workbench-deployment -#### 2. Building from Source +# Copy docker compose template (git-ignored) +mkdir -p instances/my-workbench +cp -r docker/example-setup/* instances/my-workbench/ -Use `compose.dev.yaml` in combination with `compose.yaml` to build images from source code: +# Configure environment +cd instances/my-workbench +mv template.env .env +mv configs/rest-api/template.env configs/rest-api/.env -```bash -# Build and deploy from source -docker compose -f compose.yaml -f compose.dev.yaml up -d --build +# edit the following files as needed +# instances/my-workbench/.env +# configs/rest-api/.env +# configs/rest-api/rest-api-service-config.json -# Build and deploy with TAXII server -docker compose -f compose.yaml -f compose.dev.yaml --profile with-taxii up -d --build +# Deploy +docker compose up -d -# Stop the deployment -docker compose -f compose.yaml -f compose.dev.yaml down +# (Optional) Deploy with TAXII server +docker compose --profile with-taxii up -d ``` -**Note**: When building from source, you need the following three source repositories to be available as sibling directories to this deployment repository: +Access Workbench at -- [attack-workbench-frontend](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/) -- [attack-workbench-rest-api](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/) -- [attack-workbench-taxii-server](https://mitre-attack/attack-workbench-taxii-server/) +Full variable descriptions and examples are available in [docs/configuration](docs/configuration.md). -The directory structure should look like this: +For source builds or TAXII setup, see [docs/deployment](docs/deployment.md). -```bash -. -├── attack-workbench-deployment -├── attack-workbench-frontend -├── attack-workbench-rest-api -└── attack-workbench-taxii-server (optional) -``` +For information on how to backup or restore the mongo database, see [docs/database-backups](docs/database-backups.md). -### Kubernetes +## Kubernetes For production deployments, Kubernetes manifests with Kustomize are available in the `k8s/` directory. -See [k8s/README.md](k8s/README.md) for detailed instructions. - -## Configuration - -### Environment Variables - -We make heavy use of string interpolation to minimize having to modify the Docker Compose manifest files (e.g., [compose.yaml](./compose.yaml)). Consequently, that means you must set a bunch of environment variables when using these templates. Fortunately, we've provided a dotenv template that you can source. - -Copy `template.env` to `.env` and customize the values as needed: - -```bash -cp template.env .env -``` - -Available environment variables: - -| Variable | Default Value | Description | -|----------|---------------|-------------| -| **Docker Image Tags** | | | -| `ATTACKWB_FRONTEND_VERSION` | `latest` | Frontend Docker image tag | -| `ATTACKWB_RESTAPI_VERSION` | `latest` | REST API Docker image tag | -| `ATTACKWB_TAXII_VERSION` | `latest` | TAXII server Docker image tag | -| **HTTP Listener Ports** | | | -| `ATTACKWB_FRONTEND_HTTP_PORT` | `80` | Frontend HTTP port | -| `ATTACKWB_FRONTEND_HTTPS_PORT` | `443` | Frontend HTTPS port | -| `ATTACKWB_RESTAPI_HTTP_PORT` | `3000` | REST API port | -| `ATTACKWB_DB_PORT` | `27017` | MongoDB port | -| `ATTACKWB_TAXII_HTTP_PORT` | `5002` | TAXII server port | -| **SSL/TLS Configuration** | | | -| `ATTACKWB_FRONTEND_CERTS_PATH` | `./certs` | Path to SSL certificates | -| **TAXII Configuration** | | | -| `ATTACKWB_TAXII_ENV` | `dev` | Specifies the name of the dotenv file to load (e.g., A value of `dev` tells the TAXII server to load `dev.env`) | - -### Service-Specific Configuration - -Each service has its own configuration directory: - -#### Frontend - -**Default config files**: `configs/frontend/` - -The frontend container is an Nginx instance which serves the frontend SPA and reverse proxies requests to the backend REST API. -We provide a basic `nginx.conf` template in the aforementioned directory that should get you started. -Refer to the [frontend documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) -for further details on customizing the SPA. - -#### REST API - -> [!IMPORTANT] -> The REST API service requires the `SESSION_SECRET` environment variable to be set in order to deploy. -> Without it set, `docker compose up` will fail to start this required service. - -**Default config files**: `configs/rest-api/` - -The backend REST API loads runtime configurations from environment variables, as well as from a JSON configuration file. -Templates are provided in the aforementioned directory. -Refer to the [REST API usage documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/blob/main/USAGE.md#configuration) -for further details on customizing the backend. - -#### TAXII Server - -**Default config files**: `configs/taxii/config/` - -The TAXII server loads all runtime configuration parameters from a dotenv file. -The specific filename of the dotenv file is specified by the `ATTACKWB_TAXII_ENV` environment variable. -For example, a value of `dev` tells the TAXII server to load `dev.env`. - -## Quick Start - -1. Clone this repository: - - ```bash - git clone https://github.com/center-for-threat-informed-defense/attack-workbench-deployment.git - cd attack-workbench-deployment - ``` - -2. Configure environment variables (optional): - - ```bash - cp template.env .env - # Edit .env with your preferred settings - ``` - -3. Configure REST API environment variables (required): - - ```bash - cp configs/rest-api/template.env configs/rest-api/.env - ``` - - Generate a secure random secret - - ```bash - node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" - ``` - - Set the above secret in `configs/rest-api/.env` - - ```bash - SESSION_SECRET= - ``` - -4. Deploy using pre-built images: - - ```bash - docker compose up -d - ``` - -5. Access the application at `http://localhost` (or your configured port) - -6. To include the TAXII server: - - ```bash - docker compose --profile with-taxii up -d - ``` - -## Data Persistence - -MongoDB data is persisted in the `workspace-data` named Docker volume. Thus, the `database` service can be deleted and re-deployed without losing access to the database. The database volume will be remounted to the `database` service upon deployment. - -## Troubleshooting - -### Check Service Status - -```bash -# View running containers -docker compose ps - -# Show logs for all running containers -docker compose logs - -# Follow logs -docker compose logs -f - -# Show logs for a specific container -docker compose logs frontend -docker compose logs rest-api -docker compose logs database -docker compose logs taxii -``` -## Contributing +See [k8s/README](k8s/README.md) for detailed instructions. -Please refer to the [contribution guide](./docs/CONTRIBUTING.md) for contribution guidelines, as well as the [developer guide](./docs/DEVELOPMENT.md) for information on our release process. +## Troubleshooting & Support -## License +- View logs: `docker compose logs -f` +- Check running containers: `docker compose ps` -This project is licensed under the Apache License 2.0. See the [LICENSE](./LICENSE) file for details. +More tips in [docs/troubleshooting](docs/troubleshooting.md). -## Support +For questions or issues, visit the [GitHub issues page](https://github.com/mitre-attack/attack-workbench-deployment/issues). -For issues and questions: +## Contributing & License -- Check the [deployment repository issues](https://github.com/center-for-threat-informed-defense/attack-workbench-deployment/issues) -- Refer to the main [ATT&CK Workbench documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) +- Contribution guide: [contribution guide](./docs/CONTRIBUTING.md) +- Developer guide: [developer guide](./docs/DEVELOPMENT.md) +- License: [Apache License 2.0](./LICENSE) diff --git a/configs/rest-api/template.env b/configs/rest-api/template.env deleted file mode 100644 index 7b8874e..0000000 --- a/configs/rest-api/template.env +++ /dev/null @@ -1,66 +0,0 @@ -# HTTP Listener Port -PORT=3000 - -# CORS (`*`, `disable`, or comma-separated list of FQDNs) -CORS_ALLOWED_ORIGINS=* - -# Environment -NODE_ENV=development - -# Database -DATABASE_URL=mongodb://attack-workbench-database/attack-workspace - -# Database Migration -WB_REST_DATABASE_MIGRATION_ENABLE=true - -# Authentication Mechanism -AUTHN_MECHANISM=anonymous - -# OIDC Authentication -AUTHN_OIDC_CLIENT_ID= -AUTHN_OIDC_CLIENT_SECRET= -AUTHN_OIDC_ISSUER_URL= -AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000 - -# Service Account Authentication - OIDC Client Credentials -SERVICE_ACCOUNT_OIDC_ENABLE=false -JWKS_URI= - -# Service Account Authentication - Challenge API Key -WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE=false -WB_REST_TOKEN_SIGNING_SECRET= -WB_REST_TOKEN_TIMEOUT=300 - -# Service Account Authentication - Basic API Key -WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE=false - -# Collection Index Interval -DEFAULT_INTERVAL=300 - -# Configuration File Path -JSON_CONFIG_PATH= - -# Logging -LOG_LEVEL=info - -# Static Marking Definitions Path -WB_REST_STATIC_MARKING_DEFS_PATH=./app/lib/default-static-marking-definitions/ - -# Allowed Values Configuration File Path -ALLOWED_VALUES_PATH=./app/config/allowed-values.json - -# Scheduler Settings -CHECK_WORKBENCH_INTERVAL=10 -ENABLE_SCHEDULER=true - -########## -# OPTIONAL -########## - -# Session Configuration -# Generate a secure random secret with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" -# If not provided, the REST API will generate one for you at startup (not recommended for production) -#SESSION_SECRET= - -# Path to additional CA certificates file in PEM format -#NODE_EXTRA_CA_CERTS= diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..5b5b379 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,3 @@ +# Example Docker Compose Setup + +This directory has an example docker compose setup in the [example-setup](example-setup/) directory. diff --git a/compose.certs.yaml b/docker/example-setup/compose.certs.yaml similarity index 87% rename from compose.certs.yaml rename to docker/example-setup/compose.certs.yaml index 19667b8..c9bf528 100644 --- a/compose.certs.yaml +++ b/docker/example-setup/compose.certs.yaml @@ -18,6 +18,6 @@ services: rest-api: volumes: - - .${HOST_CERTS_PATH}:/usr/src/app/certs + - .${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} diff --git a/compose.dev.yaml b/docker/example-setup/compose.dev.yaml similarity index 53% rename from compose.dev.yaml rename to docker/example-setup/compose.dev.yaml index 9c18d81..a32cb3d 100644 --- a/compose.dev.yaml +++ b/docker/example-setup/compose.dev.yaml @@ -2,12 +2,12 @@ services: frontend: image: attack-workbench-frontend - build: ../attack-workbench-frontend + build: ../../../attack-workbench-frontend rest-api: image: attack-workbench-rest-api - build: ../attack-workbench-rest-api + build: ../../../attack-workbench-rest-api taxii: image: attack-workbench-taxii-server - build: ../attack-workbench-taxii-server + build: ../../../attack-workbench-taxii-server diff --git a/compose.yaml b/docker/example-setup/compose.yaml similarity index 77% rename from compose.yaml rename to docker/example-setup/compose.yaml index d6ec03b..2167dfd 100644 --- a/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -6,10 +6,10 @@ services: depends_on: - rest-api ports: - - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:${ATTACKWB_FRONTEND_HTTP_PORT:-80}" - - "${ATTACKWB_FRONTEND_HTTPS_PORT:-443}:${ATTACKWB_FRONTEND_HTTPS_PORT:-443}" + - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:80" + - "${ATTACKWB_FRONTEND_HTTPS_PORT:-443}:443" volumes: - - ./configs/frontend/nginx.conf:/etc/nginx/nginx.conf:ro + - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.conf}:/etc/nginx/nginx.conf:ro" - "${ATTACKWB_FRONTEND_CERTS_PATH:-./certs}:/etc/nginx/certs:ro" restart: unless-stopped logging: @@ -22,13 +22,13 @@ services: container_name: attack-workbench-rest-api image: ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api:${ATTACKWB_RESTAPI_VERSION:-latest} depends_on: - - database + - mongodb ports: - "${ATTACKWB_RESTAPI_HTTP_PORT:-3000}:${ATTACKWB_RESTAPI_HTTP_PORT:-3000}" volumes: - - ./configs/rest-api/rest-api-service-config.json:/usr/src/app/resources/rest-api-service-config.json:ro + - "${ATTACKWB_RESTAPI_CONFIG_FILE:-./configs/rest-api/rest-api-service-config.json}:/usr/src/app/resources/rest-api-service-config.json:ro" env_file: - - ./configs/rest-api/.env + - "${ATTACKWB_RESTAPI_ENV_FILE:-./configs/rest-api/.env}" restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/health/ping"] @@ -41,11 +41,11 @@ services: max-size: "10m" max-file: "5" - database: + mongodb: container_name: attack-workbench-database image: mongo:8 ports: - - "${ATTACKWB_DB_PORT:-27017}:${ATTACKWB_DB_PORT:-27017}" + - "127.0.0.1:${ATTACKWB_DB_PORT:-27017}:${ATTACKWB_DB_PORT:-27017}" volumes: - workspace-data:/data/db - ./database-backup:/dump @@ -69,10 +69,9 @@ services: ports: - "${ATTACKWB_TAXII_HTTP_PORT:-5002}:${ATTACKWB_TAXII_HTTP_PORT:-5002}" volumes: - - ./configs/taxii/config:/app/config:ro + - "${ATTACKWB_TAXII_CONFIG_DIR:-./configs/taxii/config}:/app/config:ro" environment: - TAXII_ENV=${ATTACKWB_TAXII_ENV:-dev} - - TAXII_HYDRATE_ON_BOOT=true profiles: - with-taxii restart: unless-stopped diff --git a/configs/frontend/nginx.conf b/docker/example-setup/configs/frontend/nginx.conf similarity index 100% rename from configs/frontend/nginx.conf rename to docker/example-setup/configs/frontend/nginx.conf diff --git a/configs/rest-api/rest-api-service-config.json b/docker/example-setup/configs/rest-api/rest-api-service-config.json similarity index 100% rename from configs/rest-api/rest-api-service-config.json rename to docker/example-setup/configs/rest-api/rest-api-service-config.json diff --git a/docker/example-setup/configs/rest-api/template.env b/docker/example-setup/configs/rest-api/template.env new file mode 100644 index 0000000..a8e8c45 --- /dev/null +++ b/docker/example-setup/configs/rest-api/template.env @@ -0,0 +1,138 @@ +# Attack Workbench REST API - Environment Configuration Template +# Guidance: +# - Booleans: use true or false +# - Lists: use comma-separated values + +# Server +# PORT (int) - HTTP server port +# Default: 3000 +#PORT=3000 + +# Database (REQUIRED) +# DATABASE_URL (string) - MongoDB connection string +# Example (Docker): +#DATABASE_URL=mongodb://attack-workbench-database/attack-workspace +# Example (local): +#DATABASE_URL=mongodb://localhost:27017/attack-workspace +DATABASE_URL= + +# CORS_ALLOWED_ORIGINS (domains) - Allowed origins for REST API +# Accepts: +# * : allow any origin +# disable : disable CORS +# Comma-separated list of origins (http/https), e.g.: +# http://localhost:3000,https://example.com,https://sub.domain.org:8443 +# Supports localhost, private IPv4 (10.x, 172.16-31.x, 192.168.x), and FQDNs. +# Default: * +#CORS_ALLOWED_ORIGINS=* + +# Application +# NODE_ENV (string) - Environment name +# Options: development, production, test +# Default: development +#NODE_ENV=development + +# WB_REST_DATABASE_MIGRATION_ENABLE (bool) - Auto-run DB migrations on startup +# Default: true +#WB_REST_DATABASE_MIGRATION_ENABLE=true + +# Logging +# LOG_LEVEL (string) - Console log level +# Options: error, warn, http, info, verbose, debug +# Default: info +#LOG_LEVEL=info + +# Workbench Collection Indexes +# DEFAULT_INTERVAL (int, seconds) - Default polling interval for new indexes +# Note: does not affect existing indexes +# Default: 300 +#DEFAULT_INTERVAL=300 + +# Configuration Files +# JSON_CONFIG_PATH (string) - Path to a JSON file with additional configuration. +# Use this to provide arrays for service accounts and OIDC clients +# +# Some example values which align to some sample configurations which can be found here: +# https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/tree/main/resources/sample-configurations +# +# ./resources/collection-manager-apikey.json +# ./resources/collection-manager-oidc-keycloak.json +# ./resources/collection-manager-oidc-okta.json +# +# Default: empty (disabled) +#JSON_CONFIG_PATH= + +# ALLOWED_VALUES_PATH (string) - Path to allowed values configuration file +# Default: ./app/config/allowed-values.json +#ALLOWED_VALUES_PATH=./app/config/allowed-values.json + +# WB_REST_STATIC_MARKING_DEFS_PATH (string) - Directory of static marking definition JSON files +# Default: ./app/lib/default-static-marking-definitions/ +#WB_REST_STATIC_MARKING_DEFS_PATH=./app/lib/default-static-marking-definitions/ + +# Scheduler +# ENABLE_SCHEDULER (bool) - Enable background scheduler +# Default: true +#ENABLE_SCHEDULER=true + +# CHECK_WORKBENCH_INTERVAL (int, seconds) - Scheduler start interval +# Default: 10 +#CHECK_WORKBENCH_INTERVAL=10 + +# Session +# SESSION_SECRET (string) - Secret to sign session cookies. +# Default: securely generated at startup (changes on restart; not recommended for production). +# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" +#SESSION_SECRET= + +# User Authentication +# AUTHN_MECHANISM (enum) - User login mechanism +# Options: anonymous, oidc +# Default: anonymous +#AUTHN_MECHANISM=anonymous + +# OIDC settings (required if AUTHN_MECHANISM=oidc) +# AUTHN_OIDC_ISSUER_URL (string) - OIDC issuer URL (e.g., https://idp.example.com) +# Default: empty +#AUTHN_OIDC_ISSUER_URL= +# AUTHN_OIDC_CLIENT_ID (string) - OIDC client ID +# Default: empty +#AUTHN_OIDC_CLIENT_ID= +# AUTHN_OIDC_CLIENT_SECRET (string) - OIDC client secret +# Default: empty +#AUTHN_OIDC_CLIENT_SECRET= +# AUTHN_OIDC_REDIRECT_ORIGIN (string) - Origin used to build redirect URI +# Example: http://localhost:3000 -> http://localhost:3000/authn/oidc/callback +# Default: http://localhost:3000 +#AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000 + +# Service Authentication +# OIDC Client Credentials (service-to-service) +# SERVICE_ACCOUNT_OIDC_ENABLE (bool) - Enable client credentials flow +# Default: false +#SERVICE_ACCOUNT_OIDC_ENABLE=false +# JWKS_URI (string) - JWKS endpoint for IdP public keys (required if enabled) +# Default: empty +#JWKS_URI= + +# Challenge API Key (token exchange) +# WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE (bool) - Enable challenge flow +# Default: false +#WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE=false +# WB_REST_TOKEN_SIGNING_SECRET (string) - Token signing secret +# Default: securely generated at startup (changes on restart; set for production) +#WB_REST_TOKEN_SIGNING_SECRET= +# WB_REST_TOKEN_TIMEOUT (int, seconds) - Access token lifetime +# Default: 300 +#WB_REST_TOKEN_TIMEOUT=300 + +# Basic API Key (no challenge) +# WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE (bool) - Enable basic apikey auth +# Default: false +#WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE=false + +# TLS/Certificates +# NODE_EXTRA_CA_CERTS (string) - Path to additional CA certs in PEM format +# Useful when MongoDB or IdP uses a private CA +# Default: empty +#NODE_EXTRA_CA_CERTS= diff --git a/configs/taxii/README.md b/docker/example-setup/configs/taxii/README.md similarity index 100% rename from configs/taxii/README.md rename to docker/example-setup/configs/taxii/README.md diff --git a/configs/taxii/config/template.env b/docker/example-setup/configs/taxii/config/template.env similarity index 98% rename from configs/taxii/config/template.env rename to docker/example-setup/configs/taxii/config/template.env index 13d07a4..d9d50f8 100644 --- a/configs/taxii/config/template.env +++ b/docker/example-setup/configs/taxii/config/template.env @@ -21,8 +21,8 @@ TAXII_MAX_CONTENT_LENGTH=0 # ******************************************************************************************************************** # ***** NGINX SSL/TLS CERTIFICATE AUTO REG/RENEW ********************************************************************* # ******************************************************************************************************************** -CERTBOT_LE_FQDN=attack-taxii.mitre.org -CERBOT_LE_EMAIL=attack@mitre.org +CERTBOT_LE_FQDN=taxii.example.com +CERTBOT_LE_EMAIL=noreply@example.com CERTBOT_LE_ACME_SERVER=https://acme-v02.api.letsencrypt.org/directory CERTBOT_LE_RSA_KEY_SIZE=4096 diff --git a/configs/taxii/nginx.conf b/docker/example-setup/configs/taxii/nginx.conf similarity index 98% rename from configs/taxii/nginx.conf rename to docker/example-setup/configs/taxii/nginx.conf index 43c3ca5..30065d3 100644 --- a/configs/taxii/nginx.conf +++ b/docker/example-setup/configs/taxii/nginx.conf @@ -34,7 +34,7 @@ http { listen 80 default_server; listen [::]:80 default_server; - server_name attack-taxii.mitre.org; + server_name taxii.example.com; location /.well-known/acme-challenge { resolver 127.0.0.11 valid=30s; # If you're wondering if 127.0.0.11 is a typo – it's not – it is actually the diff --git a/docker/example-setup/template.env b/docker/example-setup/template.env new file mode 100644 index 0000000..b656c13 --- /dev/null +++ b/docker/example-setup/template.env @@ -0,0 +1,33 @@ +# Project +COMPOSE_PROJECT_NAME=attack-workbench + +# Docker Image Tags +ATTACKWB_FRONTEND_VERSION=latest +ATTACKWB_RESTAPI_VERSION=latest +ATTACKWB_TAXII_VERSION=latest + +# Frontend +#ATTACKWB_FRONTEND_HTTP_PORT=80 +#ATTACKWB_FRONTEND_HTTPS_PORT=443 +#ATTACKWB_FRONTEND_NGINX_CONFIG_FILE=./configs/frontend/nginx.conf +# Used for setting SSL certs in nginx +#ATTACKWB_FRONTEND_CERTS_PATH=./certs + +# REST API +#ATTACKWB_RESTAPI_HTTP_PORT=3000 +#ATTACKWB_RESTAPI_CONFIG_FILE=./configs/rest-api/rest-api-service-config.json +#ATTACKWB_RESTAPI_ENV_FILE=./configs/rest-api/.env + +# REST API Custom SSL certs (optional) +# These will be used to set NODE_EXTRA_CA_CERTS +# See compose.certs.yaml for details +#HOST_CERTS_PATH=./certs +#CERTS_FILENAME=custom-certs.pem + +# Database +#ATTACKWB_DB_PORT=27017 + +# TAXII Server +#ATTACKWB_TAXII_HTTP_PORT=5002 +#ATTACKWB_TAXII_CONFIG_DIR=./configs/taxii/config +#ATTACKWB_TAXII_ENV=dev diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..5bf6dfc --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,94 @@ +# Configuration + +## Docker Compose Environment Variables + +We make heavy use of string interpolation to minimize having to modify the Docker Compose files. +Consequently, that means you must set a bunch of environment variables when using these templates. +Fortunately, we've provided a dotenv template that you can source. + +Copy `template.env` to `.env` and customize the values as needed: + +```bash +cp template.env .env +``` + +Available environment variables: + +### Docker Image Tags + +| Variable | Default Value | Description | +|-----------------------------|---------------|-------------------------------| +| `ATTACKWB_FRONTEND_VERSION` | `latest` | Frontend Docker image tag | +| `ATTACKWB_RESTAPI_VERSION` | `latest` | REST API Docker image tag | +| `ATTACKWB_TAXII_VERSION` | `latest` | TAXII server Docker image tag | + +### Frontend + +| Variable | Default Value | Description | +|---------------------------------------|---------------------------------|------------------------------------| +| `ATTACKWB_FRONTEND_HTTP_PORT` | `80` | Frontend HTTP port | +| `ATTACKWB_FRONTEND_HTTPS_PORT` | `443` | Frontend HTTPS port | +| `ATTACKWB_FRONTEND_NGINX_CONFIG_FILE` | `./configs/frontend/nginx.conf` | Path to nginx config file | +| `ATTACKWB_FRONTEND_CERTS_PATH` | `./certs` | Path to SSL certificates for nginx | + +### REST API + +| Variable | Default Value | Description | +|--------------------------------|---------------------------------------------------|---------------------------------------------------| +| `ATTACKWB_RESTAPI_HTTP_PORT` | `3000` | REST API port | +| `ATTACKWB_RESTAPI_CONFIG_FILE` | `./configs/rest-api/rest-api-service-config.json` | Path to REST API JSON config file | +| `ATTACKWB_RESTAPI_ENV_FILE` | `./configs/rest-api/.env` | Path to REST API environment variable config file | + +### REST API Custom SSL certs (Optional) + +These will be used to set `NODE_EXTRA_CA_CERTS` in the REST API docker container. +See `compose.certs.yaml` for details + +| Variable | Default Value | Description | +|-------------------|--------------------|-------------------------------| +| `HOST_CERTS_PATH` | `./certs` | Path to custom cert directory | +| `CERTS_FILENAME` | `custom-certs.pem` | Filename of custom cert | + +### Database + +| Variable | Default Value | Description | +|--------------------|---------------|--------------| +| `ATTACKWB_DB_PORT` | `27017` | MongoDB port | + +### TAXII Server + +| Variable | Default Value | Description | +|-----------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------| +| `ATTACKWB_TAXII_HTTP_PORT` | `5002` | TAXII server port | +| `ATTACKWB_TAXII_CONFIG_DIR` | `./configs/taxii/config` | DIrectory to find TAXII config file in | +| `ATTACKWB_TAXII_ENV` | `dev` | Specifies the name of the dotenv file to load (e.g., A value of `dev` tells the TAXII server to load `dev.env`) | + +## Service-Specific Configuration + +Each service has its own configuration directory: + +### Frontend + +**Config files**: [configs/frontend/](../docker/example-setup/configs/frontend/) + +The frontend container is an Nginx instance which serves the frontend SPA and reverse proxies requests to the backend REST API. +We provide a basic `nginx.conf` template in the aforementioned directory that should get you started. +Refer to the [frontend documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) +for further details on customizing the Workbench frontend. + +### REST API + +**Config files**: [configs/rest-api/](../docker/example-setup/configs/rest-api/) + +The backend REST API loads runtime configurations from environment variables, as well as from a JSON configuration file. +Templates are provided in the aforementioned directory. +Refer to the [REST API usage documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/blob/main/USAGE.md#configuration) +for further details on customizing the backend. + +### TAXII Server + +**Config files**: [configs/taxii/config/](../docker/example-setup/configs/taxii/config/) + +The TAXII server loads all runtime configuration parameters from a dotenv file. +The specific filename of the dotenv file is specified by the `ATTACKWB_TAXII_ENV` environment variable. +For example, a value of `dev` tells the TAXII server to load `dev.env`. diff --git a/docs/database-backups.md b/docs/database-backups.md new file mode 100644 index 0000000..197038c --- /dev/null +++ b/docs/database-backups.md @@ -0,0 +1,69 @@ +# Database Backups + +The MongoDB commands `mongodump` and `mongorestore` can be used to create the database backup files and to restore the database using those files. + +The `compose.yaml` file maps the `database-backup/` directory on the host to the `/dump` directory +in the container in order to ease access to the backup files and to make sure those files exist even if the container is deleted. +This directory is listed in the `.gitignore` file so the backup files will not be added to the git repo. + +To access the command line inside the container, run this command from the host: + +```shell +docker exec -it attack-workbench-database bash +``` + +## Single Archive File + +These commands backup the data in a single compressed file. + +### Creating a Database Backup + +Create the backup as a compressed archive file: + +```shell +# From inside the attack-workbench-database container +mongodump --db attack-workspace --gzip --archive=dump/workspace.archive.gz +``` + +This creates a file in `/dump` in the container (`database-backup/` on the host). + +### Restoring the Database from the Backup + +The backup file must be in `database-backup/` on the host. + +Restoring from the compressed archive file: + +```shell +# From inside the attack-workbench-database container +mongorestore --drop --gzip --archive=dump/workspace.archive.gz +``` + +This drops the collections from the database, recreates the collections, loads the backed up documents into those collections, and rebuilds the indexes. + +## Multiple Files + +These commands backup the data in multiple files (a file for each collection and index). + +### Creating a Database Backup + +Create the backup files: + +```shell +# From inside the attack-workbench-database container +mongodump --db attack-workspace +``` + +This creates a set of files in `/dump/attack-workspace` in the container (`/database-backup/attack-workspace` on the host). + +### Restoring the Database from the Backup Files + +The backup files must be in `database-backup/attack-workspace` on the host. + +Restoring from the backup files: + +```shell +# From inside the attack-workbench-database container +mongorestore --drop dump/ +``` + +This drops the collections from the database, recreates the collections, loads the backed up documents into those collections, and rebuilds the indexes. diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..bcfe8c7 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,57 @@ +# Deployment Options + +## Docker Compose + +The ATT&CK Workbench can be deployed using Docker Compose with two different configurations: + +### 1. Using Pre-built Images (Recommended) + +Use `compose.yaml` to pull pre-built images directly from GitHub Container Registry (GHCR): + +```bash +# Deploy with pre-built images +docker compose up -d + +# Deploy with TAXII server +docker compose --profile with-taxii up -d + +# Stop the deployment +docker compose down +``` + +### 2. Building from Source + +Use `compose.dev.yaml` in combination with `compose.yaml` to build images from source code: + +```bash +# Build and deploy from source +docker compose -f compose.yaml -f compose.dev.yaml up -d --build + +# Build and deploy with TAXII server +docker compose -f compose.yaml -f compose.dev.yaml --profile with-taxii up -d --build + +# Stop the deployment +docker compose -f compose.yaml -f compose.dev.yaml down +``` + +**Note**: When building from source, you need the following three source repositories to be available as sibling directories to this deployment repository: + +- [attack-workbench-frontend](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/) +- [attack-workbench-rest-api](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/) +- [attack-workbench-taxii-server](https://github.com/mitre-attack/attack-workbench-taxii-server) + +The directory structure should look like this: + +```bash +. +├── attack-workbench-deployment +├── attack-workbench-frontend +├── attack-workbench-rest-api +└── attack-workbench-taxii-server (optional) +``` + +### Data Persistence + +MongoDB data is persisted in the `workspace-data` named Docker volume. +Thus, the `database` service can be deleted and re-deployed without losing access to the database. +The database volume will be remounted to the `database` service upon deployment. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..c21d998 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,20 @@ +# Troubleshooting + +Here are a few commands you can use to troubleshoot the docker compose setup. + +```bash +# View running containers +docker compose ps + +# Show logs for all running containers +docker compose logs + +# Follow logs +docker compose logs -f + +# Show logs for a specific container +docker compose logs frontend +docker compose logs rest-api +docker compose logs database +docker compose logs taxii +``` diff --git a/template.env b/template.env deleted file mode 100644 index 0f33b85..0000000 --- a/template.env +++ /dev/null @@ -1,22 +0,0 @@ -# Docker Image Tags -ATTACKWB_FRONTEND_VERSION=latest -ATTACKWB_RESTAPI_VERSION=latest -ATTACKWB_TAXII_VERSION=latest - -# HTTP Listener Ports -ATTACKWB_FRONTEND_HTTP_PORT=80 -ATTACKWB_FRONTEND_HTTPS_PORT=443 -ATTACKWB_RESTAPI_HTTP_PORT=3000 -ATTACKWB_DB_PORT=27017 -ATTACKWB_TAXII_HTTP_PORT=5002 - -# Nginx SSL/TLS certs path -ATTACKWB_FRONTEND_CERTS_PATH=./certs - -# TAXII dotenv filename -ATTACKWB_TAXII_ENV=dev - -# For setting custom SSL/TLS certs in nginx -# See compose.certs.yaml for details -HOST_CERTS_PATH= -CERTS_FILENAME= \ No newline at end of file From 7d9cdce6dad5eb43d44e8071ee3026cc3e14be71 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 6 Nov 2025 19:04:45 -0600 Subject: [PATCH 02/47] chore: add nginx ssl config file --- .../configs/frontend/nginx-ssl.conf | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docker/example-setup/configs/frontend/nginx-ssl.conf diff --git a/docker/example-setup/configs/frontend/nginx-ssl.conf b/docker/example-setup/configs/frontend/nginx-ssl.conf new file mode 100644 index 0000000..b0cea1e --- /dev/null +++ b/docker/example-setup/configs/frontend/nginx-ssl.conf @@ -0,0 +1,57 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + server { + listen 80 default_server; + server_name _; + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl default_server; + http2 on; + server_name _; + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + client_max_body_size 50M; + + # Disable buffering for SSE streams + #proxy_buffering off; + #proxy_cache off; + + # Keep connection alive for long-running requests + #proxy_read_timeout 600s; + #proxy_connect_timeout 300s; + #proxy_send_timeout 300s; + + # Required headers for SSE + #proxy_set_header Connection ''; + #proxy_http_version 1.1; + #chunked_transfer_encoding on; + + # Disable compression for SSE + #gzip off; + + proxy_pass http://attack-workbench-rest-api:3000; + } + } +} From 89f338f067aa0c9b732c596354eaff7cbb46f8bf Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 00:27:53 -0600 Subject: [PATCH 03/47] fix: fix typo in compose.certs.yaml --- docker/example-setup/compose.certs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/example-setup/compose.certs.yaml b/docker/example-setup/compose.certs.yaml index c9bf528..ad5102a 100644 --- a/docker/example-setup/compose.certs.yaml +++ b/docker/example-setup/compose.certs.yaml @@ -18,6 +18,6 @@ services: rest-api: volumes: - - .${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs + - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} From d15a67a79e4eb8ee4dbb272f115eb8eaa04b460a Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 00:28:59 -0600 Subject: [PATCH 04/47] fix: separate taxii service --- docker/example-setup/compose.taxii.yaml | 17 +++++++++++++++++ docker/example-setup/compose.yaml | 20 -------------------- 2 files changed, 17 insertions(+), 20 deletions(-) create mode 100644 docker/example-setup/compose.taxii.yaml diff --git a/docker/example-setup/compose.taxii.yaml b/docker/example-setup/compose.taxii.yaml new file mode 100644 index 0000000..1ea1813 --- /dev/null +++ b/docker/example-setup/compose.taxii.yaml @@ -0,0 +1,17 @@ + taxii: + container_name: attack-workbench-taxii-server + image: ghcr.io/mitre-attack/attack-workbench-taxii-server:${ATTACKWB_TAXII_VERSION:-latest} + depends_on: + - rest-api + ports: + - "${ATTACKWB_TAXII_HTTP_PORT:-5002}:${ATTACKWB_TAXII_HTTP_PORT:-5002}" + volumes: + - "${ATTACKWB_TAXII_CONFIG_DIR:-./configs/taxii/config}:/app/config:ro" + environment: + - TAXII_ENV=${ATTACKWB_TAXII_ENV:-dev} + restart: unless-stopped + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index 2167dfd..2dfbb09 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -61,25 +61,5 @@ services: max-size: "10m" max-file: "5" - taxii: - container_name: attack-workbench-taxii-server - image: ghcr.io/mitre-attack/attack-workbench-taxii-server:${ATTACKWB_TAXII_VERSION:-latest} - depends_on: - - rest-api - ports: - - "${ATTACKWB_TAXII_HTTP_PORT:-5002}:${ATTACKWB_TAXII_HTTP_PORT:-5002}" - volumes: - - "${ATTACKWB_TAXII_CONFIG_DIR:-./configs/taxii/config}:/app/config:ro" - environment: - - TAXII_ENV=${ATTACKWB_TAXII_ENV:-dev} - profiles: - - with-taxii - restart: unless-stopped - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "5" - volumes: workspace-data: From 35a41f3624e911d2c98943b04956c63f636e8871 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 00:29:28 -0600 Subject: [PATCH 05/47] feat: enable docker compose watch mode --- docker/example-setup/compose.dev.yaml | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docker/example-setup/compose.dev.yaml b/docker/example-setup/compose.dev.yaml index a32cb3d..75366ac 100644 --- a/docker/example-setup/compose.dev.yaml +++ b/docker/example-setup/compose.dev.yaml @@ -3,11 +3,52 @@ services: frontend: image: attack-workbench-frontend build: ../../../attack-workbench-frontend + develop: + watch: + # Sync source files for hot-reload + - action: sync + path: ../../../attack-workbench-frontend/src + target: /app/src + ignore: + - node_modules/ + # Rebuild on package.json changes + - action: rebuild + path: ../../../attack-workbench-frontend/package.json rest-api: image: attack-workbench-rest-api build: ../../../attack-workbench-rest-api + develop: + watch: + # Sync app source files + - action: sync + path: ../../../attack-workbench-rest-api/app + target: /usr/src/app/app + ignore: + - node_modules/ + # Restart on config changes + - action: sync+restart + path: ../../../attack-workbench-rest-api/resources + target: /usr/src/app/resources + # Rebuild on package.json changes + - action: rebuild + path: ../../../attack-workbench-rest-api/package.json taxii: image: attack-workbench-taxii-server build: ../../../attack-workbench-taxii-server + develop: + watch: + # Sync source files + - action: sync + path: ../../../attack-workbench-taxii-server/taxii + target: /app/taxii + ignore: + - node_modules/ + # Sync config files and restart + - action: sync+restart + path: ../../../attack-workbench-taxii-server/config + target: /app/config + # Rebuild on package.json changes + - action: rebuild + path: ../../../attack-workbench-taxii-server/package.json From 880c596d55ae7caceb83eda1d6c37bec716f04ea Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 00:29:53 -0600 Subject: [PATCH 06/47] feat: add setup script for workbench instance --- README.md | 37 ++- setup-workbench.sh | 810 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 846 insertions(+), 1 deletion(-) create mode 100755 setup-workbench.sh diff --git a/README.md b/README.md index 9b620f4..8484bd6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,42 @@ Optionally, you can deploy a "sidecar service" that makes your Workbench data av ## Quick Start -### Deploy with Docker Compose +### Automated Setup (Recommended) + +Use the interactive setup script to quickly create and deploy a custom Workbench instance: + +```bash +# Clone and run setup script +git clone https://github.com/mitre-attack/attack-workbench-deployment.git +cd attack-workbench-deployment +./setup-workbench.sh +``` + +Or run directly without cloning: + +```bash +curl -fsSL https://raw.githubusercontent.com/mitre-attack/attack-workbench-deployment/main/setup-workbench.sh | bash +``` + +After running the script, deploy with: + +```bash +cd instances/your-instance-name +docker compose up -d +``` + +For developer mode deployments, use: + +```bash +cd instances/your-instance-name +docker compose up -d --build +``` + +Access Workbench at + +### Manual Setup + +If you prefer to set up manually or need custom configuration: ```bash # Clone this repository diff --git a/setup-workbench.sh b/setup-workbench.sh new file mode 100755 index 0000000..470bd76 --- /dev/null +++ b/setup-workbench.sh @@ -0,0 +1,810 @@ +#!/usr/bin/env bash + +set -e + +# ATT&CK Workbench Deployment Setup Script +# This script helps you quickly set up a custom ATT&CK Workbench instance +# +# SCRIPT ORGANIZATION: +# 1. Constants and Configuration +# 2. Color and Output Functions +# 3. Validation Functions +# 4. Helper Functions (prompts, file operations) +# 5. Instance Management Functions +# 6. Configuration Functions (database, environment, certificates) +# 7. Deployment Option Functions (TAXII, dev mode) +# 8. Compose Override Generation Functions +# 9. Output Functions (summary, instructions) +# 10. Main Execution Flow + +#=============================================================================== +# CONSTANTS +#=============================================================================== + +readonly DEPLOYMENT_REPO_URL="https://github.com/mitre-attack/attack-workbench-deployment.git" +readonly CTID_GITHUB_ORG="https://github.com/center-for-threat-informed-defense" +readonly MITRE_GITHUB_ORG="https://github.com/mitre-attack" + +readonly REPO_FRONTEND="attack-workbench-frontend" +readonly REPO_REST_API="attack-workbench-rest-api" +readonly REPO_TAXII="attack-workbench-taxii-server" + +readonly DB_URL_DOCKER="mongodb://attack-workbench-database/attack-workspace" +readonly DB_URL_LOCAL="mongodb://localhost:27017/attack-workspace" + +#=============================================================================== +# COLORS & OUTPUT FUNCTIONS +#=============================================================================== + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +info() { echo -e "${BLUE}$1${NC}"; } +success() { echo -e "${GREEN}$1${NC}"; } +warning() { echo -e "${YELLOW}$1${NC}"; } +error() { echo -e "${RED}$1${NC}"; } + +#=============================================================================== +# VALIDATION FUNCTIONS +#=============================================================================== + +# Validate that a required command is available +# Usage: require_command "git" "Please install git" +require_command() { + local command="$1" + local message="$2" + + if ! command -v "$command" &> /dev/null; then + error "$command is not installed or not in PATH" + if [[ -n "$message" ]]; then + echo " $message" + fi + return 1 + fi + return 0 +} + +# Validate that a file exists +# Usage: require_file "/path/to/file" "File description" +require_file() { + local file_path="$1" + local description="$2" + + if [[ ! -f "$file_path" ]]; then + error "${description:-File} not found: $file_path" + return 1 + fi + return 0 +} + +# Validate that a directory exists +# Usage: require_directory "/path/to/dir" "Directory description" +require_directory() { + local dir_path="$1" + local description="$2" + + if [[ ! -d "$dir_path" ]]; then + error "${description:-Directory} not found: $dir_path" + return 1 + fi + return 0 +} + +#=============================================================================== +# HELPER FUNCTIONS +#=============================================================================== + +# Prompt for yes/no answer with validation +# Usage: prompt_yes_no "Question?" "Y" result_var +# Args: $1=question, $2=default (Y/N), $3=variable name to store result +prompt_yes_no() { + local question="$1" + local default="$2" + local -n result="$3" + + while true; do + read -p "$question [y/N] " -r answer + answer=${answer:-$default} + + if [[ $answer =~ ^[YyNn]$ ]]; then + result="$answer" + break + else + error "Invalid option. Please enter 'y' for yes or 'n' for no." + fi + done +} + +# Prompt for menu selection with validation +# Usage: prompt_menu result_var "option1" "option2" "option3" +# Args: $1=variable name to store result, remaining args=menu options +prompt_menu() { + local -n result="$1" + shift + local -a options=("$@") + local num_options=${#options[@]} + + while true; do + for i in "${!options[@]}"; do + echo "$((i + 1))) ${options[$i]}" + done + echo "" + read -p "Select option [1-$num_options]: " -r choice + + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$num_options" ]; then + result="$choice" + break + else + error "Invalid option. Please select 1-$num_options." + echo "" + fi + done +} + +# Prompt for non-empty string with validation +# Usage: prompt_non_empty "Question" result_var +prompt_non_empty() { + local question="$1" + local -n result="$2" + + while true; do + read -p "$question " result + if [[ -n "$result" ]]; then + break + else + error "Input cannot be empty" + fi + done +} + +# Update or add a key=value in an env file +# Usage: update_env_file "/path/to/.env" "KEY" "value" +update_env_file() { + local env_file="$1" + local key="$2" + local value="$3" + + if grep -q "^${key}=" "$env_file"; then + sed -i "s|^${key}=.*|${key}=${value}|" "$env_file" + elif grep -q "^#${key}=" "$env_file"; then + sed -i "s|^#${key}=.*|${key}=${value}|" "$env_file" + else + echo "${key}=${value}" >> "$env_file" + fi +} + +# Check if a repository exists in parent directory +# Usage: check_repo_exists "/parent/dir" "repo-name" +check_repo_exists() { + local parent_dir="$1" + local repo_name="$2" + + [[ -d "$parent_dir/$repo_name" ]] +} + +# Get GitHub URL for a repository +# Usage: get_repo_url "repo-name" +get_repo_url() { + local repo_name="$1" + + if [[ "$repo_name" == "$REPO_TAXII" ]]; then + echo "$MITRE_GITHUB_ORG/$repo_name.git" + else + echo "$CTID_GITHUB_ORG/$repo_name.git" + fi +} + +#=============================================================================== +# INSTANCE MANAGEMENT FUNCTIONS +#=============================================================================== + +# Prompt for and validate instance name +get_instance_name() { + local -n name_ref="$1" + + read -p "Enter instance name [my-workbench]: " name_ref + name_ref=${name_ref:-my-workbench} + + # Validate instance name + if [[ ! "$name_ref" =~ ^[a-zA-Z0-9_-]+$ ]]; then + error "Instance name can only contain letters, numbers, hyphens, and underscores" + exit 1 + fi +} + +# Check if instance exists and handle overwrite +handle_existing_instance() { + local instance_dir="$1" + local instance_name="$2" + + if [[ ! -d "$instance_dir" ]]; then + return 0 + fi + + warning "Instance '$instance_name' already exists at $instance_dir" + echo "" + + local overwrite="" + prompt_yes_no "Would you like to overwrite it?" "N" overwrite + + if [[ ! $overwrite =~ ^[Yy]$ ]]; then + error "Aborted" + exit 1 + fi + + warning "Removing existing instance directory..." + rm -rf "$instance_dir" +} + +# Create instance directory and copy template files +create_instance() { + local instance_dir="$1" + local deployment_dir="$2" + local source_dir="$deployment_dir/docker/example-setup" + + info "Creating instance directory: $instance_dir" + mkdir -p "$instance_dir" + + info "Copying template files..." + # Copy all files except compose templates (they're handled by this script) + find "$source_dir" -maxdepth 1 \ + ! -name "compose.dev.yaml" \ + ! -name "compose.certs.yaml" \ + ! -name "compose.taxii.yaml" \ + ! -path "$source_dir" \ + -exec cp -r {} "$instance_dir/" \; + success "Template files copied" +} + +#=============================================================================== +# CONFIGURATION FUNCTIONS +#=============================================================================== + +# Configure database connection and return the selected DATABASE_URL +configure_database() { + local -n db_url_ref="$1" + + echo "" + info "Configure MongoDB connection:" + echo "" + + local db_choice="" + prompt_menu db_choice \ + "Docker setup ($DB_URL_DOCKER)" \ + "Local MongoDB ($DB_URL_LOCAL)" \ + "Custom connection string" + + case $db_choice in + 1) + db_url_ref="$DB_URL_DOCKER" + info "Using Docker setup: $db_url_ref" + ;; + 2) + db_url_ref="$DB_URL_LOCAL" + info "Using local MongoDB: $db_url_ref" + ;; + 3) + echo "" + prompt_non_empty "Enter MongoDB connection string:" db_url_ref + info "Using custom connection: $db_url_ref" + ;; + esac + echo "" +} + +# Set up all environment files for the instance +setup_environment_files() { + local database_url="$1" + + info "Setting up environment files..." + + # Main .env file + if [[ -f "$INSTANCE_DIR/template.env" ]]; then + mv "$INSTANCE_DIR/template.env" "$INSTANCE_DIR/.env" + success "Created $INSTANCE_DIR/.env" + fi + + # REST API .env file + if [[ -f "$INSTANCE_DIR/configs/rest-api/template.env" ]]; then + local rest_api_env="$INSTANCE_DIR/configs/rest-api/.env" + mv "$INSTANCE_DIR/configs/rest-api/template.env" "$rest_api_env" + update_env_file "$rest_api_env" "DATABASE_URL" "$database_url" + success "Created $rest_api_env with DATABASE_URL configured" + fi + + # TAXII .env file (optional) + if [[ -f "$INSTANCE_DIR/configs/taxii/config/template.env" ]]; then + mv "$INSTANCE_DIR/configs/taxii/config/template.env" "$INSTANCE_DIR/configs/taxii/config/dev.env" + success "Created $INSTANCE_DIR/configs/taxii/config/dev.env" + fi +} + +# Configure custom SSL certificates for REST API +configure_custom_certificates() { + local -n host_certs_ref="$1" + local -n certs_filename_ref="$2" + + echo "" + info "Custom SSL certificates allow the REST API to trust additional CA certificates." + info "This is useful when behind a firewall that performs SSL inspection." + echo "" + + read -p "Enter host certificates path [./certs]: " user_certs_path + host_certs_ref=${user_certs_path:-./certs} + + read -p "Enter certificate filename [custom-certs.pem]: " user_certs_filename + certs_filename_ref=${user_certs_filename:-custom-certs.pem} + + info "Using certificates from: $host_certs_ref/$certs_filename_ref" + echo "" + + # Add custom cert configuration to .env + local env_file="$INSTANCE_DIR/.env" + update_env_file "$env_file" "HOST_CERTS_PATH" "$host_certs_ref" + update_env_file "$env_file" "CERTS_FILENAME" "$certs_filename_ref" + success "Added certificate configuration to $env_file" +} + +#=============================================================================== +# DEPLOYMENT OPTION FUNCTIONS +#=============================================================================== + +# Add TAXII service to compose.yaml by inserting before the volumes section +add_taxii_to_compose() { + local compose_file="$INSTANCE_DIR/compose.yaml" + local taxii_template="$DEPLOYMENT_DIR/docker/example-setup/compose.taxii.yaml" + local temp_file="$INSTANCE_DIR/compose.yaml.tmp" + + info "Adding TAXII server to compose.yaml..." + + # Insert TAXII service before the "volumes:" section + sed '/^volumes:/,$d' "$compose_file" > "$temp_file" + cat "$taxii_template" >> "$temp_file" + echo "" >> "$temp_file" + sed -n '/^volumes:/,$p' "$compose_file" >> "$temp_file" + mv "$temp_file" "$compose_file" + + success "TAXII server added to compose.yaml" +} + +# Verify all required source repositories exist for developer mode +verify_dev_mode_repos() { + local parent_dir="$1" + local enable_taxii="$2" + local -a missing_repos=() + + if ! check_repo_exists "$parent_dir" "$REPO_FRONTEND"; then + missing_repos+=("$REPO_FRONTEND") + fi + + if ! check_repo_exists "$parent_dir" "$REPO_REST_API"; then + missing_repos+=("$REPO_REST_API") + fi + + if [[ $enable_taxii =~ ^[Yy]$ ]] && ! check_repo_exists "$parent_dir" "$REPO_TAXII"; then + missing_repos+=("$REPO_TAXII") + fi + + if [[ ${#missing_repos[@]} -gt 0 ]]; then + error "Missing required repositories:" + for repo in "${missing_repos[@]}"; do + echo " - $repo" + done + echo "" + warning "Please clone the missing repositories to:" + echo " $parent_dir/" + echo "" + echo "Clone commands:" + for repo in "${missing_repos[@]}"; do + echo " git clone $(get_repo_url "$repo") $parent_dir/$repo" + done + echo "" + exit 1 + fi + + success "All required repositories found!" +} + +# Display expected directory structure for developer mode +show_dev_mode_structure() { + local deployment_dir="$1" + local enable_taxii="$2" + + echo "" + info "Developer mode requires source repositories to be cloned as siblings to the deployment repository." + echo "" + echo "Expected directory structure:" + echo " $(dirname "$deployment_dir")/" + echo " ├── attack-workbench-deployment/" + echo " ├── $REPO_FRONTEND/" + echo " ├── $REPO_REST_API/" + if [[ $enable_taxii =~ ^[Yy]$ ]]; then + echo " └── $REPO_TAXII/" + fi + echo "" +} + +#=============================================================================== +# COMPOSE OVERRIDE GENERATION FUNCTIONS +#=============================================================================== + +# Generate the frontend service override configuration for dev mode +generate_frontend_override() { + cat << EOF + + frontend: + image: $REPO_FRONTEND + build: ../../../$REPO_FRONTEND + develop: + watch: + # Sync source files for hot-reload + - action: sync + path: ../../../$REPO_FRONTEND/src + target: /app/src + ignore: + - node_modules/ + # Rebuild on package.json changes + - action: rebuild + path: ../../../$REPO_FRONTEND/package.json +EOF +} + +# Generate the rest-api service override configuration for dev mode +generate_rest_api_dev_override() { + cat << EOF + + rest-api: + image: $REPO_REST_API + build: ../../../$REPO_REST_API +EOF + + # Add custom cert volumes if enabled + if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + cat << 'EOF' + volumes: + - ${HOST_CERTS_PATH}:/usr/src/app/certs + environment: + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} +EOF + fi + + # Add develop watch configuration + cat << EOF + develop: + watch: + # Sync app source files + - action: sync + path: ../../../$REPO_REST_API/app + target: /usr/src/app/app + ignore: + - node_modules/ + # Restart on config changes + - action: sync+restart + path: ../../../$REPO_REST_API/resources + target: /usr/src/app/resources + # Rebuild on package.json changes + - action: rebuild + path: ../../../$REPO_REST_API/package.json +EOF +} + +# Generate the rest-api service override for production mode with custom certs +generate_rest_api_certs_override() { + cat << 'EOF' + + rest-api: + volumes: + - ${HOST_CERTS_PATH}:/usr/src/app/certs + environment: + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} +EOF +} + +# Generate the TAXII service override configuration for dev mode +generate_taxii_override() { + cat << EOF + + taxii: + image: $REPO_TAXII + build: ../../../$REPO_TAXII + develop: + watch: + # Sync source files + - action: sync + path: ../../../$REPO_TAXII/taxii + target: /app/taxii + ignore: + - node_modules/ + # Sync config files and restart + - action: sync+restart + path: ../../../$REPO_TAXII/config + target: /app/config + # Rebuild on package.json changes + - action: rebuild + path: ../../../$REPO_TAXII/package.json +EOF +} + +# Generate the complete compose.override.yaml file +generate_compose_override() { + local override_file="$1" + + # Write header + cat > "$override_file" << 'EOF' +# This file was generated by setup-workbench.sh +# It will be automatically merged with compose.yaml when running docker compose commands + +services: +EOF + + # Add service configurations based on mode + if [[ $DEV_MODE =~ ^[Yy]$ ]]; then + generate_frontend_override >> "$override_file" + generate_rest_api_dev_override >> "$override_file" + + if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then + generate_taxii_override >> "$override_file" + fi + else + # Production mode - only add rest-api if custom certs are enabled + if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + generate_rest_api_certs_override >> "$override_file" + fi + fi + + # Add newline at end + echo "" >> "$override_file" +} + +#=============================================================================== +# OUTPUT FUNCTIONS +#=============================================================================== + +# Display configuration summary +show_configuration_summary() { + local instance_dir="$1" + local override_file="$2" + local dev_mode="$3" + local enable_taxii="$4" + local enable_custom_certs="$5" + + info "Configuration files:" + echo " Main: $instance_dir/.env" + echo " Compose: $instance_dir/compose.yaml" + if [[ $dev_mode =~ ^[Yy]$ ]] || [[ $enable_custom_certs =~ ^[Yy]$ ]]; then + echo " + Override: $override_file" + fi + echo " REST API: $instance_dir/configs/rest-api/.env" + echo " REST API: $instance_dir/configs/rest-api/rest-api-service-config.json" + if [[ $enable_taxii =~ ^[Yy]$ ]]; then + echo " TAXII: $instance_dir/configs/taxii/config/.env" + fi + echo "" +} + +# Display custom SSL certificate information +show_certificate_info() { + local instance_dir="$1" + local host_certs_path="$2" + local certs_filename="$3" + + info "Custom SSL certificates:" + echo " Path: $host_certs_path" + echo " Filename: $certs_filename" + echo "" + warning "Make sure to place your certificate file at:" + echo " $instance_dir/$host_certs_path/$certs_filename" + echo "" +} + +# Display deployment instructions +show_deployment_instructions() { + local instance_dir="$1" + local deployment_dir="$2" + local dev_mode="$3" + + info "To deploy your instance:" + echo " cd $instance_dir" + if [[ $dev_mode =~ ^[Yy]$ ]]; then + echo " docker compose up -d --build" + echo "" + info "For hot-reloading in developer mode, use watch:" + echo " docker compose watch" + else + echo " docker compose up -d" + fi + echo "" + + info "After deployment, access your Workbench at: http://localhost" + echo "" + + info "For more information, see:" + echo " Configuration: $deployment_dir/docs/configuration.md" + echo " Deployment: $deployment_dir/docs/deployment.md" + echo "" +} + +#=============================================================================== +# BANNER +#=============================================================================== + +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ ATT&CK Workbench Deployment Setup ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" + +#=============================================================================== +# LOCATE DEPLOYMENT REPOSITORY +#=============================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEPLOYMENT_DIR="" + +# Check if we're already in the deployment repo +if [[ -f "$SCRIPT_DIR/docker/example-setup/compose.yaml" ]]; then + DEPLOYMENT_DIR="$SCRIPT_DIR" + info "Running from deployment repository: $DEPLOYMENT_DIR" +elif [[ -d "$SCRIPT_DIR/attack-workbench-deployment" ]]; then + DEPLOYMENT_DIR="$SCRIPT_DIR/attack-workbench-deployment" + info "Found deployment repository: $DEPLOYMENT_DIR" +elif [[ -f "./docker/example-setup/compose.yaml" ]]; then + DEPLOYMENT_DIR="$(pwd)" + info "Running from current directory: $DEPLOYMENT_DIR" +else + # Not in the repo - need to clone or find it + warning "Not in the attack-workbench-deployment repository" + + # Check if git is available + if ! command -v git &> /dev/null; then + error "Git is not installed. Please install git or manually clone the repository." + exit 1 + fi + + echo "" + prompt_yes_no "Would you like to clone the repository?" "Y" CLONE_REPO + + if [[ $CLONE_REPO =~ ^[Yy]$ ]]; then + info "Cloning repository from $DEPLOYMENT_REPO_URL..." + + CLONE_DIR="./attack-workbench-deployment" + if [[ -d "$CLONE_DIR" ]]; then + error "Directory $CLONE_DIR already exists" + exit 1 + fi + + git clone "$DEPLOYMENT_REPO_URL" "$CLONE_DIR" + DEPLOYMENT_DIR="$(cd "$CLONE_DIR" && pwd)" + success "Repository cloned to $DEPLOYMENT_DIR" + else + error "Cannot proceed without the deployment repository" + exit 1 + fi +fi + +cd "$DEPLOYMENT_DIR" + +#=============================================================================== +# PREREQUISITE CHECKS +#=============================================================================== + +# Check for Docker (warn but don't fail - user might not deploy immediately) +if ! require_command "docker" "Please install Docker to deploy the Workbench. Visit: https://docs.docker.com/get-docker/"; then + warning "Docker is not installed - you will need it to deploy the Workbench" +fi + +# Check for Docker Compose (warn but don't fail) +if ! docker compose version &> /dev/null 2>&1; then + warning "Docker Compose is not available" + echo " Please install Docker Compose (usually included with Docker Desktop)" +fi + + +#=============================================================================== +# MAIN EXECUTION FLOW +#=============================================================================== + +#--------------------------------------- +# Instance Setup +#--------------------------------------- + +echo "" +info "Setting up your Workbench instance..." +echo "" + +INSTANCE_NAME="" +get_instance_name INSTANCE_NAME +INSTANCE_DIR="$DEPLOYMENT_DIR/instances/$INSTANCE_NAME" + +handle_existing_instance "$INSTANCE_DIR" "$INSTANCE_NAME" +create_instance "$INSTANCE_DIR" "$DEPLOYMENT_DIR" + +#--------------------------------------- +# Deployment Options +#--------------------------------------- + +echo "" +info "Configuring deployment options..." +echo "" + +ENABLE_TAXII="" +prompt_yes_no "Do you want to deploy with the TAXII server?" "N" ENABLE_TAXII + +if [[ ! $ENABLE_TAXII =~ ^[Yy]$ ]]; then + # Remove TAXII configs if not needed + if [[ -d "$INSTANCE_DIR/configs/taxii" ]]; then + rm -rf "$INSTANCE_DIR/configs/taxii" + fi +fi + +#--------------------------------------- +# Environment Configuration +#--------------------------------------- + +echo "" +DATABASE_URL="" +configure_database DATABASE_URL +setup_environment_files "$DATABASE_URL" + +if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then + add_taxii_to_compose +fi + +echo "" +success "Instance '$INSTANCE_NAME' created successfully!" +echo "" + +#--------------------------------------- +# Additional Options +#--------------------------------------- + +DEV_MODE="" +prompt_yes_no "Do you want to set up in developer mode (build from source)?" "N" DEV_MODE + +ENABLE_CUSTOM_CERTS="" +prompt_yes_no "Do you want to configure custom SSL certificates for the REST API?" "N" ENABLE_CUSTOM_CERTS + +HOST_CERTS_PATH="./certs" +CERTS_FILENAME="custom-certs.pem" + +if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + configure_custom_certificates HOST_CERTS_PATH CERTS_FILENAME +fi + +#--------------------------------------- +# Developer Mode Setup +#--------------------------------------- + +if [[ $DEV_MODE =~ ^[Yy]$ ]]; then + show_dev_mode_structure "$DEPLOYMENT_DIR" "$ENABLE_TAXII" + PARENT_DIR="$(dirname "$DEPLOYMENT_DIR")" + verify_dev_mode_repos "$PARENT_DIR" "$ENABLE_TAXII" +fi + +#--------------------------------------- +# Generate Compose Override +#--------------------------------------- + +OVERRIDE_FILE="$INSTANCE_DIR/compose.override.yaml" + +if [[ $DEV_MODE =~ ^[Yy]$ ]] || [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + info "Generating compose.override.yaml..." + generate_compose_override "$OVERRIDE_FILE" + success "Created $OVERRIDE_FILE" + echo "" +fi + +#--------------------------------------- +# Summary +#--------------------------------------- + +show_configuration_summary "$INSTANCE_DIR" "$OVERRIDE_FILE" "$DEV_MODE" "$ENABLE_TAXII" "$ENABLE_CUSTOM_CERTS" + +if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + show_certificate_info "$INSTANCE_DIR" "$HOST_CERTS_PATH" "$CERTS_FILENAME" +fi + +show_deployment_instructions "$INSTANCE_DIR" "$DEPLOYMENT_DIR" "$DEV_MODE" From ec501ca3994a85d6f64ea98825cf588f1840fb83 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 00:33:03 -0600 Subject: [PATCH 07/47] chore: update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8484bd6..67d8fc3 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ cd attack-workbench-deployment ./setup-workbench.sh ``` +**NOTE**: Running this part doesn't work yet... + Or run directly without cloning: ```bash From 57e38345bb09beebd888035ddcde55f03f9d1860 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 07:59:28 -0600 Subject: [PATCH 08/47] fix: update TAXII compose file to use correct syntax, and update setup script accordingly --- docker/example-setup/compose.taxii.yaml | 2 ++ setup-workbench.sh | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docker/example-setup/compose.taxii.yaml b/docker/example-setup/compose.taxii.yaml index 1ea1813..189f8ef 100644 --- a/docker/example-setup/compose.taxii.yaml +++ b/docker/example-setup/compose.taxii.yaml @@ -1,3 +1,5 @@ +services: + taxii: container_name: attack-workbench-taxii-server image: ghcr.io/mitre-attack/attack-workbench-taxii-server:${ATTACKWB_TAXII_VERSION:-latest} diff --git a/setup-workbench.sh b/setup-workbench.sh index 470bd76..3f9a5b3 100755 --- a/setup-workbench.sh +++ b/setup-workbench.sh @@ -362,7 +362,8 @@ add_taxii_to_compose() { # Insert TAXII service before the "volumes:" section sed '/^volumes:/,$d' "$compose_file" > "$temp_file" - cat "$taxii_template" >> "$temp_file" + # Extract only the service definition, skipping the "services:" header + sed -n '/^services:/,${/^services:/!p;}' "$taxii_template" >> "$temp_file" echo "" >> "$temp_file" sed -n '/^volumes:/,$p' "$compose_file" >> "$temp_file" mv "$temp_file" "$compose_file" From 62c236e286b44b7306d7142f03963b8168512058 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 08:48:00 -0600 Subject: [PATCH 09/47] fix: fixed setup script to be compatible with macOS --- setup-workbench.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setup-workbench.sh b/setup-workbench.sh index 3f9a5b3..efd7dea 100755 --- a/setup-workbench.sh +++ b/setup-workbench.sh @@ -168,9 +168,13 @@ update_env_file() { local value="$3" if grep -q "^${key}=" "$env_file"; then - sed -i "s|^${key}=.*|${key}=${value}|" "$env_file" + local tmp + tmp="$(mktemp /tmp/env.XXXXXX)" + sed "s|^${key}=.*|${key}=${value}|" "$env_file" > "$tmp" && mv "$tmp" "$env_file" elif grep -q "^#${key}=" "$env_file"; then - sed -i "s|^#${key}=.*|${key}=${value}|" "$env_file" + local tmp + tmp="$(mktemp /tmp/env.XXXXXX)" + sed "s|^#${key}=.*|${key}=${value}|" "$env_file" > "$tmp" && mv "$tmp" "$env_file" else echo "${key}=${value}" >> "$env_file" fi From b44c1b36637b9bb78b8139487f64e28c3e55a866 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 12:31:29 -0600 Subject: [PATCH 10/47] docs: update README --- README.md | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/README.md b/README.md index 67d8fc3..9e21e98 100644 --- a/README.md +++ b/README.md @@ -41,38 +41,6 @@ docker compose up -d --build Access Workbench at -### Manual Setup - -If you prefer to set up manually or need custom configuration: - -```bash -# Clone this repository -git clone https://github.com/mitre-attack/attack-workbench-deployment.git -cd attack-workbench-deployment - -# Copy docker compose template (git-ignored) -mkdir -p instances/my-workbench -cp -r docker/example-setup/* instances/my-workbench/ - -# Configure environment -cd instances/my-workbench -mv template.env .env -mv configs/rest-api/template.env configs/rest-api/.env - -# edit the following files as needed -# instances/my-workbench/.env -# configs/rest-api/.env -# configs/rest-api/rest-api-service-config.json - -# Deploy -docker compose up -d - -# (Optional) Deploy with TAXII server -docker compose --profile with-taxii up -d -``` - -Access Workbench at - Full variable descriptions and examples are available in [docs/configuration](docs/configuration.md). For source builds or TAXII setup, see [docs/deployment](docs/deployment.md). From c3e57ab2d3dffe27869dace9a8d9d67bdf0e3db3 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 7 Nov 2025 12:45:19 -0600 Subject: [PATCH 11/47] docs: update readmes --- README.md | 2 -- certs/README.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e21e98..037a6c9 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ Optionally, you can deploy a "sidecar service" that makes your Workbench data av ## Quick Start -### Automated Setup (Recommended) - Use the interactive setup script to quickly create and deploy a custom Workbench instance: ```bash diff --git a/certs/README.md b/certs/README.md index ab60ab3..a294f6e 100644 --- a/certs/README.md +++ b/certs/README.md @@ -36,7 +36,7 @@ If you're using environment variables in your shell, you can use: ```yaml volumes: - - .${HOST_CERTS_PATH}:/usr/src/app/certs + - ${HOST_CERTS_PATH}:/usr/src/app/certs environment: - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} ``` From 8e538d1d6710302d38d798912a69d6857b3a61db Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 8 Nov 2025 15:22:56 -0600 Subject: [PATCH 12/47] docs: update configuration and template.env to include MONGOSTORE_CRYPTO_SECRET --- docker/example-setup/configs/rest-api/template.env | 5 +++++ docs/configuration.md | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/docker/example-setup/configs/rest-api/template.env b/docker/example-setup/configs/rest-api/template.env index a8e8c45..52706cf 100644 --- a/docker/example-setup/configs/rest-api/template.env +++ b/docker/example-setup/configs/rest-api/template.env @@ -85,6 +85,11 @@ DATABASE_URL= # Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" #SESSION_SECRET= +# MONGOSTORE_CRYPTO_SECRET (string) - Secret to encrypt session data in MongoDB. +# Default: securely generated at startup (changes on restart; not recommended for production). +# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" +#MONGOSTORE_CRYPTO_SECRET= + # User Authentication # AUTHN_MECHANISM (enum) - User login mechanism # Options: anonymous, oidc diff --git a/docs/configuration.md b/docs/configuration.md index 5bf6dfc..b0123c3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -85,6 +85,13 @@ Templates are provided in the aforementioned directory. Refer to the [REST API usage documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/blob/main/USAGE.md#configuration) for further details on customizing the backend. +**Important**: For production deployments, set the following environment variables in your `.env` file to ensure persistent secrets across server restarts: + +- `SESSION_SECRET` - Secret used to sign session cookies +- `MONGOSTORE_CRYPTO_SECRET` - Secret used to encrypt session data in MongoDB + +Generate secure secrets using: `node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"` + ### TAXII Server **Config files**: [configs/taxii/config/](../docker/example-setup/configs/taxii/config/) From 675d2f96254f6492e08686ac6179353fd56382dd Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 8 Nov 2025 23:01:38 -0600 Subject: [PATCH 13/47] fix: update container names and proxy_pass references in Docker configuration files --- docker/example-setup/compose.yaml | 7 ++----- docker/example-setup/configs/frontend/nginx-ssl.conf | 2 +- docker/example-setup/configs/frontend/nginx.conf | 2 +- docker/example-setup/configs/rest-api/template.env | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index 2dfbb09..d6bbf91 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -1,7 +1,6 @@ services: frontend: - container_name: attack-workbench-frontend image: ghcr.io/center-for-threat-informed-defense/attack-workbench-frontend:${ATTACKWB_FRONTEND_VERSION:-latest} depends_on: - rest-api @@ -19,12 +18,11 @@ services: max-file: "5" rest-api: - container_name: attack-workbench-rest-api image: ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api:${ATTACKWB_RESTAPI_VERSION:-latest} depends_on: - mongodb ports: - - "${ATTACKWB_RESTAPI_HTTP_PORT:-3000}:${ATTACKWB_RESTAPI_HTTP_PORT:-3000}" + - "${ATTACKWB_RESTAPI_HTTP_PORT:-3000}:3000" volumes: - "${ATTACKWB_RESTAPI_CONFIG_FILE:-./configs/rest-api/rest-api-service-config.json}:/usr/src/app/resources/rest-api-service-config.json:ro" env_file: @@ -42,10 +40,9 @@ services: max-file: "5" mongodb: - container_name: attack-workbench-database image: mongo:8 ports: - - "127.0.0.1:${ATTACKWB_DB_PORT:-27017}:${ATTACKWB_DB_PORT:-27017}" + - "127.0.0.1:${ATTACKWB_DB_PORT:-27017}:27017" volumes: - workspace-data:/data/db - ./database-backup:/dump diff --git a/docker/example-setup/configs/frontend/nginx-ssl.conf b/docker/example-setup/configs/frontend/nginx-ssl.conf index b0cea1e..9cd32cc 100644 --- a/docker/example-setup/configs/frontend/nginx-ssl.conf +++ b/docker/example-setup/configs/frontend/nginx-ssl.conf @@ -51,7 +51,7 @@ http { # Disable compression for SSE #gzip off; - proxy_pass http://attack-workbench-rest-api:3000; + proxy_pass http://rest-api:3000; } } } diff --git a/docker/example-setup/configs/frontend/nginx.conf b/docker/example-setup/configs/frontend/nginx.conf index 62ddc56..240b8bf 100644 --- a/docker/example-setup/configs/frontend/nginx.conf +++ b/docker/example-setup/configs/frontend/nginx.conf @@ -42,7 +42,7 @@ http { # Disable compression for SSE gzip off; - proxy_pass http://attack-workbench-rest-api:3000; + proxy_pass http://rest-api:3000; } } } diff --git a/docker/example-setup/configs/rest-api/template.env b/docker/example-setup/configs/rest-api/template.env index 52706cf..8168731 100644 --- a/docker/example-setup/configs/rest-api/template.env +++ b/docker/example-setup/configs/rest-api/template.env @@ -11,7 +11,7 @@ # Database (REQUIRED) # DATABASE_URL (string) - MongoDB connection string # Example (Docker): -#DATABASE_URL=mongodb://attack-workbench-database/attack-workspace +#DATABASE_URL=mongodb://mongodb/attack-workspace # Example (local): #DATABASE_URL=mongodb://localhost:27017/attack-workspace DATABASE_URL= From cf9c81ddf60a44f0723d3e3e47ca6e5d5c46eff7 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 9 Nov 2025 00:00:09 -0600 Subject: [PATCH 14/47] fix: update TAXII server configuration to use port 8000 and adjust related references --- docker/example-setup/compose.taxii.yaml | 3 +-- docker/example-setup/configs/taxii/config/template.env | 2 +- docker/example-setup/configs/taxii/nginx.conf | 5 ++--- k8s/base/configmap-taxii.yaml | 2 +- k8s/overlays/dev/configmap-taxii-dev.yaml | 2 +- k8s/overlays/prod/configmap-taxii-prod.yaml | 2 +- setup-workbench.sh | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docker/example-setup/compose.taxii.yaml b/docker/example-setup/compose.taxii.yaml index 189f8ef..6f3bebf 100644 --- a/docker/example-setup/compose.taxii.yaml +++ b/docker/example-setup/compose.taxii.yaml @@ -1,12 +1,11 @@ services: taxii: - container_name: attack-workbench-taxii-server image: ghcr.io/mitre-attack/attack-workbench-taxii-server:${ATTACKWB_TAXII_VERSION:-latest} depends_on: - rest-api ports: - - "${ATTACKWB_TAXII_HTTP_PORT:-5002}:${ATTACKWB_TAXII_HTTP_PORT:-5002}" + - "${ATTACKWB_TAXII_HTTP_PORT:-5002}:8000" volumes: - "${ATTACKWB_TAXII_CONFIG_DIR:-./configs/taxii/config}:/app/config:ro" environment: diff --git a/docker/example-setup/configs/taxii/config/template.env b/docker/example-setup/configs/taxii/config/template.env index d9d50f8..15ba5e6 100644 --- a/docker/example-setup/configs/taxii/config/template.env +++ b/docker/example-setup/configs/taxii/config/template.env @@ -9,7 +9,7 @@ TAXII_ENV=dev # ***** SERVER SETTINGS ********************************************************************************************** # ******************************************************************************************************************** TAXII_APP_ADDRESS=0.0.0.0 -TAXII_APP_PORT=5002 +TAXII_APP_PORT=8000 TAXII_HTTPS_ENABLED=false TAXII_SSL_PRIVATE_KEY= TAXII_SSL_PUBLIC_KEY= diff --git a/docker/example-setup/configs/taxii/nginx.conf b/docker/example-setup/configs/taxii/nginx.conf index 30065d3..875ed25 100644 --- a/docker/example-setup/configs/taxii/nginx.conf +++ b/docker/example-setup/configs/taxii/nginx.conf @@ -25,7 +25,7 @@ http { location /api { client_max_body_size 50M; - proxy_pass http://attack-workbench-rest-api:3000; + proxy_pass http://rest-api:3000; } } @@ -33,7 +33,6 @@ http { server { listen 80 default_server; - listen [::]:80 default_server; server_name taxii.example.com; location /.well-known/acme-challenge { @@ -72,7 +71,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; location /taxii { - proxy_pass http://attack-workbench-taxii-server:5000; + proxy_pass http://taxii:8000; # limit_req zone=one burst=5; } diff --git a/k8s/base/configmap-taxii.yaml b/k8s/base/configmap-taxii.yaml index dbf7d23..b2c7da6 100644 --- a/k8s/base/configmap-taxii.yaml +++ b/k8s/base/configmap-taxii.yaml @@ -6,7 +6,7 @@ metadata: data: TAXII_ENV: "prod" TAXII_APP_ADDRESS: "0.0.0.0" - TAXII_APP_PORT: "5002" + TAXII_APP_PORT: "8000" TAXII_HTTPS_ENABLED: "false" TAXII_SSL_PRIVATE_KEY: "" TAXII_SSL_PUBLIC_KEY: "" diff --git a/k8s/overlays/dev/configmap-taxii-dev.yaml b/k8s/overlays/dev/configmap-taxii-dev.yaml index d0d282b..4fab48c 100644 --- a/k8s/overlays/dev/configmap-taxii-dev.yaml +++ b/k8s/overlays/dev/configmap-taxii-dev.yaml @@ -6,7 +6,7 @@ metadata: data: TAXII_ENV: "dev" TAXII_APP_ADDRESS: "0.0.0.0" - TAXII_APP_PORT: "5002" + TAXII_APP_PORT: "8000" TAXII_HTTPS_ENABLED: "false" TAXII_SSL_PRIVATE_KEY: "" TAXII_SSL_PUBLIC_KEY: "" diff --git a/k8s/overlays/prod/configmap-taxii-prod.yaml b/k8s/overlays/prod/configmap-taxii-prod.yaml index 2c3d59b..02d45e9 100644 --- a/k8s/overlays/prod/configmap-taxii-prod.yaml +++ b/k8s/overlays/prod/configmap-taxii-prod.yaml @@ -6,7 +6,7 @@ metadata: data: TAXII_ENV: "prod" TAXII_APP_ADDRESS: "0.0.0.0" - TAXII_APP_PORT: "5002" + TAXII_APP_PORT: "8000" TAXII_HTTPS_ENABLED: "true" TAXII_SSL_PRIVATE_KEY: "/etc/ssl/private/tls.key" TAXII_SSL_PUBLIC_KEY: "/etc/ssl/certs/tls.crt" diff --git a/setup-workbench.sh b/setup-workbench.sh index efd7dea..421d3c5 100755 --- a/setup-workbench.sh +++ b/setup-workbench.sh @@ -29,7 +29,7 @@ readonly REPO_FRONTEND="attack-workbench-frontend" readonly REPO_REST_API="attack-workbench-rest-api" readonly REPO_TAXII="attack-workbench-taxii-server" -readonly DB_URL_DOCKER="mongodb://attack-workbench-database/attack-workspace" +readonly DB_URL_DOCKER="mongodb://mongodb/attack-workspace" readonly DB_URL_LOCAL="mongodb://localhost:27017/attack-workspace" #=============================================================================== From 290abd37156f34f17fa28d53324b8ffd31840ad5 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 9 Nov 2025 00:15:31 -0600 Subject: [PATCH 15/47] fix: update TAXII_STIX_SRC_URL to use correct rest-api reference --- docker/example-setup/configs/taxii/config/template.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/example-setup/configs/taxii/config/template.env b/docker/example-setup/configs/taxii/config/template.env index 15ba5e6..cba03d4 100644 --- a/docker/example-setup/configs/taxii/config/template.env +++ b/docker/example-setup/configs/taxii/config/template.env @@ -54,7 +54,7 @@ TAXII_CACHE_RECONNECT=true # ******************************************************************************************************************** # ***** STIX/WORKBENCH SETTINGS ************************************************************************************** # ******************************************************************************************************************** -TAXII_STIX_SRC_URL=http://attack-workbench-rest-api:3000 +TAXII_STIX_SRC_URL=http://rest-api:3000 TAXII_STIX_DATA_SRC=workbench TAXII_WORKBENCH_AUTH_HEADER=dGF4aWktc2VydmVyOnNlY3JldC1zcXVpcnJlbA== From 686a755b035ba346e281d6eafeee592919f80ac2 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 9 Nov 2025 00:31:50 -0600 Subject: [PATCH 16/47] fix: update TAXII_HTTP_PORT to use correct default value of 8000 --- docker/example-setup/compose.taxii.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/example-setup/compose.taxii.yaml b/docker/example-setup/compose.taxii.yaml index 6f3bebf..5aa1dac 100644 --- a/docker/example-setup/compose.taxii.yaml +++ b/docker/example-setup/compose.taxii.yaml @@ -5,7 +5,7 @@ services: depends_on: - rest-api ports: - - "${ATTACKWB_TAXII_HTTP_PORT:-5002}:8000" + - "${ATTACKWB_TAXII_HTTP_PORT:-8000}:8000" volumes: - "${ATTACKWB_TAXII_CONFIG_DIR:-./configs/taxii/config}:/app/config:ro" environment: From cf59f40abfb9540f96778ee2bd5c10ac490c0f1a Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 9 Nov 2025 00:43:23 -0600 Subject: [PATCH 17/47] fix: update TAXII_MONGO_URI to use correct MongoDB service address --- docker/example-setup/configs/taxii/config/template.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/example-setup/configs/taxii/config/template.env b/docker/example-setup/configs/taxii/config/template.env index cba03d4..286e9da 100644 --- a/docker/example-setup/configs/taxii/config/template.env +++ b/docker/example-setup/configs/taxii/config/template.env @@ -63,7 +63,7 @@ TAXII_WORKBENCH_AUTH_HEADER=dGF4aWktc2VydmVyOnNlY3JldC1zcXVpcnJlbA== # ******************************************************************************************************************** # ***** DATABASE SETTINGS ******************************************************************************************** # ******************************************************************************************************************** -TAXII_MONGO_URI=mongodb://attack-workbench-database/taxii +TAXII_MONGO_URI=mongodb://mongodb:27017/taxii TAXII_HYDRATE_ON_BOOT=true From 18ad64ac486d637420cbf9876d1903c45c3bb78a Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 10 Nov 2025 10:25:20 -0500 Subject: [PATCH 18/47] fix: eliminate namerefs for bash 3.2 compatibility --- setup-workbench.sh | 97 ++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/setup-workbench.sh b/setup-workbench.sh index efd7dea..9227b64 100755 --- a/setup-workbench.sh +++ b/setup-workbench.sh @@ -103,14 +103,15 @@ require_directory() { prompt_yes_no() { local question="$1" local default="$2" - local -n result="$3" + # local -n result="$3" + PROMPT_YES_NO_RESULT="" while true; do read -p "$question [y/N] " -r answer answer=${answer:-$default} if [[ $answer =~ ^[YyNn]$ ]]; then - result="$answer" + PROMPT_YES_NO_RESULT="$answer" break else error "Invalid option. Please enter 'y' for yes or 'n' for no." @@ -122,11 +123,12 @@ prompt_yes_no() { # Usage: prompt_menu result_var "option1" "option2" "option3" # Args: $1=variable name to store result, remaining args=menu options prompt_menu() { - local -n result="$1" - shift + # local -n result="$1" + # shift local -a options=("$@") local num_options=${#options[@]} + PROMPT_MENU_RESULT="" while true; do for i in "${!options[@]}"; do echo "$((i + 1))) ${options[$i]}" @@ -135,7 +137,7 @@ prompt_menu() { read -p "Select option [1-$num_options]: " -r choice if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$num_options" ]; then - result="$choice" + PROMPT_MENU_RESULT="$choice" break else error "Invalid option. Please select 1-$num_options." @@ -148,11 +150,12 @@ prompt_menu() { # Usage: prompt_non_empty "Question" result_var prompt_non_empty() { local question="$1" - local -n result="$2" + # local -n result="$2" + PROMPT_NON_EMPTY_RESULT="" while true; do - read -p "$question " result - if [[ -n "$result" ]]; then + read -p "$question " PROMPT_NON_EMPTY_RESULT + if [[ -n "$PROMPT_NON_EMPTY_RESULT" ]]; then break else error "Input cannot be empty" @@ -207,13 +210,14 @@ get_repo_url() { # Prompt for and validate instance name get_instance_name() { - local -n name_ref="$1" + # local -n name_ref="$1" + GET_INSTANCE_NAME_NAME_REF="" - read -p "Enter instance name [my-workbench]: " name_ref - name_ref=${name_ref:-my-workbench} + read -p "Enter instance name [my-workbench]: " GET_INSTANCE_NAME_NAME_REF + GET_INSTANCE_NAME_NAME_REF=${GET_INSTANCE_NAME_NAME_REF:-my-workbench} # Validate instance name - if [[ ! "$name_ref" =~ ^[a-zA-Z0-9_-]+$ ]]; then + if [[ ! "$GET_INSTANCE_NAME_NAME_REF" =~ ^[a-zA-Z0-9_-]+$ ]]; then error "Instance name can only contain letters, numbers, hyphens, and underscores" exit 1 fi @@ -231,8 +235,8 @@ handle_existing_instance() { warning "Instance '$instance_name' already exists at $instance_dir" echo "" - local overwrite="" - prompt_yes_no "Would you like to overwrite it?" "N" overwrite + prompt_yes_no "Would you like to overwrite it?" "N" + local overwrite="$PROMPT_YES_NO_RESULT" if [[ ! $overwrite =~ ^[Yy]$ ]]; then error "Aborted" @@ -269,31 +273,33 @@ create_instance() { # Configure database connection and return the selected DATABASE_URL configure_database() { - local -n db_url_ref="$1" + # local -n db_url_ref="$1" + CONFIGURE_DATABASE_DB_URL_REF="" echo "" info "Configure MongoDB connection:" echo "" - local db_choice="" - prompt_menu db_choice \ + prompt_menu \ "Docker setup ($DB_URL_DOCKER)" \ "Local MongoDB ($DB_URL_LOCAL)" \ "Custom connection string" + local db_choice="$PROMPT_MENU_RESULT" case $db_choice in 1) - db_url_ref="$DB_URL_DOCKER" - info "Using Docker setup: $db_url_ref" + CONFIGURE_DATABASE_DB_URL_REF="$DB_URL_DOCKER" + info "Using Docker setup: $CONFIGURE_DATABASE_DB_URL_REF" ;; 2) - db_url_ref="$DB_URL_LOCAL" - info "Using local MongoDB: $db_url_ref" + CONFIGURE_DATABASE_DB_URL_REF="$DB_URL_LOCAL" + info "Using local MongoDB: $CONFIGURE_DATABASE_DB_URL_REF" ;; 3) echo "" - prompt_non_empty "Enter MongoDB connection string:" db_url_ref - info "Using custom connection: $db_url_ref" + prompt_non_empty "Enter MongoDB connection string:" + CONFIGURE_DATABASE_DB_URL_REF="$PROMPT_NON_EMPTY_RESULT" + info "Using custom connection: $CONFIGURE_DATABASE_DB_URL_REF" ;; esac echo "" @@ -328,8 +334,10 @@ setup_environment_files() { # Configure custom SSL certificates for REST API configure_custom_certificates() { - local -n host_certs_ref="$1" - local -n certs_filename_ref="$2" + # local -n host_certs_ref="$1" + # local -n certs_filename_ref="$2" + CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF="" + CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF="" echo "" info "Custom SSL certificates allow the REST API to trust additional CA certificates." @@ -337,18 +345,18 @@ configure_custom_certificates() { echo "" read -p "Enter host certificates path [./certs]: " user_certs_path - host_certs_ref=${user_certs_path:-./certs} + CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF=${user_certs_path:-./certs} read -p "Enter certificate filename [custom-certs.pem]: " user_certs_filename - certs_filename_ref=${user_certs_filename:-custom-certs.pem} + CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF=${user_certs_filename:-custom-certs.pem} - info "Using certificates from: $host_certs_ref/$certs_filename_ref" + info "Using certificates from: $CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF/$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" echo "" # Add custom cert configuration to .env local env_file="$INSTANCE_DIR/.env" - update_env_file "$env_file" "HOST_CERTS_PATH" "$host_certs_ref" - update_env_file "$env_file" "CERTS_FILENAME" "$certs_filename_ref" + update_env_file "$env_file" "HOST_CERTS_PATH" "$CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF" + update_env_file "$env_file" "CERTS_FILENAME" "$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" success "Added certificate configuration to $env_file" } @@ -670,7 +678,8 @@ else fi echo "" - prompt_yes_no "Would you like to clone the repository?" "Y" CLONE_REPO + prompt_yes_no "Would you like to clone the repository?" "Y" + CLONE_REPO="$PROMPT_YES_NO_RESULT" if [[ $CLONE_REPO =~ ^[Yy]$ ]]; then info "Cloning repository from $DEPLOYMENT_REPO_URL..." @@ -720,8 +729,8 @@ echo "" info "Setting up your Workbench instance..." echo "" -INSTANCE_NAME="" -get_instance_name INSTANCE_NAME +get_instance_name +INSTANCE_NAME="$GET_INSTANCE_NAME_NAME_REF" INSTANCE_DIR="$DEPLOYMENT_DIR/instances/$INSTANCE_NAME" handle_existing_instance "$INSTANCE_DIR" "$INSTANCE_NAME" @@ -735,8 +744,9 @@ echo "" info "Configuring deployment options..." echo "" -ENABLE_TAXII="" -prompt_yes_no "Do you want to deploy with the TAXII server?" "N" ENABLE_TAXII + +prompt_yes_no "Do you want to deploy with the TAXII server?" "N" +ENABLE_TAXII="$PROMPT_YES_NO_RESULT" if [[ ! $ENABLE_TAXII =~ ^[Yy]$ ]]; then # Remove TAXII configs if not needed @@ -750,8 +760,8 @@ fi #--------------------------------------- echo "" -DATABASE_URL="" -configure_database DATABASE_URL +configure_database +DATABASE_URL="$CONFIGURE_DATABASE_DB_URL_REF" setup_environment_files "$DATABASE_URL" if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then @@ -766,17 +776,20 @@ echo "" # Additional Options #--------------------------------------- -DEV_MODE="" -prompt_yes_no "Do you want to set up in developer mode (build from source)?" "N" DEV_MODE -ENABLE_CUSTOM_CERTS="" -prompt_yes_no "Do you want to configure custom SSL certificates for the REST API?" "N" ENABLE_CUSTOM_CERTS +prompt_yes_no "Do you want to set up in developer mode (build from source)?" "N" +DEV_MODE="$PROMPT_YES_NO_RESULT" + +prompt_yes_no "Do you want to configure custom SSL certificates for the REST API?" "N" +ENABLE_CUSTOM_CERTS="$PROMPT_YES_NO_RESULT" HOST_CERTS_PATH="./certs" CERTS_FILENAME="custom-certs.pem" if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then - configure_custom_certificates HOST_CERTS_PATH CERTS_FILENAME + configure_custom_certificates + HOST_CERTS_PATH="$CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF" + CERTS_FILENAME="$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" fi #--------------------------------------- From 7f4db518665245932389ea96747ae40afda89176 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 10 Nov 2025 11:47:49 -0500 Subject: [PATCH 19/47] chore: cleanup comments --- setup-workbench.sh | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/setup-workbench.sh b/setup-workbench.sh index 14084a6..57c3bdb 100755 --- a/setup-workbench.sh +++ b/setup-workbench.sh @@ -98,12 +98,11 @@ require_directory() { #=============================================================================== # Prompt for yes/no answer with validation -# Usage: prompt_yes_no "Question?" "Y" result_var -# Args: $1=question, $2=default (Y/N), $3=variable name to store result +# Usage: prompt_yes_no "Question?" "Y" +# Args: $1=question, $2=default (Y/N) prompt_yes_no() { local question="$1" local default="$2" - # local -n result="$3" PROMPT_YES_NO_RESULT="" while true; do @@ -120,11 +119,9 @@ prompt_yes_no() { } # Prompt for menu selection with validation -# Usage: prompt_menu result_var "option1" "option2" "option3" -# Args: $1=variable name to store result, remaining args=menu options +# Usage: prompt_menu "option1" "option2" "option3" +# Args: menu options prompt_menu() { - # local -n result="$1" - # shift local -a options=("$@") local num_options=${#options[@]} @@ -147,10 +144,9 @@ prompt_menu() { } # Prompt for non-empty string with validation -# Usage: prompt_non_empty "Question" result_var +# Usage: prompt_non_empty "Question" prompt_non_empty() { local question="$1" - # local -n result="$2" PROMPT_NON_EMPTY_RESULT="" while true; do @@ -210,7 +206,6 @@ get_repo_url() { # Prompt for and validate instance name get_instance_name() { - # local -n name_ref="$1" GET_INSTANCE_NAME_NAME_REF="" read -p "Enter instance name [my-workbench]: " GET_INSTANCE_NAME_NAME_REF @@ -273,7 +268,6 @@ create_instance() { # Configure database connection and return the selected DATABASE_URL configure_database() { - # local -n db_url_ref="$1" CONFIGURE_DATABASE_DB_URL_REF="" echo "" @@ -334,8 +328,6 @@ setup_environment_files() { # Configure custom SSL certificates for REST API configure_custom_certificates() { - # local -n host_certs_ref="$1" - # local -n certs_filename_ref="$2" CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF="" CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF="" From bdc5c5d2d3527a44e266f82d24e751725b3009c4 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 10 Nov 2025 12:51:03 -0500 Subject: [PATCH 20/47] chore: move setup script to docker dir --- README.md | 39 +------------------ docker/README.md | 33 +++++++++++++++- .../setup-workbench.sh | 11 ++++-- 3 files changed, 41 insertions(+), 42 deletions(-) rename setup-workbench.sh => docker/setup-workbench.sh (98%) diff --git a/README.md b/README.md index 037a6c9..eed1b7a 100644 --- a/README.md +++ b/README.md @@ -6,44 +6,9 @@ Optionally, you can deploy a "sidecar service" that makes your Workbench data av ## Quick Start -Use the interactive setup script to quickly create and deploy a custom Workbench instance: +To quickly create and deploy a custom Workbench instance using Docker and Compose use the interactive setup script in the `docker/` directory. -```bash -# Clone and run setup script -git clone https://github.com/mitre-attack/attack-workbench-deployment.git -cd attack-workbench-deployment -./setup-workbench.sh -``` - -**NOTE**: Running this part doesn't work yet... - -Or run directly without cloning: - -```bash -curl -fsSL https://raw.githubusercontent.com/mitre-attack/attack-workbench-deployment/main/setup-workbench.sh | bash -``` - -After running the script, deploy with: - -```bash -cd instances/your-instance-name -docker compose up -d -``` - -For developer mode deployments, use: - -```bash -cd instances/your-instance-name -docker compose up -d --build -``` - -Access Workbench at - -Full variable descriptions and examples are available in [docs/configuration](docs/configuration.md). - -For source builds or TAXII setup, see [docs/deployment](docs/deployment.md). - -For information on how to backup or restore the mongo database, see [docs/database-backups](docs/database-backups.md). +See [docker/README](docker/README.md) for detailed instructions. ## Kubernetes diff --git a/docker/README.md b/docker/README.md index 5b5b379..626b7d1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,3 +1,32 @@ -# Example Docker Compose Setup +# Docker Compose Setup -This directory has an example docker compose setup in the [example-setup](example-setup/) directory. +Use the interactive setup script `setup-workbench.sh` to quickly create and deploy a custom Workbench instance: + +```bash +# Clone and run setup script +git clone https://github.com/mitre-attack/attack-workbench-deployment.git +cd attack-workbench-deployment +./docker/setup-workbench.sh +``` + +After running the script, deploy with: + +```bash +cd instances/your-instance-name +docker compose up -d +``` + +For developer mode deployments, use: + +```bash +cd instances/your-instance-name +docker compose up -d --build +``` + +Access Workbench at + +Full variable descriptions and examples are available in [docs/configuration](docs/configuration.md). + +For source builds or TAXII setup, see [docs/deployment](docs/deployment.md). + +For information on how to backup or restore the mongo database, see [docs/database-backups](docs/database-backups.md). diff --git a/setup-workbench.sh b/docker/setup-workbench.sh similarity index 98% rename from setup-workbench.sh rename to docker/setup-workbench.sh index 57c3bdb..00bbc5d 100755 --- a/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -601,7 +601,12 @@ show_certificate_info() { echo " Filename: $certs_filename" echo "" warning "Make sure to place your certificate file at:" - echo " $instance_dir/$host_certs_path/$certs_filename" + if [[ "$host_certs_path" = ./* ]] || [[ "$host_certs_path" = ../* ]]; then + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + echo " $instance_dir/$host_certs_path/$certs_filename" + else + echo " $host_certs_path/$certs_filename" + fi echo "" } @@ -650,8 +655,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOYMENT_DIR="" # Check if we're already in the deployment repo -if [[ -f "$SCRIPT_DIR/docker/example-setup/compose.yaml" ]]; then - DEPLOYMENT_DIR="$SCRIPT_DIR" +if [[ -f "$SCRIPT_DIR/example-setup/compose.yaml" ]]; then + DEPLOYMENT_DIR="$(dirname $SCRIPT_DIR)" info "Running from deployment repository: $DEPLOYMENT_DIR" elif [[ -d "$SCRIPT_DIR/attack-workbench-deployment" ]]; then DEPLOYMENT_DIR="$SCRIPT_DIR/attack-workbench-deployment" From 092591f130e045c3725678f6b126986039fab5b9 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 10 Nov 2025 13:22:04 -0500 Subject: [PATCH 21/47] fix: change setup to warn instead of fail on missing repos --- docker/setup-workbench.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 00bbc5d..daff5d7 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -394,7 +394,7 @@ verify_dev_mode_repos() { fi if [[ ${#missing_repos[@]} -gt 0 ]]; then - error "Missing required repositories:" + warning "Missing required repositories:" for repo in "${missing_repos[@]}"; do echo " - $repo" done @@ -406,11 +406,10 @@ verify_dev_mode_repos() { for repo in "${missing_repos[@]}"; do echo " git clone $(get_repo_url "$repo") $parent_dir/$repo" done - echo "" - exit 1 + else + success "All required repositories found!" fi - - success "All required repositories found!" + echo "" } # Display expected directory structure for developer mode @@ -628,7 +627,8 @@ show_deployment_instructions() { fi echo "" - info "After deployment, access your Workbench at: http://localhost" + info "After deployment, access your Workbench at:" + echo " http://localhost" echo "" info "For more information, see:" From 9547fb4a0ebe5c9c7f41c0ce2277e055b59af723 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 10 Nov 2025 13:45:38 -0500 Subject: [PATCH 22/47] chore: standardize interactive prompt newlines --- docker/setup-workbench.sh | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index daff5d7..3a7c294 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -141,6 +141,8 @@ prompt_menu() { echo "" fi done + + echo "" } # Prompt for non-empty string with validation @@ -240,6 +242,7 @@ handle_existing_instance() { warning "Removing existing instance directory..." rm -rf "$instance_dir" + echo "" } # Create instance directory and copy template files @@ -249,6 +252,7 @@ create_instance() { local source_dir="$deployment_dir/docker/example-setup" info "Creating instance directory: $instance_dir" + echo "" mkdir -p "$instance_dir" info "Copying template files..." @@ -260,6 +264,7 @@ create_instance() { ! -path "$source_dir" \ -exec cp -r {} "$instance_dir/" \; success "Template files copied" + echo "" } #=============================================================================== @@ -270,7 +275,7 @@ create_instance() { configure_database() { CONFIGURE_DATABASE_DB_URL_REF="" - echo "" + # echo "" info "Configure MongoDB connection:" echo "" @@ -324,6 +329,8 @@ setup_environment_files() { mv "$INSTANCE_DIR/configs/taxii/config/template.env" "$INSTANCE_DIR/configs/taxii/config/dev.env" success "Created $INSTANCE_DIR/configs/taxii/config/dev.env" fi + + echo "" } # Configure custom SSL certificates for REST API @@ -331,7 +338,7 @@ configure_custom_certificates() { CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF="" CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF="" - echo "" + # echo "" info "Custom SSL certificates allow the REST API to trust additional CA certificates." info "This is useful when behind a firewall that performs SSL inspection." echo "" @@ -342,14 +349,16 @@ configure_custom_certificates() { read -p "Enter certificate filename [custom-certs.pem]: " user_certs_filename CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF=${user_certs_filename:-custom-certs.pem} - info "Using certificates from: $CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF/$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" echo "" + info "Using certificates from: $CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF/$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" + # echo "" # Add custom cert configuration to .env local env_file="$INSTANCE_DIR/.env" update_env_file "$env_file" "HOST_CERTS_PATH" "$CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF" update_env_file "$env_file" "CERTS_FILENAME" "$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" success "Added certificate configuration to $env_file" + echo "" } #=============================================================================== @@ -373,6 +382,7 @@ add_taxii_to_compose() { mv "$temp_file" "$compose_file" success "TAXII server added to compose.yaml" + echo "" } # Verify all required source repositories exist for developer mode @@ -417,7 +427,7 @@ show_dev_mode_structure() { local deployment_dir="$1" local enable_taxii="$2" - echo "" + # echo "" info "Developer mode requires source repositories to be cloned as siblings to the deployment repository." echo "" echo "Expected directory structure:" @@ -695,6 +705,7 @@ else exit 1 fi fi +echo "" cd "$DEPLOYMENT_DIR" @@ -722,7 +733,7 @@ fi # Instance Setup #--------------------------------------- -echo "" +# echo "" info "Setting up your Workbench instance..." echo "" @@ -737,13 +748,13 @@ create_instance "$INSTANCE_DIR" "$DEPLOYMENT_DIR" # Deployment Options #--------------------------------------- -echo "" +# echo "" info "Configuring deployment options..." echo "" - prompt_yes_no "Do you want to deploy with the TAXII server?" "N" ENABLE_TAXII="$PROMPT_YES_NO_RESULT" +echo "" if [[ ! $ENABLE_TAXII =~ ^[Yy]$ ]]; then # Remove TAXII configs if not needed @@ -756,7 +767,6 @@ fi # Environment Configuration #--------------------------------------- -echo "" configure_database DATABASE_URL="$CONFIGURE_DATABASE_DB_URL_REF" setup_environment_files "$DATABASE_URL" @@ -765,7 +775,7 @@ if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then add_taxii_to_compose fi -echo "" +# echo "" success "Instance '$INSTANCE_NAME' created successfully!" echo "" @@ -783,6 +793,7 @@ ENABLE_CUSTOM_CERTS="$PROMPT_YES_NO_RESULT" HOST_CERTS_PATH="./certs" CERTS_FILENAME="custom-certs.pem" +echo "" if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then configure_custom_certificates HOST_CERTS_PATH="$CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF" From 61e4ca5a66758ebd68bba306f4fdd0884fb87f0d Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 10 Nov 2025 15:01:00 -0600 Subject: [PATCH 23/47] docs: update documentation about docker example --- README.md | 6 +++--- docker/README.md | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index eed1b7a..719579d 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ This repository contains deployment files for the ATT&CK Workbench, a web applic It is composed of a frontend Single Page App (SPA), a backend REST API, and a database. Optionally, you can deploy a "sidecar service" that makes your Workbench data available over a TAXII 2.1 API. -## Quick Start +## Docker Setup -To quickly create and deploy a custom Workbench instance using Docker and Compose use the interactive setup script in the `docker/` directory. +To quickly create and deploy a custom Workbench instance using Docker Compose use the interactive setup script in the `docker/` directory. See [docker/README](docker/README.md) for detailed instructions. -## Kubernetes +## Kubernetes Setup For production deployments, Kubernetes manifests with Kustomize are available in the `k8s/` directory. diff --git a/docker/README.md b/docker/README.md index 626b7d1..718dcc0 100644 --- a/docker/README.md +++ b/docker/README.md @@ -5,21 +5,19 @@ Use the interactive setup script `setup-workbench.sh` to quickly create and depl ```bash # Clone and run setup script git clone https://github.com/mitre-attack/attack-workbench-deployment.git -cd attack-workbench-deployment -./docker/setup-workbench.sh +cd attack-workbench-deployment/docker/ +./setup-workbench.sh ``` After running the script, deploy with: ```bash -cd instances/your-instance-name -docker compose up -d -``` +cd ../instances/your-instance-name -For developer mode deployments, use: +# deploy with docker compose +docker compose up -d -```bash -cd instances/your-instance-name +# or deploy in development mode docker compose up -d --build ``` From 30431db1737d42ebe5b40b6d13dba309acb2f173 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 10 Nov 2025 15:01:24 -0600 Subject: [PATCH 24/47] fix: remove compose project name variable --- docker/example-setup/template.env | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker/example-setup/template.env b/docker/example-setup/template.env index b656c13..14f9032 100644 --- a/docker/example-setup/template.env +++ b/docker/example-setup/template.env @@ -1,6 +1,3 @@ -# Project -COMPOSE_PROJECT_NAME=attack-workbench - # Docker Image Tags ATTACKWB_FRONTEND_VERSION=latest ATTACKWB_RESTAPI_VERSION=latest From feae9080576a256d50a5e9f5b14a98dc86cd158e Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 11 Nov 2025 17:13:49 -0600 Subject: [PATCH 25/47] fix: update script to use default paths for custom certs if not specified in .env file --- docker/setup-workbench.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 3a7c294..6a337fd 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -479,9 +479,9 @@ EOF if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then cat << 'EOF' volumes: - - ${HOST_CERTS_PATH}:/usr/src/app/certs + - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} EOF fi @@ -511,9 +511,9 @@ generate_rest_api_certs_override() { rest-api: volumes: - - ${HOST_CERTS_PATH}:/usr/src/app/certs + - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} EOF } From 6efbed793234dc4baf0825eca3881735bd019a21 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 14 Nov 2025 23:42:21 -0600 Subject: [PATCH 26/47] fix: update nginx configs for frontend --- .../configs/frontend/nginx.api.conf | 44 +++++++++++++++++++ .../{nginx-ssl.conf => nginx.api.ssl.conf} | 36 +++++++-------- .../example-setup/configs/frontend/nginx.conf | 31 +------------ .../configs/frontend/nginx.ssl.conf | 30 +++++++++++++ docker/example-setup/configs/taxii/nginx.conf | 29 ++++++++---- docs/configuration.md | 19 +++++--- 6 files changed, 126 insertions(+), 63 deletions(-) create mode 100644 docker/example-setup/configs/frontend/nginx.api.conf rename docker/example-setup/configs/frontend/{nginx-ssl.conf => nginx.api.ssl.conf} (62%) create mode 100644 docker/example-setup/configs/frontend/nginx.ssl.conf diff --git a/docker/example-setup/configs/frontend/nginx.api.conf b/docker/example-setup/configs/frontend/nginx.api.conf new file mode 100644 index 0000000..7144404 --- /dev/null +++ b/docker/example-setup/configs/frontend/nginx.api.conf @@ -0,0 +1,44 @@ +http { + server { + listen 80; + http2 on; + server_name _; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + client_max_body_size 50M; + + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; + + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; + + # Disable compression for SSE + gzip off; + + proxy_pass http://rest-api:3000; + } + + } +} diff --git a/docker/example-setup/configs/frontend/nginx-ssl.conf b/docker/example-setup/configs/frontend/nginx.api.ssl.conf similarity index 62% rename from docker/example-setup/configs/frontend/nginx-ssl.conf rename to docker/example-setup/configs/frontend/nginx.api.ssl.conf index 9cd32cc..3a6e415 100644 --- a/docker/example-setup/configs/frontend/nginx-ssl.conf +++ b/docker/example-setup/configs/frontend/nginx.api.ssl.conf @@ -1,21 +1,16 @@ -worker_processes 1; - -events { - worker_connections 1024; -} - http { server { - listen 80 default_server; + listen 80; server_name _; return 301 https://$host$request_uri; } server { - listen 443 ssl default_server; - http2 on; - server_name _; - ssl_certificate /etc/nginx/certs/server.pem; + listen 443 ssl default_server; + http2 on; + server_name _; + + ssl_certificate /etc/nginx/certs/server.pem; ssl_certificate_key /etc/nginx/certs/server.key; root /usr/share/nginx/html; @@ -35,23 +30,24 @@ http { client_max_body_size 50M; # Disable buffering for SSE streams - #proxy_buffering off; - #proxy_cache off; + proxy_buffering off; + proxy_cache off; # Keep connection alive for long-running requests - #proxy_read_timeout 600s; - #proxy_connect_timeout 300s; - #proxy_send_timeout 300s; + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; # Required headers for SSE - #proxy_set_header Connection ''; - #proxy_http_version 1.1; - #chunked_transfer_encoding on; + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; # Disable compression for SSE - #gzip off; + gzip off; proxy_pass http://rest-api:3000; } + } } diff --git a/docker/example-setup/configs/frontend/nginx.conf b/docker/example-setup/configs/frontend/nginx.conf index 240b8bf..5839940 100644 --- a/docker/example-setup/configs/frontend/nginx.conf +++ b/docker/example-setup/configs/frontend/nginx.conf @@ -1,13 +1,8 @@ -worker_processes 1; - -events { - worker_connections 1024; -} - http { server { listen 80; - server_name localhost; + http2 on; + server_name _; root /usr/share/nginx/html; index index.html index.htm; @@ -22,27 +17,5 @@ http { try_files $uri $uri/ /index.html; } - location /api { - client_max_body_size 50M; - - # Disable buffering for SSE streams - proxy_buffering off; - proxy_cache off; - - # Keep connection alive for long-running requests - proxy_read_timeout 600s; - proxy_connect_timeout 300s; - proxy_send_timeout 300s; - - # Required headers for SSE - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding on; - - # Disable compression for SSE - gzip off; - - proxy_pass http://rest-api:3000; - } } } diff --git a/docker/example-setup/configs/frontend/nginx.ssl.conf b/docker/example-setup/configs/frontend/nginx.ssl.conf new file mode 100644 index 0000000..92e9e2a --- /dev/null +++ b/docker/example-setup/configs/frontend/nginx.ssl.conf @@ -0,0 +1,30 @@ +http { + server { + listen 80; + server_name _; + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl default_server; + http2 on; + server_name _; + + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + + } +} diff --git a/docker/example-setup/configs/taxii/nginx.conf b/docker/example-setup/configs/taxii/nginx.conf index 875ed25..9ff97e0 100644 --- a/docker/example-setup/configs/taxii/nginx.conf +++ b/docker/example-setup/configs/taxii/nginx.conf @@ -1,14 +1,8 @@ -worker_processes 1; - -events { - worker_connections 1024; -} - http { - # Server block for private TAXII administrative traffic. Routes traffic for downstream Workbench services. server { listen 80; - server_name localhost; + http2 on; + server_name _; root /usr/share/nginx/html; index index.html index.htm; @@ -25,8 +19,27 @@ http { location /api { client_max_body_size 50M; + + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; + + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; + + # Disable compression for SSE + gzip off; + proxy_pass http://rest-api:3000; } + } # Server block for TAXII server's LetsEncrypt handshake process diff --git a/docs/configuration.md b/docs/configuration.md index b0123c3..59671ee 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -24,12 +24,19 @@ Available environment variables: ### Frontend -| Variable | Default Value | Description | -|---------------------------------------|---------------------------------|------------------------------------| -| `ATTACKWB_FRONTEND_HTTP_PORT` | `80` | Frontend HTTP port | -| `ATTACKWB_FRONTEND_HTTPS_PORT` | `443` | Frontend HTTPS port | -| `ATTACKWB_FRONTEND_NGINX_CONFIG_FILE` | `./configs/frontend/nginx.conf` | Path to nginx config file | -| `ATTACKWB_FRONTEND_CERTS_PATH` | `./certs` | Path to SSL certificates for nginx | +| Variable | Default Value | Description | +|---------------------------------------|-------------------------------------|------------------------------------| +| `ATTACKWB_FRONTEND_HTTP_PORT` | `80` | Frontend HTTP port | +| `ATTACKWB_FRONTEND_HTTPS_PORT` | `443` | Frontend HTTPS port | +| `ATTACKWB_FRONTEND_NGINX_CONFIG_FILE` | `./configs/frontend/nginx.api.conf` | Path to nginx config file | +| `ATTACKWB_FRONTEND_CERTS_PATH` | `./certs` | Path to SSL certificates for nginx | + +There are four sample nginx config files that can be used as reference: + +- `nginx.conf`: Minimal nginx configuration that only routes the Workbench frontend. +- `nginx.ssl.conf`: Same as `nginx.conf` but with an SSL redirect. You need to provide your own SSL certs in the `ATTACKWB_FRONTEND_CERTS_PATH` directory. +- `nginx.api.conf` (default): Nginx configuration with an additional `/api` location block for connecting to the REST API container. +- `nginx.api.ssl.conf`: Same as `nginx.api.conf` but with an SSL redirect. You need to provide your own SSL certs in the `ATTACKWB_FRONTEND_CERTS_PATH` directory. ### REST API From f785b07a31e0ac889d60dc40fd5eceef9df0beb9 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 14 Nov 2025 23:44:13 -0600 Subject: [PATCH 27/47] fix: update default nginx config --- docker/example-setup/compose.yaml | 2 +- docker/example-setup/template.env | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index d6bbf91..3881254 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -8,7 +8,7 @@ services: - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:80" - "${ATTACKWB_FRONTEND_HTTPS_PORT:-443}:443" volumes: - - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.conf}:/etc/nginx/nginx.conf:ro" + - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.api.conf}:/etc/nginx/nginx.conf:ro" - "${ATTACKWB_FRONTEND_CERTS_PATH:-./certs}:/etc/nginx/certs:ro" restart: unless-stopped logging: diff --git a/docker/example-setup/template.env b/docker/example-setup/template.env index 14f9032..e7ea52e 100644 --- a/docker/example-setup/template.env +++ b/docker/example-setup/template.env @@ -6,7 +6,7 @@ ATTACKWB_TAXII_VERSION=latest # Frontend #ATTACKWB_FRONTEND_HTTP_PORT=80 #ATTACKWB_FRONTEND_HTTPS_PORT=443 -#ATTACKWB_FRONTEND_NGINX_CONFIG_FILE=./configs/frontend/nginx.conf +#ATTACKWB_FRONTEND_NGINX_CONFIG_FILE=./configs/frontend/nginx.api.conf # Used for setting SSL certs in nginx #ATTACKWB_FRONTEND_CERTS_PATH=./certs From d43e0f79213b3e2634d04d456918c25129d149cb Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 15 Nov 2025 20:25:10 -0600 Subject: [PATCH 28/47] fix: update nginx config files --- docker/example-setup/compose.yaml | 2 +- .../configs/frontend/nginx.api.conf | 70 ++++++++--------- .../configs/frontend/nginx.api.ssl.conf | 78 +++++++++---------- .../example-setup/configs/frontend/nginx.conf | 30 ++++--- .../configs/frontend/nginx.ssl.conf | 44 +++++------ 5 files changed, 108 insertions(+), 116 deletions(-) diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index 3881254..2ccfd79 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -8,7 +8,7 @@ services: - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:80" - "${ATTACKWB_FRONTEND_HTTPS_PORT:-443}:443" volumes: - - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.api.conf}:/etc/nginx/nginx.conf:ro" + - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.api.conf}:/etc/nginx/conf.d/default.conf:ro" - "${ATTACKWB_FRONTEND_CERTS_PATH:-./certs}:/etc/nginx/certs:ro" restart: unless-stopped logging: diff --git a/docker/example-setup/configs/frontend/nginx.api.conf b/docker/example-setup/configs/frontend/nginx.api.conf index 7144404..6789577 100644 --- a/docker/example-setup/configs/frontend/nginx.api.conf +++ b/docker/example-setup/configs/frontend/nginx.api.conf @@ -1,44 +1,42 @@ -http { - server { - listen 80; - http2 on; - server_name _; - - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; - - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - location / { - try_files $uri $uri/ /index.html; - } - - location /api { - client_max_body_size 50M; +server { + listen 80; + http2 on; + server_name _; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } - # Disable buffering for SSE streams - proxy_buffering off; - proxy_cache off; + location /api { + client_max_body_size 50M; - # Keep connection alive for long-running requests - proxy_read_timeout 600s; - proxy_connect_timeout 300s; - proxy_send_timeout 300s; + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; - # Required headers for SSE - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding on; + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; - # Disable compression for SSE - gzip off; + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; - proxy_pass http://rest-api:3000; - } + # Disable compression for SSE + gzip off; + proxy_pass http://rest-api:3000; } + } diff --git a/docker/example-setup/configs/frontend/nginx.api.ssl.conf b/docker/example-setup/configs/frontend/nginx.api.ssl.conf index 3a6e415..2c2bb87 100644 --- a/docker/example-setup/configs/frontend/nginx.api.ssl.conf +++ b/docker/example-setup/configs/frontend/nginx.api.ssl.conf @@ -1,53 +1,51 @@ -http { - server { - listen 80; - server_name _; - return 301 https://$host$request_uri; - } - - server { - listen 443 ssl default_server; - http2 on; - server_name _; +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} - ssl_certificate /etc/nginx/certs/server.pem; - ssl_certificate_key /etc/nginx/certs/server.key; +server { + listen 443 ssl default_server; + http2 on; + server_name _; - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; - location / { - try_files $uri $uri/ /index.html; - } + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; - location /api { - client_max_body_size 50M; + location / { + try_files $uri $uri/ /index.html; + } - # Disable buffering for SSE streams - proxy_buffering off; - proxy_cache off; + location /api { + client_max_body_size 50M; - # Keep connection alive for long-running requests - proxy_read_timeout 600s; - proxy_connect_timeout 300s; - proxy_send_timeout 300s; + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; - # Required headers for SSE - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding on; + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; - # Disable compression for SSE - gzip off; + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; - proxy_pass http://rest-api:3000; - } + # Disable compression for SSE + gzip off; + proxy_pass http://rest-api:3000; } + } diff --git a/docker/example-setup/configs/frontend/nginx.conf b/docker/example-setup/configs/frontend/nginx.conf index 5839940..6123cec 100644 --- a/docker/example-setup/configs/frontend/nginx.conf +++ b/docker/example-setup/configs/frontend/nginx.conf @@ -1,21 +1,19 @@ -http { - server { - listen 80; - http2 on; - server_name _; +server { + listen 80; + http2 on; + server_name _; - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - location / { - try_files $uri $uri/ /index.html; - } + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + location / { + try_files $uri $uri/ /index.html; } + } diff --git a/docker/example-setup/configs/frontend/nginx.ssl.conf b/docker/example-setup/configs/frontend/nginx.ssl.conf index 92e9e2a..d423ee4 100644 --- a/docker/example-setup/configs/frontend/nginx.ssl.conf +++ b/docker/example-setup/configs/frontend/nginx.ssl.conf @@ -1,30 +1,28 @@ -http { - server { - listen 80; - server_name _; - return 301 https://$host$request_uri; - } - - server { - listen 443 ssl default_server; - http2 on; - server_name _; +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} - ssl_certificate /etc/nginx/certs/server.pem; - ssl_certificate_key /etc/nginx/certs/server.key; +server { + listen 443 ssl default_server; + http2 on; + server_name _; - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; - location / { - try_files $uri $uri/ /index.html; - } + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + location / { + try_files $uri $uri/ /index.html; } + } From e0ac962903983634c524cd7ea274fc5bd02840c6 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 11:02:52 -0500 Subject: [PATCH 29/47] feat: set docker as default mongo connection --- docker/setup-workbench.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 6a337fd..f54fe36 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -119,9 +119,11 @@ prompt_yes_no() { } # Prompt for menu selection with validation -# Usage: prompt_menu "option1" "option2" "option3" -# Args: menu options +# Usage: prompt_menu "default_index" "option1" "option2" "option3" +# Args: $1=default index (1-based), remaining args are menu options prompt_menu() { + local default_index="$1" + shift local -a options=("$@") local num_options=${#options[@]} @@ -131,7 +133,8 @@ prompt_menu() { echo "$((i + 1))) ${options[$i]}" done echo "" - read -p "Select option [1-$num_options]: " -r choice + read -p "Select option 1-$num_options: [1] " -r choice + choice=${choice:-$default_index} if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$num_options" ]; then PROMPT_MENU_RESULT="$choice" @@ -279,7 +282,7 @@ configure_database() { info "Configure MongoDB connection:" echo "" - prompt_menu \ + prompt_menu 1 \ "Docker setup ($DB_URL_DOCKER)" \ "Local MongoDB ($DB_URL_LOCAL)" \ "Custom connection string" From 388aece52cfa224826ce2e15f376464bdf2d5791 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 12:46:32 -0500 Subject: [PATCH 30/47] feat: add --accept-defaults cli option --- docker/setup-workbench.sh | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index f54fe36..5795967 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -1,6 +1,19 @@ #!/usr/bin/env bash -set -e +# Parse optional CLI arguments +ACCEPT_DEFAULTS=false +while [[ $# -gt 0 ]]; do + case "$1" in + --accept-defaults) + ACCEPT_DEFAULTS=true + shift + ;; + *) + # unknown args passed to script; keep for later processing + shift + ;; + esac +done # ATT&CK Workbench Deployment Setup Script # This script helps you quickly set up a custom ATT&CK Workbench instance @@ -101,6 +114,12 @@ require_directory() { # Usage: prompt_yes_no "Question?" "Y" # Args: $1=question, $2=default (Y/N) prompt_yes_no() { + # If defaults are automatically accepted, use the provided default and skip prompting + if $ACCEPT_DEFAULTS; then + PROMPT_YES_NO_RESULT="${2:-N}" + return + fi + local question="$1" local default="$2" PROMPT_YES_NO_RESULT="" @@ -122,6 +141,12 @@ prompt_yes_no() { # Usage: prompt_menu "default_index" "option1" "option2" "option3" # Args: $1=default index (1-based), remaining args are menu options prompt_menu() { + # If defaults are automatically accepted, use the default index and skip prompting + if $ACCEPT_DEFAULTS; then + PROMPT_MENU_RESULT="${1}" + return + fi + local default_index="$1" shift local -a options=("$@") @@ -211,10 +236,16 @@ get_repo_url() { # Prompt for and validate instance name get_instance_name() { - GET_INSTANCE_NAME_NAME_REF="" + local default_instance_name="my-workbench" + # If defaults are automatically accepted, use the default index and skip prompting + if $ACCEPT_DEFAULTS; then + GET_INSTANCE_NAME_NAME_REF="${default_instance_name}" + return + fi + read -p "Enter instance name [my-workbench]: " GET_INSTANCE_NAME_NAME_REF - GET_INSTANCE_NAME_NAME_REF=${GET_INSTANCE_NAME_NAME_REF:-my-workbench} + GET_INSTANCE_NAME_NAME_REF=${GET_INSTANCE_NAME_NAME_REF:-$default_instance_name} # Validate instance name if [[ ! "$GET_INSTANCE_NAME_NAME_REF" =~ ^[a-zA-Z0-9_-]+$ ]]; then From bb62547bb57494280326de773c493d281c173521 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 13:03:50 -0500 Subject: [PATCH 31/47] feat: add cli option to deploy taxii --- docker/setup-workbench.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 5795967..8c44e69 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -2,12 +2,17 @@ # Parse optional CLI arguments ACCEPT_DEFAULTS=false +AUTO_ENABLE_TAXII=false while [[ $# -gt 0 ]]; do case "$1" in --accept-defaults) ACCEPT_DEFAULTS=true shift ;; + --taxii-server) + AUTO_ENABLE_TAXII=true + shift + ;; *) # unknown args passed to script; keep for later processing shift @@ -243,7 +248,7 @@ get_instance_name() { GET_INSTANCE_NAME_NAME_REF="${default_instance_name}" return fi - + read -p "Enter instance name [my-workbench]: " GET_INSTANCE_NAME_NAME_REF GET_INSTANCE_NAME_NAME_REF=${GET_INSTANCE_NAME_NAME_REF:-$default_instance_name} @@ -786,9 +791,13 @@ create_instance "$INSTANCE_DIR" "$DEPLOYMENT_DIR" info "Configuring deployment options..." echo "" -prompt_yes_no "Do you want to deploy with the TAXII server?" "N" -ENABLE_TAXII="$PROMPT_YES_NO_RESULT" -echo "" +if $AUTO_ENABLE_TAXII; then + ENABLE_TAXII="y" +else + prompt_yes_no "Do you want to deploy with the TAXII server?" "N" + ENABLE_TAXII="$PROMPT_YES_NO_RESULT" + echo "" +fi if [[ ! $ENABLE_TAXII =~ ^[Yy]$ ]]; then # Remove TAXII configs if not needed From acff88e57d2c8b344f8f488450b815e4473c4b35 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 13:17:59 -0500 Subject: [PATCH 32/47] feat: add cli option to enable developer mode --- docker/setup-workbench.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 8c44e69..11bb9da 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -3,6 +3,7 @@ # Parse optional CLI arguments ACCEPT_DEFAULTS=false AUTO_ENABLE_TAXII=false +AUTO_DEV_MODE=false while [[ $# -gt 0 ]]; do case "$1" in --accept-defaults) @@ -13,6 +14,10 @@ while [[ $# -gt 0 ]]; do AUTO_ENABLE_TAXII=true shift ;; + --dev-mode) + AUTO_DEV_MODE=true + shift + ;; *) # unknown args passed to script; keep for later processing shift @@ -826,9 +831,12 @@ echo "" # Additional Options #--------------------------------------- - -prompt_yes_no "Do you want to set up in developer mode (build from source)?" "N" -DEV_MODE="$PROMPT_YES_NO_RESULT" +if $AUTO_DEV_MODE; then + DEV_MODE="y" +else + prompt_yes_no "Do you want to set up in developer mode (build from source)?" "N" + DEV_MODE="$PROMPT_YES_NO_RESULT" +fi prompt_yes_no "Do you want to configure custom SSL certificates for the REST API?" "N" ENABLE_CUSTOM_CERTS="$PROMPT_YES_NO_RESULT" From 98d7a284f095ca27fdfd9248d07a031e7e7314c6 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 13:44:19 -0500 Subject: [PATCH 33/47] feat: add cli option to set instance name --- docker/setup-workbench.sh | 51 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 11bb9da..89767db 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -1,9 +1,18 @@ #!/usr/bin/env bash +usage() { + echo "Usage: $(basename "$0") [--instance-name ] [-h|--help]" + echo + echo "Options:" + echo " --instance-name Optional instance name" + echo " -h, --help Show this help and exit" +} + # Parse optional CLI arguments ACCEPT_DEFAULTS=false AUTO_ENABLE_TAXII=false AUTO_DEV_MODE=false +AUTO_INSTANCE_NAME="" while [[ $# -gt 0 ]]; do case "$1" in --accept-defaults) @@ -18,10 +27,40 @@ while [[ $# -gt 0 ]]; do AUTO_DEV_MODE=true shift ;; - *) - # unknown args passed to script; keep for later processing + --instance-name) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --instance-name requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_INSTANCE_NAME="$2" + shift 2 + ;; + --instance-name=*) + AUTO_INSTANCE_NAME="${1#*=}" + if [[ -z "$AUTO_INSTANCE_NAME" ]]; then + echo "Error: --instance-name requires a value." >&2 + echo "" + usage + exit 1 + fi shift ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + break + ;; + -*) + echo "Unknown option: $1" >&2 + echo "" + usage + exit 1 + ;; esac done @@ -781,8 +820,12 @@ fi info "Setting up your Workbench instance..." echo "" -get_instance_name -INSTANCE_NAME="$GET_INSTANCE_NAME_NAME_REF" +if [[ -z "$AUTO_INSTANCE_NAME" ]]; then + get_instance_name + INSTANCE_NAME="$GET_INSTANCE_NAME_NAME_REF" +else + INSTANCE_NAME="$AUTO_INSTANCE_NAME" +fi INSTANCE_DIR="$DEPLOYMENT_DIR/instances/$INSTANCE_NAME" handle_existing_instance "$INSTANCE_DIR" "$INSTANCE_NAME" From a225c275e0a39a3924eba3f8b9841714594b8ff9 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 13:53:33 -0500 Subject: [PATCH 34/47] feat: add cli option to print help info --- docker/setup-workbench.sh | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 89767db..c38d1eb 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -4,8 +4,12 @@ usage() { echo "Usage: $(basename "$0") [--instance-name ] [-h|--help]" echo echo "Options:" - echo " --instance-name Optional instance name" + echo " --accept-defaults Run non-interactively using default selections" + echo " --dev-mode Setup in developer mode (build from source)" + echo " --instance-name Name of the generated configuration" echo " -h, --help Show this help and exit" + echo " --taxi-server Deploy with the TAXII server" + } # Parse optional CLI arguments @@ -19,14 +23,14 @@ while [[ $# -gt 0 ]]; do ACCEPT_DEFAULTS=true shift ;; - --taxii-server) - AUTO_ENABLE_TAXII=true - shift - ;; --dev-mode) AUTO_DEV_MODE=true shift ;; + -h|--help) + usage + exit 0 + ;; --instance-name) if [[ $# -lt 2 || "${2:-}" == -* ]]; then echo "Error: --instance-name requires a value." >&2 @@ -47,9 +51,9 @@ while [[ $# -gt 0 ]]; do fi shift ;; - -h|--help) - usage - exit 0 + --taxii-server) + AUTO_ENABLE_TAXII=true + shift ;; --) shift From 569fda1f782c753063138692c53facc99e6e386a Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 14:13:39 -0500 Subject: [PATCH 35/47] feat: add cli option to set the mongodb connection url --- docker/setup-workbench.sh | 44 ++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index c38d1eb..b559169 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -17,6 +17,7 @@ ACCEPT_DEFAULTS=false AUTO_ENABLE_TAXII=false AUTO_DEV_MODE=false AUTO_INSTANCE_NAME="" +AUTO_DATABASE_URL="" while [[ $# -gt 0 ]]; do case "$1" in --accept-defaults) @@ -51,6 +52,26 @@ while [[ $# -gt 0 ]]; do fi shift ;; + --mongodb-connection) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --mongodb-connection requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_DATABASE_URL="$2" + shift 2 + ;; + --mongodb-connection=*) + AUTO_DATABASE_URL="${1#*=}" + if [[ -z "$AUTO_DATABASE_URL" ]]; then + echo "Error: --mongodb-connection requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; --taxii-server) AUTO_ENABLE_TAXII=true shift @@ -291,7 +312,13 @@ get_repo_url() { get_instance_name() { local default_instance_name="my-workbench" - # If defaults are automatically accepted, use the default index and skip prompting + # If instance name was provided via cli, use the cli value and skip prompting + if [[ -n "${AUTO_INSTANCE_NAME-}" ]]; then + GET_INSTANCE_NAME_NAME_REF="${AUTO_INSTANCE_NAME}" + return + fi + + # If defaults are automatically accepted, use the default name and skip prompting if $ACCEPT_DEFAULTS; then GET_INSTANCE_NAME_NAME_REF="${default_instance_name}" return @@ -366,6 +393,13 @@ configure_database() { info "Configure MongoDB connection:" echo "" + # If instance name was provided via cli, use the cli value and skip prompting + if [[ -n "${AUTO_DATABASE_URL-}" ]]; then + CONFIGURE_DATABASE_DB_URL_REF="${AUTO_DATABASE_URL}" + info "Using custom connection: $CONFIGURE_DATABASE_DB_URL_REF" + return + fi + prompt_menu 1 \ "Docker setup ($DB_URL_DOCKER)" \ "Local MongoDB ($DB_URL_LOCAL)" \ @@ -824,12 +858,8 @@ fi info "Setting up your Workbench instance..." echo "" -if [[ -z "$AUTO_INSTANCE_NAME" ]]; then - get_instance_name - INSTANCE_NAME="$GET_INSTANCE_NAME_NAME_REF" -else - INSTANCE_NAME="$AUTO_INSTANCE_NAME" -fi +get_instance_name +INSTANCE_NAME="$GET_INSTANCE_NAME_NAME_REF" INSTANCE_DIR="$DEPLOYMENT_DIR/instances/$INSTANCE_NAME" handle_existing_instance "$INSTANCE_DIR" "$INSTANCE_NAME" From 62159b71eb3137025681560be6be70b33f0837bd Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 14:16:15 -0500 Subject: [PATCH 36/47] chore: standardize indentation --- docker/setup-workbench.sh | 153 +++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index b559169..bcf0b10 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -1,15 +1,14 @@ #!/usr/bin/env bash usage() { - echo "Usage: $(basename "$0") [--instance-name ] [-h|--help]" - echo - echo "Options:" - echo " --accept-defaults Run non-interactively using default selections" - echo " --dev-mode Setup in developer mode (build from source)" - echo " --instance-name Name of the generated configuration" - echo " -h, --help Show this help and exit" - echo " --taxi-server Deploy with the TAXII server" - + echo "Usage: $(basename "$0") [--instance-name ] [-h|--help]" + echo + echo "Options:" + echo " --accept-defaults Run non-interactively using default selections" + echo " --dev-mode Setup in developer mode (build from source)" + echo " --instance-name Name of the generated configuration" + echo " -h, --help Show this help and exit" + echo " --taxi-server Deploy with the TAXII server" } # Parse optional CLI arguments @@ -19,74 +18,74 @@ AUTO_DEV_MODE=false AUTO_INSTANCE_NAME="" AUTO_DATABASE_URL="" while [[ $# -gt 0 ]]; do - case "$1" in - --accept-defaults) - ACCEPT_DEFAULTS=true - shift - ;; - --dev-mode) - AUTO_DEV_MODE=true - shift - ;; - -h|--help) - usage - exit 0 - ;; - --instance-name) - if [[ $# -lt 2 || "${2:-}" == -* ]]; then - echo "Error: --instance-name requires a value." >&2 - echo "" - usage - exit 1 - fi - AUTO_INSTANCE_NAME="$2" - shift 2 - ;; - --instance-name=*) - AUTO_INSTANCE_NAME="${1#*=}" - if [[ -z "$AUTO_INSTANCE_NAME" ]]; then - echo "Error: --instance-name requires a value." >&2 - echo "" - usage - exit 1 - fi - shift - ;; - --mongodb-connection) - if [[ $# -lt 2 || "${2:-}" == -* ]]; then - echo "Error: --mongodb-connection requires a value." >&2 - echo "" - usage - exit 1 - fi - AUTO_DATABASE_URL="$2" - shift 2 - ;; - --mongodb-connection=*) - AUTO_DATABASE_URL="${1#*=}" - if [[ -z "$AUTO_DATABASE_URL" ]]; then - echo "Error: --mongodb-connection requires a value." >&2 - echo "" - usage - exit 1 - fi - shift - ;; - --taxii-server) - AUTO_ENABLE_TAXII=true - shift - ;; - --) - shift - break - ;; - -*) - echo "Unknown option: $1" >&2 - echo "" - usage - exit 1 - ;; - esac + case "$1" in + --accept-defaults) + ACCEPT_DEFAULTS=true + shift + ;; + --dev-mode) + AUTO_DEV_MODE=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + --instance-name) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --instance-name requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_INSTANCE_NAME="$2" + shift 2 + ;; + --instance-name=*) + AUTO_INSTANCE_NAME="${1#*=}" + if [[ -z "$AUTO_INSTANCE_NAME" ]]; then + echo "Error: --instance-name requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; + --mongodb-connection) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --mongodb-connection requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_DATABASE_URL="$2" + shift 2 + ;; + --mongodb-connection=*) + AUTO_DATABASE_URL="${1#*=}" + if [[ -z "$AUTO_DATABASE_URL" ]]; then + echo "Error: --mongodb-connection requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; + --taxii-server) + AUTO_ENABLE_TAXII=true + shift + ;; + --) + shift + break + ;; + -*) + echo "Unknown option: $1" >&2 + echo "" + usage + exit 1 + ;; + esac done # ATT&CK Workbench Deployment Setup Script From 6aaf4d792b256c60ac184ff4f43a800ba0ce63b7 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 14:18:00 -0500 Subject: [PATCH 37/47] chore: document mongodb connection cli option --- docker/setup-workbench.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index bcf0b10..bc268b1 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -8,6 +8,7 @@ usage() { echo " --dev-mode Setup in developer mode (build from source)" echo " --instance-name Name of the generated configuration" echo " -h, --help Show this help and exit" + echo " --mongodb-connection MongoDB connection string" echo " --taxi-server Deploy with the TAXII server" } From b665500b5cadb1442807111bc7d350b42c7c41fe Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 14:25:23 -0500 Subject: [PATCH 38/47] chore: fix usage documentation --- docker/setup-workbench.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index bc268b1..6c78ccc 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash usage() { - echo "Usage: $(basename "$0") [--instance-name ] [-h|--help]" + echo "Usage: $(basename "$0") [--accept-defaults] [--dev-mode] [--instance-name ] [-h | --help] [--mongodb-connection ] [--taxi-server]" echo echo "Options:" - echo " --accept-defaults Run non-interactively using default selections" - echo " --dev-mode Setup in developer mode (build from source)" - echo " --instance-name Name of the generated configuration" - echo " -h, --help Show this help and exit" - echo " --mongodb-connection MongoDB connection string" - echo " --taxi-server Deploy with the TAXII server" + echo " --accept-defaults Run non-interactively using default selections" + echo " --dev-mode Setup in developer mode (build from source)" + echo " --instance-name Name of the generated configuration" + echo " -h, --help Show this help and exit" + echo " --mongodb-connection MongoDB connection string" + echo " --taxi-server Deploy with the TAXII server" } # Parse optional CLI arguments From 1f94a1afd0e715e8306342091842a8453fec3f81 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Mon, 24 Nov 2025 14:56:30 -0500 Subject: [PATCH 39/47] feat: add cli options for custom ssl certs --- docker/setup-workbench.sh | 82 ++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 6c78ccc..1219e00 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -4,12 +4,14 @@ usage() { echo "Usage: $(basename "$0") [--accept-defaults] [--dev-mode] [--instance-name ] [-h | --help] [--mongodb-connection ] [--taxi-server]" echo echo "Options:" - echo " --accept-defaults Run non-interactively using default selections" - echo " --dev-mode Setup in developer mode (build from source)" - echo " --instance-name Name of the generated configuration" - echo " -h, --help Show this help and exit" - echo " --mongodb-connection MongoDB connection string" - echo " --taxi-server Deploy with the TAXII server" + echo " --accept-defaults Run non-interactively using default selections" + echo " --dev-mode Setup in developer mode (build from source)" + echo " --instance-name Name of the generated configuration" + echo " -h, --help Show this help and exit" + echo " --mongodb-connection MongoDB connection string" + echo " --taxi-server Deploy with the TAXII server" + echo " --ssl-host-certs-path Host certificates directory path (ex: './certs')" + echo " --ssl-certs-file Certificates filename (ex: 'custom-certs.pem')" } # Parse optional CLI arguments @@ -18,6 +20,8 @@ AUTO_ENABLE_TAXII=false AUTO_DEV_MODE=false AUTO_INSTANCE_NAME="" AUTO_DATABASE_URL="" +AUTO_HOST_CERTS_PATH="" +AUTO_CERTS_FILENAME="" while [[ $# -gt 0 ]]; do case "$1" in --accept-defaults) @@ -72,6 +76,46 @@ while [[ $# -gt 0 ]]; do fi shift ;; + --ssl-host-certs-path) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --ssl-host-certs-path requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_HOST_CERTS_PATH="$2" + shift 2 + ;; + --ssl-host-certs-path=*) + AUTO_HOST_CERTS_PATH="${1#*=}" + if [[ -z "$AUTO_HOST_CERTS_PATH" ]]; then + echo "Error: --ssl-host-certs-path requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; + --ssl-certs-file) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --ssl-certs-file requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_CERTS_FILENAME="$2" + shift 2 + ;; + --ssl-certs-file=*) + AUTO_CERTS_FILENAME="${1#*=}" + if [[ -z "$AUTO_CERTS_FILENAME" ]]; then + echo "Error: --ssl-certs-file requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; --taxii-server) AUTO_ENABLE_TAXII=true shift @@ -81,7 +125,7 @@ while [[ $# -gt 0 ]]; do break ;; -*) - echo "Unknown option: $1" >&2 + echo "Error: Unknown option: $1" >&2 echo "" usage exit 1 @@ -464,11 +508,19 @@ configure_custom_certificates() { info "This is useful when behind a firewall that performs SSL inspection." echo "" - read -p "Enter host certificates path [./certs]: " user_certs_path - CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF=${user_certs_path:-./certs} + if [[ -z "${AUTO_HOST_CERTS_PATH}" ]]; then + read -p "Enter host certificates path [./certs]: " user_certs_path + CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF=${user_certs_path:-./certs} + else + CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF="${AUTO_HOST_CERTS_PATH}" + fi - read -p "Enter certificate filename [custom-certs.pem]: " user_certs_filename - CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF=${user_certs_filename:-custom-certs.pem} + if [[ -z "${AUTO_CERTS_FILENAME}" ]]; then + read -p "Enter certificate filename [custom-certs.pem]: " user_certs_filename + CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF=${user_certs_filename:-custom-certs.pem} + else + CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF="${AUTO_CERTS_FILENAME}" + fi echo "" info "Using certificates from: $CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF/$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" @@ -915,8 +967,12 @@ else DEV_MODE="$PROMPT_YES_NO_RESULT" fi -prompt_yes_no "Do you want to configure custom SSL certificates for the REST API?" "N" -ENABLE_CUSTOM_CERTS="$PROMPT_YES_NO_RESULT" +if [[ -z "${AUTO_HOST_CERTS_PATH}" && -z "${AUTO_CERTS_FILENAME}" ]]; then + prompt_yes_no "Do you want to configure custom SSL certificates for the REST API?" "N" + ENABLE_CUSTOM_CERTS="$PROMPT_YES_NO_RESULT" +else + ENABLE_CUSTOM_CERTS="y" +fi HOST_CERTS_PATH="./certs" CERTS_FILENAME="custom-certs.pem" From 3597dfdd942e9bcca98b11e155079cf4dd4c92c6 Mon Sep 17 00:00:00 2001 From: Kevin Ji Date: Tue, 25 Nov 2025 10:47:07 -0500 Subject: [PATCH 40/47] chore: move cli arg parsing to correct script sections --- docker/setup-workbench.sh | 273 +++++++++++++++++++------------------- 1 file changed, 140 insertions(+), 133 deletions(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 1219e00..a541cb9 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -1,138 +1,5 @@ #!/usr/bin/env bash -usage() { - echo "Usage: $(basename "$0") [--accept-defaults] [--dev-mode] [--instance-name ] [-h | --help] [--mongodb-connection ] [--taxi-server]" - echo - echo "Options:" - echo " --accept-defaults Run non-interactively using default selections" - echo " --dev-mode Setup in developer mode (build from source)" - echo " --instance-name Name of the generated configuration" - echo " -h, --help Show this help and exit" - echo " --mongodb-connection MongoDB connection string" - echo " --taxi-server Deploy with the TAXII server" - echo " --ssl-host-certs-path Host certificates directory path (ex: './certs')" - echo " --ssl-certs-file Certificates filename (ex: 'custom-certs.pem')" -} - -# Parse optional CLI arguments -ACCEPT_DEFAULTS=false -AUTO_ENABLE_TAXII=false -AUTO_DEV_MODE=false -AUTO_INSTANCE_NAME="" -AUTO_DATABASE_URL="" -AUTO_HOST_CERTS_PATH="" -AUTO_CERTS_FILENAME="" -while [[ $# -gt 0 ]]; do - case "$1" in - --accept-defaults) - ACCEPT_DEFAULTS=true - shift - ;; - --dev-mode) - AUTO_DEV_MODE=true - shift - ;; - -h|--help) - usage - exit 0 - ;; - --instance-name) - if [[ $# -lt 2 || "${2:-}" == -* ]]; then - echo "Error: --instance-name requires a value." >&2 - echo "" - usage - exit 1 - fi - AUTO_INSTANCE_NAME="$2" - shift 2 - ;; - --instance-name=*) - AUTO_INSTANCE_NAME="${1#*=}" - if [[ -z "$AUTO_INSTANCE_NAME" ]]; then - echo "Error: --instance-name requires a value." >&2 - echo "" - usage - exit 1 - fi - shift - ;; - --mongodb-connection) - if [[ $# -lt 2 || "${2:-}" == -* ]]; then - echo "Error: --mongodb-connection requires a value." >&2 - echo "" - usage - exit 1 - fi - AUTO_DATABASE_URL="$2" - shift 2 - ;; - --mongodb-connection=*) - AUTO_DATABASE_URL="${1#*=}" - if [[ -z "$AUTO_DATABASE_URL" ]]; then - echo "Error: --mongodb-connection requires a value." >&2 - echo "" - usage - exit 1 - fi - shift - ;; - --ssl-host-certs-path) - if [[ $# -lt 2 || "${2:-}" == -* ]]; then - echo "Error: --ssl-host-certs-path requires a value." >&2 - echo "" - usage - exit 1 - fi - AUTO_HOST_CERTS_PATH="$2" - shift 2 - ;; - --ssl-host-certs-path=*) - AUTO_HOST_CERTS_PATH="${1#*=}" - if [[ -z "$AUTO_HOST_CERTS_PATH" ]]; then - echo "Error: --ssl-host-certs-path requires a value." >&2 - echo "" - usage - exit 1 - fi - shift - ;; - --ssl-certs-file) - if [[ $# -lt 2 || "${2:-}" == -* ]]; then - echo "Error: --ssl-certs-file requires a value." >&2 - echo "" - usage - exit 1 - fi - AUTO_CERTS_FILENAME="$2" - shift 2 - ;; - --ssl-certs-file=*) - AUTO_CERTS_FILENAME="${1#*=}" - if [[ -z "$AUTO_CERTS_FILENAME" ]]; then - echo "Error: --ssl-certs-file requires a value." >&2 - echo "" - usage - exit 1 - fi - shift - ;; - --taxii-server) - AUTO_ENABLE_TAXII=true - shift - ;; - --) - shift - break - ;; - -*) - echo "Error: Unknown option: $1" >&2 - echo "" - usage - exit 1 - ;; - esac -done - # ATT&CK Workbench Deployment Setup Script # This script helps you quickly set up a custom ATT&CK Workbench instance # @@ -820,6 +687,146 @@ show_deployment_instructions() { echo "" } +# Display script usage information +usage() { + echo "Usage: $(basename "$0") [--accept-defaults] [--dev-mode] [--instance-name ] [-h | --help] [--mongodb-connection ] [--taxi-server]" + echo + echo "Generate Docker Compose configurations to deploy local workbench instances." + echo + echo "Options:" + echo " --accept-defaults Run non-interactively using default selections unless overriden by other options" + echo " --dev-mode Setup in developer mode (build from source)" + echo " --instance-name Name of the generated configuration" + echo " -h, --help Show this help and exit" + echo " --mongodb-connection MongoDB connection string" + echo " --taxi-server Deploy with the TAXII server" + echo " --ssl-host-certs-path Host certificates directory path (default \"./certs\")" + echo " --ssl-certs-file Certificates filename (default \"custom-certs.pem\")" +} + +#=============================================================================== +# ARGUMENT PARSING +#=============================================================================== + +# Parse optional CLI arguments +ACCEPT_DEFAULTS=false +AUTO_ENABLE_TAXII=false +AUTO_DEV_MODE=false +AUTO_INSTANCE_NAME="" +AUTO_DATABASE_URL="" +AUTO_HOST_CERTS_PATH="" +AUTO_CERTS_FILENAME="" +while [[ $# -gt 0 ]]; do + case "$1" in + --accept-defaults) + ACCEPT_DEFAULTS=true + shift + ;; + --dev-mode) + AUTO_DEV_MODE=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + --instance-name) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --instance-name requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_INSTANCE_NAME="$2" + shift 2 + ;; + --instance-name=*) + AUTO_INSTANCE_NAME="${1#*=}" + if [[ -z "$AUTO_INSTANCE_NAME" ]]; then + echo "Error: --instance-name requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; + --mongodb-connection) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --mongodb-connection requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_DATABASE_URL="$2" + shift 2 + ;; + --mongodb-connection=*) + AUTO_DATABASE_URL="${1#*=}" + if [[ -z "$AUTO_DATABASE_URL" ]]; then + echo "Error: --mongodb-connection requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; + --ssl-host-certs-path) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --ssl-host-certs-path requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_HOST_CERTS_PATH="$2" + shift 2 + ;; + --ssl-host-certs-path=*) + AUTO_HOST_CERTS_PATH="${1#*=}" + if [[ -z "$AUTO_HOST_CERTS_PATH" ]]; then + echo "Error: --ssl-host-certs-path requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; + --ssl-certs-file) + if [[ $# -lt 2 || "${2:-}" == -* ]]; then + echo "Error: --ssl-certs-file requires a value." >&2 + echo "" + usage + exit 1 + fi + AUTO_CERTS_FILENAME="$2" + shift 2 + ;; + --ssl-certs-file=*) + AUTO_CERTS_FILENAME="${1#*=}" + if [[ -z "$AUTO_CERTS_FILENAME" ]]; then + echo "Error: --ssl-certs-file requires a value." >&2 + echo "" + usage + exit 1 + fi + shift + ;; + --taxii-server) + AUTO_ENABLE_TAXII=true + shift + ;; + --) + shift + break + ;; + -*) + echo "Error: Unknown option: $1" >&2 + echo "" + usage + exit 1 + ;; + esac +done + #=============================================================================== # BANNER #=============================================================================== From 3defb3b542a0b07422cf65018042e5d05023cb8a Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 1 Dec 2025 16:21:49 -0600 Subject: [PATCH 41/47] fix: update nginx config settings to only focus on server configuration --- docker/example-setup/compose.yaml | 2 +- .../configs/frontend/nginx.api.conf | 70 ++++++++--------- .../configs/frontend/nginx.api.ssl.conf | 78 +++++++++---------- .../example-setup/configs/frontend/nginx.conf | 30 ++++--- .../configs/frontend/nginx.ssl.conf | 44 +++++------ 5 files changed, 108 insertions(+), 116 deletions(-) diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index 3881254..2ccfd79 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -8,7 +8,7 @@ services: - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:80" - "${ATTACKWB_FRONTEND_HTTPS_PORT:-443}:443" volumes: - - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.api.conf}:/etc/nginx/nginx.conf:ro" + - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.api.conf}:/etc/nginx/conf.d/default.conf:ro" - "${ATTACKWB_FRONTEND_CERTS_PATH:-./certs}:/etc/nginx/certs:ro" restart: unless-stopped logging: diff --git a/docker/example-setup/configs/frontend/nginx.api.conf b/docker/example-setup/configs/frontend/nginx.api.conf index 7144404..6789577 100644 --- a/docker/example-setup/configs/frontend/nginx.api.conf +++ b/docker/example-setup/configs/frontend/nginx.api.conf @@ -1,44 +1,42 @@ -http { - server { - listen 80; - http2 on; - server_name _; - - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; - - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - location / { - try_files $uri $uri/ /index.html; - } - - location /api { - client_max_body_size 50M; +server { + listen 80; + http2 on; + server_name _; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } - # Disable buffering for SSE streams - proxy_buffering off; - proxy_cache off; + location /api { + client_max_body_size 50M; - # Keep connection alive for long-running requests - proxy_read_timeout 600s; - proxy_connect_timeout 300s; - proxy_send_timeout 300s; + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; - # Required headers for SSE - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding on; + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; - # Disable compression for SSE - gzip off; + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; - proxy_pass http://rest-api:3000; - } + # Disable compression for SSE + gzip off; + proxy_pass http://rest-api:3000; } + } diff --git a/docker/example-setup/configs/frontend/nginx.api.ssl.conf b/docker/example-setup/configs/frontend/nginx.api.ssl.conf index 3a6e415..2c2bb87 100644 --- a/docker/example-setup/configs/frontend/nginx.api.ssl.conf +++ b/docker/example-setup/configs/frontend/nginx.api.ssl.conf @@ -1,53 +1,51 @@ -http { - server { - listen 80; - server_name _; - return 301 https://$host$request_uri; - } - - server { - listen 443 ssl default_server; - http2 on; - server_name _; +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} - ssl_certificate /etc/nginx/certs/server.pem; - ssl_certificate_key /etc/nginx/certs/server.key; +server { + listen 443 ssl default_server; + http2 on; + server_name _; - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; - location / { - try_files $uri $uri/ /index.html; - } + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; - location /api { - client_max_body_size 50M; + location / { + try_files $uri $uri/ /index.html; + } - # Disable buffering for SSE streams - proxy_buffering off; - proxy_cache off; + location /api { + client_max_body_size 50M; - # Keep connection alive for long-running requests - proxy_read_timeout 600s; - proxy_connect_timeout 300s; - proxy_send_timeout 300s; + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; - # Required headers for SSE - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding on; + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; - # Disable compression for SSE - gzip off; + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; - proxy_pass http://rest-api:3000; - } + # Disable compression for SSE + gzip off; + proxy_pass http://rest-api:3000; } + } diff --git a/docker/example-setup/configs/frontend/nginx.conf b/docker/example-setup/configs/frontend/nginx.conf index 5839940..6123cec 100644 --- a/docker/example-setup/configs/frontend/nginx.conf +++ b/docker/example-setup/configs/frontend/nginx.conf @@ -1,21 +1,19 @@ -http { - server { - listen 80; - http2 on; - server_name _; +server { + listen 80; + http2 on; + server_name _; - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - location / { - try_files $uri $uri/ /index.html; - } + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + location / { + try_files $uri $uri/ /index.html; } + } diff --git a/docker/example-setup/configs/frontend/nginx.ssl.conf b/docker/example-setup/configs/frontend/nginx.ssl.conf index 92e9e2a..d423ee4 100644 --- a/docker/example-setup/configs/frontend/nginx.ssl.conf +++ b/docker/example-setup/configs/frontend/nginx.ssl.conf @@ -1,30 +1,28 @@ -http { - server { - listen 80; - server_name _; - return 301 https://$host$request_uri; - } - - server { - listen 443 ssl default_server; - http2 on; - server_name _; +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} - ssl_certificate /etc/nginx/certs/server.pem; - ssl_certificate_key /etc/nginx/certs/server.key; +server { + listen 443 ssl default_server; + http2 on; + server_name _; - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; - location / { - try_files $uri $uri/ /index.html; - } + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + location / { + try_files $uri $uri/ /index.html; } + } From 2e4dd13c9802274d1d89ff88b3e9158fbccce61b Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Wed, 21 Jan 2026 13:04:26 -0600 Subject: [PATCH 42/47] chore: remove Docker Desktop reference --- docker/setup-workbench.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index a541cb9..c715ed8 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -901,7 +901,7 @@ fi # Check for Docker Compose (warn but don't fail) if ! docker compose version &> /dev/null 2>&1; then warning "Docker Compose is not available" - echo " Please install Docker Compose (usually included with Docker Desktop)" + echo " Please install Docker Compose" fi From a243fb960130d258c806594774b847d503a335f0 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Wed, 21 Jan 2026 13:04:26 -0600 Subject: [PATCH 43/47] chore: remove Docker Desktop reference --- docker/setup-workbench.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index a541cb9..c715ed8 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -901,7 +901,7 @@ fi # Check for Docker Compose (warn but don't fail) if ! docker compose version &> /dev/null 2>&1; then warning "Docker Compose is not available" - echo " Please install Docker Compose (usually included with Docker Desktop)" + echo " Please install Docker Compose" fi From 515ee69eeede8be5ad6d9de2b71550eac08611a0 Mon Sep 17 00:00:00 2001 From: Jared Ondricek <90368810+jondricek@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:11:18 -0500 Subject: [PATCH 44/47] fix: update docker setup to allow database backups to be modified by end users --- docker/example-setup/compose.yaml | 1 + docker/example-setup/template.env | 1 + docs/configuration.md | 7 ++++--- docs/database-backups.md | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index 2ccfd79..7cb0377 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -46,6 +46,7 @@ services: volumes: - workspace-data:/data/db - ./database-backup:/dump + - "${ATTACKWB_DB_BACKUP_PATH:-./database-backup}:/dump" restart: unless-stopped healthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] diff --git a/docker/example-setup/template.env b/docker/example-setup/template.env index e7ea52e..4b94d49 100644 --- a/docker/example-setup/template.env +++ b/docker/example-setup/template.env @@ -23,6 +23,7 @@ ATTACKWB_TAXII_VERSION=latest # Database #ATTACKWB_DB_PORT=27017 +#ATTACKWB_DB_BACKUP_PATH=./database-backup # TAXII Server #ATTACKWB_TAXII_HTTP_PORT=5002 diff --git a/docs/configuration.md b/docs/configuration.md index 59671ee..91b6e80 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -58,9 +58,10 @@ See `compose.certs.yaml` for details ### Database -| Variable | Default Value | Description | -|--------------------|---------------|--------------| -| `ATTACKWB_DB_PORT` | `27017` | MongoDB port | +| Variable | Default Value | Description | +|---------------------------|---------------------|--------------------------| +| `ATTACKWB_DB_PORT` | `27017` | MongoDB port | +| `ATTACKWB_DB_BACKUP_PATH` | `./database-backup` | MongoDB backup directory | ### TAXII Server diff --git a/docs/database-backups.md b/docs/database-backups.md index 197038c..923a195 100644 --- a/docs/database-backups.md +++ b/docs/database-backups.md @@ -1,8 +1,10 @@ # Database Backups +TODO: Clean up this documentation! Make it way easier to manage! + The MongoDB commands `mongodump` and `mongorestore` can be used to create the database backup files and to restore the database using those files. -The `compose.yaml` file maps the `database-backup/` directory on the host to the `/dump` directory +The `compose.yaml` file maps the `ATTACKWB_DB_BACKUP_PATH` directory (defaults to `database-backup/`) on the host to the `/dump` directory in the container in order to ease access to the backup files and to make sure those files exist even if the container is deleted. This directory is listed in the `.gitignore` file so the backup files will not be added to the git repo. From 658b1aaf1a142bf065200c836d13f92db7c73a67 Mon Sep 17 00:00:00 2001 From: Jared Ondricek <90368810+jondricek@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:12:15 -0500 Subject: [PATCH 45/47] fix: remove leftover volume line in Dockerfile --- docker/example-setup/compose.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index 7cb0377..9c29e00 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -45,7 +45,6 @@ services: - "127.0.0.1:${ATTACKWB_DB_PORT:-27017}:27017" volumes: - workspace-data:/data/db - - ./database-backup:/dump - "${ATTACKWB_DB_BACKUP_PATH:-./database-backup}:/dump" restart: unless-stopped healthcheck: From 26d14636ee7e04d334074f9fb720194b8ac7ad28 Mon Sep 17 00:00:00 2001 From: Jared Ondricek <90368810+jondricek@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:17:37 -0500 Subject: [PATCH 46/47] docs: first stab at cleaning up database backup/restore documentation --- docs/database-backups.md | 42 +++++----------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/docs/database-backups.md b/docs/database-backups.md index 923a195..3900dbf 100644 --- a/docs/database-backups.md +++ b/docs/database-backups.md @@ -4,7 +4,7 @@ TODO: Clean up this documentation! Make it way easier to manage! The MongoDB commands `mongodump` and `mongorestore` can be used to create the database backup files and to restore the database using those files. -The `compose.yaml` file maps the `ATTACKWB_DB_BACKUP_PATH` directory (defaults to `database-backup/`) on the host to the `/dump` directory +The `compose.yaml` file maps the `ATTACKWB_DB_BACKUP_PATH` directory (defaults to `./database-backup`) on the host to the `/dump` directory in the container in order to ease access to the backup files and to make sure those files exist even if the container is deleted. This directory is listed in the `.gitignore` file so the backup files will not be added to the git repo. @@ -14,11 +14,7 @@ To access the command line inside the container, run this command from the host: docker exec -it attack-workbench-database bash ``` -## Single Archive File - -These commands backup the data in a single compressed file. - -### Creating a Database Backup +## Creating a Database Backup Create the backup as a compressed archive file: @@ -27,11 +23,11 @@ Create the backup as a compressed archive file: mongodump --db attack-workspace --gzip --archive=dump/workspace.archive.gz ``` -This creates a file in `/dump` in the container (`database-backup/` on the host). +This creates a file in `/dump` in the container (`$ATTACKWB_DB_BACKUP_PATH` on the host). -### Restoring the Database from the Backup +## Restoring the Database from the Backup -The backup file must be in `database-backup/` on the host. +The backup file must be in `$ATTACKWB_DB_BACKUP_PATH` on the host. Restoring from the compressed archive file: @@ -41,31 +37,3 @@ mongorestore --drop --gzip --archive=dump/workspace.archive.gz ``` This drops the collections from the database, recreates the collections, loads the backed up documents into those collections, and rebuilds the indexes. - -## Multiple Files - -These commands backup the data in multiple files (a file for each collection and index). - -### Creating a Database Backup - -Create the backup files: - -```shell -# From inside the attack-workbench-database container -mongodump --db attack-workspace -``` - -This creates a set of files in `/dump/attack-workspace` in the container (`/database-backup/attack-workspace` on the host). - -### Restoring the Database from the Backup Files - -The backup files must be in `database-backup/attack-workspace` on the host. - -Restoring from the backup files: - -```shell -# From inside the attack-workbench-database container -mongorestore --drop dump/ -``` - -This drops the collections from the database, recreates the collections, loads the backed up documents into those collections, and rebuilds the indexes. From 612a2559aad2bd8b107068182189759fca9d2769 Mon Sep 17 00:00:00 2001 From: Jared Ondricek <90368810+jondricek@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:32:46 -0500 Subject: [PATCH 47/47] fix: add validation envionment variable for REST API config --- docker/example-setup/configs/rest-api/template.env | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/example-setup/configs/rest-api/template.env b/docker/example-setup/configs/rest-api/template.env index 8168731..42dfb3f 100644 --- a/docker/example-setup/configs/rest-api/template.env +++ b/docker/example-setup/configs/rest-api/template.env @@ -141,3 +141,7 @@ DATABASE_URL= # Useful when MongoDB or IdP uses a private CA # Default: empty #NODE_EXTRA_CA_CERTS= + +# Validation +# VALIDATE_WITH_ADM_SCHEMAS - Toggle request body validation with ATT&CK Data Model schemas +#VALIDATE_WITH_ADM_SCHEMAS=false