# Charybdis - Architecture **Version**: 2.0 **Last Updated**: 2026-05-06 **Status**: Living Document > For the product vision and roadmap, see [VISION.md](../VISION.md). --- ## Executive Summary **Charybdis** is a security-native platform engineering tool that unifies software catalog, vulnerability management, and compliance posture in a single event-driven platform. Built in Rust, deployed as a single binary. ### Core Value Proposition **Problem**: - Platform engineers run 5+ disconnected tools (Backstage, DefectDojo, Dependency-Track, license scanners, compliance spreadsheets) - Software catalogs rely on static YAML files that drift from reality - Security posture is invisible at the catalog level — vulns live in separate tools with no link to services - Compliance evidence is assembled manually from fragmented data sources **Solution**: - **Dynamic software catalog** — event-driven, gRPC-native, no static YAML - **Native vulnerability management** — ingest scan results (SARIF, CycloneDX, SPDX), triage, assess, track - **Security gates & compliance** — severity thresholds, license policies, compliance framework mappings - **Plugin system for integrations** — Slack, Jira, GitHub, custom tools react to events - **Single binary deployment** — Rust + PostgreSQL, ~50MB RAM, sub-millisecond latency ### Use Case Examples **Service registration with auto-provisioning:** ``` CI/CD Pipeline (Python) → gRPC CreateEntity(kind=Component) ↓ Charybdis persists entity ↓ Event: EntityCreated published ↓ ┌────────────────────┴────────────────────┐ ↓ ↓ Jira Plugin Slack Plugin - Creates onboarding epic - Notifies #platform channel ↓ ↓ └────────────────────┬────────────────────┘ ↓ Entity visible via gRPC API / Backstage YAML adapter ``` **Vulnerability ingestion with security gates:** ``` Scanner (Trivy) → CI/CD → gRPC IngestScan(sarif_report) ↓ Charybdis parses SARIF Creates Vulnerability entities linked to Component ↓ Rules engine evaluates auto-assessment Security gate checks thresholds ↓ ┌────────────────────┴────────────────────┐ ↓ ↓ Gate PASSED Gate FAILED - Vulns tracked - Slack alert to #security - Dashboard updated - Jira ticket created - CI/CD pipeline blocked ``` --- ## Architectural Principles ### 1. **No Database Migrations Ever** The database schema is created once on first startup and **never changes**. **How it works**: - Entities stored as protobuf bytes in `entity_data BYTEA` column - Plugins add new entity types via protobuf `oneof` variants - Build system regenerates code, database schema remains static **Benefits**: - Deploy new plugins without downtime - No migration scripts to manage - Forward/backward compatibility built-in - Easy rollback (protobuf versioning) ### 2. **gRPC First, Everything Else is Adapter** Charybdis provides **only gRPC APIs**. All other interfaces (REST, YAML, GraphQL) are adapters on top. **Rationale**: - **Strong typing**: Protobuf ensures type safety across all languages - **Performance**: Binary protocol, efficient serialization - **Multi-language**: Official gRPC clients for 10+ languages - **Streaming**: Built-in support for real-time updates (future) - **Code generation**: Automatic client/server code generation **What Charybdis provides**: - ✅ gRPC API with protobuf definitions - ✅ YAML adapter endpoints (for Backstage compatibility/migration) - ❌ REST API (teams can add if needed via gateway) - ❌ GraphQL (teams can add if needed) ### 3. **Plugin-Based Extensibility** Plugins are **compile-time integrated** Rust crates that: 1. **Define entity types** via protobuf schemas 2. **React to events** via EventHandler trait 3. **Call external APIs** to synchronize state 4. **Store references** in entity annotations 5. **(Future) Extend gRPC API** with custom endpoints **Plugin characteristics**: - Not hot-swappable (require rebuild) - Type-safe at compile time - No runtime dependency resolution - Configurable via YAML config file - Can be developed by community ### 4. **Event-Driven Orchestration** Events are published **after** entities are persisted: ``` CRUD Operation → Persist to DB → Publish Event → Plugins React ``` **Event flow**: 1. Entity created/updated/deleted in database 2. Event published to event bus with full entity data 3. All subscribed plugin handlers receive event 4. Plugins filter events they care about 5. Plugins perform asynchronous actions 6. Plugins update entity annotations (via repository) **Error handling**: - Plugin errors **do not** fail the CRUD operation - Entity is already persisted before event publishing - Plugins log errors for observability - Retries handled by event bus backend ### 5. **Backstage Compatibility by Design** Charybdis entity model is **100% compatible** with Backstage's descriptor format. **How it works**: - Core protobuf types mirror Backstage YAML structure - YAML adapter endpoints serve entities in Backstage format - Backstage reads Charybdis as a dynamic location provider - No changes needed to Backstage frontend **Key compatibility points**: - Entity `kind`, `apiVersion`, `metadata`, `spec` structure - Annotation format (reverse-DNS style) - Relationship types (`dependsOn`, `partOf`, etc.) - All standard Backstage entity kinds supported --- ## System Architecture ### Component Overview ``` ┌─────────────────────────────────────────────────────────────────┐ │ Client Layer │ │ CI/CD Pipelines (Python, Go, Node.js, Bash, etc.) │ │ Security Scanners (SARIF, CycloneDX, SPDX output) │ │ IaC Tools (Terraform, Pulumi, Crossplane) │ │ Backstage (via YAML adapter, optional) │ └────────────────┬────────────────────────────────────────────────┘ │ │ gRPC / YAML HTTP ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Charybdis Core │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ gRPC API │ │ │ │ EntityService: CRUD + List (with field masks) │ │ │ │ ScanService: IngestScan (SARIF, CycloneDX, SPDX) │ │ │ │ AssessmentService: Triage, Accept, Remediate │ │ │ └────────────────────┬─────────────────────────────────────┘ │ │ │ │ │ ┌────────────────────▼─────────────────────────────────────┐ │ │ │ Security Engine (native) │ │ │ │ - Vulnerability tracking per entity │ │ │ │ - Rules engine (auto-assessment) │ │ │ │ - Security gates (severity thresholds) │ │ │ │ - License compliance │ │ │ └────────────────────┬─────────────────────────────────────┘ │ │ │ │ │ ┌────────────────────▼─────────────────────────────────────┐ │ │ │ Adapters │ │ │ │ - YAML Adapter (Backstage compat / migration) │ │ │ └────────────────────┬─────────────────────────────────────┘ │ │ │ │ │ ┌────────────────────▼─────────────────────────────────────┐ │ │ │ Repository Layer │ │ │ │ - PostgreSQL with protobuf storage │ │ │ │ - JSONB annotations for fast queries │ │ │ │ - Zero-migration schema │ │ │ └────────────────────┬─────────────────────────────────────┘ │ │ │ │ │ ┌────────────────────▼─────────────────────────────────────┐ │ │ │ Event Bus System │ │ │ │ - Memory backend (dev) │ │ │ │ - Redis backend (prod, future) │ │ │ └────────────────────┬─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ │ Entity / Vulnerability / Gate events ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Plugin Layer (integrations) │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │ │ │ Slack/Teams │ │ Jira/GitHub │ │ Custom Plugins │ │ │ │ Notifications │ │ Issue Track │ │ Your integrations │ │ │ └──────┬────────┘ └──────┬───────┘ └──────────┬─────────────┘ │ └─────────┼──────────────────┼─────────────────────┼──────────────┘ ▼ ▼ ▼ Slack/Teams API Jira/GitHub API External APIs ``` ### Data Flow #### Entity Creation Flow ``` 1. Client (CI/CD) ↓ gRPC CreateEntityRequest 2. Charybdis gRPC API ↓ Validate entity ↓ Generate UUID, timestamps 3. Entity Repository ↓ Serialize to protobuf bytes ↓ INSERT INTO entities 4. PostgreSQL ↓ Entity persisted 5. Event Bus ↓ Publish EntityCreated event (with full entity data) 6. Plugin Handlers (parallel) ├─ DefectDojo: Create product → Store product_id in annotations ├─ DependencyTrack: Create project → Store project_uuid in annotations └─ Custom: Custom actions 7. Repository.update_annotations() ↓ Merge annotations from plugins ↓ UPDATE entities SET annotations = ... 8. PostgreSQL ↓ Entity updated with external tool IDs 9. Return CreateEntityResponse to client ``` #### Backstage Integration Flow ``` 1. Backstage Catalog Backend ↓ HTTP GET /yaml/locations 2. Charybdis YAML Adapter ↓ Query repository.list_all() ↓ Convert entities to Location YAML 3. Backstage receives location list ↓ Fetches each entity: GET /yaml/entities/:id 4. Charybdis YAML Adapter ↓ repository.get_by_id() ↓ Convert entity protobuf → Backstage YAML 5. Backstage ingests entities ↓ Displays in UI with all annotations ↓ Shows linked DefectDojo products, etc. ``` --- ## Core Entity Model ### Protobuf Schema ```protobuf message Entity { // System-managed fields string id = 1; // UUID (auto-generated) google.protobuf.Timestamp created_at = 21; google.protobuf.Timestamp updated_at = 22; // Backstage-compatible fields string api_version = 2; // "backstage.io/v1alpha1" string kind = 3; // "Component", "API", "User", etc. // Polymorphic metadata (identifying information) oneof metadata { // Core types (built-in) ComponentMetadata component_metadata = 10; APIMetadata api_metadata = 11; UserMetadata user_metadata = 12; GroupMetadata group_metadata = 13; SystemMetadata system_metadata = 14; DomainMetadata domain_metadata = 15; ResourceMetadata resource_metadata = 16; LocationMetadata location_metadata = 17; TemplateMetadata template_metadata = 18; // Plugin types (dynamically added) // Example: DefectDojoProductMetadata defectdojo_product_metadata = 100; } // Polymorphic spec (configuration and behavior) oneof spec { // Core types (built-in) ComponentSpec component_spec = 10; APISpec api_spec = 11; UserSpec user_spec = 12; GroupSpec group_spec = 13; SystemSpec system_spec = 14; DomainSpec domain_spec = 15; ResourceSpec resource_spec = 16; LocationSpec location_spec = 17; TemplateSpec template_spec = 18; // Plugin types (dynamically added) // Example: DefectDojoProductSpec defectdojo_product_spec = 100; } // Annotations - external tool references // Format: "tool.com/resource-type-attribute" // Examples: // "defectdojo.com/product-id": "42" // "dependencytrack.com/project-uuid": "550e8400-..." // "github.com/repo-slug": "myorg/myrepo" map annotations = 20; } ``` ### Backstage Entity Kinds Mapping All Backstage entity kinds must be supported: | Backstage Kind | Charybdis Proto Type | Status | |----------------|---------------------|--------| | Component | ComponentMetadata/Spec | ✅ Done | | Service | ServiceMetadata/Spec | ✅ Done | | API | APIMetadata/Spec | ✅ Done | | User | UserMetadata/Spec | ✅ Done | | Group | GroupMetadata/Spec | ✅ Done | | System | SystemMetadata/Spec | ✅ Done | | Domain | DomainMetadata/Spec | ✅ Done | | Resource | ResourceMetadata/Spec | ✅ Done | | Location | LocationMetadata/Spec | Not planned (entities are event-driven, not file-based) | | Template | TemplateMetadata/Spec | Phase 3 (Scaffolder) | --- ## YAML Adapter Layer ### Purpose Serve entities in Backstage-compatible YAML format over HTTP. ### API Specification #### 1. List Locations **Endpoint**: `GET /yaml/locations` **Purpose**: Return a dynamic Location entity that lists all entities in Charybdis. **Response Format** (Backstage Location YAML): ```yaml apiVersion: backstage.io/v1alpha1 kind: Location metadata: name: charybdis-all-entities description: Dynamic location managed by Charybdis spec: type: charybdis targets: - http://charybdis.company.com/yaml/entities/service-payment - http://charybdis.company.com/yaml/entities/service-auth - http://charybdis.company.com/yaml/entities/api-payments-v1 - http://charybdis.company.com/yaml/entities/user-john-doe # ... all entities ``` **Implementation Notes**: - Query `SELECT id, kind FROM entities` - Generate URL for each entity: `/yaml/entities/{kind}-{id}` - Return as Backstage Location YAML - Cache response (invalidate on entity CRUD) #### 2. Get Entity by ID **Endpoint**: `GET /yaml/entities/:id` **Purpose**: Return a specific entity in Backstage YAML format. **Response Format** (Backstage Component YAML): ```yaml apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: payment-service namespace: production description: Core payment processing service annotations: github.com/repo-slug: myorg/payment-service defectdojo.com/product-id: "42" dependencytrack.com/project-uuid: "550e8400-e29b-41d4-a716-446655440000" tags: - payments - critical links: - url: https://dashboard.company.com/payment-service title: Dashboard icon: dashboard spec: type: service lifecycle: production owner: payments-team system: payment-system dependsOn: - component:auth-service providesApis: - payments-api-v1 ``` **Implementation Notes**: - Query `repository.get_by_id(id)` - Convert Entity protobuf → Backstage YAML - Map `oneof metadata/spec` to appropriate YAML structure - Include all annotations - Return as `text/yaml` content type #### 3. Configuration in Backstage **Backstage app-config.yaml**: ```yaml catalog: locations: - type: url target: http://charybdis.company.com/yaml/locations rules: - allow: [Component, API, User, Group, System, Domain, Resource] ``` **How it works**: 1. Backstage fetches `/yaml/locations` on catalog refresh 2. Parses Location YAML to get entity URLs 3. Fetches each entity URL (`/yaml/entities/:id`) 4. Ingests entities into Backstage catalog 5. Displays in UI with all metadata and annotations --- ## Plugin System ### Plugin Architecture Charybdis supports **two types of plugins**, both compile-time integrated: #### **1. Event-Driven Plugins** React to entity lifecycle events (create, update, delete): - **Example**: DefectDojo, DependencyTrack - **Implement**: `EventDrivenPlugin` trait - **Provide**: `ResourceHandler` implementations - **Triggered by**: Entity CRUD operations #### **2. Sync Plugins** Pull data from external sources on a schedule: - **Example**: Okta, Keycloak, Active Directory - **Implement**: `SyncPlugin` trait - **Scheduled by**: Cron expressions - **Triggered by**: Time-based schedule or manual API call ### Generic Plugin Utilities (2025-01-15) All plugins have access to reusable utilities in `src/plugins/`: #### **PluginHttpClient** Generic HTTP client with multiple auth methods: - Token authentication (DefectDojo) - Bearer authentication (modern APIs) - API Key authentication (DependencyTrack) - Basic authentication (JIRA, Bitbucket) - Methods: `get()`, `post()`, `put()`, `patch()`, `delete()` #### **AnnotationHelper** Consistent API for storing/retrieving plugin metadata: - `set_id()` / `get_id()` - Store external resource IDs - `set()` / `get()` - Store arbitrary metadata - Enforces naming: `{plugin}.com/{resource}-id` - Prevents annotation conflicts between plugins #### **FieldMapper** Maps entity fields to external tool formats: - Direct field paths: `"metadata.name"` - Static values: `{"value": "Web Application"}` - Complex mappings with entity resolution - Repository integration for cross-entity queries #### **DateUtils** Common date/time operations: - `today()`, `today_plus_days(n)` - Date formatting - `engagement_date_range(days)` - For time-bound resources - `iso_timestamp()` - ISO 8601 timestamps ### Plugin Responsibilities Plugins can: 1. **Define custom entity types** (via protobuf) 2. **React to entity events** (via ResourceHandler trait) 3. **Call external APIs** (using PluginHttpClient) 4. **Store metadata** (using AnnotationHelper) 5. **Map fields** (using FieldMapper) 6. **Schedule syncs** (SyncPlugins with cron) 7. **(Future) Extend gRPC API** (custom services) ### Plugin Lifecycle ``` 1. Development ├─ Define protobuf schema (metadata/spec messages) ├─ Implement EventHandler trait ├─ Implement external API client └─ Add configuration schema 2. Registration ├─ Add to plugins.toml (enabled = true) └─ Add to Cargo.toml dependencies 3. Build Time ├─ build.rs reads plugins.toml ├─ Generates entities.proto with plugin types ├─ Compiles all protobuf to Rust code └─ Links plugin into binary 4. Runtime ├─ Load plugin configuration from config file ├─ Initialize plugin handler ├─ Subscribe to event bus └─ React to entity events ``` ### Plugin Configuration System **File**: `config.toml` (TOML format with `${VAR}` env var substitution) ```toml # Server configuration [server] grpc_host = "[::1]" grpc_port = 50051 [server.yaml_adapter] enabled = true host = "0.0.0.0" port = 8080 # Database [database] url = "${DATABASE_URL}" max_connections = 10 # Plugin configurations [plugins.defectdojo] enabled = true base_url = "${DEFECTDOJO_API_URL}" api_token = "${DEFECTDOJO_API_TOKEN}" [plugins.defectdojo.default_engagement] auto_create = true name = "CI/CD Pipeline" engagement_type = "CI/CD" [plugins.keycloak] enabled = false # base_url = "https://keycloak.company.com" # realm = "master" [plugins.dependencytrack] enabled = false # base_url = "https://dependencytrack.company.com" # api_key = "${DEPENDENCYTRACK_API_KEY}" ``` See `config.toml.example` for the full reference with all options documented. **Configuration loading** (`src/config.rs`): ```rust pub struct Config { pub server: ServerConfig, pub database: DatabaseConfig, pub security: SecurityConfig, pub telemetry: TelemetryConfig, pub plugins: PluginsConfig, } impl Config { pub fn from_file(path: &str) -> Result { let content = std::fs::read_to_string(path)?; // Substitute ${VAR} with environment variable values let content = substitute_env_vars(&content); let config: Config = toml::from_str(&content)?; Ok(config) } } ``` **Each plugin implements**: ```rust pub trait Plugin { fn name(&self) -> &str; fn plugin_type(&self) -> PluginType; fn load_config(&mut self, config: PluginConfig) -> Result<()>; fn validate_config(&self) -> Result<()>; } ``` ### Event Handler Behavior Configuration Plugins can configure different behaviors based on: - Entity kind - Event type (created/updated/deleted) - Custom conditions **Example**: DefectDojo plugin config (`config.toml`) ```toml [plugins.defectdojo] enabled = true base_url = "${DEFECTDOJO_API_URL}" api_token = "${DEFECTDOJO_API_TOKEN}" [plugins.defectdojo.default_engagement] auto_create = true name = "CI/CD Pipeline" engagement_type = "CI/CD" ``` **Handler Implementation**: ```rust impl EventHandler for DefectDojoHandler { async fn handle_event(&self, event: &EntityEvent) -> EventResult<()> { // Check if plugin enabled if !self.config.enabled { return Ok(()); } // Match event type to configured behavior let behavior = match event.event_type { EntityEventType::Created => &self.config.on_entity_created, EntityEventType::Updated => &self.config.on_entity_updated, EntityEventType::Deleted => &self.config.on_entity_deleted, }; // Find matching rule for this entity kind for rule in behavior { if rule.matches(&event) { return self.execute_action(&rule.action, event).await; } } Ok(()) } } ``` --- ## Future: Plugin API Extensions **Future capability**: Plugins can expose custom gRPC endpoints. ### Example: DefectDojo Query Plugin **Plugin extends API with custom service**: ```protobuf // plugins/defectdojo/proto/defectdojo_service.proto service DefectDojoService { // Get findings for a specific entity rpc GetEntityFindings(GetEntityFindingsRequest) returns (GetEntityFindingsResponse); // Get all active vulnerabilities rpc GetActiveVulnerabilities(GetActiveVulnerabilitiesRequest) returns (GetActiveVulnerabilitiesResponse); // Sync entity with DefectDojo rpc SyncEntity(SyncEntityRequest) returns (SyncEntityResponse); } message GetEntityFindingsRequest { string entity_id = 1; string severity_filter = 2; // "Critical", "High", etc. bool active_only = 3; } message GetEntityFindingsResponse { repeated Finding findings = 1; int32 total_count = 2; } message Finding { int32 id = 1; string title = 2; string severity = 3; string description = 4; google.protobuf.Timestamp date = 5; bool active = 6; } ``` **Implementation**: ```rust pub struct DefectDojoServiceImpl { client: DefectDojoClient, repository: EntityRepository, } #[async_trait] impl DefectDojoService for DefectDojoServiceImpl { async fn get_entity_findings( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); // Get entity to retrieve DefectDojo product ID let entity = self.repository.get_by_id(&req.entity_id).await?; let product_id = entity .annotations .get("defectdojo.com/product-id") .ok_or_else(|| Status::not_found("No DefectDojo product linked"))?; // Fetch findings from DefectDojo API let findings = self.client .get_findings(product_id, &req.severity_filter, req.active_only) .await?; Ok(Response::new(GetEntityFindingsResponse { findings: findings.into_iter().map(|f| f.into()).collect(), total_count: findings.len() as i32, })) } } ``` **Registration in main.rs**: ```rust // Register plugin's custom gRPC service if defectdojo_plugin.is_enabled() { let defectdojo_service = DefectDojoServiceServer::new( DefectDojoServiceImpl::new(defectdojo_client, repository.clone()) ); server = server.add_service(defectdojo_service); } ``` **Client usage**: ```bash grpcurl -d '{ "entity_id": "service-payment", "severity_filter": "Critical", "active_only": true }' localhost:50051 charybdis.plugins.defectdojo.DefectDojoService/GetEntityFindings ``` --- ## Technology Decisions ### Why gRPC Only? **Decision**: Charybdis core provides only gRPC. Teams add REST/GraphQL as needed. **Rationale**: - **Strong typing**: Protobuf schemas ensure correctness across languages - **Performance**: Binary protocol, efficient for CI/CD use cases - **Multi-language**: Official clients for Python, Go, Node.js, Java, Rust, C++, C#, etc. - **Streaming**: Built-in bidirectional streaming (future: real-time updates) - **Code generation**: Automatic client generation eliminates boilerplate - **Focus**: Core team maintains one high-quality API surface **Teams can add REST if needed**: ``` grpc-gateway or Envoy → Charybdis gRPC ``` ### Why PostgreSQL + JSONB? **Decision**: Use PostgreSQL with protobuf BYTEA storage and JSONB annotations. **Rationale**: - **Scale**: Handles 1000s of entities easily (tested: 170k resources) - **JSONB performance**: GIN indexes enable fast annotation queries - **Familiarity**: Most teams already run PostgreSQL - **Reliability**: ACID transactions, mature ecosystem - **Future-proof**: Can add read replicas, partitioning if needed **Alternative considered**: MongoDB - **Verdict**: PostgreSQL with JSONB provides same flexibility with better consistency guarantees ### Why Compile-Time Plugins? **Decision**: Plugins are compile-time integrated, not runtime loaded. **Rationale**: - **Type safety**: Rust compiler ensures correctness - **Performance**: No dynamic loading overhead - **Simplicity**: No plugin version compatibility matrix - **Security**: No arbitrary code execution - **Trade-off**: Requires rebuild to add plugins (acceptable for infrastructure tool) **Community plugins**: Published as crates, teams include in their build. ### Why Event-Driven? **Decision**: Plugins react to events after entity persistence. **Rationale**: - **Decoupling**: Plugins can't break core CRUD operations - **Resilience**: Plugin failures are logged, not propagated - **Async**: Long-running operations don't block API - **Extensibility**: Add plugins without modifying core - **Observability**: Events provide audit trail **Trade-off**: Plugins can't prevent entity creation (validation must be in core or client). --- ## Scale Estimates ### Reference Deployment **Company**: ~100 engineers **Backstage Entities**: - Components: ~1,000 - APIs: ~1,000 - Users: ~700 - Groups: ~200 - Systems: ~50 - Domains: ~150 - Resources: ~170,000 **Charybdis Requirements**: - PostgreSQL: ~500MB storage (with JSONB annotations) - Memory: ~256MB for Charybdis service - CPU: Minimal (mostly I/O bound) **10x Scale** (1,000 engineers, 1.7M resources): - PostgreSQL: ~5GB storage - Memory: ~512MB - CPU: Still minimal with proper indexing **Bottlenecks**: - JSONB queries on annotations (solved with GIN indexes) - Event bus throughput (solved with Redis/RabbitMQ backend) - Plugin external API rate limits (solved with plugin-level queuing) --- ## Development Roadmap > See [VISION.md](VISION.md) for the full product vision and strategic roadmap. ### Phase 0: Catalog Foundation (Done) - ✅ Zero-migration database architecture - ✅ gRPC API with protobuf (full entity CRUD) - ✅ Event bus system (MemoryEventBus) - ✅ Event dispatcher architecture (generic + plugin-specific) - ✅ All Backstage entity kinds (Component, System, API, User, Group, Domain, Resource) - ✅ YAML adapter endpoints (Backstage compatibility) - ✅ gRPC field masks for partial updates - ✅ OpenTelemetry observability (traces, metrics, logs) - ✅ mTLS + RBAC security framework - ✅ Config loading from TOML with env var substitution - ✅ Plugin trait architecture (EventDriven + Sync) - ✅ Generic plugin utilities (HttpClient, AnnotationHelper, FieldMapper, DateUtils) - ✅ DefectDojo plugin (products, engagements, owner resolution) - ✅ Keycloak plugin (user/group sync with annotations) ### Phase 1: Security Core (Current Focus) **Goal**: Native vulnerability management and scan ingestion. - [ ] Vulnerability/Observation entity kind (protobuf + storage) - [ ] ScanService gRPC endpoint for scan ingestion - [ ] SARIF parser (covers majority of modern scanners) - [ ] CycloneDX parser (SBOMs + vulnerability data) - [ ] SPDX parser (license data) - [ ] Assessment workflow (triage, accept risk, remediate) - [ ] Rules engine for auto-assessment - [ ] Security gates (severity thresholds per product) - [ ] License tracking and policy engine ### Phase 2: Compliance & Integrations **Goal**: Compliance frameworks and integration plugins. - [ ] Compliance framework mappings (NIS2, SOC2, DORA) - [ ] VEX document support (CSAF, OpenVEX) - [ ] Export/reporting (PDF, Excel) - [ ] Notification plugins (Slack, Teams, email) - [ ] Issue tracker plugins (Jira, GitHub, GitLab) - [ ] Redis event bus backend ### Phase 3: Scaffolder & Ecosystem **Goal**: Service scaffolding and community growth. - [ ] Service scaffolder (Git-native templates) - [ ] Event-driven provisioning on scaffold - [ ] Plugin SDK documentation - [ ] Helm chart & 1-click deploy - [ ] Community plugin registry --- ## Success Criteria ### Phase 1 Success (Security Core) - [ ] Ingest SARIF scan results via gRPC - [ ] Vulnerabilities linked to catalog entities - [ ] Rules engine auto-assesses based on severity/component - [ ] Security gates block on threshold violations - [ ] Assessment workflow (triage → accept/remediate) - [ ] License data ingested from CycloneDX/SPDX ### Production Success - [ ] 1,000+ entities managed with security posture - [ ] <100ms p99 latency for CRUD operations - [ ] Single `helm install` deployment - [ ] Documentation complete - [ ] 3+ integration plugins in production --- ## Key Decisions Summary | Decision | Choice | Rationale | |----------|--------|-----------| | **API Protocol** | gRPC only | Strong typing, multi-language, performance | | **Frontend** | Backstage YAML adapter | Compatible with existing Backstage deployments | | **Security features** | Native (core) | First-class, not plugins. Vulns, compliance, gates in the core | | **External integrations** | Plugins | Slack, Jira, etc. as event-driven plugins | | **Database** | PostgreSQL + JSONB | Scale, familiarity, ACID | | **Plugin Loading** | Compile-time | Type safety, performance, security | | **Event Model** | Post-persistence | Resilience, decoupling | | **Schema Evolution** | Protobuf only | Zero migrations | | **Configuration** | TOML + env vars | Flexibility, Rust-native | | **Deployment** | Single binary + Docker + K8s | Cloud-native, minimal footprint | --- ## References - [Backstage Descriptor Format](https://backstage.io/docs/features/software-catalog/descriptor-format) - [gRPC Documentation](https://grpc.io/docs/) - [Protocol Buffers Guide](https://protobuf.dev/) - [PostgreSQL JSONB](https://www.postgresql.org/docs/current/datatype-json.html) --- ## Document Version History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2025-01-03 | Initial comprehensive architecture document | | 1.1 | 2025-01-15 | Added dual plugin architecture, generic utilities, updated roadmap | | 2.0 | 2026-04-02 | Pivoted to security-native platform engineering tool. Extracted product vision to VISION.md. Added native vulnerability management, compliance frameworks to architecture. Updated roadmap and success criteria. | --- **This document covers architecture and technical decisions.** For product vision and strategic direction, see [VISION.md](VISION.md). All code and documentation must align with these documents.