Composite AWS X-Ray tracing module — trace groups, sampling rules, and the account/Region encryption configuration, all from one composite call, defaulting to a KMS-encrypted trace posture instead of AWS's own out-of-the-box
NONE. Built for the AWS provider v6.x.
- 🗂️ Provisions
aws_xray_grouptrace groups — each a named filter expression that segments traces into its own service graph, trace summaries, and CloudWatch metrics, with optional Insights (anomaly detection). - 🎯 Provisions
aws_xray_sampling_ruleentries that tune which requests the X-Ray SDK instruments once the built-in default sampling (first request/sec + 5% of the rest) needs per-service/route control. - 🔐 Owns the account/Region
aws_xray_encryption_configsingleton — and defaults it totype = "KMS"rather than leaving a fresh account at AWS's ownNONEdefault. - 🧬 Groups and sampling rules are
for_eachcollections keyed by a caller-supplied stable string — nocount, no scalar keystone identity. ⚠️ Encryption config is modeled as a 0/1 toggle, never a collection — the AWS API has exactly one live value per account per Region, somanage_encryption_config(bool) decides whether this module instance owns it.- 🏷️ Universal tagging on groups and sampling rules only —
aws_xray_encryption_confighas no tagging API at all; it is deliberately excluded fromvar.tagsand from thetags_alloutput. - 🧱 No VPC/networking footprint — X-Ray's group/sampling-rule/encryption-config APIs are pure regional control-plane objects.
💡 Why it matters: A fresh AWS account ships X-Ray encryption at
NONEand zero custom sampling rules. One module call establishes a KMS-encrypted, cost-aware, auditable tracing posture — the regulated-industry-aligned default rather than an opt-in afterthought.
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!
terraform-aws-xray consumes only a KMS key ARN (optional) for the encryption-config singleton. It has no direct Terraform-level dependency on VPC, IAM, or compute modules — the workloads that actually emit trace segments (Lambda, ECS, EKS) call the X-Ray data-plane API out-of-band, guided by IAM policies that reference this module's group/rule ARNs.
flowchart LR
kms["terraform-aws-kms"]
iamrole["terraform-aws-iam-role"]
iampolicy["terraform-aws-iam-policy"]
xray["terraform-aws-xray"]
lambda["terraform-aws-lambda"]
ecs["terraform-aws-ecs-service"]
eks["terraform-aws-eks"]
cwlg["terraform-aws-cloudwatch-log-group"]
cwalarm["terraform-aws-cloudwatch-alarm"]
sns["terraform-aws-sns"]
kms -. "kms_key_arn (encryption_config CMK)".-> xray
xray -- "group_arns / sampling_rule_arns" --> iampolicy
iampolicy -- "xray:PutTraceSegments / GetSamplingRules statements" --> iamrole
iamrole -- "execution / instance role" --> lambda
iamrole -- "task role" --> ecs
iamrole -- "pod / IRSA role" --> eks
lambda -. "trace segments (data plane, not Terraform)".-> xray
ecs -. "trace segments (data plane, not Terraform)".-> xray
eks -. "trace segments (data plane, not Terraform)".-> xray
xray -. "service-graph metrics".-> cwlg
xray -. "anomaly alarms".-> cwalarm
xray -. "insights notifications (EventBridge/Chatbot, out of scope)".-> sns
style xray fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
ℹ️ Dotted arrows are by-reference / out-of-band relationships (a KMS ARN wired in, or a data-plane API call that Terraform never sees) — not resources this module creates. This module's only real Terraform-level
Consumesinput iskms_key_arn.
Unlike most composites in this library, there is no single scalar keystone. The primary resource (aws_xray_group.this) is itself a for_each collection, exactly like the sibling sampling-rule collection. The only true singleton is the encryption configuration — rendered via a 0/1 for_each toggle, never a caller-keyed map, because the AWS API models it as one value per account per Region.
flowchart TB
subgraph XRAYMOD["terraform-aws-xray"]
direction TB
subgraph GROUPS["Groups — keystone collection (for_each map(object), 0..N)"]
g1["aws_xray_group.this[key]<br/>id == arn (same value)<br/>tags / tags_all supported"]
end
subgraph RULES["Sampling rules — child collection (for_each map(object), 0..N)"]
r1["aws_xray_sampling_rule.this[key]<br/>id = rule_name (NOT arn)<br/>tags / tags_all supported"]
end
subgraph SINGLETON["ACCOUNT / REGION SINGLETON -- 0/1 toggle via manage_encryption_config, NEVER a caller-keyed collection"]
enc{{"aws_xray_encryption_config.this['this']<br/>id = Region name ONLY<br/>NO arn - NO tags - NO tags_all"}}
end
end
g1 -.->|"group_ids / group_arns / group_names"| OUT(("module outputs"))
r1 -.->|"sampling_rule_ids / sampling_rule_arns"| OUT
enc -.->|"encryption_config_id / encryption_config_type"| OUT
style GROUPS fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
style RULES fill:#2E7D32,color:#fff,stroke:#1b5e20,stroke-width:2px
style SINGLETON fill:#455A64,color:#fff,stroke:#eceff1,stroke-width:2px,stroke-dasharray: 6 4
style enc fill:#607D8B,color:#fff,stroke:#eceff1,stroke-width:2px
| Resource | Role | Cardinality | Tags? |
|---|---|---|---|
aws_xray_group.this |
Keystone — trace group per filter expression | per groups entry (0..N) |
✅ |
aws_xray_sampling_rule.this |
Child collection — per-service sampling tuning | per sampling_rules entry (0..N) |
✅ |
aws_xray_encryption_config.this |
Account/Region singleton — 0/1 toggle via manage_encryption_config |
0 or 1 | ❌ (no tagging API) |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
No provider {} block is declared inside the module — the caller's configured provider (and its Region/credentials) is inherited. No region variable is exposed; X-Ray has no us-east-1 global-service constraint (unlike CloudFront/WAFv2/ACM).
Least-privilege actions the Terraform identity needs to create, read, update, and delete everything this module manages:
| Action | Required for | Notes |
|---|---|---|
xray:CreateGroup, xray:UpdateGroup, xray:DeleteGroup, xray:GetGroup, xray:GetGroups |
aws_xray_group lifecycle |
— |
xray:CreateSamplingRule, xray:UpdateSamplingRule, xray:DeleteSamplingRule, xray:GetSamplingRules |
aws_xray_sampling_rule lifecycle |
— |
xray:PutEncryptionConfig, xray:GetEncryptionConfig |
aws_xray_encryption_config (account/Region singleton) |
Only exercised when manage_encryption_config = true |
xray:TagResource, xray:UntagResource, xray:ListTagsForResource |
Tagging groups and sampling rules | Encryption config has no tagging API — these actions never apply to it |
kms:DescribeKey, kms:CreateGrant |
Customer-managed KMS key supplied via encryption_config.kms_key_arn |
The CMK's own key policy must separately allow the xray.amazonaws.com service principal to use it |
ℹ️ The module itself creates no IAM role and no service-linked role.
iam:PassRoleis not required — X-Ray does not assume a service role for group/sampling-rule/encryption-config management.
- No service-linked role is required for
aws_xray_group,aws_xray_sampling_rule, oraws_xray_encryption_config. - Account/Region singleton:
aws_xray_encryption_confighas exactly one live value per account per Region. If more than one Terraform configuration (or a manual console/CLI change) manages it for the same account/Region, the last write wins and state drifts. Only one instance of this module should setmanage_encryption_config = trueper account/Region. - KMS key policy: when
encryption_config.type = "KMS"with a customer-managed key, the key's policy must grant the calling principal and X-Ray itselfkms:Decrypt,kms:GenerateDataKey, andkms:DescribeKey— otherwise trace ingestion fails silently at the API layer, not atterraform apply. - No asymmetric KMS keys. Per AWS documentation, X-Ray does not support asymmetric KMS keys for trace encryption — the supplied CMK must be a symmetric key.
- Region constraint: none beyond standard regional availability — X-Ray is available in all commercial Regions where the provider is configured. No
us-east-1global-service requirement. - Quotas (soft unless noted): 500 groups per account · 2,000 sampling rules per account · 1 encryption configuration per account per Region (hard, by design — the AWS API itself has no concept of more than one).
- Sampling rule
resource_arnwildcard: most callers setresource_arn = "*"(match all resources) unless deliberately narrowing — this is an AWS modeling choice the module passes through verbatim, not an restriction.
terraform-aws-xray/
├── providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider block
├── variables.tf # groups, sampling_rules, manage_encryption_config, encryption_config, tags, timeouts
├── main.tf # aws_xray_group.this / aws_xray_sampling_rule.this / aws_xray_encryption_config.this (0/1 toggle)
├── outputs.tf # keyed maps — no scalar id/arn; group_*, sampling_rule_*, encryption_config_*, tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM, prerequisites, emits, gotchas
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"checkout-slow" = {
filter_expression = "responsetime > 5"
}
}
sampling_rules = {
"checkout-api" = {
resource_arn = "*"
priority = 100
fixed_rate = 0.10
reservoir_size = 5
service_name = "checkout"
service_type = "*"
host = "*"
http_method = "*"
url_path = "/checkout/*"
}
}
# manage_encryption_config defaults to true — a fresh account no longer
# silently ships with X-Ray encryption at AWS's own out-of-the-box "NONE".
encryption_config = {
kms_key_arn = module.kms.arn # terraform-aws-kms
}
tags = {
Environment = "prod"
CostCenter = "infra"
}
}
⚠️ Pin the source with?ref=v1.0.0— never a branch. See the Example Library below formanage_encryption_config = falsewhen another module instance already owns the account/Region singleton.
This module has exactly one by-reference sibling input:
| Input | Type | Source module |
|---|---|---|
encryption_config.kms_key_arn |
string (KMS key ARN, optional) |
terraform-aws-kms (arn output) — customer-managed key for aws_xray_encryption_config |
No VPC/subnet/security-group wiring is required — X-Ray is a regional control-plane/API service with no network footprint of its own to manage.
| Output | Description | Consumed by |
|---|---|---|
group_ids |
Map (key → id) of X-Ray group ARNs — aws_xray_group.id is the group ARN |
Reference / drift audit |
group_arns |
Map (key → arn) of X-Ray group ARNs (identical value to group_ids) |
IAM policy Resource elements scoping xray:PutTraceSegments to a group |
group_names |
Map (key → group_name, equal to the input map key) |
Dashboards, CLI/SDK trace filtering, CloudWatch metric filters |
sampling_rule_ids |
Map (key → id, the rule_name) |
Reference / drift audit |
sampling_rule_arns |
Map (key → arn) | IAM conditions, cross-account sharing |
encryption_config_id |
The encryption config id (the Region name), or null when manage_encryption_config = false |
Reference / audit only |
encryption_config_type |
The effective encryption type (KMS or NONE), or null when not managed |
Compliance reporting / audit |
tags_all |
Map of "group/<key>" and "sampling_rule/<key>" → tags_all (the only two taggable resource types) |
Governance/audit |
ℹ️ No singular scalar
id/arnoutput exists — the keystone (aws_xray_group.this) is itself a collection, so every output is a map keyed by the caller-supplied stable string used on input.
1 · Minimal — single trace group
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"all-traces" = {
filter_expression = "" # empty string matches every trace
}
}
}2 · Trace group with Insights (anomaly detection) enabled
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"checkout-slow" = {
filter_expression = "responsetime > 5"
insights_configuration = {
insights_enabled = true
notifications_enabled = true # EventBridge events on anomaly detection
}
}
}
}3 · Sampling rule — narrow a single service's rate
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
sampling_rules = {
"checkout-api" = {
resource_arn = "*"
priority = 100
fixed_rate = 0.10
reservoir_size = 5
service_name = "checkout"
service_type = "*"
host = "*"
http_method = "*"
url_path = "/checkout/*"
attributes = { Environment = "prod" }
}
}
}4 · Multiple sampling rules via for_each — per-route tuning
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
sampling_rules = {
"checkout-api" = {
resource_arn = "*", priority = 100, fixed_rate = 0.10, reservoir_size = 5
service_name = "checkout", service_type = "*", host = "*"
http_method = "*", url_path = "/checkout/*"
}
"payments-api" = {
resource_arn = "*", priority = 90, fixed_rate = 0.25, reservoir_size = 10
service_name = "payments", service_type = "*", host = "*"
http_method = "*", url_path = "/payments/*"
}
"health-checks" = {
resource_arn = "*", priority = 200, fixed_rate = 0.0, reservoir_size = 0
service_name = "*", service_type = "*", host = "*"
http_method = "GET", url_path = "/healthz"
}
}
}5 · Multiple trace groups via for_each
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"all-traces" = { filter_expression = "" }
"5xx-errors" = { filter_expression = "http.response.status >= 500" }
"checkout-slow" = { filter_expression = "responsetime > 5 AND service(\"checkout\")" }
}
}6 · Encryption config wiring a customer-managed KMS key from terraform-aws-kms
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
name = "casey-xray"
# key policy must grant xray.amazonaws.com AND the Terraform/workload principal
# kms:Decrypt, kms:GenerateDataKey, kms:DescribeKey.
}
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
encryption_config = {
type = "KMS"
kms_key_arn = module.kms.arn
}
}7 · Encryption config on AWS-managed key material (no CMK supplied)
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
# type defaults to "KMS"; kms_key_arn defaults to null, which uses
# AWS-managed key material -- still KMS-backed, still an improvement over
# AWS's own out-of-the-box "NONE" default, but not a caller-controlled CMK.
encryption_config = {}
}8 · Tags — merge with provider default_tags
# Caller's provider block owns default_tags; resource tags win on key conflict.
provider "aws" {
region = "us-east-1"
default_tags {
tags = { Owner = "platform", ManagedBy = "terraform" }
}
}
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"checkout-slow" = {
filter_expression = "responsetime > 5"
tags = { Tier = "checkout" } # merged on top of module tags for this group
}
}
tags = {
Environment = "prod"
Owner = "observability-team" # overrides default_tags Owner on groups/sampling rules
}
}
# module.xray.tags_all["group/checkout-slow"] =>
# { Owner = "observability-team", ManagedBy = "terraform", Environment = "prod", Tier = "checkout" }
# NOTE: aws_xray_encryption_config has NO tags_all entry at all -- it is not a taggable resource.9 · Secure-by-default opt-out — type = "NONE" (documented exception)
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"all-traces" = { filter_expression = "" }
}
# Explicit opt-out of the secure baseline -- document the compliance
# exception before merging. Reverts the account/Region encryption posture
# to AWS's own out-of-the-box default.
encryption_config = {
type = "NONE"
}
}10 · Secure-by-default opt-out — manage_encryption_config = false (another instance already owns the singleton)
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"checkout-slow" = { filter_expression = "responsetime > 5" }
}
# A separate Terraform configuration (e.g. a shared-services / landing-zone
# stack) already manages aws_xray_encryption_config for this account/Region.
# Setting this module instance to false avoids last-write-wins drift.
manage_encryption_config = true # <- flip to false in the workload stack
}
# When manage_encryption_config = false:
# encryption_config_id => null
# encryption_config_type => null11 · Import an existing aws_xray_group (import key = group ARN)
import {
to = aws_xray_group.this["checkout-slow"]
id = "arn:aws:xray:us-east-1:123456789012:group/checkout-slow/AbCdEfGh123456"
}12 · Import an existing aws_xray_sampling_rule (import key = rule name, NOT the ARN)
import {
to = aws_xray_sampling_rule.this["checkout-api"]
id = "checkout-api" # the rule_name -- the sampling rule's id, not its arn
}13 · Import the existing aws_xray_encryption_config singleton (import key = Region name)
import {
to = aws_xray_encryption_config.this["this"]
id = "us-east-1" # the Region name -- this resource's only exported attribute
}ℹ️ Three different import key shapes in one module: group ARN, sampling-rule name, Region name. Get the wrong one and Terraform reports "resource not found" against the wrong lookup.
14 · Insights notifications without enabling encryption management (composability)
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"payments-anomalies" = {
filter_expression = "service(\"payments\")"
insights_configuration = {
insights_enabled = true
notifications_enabled = true
}
}
}
manage_encryption_config = false # owned by the landing-zone stack
}15 · End-to-end composition — groups + sampling rules + KMS-backed encryption (finale)
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
name = "casey-xray-traces"
# Key policy grants xray.amazonaws.com and the observability team's role
# kms:Decrypt, kms:GenerateDataKey, kms:DescribeKey.
}
module "xray" {
source = "git::https://github.com/microsoftexpert/terraform-aws-xray?ref=v1.0.0"
groups = {
"checkout-slow" = {
filter_expression = "responsetime > 5"
insights_configuration = {
insights_enabled = true
notifications_enabled = true
}
}
"5xx-errors" = {
filter_expression = "http.response.status >= 500"
}
}
sampling_rules = {
"checkout-api" = {
resource_arn = "*"
priority = 100
fixed_rate = 0.10
reservoir_size = 5
service_name = "checkout"
service_type = "*"
host = "*"
http_method = "*"
url_path = "/checkout/*"
}
"payments-api" = {
resource_arn = "*"
priority = 90
fixed_rate = 0.25
reservoir_size = 10
service_name = "payments"
service_type = "*"
host = "*"
http_method = "*"
url_path = "/payments/*"
}
}
encryption_config = {
type = "KMS"
kms_key_arn = module.kms.arn
}
tags = {
Environment = "prod"
App = "lending-portal"
}
}
# Scope IAM policy Resource elements to this module's own group/rule ARNs.
data "aws_iam_policy_document" "xray_write" {
statement {
actions = ["xray:PutTraceSegments", "xray:PutTelemetryRecords"]
resources = ["*"] # PutTraceSegments has no resource-level permission support
}
statement {
actions = ["xray:GetSamplingRules", "xray:GetSamplingTargets"]
resources = values(module.xray.sampling_rule_arns)
}
}
module "app_policy" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-policy?ref=v1.0.0"
name = "casey-lending-portal-xray"
policy = data.aws_iam_policy_document.xray_write.json
}
module "app_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
name = "casey-lending-portal"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
managed_policy_arns = [module.app_policy.arn]
}ℹ️ High-level grouping:
- Groups (keystone collection):
groups—map(object({ filter_expression, insights_configuration?, tags? })), default{} - Sampling rules (child collection):
sampling_rules—map(object({ resource_arn, priority, fixed_rate, reservoir_size, service_name, service_type, host, http_method, url_path, attributes?, tags? })), default{} - Encryption config (singleton toggle):
manage_encryption_config(bool, defaulttrue),encryption_config—object({ type = optional("KMS"), kms_key_arn = optional(null) }) - Universal:
tags(flows to groups and sampling rules only),timeouts(declared for interface consistency; not wired to any resource — none of the three resource types expose a configurabletimeoutsblock in the provider schema)
- Groups:
group_ids(==group_arns, sinceidIS the ARN),group_arns,group_names - Sampling rules:
sampling_rule_ids(therule_name, not an ARN),sampling_rule_arns - Encryption config (singleton, conditionally present):
encryption_config_id—try(..., null),nullwhenmanage_encryption_config = false;encryption_config_type— same conditional shape - Tags:
tags_all— map keyed"group/<key>"/"sampling_rule/<key>";aws_xray_encryption_confighas notags_allentry at all
ℹ️ No outputs are marked
sensitive— this module emits no secrets. No scalarid/arnoutput exists (see Cross-Module Contract).
aws_xray_group.idIS the group ARN. Unlike almost every other AWS resource in this library, the group'sidattribute is the full ARN string (arn:aws:xray:<region>:<account>:group/<name>/<hash>), not a short identifier.group_idsandgroup_arnsare both surfaced per the primary-outputs convention, but they carry an identical value — do not expectgroup_idsto be a bare name.aws_xray_sampling_rule.idis therule_name— the inverse relationship. Itsarnattribute is a separate, full ARN (arn:aws:xray:<region>:<account>:sampling-rule/<rule_name>). This is the mirror image of the group'sid/arnbehavior — wiresampling_rule_idswhere you need the bare name (e.g. AWS CLI--rule-name) andsampling_rule_arnswhere you need the ARN (IAM conditions, cross-account sharing).aws_xray_encryption_confighas noarn, notags, notags_allat all. Its only exported attribute isid— the Region name (e.g."us-east-1"), confirmed against the livehashicorp/awsv6.53.0 provider schema. There is noTagResource-style API for this setting;var.tagsis never wired into it and it never appears in thetags_alloutput.- Force-new fields:
groupsmap key (group_name) andsampling_rulesmap key (rule_name) are both(Required, force-new)on their respective resources — renaming a map key destroys and recreates that group/rule under a new identity.versionon sampling rules is hardcoded to1(the only format AWS currently supports) rather than exposed as a free-form input. tags↔tags_all↔default_tags:var.tagsflows toaws_xray_groupandaws_xray_sampling_ruleonly, merged with each item's owntags(merge(var.tags, try(each.value.tags, {})), per-item wins).tags_allon each reflects the provider-computed merge withdefault_tags(resource tags win on key conflict).default_tagsremains the caller's provider-block concern and is never set inside the module.aws_xray_encryption_configis excluded from all three — it has no tagging surface whatsoever.- Eventual consistency:
PutEncryptionConfigis asynchronous at the AWS API layer — the setting transitionsUPDATING→ACTIVE; aterraform applyimmediately followed by a trace-ingestion smoke test can race the update. Groups and sampling rules are effectively synchronous by comparison. - No destroy-ordering hazard. All three resources are pure control-plane API objects — no ENI, no NAT, no VPC attachment, no cross-references among
aws_xray_group,aws_xray_sampling_rule, andaws_xray_encryption_config. Destroy order between them is irrelevant. - The account/Region singleton is sticky at the API layer. Per the provider's own documented behavior: "Removing this resource from Terraform has no effect to the encryption configuration within X-Ray." Running
terraform destroy(or removingaws_xray_encryption_configfrom state) does not revert the account/Region toNONEor to a prior key — the last-appliedPutEncryptionConfigcall remains in effect at AWS until something else calls the API again. Do not assume a destroy is a rollback; if you need to actually revert the posture,terraform applya configuration withencryption_config.type = "NONE"first, then remove the resource. - No
us-east-1constraint. X-Ray is a regional service — the module inherits the caller's provider Region and declares noregionvariable, even though the underlying resources support the v6regionargument for multi-Region-from-one-provider patterns (out of scope here; use a provider alias at the call site if needed).
Secure-by-default posture and the explicit opt-out for each:
| Hardened default | Behavior | Opt-out / control |
|---|---|---|
| Encryption config type | "KMS" (customer-managed key when kms_key_arn is supplied; AWS-managed key material under type = "KMS" otherwise) — never left at AWS's own out-of-the-box NONE |
encryption_config.type = "NONE" explicitly (document the exception) |
| Encryption config management | This module instance manages the account/Region singleton by default (manage_encryption_config = true) so a fresh account does not silently ship at NONE |
manage_encryption_config = false — skips the singleton entirely; both encryption outputs become null |
| Insights (anomaly detection) | insights_enabled defaults to false per group (matches AWS's own default; enabling costs additional CloudWatch/X-Ray charges) |
set insights_configuration.insights_enabled = true per group |
| Insights notifications | notifications_enabled defaults to false |
set true only when insights_enabled = true |
| Tags | tags = optional(map(string), {}) on both taggable resources (groups, sampling rules) |
not opt-out — tagging is a governance invariant here, not a security control |
Other principles: the keystone is a for_each collection (not a scalar) because X-Ray has no single "the group" concept; the encryption config is modeled as a bool toggle + nullable object rather than a map(object(...)) because the AWS API itself has exactly one live value per account per Region — a caller-keyed collection would imply a cardinality that doesn't exist; KMS key creation is deliberately excluded (terraform-aws-kms owns CMK lifecycle, this module only consumes an ARN by reference).
terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan # requires valid AWS credentials (profile / SSO / OIDC) + a region
terraform apply
terraform output
⚠️ plan/applyrequire valid AWS credentials and a configured Region (provider block /AWS_PROFILE/ SSO / OIDC). 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— formatting.terraform planagainst a sandbox account — confirm the group/sampling-rule counts match yourgroups/sampling_rulesmaps, and that the encryption-config resource plans to create (or not) based onmanage_encryption_config.- After
apply, confirmencryption_config_typematches what you set, and pollaws xray get-encryption-configuntilStatusreadsACTIVE(it startsUPDATING). - Verify group/sampling-rule ARNs resolve correctly in a downstream IAM policy document before wiring workload roles.
- Because destroying
aws_xray_encryption_configdoes not revert the account posture, a destroy test in a throwaway sandbox account will not show the encryption setting reverting — this is expected, not a bug in the module.
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
group_ids = {
"checkout-slow" = "arn:aws:xray:us-east-1:123456789012:group/checkout-slow/AbCdEfGh123456"
}
group_arns = {
"checkout-slow" = "arn:aws:xray:us-east-1:123456789012:group/checkout-slow/AbCdEfGh123456"
}
group_names = { "checkout-slow" = "checkout-slow" }
sampling_rule_ids = { "checkout-api" = "checkout-api" }
sampling_rule_arns = { "checkout-api" = "arn:aws:xray:us-east-1:123456789012:sampling-rule/checkout-api" }
encryption_config_id = "us-east-1"
encryption_config_type = "KMS"
tags_all = {
"group/checkout-slow" = { "Environment" = "prod", "App" = "lending-portal" }
"sampling_rule/checkout-api" = { "Environment" = "prod", "App" = "lending-portal" }
}
- Tag drift / unexpected tags: Caused by
default_tagsoverlap.tags_allmerges resource tags over providerdefault_tagswith resource tags winning — if a value differs from what you set, adefault_tagsentry is colliding. Rememberaws_xray_encryption_confignever appears intags_allat all — it is not missing a tag, it simply cannot be tagged. - Credential-chain failures (
NoCredentialProviders/ExpiredToken): No valid credentials resolved. SetAWS_PROFILE, refresh SSO, or confirm the OIDC role assumption in CI. The module never takes credentials as variables. terraform destroydidn't revert encryption toNONE: Expected. Per the provider's documented behavior, removingaws_xray_encryption_configfrom Terraform has no effect on the live X-Ray setting — the lastPutEncryptionConfigcall stays in effect. Apply an explicittype = "NONE"configuration first if you actually need to revert.- Two Terraform configurations fighting over
encryption_config(constant plan diffs): Only one module instance should setmanage_encryption_config = trueper account/Region — it is a true API-level singleton with no locking. Set the losing instance'smanage_encryption_config = false. - Trace ingestion silently fails after switching to a customer-managed KMS key: The failure happens at the AWS API layer, not at
terraform apply— check that the CMK's key policy grants both thexray.amazonaws.comservice principal and the ingesting workload's rolekms:Decrypt/kms:GenerateDataKey/kms:DescribeKey. Also confirm the key is symmetric — X-Ray rejects asymmetric KMS keys. IllegalArgumentExceptionimporting a sampling rule: The import key is therule_name(the resource'sid), not its ARN — the reverse of the group import key, which IS the ARN. See Example Library entries 11–13 for all three import key shapes side by side.- Encryption config
Statusstuck atUPDATING:PutEncryptionConfigis asynchronous; pollGetEncryptionConfig(oraws xray get-encryption-config) until it reportsACTIVEbefore assuming the change failed. UnauthorizedOperation/AccessDeniedExceptionon create: An IAM action from the Required IAM Permissions table is missing — commonlyxray:TagResource(forgotten whentagsis non-empty) orkms:CreateGrant(customer-managed KMS path).
- Terraform Registry —
hashicorp/awsprovider:aws_xray_group,aws_xray_sampling_rule,aws_xray_encryption_config - AWS — AWS X-Ray Developer Guide: configuring sampling, groups, and encryption settings with the X-Ray API
- AWS — X-Ray encryption configuration (console and API behavior, KMS key requirements)
- AWS — Using sampling rules with the X-Ray API (reservoir/fixed-rate semantics, default rule behavior)
- —
terraform-aws-kms,terraform-aws-iam-role,terraform-aws-iam-policy,terraform-aws-cloudwatch-log-group,terraform-aws-cloudwatch-alarm(sibling modules)
🧡 "Infrastructure as Code should be standardized, consistent, and secure."