From 321553eabc40949cc56ca108ae4a1555c00d4d1b Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Thu, 6 Nov 2025 18:46:10 -0600 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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; } + }