From 98d7437f455bc9686b74623b97e211b13e7f913e Mon Sep 17 00:00:00 2001 From: David Fernandez Date: Thu, 9 Apr 2026 15:55:48 -0300 Subject: [PATCH 1/5] fix: remove provider --- infrastructure/azure/acr/provider.tf | 7 ------- infrastructure/azure/aks/provider.tf | 6 ------ infrastructure/azure/dns/provider.tf | 7 ------- infrastructure/azure/private_dns/provider.tf | 6 ------ infrastructure/azure/resource_group/provider.tf | 7 ------- infrastructure/azure/vnet/provider.tf | 7 ------- 6 files changed, 40 deletions(-) diff --git a/infrastructure/azure/acr/provider.tf b/infrastructure/azure/acr/provider.tf index ef7f06eb..a2489744 100644 --- a/infrastructure/azure/acr/provider.tf +++ b/infrastructure/azure/acr/provider.tf @@ -8,10 +8,3 @@ terraform { } } -provider "azurerm" { - features {} - resource_provider_registrations = "none" - use_cli = true - subscription_id = var.subscription_id -} - diff --git a/infrastructure/azure/aks/provider.tf b/infrastructure/azure/aks/provider.tf index 7ecaa77c..a2489744 100644 --- a/infrastructure/azure/aks/provider.tf +++ b/infrastructure/azure/aks/provider.tf @@ -8,9 +8,3 @@ terraform { } } -provider "azurerm" { - features {} - resource_provider_registrations = "none" - use_cli = true - subscription_id = var.subscription_id -} diff --git a/infrastructure/azure/dns/provider.tf b/infrastructure/azure/dns/provider.tf index ef7f06eb..a2489744 100644 --- a/infrastructure/azure/dns/provider.tf +++ b/infrastructure/azure/dns/provider.tf @@ -8,10 +8,3 @@ terraform { } } -provider "azurerm" { - features {} - resource_provider_registrations = "none" - use_cli = true - subscription_id = var.subscription_id -} - diff --git a/infrastructure/azure/private_dns/provider.tf b/infrastructure/azure/private_dns/provider.tf index 7ecaa77c..a2489744 100644 --- a/infrastructure/azure/private_dns/provider.tf +++ b/infrastructure/azure/private_dns/provider.tf @@ -8,9 +8,3 @@ terraform { } } -provider "azurerm" { - features {} - resource_provider_registrations = "none" - use_cli = true - subscription_id = var.subscription_id -} diff --git a/infrastructure/azure/resource_group/provider.tf b/infrastructure/azure/resource_group/provider.tf index ef7f06eb..a2489744 100644 --- a/infrastructure/azure/resource_group/provider.tf +++ b/infrastructure/azure/resource_group/provider.tf @@ -8,10 +8,3 @@ terraform { } } -provider "azurerm" { - features {} - resource_provider_registrations = "none" - use_cli = true - subscription_id = var.subscription_id -} - diff --git a/infrastructure/azure/vnet/provider.tf b/infrastructure/azure/vnet/provider.tf index ef7f06eb..a2489744 100644 --- a/infrastructure/azure/vnet/provider.tf +++ b/infrastructure/azure/vnet/provider.tf @@ -8,10 +8,3 @@ terraform { } } -provider "azurerm" { - features {} - resource_provider_registrations = "none" - use_cli = true - subscription_id = var.subscription_id -} - From 7cc85c443980252a3ab1759d3a920c3a4f914046 Mon Sep 17 00:00:00 2001 From: David Fernandez Date: Thu, 28 May 2026 16:25:43 -0300 Subject: [PATCH 2/5] feat(iam/agent): add assume_role_arns to allow agent to assume IAM roles Adds optional `assume_role_arns` variable (default []) to the agent IAM module. When non-empty, creates a scoped sts:AssumeRole policy and attaches it to the agent role alongside the existing Route53/ELB/EKS/AVP policies. Includes ARN format validation and tftest coverage for both the default (no policy created) and enabled (policy created and named correctly) cases. Co-Authored-By: Claude Sonnet 4.6 --- infrastructure/aws/iam/agent/main.tf | 22 +++++++++++ .../aws/iam/agent/tests/agent.tftest.hcl | 39 +++++++++++++++++++ infrastructure/aws/iam/agent/variables.tf | 11 ++++++ 3 files changed, 72 insertions(+) diff --git a/infrastructure/aws/iam/agent/main.tf b/infrastructure/aws/iam/agent/main.tf index 40b240cd..8e566db0 100644 --- a/infrastructure/aws/iam/agent/main.tf +++ b/infrastructure/aws/iam/agent/main.tf @@ -22,6 +22,9 @@ module "nullplatform_agent_role" { "nullplatform_elb_policy" = aws_iam_policy.nullplatform_elb_policy.arn, "nullplatform_avp_policy" = aws_iam_policy.nullplatform_avp_policy.arn }, + length(var.assume_role_arns) > 0 ? { + "nullplatform_assume_role_policy" = aws_iam_policy.nullplatform_assume_role_policy[0].arn + } : {}, var.additional_policies ) } @@ -150,6 +153,25 @@ resource "aws_iam_policy" "nullplatform_eks_policy" { }) } +################################################################################ +# STS AssumeRole IAM policy +################################################################################ + +resource "aws_iam_policy" "nullplatform_assume_role_policy" { + count = length(var.assume_role_arns) > 0 ? 1 : 0 + + name = "nullplatform_${var.cluster_name}_assume_role_policy" + description = "Policy allowing the agent to assume specific IAM roles" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Resource = var.assume_role_arns + }] + }) +} + ################################################################################ # AVP policy ################################################################################ diff --git a/infrastructure/aws/iam/agent/tests/agent.tftest.hcl b/infrastructure/aws/iam/agent/tests/agent.tftest.hcl index fe875865..8056f426 100644 --- a/infrastructure/aws/iam/agent/tests/agent.tftest.hcl +++ b/infrastructure/aws/iam/agent/tests/agent.tftest.hcl @@ -102,3 +102,42 @@ run "all_policies_valid_json" { error_message = "AVP policy should be valid JSON" } } + +run "assume_role_policy_not_created_by_default" { + command = plan + + assert { + condition = length(aws_iam_policy.nullplatform_assume_role_policy) == 0 + error_message = "assume_role policy should not be created when assume_role_arns is empty" + } +} + +run "assume_role_policy_created_when_arns_provided" { + command = plan + + variables { + assume_role_arns = ["arn:aws:iam::123456789012:role/some-role"] + } + + override_resource { + target = aws_iam_policy.nullplatform_assume_role_policy + values = { + arn = "arn:aws:iam::123456789012:policy/nullplatform_test-cluster_assume_role_policy" + } + } + + assert { + condition = length(aws_iam_policy.nullplatform_assume_role_policy) == 1 + error_message = "assume_role policy should be created when assume_role_arns is non-empty" + } + + assert { + condition = aws_iam_policy.nullplatform_assume_role_policy[0].name == "nullplatform_test-cluster_assume_role_policy" + error_message = "assume_role policy name should follow naming convention" + } + + assert { + condition = can(jsondecode(aws_iam_policy.nullplatform_assume_role_policy[0].policy)) + error_message = "assume_role policy should be valid JSON" + } +} diff --git a/infrastructure/aws/iam/agent/variables.tf b/infrastructure/aws/iam/agent/variables.tf index 7138eff1..dec4c7e2 100644 --- a/infrastructure/aws/iam/agent/variables.tf +++ b/infrastructure/aws/iam/agent/variables.tf @@ -18,3 +18,14 @@ variable "additional_policies" { type = map(string) default = {} } + +variable "assume_role_arns" { + description = "List of IAM role ARNs the agent is allowed to assume via sts:AssumeRole" + type = list(string) + default = [] + + validation { + condition = alltrue([for arn in var.assume_role_arns : can(regex("^arn:aws:iam::[0-9]{12}:role/.+", arn))]) + error_message = "Each ARN must match arn:aws:iam:::role/" + } +} From 08b087a5b17b4319ff145f19cd7408b60f32dea1 Mon Sep 17 00:00:00 2001 From: David Fernandez Date: Thu, 28 May 2026 16:29:13 -0300 Subject: [PATCH 3/5] chore(iam/agent): add .terraform.lock.hcl Co-Authored-By: Claude Sonnet 4.6 --- .../aws/iam/agent/.terraform.lock.hcl | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 infrastructure/aws/iam/agent/.terraform.lock.hcl diff --git a/infrastructure/aws/iam/agent/.terraform.lock.hcl b/infrastructure/aws/iam/agent/.terraform.lock.hcl new file mode 100644 index 00000000..07aa389f --- /dev/null +++ b/infrastructure/aws/iam/agent/.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/hashicorp/aws" { + version = "6.47.0" + constraints = ">= 6.28.0" + hashes = [ + "h1:aXixj6rhGPZjtzvApjS30lQfiFzp8mBFiNPzu5z02Fo=", + "zh:20fe2486489236dd558992efca2b40be286b31e750691c9bcf0328fa3bd98ee8", + "zh:23f283ceb7422e9f00a9363d6e5d82553d50749ffa85402239c88131be722f35", + "zh:2ae37c746d5dfcb8df0a8ef325621c3a7a4393daa66b077b83a79b817a67ef91", + "zh:3447c8567d2978a554ab6771a4533cc7af682bc869533091b87d2c8506c2ac16", + "zh:523a0ba927037fa3380f9dafdf54f6703c0de4db3b645abfe18a5bdd6dd94f2a", + "zh:6fc3ef5ee28d13dc0aa07f442bebd5d2b829d9900c7bbddaa8533770ead68d43", + "zh:72335055bf547f21aa7e64c1e79d1461d62c497e5f887491e7bc1483516749aa", + "zh:7c98217a75edb2282979d39daad72321b10e5b90e420a2c70332d8afb17093e5", + "zh:8a5ca65c89f389e8b5b648d428f28acf241563e9d0e893baeaef13604f94038e", + "zh:8eeb19cb3abb45a9831bbd34a24278104235848aa949b267ac0d1b014f5da30b", + "zh:a43a03bc9306dc0dd3f1f843ca1c291f24b5d41bd7485b61a83cd99184de73d8", + "zh:a54032cb98ad9bf43eb99408fd0896e091188a5354f44f7dd4ef258560dd40e5", + "zh:caba3a1ee1f9d99fff8ccb9f99e9e3c3ed554f2912c8257272f9d0b62b806d6a", + "zh:e4018d10ff9eac971213a08963f54eaa54e4d872cf78c773c0da9b93c9d739bb", + "zh:fa3a6eb20984e569e0e95cad63a5ebc05320742504a8062de764715edfdd1f8a", + ] +} From 9a5df9cb27bbc56c430b5f943189059d44c1ca58 Mon Sep 17 00:00:00 2001 From: David Fernandez Date: Tue, 9 Jun 2026 20:11:03 -0300 Subject: [PATCH 4/5] feat(agent): parametrize names, image and release for multi-instance deploys Allow running multiple nullplatform agents in the same cluster without name collisions by making IAM and Helm resource names configurable. - iam/agent: add role_name and policies_name_prefix overrides (default to the existing nullplatform-{cluster_name} convention); trust the configurable service_account_name in the OIDC provider trust relationship - nullplatform/agent: add release_name, service_account_name and image_repository variables; render serviceAccount (create/automount/name) and image.repository in the Helm values template Also ignore the local np-api-skill.token credential. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 +++- infrastructure/aws/iam/agent/main.tf | 19 +++++++++++------- infrastructure/aws/iam/agent/variables.tf | 18 +++++++++++++++++ nullplatform/agent/locals.tf | 12 ++++++----- nullplatform/agent/main.tf | 2 +- .../nullplatform_agent_values.tmpl.yaml | 14 +++++++++++-- nullplatform/agent/variables.tf | 20 +++++++++++++++++++ 7 files changed, 73 insertions(+), 16 deletions(-) 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/infrastructure/aws/iam/agent/main.tf b/infrastructure/aws/iam/agent/main.tf index 8e566db0..48782750 100644 --- a/infrastructure/aws/iam/agent/main.tf +++ b/infrastructure/aws/iam/agent/main.tf @@ -1,3 +1,8 @@ +locals { + role_name = var.role_name != "" ? var.role_name : "nullplatform-${var.cluster_name}-agent-role" + policies_name_prefix = var.policies_name_prefix != "" ? var.policies_name_prefix : "nullplatform_${var.cluster_name}" +} + ################################################################################ # IAM role for nullplatform agent service account ################################################################################ @@ -5,13 +10,13 @@ # Create IAM role with OIDC provider trust for Kubernetes service account module "nullplatform_agent_role" { source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts" - name = "nullplatform-${var.cluster_name}-agent-role" + name = local.role_name use_name_prefix = false oidc_providers = { main = { provider_arn = var.aws_iam_openid_connect_provider_arn - namespace_service_accounts = ["${var.agent_namespace}:nullplatform-agent"] + namespace_service_accounts = ["${var.agent_namespace}:${var.service_account_name}"] } } @@ -35,7 +40,7 @@ module "nullplatform_agent_role" { # Grant permissions to manage Route 53 DNS records for service discovery resource "aws_iam_policy" "nullplatform_route53_policy" { - name = "nullplatform_${var.cluster_name}_route53_policy" + name = "${local.policies_name_prefix}_route53_policy" description = "Policy for managing Route 53 DNS records" policy = jsonencode({ "Version" : "2012-10-17", @@ -70,7 +75,7 @@ resource "aws_iam_policy" "nullplatform_route53_policy" { # Grant permissions to describe and monitor load balancers and target groups resource "aws_iam_policy" "nullplatform_elb_policy" { - name = "nullplatform_${var.cluster_name}_elb_policy" + name = "${local.policies_name_prefix}_elb_policy" description = "Policy for managing Elastic Load Balancing resources" policy = jsonencode( { @@ -121,7 +126,7 @@ resource "aws_iam_policy" "nullplatform_elb_policy" { # Grant permissions to describe and list EKS cluster resources resource "aws_iam_policy" "nullplatform_eks_policy" { - name = "nullplatform_${var.cluster_name}_eks_policy" + name = "${local.policies_name_prefix}_eks_policy" description = "Policy for managing EKS cluster resources" policy = jsonencode({ "Version" : "2012-10-17", @@ -160,7 +165,7 @@ resource "aws_iam_policy" "nullplatform_eks_policy" { resource "aws_iam_policy" "nullplatform_assume_role_policy" { count = length(var.assume_role_arns) > 0 ? 1 : 0 - name = "nullplatform_${var.cluster_name}_assume_role_policy" + name = "${local.policies_name_prefix}_assume_role_policy" description = "Policy allowing the agent to assume specific IAM roles" policy = jsonencode({ Version = "2012-10-17" @@ -178,7 +183,7 @@ resource "aws_iam_policy" "nullplatform_assume_role_policy" { # Grant permissions to describe and list EKS cluster resources resource "aws_iam_policy" "nullplatform_avp_policy" { - name = "nullplatform_${var.cluster_name}_avp_policy" + name = "${local.policies_name_prefix}_avp_policy" description = "Policy for managing AVP resources" policy = jsonencode({ "Version" : "2012-10-17", diff --git a/infrastructure/aws/iam/agent/variables.tf b/infrastructure/aws/iam/agent/variables.tf index dec4c7e2..2863cd90 100644 --- a/infrastructure/aws/iam/agent/variables.tf +++ b/infrastructure/aws/iam/agent/variables.tf @@ -29,3 +29,21 @@ variable "assume_role_arns" { error_message = "Each ARN must match arn:aws:iam:::role/" } } + +variable "service_account_name" { + description = "Kubernetes service account name trusted by the IRSA role" + type = string + default = "nullplatform-agent" +} + +variable "role_name" { + description = "Override for the IAM role name. Defaults to nullplatform-{cluster_name}-agent-role" + type = string + default = "" +} + +variable "policies_name_prefix" { + description = "Override for IAM policy name prefix. Defaults to nullplatform_{cluster_name}" + type = string + default = "" +} diff --git a/nullplatform/agent/locals.tf b/nullplatform/agent/locals.tf index f0c6ac6f..ed8354db 100644 --- a/nullplatform/agent/locals.tf +++ b/nullplatform/agent/locals.tf @@ -97,10 +97,12 @@ locals { # Template único y simple nullplatform_agent_values = templatefile("${path.module}/templates/nullplatform_agent_values.tmpl.yaml", { - args = local.all_args - config_values = local.all_config - image_tag = var.image_tag - aws_iam_role_arn = var.cloud_provider == "aws" ? var.aws_iam_role_arn : "" - init_scripts = var.init_scripts + args = local.all_args + config_values = local.all_config + image_tag = var.image_tag + image_repository = var.image_repository + aws_iam_role_arn = var.cloud_provider == "aws" ? var.aws_iam_role_arn : "" + init_scripts = var.init_scripts + service_account_name = var.service_account_name }) } diff --git a/nullplatform/agent/main.tf b/nullplatform/agent/main.tf index 9ca08be2..d63e9fdc 100644 --- a/nullplatform/agent/main.tf +++ b/nullplatform/agent/main.tf @@ -49,7 +49,7 @@ resource "terraform_data" "cross_variable_validation" { # Deploy nullplatform agent to Kubernetes cluster via Helm chart resource "helm_release" "agent" { - name = "nullplatform-agent" + name = var.release_name chart = "nullplatform-agent" repository = "https://nullplatform.github.io/helm-charts" namespace = var.namespace diff --git a/nullplatform/agent/templates/nullplatform_agent_values.tmpl.yaml b/nullplatform/agent/templates/nullplatform_agent_values.tmpl.yaml index 75c9242c..ef3711ab 100644 --- a/nullplatform/agent/templates/nullplatform_agent_values.tmpl.yaml +++ b/nullplatform/agent/templates/nullplatform_agent_values.tmpl.yaml @@ -2,12 +2,19 @@ args: %{ for arg in args } - "${arg}" %{ endfor } - - %{ if aws_iam_role_arn != "" } + + %{ if aws_iam_role_arn != "" || service_account_name != "" } serviceAccount: + create: true + automount: true + %{ if service_account_name != "" } + name: "${service_account_name}" + %{ endif } + %{ if aws_iam_role_arn != "" } annotations: eks.amazonaws.com/role-arn: "${aws_iam_role_arn}" %{ endif } + %{ endif } configuration: values: @@ -17,6 +24,9 @@ configuration: image: tag: "${image_tag}" + %{ if image_repository != "" } + repository: "${image_repository}" + %{ endif } %{ if length(init_scripts) > 0 } initScripts: diff --git a/nullplatform/agent/variables.tf b/nullplatform/agent/variables.tf index ab825517..20380c26 100644 --- a/nullplatform/agent/variables.tf +++ b/nullplatform/agent/variables.tf @@ -31,6 +31,20 @@ variable "tags_selectors" { # Agent configuration ################################################################################ +# Override for the Helm release name. Defaults to nullplatform-agent +variable "release_name" { + description = "Override for the Helm release name. Defaults to nullplatform-agent" + type = string + default = "nullplatform-agent" +} + +# Override for the Kubernetes ServiceAccount name. Defaults to the chart's default (nullplatform-agent) +variable "service_account_name" { + description = "Override for the Kubernetes ServiceAccount name created by the Helm chart" + type = string + default = "" +} + # Version of the nullplatform agent Helm chart to deploy variable "nullplatform_agent_helm_version" { description = "Version of the nullplatform agent Helm chart to deploy" @@ -72,6 +86,12 @@ variable "image_tag" { type = string } +variable "image_repository" { + description = "Container image repository for the agent. Defaults to the official nullplatform image." + type = string + default = "" +} + # ARN of the AWS IAM role assigned to the agent (required when cloud_provider is 'aws') variable "aws_iam_role_arn" { description = "ARN of the AWS IAM role assigned to the agent" From 74cce6502f977e48a9401f3145b4bda2309ed308 Mon Sep 17 00:00:00 2001 From: David Fernandez Date: Tue, 9 Jun 2026 20:12:58 -0300 Subject: [PATCH 5/5] chore(security): add .terraform.lock.hcl Track the provider lock file for reproducible builds. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../aws/security/.terraform.lock.hcl | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 infrastructure/aws/security/.terraform.lock.hcl diff --git a/infrastructure/aws/security/.terraform.lock.hcl b/infrastructure/aws/security/.terraform.lock.hcl new file mode 100644 index 00000000..5ead6105 --- /dev/null +++ b/infrastructure/aws/security/.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/hashicorp/aws" { + version = "6.46.0" + constraints = "~> 6.0" + hashes = [ + "h1:VWSIs/K9tDs2X4Ej6fXHfiqlFubplb9ueRmTYmuqYh4=", + "zh:0a1dad9bd82743d63c1e65551e68948d33638dfaebb7f44bd89ec948e33ff975", + "zh:166dde870afc7d1fca056d99eb8af8a0729f54e84467e6e2fa7f0a3bea5982cf", + "zh:41bceb5f5f3aa832e07a4f077371f1309de1cf42621117d144227022d5ebc95c", + "zh:42fae38f09d1c23d8b9bdd12306d7740196837c150891f21517ada314ec40b35", + "zh:8d01f9b87083201fdfef4139068dc6540f9a0e3ac1e394b5c53195c2ac760b59", + "zh:9964ed013c0eb3cd8ff9b501a88e6a823b42655c4354a0f37e6f764bc51ab1fe", + "zh:a153cbb1cb57288955ba3db2e25368931afda529bab1afd5eda361fc368378e3", + "zh:a3bd14a333791ed26e60921d1c348ab5b9b5aae8fcfc590e40e9d741cdd9a9b4", + "zh:a4d32af825ba8f37d3c0c979a241a132a31332684b19060b0523b92c4dc5337f", + "zh:bc5ff7cf49be2c648d16c96449f0b02fda3e0d3af5e6820d625246a249ff31ee", + "zh:beacec832343dac5d4624d33eae80a44d58ef121c065b56ec220d6bbce2b5011", + "zh:c346a67110f3e17a65d4a46316f062b4af6b88d8fc3d5fb4092a6f0669719602", + "zh:cd0b4f44186a0eb2bd3cc59e4a55b5a363c9ac3303b23d0128171ff851f36336", + "zh:d524bff9f4b567ad19c3d3040ead81aa4108030238155d87a1bb2c797a7870e7", + "zh:ef23733dd0c7d40a4f3ccc761c4433a265324fa393aa4ae6015942b31a13d30e", + ] +}