Skip to content
Open
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
42 changes: 42 additions & 0 deletions modules/aws/dynamodb-project-metadata/backplane/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Overview

The backplane provisions a central DynamoDB table in your AWS management/tooling account and an
IAM role that the meshStack building block runtime can assume via workload identity federation (OIDC).
No long-lived AWS credentials are required.

## What is provisioned

| Resource | Purpose |
|---|---|
| `aws_dynamodb_table` | Stores one item per meshStack project tenant (PK: workspace, SK: project) |
| `aws_iam_openid_connect_provider` | Trusts the meshStack OIDC issuer |
| `aws_iam_role` | Grants DynamoDB write access to the building block runtime |
| `aws_iam_role_policy` | Scoped to `PutItem`, `UpdateItem`, `DeleteItem` on the table only |

## Required permissions

The AWS principal running this backplane must be able to create IAM resources and DynamoDB tables.
Typically this is run with `AdministratorAccess` in a management/tooling account.

## Usage

```hcl
module "backplane" {
source = "github.com/meshcloud/meshstack-hub//modules/aws/dynamodb-project-metadata/backplane?ref=main"

workload_identity_federation = {
issuer = "<meshStack WIF issuer>"
audience = "<meshStack WIF audience>"
subjects = ["system:serviceaccount:<namespace>:workspace.<workspace>.buildingblockdefinition.<uuid>"]
}

table_name = "meshstack-project-metadata" # optional, defaults to this value
}
```

## Outputs

| Output | Description |
|---|---|
| `table_name` | Name of the created DynamoDB table (pass to `aws_dynamodb_table_name` BBD input) |
| `workload_identity_federation_role_arn` | IAM role ARN (pass to `AWS_ROLE_ARN` BBD input) |
80 changes: 80 additions & 0 deletions modules/aws/dynamodb-project-metadata/backplane/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
data "aws_caller_identity" "current" {}

resource "random_string" "suffix" {
length = 4
special = false
upper = false
}

resource "aws_dynamodb_table" "this" {
name = var.table_name
billing_mode = "PAY_PER_REQUEST"
hash_key = "workspace_identifier"
range_key = "project_identifier"

attribute {
name = "workspace_identifier"
type = "S"
}

attribute {
name = "project_identifier"
type = "S"
}
}

resource "aws_iam_openid_connect_provider" "this" {
url = var.workload_identity_federation.issuer
client_id_list = [var.workload_identity_federation.audience]
}

data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.this.arn]
}
actions = ["sts:AssumeRoleWithWebIdentity"]

condition {
test = "StringEquals"
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:aud"
values = [var.workload_identity_federation.audience]
}

condition {
test = "StringLike"
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:sub"
values = var.workload_identity_federation.subjects
}
}
}

locals {
role_name = "MeshStackDynamoDBWriter-${random_string.suffix.result}"
}

resource "aws_iam_role" "this" {
name = local.role_name
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

data "aws_iam_policy_document" "dynamodb_write" {
statement {
effect = "Allow"
actions = [
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
]
resources = [aws_dynamodb_table.this.arn]
}
}

resource "aws_iam_role_policy" "dynamodb_write" {
name = "DynamoDBWritePolicy"
role = aws_iam_role.this.id
policy = data.aws_iam_policy_document.dynamodb_write.json
}
25 changes: 25 additions & 0 deletions modules/aws/dynamodb-project-metadata/backplane/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
output "table_name" {
description = "Name of the DynamoDB table."
value = aws_dynamodb_table.this.name
}

output "table_arn" {
description = "ARN of the DynamoDB table."
value = aws_dynamodb_table.this.arn
}

output "table_region" {
description = "AWS region where the DynamoDB table was created."
value = aws_dynamodb_table.this.region
}

output "table_console_url" {
description = "AWS Console URL for the DynamoDB table."
value = "https://${aws_dynamodb_table.this.region}.console.aws.amazon.com/dynamodbv2/home?region=${aws_dynamodb_table.this.region}#table?name=${aws_dynamodb_table.this.name}"
}

output "workload_identity_federation_role_arn" {
description = "ARN of the IAM role assumed by the building block runtime via workload identity federation."
# Manually construct ARN to avoid dependency cycle on input workload_identity_federation (which contains the BBD UUID as subject)
value = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.role_name}"
}
17 changes: 17 additions & 0 deletions modules/aws/dynamodb-project-metadata/backplane/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
variable "workload_identity_federation" {
type = object({
issuer = string
audience = string
subjects = list(string)
})
description = <<-EOT
Workload identity federation configuration. Allows the meshStack building block runtime to assume
an IAM role via OIDC without long-lived credentials.
EOT
}

variable "table_name" {
type = string
default = "meshstack-project-metadata"
description = "Name of the DynamoDB table to create for storing meshStack project metadata."
}
14 changes: 14 additions & 0 deletions modules/aws/dynamodb-project-metadata/backplane/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.0.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.0.0"
}
}
}
70 changes: 70 additions & 0 deletions modules/aws/dynamodb-project-metadata/buildingblock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
name: AWS DynamoDB Project Metadata
supportedPlatforms:
- aws
description: Pushes meshStack project metadata (workspace, project, tags, team members) into a central AWS DynamoDB table per AWS organization.
---

Automatically syncs meshStack project metadata into a central AWS DynamoDB table whenever a building
block is assigned to a tenant. This eliminates the need for long-lived meshStack API credentials in
your AWS organization — metadata is pushed from meshStack using workload identity federation.

## When to use it

Use this building block when you need meshStack project data (tags, team members, workspace/project
identifiers) available inside your AWS organization without polling the meshStack API with static
credentials. Typical use cases:

- Feeding project ownership and cost-centre tags into AWS Cost Explorer via DynamoDB.

