Skip to content

Commit

Permalink
Add a Dockerfile for the frontend project and a Docker Compose file f…
Browse files Browse the repository at this point in the history
…or all of the ATT&CK Workbench services.

Modify the method for obtaining URLs for the rest-api and collection-manager.
Add instructions for a Docker Compose deployment.
  • Loading branch information
ElJocko committed Feb 9, 2021
1 parent f02b0b6 commit bcdc631
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 34 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
npm-debug.log
32 changes: 32 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM node:10 as build

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY ./app/package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production

# Bundle app source
COPY ./app .

# Build the bundle
RUN npm run build-prod

FROM nginx:1.19

# Remove the default nginx website
RUN rm -rf /usr/share/nginx/html/*

# Copy the nginx config file
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf

# Copy the application bundles
COPY --from=build /usr/src/app/dist/app /usr/share/nginx/html


6 changes: 3 additions & 3 deletions app/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
"maximumWarning": "15kb",
"maximumError": "20kb"
}
]
}
Expand Down Expand Up @@ -133,4 +133,4 @@
}
},
"defaultProject": "app"
}
}
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"build-prod": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ApiConnector } from '../api-connector';
providedIn: 'root'
})
export class CollectionManagerConnectorService extends ApiConnector {
private get baseUrl(): string { return `${environment.integrations.collection_manager.url}:${environment.integrations.collection_manager.port}/api`; }
private get baseUrl(): string { return environment.integrations.collection_manager.url; }

constructor(private http: HttpClient, private snackbar: MatSnackBar) { super(snackbar) }

Expand All @@ -22,7 +22,7 @@ export class CollectionManagerConnectorService extends ApiConnector {
*/
public getRemoteIndex(url: string): Observable<CollectionIndex> {
let params = new HttpParams({encoder: new CustomEncoder()}).set("url", url);
return this.http.get(`${this.baseUrl}/collection-indexes/remote`, {params}).pipe(
return this.http.get(`${this.baseUrl}/collection-indexes/remote`, {params}).pipe(
tap(_ => console.log("downloaded index at", url)), // on success, trigger the success notification
map(index => { return {
"collection_index": index,
Expand All @@ -46,5 +46,5 @@ class CustomEncoder implements HttpParameterCodec {
}
decodeValue(value: string): string {
return decodeURIComponent(value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ export interface Paginated {
providedIn: 'root'
})
export class RestApiConnectorService extends ApiConnector {
private get baseUrl(): string { return `${environment.integrations.rest_api.url}:${environment.integrations.rest_api.port}/api`; }
private get baseUrl(): string { return environment.integrations.rest_api.url; }
private headers: HttpHeaders = new HttpHeaders({ 'Content-Type': 'application/json' });
constructor(private http: HttpClient, private snackbar: MatSnackBar) { super(snackbar); }

// ___ _____ _____ __ _ ___ ___ ___
// ___ _____ _____ __ _ ___ ___ ___
// / __|_ _|_ _\ \/ / /_\ | _ \_ _/ __|
// \__ \ | | | | > < / _ \| _/| |\__ \
// |___/ |_| |___/_/\_\ /_/ \_\_| |___|___/
//
//

/**
* Factory to create a new STIX get-all function
Expand All @@ -89,7 +89,7 @@ export class RestApiConnectorService extends ApiConnector {
let url = `${this.baseUrl}/${plural}`;
return this.http.get(url, {headers: this.headers, params: query}).pipe(
tap(results => console.log(`retrieved ${plural}`, results)), // on success, trigger the success notification
map(results => {
map(results => {
let response = results as any;
if (limit || offset) { // returned a paginated
let data = response.data as Array<any>;
Expand All @@ -101,7 +101,7 @@ export class RestApiConnectorService extends ApiConnector {
return response;
} else { //returned a stixObject[]
return {
pagination: {
pagination: {
total: response.length,
limit: -1,
offset: -1
Expand Down Expand Up @@ -208,11 +208,11 @@ export class RestApiConnectorService extends ApiConnector {
if (attackType == "collection") query = query.set("retrieveContents", "true");
return this.http.get(url, {headers: this.headers, params: query}).pipe(
tap(result => console.log(`retrieved ${attackType}`, result)), // on success, trigger the success notification
map(result => {
map(result => {
let x = result as any;
if (Array.isArray(result) && result.length == 0) {
if (Array.isArray(result) && result.length == 0) {
console.warn("empty result")
return [];
return [];
}
return x.map(y => {
if (y.stix.type == "malware" || y.stix.type == "tool") return new Software(y.stix.type, y);
Expand Down Expand Up @@ -473,28 +473,28 @@ export class RestApiConnectorService extends ApiConnector {
public get deleteCollection() { return this.deleteStixObjectFactory("collection"); }


// ___ ___ _ _ _____ ___ ___ _ _ ___ _ _ ___ ___ ___
// ___ ___ _ _ _____ ___ ___ _ _ ___ _ _ ___ ___ ___
// | _ \ __| | /_\_ _|_ _/ _ \| \| / __| || |_ _| _ \/ __|
// | / _|| |__ / _ \| | | | (_) | .` \__ \ __ || || _/\__ \
// |_|_\___|____/_/ \_\_| |___\___/|_|\_|___/_||_|___|_| |___/
//
//

/**
* Get relationships
* Get relationships
*
* @param {string} [sourceRef] STIX id of referenced object. Only retrieve relationships that reference this object in the source_ref property.
* @param {string} [targetRef] STIX id of referenced object. Only retrieve relationships that reference this object in the target_ref property.
* @param {string} [relationshipType] Only retrieve relationships that have a matching relationship_type.
* @param {string} [sourceType] retrieve objects where the source object is this ATT&CK type
* @param {string} [targetType] retrieve objects where the source object is this ATT&CK type
* @param {number} [limit] The number of relationships to retrieve.
* @param {number} [limit] The number of relationships to retrieve.
* @param {number} [offset] The number of relationships to skip.
* @returns {Observable<Paginated>} paginated data of the relationships
* @memberof RestApiConnectorService
*/
public getRelatedTo(sourceRef?: string, targetRef?: string, sourceType?: AttackType, targetType?: AttackType, relationshipType?: string, limit?: number, offset?: number): Observable<Paginated> {
let query = new HttpParams();

if (sourceRef) query = query.set("sourceRef", sourceRef);
if (targetRef) query = query.set("targetRef", targetRef);

Expand Down Expand Up @@ -532,11 +532,11 @@ export class RestApiConnectorService extends ApiConnector {
)
}

// ___ ___ _ _ ___ ___ _____ ___ ___ _ _ _ ___ ___ ___
// ___ ___ _ _ ___ ___ _____ ___ ___ _ _ _ ___ ___ ___
// / __/ _ \| | | | | __/ __|_ _|_ _/ _ \| \| | /_\ | _ \_ _/ __|
// | (_| (_) | |__| |__| _| (__ | | | | (_) | .` | / _ \| _/| |\__ \
// \___\___/|____|____|___\___| |_| |___\___/|_|\_| /_/ \_\_| |___|___/
//
//

/**
* POST a collection bundle (including a collection SDO and the objects to which it refers) to the back-end
Expand All @@ -554,19 +554,19 @@ export class RestApiConnectorService extends ApiConnector {
if (preview) console.log("previewed collection import", result);
else this.handleSuccess("imported collection")(result);
}),
map(result => {
return new Collection(result);
map(result => {
return new Collection(result);
}),
catchError(this.handleError_single<Collection>()),
share()
)
}

// ___ ___ _ _ ___ ___ _____ ___ ___ _ _ ___ _ _ ___ _____ __ _ ___ ___ ___
// ___ ___ _ _ ___ ___ _____ ___ ___ _ _ ___ _ _ ___ _____ __ _ ___ ___ ___
// / __/ _ \| | | | | __/ __|_ _|_ _/ _ \| \| | |_ _| \| | \| __\ \/ / /_\ | _ \_ _/ __|
// | (_| (_) | |__| |__| _| (__ | | | | (_) | .` | | || .` | |) | _| > < / _ \| _/| |\__ \
// \___\___/|____|____|___\___| |_| |___\___/|_|\_| |___|_|\_|___/|___/_/\_\ /_/ \_\_| |___|___/
//
//

/**
* Post a new collection index to the back-end
Expand Down
6 changes: 2 additions & 4 deletions app/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ export const environment = {
// configuration for the ATT&CK Workbench REST API
// https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api
enabled: true, // MUST be true for application operation
url: "http://localhost",
port: "3000",
url: "api"
},
collection_manager: {
// configuration for the ATT&CK Workbench Collection Manager
// https://github.com/center-for-threat-informed-defense/attack-workbench-collection-manager
enabled: true, //if false, all systems for collection management will be disabled
url: "http://localhost",
port: "3001"
url: "cm-api"
}
}
};
6 changes: 2 additions & 4 deletions app/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ export const environment = {
// configuration for the ATT&CK Workbench REST API
// https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api
enabled: true, // MUST be true for application operation
url: "http://localhost",
port: "3000",
url: "http://localhost:3000/api"
},
collection_manager: {
// configuration for the ATT&CK Workbench Collection Manager
// https://github.com/center-for-threat-informed-defense/attack-workbench-collection-manager
enabled: true, //if false, all systems for collection management will be disabled
url: "http://localhost",
port: "3001"
url: "http://localhost:3001/cm-api"
}
}
};
Expand Down
36 changes: 36 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: "3.9"
services:
frontend:
container_name: attack-workbench-frontend
build: .
depends_on:
- rest-api
- collection-manager
ports:
- "80:80"

rest-api:
container_name: attack-workbench-rest-api
build: ../rest-api
depends_on:
- mongodb
ports:
- "3000:3000"
environment:
- DATABASE_URL=mongodb://attack-workbench-database/attack-workspace

collection-manager:
container_name: attack-workbench-collection-manager
build: ../collection-manager
depends_on:
- rest-api
ports:
- "3001:3001"
environment:
- WORKBENCH_HOST=http://attack-workbench-rest-api

mongodb:
container_name: attack-workbench-database
image: mongo
ports:
- "27017:27017"
43 changes: 43 additions & 0 deletions docs/docker-compose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Docker Compose Installation

This document describes how to install the ATT&CK Workbench components using Docker Compose.

## Project Structure

This project (ATT&CK Workbench Frontend) includes a `docker-compose.yml` file that configures the Docker Compose installation.
In addition to this project, the ATT&CK Workbench REST API and ATT&CH Workbench Collection Manager projects must be pulled from the github repository.
These projects must be placed under a common parent directory:

```
|-- <common parent directory>
|-- frontend
|-- rest-api
|-- collection-manager
```

## Install Process

1. Navigate to the `frontend` directory (containing the `docker-compose.yml` file)
2. Run the command:
```shell
docker-compose up
```

This command will build all of the necessary Docker images and run the corresponding Docker containers.

### Containers

When deployed using Docker Compose, an ATT&CK Workbench installation will include four containers:
* frontend
* rest-api
* collection-manager
* mongodb

These containers will communicate as illustrated in the diagram below.
The `nginx` instance (part of the `frontend` container) is responsible for serving the statically built code for the ATT&CK Workbench web application.
It also acts as a reverse proxy for the `rest-api` and `collection-manager` services.

![Workbench Configuration](images/workbench-configuration-docker-compose.png)

Note that the `docker-compose.yml` file exposes the ATT&CK Workbench web application on port 80.
The `nginx` configuration file (`nginx/nginx.conf`) can be modified to use HTTPS and port 443, depending on your operational requirements.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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 {
proxy_pass http://attack-workbench-rest-api:3000;
}

location /cm-api {
proxy_pass http://attack-workbench-collection-manager:3001;
}
}
}

0 comments on commit bcdc631

Please sign in to comment.