initial-commit

This commit is contained in:
Guillaume GRABÉ
2026-05-12 17:06:43 +02:00
commit 051a080dfa
110 changed files with 26377 additions and 0 deletions
+297
View File
@@ -0,0 +1,297 @@
# Charybdis Demo
See the full flow in action: register a service via one gRPC call and watch DefectDojo auto-provision a product — no YAML files, no manual steps. Optionally, see how Backstage can consume entities from Charybdis in real-time via the YAML adapter.
## Prerequisites
- Docker & Docker Compose
- 8GB RAM recommended
- Ports available: 5432, 8080, 8081, 50051 (and 3000 if using Backstage)
## Quick Start (5 minutes)
### 1. Start the demo stack
```bash
./demo.sh
```
This will start:
- ✅ Charybdis (gRPC on :50051, YAML adapter on :8081)
- ✅ DefectDojo (UI on :8080)
- ✅ PostgreSQL instances for each service
- ️ Backstage (UI on :3000) — optional, see [Backstage Integration](#optional-backstage-integration) below
### 2. Create your first service
Using grpcurl (if installed):
```bash
grpcurl -plaintext -d '{
"entity": {
"kind": "Component",
"component_metadata": {
"name": "payment-api",
"description": "Payment processing service",
"labels": {
"team": "payments",
"env": "production"
},
"tags": ["api", "critical"]
},
"component_spec": {
"type": "service",
"lifecycle": "production",
"owner": "team-payments"
}
}
}' localhost:50051 charybdis.entities.EntityService/CreateEntity
```
Or using the included grpcurl container:
```bash
docker-compose -f docker-compose.demo.yml run --rm grpcurl \
-plaintext -d '{
"entity": {
"kind": "Component",
"component_metadata": {
"name": "payment-api",
"description": "Payment processing service"
},
"component_spec": {
"type": "service",
"lifecycle": "production",
"owner": "team-payments"
}
}
}' charybdis:50051 charybdis.entities.EntityService/CreateEntity
```
### 3. See the magic happen! ✨
**In DefectDojo** (http://localhost:8080):
- Login: `admin` / `admin`
- Go to "Products"
- You'll see "payment-api" automatically created! 🎉
**In Charybdis** (via gRPC):
- Query the entity back — it now has annotations:
- `defectdojo.com/product-id`
- `defectdojo.com/engagement-id` (if auto-create enabled)
**In the YAML adapter** (http://localhost:8081):
- Visit `http://localhost:8081/yaml/locations` — entities are served in Backstage-compatible YAML format
- Any Backstage instance pointed at this URL will pick up entities automatically
## What just happened?
```
1. You created a Component entity in Charybdis (gRPC API)
2. Charybdis stored it in PostgreSQL
3. Event published: EntityCreated(Component)
4. DefectDojo plugin received the event
5. Plugin created a Product in DefectDojo via API
6. Plugin stored the product_id in entity annotations
7. Entity is now queryable via gRPC and available via YAML adapter
```
**30 seconds instead of 30 minutes of manual provisioning — no YAML files, no manual tool setup.**
## Try more operations
### List all entities
```bash
grpcurl -plaintext -d '{}' localhost:50051 \
charybdis.entities.EntityService/ListEntities
```
### Get a specific entity
```bash
grpcurl -plaintext -d '{"id": "YOUR_ENTITY_ID"}' localhost:50051 \
charybdis.entities.EntityService/GetEntity
```
### Update an entity
```bash
grpcurl -plaintext -d '{
"id": "YOUR_ENTITY_ID",
"entity": {
"kind": "Component",
"component_metadata": {
"name": "payment-api",
"description": "Updated description"
}
}
}' localhost:50051 charybdis.entities.EntityService/UpdateEntity
```
→ Check DefectDojo: Product description updated automatically!
### Delete an entity
```bash
grpcurl -plaintext -d '{"id": "YOUR_ENTITY_ID"}' localhost:50051 \
charybdis.entities.EntityService/DeleteEntity
```
→ Check DefectDojo: Product deleted automatically!
## Explore the stack
### Check Charybdis logs
```bash
docker-compose -f docker-compose.demo.yml logs -f charybdis
```
You'll see:
- Entity CRUD operations
- Event dispatching
- Plugin execution
- DefectDojo API calls
### Check DefectDojo
Open http://localhost:8080
- Login: `admin` / `admin`
- Products: See auto-created products
- Engagements: See auto-created CI/CD engagements
### Check the YAML Adapter
```bash
# List all entity locations (Backstage-compatible format)
curl http://localhost:8081/yaml/locations
```
This endpoint serves entities as Backstage-compatible YAML — useful for Backstage integration or any tool that consumes this format.
### Access databases
**Charybdis database:**
```bash
docker-compose -f docker-compose.demo.yml exec postgres-charybdis \
psql -U charybdis -d charybdis
```
**Query entities:**
```sql
SELECT id, kind, annotations FROM entities;
```
## Troubleshooting
### Services not starting
Check logs:
```bash
docker-compose -f docker-compose.demo.yml logs
```
### DefectDojo API token issue
Manually get a token:
1. Open http://localhost:8080
2. Login: `admin` / `admin`
3. Go to Settings → API Key
4. Copy the token
5. Update `.env`: `DEFECTDOJO_API_TOKEN=your-token`
6. Restart: `docker-compose -f docker-compose.demo.yml restart charybdis`
### Port conflicts
If ports are already in use, edit `docker-compose.demo.yml` to change:
- `3000:3000``3001:3000` (Backstage)
- `8080:8080``8082:8080` (DefectDojo)
- etc.
## Clean up
### Stop services
```bash
docker-compose -f docker-compose.demo.yml down
```
### Remove all data
```bash
docker-compose -f docker-compose.demo.yml down -v
```
## Optional: Backstage Integration
If you use Backstage, point it at the Charybdis YAML adapter to replace static `catalog-info.yaml` files:
```yaml
# backstage app-config.yaml
catalog:
locations:
- type: url
target: http://charybdis:8081/yaml/locations
rules:
- allow: [Component, System, Service, API, User, Group]
```
Backstage will discover all entities from Charybdis automatically. See the `docker-compose.demo.yml` file for the commented-out Backstage service if you want to run it as part of the demo stack.
## Next Steps
1. **Read the docs**: [Getting Started](../docs/getting-started.md)
2. **Understand the architecture**: [Architecture](../docs/architecture.md)
3. **Create your own plugin**: [Plugin Guide](../plugins/README.md)
## Demo Architecture
```
┌─────────────┐
│ Client │ (grpcurl / CI/CD)
│ (gRPC) │
└──────┬──────┘
┌─────────────────────────────────────┐
│ Charybdis │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ gRPC API │ │ YAML Adapter │ │
│ │ :50051 │ │ :8081 │ │
│ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────▼────────────────▼───────┐ │
│ │ Entity Repository │ │
│ │ (PostgreSQL) │ │
│ └──────┬────────────────────────┘ │
│ │ │
│ ┌──────▼────────┐ │
│ │ Event Bus │ │
│ └──────┬────────┘ │
│ │ │
│ ┌──────▼────────────────┐ │
│ │ Plugin Dispatcher │ │
│ │ ┌─────────────────┐ │ │
│ │ │ DefectDojo │ │ │
│ │ │ Plugin │ │ │
│ │ └─────────────────┘ │ │
│ └───────────────────────┘ │
└──────┬──────────────┬───────────────┘
│ │
▼ ▼ (YAML Adapter)
┌──────────────┐ ┌────────────────────┐
│ DefectDojo │ │ Backstage / Any │
│ :8080 │ │ compatible UI │
└──────────────┘ └────────────────────┘
```
## Support
- GitHub Issues: [github.com/charybdis-catalog/charybdis/issues](https://github.com/charybdis-catalog/charybdis/issues)
- Documentation: [docs/](docs/)
- Discord: [Join our community](https://discord.gg/...)
---
**🎉 Welcome to automated DevSecOps orchestration with Charybdis!**
+102
View File
@@ -0,0 +1,102 @@
# Adding Backstage to Charybdis Demo
Backstage requires a custom build and cannot be run from a pre-built Docker image.
## Option 1: Use the YAML Adapter Directly
You can read entities from Charybdis without Backstage:
```bash
# List all entities
curl http://localhost:8081/yaml/locations
# Get specific entity
curl http://localhost:8081/yaml/entities/YOUR_ENTITY_ID
```
## Option 2: Build and Run Backstage
### 1. Create a Backstage App
```bash
# Install Node.js 18+ first
npx @backstage/create-app@latest
# Follow prompts, name it "backstage-app"
cd backstage-app
```
### 2. Configure Charybdis Integration
Edit `app-config.yaml` and add:
```yaml
catalog:
locations:
# Point to Charybdis YAML adapter
- type: url
target: http://charybdis:8080/yaml/locations
rules:
- allow: [Component, System, API, Resource, User, Group, Domain]
```
### 3. Build Docker Image
Create `backstage-app/Dockerfile`:
```dockerfile
FROM node:18-bookworm-slim
WORKDIR /app
# Copy package files
COPY package.json yarn.lock ./
COPY packages packages
# Install dependencies
RUN yarn install --frozen-lockfile --production
# Build
RUN yarn tsc
RUN yarn build:backend
# Run
EXPOSE 7007
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
```
### 4. Update docker-compose.demo.yml
Uncomment the Backstage section in `docker-compose.demo.yml`.
### 5. Start
```bash
docker-compose -f docker-compose.demo.yml up -d backstage
```
Access Backstage at http://localhost:3000
## Option 3: Run Backstage Locally (Recommended for Development)
```bash
# In backstage-app directory
yarn dev
```
Update `app-config.local.yaml`:
```yaml
catalog:
locations:
- type: url
target: http://localhost:8081/yaml/locations
rules:
- allow: [Component, System, API, Resource, User, Group, Domain]
```
Access at http://localhost:3000
---
For more info: https://backstage.io/docs/getting-started/
+92
View File
@@ -0,0 +1,92 @@
app:
title: Charybdis Demo
baseUrl: http://localhost:3000
organization:
name: Charybdis
backend:
baseUrl: http://localhost:7007
listen:
port: 7007
csp:
connect-src: ["'self'", 'http:', 'https:']
cors:
origin: http://localhost:3000
methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
credentials: true
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
database: backstage
integrations:
github:
- host: github.com
# Optional: token for private repos
# token: ${GITHUB_TOKEN}
proxy:
'/charybdis':
target: ${CHARYBDIS_URL}
changeOrigin: true
# Catalog configuration - Point to Charybdis
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location]
# Charybdis as a location provider
locations:
# Dynamic location from Charybdis YAML adapter
- type: url
target: ${CHARYBDIS_URL}/yaml/locations
rules:
- allow: [Component, System, API, Resource, User, Group, Domain]
# Processors
processors:
githubOrg:
providers:
- target: https://github.com
# Optional: token for private orgs
# token: ${GITHUB_TOKEN}
auth:
# For demo purposes, we use guest authentication
# In production, configure proper authentication
providers:
guest:
dangerouslyAllowOutsideDevelopment: true
# Scaffolder configuration
scaffolder:
defaultAuthor:
name: Charybdis
email: charybdis@example.com
# TechDocs configuration (optional)
techdocs:
builder: 'local'
generator:
runIn: 'local'
publisher:
type: 'local'
# Search configuration (optional)
search:
pg:
highlightOptions:
useHighlight: true
maxWord: 35
minWord: 15
shortWord: 3
highlightAll: false
Executable
+222
View File
@@ -0,0 +1,222 @@
#!/bin/bash
set -e
# Run from the deploy/ directory
cd "$(dirname "$0")"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}"
cat << "EOF"
______ __ __ ___
/ ____// /_ ____ _ _____ __ __ / /_ ____/ (_)____
/ / / __ \ / __ `// ___// / / // __ \ / __ // // __ \
/ /___ / / / // /_/ // / / /_/ // /_/ // /_/ // // /_/ /
\____//_/ /_/ \__,_//_/ \__, //_.___/ \__,_//_/ \____/
/____/
EOF
echo -e "${NC}"
echo -e "${GREEN}Charybdis Demo${NC}\n"
# Detect gRPC client
GRPC_CMD=""
if command -v buf &> /dev/null; then
GRPC_CMD="buf"
elif command -v grpcurl &> /dev/null; then
GRPC_CMD="grpcurl"
else
echo -e "${RED}Error: Neither 'buf' nor 'grpcurl' found. Install one:${NC}"
echo -e " brew install bufbuild/buf/buf"
echo -e " brew install grpcurl"
exit 1
fi
echo -e "${BLUE}Using gRPC client: ${GRPC_CMD}${NC}\n"
# Helper function for gRPC calls
grpc_call() {
local service_method="$1"
local data="$2"
local host="${3:-localhost:50051}"
if [ "$GRPC_CMD" = "buf" ]; then
buf curl --protocol grpc --http2-prior-knowledge \
-d "$data" \
"http://${host}/${service_method}" 2>&1
else
grpcurl -plaintext -d "$data" "$host" "$service_method" 2>&1
fi
}
# Check if Docker is running
if ! docker info > /dev/null 2>&1; then
echo -e "${RED}Error: Docker is not running. Please start Docker first.${NC}"
exit 1
fi
echo -e "${BLUE}Starting services...${NC}\n"
# Start core services (Charybdis + Postgres only for quick demo)
docker compose -f docker-compose.demo.yml up -d postgres-charybdis 2>/dev/null
echo -e "${GREEN} Postgres started${NC}"
# Wait for Postgres
for i in {1..15}; do
if docker compose -f docker-compose.demo.yml exec -T postgres-charybdis pg_isready -U charybdis > /dev/null 2>&1; then
break
fi
sleep 1
done
docker compose -f docker-compose.demo.yml up -d charybdis 2>/dev/null
echo -e "${GREEN} Charybdis started${NC}"
# Wait for Charybdis gRPC
echo -e "\n${BLUE}Waiting for Charybdis to be ready...${NC}"
for i in {1..30}; do
if timeout 2 bash -c "echo > /dev/tcp/localhost/50051" 2>/dev/null; then
echo -e "${GREEN} gRPC server ready on :50051${NC}"
break
fi
if [ $i -eq 30 ]; then
echo -e "${RED} Charybdis failed to start${NC}"
docker compose -f docker-compose.demo.yml logs charybdis | tail -10
exit 1
fi
sleep 1
done
sleep 2 # Allow service to fully initialize
# --- Demo: Entity Management ---
echo -e "\n${BLUE}=== Entity Management ===${NC}\n"
echo -e "${YELLOW}1. Creating a Component...${NC}"
RESULT=$(grpc_call "charybdis.entities.EntityService/CreateEntity" '{
"entity": {
"kind": "Component",
"component_metadata": {
"name": "payment-service",
"namespace": "default",
"description": "Payment processing microservice"
},
"component_spec": {
"type": "service",
"lifecycle": "production",
"owner": "team-payments"
}
}
}')
echo "$RESULT" | head -5
ENTITY_ID=$(echo "$RESULT" | grep '"id"' | head -1 | sed 's/.*"id": *"//;s/".*//')
echo -e "${GREEN} Created entity: ${ENTITY_ID}${NC}\n"
echo -e "${YELLOW}2. Listing entities...${NC}"
grpc_call "charybdis.entities.EntityService/ListEntities" '{}' | head -5
echo -e ""
# --- Demo: Vulnerability Ingestion ---
echo -e "\n${BLUE}=== Vulnerability Ingestion ===${NC}\n"
# Create a SARIF report
SARIF_REPORT=$(cat << 'SARIF'
{
"version": "2.1.0",
"runs": [{
"tool": {"driver": {"name": "semgrep", "version": "1.0.0", "rules": [
{"id": "sql-injection", "shortDescription": {"text": "SQL Injection"}, "defaultConfiguration": {"level": "error"}, "properties": {"tags": ["CWE-89"]}},
{"id": "xss-reflected", "shortDescription": {"text": "Reflected XSS"}, "defaultConfiguration": {"level": "warning"}, "properties": {"tags": ["CWE-79"]}},
{"id": "hardcoded-secret", "shortDescription": {"text": "Hardcoded Secret"}, "defaultConfiguration": {"level": "error"}}
]}},
"results": [
{"ruleId": "sql-injection", "level": "error", "message": {"text": "User input in SQL query without parameterization"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "src/db/queries.rs"}, "region": {"startLine": 42}}}], "partialFingerprints": {"primaryLocationLineHash": "fp-sql-001"}},
{"ruleId": "xss-reflected", "level": "warning", "message": {"text": "User input reflected in response without encoding"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "src/handlers/search.rs"}, "region": {"startLine": 18}}}]},
{"ruleId": "hardcoded-secret", "level": "error", "message": {"text": "AWS secret key found in source code"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "src/config.rs"}, "region": {"startLine": 7}}}]}
]
}]
}
SARIF
)
SARIF_B64=$(echo "$SARIF_REPORT" | base64)
echo -e "${YELLOW}3. Dry-run scan (preview without persisting)...${NC}"
grpc_call "charybdis.ingestion.IngestionService/DryRunScan" "{
\"component_ref\": \"payment-service\",
\"lifecycle\": \"production\",
\"format\": \"sarif\",
\"data\": \"${SARIF_B64}\"
}"
echo -e ""
echo -e "${YELLOW}4. Importing scan (persisting findings)...${NC}"
grpc_call "charybdis.ingestion.IngestionService/ImportScan" "{
\"component_ref\": \"payment-service\",
\"lifecycle\": \"production\",
\"format\": \"sarif\",
\"data\": \"${SARIF_B64}\"
}"
echo -e ""
echo -e "${YELLOW}5. Re-importing same scan (testing deduplication)...${NC}"
grpc_call "charybdis.ingestion.IngestionService/ImportScan" "{
\"component_ref\": \"payment-service\",
\"lifecycle\": \"production\",
\"format\": \"sarif\",
\"data\": \"${SARIF_B64}\"
}"
echo -e ""
# Simulate a fix: remove the SQL injection finding
SARIF_FIXED=$(cat << 'SARIF'
{
"version": "2.1.0",
"runs": [{
"tool": {"driver": {"name": "semgrep", "version": "1.0.0", "rules": [
{"id": "xss-reflected", "shortDescription": {"text": "Reflected XSS"}, "defaultConfiguration": {"level": "warning"}, "properties": {"tags": ["CWE-79"]}},
{"id": "hardcoded-secret", "shortDescription": {"text": "Hardcoded Secret"}, "defaultConfiguration": {"level": "error"}}
]}},
"results": [
{"ruleId": "xss-reflected", "level": "warning", "message": {"text": "User input reflected in response without encoding"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "src/handlers/search.rs"}, "region": {"startLine": 18}}}]},
{"ruleId": "hardcoded-secret", "level": "error", "message": {"text": "AWS secret key found in source code"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "src/config.rs"}, "region": {"startLine": 7}}}]}
]
}]
}
SARIF
)
SARIF_FIXED_B64=$(echo "$SARIF_FIXED" | base64)
echo -e "${YELLOW}6. Importing scan after fix (SQL injection resolved)...${NC}"
grpc_call "charybdis.ingestion.IngestionService/ImportScan" "{
\"component_ref\": \"payment-service\",
\"lifecycle\": \"production\",
\"format\": \"sarif\",
\"data\": \"${SARIF_FIXED_B64}\"
}"
echo -e ""
# --- Summary ---
echo -e "\n${GREEN}=== Demo Complete ===${NC}\n"
echo -e "${BLUE}What we demonstrated:${NC}"
echo -e " 1. Created a Component entity via gRPC"
echo -e " 2. Dry-run scan: preview findings without persisting"
echo -e " 3. Import scan: persist 3 findings (SQL injection, XSS, hardcoded secret)"
echo -e " 4. Deduplication: re-import same scan -> all unchanged"
echo -e " 5. Auto-resolution: import without SQL injection -> marked as resolved"
echo ""
echo -e "${BLUE}Access:${NC}"
echo -e " gRPC: localhost:50051"
echo -e " YAML Adapter: http://localhost:8081"
echo ""
echo -e "${BLUE}Stop:${NC}"
echo -e " docker compose -f docker-compose.demo.yml down"
echo ""
echo -e "${BLUE}Clean up:${NC}"
echo -e " docker compose -f docker-compose.demo.yml down -v"
echo ""
+233
View File
@@ -0,0 +1,233 @@
version: '3.8'
# Demo stack secrets — override via .env file or environment variables.
# These are NOT production credentials. Generate your own before deploying.
x-dd-secrets: &dd-secrets
DD_SECRET_KEY: ${DD_SECRET_KEY:-CHANGE_ME_generate_a_random_secret_key}
DD_CREDENTIAL_AES_256_KEY: ${DD_CREDENTIAL_AES_256_KEY:-CHANGE_ME_generate_a_base64_aes_key}
services:
# PostgreSQL for Charybdis
postgres-charybdis:
image: postgres:15-alpine
container_name: charybdis-postgres
environment:
POSTGRES_DB: charybdis
POSTGRES_USER: charybdis
POSTGRES_PASSWORD: ${CHARYBDIS_DB_PASSWORD:-charybdis}
ports:
- "5432:5432"
volumes:
- charybdis-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U charybdis"]
interval: 10s
timeout: 5s
retries: 5
networks:
- charybdis-network
# PostgreSQL for DefectDojo
postgres-defectdojo:
image: postgres:15-alpine
container_name: defectdojo-postgres
environment:
POSTGRES_DB: defectdojo
POSTGRES_USER: defectdojo
POSTGRES_PASSWORD: ${DD_DB_PASSWORD:-defectdojo}
volumes:
- defectdojo-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U defectdojo"]
interval: 10s
timeout: 5s
retries: 5
networks:
- charybdis-network
# Redis for DefectDojo
redis-defectdojo:
image: redis:7-alpine
container_name: defectdojo-redis
networks:
- charybdis-network
# DefectDojo
defectdojo-uwsgi:
image: defectdojo/defectdojo-django:2.38.1
platform: linux/amd64 # Force x86_64 emulation for ARM64 (Mac Silicon)
container_name: defectdojo-uwsgi
depends_on:
postgres-defectdojo:
condition: service_healthy
redis-defectdojo:
condition: service_started
environment:
DD_DEBUG: 'True'
DD_ALLOWED_HOSTS: '*'
DD_DATABASE_URL: postgresql://defectdojo:${DD_DB_PASSWORD:-defectdojo}@postgres-defectdojo:5432/defectdojo
DD_CELERY_BROKER_URL: redis://redis-defectdojo:6379/0
<<: *dd-secrets
DD_INITIALIZE: 'true'
DD_ADMIN_USER: ${DD_ADMIN_USER:-admin}
DD_ADMIN_PASSWORD: ${DD_ADMIN_PASSWORD:-admin}
DD_ADMIN_MAIL: 'admin@example.com'
ports:
- "8080:8081" # DefectDojo HTTP endpoint is on 8081 inside container
volumes:
- defectdojo-media:/app/media
networks:
- charybdis-network
# Healthcheck disabled - DefectDojo takes time to initialize
# Access will be available after ~2 minutes
# healthcheck:
# test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/').read()"]
# interval: 30s
# timeout: 10s
# retries: 5
# start_period: 120s
# DefectDojo Celery Worker
defectdojo-celery:
image: defectdojo/defectdojo-django:2.38.1
platform: linux/amd64 # Force x86_64 emulation for ARM64 (Mac Silicon)
container_name: defectdojo-celery
depends_on:
- defectdojo-uwsgi
- redis-defectdojo
environment:
DD_DATABASE_URL: postgresql://defectdojo:${DD_DB_PASSWORD:-defectdojo}@postgres-defectdojo:5432/defectdojo
DD_CELERY_BROKER_URL: redis://redis-defectdojo:6379/0
<<: *dd-secrets
entrypoint: ['/entrypoint-celery-worker.sh']
networks:
- charybdis-network
# DefectDojo Celery Beat
defectdojo-beat:
image: defectdojo/defectdojo-django:2.38.1
platform: linux/amd64 # Force x86_64 emulation for ARM64 (Mac Silicon)
container_name: defectdojo-beat
depends_on:
- defectdojo-celery
environment:
DD_DATABASE_URL: postgresql://defectdojo:${DD_DB_PASSWORD:-defectdojo}@postgres-defectdojo:5432/defectdojo
DD_CELERY_BROKER_URL: redis://redis-defectdojo:6379/0
<<: *dd-secrets
entrypoint: ['/entrypoint-celery-beat.sh']
networks:
- charybdis-network
# Keycloak (Identity Provider)
keycloak:
image: quay.io/keycloak/keycloak:24.0
container_name: charybdis-keycloak
command: start-dev --import-realm
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
ports:
- "8082:8080"
volumes:
- ./scripts/keycloak/charybdis-realm.json:/opt/keycloak/data/import/charybdis-realm.json:ro
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\n\r\n' >&3 && cat <&3 | grep -q '200'"]
interval: 10s
timeout: 5s
retries: 15
start_period: 30s
networks:
- charybdis-network
# Charybdis
charybdis:
build:
context: ..
dockerfile: Dockerfile
container_name: charybdis
depends_on:
postgres-charybdis:
condition: service_healthy
keycloak:
condition: service_healthy
defectdojo-uwsgi:
condition: service_started # Changed from service_healthy since healthcheck is disabled
environment:
DATABASE_URL: postgresql://charybdis:${CHARYBDIS_DB_PASSWORD:-charybdis}@postgres-charybdis:5432/charybdis
RUST_LOG: info
DEFECTDOJO_API_URL: http://defectdojo-uwsgi:8081
DEFECTDOJO_API_TOKEN: ${DEFECTDOJO_API_TOKEN:-changeme}
ports:
- "50051:50051" # gRPC
- "8081:8080" # YAML adapter
volumes:
- ./config.toml:/app/config.toml:ro
networks:
- charybdis-network
restart: unless-stopped
# Backstage (commented out - requires custom build)
# To enable Backstage:
# 1. Follow https://backstage.io/docs/getting-started/create-an-app
# 2. Build Docker image: cd backstage-app && yarn install && yarn build
# 3. Uncomment this section and postgres-backstage below
# backstage:
# build:
# context: ./backstage-app
# dockerfile: Dockerfile
# container_name: backstage
# depends_on:
# - charybdis
# - postgres-backstage
# environment:
# POSTGRES_HOST: postgres-backstage
# POSTGRES_PORT: 5432
# POSTGRES_USER: backstage
# POSTGRES_PASSWORD: backstage
# CHARYBDIS_URL: http://charybdis:8080
# ports:
# - "3000:3000"
# - "7007:7007"
# volumes:
# - ./backstage/app-config.yaml:/app/app-config.yaml:ro
# networks:
# - charybdis-network
# restart: unless-stopped
# postgres-backstage:
# image: postgres:15-alpine
# container_name: backstage-postgres
# environment:
# POSTGRES_DB: backstage
# POSTGRES_USER: backstage
# POSTGRES_PASSWORD: backstage
# volumes:
# - backstage-postgres-data:/var/lib/postgresql/data
# healthcheck:
# test: ["CMD-SHELL", "pg_isready -U backstage"]
# interval: 10s
# timeout: 5s
# retries: 5
# networks:
# - charybdis-network
# Helper: grpcurl for testing
grpcurl:
image: fullstorydev/grpcurl:latest
container_name: charybdis-grpcurl
network_mode: service:charybdis
entrypoint: ["/bin/sh"]
command: ["-c", "sleep infinity"]
profiles:
- tools
volumes:
charybdis-postgres-data:
defectdojo-postgres-data:
defectdojo-media:
# backstage-postgres-data: # Uncomment if enabling Backstage
networks:
charybdis-network:
driver: bridge
+78
View File
@@ -0,0 +1,78 @@
# Docker Compose for OpenTelemetry Testing
# This sets up a complete observability stack for local development
version: '3.8'
services:
# OpenTelemetry Collector
# Receives telemetry data and exports to backends
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
container_name: charybdis-otel-collector
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./docker/otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
- "8888:8888" # Prometheus metrics exposed by the collector
- "8889:8889" # Prometheus exporter metrics
- "13133:13133" # health_check extension
networks:
- charybdis-telemetry
# Jaeger - Distributed Tracing UI
jaeger:
image: jaegertracing/all-in-one:latest
container_name: charybdis-jaeger
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686" # Jaeger UI
- "14250:14250" # Jaeger gRPC
networks:
- charybdis-telemetry
# Prometheus - Metrics Storage
prometheus:
image: prom/prometheus:latest
container_name: charybdis-prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--web.enable-lifecycle'
volumes:
- ./docker/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
ports:
- "9090:9090"
networks:
- charybdis-telemetry
# Grafana - Visualization
grafana:
image: grafana/grafana:latest
container_name: charybdis-grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_SECURITY_ADMIN_USER=admin
volumes:
- ./docker/grafana/provisioning:/etc/grafana/provisioning
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
networks:
- charybdis-telemetry
depends_on:
- prometheus
- jaeger
networks:
charybdis-telemetry:
driver: bridge
volumes:
prometheus-data:
grafana-data:
+125
View File
@@ -0,0 +1,125 @@
#!/bin/bash
# scripts/generate-client-cert.sh
# Flexible client certificate generation for any use case
# Usage: ./scripts/generate-client-cert.sh <name> <ou> [days-valid]
set -e
NAME=$1
OU=$2
DAYS_VALID=${3:-365}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CERT_DIR="${SCRIPT_DIR}/../certs"
if [ -z "$NAME" ] || [ -z "$OU" ]; then
echo "Usage: $0 <name> <ou> [days-valid]"
echo ""
echo "Examples:"
echo " $0 payment-service automation # Service certificate (default 365 days)"
echo " $0 jane-doe platform-team # Admin certificate"
echo " $0 jira-plugin plugins # Plugin certificate"
echo " $0 contractor-bob monitoring 30 # Temporary certificate (30 days)"
echo ""
echo "Common OU values:"
echo " automation → service-writer role (create, read, update, list)"
echo " platform-team → admin role (full access)"
echo " monitoring → catalog-reader role (read, list only)"
echo " plugins → plugin role (read, update annotations)"
exit 1
fi
# Ensure cert directory exists
mkdir -p "$CERT_DIR"
cd "$CERT_DIR"
# Check if CA exists
if [ ! -f "ca.pem" ] || [ ! -f "ca-key.pem" ]; then
echo "❌ Error: CA not found!"
echo " Run ./scripts/generate-dev-certs.sh first to create the CA"
exit 1
fi
echo "🔐 Generating certificate for: $NAME"
echo " OU: $OU"
echo " Organization: Company"
echo " Valid for: $DAYS_VALID days"
echo ""
# Generate private key
openssl genrsa -out "${NAME}-key.pem" 2048 2>/dev/null
# Generate CSR
openssl req -new \
-key "${NAME}-key.pem" \
-out "${NAME}.csr" \
-subj "/CN=${NAME}/OU=${OU}/O=Company/C=US" \
2>/dev/null
# Sign certificate
openssl x509 -req \
-days ${DAYS_VALID} \
-in "${NAME}.csr" \
-CA ca.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out "${NAME}-cert.pem" \
-extfile <(echo "extendedKeyUsage=clientAuth") \
2>/dev/null
# Clean up CSR
rm "${NAME}.csr"
echo "✅ Certificate generated successfully!"
echo ""
echo "📋 Files created:"
echo " ${CERT_DIR}/${NAME}-cert.pem"
echo " ${CERT_DIR}/${NAME}-key.pem"
echo ""
echo "🔍 Certificate details:"
openssl x509 -in "${NAME}-cert.pem" -noout -subject -dates
echo ""
echo "🎯 Role mapping (based on OU):"
case $OU in
"platform-team"|"admin")
echo " Role: admin"
echo " Permissions: Full access (entity:*)"
;;
"automation"|"ci-cd")
echo " Role: service-writer"
echo " Permissions: create, read, update, list entities"
;;
"platform"|"monitoring"|"dashboard")
echo " Role: catalog-reader"
echo " Permissions: read, list entities only"
;;
"plugins")
echo " Role: plugin"
echo " Permissions: read, update annotations only"
;;
*)
echo " Role: Unknown - check security-config.yaml for mapping"
echo " You may need to add a rule for OU=${OU}"
;;
esac
echo ""
echo "📦 Distribute these files securely to the user:"
echo " ${NAME}-cert.pem"
echo " ${NAME}-key.pem"
echo " ca.pem"
echo ""
echo "💡 Example distribution:"
echo " # Copy to user's machine:"
echo " scp ${NAME}-cert.pem ${NAME}-key.pem ca.pem user@host:~/.charybdis/"
echo ""
echo " # Or store in secrets manager:"
echo " vault kv put secret/charybdis/${NAME} \\"
echo " cert=@${NAME}-cert.pem \\"
echo " key=@${NAME}-key.pem \\"
echo " ca=@ca.pem"
echo ""
echo "🔒 Security reminder:"
echo " - Keep private key (${NAME}-key.pem) secret!"
echo " - Store securely (1Password, Vault, etc.)"
echo " - Never commit to git"
echo " - Valid for ${DAYS_VALID} days (expires $(date -v +${DAYS_VALID}d '+%Y-%m-%d' 2>/dev/null || date -d "+${DAYS_VALID} days" '+%Y-%m-%d' 2>/dev/null || echo 'N/A'))"
echo ""
+134
View File
@@ -0,0 +1,134 @@
#!/bin/bash
set -e
# Charybdis Development Certificate Generation Script
# This script generates self-signed certificates for local development and testing
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CERT_DIR="${SCRIPT_DIR}/../certs"
DAYS_VALID=365
echo "🔐 Charybdis Certificate Generation"
echo "===================================="
echo ""
# Create certs directory
mkdir -p "${CERT_DIR}"
cd "${CERT_DIR}"
# Function to generate certificate
generate_cert() {
local name=$1
local cn=$2
local ou=$3
local cert_type=$4 # "server" or "client"
echo "📝 Generating ${name}..."
# Generate private key
openssl genrsa -out "${name}-key.pem" 2048 2>/dev/null
# Generate CSR
openssl req -new \
-key "${name}-key.pem" \
-out "${name}.csr" \
-subj "/CN=${cn}/OU=${ou}/O=Charybdis-Dev/C=US" \
2>/dev/null
# Generate certificate
if [ "${cert_type}" = "server" ]; then
openssl x509 -req \
-days ${DAYS_VALID} \
-in "${name}.csr" \
-CA ca.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out "${name}-cert.pem" \
-extfile <(echo "extendedKeyUsage=serverAuth
subjectAltName=DNS:localhost,DNS:charybdis,DNS:charybdis.local,IP:127.0.0.1") \
2>/dev/null
else
openssl x509 -req \
-days ${DAYS_VALID} \
-in "${name}.csr" \
-CA ca.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out "${name}-cert.pem" \
-extfile <(echo "extendedKeyUsage=clientAuth") \
2>/dev/null
fi
# Clean up CSR
rm "${name}.csr"
echo " ✅ Generated ${name}-cert.pem and ${name}-key.pem"
}
# 1. Generate Root CA
echo "🏛️ Generating Root CA..."
if [ ! -f ca.pem ]; then
openssl genrsa -out ca-key.pem 4096 2>/dev/null
openssl req -new -x509 \
-days 3650 \
-key ca-key.pem \
-out ca.pem \
-subj "/CN=Charybdis Dev Root CA/O=Charybdis-Dev/C=US" \
2>/dev/null
echo " ✅ Generated ca.pem (Root CA)"
else
echo " ️ Root CA already exists, skipping"
fi
echo ""
# 2. Generate Server Certificate
echo "🖥️ Generating Server Certificate..."
generate_cert "server" "localhost" "platform" "server"
echo ""
# 3. Generate Client Certificates
echo "👥 Generating Client Certificates..."
echo ""
# Admin client
generate_cert "admin" "admin-user" "platform-team" "client"
# CI/CD Pipeline client
generate_cert "ci-pipeline" "ci-pipeline" "automation" "client"
# Backstage client
generate_cert "backstage" "backstage" "platform" "client"
# DefectDojo plugin client
generate_cert "defectdojo-plugin" "defectdojo-plugin" "plugins" "client"
# DependencyTrack plugin client
generate_cert "dependencytrack-plugin" "dependencytrack-plugin" "plugins" "client"
echo ""
echo "✅ Certificate Generation Complete!"
echo ""
echo "📂 Certificates generated in: ${CERT_DIR}"
echo ""
echo "📋 Summary:"
echo " - Root CA: ca.pem, ca-key.pem"
echo " - Server: server-cert.pem, server-key.pem"
echo " - Clients:"
echo " • admin-cert.pem, admin-key.pem (role: admin)"
echo " • ci-pipeline-cert.pem, ci-pipeline-key.pem (role: service-writer)"
echo " • backstage-cert.pem, backstage-key.pem (role: catalog-reader)"
echo " • defectdojo-plugin-cert.pem, defectdojo-plugin-key.pem (role: plugin)"
echo " • dependencytrack-plugin-cert.pem, dependencytrack-plugin-key.pem (role: plugin)"
echo ""
echo "🔍 Verify certificates:"
echo " openssl x509 -in ${CERT_DIR}/server-cert.pem -text -noout"
echo ""
echo "🚀 Use with Charybdis:"
echo " export SECURITY_MTLS_ENABLED=true"
echo " export SECURITY_MTLS_SERVER_CERT=${CERT_DIR}/server-cert.pem"
echo " export SECURITY_MTLS_SERVER_KEY=${CERT_DIR}/server-key.pem"
echo " export SECURITY_MTLS_CLIENT_CA=${CERT_DIR}/ca.pem"
echo ""
echo "⚠️ These are DEVELOPMENT certificates only!"
echo " Do NOT use in production. Generate proper certificates from a trusted CA."
echo ""
@@ -0,0 +1,82 @@
{
"realm": "charybdis",
"enabled": true,
"clients": [
{
"clientId": "charybdis-sync",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "charybdis-sync-secret",
"serviceAccountsEnabled": true,
"directAccessGrantsEnabled": false,
"publicClient": false,
"protocol": "openid-connect",
"fullScopeAllowed": true
}
],
"groups": [
{
"name": "engineering",
"path": "/engineering",
"subGroups": [
{
"name": "backend",
"path": "/engineering/backend",
"subGroups": []
},
{
"name": "frontend",
"path": "/engineering/frontend",
"subGroups": []
}
]
},
{
"name": "security",
"path": "/security",
"subGroups": []
}
],
"users": [
{
"username": "alice",
"email": "alice@example.com",
"firstName": "Alice",
"lastName": "Martin",
"enabled": true,
"emailVerified": true,
"groups": ["/engineering/backend", "/security"]
},
{
"username": "bob",
"email": "bob@example.com",
"firstName": "Bob",
"lastName": "Dupont",
"enabled": true,
"emailVerified": true,
"groups": ["/engineering/frontend"]
},
{
"username": "charlie",
"email": "charlie@example.com",
"firstName": "Charlie",
"lastName": "Garcia",
"enabled": true,
"emailVerified": false,
"groups": ["/engineering"]
},
{
"username": "service-account-charybdis-sync",
"enabled": true,
"serviceAccountClientId": "charybdis-sync",
"clientRoles": {
"realm-management": [
"view-users",
"query-users",
"query-groups",
"view-realm"
]
}
}
]
}