From 806c6ae59e2cb4991070ec05b1a79fc410071a14 Mon Sep 17 00:00:00 2001 From: Pratik Lotia Date: Tue, 12 May 2026 15:25:07 -0400 Subject: [PATCH] aws node attester: add org verification support (#825) * add verify org support for aws node attester Signed-off-by: pratik-lotia * refactor with suggested changes Signed-off-by: pratik-lotia --------- Signed-off-by: pratik-lotia --- charts/spire/charts/spire-server/README.md | 6 +++++ .../spire-server/templates/configmap.yaml | 21 +++++++++++++++ charts/spire/charts/spire-server/values.yaml | 12 +++++++++ tests/unit/spire_test.go | 26 +++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/charts/spire/charts/spire-server/README.md b/charts/spire/charts/spire-server/README.md index c920d86..e8ae52c 100644 --- a/charts/spire/charts/spire-server/README.md +++ b/charts/spire/charts/spire-server/README.md @@ -435,6 +435,12 @@ In order to run Tornjak with simple HTTP Connection only, make sure you don't cr | `nodeAttestor.tpmDirect.hashes` | A list of TPM hashes that are allowed to connect. | `[]` | | `nodeAttestor.awsIID.enabled` | Enable the aws_iid node attestor | `false` | | `nodeAttestor.awsIID.assumeRole` | AWS IAM Role NAME to use for the attestation | `""` | +| `nodeAttestor.awsIID.verifyOrganization` | When enabled, SPIRE verifies the attesting node's AWS account is a member of your AWS Organization ([SPIRE aws_iid server plugin](https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_aws_iid.md)). | | +| `nodeAttestor.awsIID.verifyOrganization.enabled` | Enable AWS Organizations membership validation (`verify_organization` in plugin config) | `false` | +| `nodeAttestor.awsIID.verifyOrganization.managementAccountId` | AWS Organizations management/root account ID (12 digits); SPIRE assumes `arn:aws:iam:::role/` for listing org accounts. | `""` | +| `nodeAttestor.awsIID.verifyOrganization.assumeOrgRole` | IAM role **name** in the management account; must allow `organizations:ListAccounts` and trust the IAM identity used by the SPIRE server. | `""` | +| `nodeAttestor.awsIID.verifyOrganization.managementAccountRegion` | Optional region SPIRE uses for org validation/cache keying (defaults in SPIRE if unset). | `""` | +| `nodeAttestor.awsIID.verifyOrganization.orgAccountMapTTL` | Optional cache TTL for the org account map (SPIRE expects a duration ≥ 1m when set; e.g. `3m`, `5m`). Defaults to 3 minutes. | `3m` | | `nodeAttestor.gcpIIT.enabled` | Enable the gcp_iit node attestor | `false` | | `nodeAttestor.gcpIIT.projectIDAllowList` | List of ProjectIDs from which nodes can be attested | `[]` | | `nodeAttestor.gcpIIT.useInstanceMetadata` | If true, instance metadata is fetched from the Google Compute Engine API and used to augment the node selectors produced by the plugin | `false` | diff --git a/charts/spire/charts/spire-server/templates/configmap.yaml b/charts/spire/charts/spire-server/templates/configmap.yaml index 9d40d33..283ae7c 100644 --- a/charts/spire/charts/spire-server/templates/configmap.yaml +++ b/charts/spire/charts/spire-server/templates/configmap.yaml @@ -159,6 +159,14 @@ plugins: {{- end }} disable_migration: {{ .Values.dataStore.sql.disableMigration }} +{{- with .Values.nodeAttestor.awsIID.verifyOrganization }} +{{- if and (eq ($.Values.nodeAttestor.awsIID.enabled | toString) "true") (eq (.enabled | toString) "true") }} +{{- if or (eq .managementAccountId "") (eq .assumeOrgRole "") }} +{{- fail "nodeAttestor.awsIID.verifyOrganization requires managementAccountId and assumeOrgRole when enabled=true" }} +{{- end }} +{{- end }} +{{- end }} + {{- if or .Values.nodeAttestor.k8sPSAT.enabled .Values.nodeAttestor.externalK8sPSAT.enabled .Values.nodeAttestor.joinToken.enabled .Values.nodeAttestor.httpChallenge.enabled .Values.nodeAttestor.tpmDirect.enabled .Values.nodeAttestor.awsIID.enabled .Values.nodeAttestor.gcpIIT.enabled }} NodeAttestor: {{- $clusters := default .Values.kubeConfigs .Values.nodeAttestor.externalK8sPSAT.clusters }} @@ -246,6 +254,19 @@ plugins: {{- if ne .assumeRole "" }} assume_role: {{ .assumeRole | quote }} {{- end }} + {{- with .verifyOrganization }} + {{- if eq (.enabled | toString) "true" }} + verify_organization: + management_account_id: {{ .managementAccountId | quote }} + assume_org_role: {{ .assumeOrgRole | quote }} + {{- if ne .managementAccountRegion "" }} + management_account_region: {{ .managementAccountRegion | quote }} + {{- end }} + {{- if ne .orgAccountMapTTL "" }} + org_account_map_ttl: {{ .orgAccountMapTTL | quote }} + {{- end }} + {{- end }} + {{- end }} {{- end }} {{- end }} {{- with .Values.nodeAttestor.gcpIIT }} diff --git a/charts/spire/charts/spire-server/values.yaml b/charts/spire/charts/spire-server/values.yaml index 6b1f11e..1fbb497 100644 --- a/charts/spire/charts/spire-server/values.yaml +++ b/charts/spire/charts/spire-server/values.yaml @@ -1074,6 +1074,18 @@ nodeAttestor: enabled: false ## @param nodeAttestor.awsIID.assumeRole AWS IAM Role NAME to use for the attestation assumeRole: "" + ## @extra nodeAttestor.awsIID.verifyOrganization When enabled, SPIRE verifies the attesting node's AWS account is a member of your AWS Organization ([SPIRE aws_iid server plugin](https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_aws_iid.md)). + verifyOrganization: + ## @param nodeAttestor.awsIID.verifyOrganization.enabled Enable AWS Organizations membership validation (`verify_organization` in plugin config) + enabled: false + ## @param nodeAttestor.awsIID.verifyOrganization.managementAccountId AWS Organizations management/root account ID (12 digits); SPIRE assumes `arn:aws:iam:::role/` for listing org accounts. + managementAccountId: "" + ## @param nodeAttestor.awsIID.verifyOrganization.assumeOrgRole IAM role **name** in the management account; must allow `organizations:ListAccounts` and trust the IAM identity used by the SPIRE server. + assumeOrgRole: "" + ## @param nodeAttestor.awsIID.verifyOrganization.managementAccountRegion Optional region SPIRE uses for org validation/cache keying (defaults in SPIRE if unset). + managementAccountRegion: "" + ## @param nodeAttestor.awsIID.verifyOrganization.orgAccountMapTTL Optional cache TTL for the org account map (SPIRE expects a duration ≥ 1m when set; e.g. `3m`, `5m`). Defaults to 3 minutes. + orgAccountMapTTL: "3m" gcpIIT: ## @param nodeAttestor.gcpIIT.enabled Enable the gcp_iit node attestor enabled: false diff --git a/tests/unit/spire_test.go b/tests/unit/spire_test.go index b704d45..1c79419 100644 --- a/tests/unit/spire_test.go +++ b/tests/unit/spire_test.go @@ -150,6 +150,32 @@ spire-server: Expect(notes).Should(ContainSubstring("Installed")) }) }) + Describe("spire-server.nodeAttestor.awsIID.verifyOrganization", func() { + It("emits verify_organization in server config JSON", func() { + objs, err := ValueStringRender(chart, ` +spire-server: + nodeAttestor: + k8sPSAT: + enabled: false + awsIID: + enabled: true + verifyOrganization: + enabled: true + managementAccountId: "111122223333" + assumeOrgRole: "spire-server-org-validator" + managementAccountRegion: "us-east-1" + orgAccountMapTTL: "5m" +`) + Expect(err).Should(Succeed()) + notes := objs["spire/charts/spire-server/templates/configmap.yaml"] + Expect(notes).Should(ContainSubstring(`verify_organization`)) + Expect(notes).Should(ContainSubstring(`management_account_id`)) + Expect(notes).Should(ContainSubstring(`111122223333`)) + Expect(notes).Should(ContainSubstring(`spire-server-org-validator`)) + Expect(notes).Should(ContainSubstring(`us-east-1`)) + Expect(notes).Should(ContainSubstring(`5m`)) + }) + }) Describe("spire-server.credentialComposer.uniqueID", func() { It("spire server uniqueid credential composer", func() { objs, err := ValueStringRender(chart, `