Stands up an Amazon Detective behavior graph for multi-account security investigation — the regional graph (keystone), member-account invitations, the cross-account invitation accepter, and AWS Organizations delegated-administrator / org-wide auto-enrollment, all correctly ordered and toggleable per invocation shape. Built for the AWS provider v6.x.
- 🕵️ Investigation graph, fully wired. Creates
aws_detective_graph(the keystone) plus everything meaningless without it: member-account invitations, the cross-account invitation accepter, and AWS Organizations delegated-admin / org-wide auto-enrollment. - 🔀 Two invocation shapes, one module. Invoke with
create_graph = truefrom the account that will own the graph (administrator), orcreate_graph = falsefrom a member account's own provider context purely to accept an invitation and/or read organization state against an externally-suppliedgraph_arn. - 🔗 Correct ordering, automatically.
organization_admin_accountis sequenced beforeorganization_configurationviadepends_on(no direct attribute reference exists between them, since they typically run from different accounts) — mirroring the pattern intf-mod-aws-inspector2. - 🏢 Org-wide delegated administrator — same shape as GuardDuty / Security Hub. One designated admin account manages the org's Detective behavior graph and can auto-enroll new accounts as they join.
- 🤝 A member must ACCEPT before it's fully part of the graph.
aws_detective_memberonly sends the invitation from the administrator account;aws_detective_invitation_accepter— run in the member's own provider context — completes the join, mirroring the cross-account accepter pattern used elsewhere in this library (e.g. GuardDuty's invite accepter). - 🏷️ Tags where they're accepted.
var.tagsflows to the only taggable Detective resource — the graph — and merges with providerdefault_tags; members, the accepter, and both organization resources are not taggable. - 🧮 Members as data.
membersis amap(object(...))collection rendered withfor_each, keyed by a stable caller string.
💡 Why it matters: GuardDuty tells you something happened; Detective helps you figure out what actually happened — it automatically organizes findings, VPC flow logs, and DNS/API activity into a graph an investigator can pivot through in seconds instead of stitching together CloudTrail queries by hand. For an FI under regulatory oversight handling PII, that shortens the single most expensive phase of an incident — root-cause investigation — from days to minutes, and doing it consistently across every account starts with
tf-mod-aws-guarddutyenabled first.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
tf-mod-aws-detective is a security investigation consumer — it sits downstream of GuardDuty's findings and upstream of aggregation/response, and shares its delegated-administrator wiring with AWS Organizations.
flowchart LR
gd["tf-mod-aws-guardduty<br/>threat findings source"]
det["tf-mod-aws-detective"]
hub["tf-mod-aws-security-hub<br/>findings aggregation"]
org["tf-mod-aws-organizations<br/>delegated admin / accounts"]
eb["tf-mod-aws-eventbridge<br/>investigation-driven response"]
gd -.->|"findings feed investigation graph"| det
org -->|"organization_admin_account_id / member account ids"| det
det -.->|"investigation context"| hub
det -.->|"graph events"| eb
style det fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
⚠️ Enable GuardDuty first. Detective ingests GuardDuty findings, VPC Flow Logs, and DNS query logs as its primary evidence sources. It will still enable and analyze flow/DNS data without GuardDuty, but the investigative payoff is materially smaller —tf-mod-aws-guarddutyis not a Terraform-level dependency of this module, but it is the operationally recommended prerequisite.
flowchart TD
subgraph mod["tf-mod-aws-detective"]
keystone["aws_detective_graph.this<br/>(keystone, guarded for_each)<br/>toggled by create_graph"]
mem["aws_detective_member.this<br/>for_each members"]
oaa["aws_detective_organization_admin_account.this<br/>guarded for_each"]
ocfg["aws_detective_organization_configuration.this<br/>guarded for_each"]
acc["aws_detective_invitation_accepter.this<br/>guarded for_each"]
end
keystone --> mem
keystone --> ocfg
oaa --> ocfg
mem -.->|"member account's own provider"| acc
keystone -.->|"local.graph_arn when create_graph=false"| acc
style keystone fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
style mem stroke-dasharray: 5 5
style oaa stroke-dasharray: 5 5
style ocfg stroke-dasharray: 5 5
style acc stroke-dasharray: 5 5
| Resource | Count | Created when |
|---|---|---|
aws_detective_graph.this |
0 or 1 | create_graph = true (default) |
aws_detective_member.this |
0..N | one per members entry |
aws_detective_organization_admin_account.this |
0 or 1 | enable_organization_admin_account = true |
aws_detective_organization_configuration.this |
0 or 1 | organization_configuration != null |
aws_detective_invitation_accepter.this |
0 or 1 | accept_invitation = true |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
The module declares only a required_providers block (providers.tf) and inherits the configured provider. There is no provider {} block and no credential variable — credentials resolve through the standard AWS chain at the root/pipeline level (env vars → SSO/shared credentials → assume_role → instance profile / IRSA → OIDC web identity). Detective is regional: the graph and all children are created in the inherited provider's Region. To cover multiple Regions, instantiate this module once per Region with provider aliases.
Least-privilege actions the Terraform execution identity needs to manage this module. Split by resource because the organization admin-account and org-configuration paths run from different accounts (management vs. delegated administrator).
| Action | Required for | Notes |
|---|---|---|
detective:CreateGraph, detective:DeleteGraph, detective:ListGraphs, detective:TagResource, detective:UntagResource, detective:ListTagsForResource |
Graph lifecycle / tagging | The only taggable resource; only when create_graph = true |
detective:CreateMembers, detective:DeleteMembers, detective:GetMembers, detective:ListMembers |
Member-account invitations | Administrator account only; when members is set |
detective:AcceptInvitation, detective:GetMembers, detective:DisassociateMembership |
Invitation acceptance | Member account only; when accept_invitation = true |
detective:EnableOrganizationAdminAccount, detective:DisableOrganizationAdminAccount, detective:ListOrganizationAdminAccounts |
Delegated administrator | Organizations management account only |
organizations:EnableAWSServiceAccess, organizations:ListDelegatedAdministrators, organizations:RegisterDelegatedAdministrator, organizations:DeregisterDelegatedAdministrator, organizations:DescribeOrganization, organizations:DescribeAccount |
Delegated-admin registration is an Organizations-service action, not purely Detective | Management account only |
detective:UpdateOrganizationConfiguration, detective:DescribeOrganizationConfiguration |
Org-wide auto-enrollment policy | Delegated administrator account only |
ℹ️ No
iam:PassRole. No resource in this module assumes or passes an IAM role.
- Enable GuardDuty first (strongly recommended). Detective's evidence sources are GuardDuty findings, VPC Flow Logs, and DNS query logs — the graph works without GuardDuty but is materially less useful.
- One behavior graph per account per Region (hard limit). Instantiating this module twice with
create_graph = truein the same account+Region fails on the second graph. Use provider aliases (one module instance per Region). - Organization-wide delegated administrator — mirrors GuardDuty/Security Hub exactly. One designated Detective delegated administrator account manages the org's graph and can auto-enroll new accounts; register it via
enable_organization_admin_accountfrom the management account before applyingorganization_configurationfrom the delegated administrator account. - Detective trusted access for AWS Organizations (
aws_service_access_principalsincludingdetective.amazonaws.com) must already be enabled on the Organization beforeaws_detective_organization_admin_accountsucceeds — a one-time Organizations-level action, typically performed in the root module or viatf-mod-aws-organizations, outside this module's control. - No service-linked role is documented as required for Detective itself.
- Quotas (Region-specific; see the Detective User Guide):
- 1 behavior graph per account per Region.
- 1,200 member accounts per behavior graph (invited or organization-enrolled, combined).
- Detective can only ingest data back to the account's existing log retention — enabling it late means historical activity prior to enablement is not retroactively analyzed.
tf-mod-aws-detective/
├── providers.tf # required_providers (aws >= 6.0, < 7.0); no provider block
├── variables.tf # create_graph → graph_arn → members → org admin account → org configuration → accept_invitation → tags → timeouts
├── main.tf # graph (this, guarded) → members / org admin account / org configuration / invitation accepter
├── outputs.tf # id + arn + graph/member/org/accepter attrs + tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM permissions, prerequisites, gotchas
Smallest secure call — standalone graph, no members, no org wiring:
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
# create_graph = true (default) — a standalone behavior graph in this account+Region.
tags = {
Environment = "prod"
CostCenter = "1234"
}
}| Input | Type | Source module |
|---|---|---|
graph_arn (when create_graph = false) |
string (Detective graph ARN) |
Another invocation of this module (administrator account's arn output) |
organization_admin_account_id |
string (12-digit account id) |
Caller-supplied, or data.aws_organizations_organization / data.aws_caller_identity |
members[*].account_id |
string (12-digit account id) |
Caller-supplied member/child account ids |
| Output | Description | Consumed by |
|---|---|---|
id |
Graph id, or null when create_graph = false |
State inspection |
arn / graph_arn |
Graph ARN (arn:<partition>:detective:<region>:<account>:graph:<graph-id>) — cross-resource reference type, or null when create_graph = false |
Member-account module calls' graph_arn input; Security Hub integration; audit |
effective_graph_arn |
The graph ARN this call actually operates against — never null when the module manages any child resource | Internal wiring reference |
created_time |
Graph creation timestamp, or null |
Audit |
tags_all |
All tags incl. provider default_tags on the graph, or null |
Governance/audit |
member_ids / member_statuses / member_administrator_ids / member_volume_usage_bytes |
Maps of member label → id / status / administrator account id / daily ingest volume | Org rollup, membership health monitoring |
organization_admin_account_id |
Registered delegated administrator account id, or null |
Audit; cross-check against tf-mod-aws-guardduty / tf-mod-aws-security-hub delegated admin |
organization_configuration_id / organization_auto_enable |
Org configuration state, or null |
Compliance reporting |
invitation_accepter_id |
Accepter id, or null when accept_invitation = false |
Audit/state inspection |
1 · Minimal secure baseline (standalone graph)
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
# create_graph = true — all defaults.
}2 · Tags (merge with provider default_tags)
# Caller's provider block owns default_tags; the module never sets it.
provider "aws" {
default_tags { tags = { Owner = "security", ManagedBy = "terraform" } }
}
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
tags = {
Environment = "prod" # resource tag — wins over default_tags on key conflict
DataClass = "internal"
}
}
# module.detective.tags_all == { Owner, ManagedBy, Environment, DataClass }
# Only the graph is taggable — members, the accepter, and both org resources are not.3 · Multi-account: invite member accounts (administrator account)
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
members = {
audit = {
account_id = "123456789012"
email_address = "security+audit@financialpartners.com"
message = "Please accept to join the investigation graph."
}
sandbox = {
account_id = "444455556666"
email_address = "security+sandbox@financialpartners.com"
disable_email_notification = true
}
}
}4 · Accept an invitation (member account's own provider context)
provider "aws" {
alias = "member"
region = "us-east-1"
#... assume_role / profile targeting the MEMBER account
}
module "detective_member" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
providers = { aws = aws.member }
create_graph = false # this account does not own a graph
accept_invitation = true
graph_arn = module.detective.arn # the administrator's graph ARN
}5 · Organization-wide: register the delegated administrator (management account)
module "detective_admin" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
create_graph = false # the management account does not own the org's graph
enable_organization_admin_account = true
organization_admin_account_id = "222233334444" # the security-tooling account
}6 · Organization-wide: apply the auto-enrollment policy (delegated administrator account)
provider "aws" {
alias = "delegated_admin"
#... targets account 222233334444
}
module "detective_delegated_admin" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
providers = { aws = aws.delegated_admin }
# create_graph = true — the delegated administrator owns the org's graph.
organization_configuration = {
auto_enable = true # SECURE-BY-DEFAULT TRADEOFF: new org accounts are auto-enrolled —
# continuous coverage, but Terraform is no longer the sole source
# of truth for membership. See README § Design Principles.
}
}7 · Secure-by-default opt-out: individual invitations instead of org auto-enrollment
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
# organization_configuration left null (default) — every account is invited
# explicitly and individually via `members`, keeping Terraform as the single
# source of truth for graph membership at the cost of a manual invite step
# for every new account.
members = {
audit = { account_id = "123456789012", email_address = "security+audit@financialpartners.com" }
}
}8 · Suppress the root-user invitation email
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
members = {
sandbox = {
account_id = "444455556666"
email_address = "security+sandbox@financialpartners.com"
disable_email_notification = true # default false — root user IS notified by default
}
}
}9 · for_each pattern: bulk-invite from a map of business units
locals {
business_unit_accounts = {
lending = { account_id = "111122223333", email_address = "security+lending@financialpartners.com" }
servicing = { account_id = "444455556666", email_address = "security+servicing@financialpartners.com" }
treasury = { account_id = "777788889999", email_address = "security+treasury@financialpartners.com" }
}
}
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
members = local.business_unit_accounts
}10 · Attach members to an externally-created graph (create_graph = false)
module "detective_members_only" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
create_graph = false
graph_arn = "arn:aws:detective:us-east-1:123456789101:graph:231684d34gh74g4bae1dbc7bd807d02d"
members = {
audit = { account_id = "123456789012", email_address = "security+audit@financialpartners.com" }
}
}11 · import block — bring an existing graph under management
import {
to = module.detective.aws_detective_graph.this["this"]
id = "arn:aws:detective:us-east-1:123456789101:graph:231684d34gh74g4bae1dbc7bd807d02d"
}
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
}12 · Multi-Region coverage with provider aliases
# Detective is regional and singular — one graph per Region. Instantiate per Region.
provider "aws" { region = "us-east-1" }
provider "aws" { alias = "west"
region = "us-west-2"
}
module "detective_use1" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
}
module "detective_usw2" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
providers = { aws = aws.west }
}13 · Single-call demo/test: graph + member + accepter via provider aliases
provider "aws" { alias = "primary" }
provider "aws" { alias = "member" }
module "detective_primary" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
providers = { aws = aws.primary }
members = {
member1 = { account_id = "444455556666", email_address = "security+member1@financialpartners.com" }
}
}
module "detective_member" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
providers = { aws = aws.member }
create_graph = false
accept_invitation = true
graph_arn = module.detective_primary.arn
}
# NOTE: this single-call, multi-provider shape is convenient for sandbox/test
# accounts under common control; in production the administrator and member
# module calls typically live in separate root-module states/pipelines.14 · Cross-check the org's delegated security administrator
# Confirm the same account is the delegated admin for Detective, GuardDuty, and Security Hub
# so incident response has one consistent "security account" across all three.
output "delegated_admins" {
value = {
detective = module.detective_admin.organization_admin_account_id
guardduty = module.guardduty.account_id
}
}15 · End-to-end composition — GuardDuty findings feeding a delegated-admin Detective graph
# 1. Threat detection first — GuardDuty is Detective's primary evidence source.
module "guardduty" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-guardduty?ref=v1.0.0"
}
# 2. Register the org's Detective delegated administrator (management account).
module "detective_admin" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
create_graph = false
enable_organization_admin_account = true
organization_admin_account_id = "222233334444"
}
# 3. From the delegated administrator account: own the graph, auto-enroll new
# org accounts, and invite one legacy account that predates the org policy.
provider "aws" {
alias = "delegated_admin"
#... targets account 222233334444
}
module "detective" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-detective?ref=v1.0.0"
providers = { aws = aws.delegated_admin }
organization_configuration = { auto_enable = true }
members = {
legacy-account = {
account_id = "999988887777"
email_address = "security+legacy@financialpartners.com"
}
}
tags = { Environment = "prod", DataClass = "internal" }
depends_on = [module.guardduty, module.detective_admin]
}| Name | Type | Default | Description |
|---|---|---|---|
create_graph |
bool |
true |
Whether to create the keystone graph. false for member-only / attach-to-existing-graph invocations. |
graph_arn |
string |
null |
ARN of an existing graph to operate against when create_graph = false. Required when child resources are used in that mode. |
members |
map(object({...})) |
{} |
Member-account invitations (administrator account only); account_id, email_address, message, disable_email_notification. Not taggable. |
enable_organization_admin_account |
bool |
false |
Whether to register organization_admin_account_id as the org's Detective delegated administrator. |
organization_admin_account_id |
string |
null |
12-digit account id to register. Required when enable_organization_admin_account = true. |
organization_configuration |
object({ auto_enable = bool }) |
null |
Org-wide auto-enrollment policy, applied from the delegated administrator account. |
accept_invitation |
bool |
false |
Whether to accept a pending invitation against graph_arn — run from the member account's own provider context. |
tags |
map(string) |
{} |
Tags for the graph (the only taggable resource); merge with default_tags (resource tags win). |
timeouts |
object({...}) |
{} |
Reserved for interface consistency — no Detective resource exposes a configurable timeouts block. |
See variables.tf for full heredoc schemas and validation rules.
| Name | Description |
|---|---|
id |
Graph id, or null when create_graph = false. |
arn |
Graph ARN (arn:<partition>:detective:<region>:<account>:graph:<graph-id>), or null. |
graph_arn |
Explicit alias of arn. |
effective_graph_arn |
The graph ARN this call actually operates against — never null when any child resource is managed. |
created_time |
Graph creation timestamp, or null. |
tags_all |
All tags incl. provider default_tags on the graph, or null. |
member_ids / member_statuses / member_administrator_ids / member_volume_usage_bytes |
Maps keyed by member label. |
organization_admin_account_id |
Registered delegated administrator account id, or null. |
organization_configuration_id / organization_auto_enable |
Org configuration state, or null. |
invitation_accepter_id |
Accepter id, or null when accept_invitation = false. |
- ARN format.
arn:<partition>:detective:<region>:<account-id>:graph:<graph-id>— this is the cross-resource reference type, surfaced as botharnandgraph_arn. The plainidon every Detective resource in this family is either the same value as the graph identifier or a synthetic composite (e.g.aws_detective_member's import id is<graph_arn>/<account_id>) — preferarn/graph_arn/effective_graph_arnfor wiring. - Force-new fields. None of the five resources document a force-new argument beyond the identity fields themselves (
account_idon member/org-admin resources,graph_arnon the accepter/org-configuration) — these are effectively immutable in practice because changing them changes which graph or account the resource refers to, not a mutable property of the same resource. tags↔tags_all↔default_tags. Onlyaws_detective_graphis taggable.tags_allis the provider-computed merge of resource tags over providerdefault_tags, with resource tags winning on key conflict.default_tagsremains the caller's provider-block concern — never set here.- Eventual consistency / ordering.
aws_detective_organization_configurationmust run from an account that is already the delegated administrator — applying it beforeaws_detective_organization_admin_accountexists fails at the API level, not at plan time. The module encodesorganization_admin_account -> organization_configurationviadepends_onfor the single-call case; in the common two-account rollout, apply the admin-account registration in one apply, then the org configuration in a subsequent apply from the delegated administrator's provider. - Destroy behavior.
aws_detective_organization_configurationhas no meaningful destroy/import behavior — Terraform assumes management of it automatically without import and performs no action on removal from configuration; removing it from state does not revert auto-enrollment on the AWS side. Destroying the graph is irreversible from an investigation standpoint (the graph and its accumulated context are deleted). - us-east-1 globals. N/A — Detective is a regional service with no global-resource coupling. There is no
regionvariable; the graph lives in the inherited provider's Region. For multi-Region coverage, use one module instance per Region via provider aliases (example 12).
Secure-by-default posture and every opt-out, explicitly:
| Posture | Default | Opt-out |
|---|---|---|
| Organization-wide auto-enrollment | organization_configuration = null — no auto-enrollment; accounts invited individually via members |
Set organization_configuration = { auto_enable = true } for automatic, continuous coverage of new org accounts (recommended for the PII/privacy-regulation baseline) — trades away Terraform-as-single-source-of-truth for membership; see example 6/7 |
| Member invitation email | disable_email_notification = false — the invited account's root user IS notified |
Set true per-member to suppress the email (the Personal Health Dashboard alert still fires) |
| Tagging | var.tags applied to the graph by default |
n/a — Detective has no encryption/public-access toggle to opt out of; the graph is always tagged when created |
Other principles:
- One composite, one keystone — made toggleable. The graph is the keystone, but it is rendered behind a guarded
for_each(create_graph) so the same module also serves a member account's accepter-only invocation, mirroringtf-mod-aws-inspector2's individually-toggleable-switches philosophy. - GuardDuty and AWS Organizations are deliberately out of scope. Detective consumes GuardDuty's findings at the data-plane level (not via Terraform wiring) and accepts raw account-id strings for organization inputs rather than depending on the not-yet-authored
tf-mod-aws-organizationsPhase-7 dependency chain. for_each, nevercount, for the members collection — keyed by stable caller strings so reorders don't churn the plan.- Primary outputs
id+arn(plus theeffective_graph_arnnon-null convenience alias) and per-member id/status maps.
# Validate without backend or credentials
terraform init -backend=false
terraform validate
terraform fmt -check
plan/applyrequire valid AWS credentials (profile / SSO / OIDC) resolved through the standard provider chain, a configured Region, and the IAM actions listed above. Cross-account examples (member acceptance, delegated admin, org configuration) require the correct provider alias targeting each account.
⚠️ Always pin the module source with?ref=v1.0.0— never a branch.
terraform init -backend=false && terraform validate— schema + reference integrity.terraform fmt -check— canonical formatting.terraform planagainst a sandbox account to confirm the graph and any members/org resources/accepter materialize in the correct order.- Assert
module.detective.id,arn,tags_all, and (if configured)member_statuses/organization_admin_account_idin your root-module test harness. - After apply, confirm in the console/CLI (
aws detective list-graphs) that the graph exists and, for members, thatstatusprogresses fromINVITEDtoENABLEDonce the member account applies its accepter.
module.detective.aws_detective_graph.this["this"]: Creation complete after 4s [id=arn:aws:detective:us-east-1:123456789012:graph:231684d34gh74g4bae1dbc7bd807d02d]
module.detective.aws_detective_member.this["audit"]: Creation complete after 2s
Outputs:
id = "arn:aws:detective:us-east-1:123456789012:graph:231684d34gh74g4bae1dbc7bd807d02d"
arn = "arn:aws:detective:us-east-1:123456789012:graph:231684d34gh74g4bae1dbc7bd807d02d"
effective_graph_arn = "arn:aws:detective:us-east-1:123456789012:graph:231684d34gh74g4bae1dbc7bd807d02d"
member_statuses = {
"audit" = "INVITED"
}
| Symptom | Likely cause | Fix |
|---|---|---|
ConflictException: You already have a behavior graph |
A graph already exists in this account+Region (hard limit of 1) | Import the existing graph (example 11), or manage Detective from a single module instance per Region |
ValidationException on aws_detective_organization_configuration |
Applied before aws_detective_organization_admin_account exists, or from the wrong account |
Register the delegated administrator first (management account), then apply the org configuration from the delegated administrator account |
Member stays INVITED / VERIFICATION_IN_PROGRESS, never ENABLED |
Member account hasn't run accept_invitation = true, or ran it from the wrong account/provider |
Apply this module with create_graph = false, accept_invitation = true, graph_arn = <administrator arn> from the member account's own provider |
AccessDeniedException creating the graph |
Terraform identity lacks detective:CreateGraph |
Grant the graph lifecycle actions listed above |
graph_arn is required when create_graph = false... validation error |
Attempted to set members, organization_configuration, or accept_invitation = true with create_graph = false and no graph_arn |
Supply graph_arn pointing at the administrator's graph |
| Tag drift on every plan | A tag also set by provider default_tags with a different value, applied to the graph |
Let resource tags win, or remove the overlap from default_tags |
| Organization admin-account registration fails | Detective trusted access (aws_service_access_principals) not yet enabled for the Organization |
Enable detective.amazonaws.com trusted access at the Organizations level before applying this module's org-admin resource |
terraform destroy on organization_configuration doesn't seem to change anything on the AWS side |
Documented provider behavior — this resource takes no action on removal from configuration | Expected; if you need to actually disable auto-enrollment, do so via the AWS console/API directly, then remove the resource from Terraform state |
- Amazon Detective quotas
- Managing multiple accounts in Detective (Organizations)
- Detective and GuardDuty
- Terraform:
aws_detective_graph·aws_detective_member·aws_detective_invitation_accepter·aws_detective_organization_admin_account·aws_detective_organization_configuration - Sibling modules:
tf-mod-aws-guardduty,tf-mod-aws-security-hub,tf-mod-aws-organizations,tf-mod-aws-eventbridge - Module internals:
SCOPE.md
🧡 "Infrastructure as Code should be standardized, consistent, and secure."