From 457d712e35626525c26634510078b27641310ba3 Mon Sep 17 00:00:00 2001 From: Scot Lunsford Date: Wed, 20 May 2026 16:28:46 -0500 Subject: [PATCH 1/2] feat: add live reload development workflow --- docker/README.md | 18 ++++- docker/example-setup/compose.dev.yaml | 71 ++++++++++++++------ docker/setup-workbench.sh | 94 ++++++++++++++++++++------- 3 files changed, 140 insertions(+), 43 deletions(-) diff --git a/docker/README.md b/docker/README.md index 718dcc0..832dcb4 100644 --- a/docker/README.md +++ b/docker/README.md @@ -14,15 +14,29 @@ After running the script, deploy with: ```bash cd ../instances/your-instance-name -# deploy with docker compose +# Deploy with published images docker compose up -d -# or deploy in development mode +# Build a developer-mode stack without active file watching docker compose up -d --build + +# Build, run, and watch source changes in developer mode +docker compose up --watch --build + +# Or start watching after the developer-mode stack is already running +docker compose watch ``` Access Workbench at +Developer mode requires Docker Compose 2.22.0 or newer for Compose Watch and sibling component repositories next to this deployment repository: + +- `attack-workbench-frontend` +- `attack-workbench-rest-api` +- `attack-workbench-taxii-server` when TAXII is enabled + +In developer mode, Compose Watch syncs component source into named Docker volumes mounted at the application source paths. This avoids writing watched files directly into image layers, which is important on Docker Desktop environments that use Enhanced Container Isolation/rootless runtimes. The frontend runs Angular's dev server, the REST API runs its development watcher, and TAXII runs Nest's watch mode. Dependency manifest and build configuration changes such as `package.json`, `package-lock.json`, or Angular TypeScript config files rebuild the affected service image. + 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). diff --git a/docker/example-setup/compose.dev.yaml b/docker/example-setup/compose.dev.yaml index 75366ac..e4702fd 100644 --- a/docker/example-setup/compose.dev.yaml +++ b/docker/example-setup/compose.dev.yaml @@ -2,53 +2,86 @@ services: frontend: image: attack-workbench-frontend - build: ../../../attack-workbench-frontend + build: + context: ../../../attack-workbench-frontend + target: dev + volumes: !override + - frontend_src:/workspace/src develop: watch: # Sync source files for hot-reload - action: sync path: ../../../attack-workbench-frontend/src - target: /app/src - ignore: - - node_modules/ + target: /workspace/src + initial_sync: true + # Rebuild on Angular build configuration changes + - action: rebuild + path: ../../../attack-workbench-frontend/angular.json + - action: rebuild + path: ../../../attack-workbench-frontend/tsconfig.json + - action: rebuild + path: ../../../attack-workbench-frontend/tsconfig.app.json # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-frontend/package.json + - action: rebuild + path: ../../../attack-workbench-frontend/package-lock.json rest-api: image: attack-workbench-rest-api - build: ../../../attack-workbench-rest-api + build: + context: ../../../attack-workbench-rest-api + target: dev + volumes: !override + - rest_api_app:/usr/src/app/app + - rest_api_bin:/usr/src/app/bin + - rest_api_resources:/usr/src/app/resources 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 + initial_sync: true + # Sync server entrypoint and config files + - action: sync + path: ../../../attack-workbench-rest-api/bin + target: /usr/src/app/bin + initial_sync: true + # Sync generated runtime config file from this compose instance - action: sync+restart - path: ../../../attack-workbench-rest-api/resources - target: /usr/src/app/resources + path: ./configs/rest-api/rest-api-service-config.json + target: /usr/src/app/resources/rest-api-service-config.json + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-rest-api/package.json + - action: rebuild + path: ../../../attack-workbench-rest-api/package-lock.json taxii: image: attack-workbench-taxii-server - build: ../../../attack-workbench-taxii-server + build: + context: ../../../attack-workbench-taxii-server + target: development + volumes: + - taxii_src:/app/src 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 + path: ../../../attack-workbench-taxii-server/src + target: /app/src + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-taxii-server/package.json + - action: rebuild + path: ../../../attack-workbench-taxii-server/package-lock.json + +volumes: + frontend_src: + rest_api_app: + rest_api_bin: + rest_api_resources: + taxii_src: diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 9774459..30e9598 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -681,18 +681,30 @@ generate_frontend_override() { frontend: image: $REPO_FRONTEND - build: ../../../$REPO_FRONTEND + build: + context: ../../../$REPO_FRONTEND + target: dev + volumes: !override + - frontend_src:/workspace/src develop: watch: # Sync source files for hot-reload - action: sync path: ../../../$REPO_FRONTEND/src - target: /app/src - ignore: - - node_modules/ + target: /workspace/src + initial_sync: true + # Rebuild on Angular build configuration changes + - action: rebuild + path: ../../../$REPO_FRONTEND/angular.json + - action: rebuild + path: ../../../$REPO_FRONTEND/tsconfig.json + - action: rebuild + path: ../../../$REPO_FRONTEND/tsconfig.app.json # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_FRONTEND/package.json + - action: rebuild + path: ../../../$REPO_FRONTEND/package-lock.json EOF } @@ -702,16 +714,28 @@ generate_rest_api_dev_override() { rest-api: image: $REPO_REST_API - build: ../../../$REPO_REST_API + build: + context: ../../../$REPO_REST_API + target: dev EOF # Add custom cert volumes if enabled if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then cat << 'EOF' - volumes: + volumes: !override + - rest_api_app:/usr/src/app/app + - rest_api_bin:/usr/src/app/bin + - rest_api_resources:/usr/src/app/resources - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} +EOF + else + cat << 'EOF' + volumes: !override + - rest_api_app:/usr/src/app/app + - rest_api_bin:/usr/src/app/bin + - rest_api_resources:/usr/src/app/resources EOF fi @@ -723,15 +747,22 @@ EOF - action: sync path: ../../../$REPO_REST_API/app target: /usr/src/app/app - ignore: - - node_modules/ - # Restart on config changes + initial_sync: true + # Sync server entrypoint and config files + - action: sync + path: ../../../$REPO_REST_API/bin + target: /usr/src/app/bin + initial_sync: true + # Sync generated runtime config file from this compose instance - action: sync+restart - path: ../../../$REPO_REST_API/resources - target: /usr/src/app/resources + path: ./configs/rest-api/rest-api-service-config.json + target: /usr/src/app/resources/rest-api-service-config.json + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_REST_API/package.json + - action: rebuild + path: ../../../$REPO_REST_API/package-lock.json EOF } @@ -808,22 +839,23 @@ generate_taxii_override() { taxii: image: $REPO_TAXII - build: ../../../$REPO_TAXII + build: + context: ../../../$REPO_TAXII + target: development + volumes: + - taxii_src:/app/src 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 + path: ../../../$REPO_TAXII/src + target: /app/src + initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_TAXII/package.json + - action: rebuild + path: ../../../$REPO_TAXII/package-lock.json EOF } @@ -850,6 +882,21 @@ EOF if [[ $ENABLE_INSIGHTS =~ ^[Yy]$ ]]; then generate_insights_override >> "$override_file" fi + + cat >> "$override_file" << 'EOF' + +volumes: + frontend_src: + rest_api_app: + rest_api_bin: + rest_api_resources: +EOF + + if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then + cat >> "$override_file" << 'EOF' + taxii_src: +EOF + fi else # Production mode - only add rest-api if custom certs are enabled if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then @@ -895,7 +942,7 @@ show_configuration_summary() { echo " REST API: $config_root/configs/rest-api/.env" echo " REST API: $config_root/configs/rest-api/rest-api-service-config.json" if [[ $enable_taxii =~ ^[Yy]$ ]]; then - echo " TAXII: $config_root/configs/taxii/config/.env" + echo " TAXII: $config_root/configs/taxii/config/dev.env" fi if [[ $enable_insights =~ ^[Yy]$ ]]; then echo " Grafana: $config_root/configs/grafana/.env" @@ -936,9 +983,12 @@ show_deployment_instructions() { info "To deploy your instance:" echo " cd $instance_dir" if [[ $dev_mode =~ ^[Yy]$ ]]; then + echo " docker compose up --watch --build" + echo "" + info "For detached developer mode without active watching:" echo " docker compose up -d --build" echo "" - info "For hot-reloading in developer mode, use watch:" + info "To start watching an already-running developer-mode stack:" echo " docker compose watch" else echo " docker compose up -d" From 504b295de8e46403862865d6add82f96a6615ac7 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 12 Oct 2026 07:46:07 -0500 Subject: [PATCH 2/2] feat: update development Compose and service configuration to work with reloadable containers --- .github/workflows/development-compose.yml | 17 ++ docker/README.md | 36 +-- docker/example-setup/compose.dev.yaml | 100 +++++--- docker/example-setup/compose.yaml | 3 + .../configs/frontend/config.json | 14 ++ .../configs/frontend/nginx.api.conf | 4 + .../configs/frontend/nginx.api.ssl.conf | 4 + .../example-setup/configs/frontend/nginx.conf | 4 + .../configs/frontend/nginx.ssl.conf | 4 + .../configs/frontend/template.env | 40 ++++ .../configs/rest-api/template.env | 54 ++++- .../configs/taxii/config/template.env | 123 ++++++---- docker/example-setup/template.env | 2 + docker/setup-workbench.sh | 220 ++++++++++++------ docs/DEVELOPMENT.md | 11 + docs/configuration.md | 112 ++++++++- docs/deployment.md | 40 +--- docs/local-development.md | 57 +++++ k8s/base/configmap-nginx.yaml | 4 + 19 files changed, 626 insertions(+), 223 deletions(-) create mode 100644 .github/workflows/development-compose.yml create mode 100644 docker/example-setup/configs/frontend/config.json create mode 100644 docker/example-setup/configs/frontend/template.env create mode 100644 docs/local-development.md diff --git a/.github/workflows/development-compose.yml b/.github/workflows/development-compose.yml new file mode 100644 index 0000000..e5bb7c1 --- /dev/null +++ b/.github/workflows/development-compose.yml @@ -0,0 +1,17 @@ +name: Development Compose example +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check generated development example + run: | + bash -n docker/setup-workbench.sh + bash docker/setup-workbench.sh --print-dev-compose > /tmp/compose.dev.yaml + diff -u docker/example-setup/compose.dev.yaml /tmp/compose.dev.yaml diff --git a/docker/README.md b/docker/README.md index 832dcb4..8101913 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,44 +1,26 @@ # Docker Compose Setup -Use the interactive setup script `setup-workbench.sh` to quickly create and deploy a custom Workbench instance: +Use the interactive setup script to create a 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: +After setup, start the instance: ```bash cd ../instances/your-instance-name - -# Deploy with published images docker compose up -d - -# Build a developer-mode stack without active file watching -docker compose up -d --build - -# Build, run, and watch source changes in developer mode -docker compose up --watch --build - -# Or start watching after the developer-mode stack is already running -docker compose watch ``` -Access Workbench at - -Developer mode requires Docker Compose 2.22.0 or newer for Compose Watch and sibling component repositories next to this deployment repository: - -- `attack-workbench-frontend` -- `attack-workbench-rest-api` -- `attack-workbench-taxii-server` when TAXII is enabled - -In developer mode, Compose Watch syncs component source into named Docker volumes mounted at the application source paths. This avoids writing watched files directly into image layers, which is important on Docker Desktop environments that use Enhanced Container Isolation/rootless runtimes. The frontend runs Angular's dev server, the REST API runs its development watcher, and TAXII runs Nest's watch mode. Dependency manifest and build configuration changes such as `package.json`, `package-lock.json`, or Angular TypeScript config files rebuild the affected service image. - -Full variable descriptions and examples are available in [docs/configuration](docs/configuration.md). +Open , or the HTTP port selected during setup. -For source builds or TAXII setup, see [docs/deployment](docs/deployment.md). +For live development, select developer mode during setup, then run +`docker compose up --watch --build`. See [local development](../docs/local-development.md) +for prerequisites, reload behavior, and switching branches. -For information on how to backup or restore the mongo database, see [docs/database-backups](docs/database-backups.md). +- [Configuration](../docs/configuration.md): ports, mounted files, and service settings. +- [Deployment](../docs/deployment.md): deployment choices and optional services. +- [Database backups](../docs/database-backups.md): backup and restore procedures. diff --git a/docker/example-setup/compose.dev.yaml b/docker/example-setup/compose.dev.yaml index e4702fd..d78ec66 100644 --- a/docker/example-setup/compose.dev.yaml +++ b/docker/example-setup/compose.dev.yaml @@ -1,3 +1,6 @@ +# This file was generated by setup-workbench.sh +# It will be automatically merged with compose.yaml when running docker compose commands + services: frontend: @@ -5,18 +8,37 @@ services: build: context: ../../../attack-workbench-frontend target: dev + environment: + - WORKBENCH_FRONTEND_API_PROXY_TARGET=http://rest-api:3000 + ports: !override + - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:80" volumes: !override - - frontend_src:/workspace/src + - "${ATTACKWB_FRONTEND_CONFIG_DIR:-./configs/frontend}:/etc/workbench:ro" + - type: bind + source: ../../../attack-workbench-frontend/src + target: /workspace/src + read_only: true + bind: + create_host_path: false develop: watch: - # Sync source files for hot-reload - - action: sync - path: ../../../attack-workbench-frontend/src - target: /workspace/src - initial_sync: true + # Angular polls source; restart it to discover newly added static assets. + - action: restart + path: ../../../attack-workbench-frontend/src/assets + - action: restart + # Watch the directory so adding/removing optional config.json is detected. + path: ${ATTACKWB_FRONTEND_CONFIG_DIR:-./configs/frontend} + ignore: + - .env + - template.env + - '*.conf' # Rebuild on Angular build configuration changes - action: rebuild path: ../../../attack-workbench-frontend/angular.json + - action: rebuild + path: ../../../attack-workbench-frontend/proxy.conf.cjs + - action: rebuild + path: ../../../attack-workbench-frontend/scripts - action: rebuild path: ../../../attack-workbench-frontend/tsconfig.json - action: rebuild @@ -32,27 +54,30 @@ services: build: context: ../../../attack-workbench-rest-api target: dev - volumes: !override - - rest_api_app:/usr/src/app/app - - rest_api_bin:/usr/src/app/bin - - rest_api_resources:/usr/src/app/resources + volumes: + - type: bind + source: ../../../attack-workbench-rest-api/app + target: /usr/src/app/app + read_only: true + bind: + create_host_path: false + - type: bind + source: ../../../attack-workbench-rest-api/bin + target: /usr/src/app/bin + read_only: true + bind: + create_host_path: false develop: watch: - # Sync app source files - - action: sync + # Restart the API to load edits from its source mounts + - action: restart path: ../../../attack-workbench-rest-api/app - target: /usr/src/app/app - initial_sync: true - # Sync server entrypoint and config files - - action: sync + # Restart after server entrypoint and config changes + - action: restart path: ../../../attack-workbench-rest-api/bin - target: /usr/src/app/bin - initial_sync: true - # Sync generated runtime config file from this compose instance - - action: sync+restart - path: ./configs/rest-api/rest-api-service-config.json - target: /usr/src/app/resources/rest-api-service-config.json - initial_sync: true + # The base compose file mounts this config before the API starts. + - action: restart + path: ${ATTACKWB_RESTAPI_CONFIG_FILE:-./configs/rest-api/rest-api-service-config.json} # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-rest-api/package.json @@ -65,23 +90,28 @@ services: context: ../../../attack-workbench-taxii-server target: development volumes: - - taxii_src:/app/src + - type: bind + source: ../../../attack-workbench-taxii-server/src + target: /app/src + read_only: true + bind: + create_host_path: false develop: watch: - # Sync source files - - action: sync + # Rebuild TypeScript and restart both TAXII server and collector + - action: restart path: ../../../attack-workbench-taxii-server/src - target: /app/src - initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-taxii-server/package.json - action: rebuild path: ../../../attack-workbench-taxii-server/package-lock.json - -volumes: - frontend_src: - rest_api_app: - rest_api_bin: - rest_api_resources: - taxii_src: + # Rebuild when Nest or TypeScript build configuration changes + - action: rebuild + path: ../../../attack-workbench-taxii-server/nest-cli.json + - action: rebuild + path: ../../../attack-workbench-taxii-server/ecosystem.config.js + - action: rebuild + path: ../../../attack-workbench-taxii-server/tsconfig.json + - action: rebuild + path: ../../../attack-workbench-taxii-server/tsconfig.build.json diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml index 71a4af2..f693ad7 100644 --- a/docker/example-setup/compose.yaml +++ b/docker/example-setup/compose.yaml @@ -10,6 +10,9 @@ services: volumes: - "${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" + - "${ATTACKWB_FRONTEND_CONFIG_DIR:-./configs/frontend}:/etc/workbench:ro" + env_file: + - "${ATTACKWB_FRONTEND_ENV_FILE:-./configs/frontend/.env}" restart: unless-stopped logging: driver: "json-file" diff --git a/docker/example-setup/configs/frontend/config.json b/docker/example-setup/configs/frontend/config.json new file mode 100644 index 0000000..348765a --- /dev/null +++ b/docker/example-setup/configs/frontend/config.json @@ -0,0 +1,14 @@ +{ + "apiUrl": "api", + "attackWebsiteEnabled": false, + "attackWebsiteUrl": "https://attack.mitre.org", + "collectionManagerEnabled": true, + "recommendedIndexes": [ + { + "name": "MITRE ATT&CK", + "url": "https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/index.json" + } + ], + "logLevel": "ERROR", + "defaultLandingPage": "" +} diff --git a/docker/example-setup/configs/frontend/nginx.api.conf b/docker/example-setup/configs/frontend/nginx.api.conf index 6789577..23eb26a 100644 --- a/docker/example-setup/configs/frontend/nginx.api.conf +++ b/docker/example-setup/configs/frontend/nginx.api.conf @@ -12,6 +12,10 @@ server { 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 = /assets/config.json { + add_header Cache-Control "no-store" always; + } + location / { try_files $uri $uri/ /index.html; } diff --git a/docker/example-setup/configs/frontend/nginx.api.ssl.conf b/docker/example-setup/configs/frontend/nginx.api.ssl.conf index 2c2bb87..23384ea 100644 --- a/docker/example-setup/configs/frontend/nginx.api.ssl.conf +++ b/docker/example-setup/configs/frontend/nginx.api.ssl.conf @@ -21,6 +21,10 @@ server { 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 = /assets/config.json { + add_header Cache-Control "no-store" always; + } + location / { try_files $uri $uri/ /index.html; } diff --git a/docker/example-setup/configs/frontend/nginx.conf b/docker/example-setup/configs/frontend/nginx.conf index 6123cec..8af6c2b 100644 --- a/docker/example-setup/configs/frontend/nginx.conf +++ b/docker/example-setup/configs/frontend/nginx.conf @@ -12,6 +12,10 @@ server { 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 = /assets/config.json { + add_header Cache-Control "no-store" always; + } + 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 d423ee4..e718b0c 100644 --- a/docker/example-setup/configs/frontend/nginx.ssl.conf +++ b/docker/example-setup/configs/frontend/nginx.ssl.conf @@ -21,6 +21,10 @@ server { 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 = /assets/config.json { + add_header Cache-Control "no-store" always; + } + location / { try_files $uri $uri/ /index.html; } diff --git a/docker/example-setup/configs/frontend/template.env b/docker/example-setup/configs/frontend/template.env new file mode 100644 index 0000000..0aa71df --- /dev/null +++ b/docker/example-setup/configs/frontend/template.env @@ -0,0 +1,40 @@ +# Template-Version: 1.0 +# Template-Updated: 2026-10-11 +# Template-Source: attack-workbench-frontend/template.env +# +# Changelog +# --------- +# - 1.0 (2026-10-11): Introduce template metadata + +# Optional frontend settings. Copy to .env and uncomment the values you need. +# Native commands load .env automatically; existing process variables take priority. +# See docs/admin/configuration.md. All public application settings reach the browser. + +# Public application settings (production website/log defaults differ; see below). +# WORKBENCH_FRONTEND_API_URL=api +# WORKBENCH_FRONTEND_ATTACK_WEBSITE_ENABLED=false +# Development default: http://localhost:8000; production: https://attack.mitre.org +# WORKBENCH_FRONTEND_ATTACK_WEBSITE_URL=https://attack.mitre.org +# WORKBENCH_FRONTEND_COLLECTION_MANAGER_ENABLED=true +# WORKBENCH_FRONTEND_RECOMMENDED_INDEXES='[{"name":"MITRE ATT&CK","url":"https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/index.json"}]' +# Use [] to remove recommended indexes. +# Development default: INFO; production: ERROR +# WORKBENCH_FRONTEND_LOG_LEVEL=INFO +# WORKBENCH_FRONTEND_DEFAULT_LANDING_PAGE= + +# Optional public JSON input. Native default: src/assets/config.json; container: /etc/workbench/config.json. +# WORKBENCH_FRONTEND_CONFIG_FILE=/path/to/config.json + +# Development proxy upstream origin, without an /api suffix. +# WORKBENCH_FRONTEND_API_PROXY_TARGET=http://127.0.0.1:3000 + +# Build metadata: package version, Git HEAD, and current UTC time by default. +# Production runtime settings do not change this bundled build identity. +# WORKBENCH_FRONTEND_APP_VERSION=3.2.0 +# WORKBENCH_FRONTEND_GIT_COMMIT=abc123 +# WORKBENCH_FRONTEND_BUILD_DATE=2026-10-10T12:00:00Z +# Also export generated build metadata (path relative to the command's cwd). +# WORKBENCH_FRONTEND_BUILD_INFO_OUTPUT_PATH=dist/build-info.json + +# Optional real-server test endpoint (Node tests have no Angular proxy). +# WORKBENCH_FRONTEND_TEST_API_URL=http://localhost:3000/api diff --git a/docker/example-setup/configs/rest-api/template.env b/docker/example-setup/configs/rest-api/template.env index f0ddd50..4251263 100644 --- a/docker/example-setup/configs/rest-api/template.env +++ b/docker/example-setup/configs/rest-api/template.env @@ -1,3 +1,11 @@ +# Template-Version: 1.0 +# Template-Updated: 2026-10-11 +# Template-Source: attack-workbench-rest-api/template.env +# +# Changelog +# --------- +# - 1.0 (2026-10-11): Introduce template metadata + # Attack Workbench REST API - Environment Configuration Template # Guidance: # - Booleans: use true or false @@ -62,7 +70,7 @@ DATABASE_URL= # Default: empty (disabled) #JSON_CONFIG_PATH= -# ALLOWED_VALUES_PATH (string) - Path to allowed values configuration file +# ALLOWED_VALUES_PATH (string) - Seed file read only when Allowed Values configuration is first initialized # Default: ./app/config/allowed-values.json #ALLOWED_VALUES_PATH=./app/config/allowed-values.json @@ -70,14 +78,48 @@ DATABASE_URL= # Default: ./app/lib/default-static-marking-definitions/ #WB_REST_STATIC_MARKING_DEFS_PATH=./app/lib/default-static-marking-definitions/ +# WB_REST_STATIC_BYPASS_RULES_PATH (string) - Default validation bypass rules loaded at startup +# Default: ./app/lib/default-bypass-rules.json +#WB_REST_STATIC_BYPASS_RULES_PATH=./app/lib/default-bypass-rules.json + # 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 +# SYNC_COLLECTION_INDEXES_CRON (string) - Cron schedule for polling collection indexes (process timezone) +# Default: * * * * * (every minute); disabled when ENABLE_SCHEDULER=false +#SYNC_COLLECTION_INDEXES_CRON=* * * * * + +# CHECK_WIP_ATTACK_IDS_CRON (string) - Cron setting for WIP ATT&CK-ID checks (currently unused by scheduler) +# Default: 0 * * * * (hourly); no current task consumes this setting +#CHECK_WIP_ATTACK_IDS_CRON=0 * * * * + +# VIRTUAL_TRACK_SCHEDULES_CRON (string) - Reconcile virtual-track schedules +# Discovers new cron schedules, recovers due dates, and retries failed runs. +# Standard 5-field cron syntax, interpreted in UTC. +# Default: * * * * * (every minute) +#VIRTUAL_TRACK_SCHEDULES_CRON=* * * * * + +# Validation +# VALIDATE_WITH_ADM_SCHEMAS (bool) - Validate POST/PUT bodies against the ATT&CK Data Model +# When true, requests that fail ADM validation are rejected before persistence. +# Default: false +#VALIDATE_WITH_ADM_SCHEMAS=false + +# VALIDATE_OBJECTS_CRON (string) - Cron pattern for the scheduled re-validation task +# Re-validates every SDO/SRO against the current ADM and refreshes workspace.validation. +# Standard 5-field cron syntax (minute hour day-of-month month day-of-week). +# Skipped entirely when ENABLE_SCHEDULER=false. +# Default: 0 3 * * * (daily at 3:00 AM) +#VALIDATE_OBJECTS_CRON=0 3 * * * + +# ADM_LOG_LEVEL (enum) - Verbosity of the @mitre-attack/attack-data-model library's logger +# Read by the ADM library directly; independent of LOG_LEVEL and not configurable via JSON. +# Options: debug, info, warn, error, silent (inclusive — info enables info+warn+error) +# Set to error or silent to suppress per-relationship deprecation warnings during scheduler runs. +# Default: warn +#ADM_LOG_LEVEL=warn # Session # SESSION_SECRET (string) - Secret to sign session cookies. @@ -141,7 +183,3 @@ DATABASE_URL= # Useful when MongoDB or IdP uses a private CA # Default: empty #NODE_EXTRA_CA_CERTS= - -# Validation -# VALIDATE_WITH_ADM_SCHEMAS - Toggle request body validation with ATT&CK Data Model schemas -#VALIDATE_WITH_ADM_SCHEMAS=false diff --git a/docker/example-setup/configs/taxii/config/template.env b/docker/example-setup/configs/taxii/config/template.env index 286e9da..366e4b0 100644 --- a/docker/example-setup/configs/taxii/config/template.env +++ b/docker/example-setup/configs/taxii/config/template.env @@ -1,46 +1,61 @@ -# ******************************************************************************************************************** -# ***** ENVIRONMENT ************************************************************************************************** -# ******************************************************************************************************************** +# Template-Version: 1.0 +# Template-Updated: 2026-10-11 +# Template-Source: attack-workbench-taxii-server/config/template.env +# +# Changelog +# --------- +# - 1.0 (2026-10-11): Document server settings and PEM helper inputs + +# ********************************************************** +# ***** ENVIRONMENT **************************************** +# ********************************************************** +# Select config/.env via the shell or container environment before startup. TAXII_ENV=dev - - -# ******************************************************************************************************************** -# ***** SERVER SETTINGS ********************************************************************************************** -# ******************************************************************************************************************** +# ********************************************************** +# ***** SERVER SETTINGS ************************************ +# ********************************************************** +# Interface to bind (default: 0.0.0.0). TAXII_APP_ADDRESS=0.0.0.0 +# HTTP port (default: 8000). TAXII_APP_PORT=8000 +# HTTPS port when enabled (default: 8443). +TAXII_APP_PORT_HTTPS=8443 +# Enable the HTTPS listener (default: false). TAXII_HTTPS_ENABLED=false +# Base64-encoded PEM private key; required for HTTPS. TAXII_SSL_PRIVATE_KEY= +# Base64-encoded PEM certificate; required for HTTPS. TAXII_SSL_PUBLIC_KEY= +# Allow browser cross-origin requests (default: false). TAXII_CORS_ENABLED=false +# JSON request-body size limit passed to Express (runtime default: 1000). TAXII_MAX_CONTENT_LENGTH=0 - - -# ******************************************************************************************************************** -# ***** NGINX SSL/TLS CERTIFICATE AUTO REG/RENEW ********************************************************************* -# ******************************************************************************************************************** +# ********************************************************** +# ***** NGINX SSL/TLS CERTIFICATE AUTO REG/RENEW *********** +# ********************************************************** CERTBOT_LE_FQDN=taxii.example.com -CERTBOT_LE_EMAIL=noreply@example.com +CERTBOT_LE_EMAIL=contact@example.com CERTBOT_LE_ACME_SERVER=https://acme-v02.api.letsencrypt.org/directory CERTBOT_LE_RSA_KEY_SIZE=4096 - - -# ******************************************************************************************************************** -# ***** CONTACT INFORMATION ****************************************************************************************** -# ******************************************************************************************************************** -TAXII_API_ROOT_PATH=api +# ********************************************************** +# ***** CONTACT INFORMATION ******************************** +# ********************************************************** +# API route prefix, also advertised by discovery (runtime default: /api/v21). +TAXII_API_ROOT_PATH=/api +# Title advertised in discovery and API-root information. TAXII_API_ROOT_TITLE=ATT&CK Workbench TAXII 2.1 Server +# Description advertised in discovery and API-root information. TAXII_API_ROOT_DESCRIPTION=Provides access to the latest version of ATT&CK data through a TAXII 2.1 compliant REST API -TAXII_CONTACT=attack@mitre.org - +# Contact email loaded by configuration (default: no-reply@your-company.tld). +TAXII_CONTACT_EMAIL=contact@example.com - -# ******************************************************************************************************************** -# ***** CACHE SETTINGS *********************************************************************************************** -# ******************************************************************************************************************** +# ********************************************************** +# ***** CACHE SETTINGS ************************************* +# ********************************************************** +# Legacy entries: current server source does not read TAXII_CACHE_* variables. TAXII_CACHE_TYPE=memcached TAXII_CACHE_HOST=attack-workbench-taxii-cache TAXII_CACHE_PORT=11211 @@ -49,32 +64,52 @@ TAXII_CACHE_MAX_ITEM_SIZE=50m TAXII_CACHE_MEM_SIZE=4096 TAXII_CACHE_RECONNECT=true - - -# ******************************************************************************************************************** -# ***** STIX/WORKBENCH SETTINGS ************************************************************************************** -# ******************************************************************************************************************** -TAXII_STIX_SRC_URL=http://rest-api:3000 +# ********************************************************** +# ***** STIX/WORKBENCH SETTINGS **************************** +# ********************************************************** +# Determines where STIX data is sourced from. Supported values: +# workbench - hydrate from a running ATT&CK Workbench REST API instance (default) +# mitre-attack - hydrate from the official MITRE ATT&CK releases on GitHub (mitre-attack/attack-stix-data) TAXII_STIX_DATA_SRC=workbench +# Workbench API origin, without /api; used by the workbench data provider. +TAXII_STIX_SRC_URL=http://rest-api:3000 +# Base64 user:API-key value for Workbench Basic service authentication. TAXII_WORKBENCH_AUTH_HEADER=dGF4aWktc2VydmVyOnNlY3JldC1zcXVpcnJlbA== - - - -# ******************************************************************************************************************** -# ***** DATABASE SETTINGS ******************************************************************************************** -# ******************************************************************************************************************** +# Only used when TAXII_STIX_DATA_SRC=mitre-attack. Defaults to the raw GitHub content URL for +# mitre-attack/attack-stix-data. Override to point at a mirror of the repository. +#TAXII_MITRE_ATTACK_DATA_URL=https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master + +# ********************************************************** +# ***** DATABASE SETTINGS ********************************** +# ********************************************************** +# MongoDB connection URI for the TAXII data store. TAXII_MONGO_URI=mongodb://mongodb:27017/taxii +# Hydrate the selected data source at collector startup (runtime default: false). TAXII_HYDRATE_ON_BOOT=true - - -# ******************************************************************************************************************** -# ***** LOGGING SETTINGS ********************************************************************************************* -# ******************************************************************************************************************** +# ********************************************************** +# ***** LOGGING SETTINGS *********************************** +# ********************************************************** +# Logging verbosity; example: verbose. The template overrides the runtime default. TAXII_LOG_LEVEL=verbose +# Enable dated taxii_server_*.log output with true (default: false). TAXII_LOG_TO_FILE= +# Optional HTTP log receiver host; unset disables this transport. TAXII_LOG_TO_HTTP_HOST= +# HTTP log receiver port (1-65535). TAXII_LOG_TO_HTTP_PORT= +# HTTP log receiver path, such as /logs. TAXII_LOG_TO_HTTP_PATH= +# Optional Slack webhook URL; unset disables this transport. TAXII_LOG_TO_SLACK_URL= -TAXII_LOG_TO_SENTRY_DSN= \ No newline at end of file +# Optional Sentry DSN; unset disables this transport. +TAXII_LOG_TO_SENTRY_DSN= + +# ********************************************************** +# ***** PEM ENCODING HELPER ********************************* +# ********************************************************** +# Only scripts/encodePem.js reads these paths; the server does not. +# Export them before running the helper, or use Node's --env-file option. +# The helper prints encoded values for TAXII_SSL_PRIVATE_KEY/PUBLIC_KEY. +#TAXII_PATH_TO_PRIVATE_KEY=/path/to/private-key.pem +#TAXII_PATH_TO_PUBLIC_KEY=/path/to/public-certificate.pem diff --git a/docker/example-setup/template.env b/docker/example-setup/template.env index e69a0ac..34b0c8f 100644 --- a/docker/example-setup/template.env +++ b/docker/example-setup/template.env @@ -5,6 +5,8 @@ ATTACKWB_GRAFANA_VERSION=latest ATTACKWB_TAXII_VERSION=latest # Frontend +#ATTACKWB_FRONTEND_ENV_FILE=./configs/frontend/.env +#ATTACKWB_FRONTEND_CONFIG_DIR=./configs/frontend #ATTACKWB_FRONTEND_HTTP_PORT=80 #ATTACKWB_FRONTEND_HTTPS_PORT=443 #ATTACKWB_FRONTEND_NGINX_CONFIG_FILE=./configs/frontend/nginx.api.conf diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh index 30e9598..f323195 100755 --- a/docker/setup-workbench.sh +++ b/docker/setup-workbench.sh @@ -430,6 +430,12 @@ setup_environment_files() { success "Created $INSTANCE_DIR/.env" fi + # Frontend .env file + if [[ -f "$INSTANCE_DIR/configs/frontend/template.env" ]]; then + mv "$INSTANCE_DIR/configs/frontend/template.env" "$INSTANCE_DIR/configs/frontend/.env" + success "Created $INSTANCE_DIR/configs/frontend/.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" @@ -462,7 +468,7 @@ 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 "" } @@ -472,14 +478,14 @@ sync_container_mount_files() { local host_mount_dir="$2" local env_file="$source_instance_dir/.env" + mkdir -p "$host_mount_dir/configs" "$host_mount_dir/certs" "$host_mount_dir/database-backup" + if [[ "$host_mount_dir" == "$source_instance_dir" ]]; then return fi info "Syncing container-mounted files to: $host_mount_dir" - mkdir -p "$host_mount_dir/configs" "$host_mount_dir/certs" "$host_mount_dir/database-backup" - if [[ -d "$source_instance_dir/configs" ]]; then cp -R "$source_instance_dir/configs/." "$host_mount_dir/configs/" fi @@ -505,6 +511,8 @@ sync_container_mount_files() { fi update_env_file "$env_file" "ATTACKWB_FRONTEND_NGINX_CONFIG_FILE" "$host_mount_dir/configs/frontend/nginx.api.conf" + update_env_file "$env_file" "ATTACKWB_FRONTEND_ENV_FILE" "$host_mount_dir/configs/frontend/.env" + update_env_file "$env_file" "ATTACKWB_FRONTEND_CONFIG_DIR" "$host_mount_dir/configs/frontend" update_env_file "$env_file" "ATTACKWB_FRONTEND_CERTS_PATH" "$host_mount_dir/certs" update_env_file "$env_file" "ATTACKWB_RESTAPI_CONFIG_FILE" "$host_mount_dir/configs/rest-api/rest-api-service-config.json" update_env_file "$env_file" "ATTACKWB_RESTAPI_ENV_FILE" "$host_mount_dir/configs/rest-api/.env" @@ -684,18 +692,37 @@ generate_frontend_override() { build: context: ../../../$REPO_FRONTEND target: dev + environment: + - WORKBENCH_FRONTEND_API_PROXY_TARGET=http://rest-api:3000 + ports: !override + - "\${ATTACKWB_FRONTEND_HTTP_PORT:-80}:80" volumes: !override - - frontend_src:/workspace/src + - "\${ATTACKWB_FRONTEND_CONFIG_DIR:-./configs/frontend}:/etc/workbench:ro" + - type: bind + source: ../../../$REPO_FRONTEND/src + target: /workspace/src + read_only: true + bind: + create_host_path: false develop: watch: - # Sync source files for hot-reload - - action: sync - path: ../../../$REPO_FRONTEND/src - target: /workspace/src - initial_sync: true + # Angular polls source; restart it to discover newly added static assets. + - action: restart + path: ../../../$REPO_FRONTEND/src/assets + - action: restart + # Watch the directory so adding/removing optional config.json is detected. + path: \${ATTACKWB_FRONTEND_CONFIG_DIR:-./configs/frontend} + ignore: + - .env + - template.env + - '*.conf' # Rebuild on Angular build configuration changes - action: rebuild path: ../../../$REPO_FRONTEND/angular.json + - action: rebuild + path: ../../../$REPO_FRONTEND/proxy.conf.cjs + - action: rebuild + path: ../../../$REPO_FRONTEND/scripts - action: rebuild path: ../../../$REPO_FRONTEND/tsconfig.json - action: rebuild @@ -719,23 +746,27 @@ generate_rest_api_dev_override() { target: dev EOF - # Add custom cert volumes if enabled + cat << EOF + volumes: + - type: bind + source: ../../../$REPO_REST_API/app + target: /usr/src/app/app + read_only: true + bind: + create_host_path: false + - type: bind + source: ../../../$REPO_REST_API/bin + target: /usr/src/app/bin + read_only: true + bind: + create_host_path: false +EOF + if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then cat << 'EOF' - volumes: !override - - rest_api_app:/usr/src/app/app - - rest_api_bin:/usr/src/app/bin - - rest_api_resources:/usr/src/app/resources - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} -EOF - else - cat << 'EOF' - volumes: !override - - rest_api_app:/usr/src/app/app - - rest_api_bin:/usr/src/app/bin - - rest_api_resources:/usr/src/app/resources EOF fi @@ -743,21 +774,15 @@ EOF cat << EOF develop: watch: - # Sync app source files - - action: sync + # Restart the API to load edits from its source mounts + - action: restart path: ../../../$REPO_REST_API/app - target: /usr/src/app/app - initial_sync: true - # Sync server entrypoint and config files - - action: sync + # Restart after server entrypoint and config changes + - action: restart path: ../../../$REPO_REST_API/bin - target: /usr/src/app/bin - initial_sync: true - # Sync generated runtime config file from this compose instance - - action: sync+restart - path: ./configs/rest-api/rest-api-service-config.json - target: /usr/src/app/resources/rest-api-service-config.json - initial_sync: true + # The base compose file mounts this config before the API starts. + - action: restart + path: \${ATTACKWB_RESTAPI_CONFIG_FILE:-./configs/rest-api/rest-api-service-config.json} # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_REST_API/package.json @@ -843,28 +868,39 @@ generate_taxii_override() { context: ../../../$REPO_TAXII target: development volumes: - - taxii_src:/app/src + - type: bind + source: ../../../$REPO_TAXII/src + target: /app/src + read_only: true + bind: + create_host_path: false develop: watch: - # Sync source files - - action: sync + # Rebuild TypeScript and restart both TAXII server and collector + - action: restart path: ../../../$REPO_TAXII/src - target: /app/src - initial_sync: true # Rebuild on package.json changes - action: rebuild path: ../../../$REPO_TAXII/package.json - action: rebuild path: ../../../$REPO_TAXII/package-lock.json + # Rebuild when Nest or TypeScript build configuration changes + - action: rebuild + path: ../../../$REPO_TAXII/nest-cli.json + - action: rebuild + path: ../../../$REPO_TAXII/ecosystem.config.js + - action: rebuild + path: ../../../$REPO_TAXII/tsconfig.json + - action: rebuild + path: ../../../$REPO_TAXII/tsconfig.build.json EOF } # Generate the complete compose.override.yaml file -generate_compose_override() { - local override_file="$1" +render_compose_override() { # Write header - cat > "$override_file" << 'EOF' + cat << 'EOF' # This file was generated by setup-workbench.sh # It will be automatically merged with compose.yaml when running docker compose commands @@ -873,42 +909,30 @@ EOF # Add service configurations based on mode if [[ $DEV_MODE =~ ^[Yy]$ ]]; then - generate_frontend_override >> "$override_file" - generate_rest_api_dev_override >> "$override_file" + generate_frontend_override + generate_rest_api_dev_override if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then - generate_taxii_override >> "$override_file" + generate_taxii_override fi if [[ $ENABLE_INSIGHTS =~ ^[Yy]$ ]]; then - generate_insights_override >> "$override_file" + generate_insights_override fi - cat >> "$override_file" << 'EOF' - -volumes: - frontend_src: - rest_api_app: - rest_api_bin: - rest_api_resources: -EOF - - if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then - cat >> "$override_file" << 'EOF' - taxii_src: -EOF - 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" + generate_rest_api_certs_override if [[ $ENABLE_INSIGHTS =~ ^[Yy]$ ]]; then - generate_insights_override >> "$override_file" + generate_insights_override fi fi fi - # Add newline at end - echo "" >> "$override_file" +} + +generate_compose_override() { + render_compose_override > "$1" } #=============================================================================== @@ -939,6 +963,8 @@ show_configuration_summary() { if [[ "$config_root" != "$instance_dir" ]]; then echo " Mount Dir: $config_root" fi + echo " Frontend: $config_root/configs/frontend/.env" + echo " Frontend: $config_root/configs/frontend/config.json" echo " REST API: $config_root/configs/rest-api/.env" echo " REST API: $config_root/configs/rest-api/rest-api-service-config.json" if [[ $enable_taxii =~ ^[Yy]$ ]]; then @@ -952,6 +978,47 @@ show_configuration_summary() { echo "" } +# Display the equivalent non-interactive setup command +show_recreation_command() { + local deployment_dir="$1" + local instance_name="$2" + local database_url="$3" + local host_mount_dir="$4" + local dev_mode="$5" + local enable_taxii="$6" + local enable_insights="$7" + local enable_custom_certs="$8" + local host_certs_path="$9" + local certs_filename="${10}" + + local -a command=( + "$deployment_dir/docker/setup-workbench.sh" + "--accept-defaults" + "--instance-name" "$instance_name" + "--mongodb-connection" "$database_url" + "--host-mount-dir" "$host_mount_dir" + ) + + if [[ $enable_taxii =~ ^[Yy]$ ]]; then + command+=("--taxii-server") + fi + if [[ $enable_insights =~ ^[Yy]$ ]]; then + command+=("--insights") + fi + if [[ $dev_mode =~ ^[Yy]$ ]]; then + command+=("--dev-mode") + fi + if [[ $enable_custom_certs =~ ^[Yy]$ ]]; then + command+=("--ssl-host-certs-path" "$host_certs_path") + command+=("--ssl-certs-file" "$certs_filename") + fi + + info "Equivalent non-interactive command:" + printf " " + printf "%q " "${command[@]}" + printf "\n\n" +} + # Display custom SSL certificate information show_certificate_info() { local instance_dir="$1" @@ -1023,6 +1090,7 @@ usage() { 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 " --print-dev-compose Render the default development example and exit" echo " --instance-name Name of the generated configuration" echo " -h, --help Show this help and exit" echo " --mongodb-connection MongoDB connection string" @@ -1053,6 +1121,14 @@ while [[ $# -gt 0 ]]; do ACCEPT_DEFAULTS=true shift ;; + --print-dev-compose) + DEV_MODE=y + ENABLE_TAXII=y + ENABLE_INSIGHTS=n + ENABLE_CUSTOM_CERTS=n + render_compose_override + exit 0 + ;; --dev-mode) AUTO_DEV_MODE=true shift @@ -1323,6 +1399,13 @@ fi # Environment Configuration #--------------------------------------- +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 + configure_host_mount_dir HOST_MOUNT_DIR="$CONFIGURE_HOST_MOUNT_DIR_REF" @@ -1346,17 +1429,11 @@ echo "" # Additional Options #--------------------------------------- -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 if [[ -z "${AUTO_HOST_CERTS_PATH}" && -z "${AUTO_CERTS_FILENAME}" ]]; then prompt_yes_no "Do you want to configure custom SSL certificates for containerized services?" "N" ENABLE_CUSTOM_CERTS="$PROMPT_YES_NO_RESULT" -else +else ENABLE_CUSTOM_CERTS="y" fi @@ -1400,6 +1477,7 @@ fi #--------------------------------------- show_configuration_summary "$INSTANCE_DIR" "$OVERRIDE_FILE" "$DEV_MODE" "$ENABLE_TAXII" "$ENABLE_INSIGHTS" "$ENABLE_CUSTOM_CERTS" "$HOST_MOUNT_DIR" +show_recreation_command "$DEPLOYMENT_DIR" "$INSTANCE_NAME" "$DATABASE_URL" "$HOST_MOUNT_DIR" "$DEV_MODE" "$ENABLE_TAXII" "$ENABLE_INSIGHTS" "$ENABLE_CUSTOM_CERTS" "$HOST_CERTS_PATH" "$CERTS_FILENAME" if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then show_certificate_info "$INSTANCE_DIR" "$HOST_CERTS_PATH" "$CERTS_FILENAME" fi diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 91c80c3..c751177 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -118,3 +118,14 @@ For detailed information on contributing to the ATT&CK Workbench, including comm ## Questions? For questions about this process, please open an issue in the relevant repository or contact the development team. + +## Development Compose template + +`docker/setup-workbench.sh` owns the development service definitions. After editing +its renderer, refresh the reference file from the repository root: + +```bash +bash docker/setup-workbench.sh --print-dev-compose > docker/example-setup/compose.dev.yaml +``` + +CI checks that the rendered file matches the example. diff --git a/docs/configuration.md b/docs/configuration.md index e866355..3bb03aa 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -35,6 +35,8 @@ Available environment variables: | `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_ENV_FILE` | `./configs/frontend/.env` | Frontend process environment file | +| `ATTACKWB_FRONTEND_CONFIG_DIR` | `./configs/frontend` | Directory containing optional public `config.json` | | `ATTACKWB_FRONTEND_CERTS_PATH` | `./certs` | Path to SSL certificates for nginx | There are four sample nginx config files that can be used as reference: @@ -44,6 +46,11 @@ There are four sample nginx config files that can be used as reference: - `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. +In developer mode, Angular serves the frontend over HTTP. The generated override +sets `WORKBENCH_FRONTEND_API_PROXY_TARGET=http://rest-api:3000`; published host ports do +not change this internal target. For frontend-owned variables and build settings, +see the [frontend configuration guide](https://github.com/mitre-attack/attack-workbench-frontend/blob/next/docs/admin/configuration.md). + ### REST API | Variable | Default Value | Description | @@ -101,10 +108,76 @@ Each service has its own configuration directory: **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/mitre-attack/attack-workbench-frontend) -for further details on customizing the Workbench frontend. +The frontend container generates its public configuration before starting Nginx. +Setup renames the embedded [frontend template](../docker/example-setup/configs/frontend/template.env) +to `configs/frontend/.env` in both development and published mode. No checkout +lookup, release asset, network request, or image inspection is needed. Select image +tags independently in the instance's root `.env`. + +All upstream service templates carry a commented `Template-Version: 1.0` header. +This is a template revision, not an application or image version. The frontend +embedded copy, REST API copy, and TAXII copy match their source templates. +Update embedded copies deliberately when upstream settings change. + +Keep `WORKBENCH_FRONTEND_*` settings in `configs/frontend/.env`. The instance's +root `.env` controls Compose image tags, ports, and paths. Confirming overwrite +removes the old instance directory and initializes fresh configuration from the +embedded templates for every service. Setup does not back up or preserve edits. +If a separate host mount directory is selected, generated configuration is copied +there and replaces files with matching names. + +#### Optional JSON input + +Environment variables are enough for every supported frontend setting. The +embedded `configs/frontend/config.json` lists every public setting with the +frontend's production defaults. Setup copies it, but the file may be removed in +both development and production; missing default +JSON uses environment settings and defaults. An explicitly selected missing file +via `WORKBENCH_FRONTEND_CONFIG_FILE` remains an error. The default JSON is not +generated from environment variables and the container never writes back to it. +The full example explicitly selects `https://attack.mitre.org` for +`attackWebsiteUrl` and `ERROR` for `logLevel`, including when used in development. +Without those JSON settings, development defaults are `http://localhost:8000` +and `INFO`. Override them in `configs/frontend/.env`, or use `{}` or partial +JSON to inherit the defaults for the current mode, for example: + +```json +{"defaultLandingPage":"tactic","recommendedIndexes":[]} +``` + +To refresh the embedded example from the frontend's defaults, run these commands +from a sibling `attack-workbench-frontend` checkout: + +```bash +node scripts/compile-configuration.mjs +node scripts/write-runtime-config.mjs --defaults-only --production --output ../attack-workbench-deployment/docker/example-setup/configs/frontend/config.json +``` + +`--defaults-only` ignores source JSON, dotenv files, and environment overrides. +Refresh the example deliberately when adopting updated frontend settings. + +JSON is useful for larger arrays and existing configurations. Container environment +values override this input; unspecified settings use frontend defaults. The +read-only directory is mounted at `/etc/workbench`. Startup generates the effective +browser document separately at `/usr/share/nginx/html/assets/config.json`. +Native development similarly reads optional source JSON and writes generated +output outside `src`. See the frontend configuration reference below for all fields. + +From the instance directory, apply settings with: + +```bash +# Re-read container environment variables after editing the frontend .env. +docker compose up -d --force-recreate frontend + +# Re-read mounted JSON when Compose Watch is stopped. +docker compose restart frontend +``` + +Then reload the browser. In developer mode, Compose Watch restarts the frontend +when the mounted JSON changes. An environment-file edit still requires recreation. +The included Nginx configurations serve `/assets/config.json` with `no-store`. +See the [frontend configuration reference](https://github.com/mitre-attack/attack-workbench-frontend/blob/next/docs/admin/configuration.md) +for public settings, precedence, and validation. ### REST API @@ -122,6 +195,37 @@ for further details on customizing the backend. Generate secure secrets using: `node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"` +#### REST API configuration files + +The instance's root `.env` controls Compose interpolation. API process variables +belong in `configs/rest-api/.env` (or the file selected by +`ATTACKWB_RESTAPI_ENV_FILE`). For this Compose network, the database address is +`DATABASE_URL=mongodb://mongodb/attack-workspace`. + +The base Compose file mounts the JSON configuration selected by +`ATTACKWB_RESTAPI_CONFIG_FILE` read-only at +`/usr/src/app/resources/rest-api-service-config.json`. Enable loading it by setting +`JSON_CONFIG_PATH=./resources/rest-api-service-config.json` in the API environment +file. This mount is available with or without Watch. + +Apply configuration changes from the instance directory: + +```bash +# Reload a JSON file when Watch is stopped. +docker compose restart rest-api + +# Reload environment-file changes or a changed mount path. +docker compose up -d --force-recreate rest-api + +# Check startup and authentication configuration messages. +docker compose logs rest-api +``` + +In developer mode, Watch restarts the API automatically after edits to the mounted +JSON file. Restarting alone does not replace a container's environment. API +setting definitions, precedence, and authentication options remain in the +[REST API configuration reference](https://github.com/mitre-attack/attack-workbench-rest-api/blob/next/docs/admin/configuration.md). + ### Grafana **Config files**: [configs/grafana/](../docker/example-setup/configs/grafana/) diff --git a/docs/deployment.md b/docs/deployment.md index 10410e0..a04f1e9 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -22,52 +22,24 @@ directory, generate the instance with: ### 1. Using Pre-built Images (Recommended) -Use `compose.yaml` to pull pre-built images directly from GitHub Container Registry (GHCR): +Use the generated `compose.yaml` to pull pre-built images from GitHub Container Registry (GHCR). Select optional services during setup; the generated file includes those choices: ```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/mitre-attack/attack-workbench-frontend/) -- [attack-workbench-rest-api](https://github.com/mitre-attack/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) -``` +Use the setup script's developer mode to build from sibling source checkouts. +See [local development](local-development.md) for the required Compose version, +repository layout, startup commands, and reload behavior. ### 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. +Thus, the `mongodb` service can be deleted and re-deployed without losing access to the database. +The database volume will be remounted to the `mongodb` service upon deployment. diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 0000000..893b879 --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,57 @@ +# Local development with Docker Compose + +Use this workflow when editing the frontend, REST API, or TAXII server in local +checkouts. For published images, follow the [Docker quickstart](../docker/README.md). + +## Start a development instance + +Use Docker Compose **2.32.0 or newer** (`docker compose version`). The +[`restart` Watch action](https://docs.docker.com/reference/compose-file/develop/#action) +sets this minimum version. + +Keep the component repositories beside the deployment repository: + +```text +working-directory/ +├── attack-workbench-deployment/ +├── attack-workbench-frontend/ +├── attack-workbench-rest-api/ +└── attack-workbench-taxii-server/ # when TAXII is enabled +``` + +From the deployment repository: + +```bash +./docker/setup-workbench.sh --dev-mode --instance-name my-workbench +cd instances/my-workbench +docker compose up --watch --build +``` + +Add `--taxii-server` to the setup command to include TAXII. Setup generates +`compose.override.yaml`, which Compose loads automatically. The sibling checkouts +must be accessible to Docker on the host running its daemon. + +Open the configured frontend HTTP port. The frontend proxies `/api` to +`http://rest-api:3000` inside the Compose network, so changing the API's published +host port does not require changing the browser configuration. Local development +uses HTTP without certificates. For access from another computer, use the +Docker host's address; see the frontend's +[API routing and remote access guidance](https://github.com/mitre-attack/attack-workbench-frontend/blob/next/docs/admin/configuration.md#api-routing) +for development-server hostname settings. + +## What reloads + +| Change | Result while Watch is running | +| --- | --- | +| Frontend components, templates, styles, or build-mode settings | Angular detects the mounted source through polling and rebuilds. | +| Frontend `src/assets` files | Compose restarts Angular, including when an asset is added or deleted. | +| Add, edit, or remove frontend `configs/frontend/config.json` | Compose restarts Angular and regenerates public configuration; an absent default file uses environment settings and defaults. | +| REST API `app` or `bin` files, including OpenAPI YAML | Compose restarts Node to load the current source. | +| REST API JSON configuration | Compose restarts the API. Enable loading the file as described in [service configuration](configuration.md#rest-api-configuration-files). | +| TAXII `src` files | Compose restarts the container, compiles TypeScript, and starts the server and collector under PM2. | +| Watched dependency manifests or build configuration | Compose rebuilds the affected image and recreates the container. | + +The generated override defines the exact watched paths. After changing an +unwatched build input, run `docker compose up -d --build` for the affected service. +For frontend and API environment-file changes, recreate the affected container +as described in [service configuration](configuration.md#service-specific-configuration). diff --git a/k8s/base/configmap-nginx.yaml b/k8s/base/configmap-nginx.yaml index cb3a2a0..98ec2cc 100644 --- a/k8s/base/configmap-nginx.yaml +++ b/k8s/base/configmap-nginx.yaml @@ -16,6 +16,10 @@ data: 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 = /assets/config.json { + add_header Cache-Control "no-store" always; + } + location / { try_files $uri $uri/ /index.html; }