Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define services in config and add Docker workflow #41

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
FLAG_REGEX="[A-Z0-9]{31}="
TULIP_MONGO="mongo:27017"

# The IP of the virtual machine.
VM_IP="10.10.3.1"

# The location of your pcaps as seen by the host
TRAFFIC_DIR_HOST="./services/test_pcap"

Expand All @@ -12,6 +15,12 @@ TICK_START="2018-06-27T13:00+02:00"
# Tick length in ms
TICK_LENGTH=180000

# The location of your services.json file as seen by the host
SERVICES_PATH_HOST="./services.json"

# The location of your services.json file as seen by the container
SERVICES_PATH_DOCKER="/services.json"

#PCAP_OVER_IP="host.docker.internal:1337"
#For multiple PCAP_OVER_IP you can comma separate
#PCAP_OVER_IP="host.docker.internal:1337,otherhost.com:5050"
51 changes: 51 additions & 0 deletions .github/workflows/docker-ghcr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Build and Upload Docker image

on:
push:
branches: ['master']

env:
REGISTRY: ghcr.io
IMAGE_NAME_PREFIX: ${{ github.repository }}

jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

strategy:
matrix:
build:
- image_name: frontend
file: Dockerfile-frontend
context: frontend

- image_name: api
file: Dockerfile-api
context: services/api

- image_name: assembler
file: Dockerfile-assembler
context: services/go-importer

- image_name: enricher
file: Dockerfile-enricher
context: services/go-importer

steps:
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push image
uses: docker/build-push-action@v5
with:
context: "{{defaultContext}}:${{ matrix.build.context }}"
file: ${{ matrix.build.file }}
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_PREFIX }}-${{ matrix.build.image_name }}:latest
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,4 @@ workspace.xml
.idea

/traffic
services.json
21 changes: 6 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,20 @@ Tulip was developed by Team Europe for use in the first International Cyber Secu
![](./demo_images/demo2.png)
![](./demo_images/demo3.png)

## Configuration
Before starting the stack, edit `services/configurations.py`:

```
vm_ip = "10.60.4.1"
services = [{"ip": vm_ip, "port": 18080, "name": "BIOMarkt"},
{"ip": vm_ip, "port": 5555, "name": "SaaS"},
]
```

You can also edit this during the CTF, just rebuild the `api` service:
```
docker-compose up --build -d api
```

## Usage

The stack can be started with docker-compose, after creating an `.env` file. See `.env.example` as an example of how to configure your environment.

```
cp .env.example .env
# < Edit the .env file with your favourite text editor >

cp services.json.example services.json
# < Edit the services.json file >

docker-compose up -d --build
```

To ingest traffic, it is recommended to create a shared bind mount with the docker-compose. One convenient way to set this up is as follows:
1. On the vulnbox, start a rotating packet sniffer (e.g. tcpdump, suricata, ...)
1. Using rsync, copy complete captures to the machine running tulip (e.g. to /traffic)
Expand Down
74 changes: 74 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
version: "3.2"
services:
mongo:
image: mongo:5
networks:
- internal
restart: always
ports:
- "127.0.0.1:27017:27017"

frontend:
image: ghcr.io/iamsilk/tulip-frontend:latest
restart: unless-stopped
ports:
- "3000:3000"
depends_on:
- mongo
networks:
- internal
environment:
API_SERVER_ENDPOINT: http://api:5000/

api:
image: ghcr.io/iamsilk/tulip-api:latest
restart: unless-stopped
depends_on:
- mongo
networks:
- internal
volumes:
- ${TRAFFIC_DIR_HOST}:${TRAFFIC_DIR_DOCKER}:ro
- ${SERVICES_PATH_HOST}:${SERVICES_PATH_DOCKER}:ro
environment:
VM_IP: ${VM_IP}
TULIP_MONGO: mongo:27017
TULIP_TRAFFIC_DIR: ${TRAFFIC_DIR_DOCKER}
TULIP_SERVICES_PATH: ${SERVICES_PATH_DOCKER}
FLAG_REGEX: ${FLAG_REGEX}
TICK_START: ${TICK_START}
TICK_LENGTH: ${TICK_LENGTH}

