diff --git a/.env.example b/.env.example index ff4d170..defd3fa 100644 --- a/.env.example +++ b/.env.example @@ -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" @@ -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" \ No newline at end of file diff --git a/.github/workflows/docker-ghcr.yaml b/.github/workflows/docker-ghcr.yaml new file mode 100644 index 0000000..1fe67d3 --- /dev/null +++ b/.github/workflows/docker-ghcr.yaml @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 562379c..86ede05 100755 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,4 @@ workspace.xml .idea /traffic +services.json \ No newline at end of file diff --git a/README.md b/README.md index a892ded..61583b9 100755 --- a/README.md +++ b/README.md @@ -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) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..39596d1 --- /dev/null +++ b/docker-compose.prod.yml @@ -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: diff --git a/docker-compose.yml b/docker-compose.yml index 06fa3f0..a6a4649 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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} diff --git a/frontend/src/components/FlowList.tsx b/frontend/src/components/FlowList.tsx index dc9ccef..517298f 100644 --- a/frontend/src/components/FlowList.tsx +++ b/frontend/src/components/FlowList.tsx @@ -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", })); diff --git a/services.json.example b/services.json.example new file mode 100644 index 0000000..f0fed30 --- /dev/null +++ b/services.json.example @@ -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" + } +] \ No newline at end of file diff --git a/services/api/configurations.py b/services/api/configurations.py index 14169b5..6dd06bb 100755 --- a/services/api/configurations.py +++ b/services/api/configurations.py @@ -24,6 +24,7 @@ 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) @@ -31,11 +32,22 @@ 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"}] \ No newline at end of file +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"} + ]