Skip to content

feat: Add Response Orchestrator — autonomous adaptive defense (closed… #79

feat: Add Response Orchestrator — autonomous adaptive defense (closed…

feat: Add Response Orchestrator — autonomous adaptive defense (closed… #79

Workflow file for this run

name: CD - Continuous Deployment
on:
push:
branches: [ master, main ]
tags:
- 'v*'
workflow_dispatch:
env:
REGISTRY: ghcr.io
# Force lowercase for Docker image compatibility
IMAGE_NAME: zhadyz/ai_soc
jobs:
# ============================================================================
# Build and Push Docker Images
# ============================================================================
build-and-push:
name: Build & Push Images
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
service:
- alert-triage
- rag-service
- log-summarization
- ml-inference
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.service }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@v5
if: hashFiles(format('services/{0}/Dockerfile', matrix.service)) != ''
with:
context: services/${{ matrix.service }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ============================================================================
# Security Scanning
# ============================================================================
security-scan:
name: Security Scan Images
runs-on: ubuntu-latest
needs: build-and-push
permissions:
contents: read
security-events: write
strategy:
matrix:
service:
- alert-triage
- rag-service
steps:
- name: Run Trivy security scan
uses: aquasecurity/trivy-action@master
continue-on-error: true # Don't fail if image doesn't exist
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.service }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-${{ matrix.service }}.sarif'
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v4
if: always() # Upload even if scan step failed/skipped
continue-on-error: true
with:
sarif_file: 'trivy-${{ matrix.service }}.sarif'
# ============================================================================
# Deploy to Staging
# Build AI service images, start them in CI, run smoke + integration tests
# ============================================================================
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [build-and-push, security-scan]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Compose
run: |
# Docker Compose v2 is bundled with Docker on ubuntu-latest - verify
docker compose version
- name: Create minimal .env for CI
run: |
cat > .env <<'EOF'
INDEXER_USERNAME=admin
INDEXER_PASSWORD=SecurePassword1!
API_PASSWORD=SecurePassword1!
WAZUH_API_PASSWORD=SecurePassword1!
KIBANA_PASSWORD=SecurePassword1!
EOF
- name: Build AI service images
run: |
docker compose -f docker-compose/ai-services.yml build --parallel
env:
DOCKER_BUILDKIT: 1
- name: Start AI services (no SIEM in CI - resource constraints)
run: |
# Start ChromaDB first (RAG service dependency)
docker compose -f docker-compose/ai-services.yml up -d chromadb
echo "Waiting for ChromaDB..."
timeout 60 bash -c 'until docker compose -f docker-compose/ai-services.yml ps chromadb | grep -q "healthy\|Up"; do sleep 3; done' || true
# Start AI services without Ollama dependency (Ollama too large for CI)
# Use --no-deps to skip Ollama health check requirement
docker compose -f docker-compose/ai-services.yml up -d --no-deps ml-inference rag-service
docker compose -f docker-compose/ai-services.yml up -d --no-deps alert-triage
echo "AI services started (Ollama skipped in CI)"
- name: Wait for services to be healthy
run: |
echo "Waiting for AI services to pass health checks (max 120s)..."
max_wait=120
elapsed=0
services=("ml-inference:8500" "rag-service:8300" "alert-triage:8100")
for svc_port in "${services[@]}"; do
svc="${svc_port%%:*}"
port="${svc_port##*:}"
wait=0
echo -n " Waiting for $svc..."
while [[ $wait -lt $max_wait ]]; do
if curl -sf "http://localhost:${port}/health" > /dev/null 2>&1; then
echo " healthy (${wait}s)"
break
fi
sleep 5
wait=$((wait + 5))
done
if [[ $wait -ge $max_wait ]]; then
echo " TIMEOUT after ${max_wait}s"
fi
done
- name: Run smoke tests
run: |
echo "=== Smoke Tests ==="
PASS=0
FAIL=0
check_health() {
local name="$1"
local url="$2"
if curl -sf --max-time 10 "$url" > /dev/null; then
echo " PASS: $name ($url)"
PASS=$((PASS + 1))
else
echo " FAIL: $name ($url)"
FAIL=$((FAIL + 1))
fi
}
check_health "ML Inference health" "http://localhost:8500/health"
check_health "RAG Service health" "http://localhost:8300/health"
check_health "Alert Triage health" "http://localhost:8100/health"
check_health "RAG collections" "http://localhost:8300/collections"
check_health "ML models list" "http://localhost:8500/models"
echo ""
echo "Smoke tests: ${PASS} passed, ${FAIL} failed"
if [[ $FAIL -gt 0 ]]; then
echo "::warning::${FAIL} smoke test(s) failed"
fi
- name: Run integration tests
run: |
echo "=== Integration Tests ==="
# Test ML inference prediction endpoint
echo "Testing ML inference prediction..."
features=$(python3 -c "import json; print(json.dumps({'features': [0.1]*78, 'model_name': 'random_forest'}))")
ml_response=$(curl -sf -X POST \
-H "Content-Type: application/json" \
-d "$features" \
"http://localhost:8500/predict" || echo "ERROR")
if echo "$ml_response" | python3 -c "import sys, json; d = json.load(sys.stdin); assert 'prediction' in d, 'missing prediction field'" 2>/dev/null; then
echo " PASS: ML prediction returns valid response"
else
echo " WARN: ML prediction - response: $ml_response"
fi
# Test RAG retrieval endpoint
echo "Testing RAG retrieval..."
rag_response=$(curl -sf -X POST \
-H "Content-Type: application/json" \
-d '{"query":"brute force attack","collection":"mitre_attack","top_k":3}' \
"http://localhost:8300/retrieve" || echo "ERROR")
if echo "$rag_response" | python3 -c "import sys, json; d = json.load(sys.stdin); assert 'results' in d, 'missing results field'" 2>/dev/null; then
echo " PASS: RAG retrieval returns valid response"
else
echo " WARN: RAG retrieval - response: $rag_response"
fi
# Test alert triage root endpoint
echo "Testing Alert Triage API..."
triage_response=$(curl -sf "http://localhost:8100/" || echo "ERROR")
if [[ "$triage_response" != "ERROR" ]]; then
echo " PASS: Alert Triage API responding"
else
echo " WARN: Alert Triage API not responding"
fi
echo "Integration tests complete."
- name: Show container logs on failure
if: failure()
run: |
echo "=== Container Status ==="
docker compose -f docker-compose/ai-services.yml ps
echo ""
echo "=== ML Inference Logs ==="
docker compose -f docker-compose/ai-services.yml logs ml-inference --tail=50 || true
echo ""
echo "=== RAG Service Logs ==="
docker compose -f docker-compose/ai-services.yml logs rag-service --tail=50 || true
echo ""
echo "=== Alert Triage Logs ==="
docker compose -f docker-compose/ai-services.yml logs alert-triage --tail=50 || true
- name: Tear down staging services
if: always()
run: |
docker compose -f docker-compose/ai-services.yml down --volumes --remove-orphans || true
echo "Staging environment torn down."
# ============================================================================
# Deploy to Production (on tag only)
# Build/push images to GHCR and create GitHub Release
# ============================================================================
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: deploy-staging
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history for changelog generation
- name: Extract version from tag
id: version
run: |
TAG="${{ github.ref_name }}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Verify production images were pushed
run: |
echo "Verifying images exist in GHCR for tag ${{ steps.version.outputs.tag }}..."
services=("alert-triage" "rag-service" "ml-inference")
for svc in "${services[@]}"; do
image="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${svc}:${{ steps.version.outputs.tag }}"
echo " Checking: $image"
# Pull to verify (will fail gracefully if not found)
docker pull "$image" 2>/dev/null && echo " Found" || echo " Not yet available (built by build-and-push job)"
done
- name: Generate changelog
id: changelog
run: |
# Get commits since last tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || git rev-list --max-parents=0 HEAD)
COMMITS=$(git log "${PREV_TAG}..HEAD" --oneline --no-merges 2>/dev/null | head -30 || echo "Initial release")
echo "Previous tag: $PREV_TAG"
echo "Commits since previous tag:"
echo "$COMMITS"
# Write to file for multiline handling
{
echo "changelog<<EOF"
if [[ -n "$COMMITS" ]]; then
echo "$COMMITS" | while IFS= read -r line; do
echo "- ${line}"
done
else
echo "- See commit history for changes"
fi
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag }}
name: "AI-SOC ${{ steps.version.outputs.tag }}"
draft: false
prerelease: false
body: |
## AI-SOC Release ${{ steps.version.outputs.tag }}
AI-Augmented Security Operations Center - automated threat detection and response platform.
### What's Changed
${{ steps.changelog.outputs.changelog }}
### Services
| Service | Port | Description |
|---------|------|-------------|
| Alert Triage | 8100 | LLM-powered alert analysis with MITRE ATT&CK mapping |
| RAG Service | 8300 | Security knowledge base (CVEs, runbooks, MITRE) |
| ML Inference | 8500 | Anomaly detection and threat classification |
| Wazuh Integration | 8002 | SIEM alert forwarding and enrichment |
### Docker Images (GHCR)
```
ghcr.io/${{ env.IMAGE_NAME }}/alert-triage:${{ steps.version.outputs.tag }}
ghcr.io/${{ env.IMAGE_NAME }}/rag-service:${{ steps.version.outputs.tag }}
ghcr.io/${{ env.IMAGE_NAME }}/ml-inference:${{ steps.version.outputs.tag }}
```
### Deployment Instructions
**Quick Deploy (Linux/macOS):**
```bash
git clone https://github.com/${{ github.repository }}.git
cd $(basename ${{ github.repository }})
git checkout ${{ steps.version.outputs.tag }}
chmod +x deploy-ai-soc.sh
./deploy-ai-soc.sh
```
**Quick Deploy (Windows):**
```powershell
git clone https://github.com/${{ github.repository }}.git
cd (Split-Path ${{ github.repository }} -Leaf)
git checkout ${{ steps.version.outputs.tag }}
.\deploy-ai-soc.ps1
```
**Requirements:**
- Docker Desktop 24+ with Docker Compose v2
- 8GB+ RAM, 20GB+ disk space
- Internet access for Ollama model download
### Access URLs (after deployment)
- Wazuh Dashboard: https://localhost:443
- Grafana: http://localhost:3001
- Alert Triage API: http://localhost:8100/docs
- RAG Service API: http://localhost:8300/docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ============================================================================
# Rollback on Failure
# ============================================================================
rollback:
name: Rollback on Failure
runs-on: ubuntu-latest
needs: [deploy-staging, deploy-production]
if: failure()
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Tear down any leftover staging services
run: |
echo "Deployment failed - tearing down staging environment..."
docker compose -f docker-compose/ai-services.yml down --volumes --remove-orphans 2>/dev/null || true
echo "Staging environment cleaned up."
- name: Post failure summary
run: |
echo "## Deployment Failure Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Branch/Tag:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "**Triggered by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Failed Jobs" >> $GITHUB_STEP_SUMMARY
echo "One or more deployment jobs failed. Review the workflow run for details." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Recovery Steps" >> $GITHUB_STEP_SUMMARY
echo "1. Review logs for the failed job" >> $GITHUB_STEP_SUMMARY
echo "2. Fix the issue and push a new commit" >> $GITHUB_STEP_SUMMARY
echo "3. Re-run the workflow via Actions > Re-run failed jobs" >> $GITHUB_STEP_SUMMARY