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 scheme

  • scope: 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

name

str

Layer name (e.g., zone, customer, ring, landscape)

required

bool

true

Whether the deployment must provide a value for this layer

default

str

null

Default value if no value is provided (only if required: false)

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:

  1. Scope matching: The deployment file’s relative path (from workspace root) is matched against all scheme scopes in order using glob pattern matching

  2. First match wins: The first scheme whose scope matches the file path is selected

  3. Value resolution: Layer values are extracted from the deployment’s properties field or use defaults

  4. Path 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

pattern

str

Regex the field value must fully match

required

bool

true

Whether the field must be present in resource config

description

str

null

Human-readable description of the field

Boolean fields must use a pattern — there is no native type: boolean in 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

spec.zones[*].name

Captured value must appear in a field of the loaded ConfigurationModel

customers/{tenant}/tenant.yaml

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

  1. After Terraform artifacts are generated by strata build run, the policy engine finds the terraform/ subdirectory under build_path (falling back to build_path itself if no subdirectory exists).

  2. Invokes: checkov --directory <terraform_dir> --framework terraform --output json --compact

  3. Parses Checkov JSON output into CheckovFinding records with severity, resource, and file path.

  4. Applies severity_gate — findings at or above the gate level become policy violations.

Severity gate

severity_gate

Fails on

critical

CRITICAL only

high (default)

HIGH, CRITICAL

medium

MEDIUM, HIGH, CRITICAL

low

LOW, MEDIUM, HIGH, CRITICAL

Graceful degradation

  • Checkov not installed → policy skips (passes), warning logged

  • No .tf files found in build path → policy skips

  • Checkov 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

endpoint / OPA_ENDPOINT

Behavior

Set and server reachable

HTTP mode: POST /v1/data/{rule}

Set but server unreachable

Falls back to CLI mode

Not set

CLI mode: opa eval --stdin-input

Not set and opa not installed

Policy skips (passes), warning logged

Graceful degradation

  • OPA not installed and no server configured → policy skips (passes), warning logged

  • policy_dir not found → policy skips

  • Server 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:

store value

Resolver type

Integration required?

Notes

constant

Built-in

No

Literal value — avoid for real secrets

environment

Built-in

No

Reads a named env var from the local shell

github

Built-in

No

Reads a GitHub Actions injected env var

azure-keyvault

Integration

Yes

Azure Key Vault secret

bitwarden

Integration

Yes

Bitwarden Secrets Manager item

vault

Integration

Yes

HashiCorp Vault / OpenBao secret

infisical

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

type

enum

local or gitops

path

str

.strata/deployments

Base path for manifest files

repository

str

null

Repository name (required when type=gitops)

branch

str

null

Target branch (required when type=gitops)

tag

bool

true

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

enabled

bool

true

Write the artifact. Set false to disable without removing config

path

str

.strata/outputs

Base path for output artifacts

sensitive

enum

redact

redact — keep key, replace value with "(sensitive)". omit — drop key entirely

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

index

main

Splunk index

source

strata

Event source label

sourcetype

_json

Sourcetype (use _json for structured data)

channel

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

protocol

tcp

tcp (Logstash) or http (Elasticsearch)

index_pattern

strata-audit

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

protocol

http

http (OTLP/HTTP JSON)

resource_attributes

{}

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

data_collection_rule_id

Yes

Immutable DCR ID (e.g. dcr-abc123)

stream_name

Yes

Custom stream name (e.g. Custom-DeployAudit_CL)

- 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

name

string

Yes

Unique name referenced by audit.sinks[].integration

type

enum

Yes

splunk, elk, otel, sentinel

enabled

bool

No

Defaults to true. Set false to disable without removing

endpoints.address

string

Yes

Target URL or host:port

authentication

object

No

method: api_key, basic, bearer

properties

map

No

Type-specific configuration (see per-type tables)


Notes

  • Built-in default in src/STRATA_platform/data/configuration.yaml always loads first

  • Use numeric prefixes (00-, 10-, 20-) to control merge order

  • Set additional_regions: false to restrict regions

  • Regex patterns validate resource configurations

  • See workspace.md, environment.md, deployment.md for usage