diff --git a/README.md b/README.md index 175aaee..719579d 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,32 @@ # ATT&CK Workbench Deployment -This repository contains deployment files for the ATT&CK Workbench, a web application for editing ATT&CK data represented in STIX. It is composed of a frontend SPA, a backend REST API, and a database. Optionally, you can deploy a "sidecar service" that makes your Workbench data available over a TAXII 2.1 API. +This repository contains deployment files for the ATT&CK Workbench, a web application for editing ATT&CK data represented in STIX. +It is composed of a frontend Single Page App (SPA), a backend REST API, and a database. +Optionally, you can deploy a "sidecar service" that makes your Workbench data available over a TAXII 2.1 API. -## Deployment Options +## Docker Setup -### Docker Compose +To quickly create and deploy a custom Workbench instance using Docker Compose use the interactive setup script in the `docker/` directory. -The ATT&CK Workbench can be deployed using Docker Compose with two different configurations: +See [docker/README](docker/README.md) for detailed instructions. -#### 1. Using Pre-built Images (Recommended) - -Use `compose.yaml` to pull pre-built images directly from GitHub Container Registry (GHCR): - -```bash -# Deploy with pre-built images -docker compose up -d - -# Deploy with TAXII server -docker compose --profile with-taxii up -d - -# Stop the deployment -docker compose down -``` - -#### 2. Building from Source - -Use `compose.dev.yaml` in combination with `compose.yaml` to build images from source code: - -```bash -# Build and deploy from source -docker compose -f compose.yaml -f compose.dev.yaml up -d --build - -# Build and deploy with TAXII server -docker compose -f compose.yaml -f compose.dev.yaml --profile with-taxii up -d --build - -# Stop the deployment -docker compose -f compose.yaml -f compose.dev.yaml down -``` - -**Note**: When building from source, you need the following three source repositories to be available as sibling directories to this deployment repository: - -- [attack-workbench-frontend](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/) -- [attack-workbench-rest-api](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/) -- [attack-workbench-taxii-server](https://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) -``` - -### Kubernetes +## Kubernetes Setup For production deployments, Kubernetes manifests with Kustomize are available in the `k8s/` directory. -See [k8s/README.md](k8s/README.md) for detailed instructions. - -## Configuration - -### Environment Variables - -We make heavy use of string interpolation to minimize having to modify the Docker Compose manifest files (e.g., [compose.yaml](./compose.yaml)). Consequently, that means you must set a bunch of environment variables when using these templates. Fortunately, we've provided a dotenv template that you can source. - -Copy `template.env` to `.env` and customize the values as needed: - -```bash -cp template.env .env -``` - -Available environment variables: - -| Variable | Default Value | Description | -|----------|---------------|-------------| -| **Docker Image Tags** | | | -| `ATTACKWB_FRONTEND_VERSION` | `latest` | Frontend Docker image tag | -| `ATTACKWB_RESTAPI_VERSION` | `latest` | REST API Docker image tag | -| `ATTACKWB_TAXII_VERSION` | `latest` | TAXII server Docker image tag | -| **HTTP Listener Ports** | | | -| `ATTACKWB_FRONTEND_HTTP_PORT` | `80` | Frontend HTTP port | -| `ATTACKWB_FRONTEND_HTTPS_PORT` | `443` | Frontend HTTPS port | -| `ATTACKWB_RESTAPI_HTTP_PORT` | `3000` | REST API port | -| `ATTACKWB_DB_PORT` | `27017` | MongoDB port | -| `ATTACKWB_TAXII_HTTP_PORT` | `5002` | TAXII server port | -| **SSL/TLS Configuration** | | | -| `ATTACKWB_FRONTEND_CERTS_PATH` | `./certs` | Path to SSL certificates | -| **TAXII Configuration** | | | -| `ATTACKWB_TAXII_ENV` | `dev` | Specifies the name of the dotenv file to load (e.g., A value of `dev` tells the TAXII server to load `dev.env`) | - -### Service-Specific Configuration - -Each service has its own configuration directory: - -#### Frontend - -**Default config files**: `configs/frontend/` - -The frontend container is an Nginx instance which serves the frontend SPA and reverse proxies requests to the backend REST API. -We provide a basic `nginx.conf` template in the aforementioned directory that should get you started. -Refer to the [frontend documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) -for further details on customizing the SPA. - -#### REST API - -> [!IMPORTANT] -> The REST API service requires the `SESSION_SECRET` environment variable to be set in order to deploy. -> Without it set, `docker compose up` will fail to start this required service. - -**Default config files**: `configs/rest-api/` - -The backend REST API loads runtime configurations from environment variables, as well as from a JSON configuration file. -Templates are provided in the aforementioned directory. -Refer to the [REST API usage documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/blob/main/USAGE.md#configuration) -for further details on customizing the backend. - -#### TAXII Server - -**Default config files**: `configs/taxii/config/` - -The TAXII server loads all runtime configuration parameters from a dotenv file. -The specific filename of the dotenv file is specified by the `ATTACKWB_TAXII_ENV` environment variable. -For example, a value of `dev` tells the TAXII server to load `dev.env`. - -## Quick Start - -1. Clone this repository: - - ```bash - git clone https://github.com/center-for-threat-informed-defense/attack-workbench-deployment.git - cd attack-workbench-deployment - ``` - -2. Configure environment variables (optional): - - ```bash - cp template.env .env - # Edit .env with your preferred settings - ``` - -3. Configure REST API environment variables (required): - - ```bash - cp configs/rest-api/template.env configs/rest-api/.env - ``` - - Generate a secure random secret - - ```bash - node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" - ``` - - Set the above secret in `configs/rest-api/.env` - - ```bash - SESSION_SECRET= - ``` - -4. Deploy using pre-built images: - - ```bash - docker compose up -d - ``` - -5. Access the application at `http://localhost` (or your configured port) - -6. To include the TAXII server: - - ```bash - docker compose --profile with-taxii up -d - ``` - -## Data Persistence - -MongoDB data is persisted in the `workspace-data` named Docker volume. Thus, the `database` service can be deleted and re-deployed without losing access to the database. The database volume will be remounted to the `database` service upon deployment. - -## Troubleshooting - -### Check Service Status - -```bash -# View running containers -docker compose ps - -# Show logs for all running containers -docker compose logs - -# Follow logs -docker compose logs -f - -# Show logs for a specific container -docker compose logs frontend -docker compose logs rest-api -docker compose logs database -docker compose logs taxii -``` -## Contributing +See [k8s/README](k8s/README.md) for detailed instructions. -Please refer to the [contribution guide](./docs/CONTRIBUTING.md) for contribution guidelines, as well as the [developer guide](./docs/DEVELOPMENT.md) for information on our release process. +## Troubleshooting & Support -## License +- View logs: `docker compose logs -f` +- Check running containers: `docker compose ps` -This project is licensed under the Apache License 2.0. See the [LICENSE](./LICENSE) file for details. +More tips in [docs/troubleshooting](docs/troubleshooting.md). -## Support +For questions or issues, visit the [GitHub issues page](https://github.com/mitre-attack/attack-workbench-deployment/issues). -For issues and questions: +## Contributing & License -- Check the [deployment repository issues](https://github.com/center-for-threat-informed-defense/attack-workbench-deployment/issues) -- Refer to the main [ATT&CK Workbench documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) +- Contribution guide: [contribution guide](./docs/CONTRIBUTING.md) +- Developer guide: [developer guide](./docs/DEVELOPMENT.md) +- License: [Apache License 2.0](./LICENSE) diff --git a/certs/README.md b/certs/README.md index ab60ab3..a294f6e 100644 --- a/certs/README.md +++ b/certs/README.md @@ -36,7 +36,7 @@ If you're using environment variables in your shell, you can use: ```yaml volumes: - - .${HOST_CERTS_PATH}:/usr/src/app/certs + - ${HOST_CERTS_PATH}:/usr/src/app/certs environment: - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} ``` diff --git a/compose.dev.yaml b/compose.dev.yaml deleted file mode 100644 index 9c18d81..0000000 --- a/compose.dev.yaml +++ /dev/null @@ -1,13 +0,0 @@ -services: - - frontend: - image: attack-workbench-frontend - build: ../attack-workbench-frontend - - rest-api: - image: attack-workbench-rest-api - build: ../attack-workbench-rest-api - - taxii: - image: attack-workbench-taxii-server - build: ../attack-workbench-taxii-server diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index d6ec03b..0000000 --- a/compose.yaml +++ /dev/null @@ -1,86 +0,0 @@ -services: - - frontend: - container_name: attack-workbench-frontend - image: ghcr.io/center-for-threat-informed-defense/attack-workbench-frontend:${ATTACKWB_FRONTEND_VERSION:-latest} - depends_on: - - rest-api - ports: - - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:${ATTACKWB_FRONTEND_HTTP_PORT:-80}" - - "${ATTACKWB_FRONTEND_HTTPS_PORT:-443}:${ATTACKWB_FRONTEND_HTTPS_PORT:-443}" - volumes: - - ./configs/frontend/nginx.conf:/etc/nginx/nginx.conf:ro - - "${ATTACKWB_FRONTEND_CERTS_PATH:-./certs}:/etc/nginx/certs:ro" - restart: unless-stopped - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "5" - - rest-api: - container_name: attack-workbench-rest-api - image: ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api:${ATTACKWB_RESTAPI_VERSION:-latest} - depends_on: - - database - ports: - - "${ATTACKWB_RESTAPI_HTTP_PORT:-3000}:${ATTACKWB_RESTAPI_HTTP_PORT:-3000}" - volumes: - - ./configs/rest-api/rest-api-service-config.json:/usr/src/app/resources/rest-api-service-config.json:ro - env_file: - - ./configs/rest-api/.env - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/api/health/ping"] - interval: 30s - timeout: 10s - retries: 3 - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "5" - - database: - container_name: attack-workbench-database - image: mongo:8 - ports: - - "${ATTACKWB_DB_PORT:-27017}:${ATTACKWB_DB_PORT:-27017}" - volumes: - - workspace-data:/data/db - - ./database-backup:/dump - restart: unless-stopped - healthcheck: - test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] - interval: 30s - timeout: 10s - retries: 5 - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "5" - - taxii: - container_name: attack-workbench-taxii-server - image: ghcr.io/mitre-attack/attack-workbench-taxii-server:${ATTACKWB_TAXII_VERSION:-latest} - depends_on: - - rest-api - ports: - - "${ATTACKWB_TAXII_HTTP_PORT:-5002}:${ATTACKWB_TAXII_HTTP_PORT:-5002}" - volumes: - - ./configs/taxii/config:/app/config:ro - environment: - - TAXII_ENV=${ATTACKWB_TAXII_ENV:-dev} - - TAXII_HYDRATE_ON_BOOT=true - profiles: - - with-taxii - restart: unless-stopped - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "5" - -volumes: - workspace-data: diff --git a/configs/frontend/nginx.conf b/configs/frontend/nginx.conf deleted file mode 100644 index 62ddc56..0000000 --- a/configs/frontend/nginx.conf +++ /dev/null @@ -1,48 +0,0 @@ -worker_processes 1; - -events { - worker_connections 1024; -} - -http { - server { - listen 80; - server_name localhost; - - root /usr/share/nginx/html; - index index.html index.htm; - include /etc/nginx/mime.types; - - gzip on; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - location / { - try_files $uri $uri/ /index.html; - } - - location /api { - client_max_body_size 50M; - - # Disable buffering for SSE streams - proxy_buffering off; - proxy_cache off; - - # Keep connection alive for long-running requests - proxy_read_timeout 600s; - proxy_connect_timeout 300s; - proxy_send_timeout 300s; - - # Required headers for SSE - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding on; - - # Disable compression for SSE - gzip off; - - proxy_pass http://attack-workbench-rest-api:3000; - } - } -} diff --git a/configs/rest-api/template.env b/configs/rest-api/template.env deleted file mode 100644 index 7b8874e..0000000 --- a/configs/rest-api/template.env +++ /dev/null @@ -1,66 +0,0 @@ -# HTTP Listener Port -PORT=3000 - -# CORS (`*`, `disable`, or comma-separated list of FQDNs) -CORS_ALLOWED_ORIGINS=* - -# Environment -NODE_ENV=development - -# Database -DATABASE_URL=mongodb://attack-workbench-database/attack-workspace - -# Database Migration -WB_REST_DATABASE_MIGRATION_ENABLE=true - -# Authentication Mechanism -AUTHN_MECHANISM=anonymous - -# OIDC Authentication -AUTHN_OIDC_CLIENT_ID= -AUTHN_OIDC_CLIENT_SECRET= -AUTHN_OIDC_ISSUER_URL= -AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000 - -# Service Account Authentication - OIDC Client Credentials -SERVICE_ACCOUNT_OIDC_ENABLE=false -JWKS_URI= - -# Service Account Authentication - Challenge API Key -WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE=false -WB_REST_TOKEN_SIGNING_SECRET= -WB_REST_TOKEN_TIMEOUT=300 - -# Service Account Authentication - Basic API Key -WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE=false - -# Collection Index Interval -DEFAULT_INTERVAL=300 - -# Configuration File Path -JSON_CONFIG_PATH= - -# Logging -LOG_LEVEL=info - -# Static Marking Definitions Path -WB_REST_STATIC_MARKING_DEFS_PATH=./app/lib/default-static-marking-definitions/ - -# Allowed Values Configuration File Path -ALLOWED_VALUES_PATH=./app/config/allowed-values.json - -# Scheduler Settings -CHECK_WORKBENCH_INTERVAL=10 -ENABLE_SCHEDULER=true - -########## -# OPTIONAL -########## - -# Session Configuration -# Generate a secure random secret with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" -# If not provided, the REST API will generate one for you at startup (not recommended for production) -#SESSION_SECRET= - -# Path to additional CA certificates file in PEM format -#NODE_EXTRA_CA_CERTS= diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..718dcc0 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,30 @@ +# Docker Compose Setup + +Use the interactive setup script `setup-workbench.sh` to quickly create and deploy a custom Workbench instance: + +```bash +# Clone and run setup script +git clone https://github.com/mitre-attack/attack-workbench-deployment.git +cd attack-workbench-deployment/docker/ +./setup-workbench.sh +``` + +After running the script, deploy with: + +```bash +cd ../instances/your-instance-name + +# 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). + +For source builds or TAXII setup, see [docs/deployment](docs/deployment.md). + +For information on how to backup or restore the mongo database, see [docs/database-backups](docs/database-backups.md). diff --git a/compose.certs.yaml b/docker/example-setup/compose.certs.yaml similarity index 87% rename from compose.certs.yaml rename to docker/example-setup/compose.certs.yaml index 19667b8..ad5102a 100644 --- a/compose.certs.yaml +++ b/docker/example-setup/compose.certs.yaml @@ -18,6 +18,6 @@ services: rest-api: volumes: - - .${HOST_CERTS_PATH}:/usr/src/app/certs + - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs environment: - - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME} + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} diff --git a/docker/example-setup/compose.dev.yaml b/docker/example-setup/compose.dev.yaml new file mode 100644 index 0000000..75366ac --- /dev/null +++ b/docker/example-setup/compose.dev.yaml @@ -0,0 +1,54 @@ +services: + + frontend: + image: attack-workbench-frontend + build: ../../../attack-workbench-frontend + develop: + watch: + # Sync source files for hot-reload + - action: sync + path: ../../../attack-workbench-frontend/src + target: /app/src + ignore: + - node_modules/ + # Rebuild on package.json changes + - action: rebuild + path: ../../../attack-workbench-frontend/package.json + + rest-api: + image: attack-workbench-rest-api + build: ../../../attack-workbench-rest-api + develop: + watch: + # Sync app source files + - action: sync + path: ../../../attack-workbench-rest-api/app + target: /usr/src/app/app + ignore: + - node_modules/ + # Restart on config changes + - action: sync+restart + path: ../../../attack-workbench-rest-api/resources + target: /usr/src/app/resources + # Rebuild on package.json changes + - action: rebuild + path: ../../../attack-workbench-rest-api/package.json + + taxii: + image: attack-workbench-taxii-server + build: ../../../attack-workbench-taxii-server + develop: + watch: + # Sync source files + - action: sync + path: ../../../attack-workbench-taxii-server/taxii + target: /app/taxii + ignore: + - node_modules/ + # Sync config files and restart + - action: sync+restart + path: ../../../attack-workbench-taxii-server/config + target: /app/config + # Rebuild on package.json changes + - action: rebuild + path: ../../../attack-workbench-taxii-server/package.json diff --git a/docker/example-setup/compose.taxii.yaml b/docker/example-setup/compose.taxii.yaml new file mode 100644 index 0000000..5aa1dac --- /dev/null +++ b/docker/example-setup/compose.taxii.yaml @@ -0,0 +1,18 @@ +services: + + taxii: + image: ghcr.io/mitre-attack/attack-workbench-taxii-server:${ATTACKWB_TAXII_VERSION:-latest} + depends_on: + - rest-api + ports: + - "${ATTACKWB_TAXII_HTTP_PORT:-8000}:8000" + volumes: + - "${ATTACKWB_TAXII_CONFIG_DIR:-./configs/taxii/config}:/app/config:ro" + environment: + - TAXII_ENV=${ATTACKWB_TAXII_ENV:-dev} + restart: unless-stopped + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" diff --git a/docker/example-setup/compose.yaml b/docker/example-setup/compose.yaml new file mode 100644 index 0000000..2ccfd79 --- /dev/null +++ b/docker/example-setup/compose.yaml @@ -0,0 +1,62 @@ +services: + + frontend: + image: ghcr.io/center-for-threat-informed-defense/attack-workbench-frontend:${ATTACKWB_FRONTEND_VERSION:-latest} + depends_on: + - rest-api + ports: + - "${ATTACKWB_FRONTEND_HTTP_PORT:-80}:80" + - "${ATTACKWB_FRONTEND_HTTPS_PORT:-443}:443" + volumes: + - "${ATTACKWB_FRONTEND_NGINX_CONFIG_FILE:-./configs/frontend/nginx.api.conf}:/etc/nginx/conf.d/default.conf:ro" + - "${ATTACKWB_FRONTEND_CERTS_PATH:-./certs}:/etc/nginx/certs:ro" + restart: unless-stopped + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" + + rest-api: + image: ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api:${ATTACKWB_RESTAPI_VERSION:-latest} + depends_on: + - mongodb + ports: + - "${ATTACKWB_RESTAPI_HTTP_PORT:-3000}:3000" + volumes: + - "${ATTACKWB_RESTAPI_CONFIG_FILE:-./configs/rest-api/rest-api-service-config.json}:/usr/src/app/resources/rest-api-service-config.json:ro" + env_file: + - "${ATTACKWB_RESTAPI_ENV_FILE:-./configs/rest-api/.env}" + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health/ping"] + interval: 30s + timeout: 10s + retries: 3 + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" + + mongodb: + image: mongo:8 + ports: + - "127.0.0.1:${ATTACKWB_DB_PORT:-27017}:27017" + volumes: + - workspace-data:/data/db + - ./database-backup:/dump + restart: unless-stopped + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 30s + timeout: 10s + retries: 5 + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" + +volumes: + workspace-data: diff --git a/docker/example-setup/configs/frontend/nginx.api.conf b/docker/example-setup/configs/frontend/nginx.api.conf new file mode 100644 index 0000000..6789577 --- /dev/null +++ b/docker/example-setup/configs/frontend/nginx.api.conf @@ -0,0 +1,42 @@ +server { + listen 80; + http2 on; + server_name _; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + client_max_body_size 50M; + + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; + + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; + + # Disable compression for SSE + gzip off; + + proxy_pass http://rest-api:3000; + } + +} diff --git a/docker/example-setup/configs/frontend/nginx.api.ssl.conf b/docker/example-setup/configs/frontend/nginx.api.ssl.conf new file mode 100644 index 0000000..2c2bb87 --- /dev/null +++ b/docker/example-setup/configs/frontend/nginx.api.ssl.conf @@ -0,0 +1,51 @@ +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl default_server; + http2 on; + server_name _; + + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + client_max_body_size 50M; + + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; + + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; + + # Disable compression for SSE + gzip off; + + proxy_pass http://rest-api:3000; + } + +} diff --git a/docker/example-setup/configs/frontend/nginx.conf b/docker/example-setup/configs/frontend/nginx.conf new file mode 100644 index 0000000..6123cec --- /dev/null +++ b/docker/example-setup/configs/frontend/nginx.conf @@ -0,0 +1,19 @@ +server { + listen 80; + http2 on; + server_name _; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + +} diff --git a/docker/example-setup/configs/frontend/nginx.ssl.conf b/docker/example-setup/configs/frontend/nginx.ssl.conf new file mode 100644 index 0000000..d423ee4 --- /dev/null +++ b/docker/example-setup/configs/frontend/nginx.ssl.conf @@ -0,0 +1,28 @@ +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl default_server; + http2 on; + server_name _; + + ssl_certificate /etc/nginx/certs/server.pem; + ssl_certificate_key /etc/nginx/certs/server.key; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + +} diff --git a/configs/rest-api/rest-api-service-config.json b/docker/example-setup/configs/rest-api/rest-api-service-config.json similarity index 100% rename from configs/rest-api/rest-api-service-config.json rename to docker/example-setup/configs/rest-api/rest-api-service-config.json diff --git a/docker/example-setup/configs/rest-api/template.env b/docker/example-setup/configs/rest-api/template.env new file mode 100644 index 0000000..8168731 --- /dev/null +++ b/docker/example-setup/configs/rest-api/template.env @@ -0,0 +1,143 @@ +# Attack Workbench REST API - Environment Configuration Template +# Guidance: +# - Booleans: use true or false +# - Lists: use comma-separated values + +# Server +# PORT (int) - HTTP server port +# Default: 3000 +#PORT=3000 + +# Database (REQUIRED) +# DATABASE_URL (string) - MongoDB connection string +# Example (Docker): +#DATABASE_URL=mongodb://mongodb/attack-workspace +# Example (local): +#DATABASE_URL=mongodb://localhost:27017/attack-workspace +DATABASE_URL= + +# CORS_ALLOWED_ORIGINS (domains) - Allowed origins for REST API +# Accepts: +# * : allow any origin +# disable : disable CORS +# Comma-separated list of origins (http/https), e.g.: +# http://localhost:3000,https://example.com,https://sub.domain.org:8443 +# Supports localhost, private IPv4 (10.x, 172.16-31.x, 192.168.x), and FQDNs. +# Default: * +#CORS_ALLOWED_ORIGINS=* + +# Application +# NODE_ENV (string) - Environment name +# Options: development, production, test +# Default: development +#NODE_ENV=development + +# WB_REST_DATABASE_MIGRATION_ENABLE (bool) - Auto-run DB migrations on startup +# Default: true +#WB_REST_DATABASE_MIGRATION_ENABLE=true + +# Logging +# LOG_LEVEL (string) - Console log level +# Options: error, warn, http, info, verbose, debug +# Default: info +#LOG_LEVEL=info + +# Workbench Collection Indexes +# DEFAULT_INTERVAL (int, seconds) - Default polling interval for new indexes +# Note: does not affect existing indexes +# Default: 300 +#DEFAULT_INTERVAL=300 + +# Configuration Files +# JSON_CONFIG_PATH (string) - Path to a JSON file with additional configuration. +# Use this to provide arrays for service accounts and OIDC clients +# +# Some example values which align to some sample configurations which can be found here: +# https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/tree/main/resources/sample-configurations +# +# ./resources/collection-manager-apikey.json +# ./resources/collection-manager-oidc-keycloak.json +# ./resources/collection-manager-oidc-okta.json +# +# Default: empty (disabled) +#JSON_CONFIG_PATH= + +# ALLOWED_VALUES_PATH (string) - Path to allowed values configuration file +# Default: ./app/config/allowed-values.json +#ALLOWED_VALUES_PATH=./app/config/allowed-values.json + +# WB_REST_STATIC_MARKING_DEFS_PATH (string) - Directory of static marking definition JSON files +# Default: ./app/lib/default-static-marking-definitions/ +#WB_REST_STATIC_MARKING_DEFS_PATH=./app/lib/default-static-marking-definitions/ + +# Scheduler +# ENABLE_SCHEDULER (bool) - Enable background scheduler +# Default: true +#ENABLE_SCHEDULER=true + +# CHECK_WORKBENCH_INTERVAL (int, seconds) - Scheduler start interval +# Default: 10 +#CHECK_WORKBENCH_INTERVAL=10 + +# Session +# SESSION_SECRET (string) - Secret to sign session cookies. +# Default: securely generated at startup (changes on restart; not recommended for production). +# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" +#SESSION_SECRET= + +# MONGOSTORE_CRYPTO_SECRET (string) - Secret to encrypt session data in MongoDB. +# Default: securely generated at startup (changes on restart; not recommended for production). +# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" +#MONGOSTORE_CRYPTO_SECRET= + +# User Authentication +# AUTHN_MECHANISM (enum) - User login mechanism +# Options: anonymous, oidc +# Default: anonymous +#AUTHN_MECHANISM=anonymous + +# OIDC settings (required if AUTHN_MECHANISM=oidc) +# AUTHN_OIDC_ISSUER_URL (string) - OIDC issuer URL (e.g., https://idp.example.com) +# Default: empty +#AUTHN_OIDC_ISSUER_URL= +# AUTHN_OIDC_CLIENT_ID (string) - OIDC client ID +# Default: empty +#AUTHN_OIDC_CLIENT_ID= +# AUTHN_OIDC_CLIENT_SECRET (string) - OIDC client secret +# Default: empty +#AUTHN_OIDC_CLIENT_SECRET= +# AUTHN_OIDC_REDIRECT_ORIGIN (string) - Origin used to build redirect URI +# Example: http://localhost:3000 -> http://localhost:3000/authn/oidc/callback +# Default: http://localhost:3000 +#AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000 + +# Service Authentication +# OIDC Client Credentials (service-to-service) +# SERVICE_ACCOUNT_OIDC_ENABLE (bool) - Enable client credentials flow +# Default: false +#SERVICE_ACCOUNT_OIDC_ENABLE=false +# JWKS_URI (string) - JWKS endpoint for IdP public keys (required if enabled) +# Default: empty +#JWKS_URI= + +# Challenge API Key (token exchange) +# WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE (bool) - Enable challenge flow +# Default: false +#WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE=false +# WB_REST_TOKEN_SIGNING_SECRET (string) - Token signing secret +# Default: securely generated at startup (changes on restart; set for production) +#WB_REST_TOKEN_SIGNING_SECRET= +# WB_REST_TOKEN_TIMEOUT (int, seconds) - Access token lifetime +# Default: 300 +#WB_REST_TOKEN_TIMEOUT=300 + +# Basic API Key (no challenge) +# WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE (bool) - Enable basic apikey auth +# Default: false +#WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE=false + +# TLS/Certificates +# NODE_EXTRA_CA_CERTS (string) - Path to additional CA certs in PEM format +# Useful when MongoDB or IdP uses a private CA +# Default: empty +#NODE_EXTRA_CA_CERTS= diff --git a/configs/taxii/README.md b/docker/example-setup/configs/taxii/README.md similarity index 100% rename from configs/taxii/README.md rename to docker/example-setup/configs/taxii/README.md diff --git a/configs/taxii/config/template.env b/docker/example-setup/configs/taxii/config/template.env similarity index 94% rename from configs/taxii/config/template.env rename to docker/example-setup/configs/taxii/config/template.env index 13d07a4..286e9da 100644 --- a/configs/taxii/config/template.env +++ b/docker/example-setup/configs/taxii/config/template.env @@ -9,7 +9,7 @@ TAXII_ENV=dev # ***** SERVER SETTINGS ********************************************************************************************** # ******************************************************************************************************************** TAXII_APP_ADDRESS=0.0.0.0 -TAXII_APP_PORT=5002 +TAXII_APP_PORT=8000 TAXII_HTTPS_ENABLED=false TAXII_SSL_PRIVATE_KEY= TAXII_SSL_PUBLIC_KEY= @@ -21,8 +21,8 @@ TAXII_MAX_CONTENT_LENGTH=0 # ******************************************************************************************************************** # ***** NGINX SSL/TLS CERTIFICATE AUTO REG/RENEW ********************************************************************* # ******************************************************************************************************************** -CERTBOT_LE_FQDN=attack-taxii.mitre.org -CERBOT_LE_EMAIL=attack@mitre.org +CERTBOT_LE_FQDN=taxii.example.com +CERTBOT_LE_EMAIL=noreply@example.com CERTBOT_LE_ACME_SERVER=https://acme-v02.api.letsencrypt.org/directory CERTBOT_LE_RSA_KEY_SIZE=4096 @@ -54,7 +54,7 @@ TAXII_CACHE_RECONNECT=true # ******************************************************************************************************************** # ***** STIX/WORKBENCH SETTINGS ************************************************************************************** # ******************************************************************************************************************** -TAXII_STIX_SRC_URL=http://attack-workbench-rest-api:3000 +TAXII_STIX_SRC_URL=http://rest-api:3000 TAXII_STIX_DATA_SRC=workbench TAXII_WORKBENCH_AUTH_HEADER=dGF4aWktc2VydmVyOnNlY3JldC1zcXVpcnJlbA== @@ -63,7 +63,7 @@ TAXII_WORKBENCH_AUTH_HEADER=dGF4aWktc2VydmVyOnNlY3JldC1zcXVpcnJlbA== # ******************************************************************************************************************** # ***** DATABASE SETTINGS ******************************************************************************************** # ******************************************************************************************************************** -TAXII_MONGO_URI=mongodb://attack-workbench-database/taxii +TAXII_MONGO_URI=mongodb://mongodb:27017/taxii TAXII_HYDRATE_ON_BOOT=true diff --git a/configs/taxii/nginx.conf b/docker/example-setup/configs/taxii/nginx.conf similarity index 77% rename from configs/taxii/nginx.conf rename to docker/example-setup/configs/taxii/nginx.conf index 43c3ca5..9ff97e0 100644 --- a/configs/taxii/nginx.conf +++ b/docker/example-setup/configs/taxii/nginx.conf @@ -1,14 +1,8 @@ -worker_processes 1; - -events { - worker_connections 1024; -} - http { - # Server block for private TAXII administrative traffic. Routes traffic for downstream Workbench services. server { listen 80; - server_name localhost; + http2 on; + server_name _; root /usr/share/nginx/html; index index.html index.htm; @@ -25,16 +19,34 @@ http { location /api { client_max_body_size 50M; - proxy_pass http://attack-workbench-rest-api:3000; + + # Disable buffering for SSE streams + proxy_buffering off; + proxy_cache off; + + # Keep connection alive for long-running requests + proxy_read_timeout 600s; + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + + # Required headers for SSE + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding on; + + # Disable compression for SSE + gzip off; + + proxy_pass http://rest-api:3000; } + } # Server block for TAXII server's LetsEncrypt handshake process server { listen 80 default_server; - listen [::]:80 default_server; - server_name attack-taxii.mitre.org; + server_name taxii.example.com; location /.well-known/acme-challenge { resolver 127.0.0.11 valid=30s; # If you're wondering if 127.0.0.11 is a typo – it's not – it is actually the @@ -72,7 +84,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; location /taxii { - proxy_pass http://attack-workbench-taxii-server:5000; + proxy_pass http://taxii:8000; # limit_req zone=one burst=5; } diff --git a/docker/example-setup/template.env b/docker/example-setup/template.env new file mode 100644 index 0000000..e7ea52e --- /dev/null +++ b/docker/example-setup/template.env @@ -0,0 +1,30 @@ +# Docker Image Tags +ATTACKWB_FRONTEND_VERSION=latest +ATTACKWB_RESTAPI_VERSION=latest +ATTACKWB_TAXII_VERSION=latest + +# Frontend +#ATTACKWB_FRONTEND_HTTP_PORT=80 +#ATTACKWB_FRONTEND_HTTPS_PORT=443 +#ATTACKWB_FRONTEND_NGINX_CONFIG_FILE=./configs/frontend/nginx.api.conf +# Used for setting SSL certs in nginx +#ATTACKWB_FRONTEND_CERTS_PATH=./certs + +# REST API +#ATTACKWB_RESTAPI_HTTP_PORT=3000 +#ATTACKWB_RESTAPI_CONFIG_FILE=./configs/rest-api/rest-api-service-config.json +#ATTACKWB_RESTAPI_ENV_FILE=./configs/rest-api/.env + +# REST API Custom SSL certs (optional) +# These will be used to set NODE_EXTRA_CA_CERTS +# See compose.certs.yaml for details +#HOST_CERTS_PATH=./certs +#CERTS_FILENAME=custom-certs.pem + +# Database +#ATTACKWB_DB_PORT=27017 + +# TAXII Server +#ATTACKWB_TAXII_HTTP_PORT=5002 +#ATTACKWB_TAXII_CONFIG_DIR=./configs/taxii/config +#ATTACKWB_TAXII_ENV=dev diff --git a/docker/setup-workbench.sh b/docker/setup-workbench.sh new file mode 100755 index 0000000..6a337fd --- /dev/null +++ b/docker/setup-workbench.sh @@ -0,0 +1,836 @@ +#!/usr/bin/env bash + +set -e + +# ATT&CK Workbench Deployment Setup Script +# This script helps you quickly set up a custom ATT&CK Workbench instance +# +# SCRIPT ORGANIZATION: +# 1. Constants and Configuration +# 2. Color and Output Functions +# 3. Validation Functions +# 4. Helper Functions (prompts, file operations) +# 5. Instance Management Functions +# 6. Configuration Functions (database, environment, certificates) +# 7. Deployment Option Functions (TAXII, dev mode) +# 8. Compose Override Generation Functions +# 9. Output Functions (summary, instructions) +# 10. Main Execution Flow + +#=============================================================================== +# CONSTANTS +#=============================================================================== + +readonly DEPLOYMENT_REPO_URL="https://github.com/mitre-attack/attack-workbench-deployment.git" +readonly CTID_GITHUB_ORG="https://github.com/center-for-threat-informed-defense" +readonly MITRE_GITHUB_ORG="https://github.com/mitre-attack" + +readonly REPO_FRONTEND="attack-workbench-frontend" +readonly REPO_REST_API="attack-workbench-rest-api" +readonly REPO_TAXII="attack-workbench-taxii-server" + +readonly DB_URL_DOCKER="mongodb://mongodb/attack-workspace" +readonly DB_URL_LOCAL="mongodb://localhost:27017/attack-workspace" + +#=============================================================================== +# COLORS & OUTPUT FUNCTIONS +#=============================================================================== + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +info() { echo -e "${BLUE}$1${NC}"; } +success() { echo -e "${GREEN}$1${NC}"; } +warning() { echo -e "${YELLOW}$1${NC}"; } +error() { echo -e "${RED}$1${NC}"; } + +#=============================================================================== +# VALIDATION FUNCTIONS +#=============================================================================== + +# Validate that a required command is available +# Usage: require_command "git" "Please install git" +require_command() { + local command="$1" + local message="$2" + + if ! command -v "$command" &> /dev/null; then + error "$command is not installed or not in PATH" + if [[ -n "$message" ]]; then + echo " $message" + fi + return 1 + fi + return 0 +} + +# Validate that a file exists +# Usage: require_file "/path/to/file" "File description" +require_file() { + local file_path="$1" + local description="$2" + + if [[ ! -f "$file_path" ]]; then + error "${description:-File} not found: $file_path" + return 1 + fi + return 0 +} + +# Validate that a directory exists +# Usage: require_directory "/path/to/dir" "Directory description" +require_directory() { + local dir_path="$1" + local description="$2" + + if [[ ! -d "$dir_path" ]]; then + error "${description:-Directory} not found: $dir_path" + return 1 + fi + return 0 +} + +#=============================================================================== +# HELPER FUNCTIONS +#=============================================================================== + +# Prompt for yes/no answer with validation +# Usage: prompt_yes_no "Question?" "Y" +# Args: $1=question, $2=default (Y/N) +prompt_yes_no() { + local question="$1" + local default="$2" + PROMPT_YES_NO_RESULT="" + + while true; do + read -p "$question [y/N] " -r answer + answer=${answer:-$default} + + if [[ $answer =~ ^[YyNn]$ ]]; then + PROMPT_YES_NO_RESULT="$answer" + break + else + error "Invalid option. Please enter 'y' for yes or 'n' for no." + fi + done +} + +# Prompt for menu selection with validation +# Usage: prompt_menu "option1" "option2" "option3" +# Args: menu options +prompt_menu() { + local -a options=("$@") + local num_options=${#options[@]} + + PROMPT_MENU_RESULT="" + while true; do + for i in "${!options[@]}"; do + echo "$((i + 1))) ${options[$i]}" + done + echo "" + read -p "Select option [1-$num_options]: " -r choice + + if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$num_options" ]; then + PROMPT_MENU_RESULT="$choice" + break + else + error "Invalid option. Please select 1-$num_options." + echo "" + fi + done + + echo "" +} + +# Prompt for non-empty string with validation +# Usage: prompt_non_empty "Question" +prompt_non_empty() { + local question="$1" + PROMPT_NON_EMPTY_RESULT="" + + while true; do + read -p "$question " PROMPT_NON_EMPTY_RESULT + if [[ -n "$PROMPT_NON_EMPTY_RESULT" ]]; then + break + else + error "Input cannot be empty" + fi + done +} + +# Update or add a key=value in an env file +# Usage: update_env_file "/path/to/.env" "KEY" "value" +update_env_file() { + local env_file="$1" + local key="$2" + local value="$3" + + if grep -q "^${key}=" "$env_file"; then + local tmp + tmp="$(mktemp /tmp/env.XXXXXX)" + sed "s|^${key}=.*|${key}=${value}|" "$env_file" > "$tmp" && mv "$tmp" "$env_file" + elif grep -q "^#${key}=" "$env_file"; then + local tmp + tmp="$(mktemp /tmp/env.XXXXXX)" + sed "s|^#${key}=.*|${key}=${value}|" "$env_file" > "$tmp" && mv "$tmp" "$env_file" + else + echo "${key}=${value}" >> "$env_file" + fi +} + +# Check if a repository exists in parent directory +# Usage: check_repo_exists "/parent/dir" "repo-name" +check_repo_exists() { + local parent_dir="$1" + local repo_name="$2" + + [[ -d "$parent_dir/$repo_name" ]] +} + +# Get GitHub URL for a repository +# Usage: get_repo_url "repo-name" +get_repo_url() { + local repo_name="$1" + + if [[ "$repo_name" == "$REPO_TAXII" ]]; then + echo "$MITRE_GITHUB_ORG/$repo_name.git" + else + echo "$CTID_GITHUB_ORG/$repo_name.git" + fi +} + +#=============================================================================== +# INSTANCE MANAGEMENT FUNCTIONS +#=============================================================================== + +# Prompt for and validate instance name +get_instance_name() { + GET_INSTANCE_NAME_NAME_REF="" + + read -p "Enter instance name [my-workbench]: " GET_INSTANCE_NAME_NAME_REF + GET_INSTANCE_NAME_NAME_REF=${GET_INSTANCE_NAME_NAME_REF:-my-workbench} + + # Validate instance name + if [[ ! "$GET_INSTANCE_NAME_NAME_REF" =~ ^[a-zA-Z0-9_-]+$ ]]; then + error "Instance name can only contain letters, numbers, hyphens, and underscores" + exit 1 + fi +} + +# Check if instance exists and handle overwrite +handle_existing_instance() { + local instance_dir="$1" + local instance_name="$2" + + if [[ ! -d "$instance_dir" ]]; then + return 0 + fi + + warning "Instance '$instance_name' already exists at $instance_dir" + echo "" + + prompt_yes_no "Would you like to overwrite it?" "N" + local overwrite="$PROMPT_YES_NO_RESULT" + + if [[ ! $overwrite =~ ^[Yy]$ ]]; then + error "Aborted" + exit 1 + fi + + warning "Removing existing instance directory..." + rm -rf "$instance_dir" + echo "" +} + +# Create instance directory and copy template files +create_instance() { + local instance_dir="$1" + local deployment_dir="$2" + local source_dir="$deployment_dir/docker/example-setup" + + info "Creating instance directory: $instance_dir" + echo "" + mkdir -p "$instance_dir" + + info "Copying template files..." + # Copy all files except compose templates (they're handled by this script) + find "$source_dir" -maxdepth 1 \ + ! -name "compose.dev.yaml" \ + ! -name "compose.certs.yaml" \ + ! -name "compose.taxii.yaml" \ + ! -path "$source_dir" \ + -exec cp -r {} "$instance_dir/" \; + success "Template files copied" + echo "" +} + +#=============================================================================== +# CONFIGURATION FUNCTIONS +#=============================================================================== + +# Configure database connection and return the selected DATABASE_URL +configure_database() { + CONFIGURE_DATABASE_DB_URL_REF="" + + # echo "" + info "Configure MongoDB connection:" + echo "" + + prompt_menu \ + "Docker setup ($DB_URL_DOCKER)" \ + "Local MongoDB ($DB_URL_LOCAL)" \ + "Custom connection string" + local db_choice="$PROMPT_MENU_RESULT" + + case $db_choice in + 1) + CONFIGURE_DATABASE_DB_URL_REF="$DB_URL_DOCKER" + info "Using Docker setup: $CONFIGURE_DATABASE_DB_URL_REF" + ;; + 2) + CONFIGURE_DATABASE_DB_URL_REF="$DB_URL_LOCAL" + info "Using local MongoDB: $CONFIGURE_DATABASE_DB_URL_REF" + ;; + 3) + echo "" + prompt_non_empty "Enter MongoDB connection string:" + CONFIGURE_DATABASE_DB_URL_REF="$PROMPT_NON_EMPTY_RESULT" + info "Using custom connection: $CONFIGURE_DATABASE_DB_URL_REF" + ;; + esac + echo "" +} + +# Set up all environment files for the instance +setup_environment_files() { + local database_url="$1" + + info "Setting up environment files..." + + # Main .env file + if [[ -f "$INSTANCE_DIR/template.env" ]]; then + mv "$INSTANCE_DIR/template.env" "$INSTANCE_DIR/.env" + success "Created $INSTANCE_DIR/.env" + fi + + # REST API .env file + if [[ -f "$INSTANCE_DIR/configs/rest-api/template.env" ]]; then + local rest_api_env="$INSTANCE_DIR/configs/rest-api/.env" + mv "$INSTANCE_DIR/configs/rest-api/template.env" "$rest_api_env" + update_env_file "$rest_api_env" "DATABASE_URL" "$database_url" + success "Created $rest_api_env with DATABASE_URL configured" + fi + + # TAXII .env file (optional) + if [[ -f "$INSTANCE_DIR/configs/taxii/config/template.env" ]]; then + mv "$INSTANCE_DIR/configs/taxii/config/template.env" "$INSTANCE_DIR/configs/taxii/config/dev.env" + success "Created $INSTANCE_DIR/configs/taxii/config/dev.env" + fi + + echo "" +} + +# Configure custom SSL certificates for REST API +configure_custom_certificates() { + CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF="" + CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF="" + + # echo "" + info "Custom SSL certificates allow the REST API to trust additional CA certificates." + info "This is useful when behind a firewall that performs SSL inspection." + echo "" + + read -p "Enter host certificates path [./certs]: " user_certs_path + CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF=${user_certs_path:-./certs} + + read -p "Enter certificate filename [custom-certs.pem]: " user_certs_filename + CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF=${user_certs_filename:-custom-certs.pem} + + echo "" + info "Using certificates from: $CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF/$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" + # echo "" + + # Add custom cert configuration to .env + local env_file="$INSTANCE_DIR/.env" + update_env_file "$env_file" "HOST_CERTS_PATH" "$CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF" + update_env_file "$env_file" "CERTS_FILENAME" "$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" + success "Added certificate configuration to $env_file" + echo "" +} + +#=============================================================================== +# DEPLOYMENT OPTION FUNCTIONS +#=============================================================================== + +# Add TAXII service to compose.yaml by inserting before the volumes section +add_taxii_to_compose() { + local compose_file="$INSTANCE_DIR/compose.yaml" + local taxii_template="$DEPLOYMENT_DIR/docker/example-setup/compose.taxii.yaml" + local temp_file="$INSTANCE_DIR/compose.yaml.tmp" + + info "Adding TAXII server to compose.yaml..." + + # Insert TAXII service before the "volumes:" section + sed '/^volumes:/,$d' "$compose_file" > "$temp_file" + # Extract only the service definition, skipping the "services:" header + sed -n '/^services:/,${/^services:/!p;}' "$taxii_template" >> "$temp_file" + echo "" >> "$temp_file" + sed -n '/^volumes:/,$p' "$compose_file" >> "$temp_file" + mv "$temp_file" "$compose_file" + + success "TAXII server added to compose.yaml" + echo "" +} + +# Verify all required source repositories exist for developer mode +verify_dev_mode_repos() { + local parent_dir="$1" + local enable_taxii="$2" + local -a missing_repos=() + + if ! check_repo_exists "$parent_dir" "$REPO_FRONTEND"; then + missing_repos+=("$REPO_FRONTEND") + fi + + if ! check_repo_exists "$parent_dir" "$REPO_REST_API"; then + missing_repos+=("$REPO_REST_API") + fi + + if [[ $enable_taxii =~ ^[Yy]$ ]] && ! check_repo_exists "$parent_dir" "$REPO_TAXII"; then + missing_repos+=("$REPO_TAXII") + fi + + if [[ ${#missing_repos[@]} -gt 0 ]]; then + warning "Missing required repositories:" + for repo in "${missing_repos[@]}"; do + echo " - $repo" + done + echo "" + warning "Please clone the missing repositories to:" + echo " $parent_dir/" + echo "" + echo "Clone commands:" + for repo in "${missing_repos[@]}"; do + echo " git clone $(get_repo_url "$repo") $parent_dir/$repo" + done + else + success "All required repositories found!" + fi + echo "" +} + +# Display expected directory structure for developer mode +show_dev_mode_structure() { + local deployment_dir="$1" + local enable_taxii="$2" + + # echo "" + info "Developer mode requires source repositories to be cloned as siblings to the deployment repository." + echo "" + echo "Expected directory structure:" + echo " $(dirname "$deployment_dir")/" + echo " ├── attack-workbench-deployment/" + echo " ├── $REPO_FRONTEND/" + echo " ├── $REPO_REST_API/" + if [[ $enable_taxii =~ ^[Yy]$ ]]; then + echo " └── $REPO_TAXII/" + fi + echo "" +} + +#=============================================================================== +# COMPOSE OVERRIDE GENERATION FUNCTIONS +#=============================================================================== + +# Generate the frontend service override configuration for dev mode +generate_frontend_override() { + cat << EOF + + frontend: + image: $REPO_FRONTEND + build: ../../../$REPO_FRONTEND + develop: + watch: + # Sync source files for hot-reload + - action: sync + path: ../../../$REPO_FRONTEND/src + target: /app/src + ignore: + - node_modules/ + # Rebuild on package.json changes + - action: rebuild + path: ../../../$REPO_FRONTEND/package.json +EOF +} + +# Generate the rest-api service override configuration for dev mode +generate_rest_api_dev_override() { + cat << EOF + + rest-api: + image: $REPO_REST_API + build: ../../../$REPO_REST_API +EOF + + # Add custom cert volumes if enabled + if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + cat << 'EOF' + volumes: + - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs + environment: + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} +EOF + fi + + # Add develop watch configuration + cat << EOF + develop: + watch: + # Sync app source files + - action: sync + path: ../../../$REPO_REST_API/app + target: /usr/src/app/app + ignore: + - node_modules/ + # Restart on config changes + - action: sync+restart + path: ../../../$REPO_REST_API/resources + target: /usr/src/app/resources + # Rebuild on package.json changes + - action: rebuild + path: ../../../$REPO_REST_API/package.json +EOF +} + +# Generate the rest-api service override for production mode with custom certs +generate_rest_api_certs_override() { + cat << 'EOF' + + rest-api: + volumes: + - ${HOST_CERTS_PATH:-./certs}:/usr/src/app/certs + environment: + - NODE_EXTRA_CA_CERTS=./certs/${CERTS_FILENAME:-custom-certs.pem} +EOF +} + +# Generate the TAXII service override configuration for dev mode +generate_taxii_override() { + cat << EOF + + taxii: + image: $REPO_TAXII + build: ../../../$REPO_TAXII + develop: + watch: + # Sync source files + - action: sync + path: ../../../$REPO_TAXII/taxii + target: /app/taxii + ignore: + - node_modules/ + # Sync config files and restart + - action: sync+restart + path: ../../../$REPO_TAXII/config + target: /app/config + # Rebuild on package.json changes + - action: rebuild + path: ../../../$REPO_TAXII/package.json +EOF +} + +# Generate the complete compose.override.yaml file +generate_compose_override() { + local override_file="$1" + + # Write header + cat > "$override_file" << 'EOF' +# This file was generated by setup-workbench.sh +# It will be automatically merged with compose.yaml when running docker compose commands + +services: +EOF + + # Add service configurations based on mode + if [[ $DEV_MODE =~ ^[Yy]$ ]]; then + generate_frontend_override >> "$override_file" + generate_rest_api_dev_override >> "$override_file" + + if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then + generate_taxii_override >> "$override_file" + fi + else + # Production mode - only add rest-api if custom certs are enabled + if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + generate_rest_api_certs_override >> "$override_file" + fi + fi + + # Add newline at end + echo "" >> "$override_file" +} + +#=============================================================================== +# OUTPUT FUNCTIONS +#=============================================================================== + +# Display configuration summary +show_configuration_summary() { + local instance_dir="$1" + local override_file="$2" + local dev_mode="$3" + local enable_taxii="$4" + local enable_custom_certs="$5" + + info "Configuration files:" + echo " Main: $instance_dir/.env" + echo " Compose: $instance_dir/compose.yaml" + if [[ $dev_mode =~ ^[Yy]$ ]] || [[ $enable_custom_certs =~ ^[Yy]$ ]]; then + echo " + Override: $override_file" + fi + echo " REST API: $instance_dir/configs/rest-api/.env" + echo " REST API: $instance_dir/configs/rest-api/rest-api-service-config.json" + if [[ $enable_taxii =~ ^[Yy]$ ]]; then + echo " TAXII: $instance_dir/configs/taxii/config/.env" + fi + echo "" +} + +# Display custom SSL certificate information +show_certificate_info() { + local instance_dir="$1" + local host_certs_path="$2" + local certs_filename="$3" + + info "Custom SSL certificates:" + echo " Path: $host_certs_path" + echo " Filename: $certs_filename" + echo "" + warning "Make sure to place your certificate file at:" + if [[ "$host_certs_path" = ./* ]] || [[ "$host_certs_path" = ../* ]]; then + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + echo " $instance_dir/$host_certs_path/$certs_filename" + else + echo " $host_certs_path/$certs_filename" + fi + echo "" +} + +# Display deployment instructions +show_deployment_instructions() { + local instance_dir="$1" + local deployment_dir="$2" + local dev_mode="$3" + + info "To deploy your instance:" + echo " cd $instance_dir" + if [[ $dev_mode =~ ^[Yy]$ ]]; then + echo " docker compose up -d --build" + echo "" + info "For hot-reloading in developer mode, use watch:" + echo " docker compose watch" + else + echo " docker compose up -d" + fi + echo "" + + info "After deployment, access your Workbench at:" + echo " http://localhost" + echo "" + + info "For more information, see:" + echo " Configuration: $deployment_dir/docs/configuration.md" + echo " Deployment: $deployment_dir/docs/deployment.md" + echo "" +} + +#=============================================================================== +# BANNER +#=============================================================================== + +echo "" +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ ATT&CK Workbench Deployment Setup ║" +echo "╚════════════════════════════════════════════════════════════╝" +echo "" + +#=============================================================================== +# LOCATE DEPLOYMENT REPOSITORY +#=============================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEPLOYMENT_DIR="" + +# Check if we're already in the deployment repo +if [[ -f "$SCRIPT_DIR/example-setup/compose.yaml" ]]; then + DEPLOYMENT_DIR="$(dirname $SCRIPT_DIR)" + info "Running from deployment repository: $DEPLOYMENT_DIR" +elif [[ -d "$SCRIPT_DIR/attack-workbench-deployment" ]]; then + DEPLOYMENT_DIR="$SCRIPT_DIR/attack-workbench-deployment" + info "Found deployment repository: $DEPLOYMENT_DIR" +elif [[ -f "./docker/example-setup/compose.yaml" ]]; then + DEPLOYMENT_DIR="$(pwd)" + info "Running from current directory: $DEPLOYMENT_DIR" +else + # Not in the repo - need to clone or find it + warning "Not in the attack-workbench-deployment repository" + + # Check if git is available + if ! command -v git &> /dev/null; then + error "Git is not installed. Please install git or manually clone the repository." + exit 1 + fi + + echo "" + prompt_yes_no "Would you like to clone the repository?" "Y" + CLONE_REPO="$PROMPT_YES_NO_RESULT" + + if [[ $CLONE_REPO =~ ^[Yy]$ ]]; then + info "Cloning repository from $DEPLOYMENT_REPO_URL..." + + CLONE_DIR="./attack-workbench-deployment" + if [[ -d "$CLONE_DIR" ]]; then + error "Directory $CLONE_DIR already exists" + exit 1 + fi + + git clone "$DEPLOYMENT_REPO_URL" "$CLONE_DIR" + DEPLOYMENT_DIR="$(cd "$CLONE_DIR" && pwd)" + success "Repository cloned to $DEPLOYMENT_DIR" + else + error "Cannot proceed without the deployment repository" + exit 1 + fi +fi +echo "" + +cd "$DEPLOYMENT_DIR" + +#=============================================================================== +# PREREQUISITE CHECKS +#=============================================================================== + +# Check for Docker (warn but don't fail - user might not deploy immediately) +if ! require_command "docker" "Please install Docker to deploy the Workbench. Visit: https://docs.docker.com/get-docker/"; then + warning "Docker is not installed - you will need it to deploy the Workbench" +fi + +# Check for Docker Compose (warn but don't fail) +if ! docker compose version &> /dev/null 2>&1; then + warning "Docker Compose is not available" + echo " Please install Docker Compose (usually included with Docker Desktop)" +fi + + +#=============================================================================== +# MAIN EXECUTION FLOW +#=============================================================================== + +#--------------------------------------- +# Instance Setup +#--------------------------------------- + +# echo "" +info "Setting up your Workbench instance..." +echo "" + +get_instance_name +INSTANCE_NAME="$GET_INSTANCE_NAME_NAME_REF" +INSTANCE_DIR="$DEPLOYMENT_DIR/instances/$INSTANCE_NAME" + +handle_existing_instance "$INSTANCE_DIR" "$INSTANCE_NAME" +create_instance "$INSTANCE_DIR" "$DEPLOYMENT_DIR" + +#--------------------------------------- +# Deployment Options +#--------------------------------------- + +# echo "" +info "Configuring deployment options..." +echo "" + +prompt_yes_no "Do you want to deploy with the TAXII server?" "N" +ENABLE_TAXII="$PROMPT_YES_NO_RESULT" +echo "" + +if [[ ! $ENABLE_TAXII =~ ^[Yy]$ ]]; then + # Remove TAXII configs if not needed + if [[ -d "$INSTANCE_DIR/configs/taxii" ]]; then + rm -rf "$INSTANCE_DIR/configs/taxii" + fi +fi + +#--------------------------------------- +# Environment Configuration +#--------------------------------------- + +configure_database +DATABASE_URL="$CONFIGURE_DATABASE_DB_URL_REF" +setup_environment_files "$DATABASE_URL" + +if [[ $ENABLE_TAXII =~ ^[Yy]$ ]]; then + add_taxii_to_compose +fi + +# echo "" +success "Instance '$INSTANCE_NAME' created successfully!" +echo "" + +#--------------------------------------- +# Additional Options +#--------------------------------------- + + +prompt_yes_no "Do you want to set up in developer mode (build from source)?" "N" +DEV_MODE="$PROMPT_YES_NO_RESULT" + +prompt_yes_no "Do you want to configure custom SSL certificates for the REST API?" "N" +ENABLE_CUSTOM_CERTS="$PROMPT_YES_NO_RESULT" + +HOST_CERTS_PATH="./certs" +CERTS_FILENAME="custom-certs.pem" + +echo "" +if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + configure_custom_certificates + HOST_CERTS_PATH="$CONFIGURE_CUSTOM_CERTIFICATES_HOST_CERTS_REF" + CERTS_FILENAME="$CONFIGURE_CUSTOM_CERTIFICATES_CERTS_FILENAME_REF" +fi + +#--------------------------------------- +# Developer Mode Setup +#--------------------------------------- + +if [[ $DEV_MODE =~ ^[Yy]$ ]]; then + show_dev_mode_structure "$DEPLOYMENT_DIR" "$ENABLE_TAXII" + PARENT_DIR="$(dirname "$DEPLOYMENT_DIR")" + verify_dev_mode_repos "$PARENT_DIR" "$ENABLE_TAXII" +fi + +#--------------------------------------- +# Generate Compose Override +#--------------------------------------- + +OVERRIDE_FILE="$INSTANCE_DIR/compose.override.yaml" + +if [[ $DEV_MODE =~ ^[Yy]$ ]] || [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + info "Generating compose.override.yaml..." + generate_compose_override "$OVERRIDE_FILE" + success "Created $OVERRIDE_FILE" + echo "" +fi + +#--------------------------------------- +# Summary +#--------------------------------------- + +show_configuration_summary "$INSTANCE_DIR" "$OVERRIDE_FILE" "$DEV_MODE" "$ENABLE_TAXII" "$ENABLE_CUSTOM_CERTS" + +if [[ $ENABLE_CUSTOM_CERTS =~ ^[Yy]$ ]]; then + show_certificate_info "$INSTANCE_DIR" "$HOST_CERTS_PATH" "$CERTS_FILENAME" +fi + +show_deployment_instructions "$INSTANCE_DIR" "$DEPLOYMENT_DIR" "$DEV_MODE" diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..59671ee --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,108 @@ +# Configuration + +## Docker Compose Environment Variables + +We make heavy use of string interpolation to minimize having to modify the Docker Compose files. +Consequently, that means you must set a bunch of environment variables when using these templates. +Fortunately, we've provided a dotenv template that you can source. + +Copy `template.env` to `.env` and customize the values as needed: + +```bash +cp template.env .env +``` + +Available environment variables: + +### Docker Image Tags + +| Variable | Default Value | Description | +|-----------------------------|---------------|-------------------------------| +| `ATTACKWB_FRONTEND_VERSION` | `latest` | Frontend Docker image tag | +| `ATTACKWB_RESTAPI_VERSION` | `latest` | REST API Docker image tag | +| `ATTACKWB_TAXII_VERSION` | `latest` | TAXII server Docker image tag | + +### Frontend + +| Variable | Default Value | Description | +|---------------------------------------|-------------------------------------|------------------------------------| +| `ATTACKWB_FRONTEND_HTTP_PORT` | `80` | Frontend HTTP port | +| `ATTACKWB_FRONTEND_HTTPS_PORT` | `443` | Frontend HTTPS port | +| `ATTACKWB_FRONTEND_NGINX_CONFIG_FILE` | `./configs/frontend/nginx.api.conf` | Path to nginx config file | +| `ATTACKWB_FRONTEND_CERTS_PATH` | `./certs` | Path to SSL certificates for nginx | + +There are four sample nginx config files that can be used as reference: + +- `nginx.conf`: Minimal nginx configuration that only routes the Workbench frontend. +- `nginx.ssl.conf`: Same as `nginx.conf` but with an SSL redirect. You need to provide your own SSL certs in the `ATTACKWB_FRONTEND_CERTS_PATH` directory. +- `nginx.api.conf` (default): Nginx configuration with an additional `/api` location block for connecting to the REST API container. +- `nginx.api.ssl.conf`: Same as `nginx.api.conf` but with an SSL redirect. You need to provide your own SSL certs in the `ATTACKWB_FRONTEND_CERTS_PATH` directory. + +### REST API + +| Variable | Default Value | Description | +|--------------------------------|---------------------------------------------------|---------------------------------------------------| +| `ATTACKWB_RESTAPI_HTTP_PORT` | `3000` | REST API port | +| `ATTACKWB_RESTAPI_CONFIG_FILE` | `./configs/rest-api/rest-api-service-config.json` | Path to REST API JSON config file | +| `ATTACKWB_RESTAPI_ENV_FILE` | `./configs/rest-api/.env` | Path to REST API environment variable config file | + +### REST API Custom SSL certs (Optional) + +These will be used to set `NODE_EXTRA_CA_CERTS` in the REST API docker container. +See `compose.certs.yaml` for details + +| Variable | Default Value | Description | +|-------------------|--------------------|-------------------------------| +| `HOST_CERTS_PATH` | `./certs` | Path to custom cert directory | +| `CERTS_FILENAME` | `custom-certs.pem` | Filename of custom cert | + +### Database + +| Variable | Default Value | Description | +|--------------------|---------------|--------------| +| `ATTACKWB_DB_PORT` | `27017` | MongoDB port | + +### TAXII Server + +| Variable | Default Value | Description | +|-----------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------| +| `ATTACKWB_TAXII_HTTP_PORT` | `5002` | TAXII server port | +| `ATTACKWB_TAXII_CONFIG_DIR` | `./configs/taxii/config` | DIrectory to find TAXII config file in | +| `ATTACKWB_TAXII_ENV` | `dev` | Specifies the name of the dotenv file to load (e.g., A value of `dev` tells the TAXII server to load `dev.env`) | + +## Service-Specific Configuration + +Each service has its own configuration directory: + +### Frontend + +**Config files**: [configs/frontend/](../docker/example-setup/configs/frontend/) + +The frontend container is an Nginx instance which serves the frontend SPA and reverse proxies requests to the backend REST API. +We provide a basic `nginx.conf` template in the aforementioned directory that should get you started. +Refer to the [frontend documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) +for further details on customizing the Workbench frontend. + +### REST API + +**Config files**: [configs/rest-api/](../docker/example-setup/configs/rest-api/) + +The backend REST API loads runtime configurations from environment variables, as well as from a JSON configuration file. +Templates are provided in the aforementioned directory. +Refer to the [REST API usage documentation](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/blob/main/USAGE.md#configuration) +for further details on customizing the backend. + +**Important**: For production deployments, set the following environment variables in your `.env` file to ensure persistent secrets across server restarts: + +- `SESSION_SECRET` - Secret used to sign session cookies +- `MONGOSTORE_CRYPTO_SECRET` - Secret used to encrypt session data in MongoDB + +Generate secure secrets using: `node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"` + +### TAXII Server + +**Config files**: [configs/taxii/config/](../docker/example-setup/configs/taxii/config/) + +The TAXII server loads all runtime configuration parameters from a dotenv file. +The specific filename of the dotenv file is specified by the `ATTACKWB_TAXII_ENV` environment variable. +For example, a value of `dev` tells the TAXII server to load `dev.env`. diff --git a/docs/database-backups.md b/docs/database-backups.md new file mode 100644 index 0000000..197038c --- /dev/null +++ b/docs/database-backups.md @@ -0,0 +1,69 @@ +# Database Backups + +The MongoDB commands `mongodump` and `mongorestore` can be used to create the database backup files and to restore the database using those files. + +The `compose.yaml` file maps the `database-backup/` directory on the host to the `/dump` directory +in the container in order to ease access to the backup files and to make sure those files exist even if the container is deleted. +This directory is listed in the `.gitignore` file so the backup files will not be added to the git repo. + +To access the command line inside the container, run this command from the host: + +```shell +docker exec -it attack-workbench-database bash +``` + +## Single Archive File + +These commands backup the data in a single compressed file. + +### Creating a Database Backup + +Create the backup as a compressed archive file: + +```shell +# From inside the attack-workbench-database container +mongodump --db attack-workspace --gzip --archive=dump/workspace.archive.gz +``` + +This creates a file in `/dump` in the container (`database-backup/` on the host). + +### Restoring the Database from the Backup + +The backup file must be in `database-backup/` on the host. + +Restoring from the compressed archive file: + +```shell +# From inside the attack-workbench-database container +mongorestore --drop --gzip --archive=dump/workspace.archive.gz +``` + +This drops the collections from the database, recreates the collections, loads the backed up documents into those collections, and rebuilds the indexes. + +## Multiple Files + +These commands backup the data in multiple files (a file for each collection and index). + +### Creating a Database Backup + +Create the backup files: + +```shell +# From inside the attack-workbench-database container +mongodump --db attack-workspace +``` + +This creates a set of files in `/dump/attack-workspace` in the container (`/database-backup/attack-workspace` on the host). + +### Restoring the Database from the Backup Files + +The backup files must be in `database-backup/attack-workspace` on the host. + +Restoring from the backup files: + +```shell +# From inside the attack-workbench-database container +mongorestore --drop dump/ +``` + +This drops the collections from the database, recreates the collections, loads the backed up documents into those collections, and rebuilds the indexes. diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..bcfe8c7 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,57 @@ +# Deployment Options + +## Docker Compose + +The ATT&CK Workbench can be deployed using Docker Compose with two different configurations: + +### 1. Using Pre-built Images (Recommended) + +Use `compose.yaml` to pull pre-built images directly from GitHub Container Registry (GHCR): + +```bash +# Deploy with pre-built images +docker compose up -d + +# Deploy with TAXII server +docker compose --profile with-taxii up -d + +# Stop the deployment +docker compose down +``` + +### 2. Building from Source + +Use `compose.dev.yaml` in combination with `compose.yaml` to build images from source code: + +```bash +# Build and deploy from source +docker compose -f compose.yaml -f compose.dev.yaml up -d --build + +# Build and deploy with TAXII server +docker compose -f compose.yaml -f compose.dev.yaml --profile with-taxii up -d --build + +# Stop the deployment +docker compose -f compose.yaml -f compose.dev.yaml down +``` + +**Note**: When building from source, you need the following three source repositories to be available as sibling directories to this deployment repository: + +- [attack-workbench-frontend](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/) +- [attack-workbench-rest-api](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/) +- [attack-workbench-taxii-server](https://github.com/mitre-attack/attack-workbench-taxii-server) + +The directory structure should look like this: + +```bash +. +├── attack-workbench-deployment +├── attack-workbench-frontend +├── attack-workbench-rest-api +└── attack-workbench-taxii-server (optional) +``` + +### Data Persistence + +MongoDB data is persisted in the `workspace-data` named Docker volume. +Thus, the `database` service can be deleted and re-deployed without losing access to the database. +The database volume will be remounted to the `database` service upon deployment. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..c21d998 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,20 @@ +# Troubleshooting + +Here are a few commands you can use to troubleshoot the docker compose setup. + +```bash +# View running containers +docker compose ps + +# Show logs for all running containers +docker compose logs + +# Follow logs +docker compose logs -f + +# Show logs for a specific container +docker compose logs frontend +docker compose logs rest-api +docker compose logs database +docker compose logs taxii +``` diff --git a/k8s/base/configmap-taxii.yaml b/k8s/base/configmap-taxii.yaml index dbf7d23..b2c7da6 100644 --- a/k8s/base/configmap-taxii.yaml +++ b/k8s/base/configmap-taxii.yaml @@ -6,7 +6,7 @@ metadata: data: TAXII_ENV: "prod" TAXII_APP_ADDRESS: "0.0.0.0" - TAXII_APP_PORT: "5002" + TAXII_APP_PORT: "8000" TAXII_HTTPS_ENABLED: "false" TAXII_SSL_PRIVATE_KEY: "" TAXII_SSL_PUBLIC_KEY: "" diff --git a/k8s/overlays/dev/configmap-taxii-dev.yaml b/k8s/overlays/dev/configmap-taxii-dev.yaml index d0d282b..4fab48c 100644 --- a/k8s/overlays/dev/configmap-taxii-dev.yaml +++ b/k8s/overlays/dev/configmap-taxii-dev.yaml @@ -6,7 +6,7 @@ metadata: data: TAXII_ENV: "dev" TAXII_APP_ADDRESS: "0.0.0.0" - TAXII_APP_PORT: "5002" + TAXII_APP_PORT: "8000" TAXII_HTTPS_ENABLED: "false" TAXII_SSL_PRIVATE_KEY: "" TAXII_SSL_PUBLIC_KEY: "" diff --git a/k8s/overlays/prod/configmap-taxii-prod.yaml b/k8s/overlays/prod/configmap-taxii-prod.yaml index 2c3d59b..02d45e9 100644 --- a/k8s/overlays/prod/configmap-taxii-prod.yaml +++ b/k8s/overlays/prod/configmap-taxii-prod.yaml @@ -6,7 +6,7 @@ metadata: data: TAXII_ENV: "prod" TAXII_APP_ADDRESS: "0.0.0.0" - TAXII_APP_PORT: "5002" + TAXII_APP_PORT: "8000" TAXII_HTTPS_ENABLED: "true" TAXII_SSL_PRIVATE_KEY: "/etc/ssl/private/tls.key" TAXII_SSL_PUBLIC_KEY: "/etc/ssl/certs/tls.crt" diff --git a/template.env b/template.env deleted file mode 100644 index 0f33b85..0000000 --- a/template.env +++ /dev/null @@ -1,22 +0,0 @@ -# Docker Image Tags -ATTACKWB_FRONTEND_VERSION=latest -ATTACKWB_RESTAPI_VERSION=latest -ATTACKWB_TAXII_VERSION=latest - -# HTTP Listener Ports -ATTACKWB_FRONTEND_HTTP_PORT=80 -ATTACKWB_FRONTEND_HTTPS_PORT=443 -ATTACKWB_RESTAPI_HTTP_PORT=3000 -ATTACKWB_DB_PORT=27017 -ATTACKWB_TAXII_HTTP_PORT=5002 - -# Nginx SSL/TLS certs path -ATTACKWB_FRONTEND_CERTS_PATH=./certs - -# TAXII dotenv filename -ATTACKWB_TAXII_ENV=dev - -# For setting custom SSL/TLS certs in nginx -# See compose.certs.yaml for details -HOST_CERTS_PATH= -CERTS_FILENAME= \ No newline at end of file