Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/module/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ conventions, see the reference files in `.agents/references/`.
5. **Write the BBD readme** → `.agents/references/bbd-readme.md`

6. **Write `meshstack_integration.tf`** — follow AGENTS.md § `meshstack_integration.tf` Conventions
- Always add `lifecycle { ignore_changes = [ availability ] }` to every `meshstack_platform` resource (see AGENTS.md § `meshstack_platform` Lifecycle)

7. **Validate**:
```sh
Expand Down
165 changes: 165 additions & 0 deletions .agents/skills/stackit-backplane.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
description: STACKIT backplane identity conventions for meshstack-hub modules under modules/stackit/. Covers service account + key pattern, required variables/outputs, provider configuration, meshstack_integration.tf wiring, and the STACKIT backplane checklist.
---

# STACKIT Backplane Identity Conventions

STACKIT backplanes **must** use a **service account with a long-lived key** as the automation
principal for building block execution. The key JSON is provisioned in the backplane and injected
as a sensitive static input into the building block definition.

## Rationale

- **Self-contained credentials**: The service account and its key are provisioned once in the
backplane Terraform module. The key JSON is a single credential that bundles the service account
email, key ID, and private key — no extra wiring needed.
- **Least-privilege**: Each building block gets its own service account with exactly the roles it
needs (project-scoped or organization-scoped).
- **No provider configuration in backplane**: The backplane module does not include a `provider.tf`.
Authentication for the backplane itself is configured by the caller (e.g. the platform team running
`tofu apply` or the integration runtime).
- **Sensitive by default**: The `service_account_key_json` output is marked `sensitive = true`.
meshStack's STATIC input wiring uses the `sensitive.argument.secret_value` field to ensure the
key is stored and transmitted as a secret.

<!-- scorecard-checks: stackit_uses_service_account_key -->
## Implementation Pattern

```hcl
# backplane/main.tf — service account + key + role assignments

resource "stackit_service_account" "backplane" {
project_id = var.project_id
name = "mesh-<service-name>"
}

resource "stackit_service_account_key" "backplane" {
project_id = var.project_id
service_account_email = stackit_service_account.backplane.email
}

# Project-scoped role assignment (use this for project-level resources):
resource "stackit_authorization_project_role_assignment" "this" {
resource_id = var.project_id
role = "<required-role>"
subject = stackit_service_account.backplane.email
}

# Organization-scoped role assignment (use this for org-level resources):
resource "stackit_authorization_organization_role_assignment" "this" {
resource_id = var.organization_id
role = "<required-role>"
subject = stackit_service_account.backplane.email
}
```

<!-- scorecard-checks: stackit_service_account_key_output -->
## Backplane Outputs (STACKIT)

Every STACKIT backplane must output the service account key JSON:

```hcl
output "service_account_key_json" {
value = stackit_service_account_key.backplane.json
description = "Service account key JSON for authenticating the STACKIT provider in the buildingblock."
sensitive = true
}
```

Additional outputs (e.g. `project_id`, resource IDs) can be added as needed.

## Backplane Variables (STACKIT)

All STACKIT backplanes require at minimum:

```hcl
variable "project_id" {
type = string
nullable = false
description = "STACKIT project ID where the service account will be created."
}
```

Backplanes that manage organization-level resources also require:

```hcl
variable "organization_id" {
type = string
nullable = false
description = "STACKIT organization ID where the service account will be granted permissions."
}
```

<!-- scorecard-checks: stackit_provider_uses_key -->
## Buildingblock Provider Configuration

The buildingblock `provider.tf` must use `service_account_key` for authentication.
Do **not** use `service_account_email` alone — it does not authenticate.

```hcl
# buildingblock/provider.tf
provider "stackit" {
service_account_key = var.service_account_key_json
# Add any extra provider flags required by the resources (e.g. enable_beta_resources, experiments):
# enable_beta_resources = true
# experiments = ["some-feature"]
}
```

## Buildingblock Variable

```hcl
variable "service_account_key_json" {
type = string
nullable = false
sensitive = true
description = "Service account key JSON for authenticating the STACKIT provider."
}
```

The key JSON bundles the service account email — do **not** add a separate `service_account_email`
variable when `service_account_key_json` is present.

## `meshstack_integration.tf` Wiring (STACKIT)

Pass the key from the backplane as a **STATIC sensitive** input:

```hcl
module "backplane" {
source = "github.com/meshcloud/meshstack-hub//modules/stackit/<service>/backplane?ref=${var.hub.git_ref}"

project_id = var.stackit_project_id
# organization_id = var.stackit_organization_id # if org-scoped roles are needed
}