@JohannesRudolph JohannesRudolph Jun 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d: meshStack can natively tag AWS accounts when the tag definition enables tag replication

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats true, they want to have it in a dynamo DB because ServiceNow will read it from there

- Driving IAM permission boundaries or SCPs with project-level metadata.
- Providing a CMDB-like inventory of all meshStack tenants in your AWS org.

## Shared Responsibility

| Responsibility | Platform Team | Application Team |
|---|:---:|:---:|
| Deploy backplane (DynamoDB table + IAM role) | ✅ | ❌ |
| Assign building block to tenants | ✅ | ❌ |
| Consume DynamoDB data in tooling/pipelines | ✅ | ❌ |
| Keep meshStack project tags up to date | ❌ | ✅ |
| Manage team membership in meshStack | ❌ | ✅ |

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.12.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.0.0 |
| <a name="requirement_meshstack"></a> [meshstack](#requirement\_meshstack) | >= 0.22.0 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_dynamodb_table_item.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table_item) | resource |
| [meshstack_project.this](https://registry.terraform.io/providers/meshcloud/meshstack/latest/docs/data-sources/project) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_aws_dynamodb_table_name"></a> [aws\_dynamodb\_table\_name](#input\_aws\_dynamodb\_table\_name) | Name of the DynamoDB table to write project metadata to. | `string` | n/a | yes |
| <a name="input_aws_region"></a> [aws\_region](#input\_aws\_region) | AWS region where the DynamoDB table is located. | `string` | n/a | yes |
| <a name="input_platform_identifier"></a> [platform\_identifier](#input\_platform\_identifier) | meshStack platform identifier (typically the AWS account ID for AWS tenants). | `string` | n/a | yes |
| <a name="input_project_identifier"></a> [project\_identifier](#input\_project\_identifier) | meshStack project identifier. | `string` | n/a | yes |
| <a name="input_users"></a> [users](#input\_users) | Project team members with their roles, injected by meshStack. | <pre>list(object({<br/> meshIdentifier = string<br/> username = string<br/> firstName = string<br/> lastName = string<br/> email = string<br/> euid = string<br/> roles = list(string)<br/> }))</pre> | `[]` | no |
| <a name="input_workspace_identifier"></a> [workspace\_identifier](#input\_workspace\_identifier) | meshStack workspace identifier. | `string` | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_dynamodb_item_key"></a> [dynamodb\_item\_key](#output\_dynamodb\_item\_key) | Composite key of the DynamoDB item written for this project (workspace/project). |
| <a name="output_dynamodb_item_url"></a> [dynamodb\_item\_url](#output\_dynamodb\_item\_url) | AWS Console URL to view the specific item written for this project. |
| <a name="output_dynamodb_table_name"></a> [dynamodb\_table\_name](#output\_dynamodb\_table\_name) | Name of the DynamoDB table the metadata was written to. |
<!-- END_TF_DOCS -->
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions modules/aws/dynamodb-project-metadata/buildingblock/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
data "meshstack_project" "this" {
metadata = {
name = var.project_identifier
owned_by_workspace = var.workspace_identifier
}
}

locals {
tags_json = jsonencode(data.meshstack_project.this.spec.tags)
users_json = jsonencode([for u in var.users : { email = u.email, roles = u.roles }])
}

resource "aws_dynamodb_table_item" "this" {
table_name = var.aws_dynamodb_table_name
hash_key = "workspace_identifier"
range_key = "project_identifier"

item = jsonencode({
workspace_identifier = { S = var.workspace_identifier }
project_identifier = { S = var.project_identifier }
platform_identifier = { S = var.platform_identifier }
tags = { S = local.tags_json }
users = { S = local.users_json }
})
}
14 changes: 14 additions & 0 deletions modules/aws/dynamodb-project-metadata/buildingblock/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
output "dynamodb_item_key" {
description = "Composite key of the DynamoDB item written for this project (workspace/project)."
value = "${var.workspace_identifier}/${var.project_identifier}"
}

output "dynamodb_table_name" {
description = "Name of the DynamoDB table the metadata was written to."
value = var.aws_dynamodb_table_name
}

output "dynamodb_item_url" {
description = "AWS Console URL to view the specific item written for this project."
value = "https://${var.aws_region}.console.aws.amazon.com/dynamodbv2/home?region=${var.aws_region}#item?table=${var.aws_dynamodb_table_name}&itemMode=2&pk=${var.workspace_identifier}&sk=${var.project_identifier}&route=ROUTE_ITEM_EXPLORER"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
provider "aws" {
region = var.aws_region
}

provider "meshstack" {
# configured via environment variables (MESHSTACK_ENDPOINT, MESHSTACK_API_KEY)
}
38 changes: 38 additions & 0 deletions modules/aws/dynamodb-project-metadata/buildingblock/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
variable "workspace_identifier" {
type = string
description = "meshStack workspace identifier."
}

variable "project_identifier" {
type = string
description = "meshStack project identifier."
}

variable "platform_identifier" {
type = string
description = "meshStack platform identifier (typically the AWS account ID for AWS tenants)."
}

variable "users" {
type = list(object({
meshIdentifier = string
username = string
firstName = string
lastName = string
email = string
euid = string
roles = list(string)
}))
default = []
description = "Project team members with their roles, injected by meshStack."
}

variable "aws_region" {
type = string
description = "AWS region where the DynamoDB table is located."
}

variable "aws_dynamodb_table_name" {
type = string
description = "Name of the DynamoDB table to write project metadata to."
}
14 changes: 14 additions & 0 deletions modules/aws/dynamodb-project-metadata/buildingblock/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.12.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.0.0"
}
meshstack = {
source = "meshcloud/meshstack"
version = ">= 0.22.0"
}
}
}
Loading
Loading