assembler:
image: ghcr.io/iamsilk/tulip-assembler:latest
restart: unless-stopped
depends_on:
- mongo
networks:
- internal
volumes:
- ${TRAFFIC_DIR_HOST}:${TRAFFIC_DIR_DOCKER}:ro
command: "./assembler -dir ${TRAFFIC_DIR_DOCKER}"
environment:
TULIP_MONGO: ${TULIP_MONGO}
FLAG_REGEX: ${FLAG_REGEX}
PCAP_OVER_IP: ${PCAP_OVER_IP}
extra_hosts:
- "host.docker.internal:host-gateway"


enricher:
image: ghcr.io/iamsilk/tulip-enricher:latest
restart: unless-stopped
depends_on:
- mongo
networks:
- internal
volumes:
- ${TRAFFIC_DIR_HOST}:${TRAFFIC_DIR_DOCKER}:ro
command: "./enricher -eve ${TRAFFIC_DIR_DOCKER}/eve.json"
environment:
TULIP_MONGO: ${TULIP_MONGO}

networks:
internal:
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ services:
- internal
volumes:
- ${TRAFFIC_DIR_HOST}:${TRAFFIC_DIR_DOCKER}:ro
- ${SERVICES_PATH_HOST}:${SERVICES_PATH_DOCKER}:ro
environment:
VM_IP: ${VM_IP}
TULIP_MONGO: mongo:27017
TULIP_TRAFFIC_DIR: ${TRAFFIC_DIR_DOCKER}
TULIP_SERVICES_PATH: ${SERVICES_PATH_DOCKER}
FLAG_REGEX: ${FLAG_REGEX}
TICK_START: ${TICK_START}
TICK_LENGTH: ${TICK_LENGTH}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/FlowList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function FlowList() {
const transformedFlowData = flowData?.map((flow) => ({
...flow,
service_tag:
services?.find((s) => s.ip === flow.dst_ip && s.port === flow.dst_port)
services?.find((s) => s.port === flow.dst_port)
?.name ?? "unknown",
}));

Expand Down
26 changes: 26 additions & 0 deletions services.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"port": 9876,
"name": "cc_market"
},
{
"port": 80,
"name": "maze"
},
{
"port": 8080,
"name": "scadent"
},
{
"port": 5000,
"name": "starchaser"
},
{
"port": 1883,
"name": "scadnet_bin"
},
{
"port": -1,
"name": "other"
}
]
26 changes: 19 additions & 7 deletions services/api/configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,30 @@

import os
from pathlib import Path
import json

traffic_dir = Path(os.getenv("TULIP_TRAFFIC_DIR", "/traffic"))
tick_length = os.getenv("TICK_LENGTH", 2*60*1000)
start_date = os.getenv("TICK_START", "2018-06-27T13:00+02:00")
mongo_host = os.getenv("TULIP_MONGO", "localhost:27017")
flag_regex = os.getenv("FLAG_REGEX", "[A-Z0-9]{31}=")
mongo_server = f'mongodb://{mongo_host}/'
vm_ip = "10.10.3.1"

services = [{"ip": vm_ip, "port": 9876, "name": "cc_market"},
{"ip": vm_ip, "port": 80, "name": "maze"},
{"ip": vm_ip, "port": 8080, "name": "scadent"},
{"ip": vm_ip, "port": 5000, "name": "starchaser"},
{"ip": vm_ip, "port": 1883, "name": "scadnet_bin"},
{"ip": vm_ip, "port": -1, "name": "other"}]
vm_ip = os.getenv("VM_IP", "10.10.3.1")
services_path = os.getenv("TULIP_SERVICES_PATH", None)

if services_path is not None:
with open(services_path, 'r') as f:
services = json.load(f)
for service in services:
if "ip" not in service:
service["ip"] = vm_ip
else:
services = [
{"ip": vm_ip, "port": 9876, "name": "cc_market"},
{"ip": vm_ip, "port": 80, "name": "maze"},
{"ip": vm_ip, "port": 8080, "name": "scadent"},
{"ip": vm_ip, "port": 5000, "name": "starchaser"},
{"ip": vm_ip, "port": 1883, "name": "scadnet_bin"},
{"ip": vm_ip, "port": -1, "name": "other"}
]