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
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!-- BEGIN_TF_DOCS -->
# spire/agent-iam-role

Creates an IAM role for the SPIRE agent with AWS Secrets Manager permissions for
the aws\_secretsmanager SVIDStore plugin. The plugin stores workload SVIDs as secrets
in Secrets Manager; the agent needs create, update, describe, and delete permissions
on secrets under the configured name prefix.

## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.10 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 6.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 6.0 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_eks_pod_identity_association.spire_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association) | resource |
| [aws_iam_role.spire_agent](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.secretsmanager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.secretsmanager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_cluster_name"></a> [cluster\_name](#input\_cluster\_name) | EKS cluster name. Required for Pod Identity association. | `string` | `null` | no |
| <a name="input_iam_mode"></a> [iam\_mode](#input\_iam\_mode) | IAM binding mode. 'pod\_identity' uses EKS Pod Identity (requires eks-pod-identity-agent add-on). 'irsa' uses IAM Roles for Service Accounts (requires enable\_irsa = true in the cluster unit and oidc\_provider\_arn to be set). | `string` | `"pod_identity"` | no |
| <a name="input_namespace"></a> [namespace](#input\_namespace) | Kubernetes namespace the SPIRE agent runs in. Used for the Pod Identity association and IRSA trust condition. | `string` | `"spire-system"` | no |
| <a name="input_oidc_provider_arn"></a> [oidc\_provider\_arn](#input\_oidc\_provider\_arn) | ARN of the EKS cluster OIDC provider. Required for IRSA mode. | `string` | `null` | no |
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | Name for the SPIRE agent IAM role. | `string` | n/a | yes |
| <a name="input_service_account_name"></a> [service\_account\_name](#input\_service\_account\_name) | Kubernetes service account name for the SPIRE agent. Used for the Pod Identity association and IRSA trust condition. | `string` | `"spire-agent"` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_role_arn"></a> [role\_arn](#output\_role\_arn) | ARN of the SPIRE agent IAM role. |
| <a name="output_role_name"></a> [role\_name](#output\_role\_name) | Name of the SPIRE agent IAM role. |
<!-- END_TF_DOCS -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* # spire/agent-iam-role
*
* Creates an IAM role for the SPIRE agent with AWS Secrets Manager permissions for
* the aws_secretsmanager SVIDStore plugin. The plugin stores workload SVIDs as secrets
* in Secrets Manager; the agent needs create, update, describe, and delete permissions
* on secrets under the configured name prefix.
*/

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

locals {
account_id = data.aws_caller_identity.current.account_id
region = data.aws_region.current.region

oidc_provider_url = var.oidc_provider_arn != null ? trimprefix(var.oidc_provider_arn, "arn:aws:iam::${local.account_id}:oidc-provider/") : null
}

# --- Trust policy ---

data "aws_iam_policy_document" "assume_role" {
dynamic "statement" {
for_each = var.iam_mode == "pod_identity" ? [1] : []
content {
actions = ["sts:AssumeRole", "sts:TagSession"]
principals {
type = "Service"
identifiers = ["pods.eks.amazonaws.com"]
}
dynamic "condition" {
for_each = var.cluster_name != null ? [1] : []
content {
test = "StringEquals"
variable = "aws:RequestTag/eks-cluster-name"
values = [var.cluster_name]
}
}
condition {
test = "StringEquals"
variable = "aws:RequestTag/kubernetes-namespace"
values = [var.namespace]
}
condition {
test = "StringEquals"
variable = "aws:RequestTag/kubernetes-service-account"
values = [var.service_account_name]
}
}
}

dynamic "statement" {
for_each = var.iam_mode == "irsa" ? [1] : []
content {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [var.oidc_provider_arn]
}
condition {
test = "StringEquals"
variable = "${local.oidc_provider_url}:sub"
values = ["system:serviceaccount:${var.namespace}:${var.service_account_name}"]
}
condition {
test = "StringEquals"
variable = "${local.oidc_provider_url}:aud"
values = ["sts.amazonaws.com"]
}
}
}
}

resource "aws_iam_role" "spire_agent" {
name = var.role_name
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

# --- Secrets Manager SVIDStore policy ---
#
# The plugin tags every secret it creates with spire-svid=true and validates that tag
# before updating or deleting existing secrets. Tag conditions therefore scope each
# statement precisely:
# - CreateSecret: require the tag in the request so only tagged secrets can be created.
# - DescribeSecret: StringEqualsIfExists so that the plugin can check whether a secret
# exists before creating it — when the secret doesn't yet exist there is no resource
# tag for IAM to evaluate, so StringEquals would deny the call.
# - All other operations: require the tag on the resource.

data "aws_iam_policy_document" "secretsmanager" {
statement {
sid = "CreateSVIDSecret"
actions = ["secretsmanager:CreateSecret"]
resources = ["*"]
condition {
test = "StringEquals"
variable = "aws:RequestTag/spire-svid"
values = ["true"]
}
}

statement {
sid = "DescribeSVIDSecret"
actions = ["secretsmanager:DescribeSecret"]
resources = ["*"]
condition {
test = "StringEqualsIfExists"
variable = "aws:ResourceTag/spire-svid"
values = ["true"]
}
}

statement {
sid = "ManageSVIDSecrets"
actions = [
"secretsmanager:DeleteSecret",
"secretsmanager:PutSecretValue",
"secretsmanager:RestoreSecret",
"secretsmanager:TagResource",
]
resources = ["*"]
condition {
test = "StringEquals"
variable = "aws:ResourceTag/spire-svid"
values = ["true"]
}
}
}

resource "aws_iam_role_policy" "secretsmanager" {
name = "${var.role_name}-secretsmanager"
role = aws_iam_role.spire_agent.name
policy = data.aws_iam_policy_document.secretsmanager.json
}

# --- EKS Pod Identity association ---

resource "aws_eks_pod_identity_association" "spire_agent" {
count = var.iam_mode == "pod_identity" && var.cluster_name != null ? 1 : 0

cluster_name = var.cluster_name
namespace = var.namespace
service_account = var.service_account_name
role_arn = aws_iam_role.spire_agent.arn
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "role_arn" {
description = "ARN of the SPIRE agent IAM role."
value = aws_iam_role.spire_agent.arn
}

output "role_name" {
description = "Name of the SPIRE agent IAM role."
value = aws_iam_role.spire_agent.name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
variable "role_name" {
type = string
description = "Name for the SPIRE agent IAM role."
}

variable "iam_mode" {
type = string
default = "pod_identity"
description = "IAM binding mode. 'pod_identity' uses EKS Pod Identity (requires eks-pod-identity-agent add-on). 'irsa' uses IAM Roles for Service Accounts (requires enable_irsa = true in the cluster unit and oidc_provider_arn to be set)."

validation {
condition = contains(["pod_identity", "irsa"], var.iam_mode)
error_message = "iam_mode must be 'pod_identity' or 'irsa'."
}
}

variable "namespace" {
type = string
default = "spire-system"
description = "Kubernetes namespace the SPIRE agent runs in. Used for the Pod Identity association and IRSA trust condition."
}

variable "service_account_name" {
type = string
default = "spire-agent"
description = "Kubernetes service account name for the SPIRE agent. Used for the Pod Identity association and IRSA trust condition."
}

variable "cluster_name" {
type = string
default = null
description = "EKS cluster name. Required for Pod Identity association."
}

variable "oidc_provider_arn" {
type = string
default = null
description = "ARN of the EKS cluster OIDC provider. Required for IRSA mode."

validation {
condition = var.iam_mode != "irsa" || var.oidc_provider_arn != null
error_message = "oidc_provider_arn must be set when iam_mode is 'irsa'."
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.10"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
locals {
# Name for the attestation policy in Cofide Connect.
policy_name = "connect-trust-zone-aws-reference-arch-svidstore"

# SPIFFE ID path assigned to workloads matching this policy (e.g. ns/<namespace>/sa/<service-account>).
# Required — no default.
spiffe_id_path = "ns/default/sa/my-app"

# SPIFFE ID path of the parent node agent. For k8s-psat-attested nodes this follows the pattern
# /spire/agent/k8s_psat/<cluster-name>/<node-uid>. Can be derived from the SPIRE server entry list.
# Required — no default.
parent_id_path = "/spire/agent/k8s_psat/connect-trust-zone-aws-reference-arch/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"

# SPIRE selectors used to match workloads to this attestation policy.
# Required — no default.
selectors = [
{
type = "aws_secretsmanager"
value = "secretname:my-secret"
},
]

# Optional: override values if not reading from their respective dependency units.
# connect_url = "example.cofide.dev:443"
}

# Note: this unit requires a locally built cofide/cofide provider that includes the store_svid
# field on the static attestation policy. Configure dev_overrides in ~/.terraformrc to use it:
#
# provider_installation {
# dev_overrides {
# "cofide/cofide" = "/path/to/built/binary/directory"
# }
# direct {}
# }
#
# Build the provider from the terraform-provider-cofide repository (sibling of this repo) with:
# go build -o ~/go/bin/terraform-provider-cofide
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
resource "cofide_connect_attestation_policy" "this" {
name = var.policy_name

static = {
spiffe_id_path = var.spiffe_id_path
parent_id_path = var.parent_id_path
selectors = var.selectors
store_svid = true
}
}

resource "cofide_connect_ap_binding" "this" {
trust_zone_id = var.trust_zone_id
policy_id = cofide_connect_attestation_policy.this.id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "policy_id" {
description = "The ID of the attestation policy registered with Cofide Connect."
value = cofide_connect_attestation_policy.this.id
}

output "binding_id" {
description = "The ID of the attestation policy binding."
value = cofide_connect_ap_binding.this.id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provider "cofide" {
connect_url = var.connect_url
# api_token is configured via the COFIDE_API_TOKEN environment variable.
}
Loading