Skip to content

chore: untrack .claude directory from git #5

chore: untrack .claude directory from git

chore: untrack .claude directory from git #5

Workflow file for this run

# =============================================================================
# SOC Lab Docker – CI: Stack Integration Test
# =============================================================================
#
# Runs on every push and PR to main.
# Tests the full pipeline end-to-end on Linux and macOS:
# 1. Start the Docker Compose ELK stack
# 2. Wait for Elasticsearch + Kibana to be healthy
# 3. Confirm events flow from generator → Filebeat → Elasticsearch
# 4. Run the brute force attack simulation
# 5. Confirm simulation events are indexed
#
# Ubuntu: uses docker compose (V2 plugin, pre-installed on ubuntu-latest).
# The standalone docker-compose V1 binary was removed from runners.
#
# macOS: Docker is not pre-installed. Colima is used as the runtime.
# --vm-type qemu is required; the default VZ driver crashes on
# GitHub-hosted runners.
# =============================================================================
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
integration-test:
name: Integration test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 35
strategy:
fail-fast: false # let both OS jobs run even if one fails
matrix:
os: [ubuntu-latest, macos-latest]
steps:
# -----------------------------------------------------------------------
- name: Checkout repository
uses: actions/checkout@v4
# -----------------------------------------------------------------------
# macOS: Docker is not pre-installed — use Colima as the runtime.
# --vm-type qemu: the default VZ (Virtualization.framework) driver
# crashes on GitHub-hosted runners; QEMU is slower but reliable.
#
# PATH note: on Apple Silicon runners brew installs qemu into a keg
# at $(brew --prefix qemu)/bin which is NOT always symlinked into
# $(brew --prefix)/bin. We add the keg bin explicitly so Colima can
# find qemu-img before calling `colima start`.
# -----------------------------------------------------------------------
- name: Set up Docker via Colima (macOS only)
if: runner.os == 'macOS'
run: |
brew install colima docker docker-compose qemu
# Register docker-compose as a Docker CLI plugin so that
# `docker compose` (plugin form) works alongside the standalone binary
mkdir -p ~/.docker/cli-plugins
ln -sfn "$(brew --prefix)/opt/docker-compose/bin/docker-compose" \
~/.docker/cli-plugins/docker-compose
# Expose the QEMU keg bin so colima can locate qemu-img
export PATH="$(brew --prefix qemu)/bin:$PATH"
echo "qemu-img: $(which qemu-img)"
# Start Colima with QEMU — VZ driver fails on GitHub Actions runners
colima start --cpu 2 --memory 4 --disk 20 --vm-type qemu
# Verify the daemon is reachable before proceeding
docker info
# -----------------------------------------------------------------------
- name: Prepare environment
run: |
cp .env.example .env
mkdir -p data/logs
# -----------------------------------------------------------------------
- name: Start lab stack
run: docker compose up -d
# -----------------------------------------------------------------------
- name: Wait for Elasticsearch to be healthy
run: |
echo "Polling Elasticsearch health (up to 5 min)..."
for i in $(seq 1 30); do
if curl -sf http://localhost:9200/_cluster/health 2>/dev/null \
| grep -qv '"status":"red"'; then
echo "Elasticsearch is healthy"
curl -s http://localhost:9200/_cluster/health
break
fi
if [ "$i" -eq 30 ]; then
echo "Elasticsearch failed to become healthy in time"
docker compose logs --tail=40 elasticsearch
exit 1
fi
echo " ($i/30) not ready — retrying in 10s..."
sleep 10
done
# -----------------------------------------------------------------------
- name: Wait for Kibana to be available
run: |
echo "Polling Kibana status (up to 5 min)..."
for i in $(seq 1 30); do
if curl -sf http://localhost:5601/api/status 2>/dev/null \
| grep -q '"level":"available"'; then
echo "Kibana is available"
break
fi
if [ "$i" -eq 30 ]; then
echo "Kibana failed to become available in time"
docker compose logs --tail=40 kibana
exit 1
fi
echo " ($i/30) not ready — retrying in 10s..."
sleep 10
done
# -----------------------------------------------------------------------
- name: Wait for events to appear in Elasticsearch
run: |
echo "Waiting for generator → Filebeat → Elasticsearch pipeline..."
for i in $(seq 1 18); do
count=$(curl -sf "http://localhost:9200/soc-lab-*/_count" 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" \
2>/dev/null || echo 0)
if [ "$count" -gt 0 ]; then
echo "Events confirmed in Elasticsearch: $count"
break
fi
if [ "$i" -eq 18 ]; then
echo "No events indexed after timeout"
docker compose logs --tail=30 log-generator
docker compose logs --tail=30 filebeat
exit 1
fi
echo " ($i/18) count=$count — retrying in 10s..."
sleep 10
done
# -----------------------------------------------------------------------
- name: Run brute force simulation
run: bash scripts/brute_force_simulation.sh
# -----------------------------------------------------------------------
- name: Verify simulation events are indexed
run: |
echo "Waiting 15s for simulation events to be indexed..."
sleep 15
count=$(curl -sf "http://localhost:9200/soc-lab-*/_search" \
-H "Content-Type: application/json" \
-d '{"query":{"term":{"labels.simulation":"attack"}},"track_total_hits":true}' \
2>/dev/null \
| python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d['hits']['total']['value'])" \
2>/dev/null || echo 0)
echo "Simulation events found: $count"
if [ "$count" -eq 0 ]; then
echo "ERROR: No simulation events found in index"
exit 1
fi
echo "Simulation events confirmed"
# -----------------------------------------------------------------------
- name: Dump all service logs on failure
if: failure()
run: |
echo "===== docker compose ps ====="
docker compose ps
echo "===== elasticsearch (last 50 lines) ====="
docker compose logs --tail=50 elasticsearch
echo "===== log-generator (last 50 lines) ====="
docker compose logs --tail=50 log-generator
echo "===== filebeat (last 50 lines) ====="
docker compose logs --tail=50 filebeat
echo "===== kibana (last 50 lines) ====="
docker compose logs --tail=50 kibana
# -----------------------------------------------------------------------
- name: Teardown
if: always()
run: docker compose down -v