Configuration
Platform-wide validation schemas and defaults for providers, resources, and topologies.
Purpose
Define validation rules for providers/resources/topologies, establish platform defaults, support multiple layered configs with merge order (built-in → custom 1…N, later overrides).
Schema
apiVersion: strata.huybrechts.xyz/v1
kind: configuration
meta:
name: <name> # ^[a-z][a-z0-9_]*$
spec:
configuration: {} # Platform defaults
properties: {} # Custom properties
providers: [] # Provider definitions (regions, resources)
topologies: [] # Topology schemas (components, rules)
Providers
Define allowed regions and resource validation patterns:
providers:
- name: <provider>
additional_regions: false # Restrict to listed regions
regions: [eu-fr, us-ny]
additional_resources: false # Restrict to defined resources
resources:
- name: virtualmachine
category: compute
configuration: # Regex validation
cpu_cores: "^[1-9][0-9]?$" # 1-99
ram_mb: "^(512|1024|2048|4096)$"
Topologies
Define cluster component rules:
topologies:
- type: docker-swarm
components:
- role: manager
is_control: true
min_count: 1
max_count: 7
- role: worker
min_count: 1
max_count: 0 # unlimited
Layering — Artifact Path Hierarchies
Define how deployment artifacts are organized into a hierarchical path structure. Use layering when different deployment files need to be placed into different directories during the build process.
Layering Schemes
Two formats are supported:
Single-scheme layering (spec.layering — deprecated, for backward compatibility):
spec:
layering:
- name: zone
required: true
- name: customer
required: true
- name: environment
required: false
default: dev
This creates a single layer scheme applied to all deployments. The artifact path is constructed by combining values from each layer in order, separated by /. For example, zone: europe, customer: contoso, environment: prd produces artifact path europe/contoso/prd.
Multi-scheme layering (spec.layerings — recommended):
spec:
layerings:
- name: zone-tenant
scope: zones/**
layers:
- name: zone
required: true
- name: customer
required: true
- name: environment
required: false
default: dev
- name: landscape-ring
scope: landscape/**
layers:
- name: landscape
required: true
- name: ring
required: true
This declares multiple layer schemes. Each scheme has:
name: Identifier for the schemescope: Glob pattern matching deployment file paths (e.g.,zones/**,landscape/**,**)layers: Ordered list of layer definitions
During deployment validation, the deployment file’s path is matched against schemes in order. The first matching scope wins, and that scheme’s layers are used to resolve the artifact path.
Layer Definition
Each layer entry has:
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
Layer name (e.g., |
|
|
|
Whether the deployment must provide a value for this layer |
|
|
|
Default value if no value is provided (only if |
Important: Layer names are arbitrary and must be unique within a scheme. The layer named environment no longer has special meaning — any valid layer can be the last layer in the hierarchy.
Artifact Path Resolution
When a deployment references a configuration file with layering:
Scope matching: The deployment file’s relative path (from workspace root) is matched against all scheme scopes in order using glob pattern matching
First match wins: The first scheme whose scope matches the file path is selected
Value resolution: Layer values are extracted from the deployment’s
propertiesfield or use defaultsPath construction: The artifact path is built as
layer1_value/layer2_value/.../layerN_value
Example matching:
Deployment files:
├── zones/europe/contoso/prd/deploy.yaml ← matches scope: zones/**
├── landscape/platform/ring2/deploy.yaml ← matches scope: landscape/**
└── shared/base.yaml ← no match; layering not applied
Migration from layering to layerings
If you’re using the deprecated single-scheme layering format, migrate as follows:
Before:
spec:
layering:
- name: zone
required: true
- name: customer
required: true
- name: environment
default: dev
After:
spec:
layerings:
- name: default
scope: "**" # Matches all deployment files
layers:
- name: zone
required: true
- name: customer
required: true
- name: environment
default: dev
Wrap your existing layering list in a single ScopedLayeringModel with scope: "**" (catch-all). The "**" scope matches all files, so all deployments use that scheme — equivalent to the old single-scheme behavior.
Mutual Exclusion
A configuration cannot have both spec.layering and spec.layerings defined. The system enforces this with a validation error:
# ✗ INVALID — both formats present
spec:
layering: [...]
layerings: [...]
Choose one format and remove the other.
Example
meta:
name: cloud_validation
spec:
providers:
- name: kamatera
additional_regions: false
regions: [eu-fr, us-ny]
resources:
- name: virtualmachine
category: compute
configuration:
cpu_cores: "^[1-9][0-9]?$" # 1-99
ram_mb: "^(512|1024|2048|4096)$"
topologies:
- type: docker-swarm
components:
- role: manager
is_control: true
min_count: 1
max_count: 7
- role: worker
min_count: 1
layerings:
# Multi-tenant deployments with region isolation
- name: regional-tenant
scope: zones/**
layers:
- name: zone
required: true
- name: customer
required: true
- name: environment
required: false
default: dev
# Ring-based deployments (canary → production)
- name: ring-promotion
scope: landscape/**
layers:
- name: landscape
required: true
- name: ring
required: true
Configuration Schema Fields
The configuration dict on a resource (and properties at the spec level) maps field names to validation rules. Each entry is either a shorthand regex string or a structured ConfigurationSchemaField:
resources:
- name: virtualmachine
configuration:
# Shorthand: just the pattern string (required=true, no description)
cpu_cores: "^[1-9][0-9]?$"
# Structured: pattern + optional flags
enable_backup:
pattern: "^(true|false)$"
required: false
description: "Whether automated backups are enabled"
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
Regex the field value must fully match |
|
|
|
Whether the field must be present in resource config |
|
|
|
Human-readable description of the field |
Boolean fields must use a pattern — there is no native
type: booleanin the schema. The configuration schema is regex-only; all values are validated as strings. Use"^(true|false)$"as the pattern and pass"true"or"false"as the value.# Schema (in configuration YAML) enable_backup: pattern: "^(true|false)$" required: false # Usage (in deployment/resource YAML) configuration: enable_backup: "true" # string, not a YAML boolean
Merge Behavior
Multiple configs merge: built-in → 00-.yaml → 10-.yaml → 99-*.yaml
Properties: Last wins (override)
Providers/Topologies: Additive (extend list)
Validation
Valid regex patterns in resource configuration
min_count ≤ max_count for topology components
Unique provider/topology names after merge
Defined regions/resources when additional_* = false
Path Convention Policy
Declare directory structure conventions in spec.paths. A path_convention policy type
enforces these at validation time.
Declaring conventions
spec:
paths:
- name: zone-deployment-tree
scope: "zones/**" # files in this subtree are candidates
pattern: "zones/{zone}/customers/{tenant}/{env}" # {segment} captures one path part
validate:
zone: spec.zones[*].name # captured value must be a declared zone
tenant: "customers/{tenant}/tenant.yaml" # file at this path must exist
env: spec.environments[*].name # captured value must be a declared environment
- name: tenant-registry
scope: "customers/**"
pattern: "customers/{tenant}"
validate:
tenant: "customers/{tenant}/tenant.yaml"
- name: landscape-registry
scope: "landscape/**"
pattern: "landscape/{landscape}"
validate:
landscape: "landscape/{landscape}/landscape.yaml"
Enforcement policy
spec:
policies:
# Enforce all conventions
- name: enforce-paths
type: path_convention
phase: validate
enforcement: deny
# Enforce specific conventions only
- name: advisory-landscape
type: path_convention
phase: validate
enforcement: warn
configuration:
conventions: [landscape-registry]
Inline convention (deploy-repo mode)
For repositories without a configuration model, declare the convention inline on the policy:
policies:
- name: deploy-layout
type: path_convention
phase: validate
enforcement: deny
configuration:
scope: "deploy/**"
pattern: "deploy/{landscape}/{ring}"
validate:
landscape: "deploy/{landscape}/landscape.yaml"
Validation rule types
Rule syntax |
Meaning |
|---|---|
|
Captured value must appear in a field of the loaded ConfigurationModel |
|
File at this path (relative to workspace root) must exist on disk |
spec.* rules require --deep validation (configuration service must be available). File
existence rules work in both surface and deep mode.
Files that match the scope but not the pattern (e.g., shallower depth) are skipped — not a violation. Files that match no scope are not checked by that convention.
Checkov IaC Security Policy
Run Checkov against Terraform build artifacts during the build phase.
Requires Checkov to be installed (pip install checkov). Gracefully skipped when not available.
Declaring the integration
spec:
integrations:
- name: checkov
type: checkov
capabilities: [iac_security]
required: false
validation:
command: checkov --version
min_version: "2.0.0"
Enabling the policy
spec:
policies:
- name: terraform_security_baseline
type: checkov
phase: build
enforcement: deny
description: "Block builds with HIGH or CRITICAL Checkov findings"
configuration:
framework: terraform # default: terraform
severity_gate: high # critical|high|medium|low (default: high)
skip_checks: # CKV IDs to suppress (false positives, accepted risks)
- CKV_AWS_1
- CKV_AWS_20
include_checks: [] # if set, run ONLY these checks (empty = run all)
custom_checks_dir: ".strata/checkov/custom/" # optional: custom rule directory
timeout: 120 # subprocess timeout in seconds (default: 120)
How it works
After Terraform artifacts are generated by
strata build run, the policy engine finds theterraform/subdirectory underbuild_path(falling back tobuild_pathitself if no subdirectory exists).Invokes:
checkov --directory <terraform_dir> --framework terraform --output json --compactParses Checkov JSON output into
CheckovFindingrecords with severity, resource, and file path.Applies
severity_gate— findings at or above the gate level become policy violations.
Severity gate
|
Fails on |
|---|---|
|
CRITICAL only |
|
HIGH, CRITICAL |
|
MEDIUM, HIGH, CRITICAL |
|
LOW, MEDIUM, HIGH, CRITICAL |
Graceful degradation
Checkov not installed → policy skips (passes), warning logged
No
.tffiles found in build path → policy skipsCheckov subprocess fails → policy skips (non-fatal, never blocks build)
OPA Policy
Evaluate Open Policy Agent Rego rules against the
deployment context. Supports two modes: HTTP REST to a running OPA server (fast), or
opa eval CLI as a stateless fallback (no server required).
strata does not manage the OPA server lifecycle — that is the operator’s responsibility.
Installation
brew install opa # macOS
# or: https://www.openpolicyagent.org/docs/latest/#1-download-opa
Declaring the policy
spec:
policies:
- name: zone_enforcement
type: opa
phase: build
enforcement: deny
configuration:
rule: "data.strata.zones.deny" # OPA rule path to evaluate
policy_dir: ".strata/policies/" # .rego files directory (CLI mode)
endpoint: "http://localhost:8181" # OPA server URL (HTTP mode, optional)
timeout: 30
Writing OPA rules
Rules must return a set of violation strings named deny:
package strata.zones
deny contains msg if {
resource := input.platform.spec.resources[_]
not resource.properties.region in input.configuration.spec.allowed_regions
msg := sprintf("Resource '%s' in disallowed region '%s'",
[resource.meta.name, resource.properties.region])
}
OPA input document
strata sends a JSON document containing available context:
{
"phase": "build",
"platform": { ... }, // platform artifact (if available)
"configuration": { ... }, // configuration model (if available)
"deployment": { ... }, // deployment model (if available)
"plan_data": { ... }, // terraform plan JSON (if available)
"work_path": "/workspace",
"build_path": "/workspace/.strata/build"
}
Mode selection
|
Behavior |
|---|---|
Set and server reachable |
HTTP mode: |
Set but server unreachable |
Falls back to CLI mode |
Not set |
CLI mode: |
Not set and |
Policy skips (passes), warning logged |
Graceful degradation
OPA not installed and no server configured → policy skips (passes), warning logged
policy_dirnot found → policy skipsServer unreachable → falls back to CLI mode
Rule returns empty set or false → pass (no violations)
Secret Stores
Secrets in spec.secrets are resolved at build time by the strata build command. The store field controls which backend is used. The following stores are supported:
|
Resolver type |
Integration required? |
Notes |
|---|---|---|---|
|
Built-in |
No |
Literal value — avoid for real secrets |
|
Built-in |
No |
Reads a named env var from the local shell |
|
Built-in |
No |
Reads a GitHub Actions injected env var |
|
Integration |
Yes |
Azure Key Vault secret |
|
Integration |
Yes |
Bitwarden Secrets Manager item |
|
Integration |
Yes |
HashiCorp Vault / OpenBao secret |
|
Integration |
Yes |
Infisical secret |
github — GitHub Actions secrets
GitHub Actions secrets are injected into the runner’s environment as plain environment variables before each job step executes. The github store type reads from those environment variables.
spec:
secrets:
- key: db_password
store: github
value: DB_PASSWORD # GitHub secret name (env var injected by Actions)
description: "Database password from GitHub Secrets"
How it works: The value field is the environment variable name. GitHub Actions maps your repository secret DB_PASSWORD to the env var DB_PASSWORD when you reference it in the workflow’s env: block. The resolver calls os.environ.get("DB_PASSWORD") at build time.
Uppercase normalization: GitHub uppercases all secret names at storage time. The resolver automatically uppercases value before the lookup — so value: db_password and value: DB_PASSWORD are equivalent.
Local development: Running strata build locally with store: github secrets emits a warning because GITHUB_ACTIONS is not set. Set the env vars manually for local testing:
$env:DB_PASSWORD = "local-test-value"
version field: Not supported for store: github. GitHub Secrets are not versioned. Specifying version raises a validation error.
Production policy: If your configuration defines security.allowed_secret_stores, add "github" explicitly:
spec:
security:
allowed_secret_stores:
- github
- azure-keyvault
Deployment Artifacts
The spec.deployment section controls where deployment artifacts are persisted.
Manifest Storage
Controls where the deployment manifest (full audit record) is written after every deploy run. When absent, manifests are not written.
spec:
deployment:
manifest:
type: local # local | gitops
path: ".strata/deployments" # base path; appended: /{deployment}/{version}/{timestamp}.json
For GitOps storage:
spec:
deployment:
manifest:
type: gitops
repository: state-repo # must match a name in spec.remotes
branch: manifests
path: deployments
tag: true # create git tag {deployment}/{version} after write
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
|
|
|
|
Base path for manifest files |
|
|
|
Repository name (required when |
|
|
|
Target branch (required when |
|
|
|
Create a git tag after writing (gitops only) |
Terraform Output Artifact Storage
Controls whether Terraform output values are written to a durable artifact file after a successful deploy. When absent, outputs are available to downstream stages within the same run but not persisted.
spec:
deployment:
outputs:
enabled: true # set to false to disable
path: ".strata/outputs" # base path; appended: /{deployment}/{version}/{stage}.json
sensitive: redact # redact | omit
Written to: {work_path}/{path}/{deployment_name}/{version}/{stage_name}.json
Artifact structure:
{
"deployment": "prod_deploy",
"version": "2.0.0",
"stage": "network",
"written_at": "2026-06-15T10:00:00Z",
"outputs": {
"server_ip": "10.0.0.5",
"db_password": "(sensitive)"
}
}
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
Write the artifact. Set |
|
|
|
Base path for output artifacts |
|
|
|
|
Sensitive output handling applies to any Terraform output declared
sensitive = true. Non-sensitive outputs are always stored as-is. Write failures are non-fatal and logged as warnings.
Integrations
spec.integrations declares external service integrations used by the platform — primarily
SIEM sinks for audit event forwarding. Each integration is referenced by name from
spec.audit.sinks[].integration in environment YAML.
spec:
integrations:
- name: splunk_hec # Referenced by audit sinks
type: splunk
enabled: true
endpoints:
address: "https://splunk.internal:8088"
authentication:
method: api_key
api_key:
api_key: "${SPLUNK_HEC_TOKEN}" # Resolved from env at deploy time
properties:
index: strata
source: strata-deploy
sourcetype: _json
channel: "guid-for-indexer-ack" # optional HEC channel
Supported SIEM types
splunk — Splunk HTTP Event Collector (HEC)
Forwards events via the HEC endpoint (POST /services/collector).
Property |
Default |
Description |
|---|---|---|
|
|
Splunk index |
|
|
Event source label |
|
|
Sourcetype (use |
|
— |
HEC channel GUID (enables indexer acknowledgement) |
- name: splunk_hec
type: splunk
endpoints:
address: "https://splunk.corp.example:8088"
authentication:
method: api_key
api_key:
api_key: "${SPLUNK_HEC_TOKEN}"
properties:
index: platform
sourcetype: _json
elk — ELK / Logstash
Forwards events via TCP (Logstash JSON codec) or HTTP (Elasticsearch Bulk API).
Property |
Default |
Description |
|---|---|---|
|
|
|
|
|
Elasticsearch index prefix |
# TCP (Logstash JSON input)
- name: elk_logstash
type: elk
endpoints:
address: "logstash.internal:5044"
properties:
protocol: tcp
# HTTP (Elasticsearch Bulk API)
- name: elk_es
type: elk
endpoints:
address: "https://es.internal:9200"
authentication:
method: basic
basic:
username: strata
password: "${ES_PASSWORD}"
properties:
protocol: http
index_pattern: strata-prod
otel — OpenTelemetry (OTLP/HTTP)
Forwards events as OTLP Log Records to any OpenTelemetry-compatible backend (Grafana, Datadog, Splunk OTel Collector, etc.).
Property |
Default |
Description |
|---|---|---|
|
|
|
|
|
Extra OTel resource attributes map |
- name: otel_collector
type: otel
endpoints:
address: "https://otel.internal:4318"
properties:
resource_attributes:
service.name: strata
deployment.environment: production
sentinel — Azure Sentinel (DCR Logs Ingestion API)
Forwards events via the Azure Monitor Logs Ingestion API (DCR-based).
Uses DefaultAzureCredential — managed identity, service principal, or Azure CLI.
Property |
Required |
Description |
|---|---|---|
|
Yes |
Immutable DCR ID (e.g. |
|
Yes |
Custom stream name (e.g. |
- name: azure_sentinel
type: sentinel
endpoints:
address: "https://my-dce.westeurope-1.ingest.monitor.azure.com"
properties:
data_collection_rule_id: dcr-0abc1234567890def
stream_name: Custom-StrataDeployAudit_CL
Integration field reference
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique name referenced by |
|
enum |
Yes |
|
|
bool |
No |
Defaults to |
|
string |
Yes |
Target URL or |
|
object |
No |
|
|
map |
No |
Type-specific configuration (see per-type tables) |
Notes
Built-in default in
src/STRATA_platform/data/configuration.yamlalways loads firstUse numeric prefixes (00-, 10-, 20-) to control merge order
Set
additional_regions: falseto restrict regionsRegex patterns validate resource configurations
See workspace.md, environment.md, deployment.md for usage