From c6491bdc04aa77cebe8dbb84161d1f8d6fc40b64 Mon Sep 17 00:00:00 2001 From: David Fernandez Date: Tue, 9 Jun 2026 21:05:17 -0300 Subject: [PATCH] feat(identity-access-control): add cloud-agnostic provider config module Add nullplatform/identity-access-control, which configures an identity & access control provider in nullplatform via nullplatform_provider_config. The module is cloud-agnostic: the provider `type` and `attributes` are exposed as variables so new clouds can be onboarded without changing the module. `type` defaults to the AWS IAM provider (aws-iam-configuration), and `attributes` carries the provider-specific config (JSON-encoded to match the selected specification's schema). For AWS, this is the platform-side counterpart to infrastructure/aws/iam/agent, which grants the agent sts:AssumeRole over the published role ARNs. - type variable (default aws-iam-configuration) + generic attributes (any) - supports dimensions - no ignore_changes on attributes: Terraform stays the source of truth - includes README (AWS + new-cloud usage) and tests (3 passing) Also ignore the local np-api-skill.token credential. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 +- .../.terraform.lock.hcl | 25 +++++ .../identity-access-control/README.md | 104 ++++++++++++++++++ nullplatform/identity-access-control/main.tf | 6 + .../identity-access-control/outputs.tf | 9 ++ .../identity-access-control/providers.tf | 8 ++ .../tests/identity_access_control.tftest.hcl | 59 ++++++++++ .../identity-access-control/variables.tf | 21 ++++ 8 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 nullplatform/identity-access-control/.terraform.lock.hcl create mode 100644 nullplatform/identity-access-control/README.md create mode 100644 nullplatform/identity-access-control/main.tf create mode 100644 nullplatform/identity-access-control/outputs.tf create mode 100644 nullplatform/identity-access-control/providers.tf create mode 100644 nullplatform/identity-access-control/tests/identity_access_control.tftest.hcl create mode 100644 nullplatform/identity-access-control/variables.tf diff --git a/.gitignore b/.gitignore index 5bb94ce6..33454bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -171,4 +171,6 @@ terraform.tfstate.backup .claude/ # Local examples (for testing only, not part of the module distribution) -nullplatform/scope_definition/examples/ \ No newline at end of file +nullplatform/scope_definition/examples/ +# Local API skill token (credential — never commit) +np-api-skill.token diff --git a/nullplatform/identity-access-control/.terraform.lock.hcl b/nullplatform/identity-access-control/.terraform.lock.hcl new file mode 100644 index 00000000..8358a393 --- /dev/null +++ b/nullplatform/identity-access-control/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/nullplatform/nullplatform" { + version = "0.0.92" + constraints = ">= 0.0.86" + hashes = [ + "h1:anULseuczNv7bKlwfIP4cFkd2o9FGwJGUm+g+BnkuaE=", + "zh:09ceed1197d54ea5ac0338a2c542646382a68df72b0ad98ff13389e1c50e00a2", + "zh:132329b0a0304663e7a5826a3e667bb4337e89fda0c5e66bd43dcadf608b3b27", + "zh:3a055e9eb7e8d1ed7a06e95a227ab59c387eec3b043fafeadcbe8c4bfd8db41a", + "zh:3b3182b4052e50a3357cad70c8a8eceb6cb95ee34101eef3fc931b9d7b8c771d", + "zh:3db68b8c66913e30eb2d566ba3d1a433bed923d2f7da629b73591921cd4547c3", + "zh:56a40f23e80ed1226d1d72ad2e6514d3e7ec031b64c70da8ee1a24fd2e30c068", + "zh:6278739e6710e91a0b1c2720bc4dc2e50b51042bd4f848ffa9781e3f4f6cc51c", + "zh:73d452f3b9a34a1f360313c55c660f8b367355588d710dea457095b600029e55", + "zh:95b649f230267988c1d85f6d497b63a4ed79317f6f79af385da180702f8e196d", + "zh:a54b3ac2ebc339e2464c1f65bd3feb471adfd30bc6ac4cbabe40107a29e98a3d", + "zh:c00790341e04dd0fdeffb58eb19bec0536d56e5fc878eb8f804cf1414a685f4d", + "zh:cd756a988f2ed19da02d55179225d449296fbc8df781f2ba2f5ea5b143df4578", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:fc2bc683df56dd1087114d43ad6864dd3f3dd8a34ca2c81626b1f08600a88b94", + "zh:fffb4fb18254dc734ff5610c22caa9e9f45627bfb060a595a296cde180b28be6", + ] +} diff --git a/nullplatform/identity-access-control/README.md b/nullplatform/identity-access-control/README.md new file mode 100644 index 00000000..cc39adac --- /dev/null +++ b/nullplatform/identity-access-control/README.md @@ -0,0 +1,104 @@ +# Module: identity-access-control + +## Description + +Configures an identity & access control provider in nullplatform via a `nullplatform_provider_config` resource. The provider type defaults to the AWS IAM provider (`aws-iam-configuration`) but is exposed as a variable, so new clouds can be supported by passing their own `type` and `attributes`. + +## Architecture + +The module creates a single `nullplatform_provider_config` resource. The `type` input selects which provider specification to configure (default `aws-iam-configuration`), and the `attributes` input carries the provider-specific configuration, JSON-encoded to match that specification's schema. `dimensions` metadata is supported for environment- or region-specific configuration. The module is intentionally generic: it does not validate cloud-specific attribute shapes, leaving that to the caller, so adding a new cloud requires no changes here. Unlike provider configs that hold externally-rotated secrets, this module does not set `ignore_changes` on `attributes`, so Terraform remains the source of truth and changes are propagated on apply. + +For AWS, this module is the platform-side counterpart to `infrastructure/aws/iam/agent`: that module grants the agent `sts:AssumeRole` permission over the role ARNs, while this module publishes those ARNs to nullplatform under friendly selectors. + +## Features + +- Creates a nullplatform identity & access control provider configuration +- Cloud-agnostic: `type` and `attributes` are inputs, defaulting to AWS IAM +- For AWS IAM, maps friendly selectors to assumable IAM role ARNs for use in scope/service code +- Supports dimensions for environment- or region-specific configuration +- Keeps Terraform as the source of truth for the configuration (no attribute drift suppression) + +## Basic Usage (AWS IAM — default) + +```hcl +module "identity_access_control" { + source = "git::https://github.com/nullplatform/tofu-modules.git//nullplatform/identity-access-control?ref=v4.0.1" + + nrn = "your-nrn" + + attributes = { + iam_role_arns = { + arns = [ + { + selector = "billing" + arn = "arn:aws:iam::123456789012:role/billing-reader" + }, + { + selector = "analytics" + arn = "arn:aws:iam::123456789012:role/analytics-reader" + }, + ] + } + } +} +``` + +## Usage for a new cloud + +```hcl +module "identity_access_control" { + source = "git::https://github.com/nullplatform/tofu-modules.git//nullplatform/identity-access-control?ref=v4.0.1" + + nrn = "your-nrn" + type = "azure-iam-configuration" # slug of the provider specification + + attributes = { + # ... shape matching the azure-iam-configuration schema + } +} +``` + +## Using Outputs + +```hcl +# Reference outputs in other resources +resource "example_resource" "this" { + example_attribute = module.identity_access_control.id +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [nullplatform](#requirement\_nullplatform) | >= 0.0.86 | + +## Providers + +| Name | Version | +|------|---------| +| [nullplatform](#provider\_nullplatform) | >= 0.0.86 | + +## Resources + +| Name | Type | +|------|------| +| [nullplatform_provider_config.identity_access_control](https://registry.terraform.io/providers/nullplatform/nullplatform/latest/docs/resources/provider_config) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [nrn](#input\_nrn) | nullplatform Resource Name where the provider configuration applies | `string` | n/a | yes | +| [attributes](#input\_attributes) | Provider-specific configuration, matching the schema of the selected provider type | `any` | n/a | yes | +| [type](#input\_type) | Slug of the nullplatform provider specification to configure | `string` | `"aws-iam-configuration"` | no | +| [dimensions](#input\_dimensions) | Dimensions used to scope this provider configuration | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [id](#output\_id) | ID of the provider configuration | +| [nrn](#output\_nrn) | NRN the provider configuration is attached to | + diff --git a/nullplatform/identity-access-control/main.tf b/nullplatform/identity-access-control/main.tf new file mode 100644 index 00000000..c879d968 --- /dev/null +++ b/nullplatform/identity-access-control/main.tf @@ -0,0 +1,6 @@ +resource "nullplatform_provider_config" "identity_access_control" { + nrn = var.nrn + type = var.type + dimensions = var.dimensions + attributes = jsonencode(var.attributes) +} diff --git a/nullplatform/identity-access-control/outputs.tf b/nullplatform/identity-access-control/outputs.tf new file mode 100644 index 00000000..eadc8254 --- /dev/null +++ b/nullplatform/identity-access-control/outputs.tf @@ -0,0 +1,9 @@ +output "id" { + description = "ID of the provider configuration" + value = nullplatform_provider_config.identity_access_control.id +} + +output "nrn" { + description = "NRN the provider configuration is attached to" + value = nullplatform_provider_config.identity_access_control.nrn +} diff --git a/nullplatform/identity-access-control/providers.tf b/nullplatform/identity-access-control/providers.tf new file mode 100644 index 00000000..506a5a50 --- /dev/null +++ b/nullplatform/identity-access-control/providers.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + nullplatform = { + source = "nullplatform/nullplatform" + version = ">= 0.0.86" + } + } +} diff --git a/nullplatform/identity-access-control/tests/identity_access_control.tftest.hcl b/nullplatform/identity-access-control/tests/identity_access_control.tftest.hcl new file mode 100644 index 00000000..111a9e9b --- /dev/null +++ b/nullplatform/identity-access-control/tests/identity_access_control.tftest.hcl @@ -0,0 +1,59 @@ +mock_provider "nullplatform" {} + +variables { + nrn = "organization=myorg:account=myaccount" + attributes = { + iam_role_arns = { + arns = [ + { selector = "billing", arn = "arn:aws:iam::123456789012:role/billing-reader" }, + { selector = "analytics", arn = "arn:aws:iam::123456789012:role/analytics-reader" } + ] + } + } +} + +run "default_type_is_aws_iam_configuration" { + command = plan + + assert { + condition = nullplatform_provider_config.identity_access_control.type == "aws-iam-configuration" + error_message = "Provider config type should default to 'aws-iam-configuration'" + } + + assert { + condition = nullplatform_provider_config.identity_access_control.nrn == "organization=myorg:account=myaccount" + error_message = "NRN should match input" + } +} + +run "custom_type_for_new_cloud" { + command = plan + + variables { + type = "azure-iam-configuration" + } + + assert { + condition = nullplatform_provider_config.identity_access_control.type == "azure-iam-configuration" + error_message = "Provider config type should honor the type variable" + } +} + +run "attributes_are_json_encoded" { + command = plan + + assert { + condition = can(jsondecode(nullplatform_provider_config.identity_access_control.attributes)) + error_message = "attributes should be valid JSON" + } + + assert { + condition = length(jsondecode(nullplatform_provider_config.identity_access_control.attributes).iam_role_arns.arns) == 2 + error_message = "attributes should encode the provided structure verbatim" + } + + assert { + condition = strcontains(nullplatform_provider_config.identity_access_control.attributes, "arn:aws:iam::123456789012:role/billing-reader") + error_message = "attributes should contain the configured role ARN" + } +} diff --git a/nullplatform/identity-access-control/variables.tf b/nullplatform/identity-access-control/variables.tf new file mode 100644 index 00000000..3d1159f3 --- /dev/null +++ b/nullplatform/identity-access-control/variables.tf @@ -0,0 +1,21 @@ +variable "nrn" { + description = "nullplatform Resource Name where the identity & access control provider configuration applies" + type = string +} + +variable "type" { + description = "Slug of the nullplatform provider specification to configure (e.g. aws-iam-configuration). Set this when adding support for a new cloud." + type = string + default = "aws-iam-configuration" +} + +variable "attributes" { + description = "Provider-specific configuration, matching the schema of the selected provider type. Encoded to JSON for the provider config. For aws-iam-configuration: { iam_role_arns = { arns = [{ selector, arn }] } }." + type = any +} + +variable "dimensions" { + description = "Dimensions used to scope this provider configuration (e.g., environment, region)" + type = map(string) + default = {} +}