Composite serverless orchestration module for AWS Step Functions. It owns a state machine (STANDARD or EXPRESS) and its optional companion activities — so a single module call yields a versioned, logged, X-Ray-traced workflow definition wired to an externally-supplied execution role, aligned with Casey's secure-by-default posture for regulated NPI/GLBA workloads.
- Module type: Composite
- Primary resource (keystone):
aws_sfn_state_machine.this
The module manages the following (allow-list):
aws_sfn_state_machine— keystone; STANDARD or EXPRESS workflow, ASLdefinition,logging_configuration,tracing_configuration,encryption_configuration, optionalpublish(versioning)aws_sfn_activity— child collection viafor_eachovermap(object(...))); long-polled workers forTaskstates ofType = "Task"withResource = "arn:aws:states:::activity:..."(STANDARD workflows only — see Provider gotchas)
Referenced by arn/id, never created here:
- State machine execution role —
aws_iam_role(fromterraform-aws-iam-role), supplied asrole_arn. This module never creates or modifies IAM roles/policies; it only passes the role ARN toaws_sfn_state_machine.role_arn. The policy attached to that role determining what the ASL definition may actually invoke (Lambda, ECS, SNS, DynamoDB, etc.) is entirely out of scope and must be authored by the caller viaterraform-aws-iam-role/terraform-aws-iam-policy— see the Required IAM permissions section below for why this matters. - CloudWatch Log Group —
aws_cloudwatch_log_group(fromterraform-aws-cloudwatch-log-group), supplied aslog_destination_arnforlogging_configuration.log_destination. This module does not create or manage log group retention/encryption. - Customer-managed KMS key —
aws_kms_key(fromterraform-aws-kms), supplied askms_key_idforencryption_configuration. Defaults toAWS_OWNED_KEY(no CMK) when not supplied. - Downstream integration targets — Lambda functions, ECS task
definitions, SNS topics, SQS queues, DynamoDB tables, etc. referenced by
ARN inside the ASL
definitionstring the caller supplies. This module treatsdefinitionas an opaque string; it does not parse or validate ASL service integrations.
| Input | Type | Source module |
|---|---|---|
role_arn |
string (required) |
terraform-aws-iam-role |
log_destination_arn |
string (optional, but required in practice — see Secure-by-default) |
terraform-aws-cloudwatch-log-group |
kms_key_arn |
string (optional) |
terraform-aws-kms |
Least-privilege actions the Terraform executor identity (the CI/CD
pipeline role or human operator applying this module) needs. This is
distinct from — and must not be confused with — the state machine
execution role (role_arn), which is a separate runtime identity assumed
by the Step Functions service itself.
| Action | Required for |
|---|---|
states:CreateStateMachine, states:DescribeStateMachine, states:UpdateStateMachine, states:DeleteStateMachine, states:ListStateMachines |
State machine lifecycle |
states:CreateActivity, states:DescribeActivity, states:DeleteActivity, states:ListActivities |
Activity lifecycle |
states:TagResource, states:UntagResource, states:ListTagsForResource |
Tagging both resource types |
states:PublishStateMachineVersion |
Only when publish = true |
iam:PassRole (scoped to the specific role_arn supplied, ideally via a StringEquals condition on iam:PassedToService = "states.amazonaws.com") |
MANDATORY. aws_sfn_state_machine.this cannot be created or updated without passing an execution role to the Step Functions service. Terraform's executing identity must hold iam:PassRole on that exact role ARN, or states:CreateStateMachine fails with an AccessDenied error even though every states:* permission above is granted. |
kms:DescribeKey |
Only when a customer-managed kms_key_id is supplied to encryption_configuration (validates the key exists/is enabled) |
⚠️ iam:PassRoleis the single most common cause of first-apply failure for this module. Grant it narrowly — scope the resource to the specific execution-role ARN(s) this module is expected to pass, neverResource: "*", and add theiam:PassedToServicecondition so the permission cannot be reused to pass the role to an unrelated service.
This is the permissions gotcha that catches new module consumers. The
IAM actions above only cover what Terraform needs to create the state
machine resource. They say nothing about what the state machine can do
once it runs. At execution time, Step Functions assumes role_arn and uses
that role's own permissions — not the Terraform executor's — to carry
out every Task state in the ASL definition. If the definition invokes
Lambda, runs an ECS task, publishes to SNS, writes to DynamoDB, starts a
nested state machine, etc., the execution role must separately hold the
matching runtime permissions (e.g. lambda:InvokeFunction,
ecs:RunTask + a second, distinct iam:PassRole for the ECS task role,
sns:Publish, dynamodb:PutItem, states:StartExecution for nested
workflows). This module has no visibility into the ASL definition string
— it is treated as an opaque value — so it cannot scope, validate, or even
warn about missing execution-role permissions. This is the same class of
gotcha as CodePipeline/CloudFormation service roles: the orchestrator's
IAM role must be provisioned with the union of every downstream action its
pipeline/stack/state-machine steps will call, and that provisioning happens
entirely in terraform-aws-iam-role / terraform-aws-iam-policy, outside this
module's blast radius. Document the required execution-role policy
alongside the ASL definition at the call site, not in this module.
-
No service-linked role required for Step Functions state machines or activities.
-
STANDARD vs EXPRESS is an immutable, foundational choice (
typecannot be changed after creation — a type change is force-new). The two types have materially different execution, logging, and pricing models:STANDARD (default) EXPRESS Max execution duration Up to 1 year Up to 5 minutes Execution semantics Exactly-once Asynchronous: at-least-once; Synchronous: at-most-once Execution history Full history via API/console for 90 days post-completion No API/console history; must enable logging_configurationto CloudWatch Logs to retain any execution recordPricing Per state transition Per execution count + duration + memory consumed aws_sfn_activity(long-polled workers)Supported Not supported — do not pair activities with an EXPRESS state machine Distributed Map state Supported Not supported .sync/.waitForTaskTokenservice integration patternsSupported Not supported Best fit Long-running, auditable, non-idempotent workflows (payment processing, human-approval steps, EMR orchestration) High-volume, short-duration, idempotent workloads (IoT ingestion, streaming transforms, synchronous API-backed workflows) Choose
typedeliberately at design time; there is no in-place migration. -
Logging requires a resource policy grant on the log group, not just
logging_configuration. The CloudWatch Log Group (or its resource policy) must allow the Step Functions service principal (states.amazonaws.com) to write to it, andlog_destinationmust be the log group ARN suffixed with:*. This module appends:*automatically; the log group's resource policy is provisioned byterraform-aws-cloudwatch-log-groupor a standaloneaws_cloudwatch_log_resource_policy, not by this module. -
X-Ray tracing requires the execution role to hold the AWS managed policy (or equivalent) for X-Ray write access (
xray:PutTraceSegments,xray:PutTelemetryRecords,xray:GetSamplingRules,xray:GetSamplingTargets) — another instance of the transitive-permissions gotcha above, sincetracing_configuration.enabled = truealone does not grant the execution role anything. -
Quotas: default execution start-rate and state-transition-rate quotas are Region-specific and adjustable via Service Quotas; high-throughput EXPRESS workloads should confirm headroom before go-live. Activities are capped per-account/Region (soft limit, raisable).
-
Encryption:
encryption_configuration.type = "AWS_OWNED_KEY"(no CMK) is the provider default; supplyingkms_key_idswitches toCUSTOMER_MANAGED_KMS_KEY. The CMK's key policy must separately grant the execution role (and the Step Functions service principal) permission to use the key, or executions fail at runtime with a KMS access-denied error.
| Output | Description | Consumed by |
|---|---|---|
id |
State machine ARN (Step Functions uses the ARN as the resource id) | Modules/callers that reference this state machine by id |
arn |
State machine ARN (arn:aws:states:<region>:<account>:stateMachine:<name>) — cross-resource reference type |
terraform-aws-eventbridge (rule target), terraform-aws-iam-policy (states:StartExecution resource), API Gateway / Lambda callers that invoke this workflow |
name |
State machine name | Tagging, monitoring, dashboards keyed on name |
state_machine_version_arn |
Version-qualified ARN (only set when publish = true) |
Callers pinning an execution to an immutable published version |
status |
Current status (ACTIVE or DELETING) |
Health checks / drift detection |
creation_date |
Creation timestamp | Audit |
revision_id |
Revision id of the definition/role_arn/configuration, incremented on every update | Optimistic-concurrency checks against out-of-band changes |
activity_ids / activity_arns / activity_names |
Maps of activity id/arn/name keyed by the caller's stable key | Worker processes that long-poll GetActivityTask; the caller's own ASL definition (Task-state Resource) |
tags_all |
All tags incl. provider default_tags |
Governance/audit |
ℹ️ The provider schema also exposes
descriptionandversion_descriptionas computed-only attributes onaws_sfn_state_machine, but neither is documented in the currenthashicorp/awsresource reference or the AWS Step Functions API (CreateStateMachine/DescribeStateMachine) as a populated field. This module deliberately does not surface them as outputs until their behavior is officially documented.
typeis FORCE-NEW. ChangingSTANDARD↔EXPRESSdestroys and recreates the state machine (and its execution history). Decide the type at design time.name/name_prefixare FORCE-NEW (name changes recreate the resource, same as most named AWS resources without in-place rename).nameandname_prefixare mutually exclusive.definitionis a live plan diff, not a hidden hash. Because ASL is supplied as a raw JSON string, whitespace/key-ordering changes can produce noisy diffs; preferjsonencode()on a structuredlocalin the caller to keep plans deterministic. This module passesdefinitionthrough verbatim.log_destinationmust end in:*. The provider does not append this automatically for you when composing the ARN from a plain log group ARN; the module handles the suffix internally so callers supply the bare log group ARN.logging_configurationandtracing_configurationrequire execution-role permissions to function — see the Transitive-permissions gotcha above. Enabling them here is necessary but not sufficient.idandarnare the same value for bothaws_sfn_state_machineandaws_sfn_activity— Step Functions has no separate short-form id; the ARN is the id. Do not expect a bare resource id distinct from the ARN.- Activities are STANDARD-only. Pairing
aws_sfn_activityresources with anEXPRESSstate machine produces a state machine that references an activity ARN the EXPRESS runtime cannot invoke — this fails at execution time, not at plan/apply time, so it will not surface as a Terraform error. The module does not hard-block this combination (Terraform cannot inspect the ASLdefinitionstring to know which activities are referenced), but the README and variable descriptions call it out prominently. encryption_configuration.typedocumentation discrepancy (discovered during authoring). Theaws_sfn_activityresource page in thehashicorp/awsTerraform Registry documentation listsAWS_KMS_KEYas the non-CMKencryption_configuration.typevalue, but the authoritative AWS APIEncryptionConfigurationtype — shared by bothCreateStateMachineandCreateActivity— documentsAWS_OWNED_KEYas the only valid non-CMK value, matchingaws_sfn_state_machine. Since the provider schema typesencryption_configuration.typeas a plain, unvalidated string (no enum enforcement at the Terraform layer), this module rendersAWS_OWNED_KEYconsistently for both the state machine and every activity to match the authoritative AWS API contract.tagsvstags_all.var.tagsflows toaws_sfn_state_machine.thisand everyaws_sfn_activitychild;tags_allon each resource is the computed merge of resource tags over providerdefault_tags(resource tags win).default_tagsis the caller's provider-block concern.- Destroy ordering. State machines and activities have no ENI/NAT-style
destroy-ordering hazard, but a state machine with in-flight executions
can still be deleted (Step Functions stops executions asynchronously on
delete) — there is no
force_delete/drain flag, so in-flight work is simply abandoned. Coordinate destroys with running workloads out of band. - Publishing (
publish = true) creates an immutable version on every apply that changesdefinition,role_arn, or the config blocks — version count grows unbounded over the resource's lifetime with no built-in pruning; consider a lifecycle policy or manual cleanup if versioning is heavily used.
| Posture | Default | Opt-out |
|---|---|---|
CloudWatch Logs (logging_configuration) |
Enabled — level = "ALL", include_execution_data = true, requires log_destination_arn |
enable_logging = false (requires a documented exception — regulated workloads should retain execution history) |
X-Ray tracing (tracing_configuration) |
Enabled (enabled = true) |
enable_tracing = false |
| Encryption at rest | AWS_OWNED_KEY (AWS-managed, always on — Step Functions encrypts state data at rest unconditionally) |
Supply kms_key_arn to upgrade to CUSTOMER_MANAGED_KMS_KEY; there is no way to fully disable encryption (not a real opt-out, by design) |
include_execution_data |
true by default so execution history is auditable |
Set logging_include_execution_data = false when the ASL definition's input/output payloads may carry NPI that should not land in CloudWatch Logs — recommended for any workflow touching member/borrower data |
| Log level | "ALL" |
logging_level variable (ALL | ERROR | FATAL | OFF, closed enum) |
| Type | STANDARD (auditable, exactly-once — the safer default for regulated workflows) |
type = "EXPRESS" for high-volume/idempotent workloads that explicitly accept at-least-once semantics |
ℹ️ NPI callout: because
include_execution_data = truelogs full state input/output to CloudWatch Logs, any state machine whose ASL definition passes member/borrower NPI through state input/output should either setlogging_include_execution_data = falseor ensure the target log group is access-controlled and retention-managed to the same standard as any other NPI-bearing log stream. This module defaults totruefor auditability; flip it per Casey's data-classification policy for the specific workflow.
- The module wraps exactly one state machine plus its optional activities so
a single call yields a complete, logged, traced workflow definition — the
same "one call, fully wired" ergonomic as
terraform-aws-vpc. - The execution role is deliberately out of scope and required as an
input (
role_arn), keeping IAM authoring (and the transitive-permissions problem) centralized interraform-aws-iam-rolerather than duplicated or hidden inside every module that needs an execution role. definitionis accepted as an opaque string (not parsed/typed) because ASL is a rich, evolving JSON DSL with nestedTask/Map/Parallel/Choicestates — modeling it as a Terraformobject()schema would be brittle and would lag new state types. Callers own the ASL, typically viajsonencode()or atemplatefile()in the calling root module.aws_sfn_activity— the one first-class ASL dependency this module can model structurally — is exposed as a properfor_eachchild collection so activity ARNs can be referenced back into the caller'sdefinitionbefore the state machine is created.- Activities are modeled as a child collection (
for_eachovermap(object({...}))) rather than a single resource, since a workflow may poll multiple long-running activity workers, each independently named and tagged. log_destination_arnandkms_key_arnare accepted as plain ARNs (not full object blocks) since the only per-call decision is which log group / key to use — the log group's own retention, encryption, and resource policy areterraform-aws-cloudwatch-log-group's andterraform-aws-kms's concerns, not this module's.