# Inside meshstack_backplane_definition version_spec.inputs:
service_account_key_json = {
display_name = "Service Account Key JSON"
description = "Service account key JSON for authenticating the STACKIT provider."
type = "STRING"
assignment_type = "STATIC"
sensitive = {
argument = {
secret_value = module.backplane.service_account_key_json
}
}
}
```

## What to Avoid

- ❌ `service_account_email` alone in the provider — missing authentication credential
- ❌ Long-lived `STACKIT_SERVICE_ACCOUNT_TOKEN` injected via env var — not reproducible across runs
- ❌ Hardcoded key values in integration files
- ❌ Non-sensitive output for `service_account_key_json` — always mark it `sensitive = true`

## Checklist for STACKIT Backplanes

- [ ] `stackit_service_account` resource present
- [ ] `stackit_service_account_key` resource present (same project as the service account)
- [ ] Required role assignments present (`stackit_authorization_project_role_assignment` or `stackit_authorization_organization_role_assignment`)
- [ ] `service_account_key_json` output marked `sensitive = true`
- [ ] Buildingblock `provider.tf` uses `service_account_key = var.service_account_key_json`
- [ ] Buildingblock `variables.tf` has `service_account_key_json` (sensitive, nullable = false)
- [ ] No separate `service_account_email` variable in buildingblock when key is present
- [ ] `meshstack_integration.tf` wires key via `sensitive.argument.secret_value`
21 changes: 21 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ resource "meshstack_building_block_definition" "this" {
}
```

<!-- scorecard-checks: platform_lifecycle_ignore_availability -->
### `meshstack_platform` Lifecycle

Every `meshstack_platform` resource must include a `lifecycle` block that ignores changes to `availability`:

```hcl
resource "meshstack_platform" "this" {
# ...
lifecycle {
ignore_changes = [spec.availability]
}
}
```

The `availability` field controls publication state and access restrictions. meshStack operators modify this after initial deployment (e.g. to publish a platform to users) — Terraform must not reset it on subsequent applies.

---

<!-- scorecard-checks: provider_pinned -->
Expand Down Expand Up @@ -201,6 +217,10 @@ See [.agents/skills/aws-backplane.md](.agents/skills/aws-backplane.md) for the f

See [.agents/skills/azure-backplane.md](.agents/skills/azure-backplane.md) for the full Azure backplane identity conventions, including UAMI patterns, WIF wiring, required variables/outputs, and the Azure backplane checklist.

## STACKIT Backplane Identity Conventions

See [.agents/skills/stackit-backplane.md](.agents/skills/stackit-backplane.md) for the full STACKIT backplane identity conventions, including the service account + key pattern, required variables/outputs, provider configuration, and the STACKIT backplane checklist.

---

<!-- scorecard-checks: readme_frontmatter, logo, app_team_readme, bbd_readme, bbd_readme_no_leading_heading, bbd_readme_shared_responsibility, no_documentation_md_output -->
Expand Down Expand Up @@ -336,5 +356,6 @@ See [.agents/skills/write-e2e-test/SKILL.md](.agents/skills/write-e2e-test/SKILL
- [ ] `meshstack` and `hub` variables are at the end of the variable section
- [ ] `logo.png` included in `buildingblock/`
- [ ] No `documentation_md` output in `backplane/` — use BBD `readme` field and `backplane/README.md` instead
- [ ] `meshstack_platform` resources include `lifecycle { ignore_changes = [spec.availability] }`
- [ ] No trailing whitespace
- [ ] **Azure modules**: also follow the [Azure Backplane Checklist](.agents/skills/azure-backplane.md#checklist-for-azure-backplanes)
4 changes: 4 additions & 0 deletions modules/aks/meshstack_integration.tf
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ resource "meshstack_platform" "aks" {
}
}
}

lifecycle {
ignore_changes = [spec.availability]
}
}

resource "meshstack_landingzone" "aks_default" {
Expand Down
4 changes: 4 additions & 0 deletions modules/azure/meshstack_integration.tf
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ resource "meshstack_platform" "azure" {
}
}
}

lifecycle {
ignore_changes = [spec.availability]
}
}

