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 718dcc0..8101913 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,30 +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 docker compose docker compose up -d - -# or deploy in development mode -docker compose up -d --build ``` -Access Workbench at - -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 75366ac..d78ec66 100644 --- a/docker/example-setup/compose.dev.yaml +++ b/docker/example-setup/compose.dev.yaml @@ -1,54 +1,117 @@ +# This file was generated by setup-workbench.sh +# It will be automatically merged with compose.yaml when running docker compose commands + services: frontend: image: attack-workbench-frontend - build: ../../../attack-workbench-frontend + 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 + - "${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: /app/src + # 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: - - node_modules/ + - .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 + 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: + - 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 - ignore: - - node_modules/ - # Restart on config changes - - action: sync+restart - path: ../../../attack-workbench-rest-api/resources - target: /usr/src/app/resources + # Restart after server entrypoint and config changes + - action: restart + path: ../../../attack-workbench-rest-api/bin + # 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 + - 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: + - 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 - 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 TypeScript and restart both TAXII server and collector + - action: restart + path: ../../../attack-workbench-taxii-server/src # Rebuild on package.json changes - action: rebuild path: ../../../attack-workbench-taxii-server/package.json + - action: rebuild + path: ../../../attack-workbench-taxii-server/package-lock.json + # 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 9774459..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" @@ -681,18 +689,49 @@ generate_frontend_override() { frontend: image: $REPO_FRONTEND - build: ../../../$REPO_FRONTEND + 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 + - "\${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: /app/src + # 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: - - node_modules/ + - .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 + 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,13 +741,29 @@ generate_rest_api_dev_override() { rest-api: image: $REPO_REST_API - build: ../../../$REPO_REST_API + build: + context: ../../../$REPO_REST_API + target: dev +EOF + + 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 - # Add custom cert volumes if enabled if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then cat << 'EOF' - volumes: - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} @@ -719,19 +774,20 @@ 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 - ignore: - - node_modules/ - # Restart on config changes - - action: sync+restart - path: ../../../$REPO_REST_API/resources - target: /usr/src/app/resources + # Restart after server entrypoint and config changes + - action: restart + path: ../../../$REPO_REST_API/bin + # 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 + - action: rebuild + path: ../../../$REPO_REST_API/package-lock.json EOF } @@ -808,31 +864,43 @@ generate_taxii_override() { taxii: image: $REPO_TAXII - build: ../../../$REPO_TAXII + build: + context: ../../../$REPO_TAXII + target: development + volumes: + - type: bind + source: ../../../$REPO_TAXII/src + target: /app/src + read_only: true + bind: + create_host_path: false 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 TypeScript and restart both TAXII server and collector + - action: restart + path: ../../../$REPO_TAXII/src # 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 @@ -841,27 +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 + 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" } #=============================================================================== @@ -892,10 +963,12 @@ 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 - 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" @@ -905,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" @@ -936,9 +1050,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" @@ -973,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" @@ -1003,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 @@ -1273,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" @@ -1296,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 @@ -1350,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; }