resource "meshstack_landingzone" "azure_default" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ resource "meshstack_platform" "this" {

contributing_workspaces = []
}

lifecycle {
ignore_changes = [spec.availability]
}
}

# dev meshLandingZone
Expand Down
4 changes: 4 additions & 0 deletions modules/stackit/meshstack_integration.tf
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ resource "meshstack_platform" "stackit" {
owned_by_workspace = var.meshstack.owning_workspace_identifier
}

lifecycle {
ignore_changes = [spec.availability]
}

spec = {
display_name = "STACKIT Project"
description = "Create a STACKIT project with role-based access control."
Expand Down
35 changes: 35 additions & 0 deletions modules/stackit/spoke-network/backplane/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
resource "stackit_service_account" "backplane" {
project_id = var.project_id
name = "mesh-spoke-network"
}

resource "stackit_service_account_federated_identity_provider" "backplane" {
for_each = { for i, s in var.workload_identity_federation.subjects : tostring(i) => s }

project_id = var.project_id
service_account_email = stackit_service_account.backplane.email
name = "meshstack-${each.key}"
issuer = var.workload_identity_federation.issuer

assertions = [
{
item = "aud"
operator = "equals"
value = "api://AzureADTokenExchange"
},
{
item = "sub"
operator = "equals"
value = each.value
}
]
}

# network.admin at org scope allows managing routing tables in the network area
# and routed networks in tenant projects. Least-privilege alternative: if STACKIT
# introduces a narrower "network.editor" role, prefer that.
resource "stackit_authorization_organization_role_assignment" "network_admin" {
resource_id = var.organization_id
role = "iaas.network.admin"
subject = stackit_service_account.backplane.email
}
4 changes: 4 additions & 0 deletions modules/stackit/spoke-network/backplane/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "service_account_email" {
value = stackit_service_account.backplane.email
description = "Email of the STACKIT service account used by the buildingblock provider via WIF."
}
20 changes: 20 additions & 0 deletions modules/stackit/spoke-network/backplane/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "project_id" {
type = string
nullable = false
description = "STACKIT project ID where the service account will be created."
}

variable "organization_id" {
type = string
nullable = false
description = "STACKIT organization ID where the service account will be granted network management permissions."
}

variable "workload_identity_federation" {
type = object({
issuer = string
subjects = list(string)
})
nullable = false
description = "WIF issuer URL and subject list for the meshStack building block identity provider."
}
10 changes: 10 additions & 0 deletions modules/stackit/spoke-network/backplane/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.12.0"

required_providers {
stackit = {
source = "stackitcloud/stackit"
version = "~> 0.98.0"
}
}
}
31 changes: 31 additions & 0 deletions modules/stackit/spoke-network/buildingblock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: STACKIT Spoke Network
supportedPlatforms:
- stackit
description: Provisions a routed network in a STACKIT project and attaches it to the platform hub network area.
---

# STACKIT Spoke Network — Building Block

Provisions a routed network in a STACKIT project and attaches it to the platform hub network area. Optionally creates a custom routing table with a default route via a firewall next-hop.

## Inputs

| Name | Type | Description |
|------|------|-------------|
| `project_id` | string | Tenant STACKIT project ID (from PLATFORM_TENANT_ID) |
| `organization_id` | string | STACKIT organization ID |
| `network_area_id` | string | Hub network area ID |
| `service_account_key_json` | string (sensitive) | Backplane SA credentials |
| `network_prefix_length` | number | Subnet prefix length (24–28, default 25) |
| `firewall_next_hop_ip` | string | Next-hop IP for default route; null = no routing table |
| `ipv4_nameservers` | string | JSON-encoded nameserver list; null = STACKIT defaults |

## Outputs

| Name | Description |
|------|-------------|
| `network_id` | Spoke network ID |
| `network_cidr` | Allocated CIDR block |
| `routing_table_id` | Custom routing table ID (null if no firewall) |
| `summary` | Markdown summary rendered in meshStack |
10 changes: 10 additions & 0 deletions modules/stackit/spoke-network/buildingblock/SUMMARY.md.tftpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Spoke Network

| Property | Value |
|----------|-------|
| **Network ID** | `${network_id}` |
| **Network CIDR** | `${network_cidr}` |
| **Hub Network Area** | `${network_area_id}` |
%{~ if has_routing_table}
| **Routing Table** | `${routing_table_id}` |
%{~ endif}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading