A secure-by-default AWS Payment Cryptography HSM-backed key for card and payment cryptographic workflows — non-exportable by default, TR-31-scoped key usage, friendly aliases. Built for the AWS provider v6.x.
- 🔐 Creates an HSM-backed AWS Payment Cryptography key (
aws_paymentcryptography_key) — a purpose-built key for PCI-scoped payment cryptographic workflows (PIN translation/verification, card verification values, TR-31/TR-34/ECDH key exchange, EMV master-key derivation, MAC generation/verification). - 🚫 Non-exportable by default posture recommended —
exportablehas no baked-in default and must be stated explicitly; the recommended value isfalseso key material never leaves the HSM boundary. - 🎯 Least-privilege
key_modes_of_use— every cryptographic-operation flag (encrypt,decrypt,sign,wrap, etc.) defaults tofalse; the caller enumerates only the operations the key actually needs. - 🏷️ Builds any number of friendly aliases (
aws_paymentcryptography_key_alias) pointing at the same key, keyed by a stable logical name. - 🔍 Emits key metadata only — algorithm, class, usage, state, and key check value (KCV). No key material is ever read back or output, by design and by API limitation.
⚠️ A different service from KMS. This is not a general-purpose encryption key — usetf-mod-aws-kmsfor at-rest encryption; use this module only for payment/card cryptographic key material.
💡 Why it matters: payment cryptography sits at the center of PCI compliance and card-fraud blast radius. This module makes the non-exportable, narrowly-scoped, HSM-bound posture the path of least resistance, so PIN-translation and card-verification keys default to the safest configuration a regulated FI can operate.
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!
flowchart LR
iam["tf-mod-aws-iam-role"]
pc["tf-mod-aws-payment-cryptography"]
policy["Caller IAM policies\n(Resource = key arn)"]
data["Payment Cryptography\nData Plane (app runtime)"]
iam -->|principal arn referenced in policy| policy
policy -->|scoped access to| pc
pc -->|key arn / KeyIdentifier| data
style pc fill:#FF9900,color:#fff
This module is not a foundation module in the usual sense — it consumes nothing from other infrastructure modules. It sits alongside tf-mod-aws-iam-role (whose role ARNs are referenced in caller-authored IAM policies scoped to this key's arn) and is consumed at runtime by application code making AWS Payment Cryptography Data Plane API calls (EncryptData, TranslatePinData, GenerateCardValidationData, etc.) — those calls are outside Terraform's scope entirely.
flowchart TD
subgraph module["tf-mod-aws-payment-cryptography"]
key["aws_paymentcryptography_key.this<br/>(keystone HSM-backed key)"]
alias["aws_paymentcryptography_key_alias.this<br/>(for_each, optional)"]
end
key -->|key_arn| alias
style key fill:#FF9900,color:#fff
| Resource | Role | Created when |
|---|---|---|
aws_paymentcryptography_key.this |
Keystone HSM-backed payment key | Always |
aws_paymentcryptography_key_alias.this |
Friendly alias(es) pointing at the key (for_each) |
aliases non-empty |
ℹ️ This is a deliberately small composite — two resources, no key policy resource (AWS Payment Cryptography has no Terraform-managed resource-policy analog to
aws_kms_key_policy; access control is expressed entirely through IAM identity/resource policies the caller authors against the keyarn).
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
No provider {} block, no configuration_aliases — this module inherits the caller's single provider configuration (standard region model; no region variable).
The Terraform identity needs the following actions. CreateKey cannot be resource-scoped (no ARN exists yet); everything else can be scoped to the key arn once known.
| Action | Required for | Notes |
|---|---|---|
payment-cryptography:CreateKey |
Key creation | Cannot be resource-scoped — no ARN yet |
payment-cryptography:GetKey, payment-cryptography:ListKeys |
Read-back / plan refresh | — |
payment-cryptography:StartKeyUsage, payment-cryptography:StopKeyUsage |
enabled lifecycle toggling |
— |
payment-cryptography:DeleteKey, payment-cryptography:RestoreKey |
Destroy / recovery | Deletion is scheduled with a recovery window, mirroring KMS |
payment-cryptography:TagResource, payment-cryptography:UntagResource, payment-cryptography:ListTagsForResource |
Tagging | — |
payment-cryptography:CreateAlias, payment-cryptography:DeleteAlias, payment-cryptography:UpdateAlias, payment-cryptography:GetAlias, payment-cryptography:ListAliases |
Alias lifecycle | — |
payment-cryptography:ExportKey |
Only if a key is exportable = true and later exported |
Not called by this module — needed by the runtime identity performing the export, not Terraform |
ℹ️ No
iam:PassRoleis required. This module creates no IAM role and assumes nothing.
⚠️ No service-linked role is auto-created for standard key management. An SLR is only relevant for Multi-Party Approval (MPA) or Multi-Region replication features, both configured outside this module.
- Different action namespace from KMS. Ensure the executing IAM principal is permissioned for
payment-cryptography:*, notkms:*— these are entirely separate services despite superficial similarity. - No service-linked role required for the resources this module manages.
- Region availability. AWS Payment Cryptography is available only in a subset of AWS Regions — confirm the target Region supports the service before relying on it; no
regionvariable is exposed (standard provider-inheritance model). - HSM / PCI boundary. Keys are generated and held inside PCI PTS HSM-listed hardware security modules.
exportable = falsekeys can never have their material read out via any API. - Quotas. Control-plane request-rate limits apply per account/Region (
CreateKey,GetKey, alias operations) — see the AWS Payment Cryptography service quotas page before provisioning large batches of keys (e.g. per-merchant BDKs) viafor_each.
tf-mod-aws-payment-cryptography/
├── providers.tf # required_providers only — no provider {} block
├── variables.tf # key_attributes, exportable, enabled, key_check_value_algorithm, aliases, tags, timeouts
├── main.tf # aws_paymentcryptography_key.this + aws_paymentcryptography_key_alias.this (for_each)
├── outputs.tf # id, arn, key_check_value, key_origin, key_state, alias_names, tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM, prerequisites, gotchas, design decisions
Smallest working call — a non-exportable PIN encryption key (TR31_P0), the recommended posture:
module "pin_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false # RECOMMENDED — key material never leaves the HSM boundary
key_attributes = {
key_algorithm = "TDES_3KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
aliases = {
primary = "pin-encryption-prod"
}
tags = {
Environment = "prod"
DataClass = "PCI"
}
}
# Wire the key ARN into an IAM policy Resource element or a Data Plane KeyIdentifier:
# key_arn = module.pin_key.arn
⚠️ Always pin the source with?ref=v1.0.0— never a branch.
| Input | Type | Source module |
|---|---|---|
| (none) | — | — |
This module consumes nothing from other infrastructure modules — it is typically wired standalone, with IAM policies (authored by the caller, not generated here) referencing its
arn.
| Output | Description | Consumed by |
|---|---|---|
id |
Key id (identical to arn) |
AWS Payment Cryptography API references |
arn |
Key ARN — the canonical cross-resource reference | IAM policy Resource elements, aws_paymentcryptography_key_alias.key_arn, Data Plane KeyIdentifier |
key_check_value |
KCV — non-secret checksum confirming key identity | Counterparty key-exchange verification |
key_origin |
Source of key material | Audit |
key_state |
Lifecycle state | Operational monitoring |
alias_names |
Map of logical alias key → normalized alias/<name> |
Application config referencing the key by friendly name |
tags_all |
All tags incl. provider default_tags |
Governance / audit |
1 · Minimal — non-exportable PIN encryption key
module "pin_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "TDES_3KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
}2 · With tags (merges with provider default_tags)
# Provider-level default_tags are the CALLER's concern, set once:
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
ManagedBy = "Terraform"
Owner = "payments-platform-team"
}
}
}
module "pin_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "TDES_3KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
# Resource tags MERGE with default_tags; resource tags win on key conflict.
tags = {
Environment = "prod"
DataClass = "PCI" # overrides any default_tags DataClass
}
}
# module.pin_key.tags_all == { ManagedBy, Owner, Environment, DataClass }3 · Card verification key (CVV/CVV2) — TR31_C0
module "cvv_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "TDES_2KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_C0_CARD_VERIFICATION_KEY"
key_modes_of_use = {
generate = true
verify = true
}
}
aliases = {
cvv2 = "cvv2-issuer-prod"
}
tags = { KEY_PURPOSE = "CVV2", CARD_BIN = "12345678" }
}4 · AES-256 symmetric data encryption key — TR31_D0
module "data_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "AES_256"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_D0_SYMMETRIC_DATA_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
}5 · HMAC key for MAC generation/verification — TR31_M7
module "mac_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "HMAC_SHA256"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_M7_HMAC_KEY"
key_modes_of_use = {
generate = true
verify = true
}
}
tags = { KEY_PURPOSE = "SPA2_AAV", CARD_BIN = "12345678" }
}6 · Key-block protection key (KEK) for TR-31 key exchange
module "kek" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "AES_256"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_K1_KEY_BLOCK_PROTECTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
aliases = { kek = "tr31-kek-prod" }
}7 · EMV master key for application cryptogram derivation — TR31_E0
module "emv_mkey" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "TDES_3KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_E0_EMV_MKEY_APP_CRYPTOGRAMS"
key_modes_of_use = {
derive_key = true
}
}
}8 · Asymmetric key for digital signature — TR31_S0 (RSA)
module "signing_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "RSA_2048"
key_class = "PRIVATE_KEY"
key_usage = "TR31_S0_ASYMMETRIC_KEY_FOR_DIGITAL_SIGNATURE"
key_modes_of_use = {
sign = true
}
}
}9 · Explicit key check value algorithm (counterparty KCV convention)
module "legacy_kek" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_check_value_algorithm = "ANSI_X9_24" # match a TDES-era counterparty's KCV convention
key_attributes = {
key_algorithm = "TDES_2KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_K0_KEY_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
}10 · Multiple aliases pointing at one key
module "shared_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "AES_256"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_D0_SYMMETRIC_DATA_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
# "alias/" prefix optional — the module normalizes it.
aliases = {
primary = "shared-data-key"
reporting = "alias/shared-reporting-key"
}
}11 · for_each pattern — a batch of per-merchant BDKs
locals {
merchants = toset(["merchant-a", "merchant-b", "merchant-c"])
}
module "merchant_bdk" {
for_each = local.merchants
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "AES_128"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_B0_BASE_DERIVATION_KEY"
key_modes_of_use = {
derive_key = true
}
}
aliases = { bdk = "dukpt-bdk-${each.key}" }
tags = { Merchant = each.key }
}
# Stay under the account/Region CreateKey request-rate quota for large batches.12 · Import an existing key (by ARN)
import {
to = module.pin_key.aws_paymentcryptography_key.this
id = "arn:aws:payment-cryptography:us-east-1:111122223333:key/qtbojf64yshyvyzf"
}
module "pin_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "TDES_3KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
}13 · Secure-by-default opt-out — exportable = true (EXCEPTION, review required)
# ⚠️ Weakens the baseline. Document the exception and get security sign-off
# before enabling — this permits ExportKey (e.g. TR-34/RSA-wrap to a legacy HSM).
module "legacy_interop_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = true # OPT-OUT: key material CAN be exported (wrapped) — documented exception only
key_attributes = {
key_algorithm = "TDES_2KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_K2_TR34_ASYMMETRIC_KEY"
key_modes_of_use = {
encrypt = true
wrap = true
}
}
tags = { Exception = "legacy-hsm-interop-2026-07", ApprovedBy = "security-review" }
}14 · Disabled key (retained, not usable)
module "retired_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
enabled = false # OPT-OUT: disables cryptographic operations without deleting the key
key_attributes = {
key_algorithm = "TDES_3KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
}15 · End-to-end composition — IAM policy scoped to a PIN key (mandatory finale)
# 1) The non-exportable, HSM-bound PIN encryption key
module "pin_key" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-payment-cryptography?ref=v1.0.0"
exportable = false
key_attributes = {
key_algorithm = "TDES_3KEY"
key_class = "SYMMETRIC_KEY"
key_usage = "TR31_P0_PIN_ENCRYPTION_KEY"
key_modes_of_use = {
encrypt = true
decrypt = true
wrap = true
unwrap = true
}
}
aliases = { primary = "pin-encryption-prod" }
tags = { Environment = "prod", DataClass = "PCI" }
}
# 2) An IAM policy scoped to exactly the actions the payment-processing
# service needs, authored by the caller (out of this module's scope).
data "aws_iam_policy_document" "pin_translation_data_plane" {
statement {
sid = "AllowPinTranslateDataPlane"
effect = "Allow"
actions = [
"payment-cryptography-data:TranslatePinData",
"payment-cryptography-data:DecryptData",
]
resources = [module.pin_key.arn]
}
}
module "pin_translation_policy" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-policy?ref=v1.0.0"
name = "pin-translation-data-plane"
policy = data.aws_iam_policy_document.pin_translation_data_plane.json
}
# 3) The role assumed by the payment-processing service, with the policy attached
module "pin_translation_role" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
name = "pin-translation-service"
assume_role_policy = data.aws_iam_policy_document.service_trust.json
managed_policy_arns = [module.pin_translation_policy.arn]
}ℹ️ High-level grouping below.
- Core (FORCE-NEW):
key_attributes—key_algorithm,key_class,key_usage,key_modes_of_use(all immutable; changing any recreates the key). - Security (FORCE-NEW, no default):
exportable— the caller must statetrue/falseexplicitly; -recommended value isfalse. - Lifecycle:
enabled(defaulttrue). - Key exchange:
key_check_value_algorithm(defaultnull— service picks the conventional algorithm for the key type). - Aliases:
aliases—map(string)keyed by a stable logical name, values normalized toalias/<name>. - Universal:
tags,timeouts.
id/arn— identical values; the key ARN (arn:aws:payment-cryptography:<region>:<account>:key/<key-id>) is the canonical cross-resource reference.key_check_value— the KCV checksum (non-secret).key_origin— source of the key material.key_state— lifecycle state (e.g.CREATE_COMPLETE,DELETE_PENDING).alias_names— map of logical alias key → normalized alias name (empty whenaliases = {}).tags_all— merged resource + providerdefault_tags.- No key-material output exists. There is nothing to emit —
exportable = falsekeys have no read-back API at all, and this module never wires up the separateExportKeyData Plane call even forexportable = truekeys.
- ARN / id formats. Key ARN:
arn:aws:payment-cryptography:<region>:<account>:key/<key-id>. This resource'sidandarnare identical (confirmed by the provider's import identity schema, which requires onlyarn). - Force-new / immutable fields.
exportableand every field underkey_attributes(key_algorithm,key_class,key_usage,key_modes_of_use) are FORCE-NEW — AWS Payment Cryptography has no UpdateKey API for these. Changing any of them destroys and recreates the key with a new ARN and new key material; treat it as a deliberate key-rotation event (issue new key, re-point aliases, retire the old key), not an in-place edit. tags↔tags_all↔default_tags.var.tagsflows only toaws_paymentcryptography_key— the alias resource is not taggable.tags_allis the computed merge of resource tags over the provider'sdefault_tags; resource tags win on key conflict.default_tagsis the caller's provider-block concern, never set in the module.- Eventual consistency. As with most AWS control-plane services, a freshly created key may briefly be unresolvable by the alias resource or by IAM policy evaluation immediately after creation; Terraform's dependency graph (
key_arn = aws_paymentcryptography_key.this.arn) orders the alias after the key, but downstream IAM propagation is outside Terraform's control. - Destroy ordering. Deletion is scheduled (mirroring KMS behavior) rather than immediate; the key is recoverable via
RestoreKeywithin the waiting window. Aliases are destroyed ahead of the key in Terraform's graph since they depend onkey_arn. - No
us-east-1global-resource requirement. AWS Payment Cryptography keys are regional; this module has no CloudFront/WAFv2/ACM-style Region coupling.
Secure-by-default posture — each hardened default (or, where the module intentionally has none, the reasoning) and the exact variable that relaxes it:
| Posture | Hardened default | Opt-out (variable) |
|---|---|---|
| Key exportability | No baked-in default — the caller must state intent; -recommended value is exportable = false (non-exportable, HSM-bound) |
exportable = true, with a documented legacy-interop exception and security sign-off |
| Key-material exposure | No Terraform-managed export path exists at all | n/a — by design, not configurable |
| Scope of cryptographic operations | Every key_modes_of_use flag defaults to false — least-privilege, caller enumerates only needed operations |
no_restrictions = true removes this floor — discouraged |
| Key enablement | enabled = true |
enabled = false (disable without deleting) |
| Secret exposure | No output ever carries key material, exportable or not | n/a |
Additional principles:
- Small composite, two resources. The key and its alias(es) are inseparable in practice — one module call produces both.
exportabledeliberately has no default value. Unlike most secure-by-default booleans (which quietly default to the secure posture), payment-key exportability is consequential enough that the module forces an explicit, reviewable decision rather than letting either posture happen silently.- No key-policy resource in scope, unlike
tf-mod-aws-kms— access control is expressed entirely through caller-authored IAM identity/resource policies scoped to the keyarn.
terraform init -backend=false
terraform validate
terraform fmt -check
# plan/apply require valid AWS credentials (profile / SSO / OIDC) and a region:
terraform plan
terraform apply
terraform output
⚠️ Pin the module source with?ref=v1.0.0— never a branch.
terraform init -backend=false && terraform validate— offline schema validation.terraform fmt -check— formatting gate.terraform planagainst a sandbox account — confirmkey_attributesrenders the expectedkey_algorithm/key_class/key_usage/key_modes_of_usecombination and that it matches a valid combination per the AWS Payment Cryptography "key attributes" reference table (the service rejects invalid combinations at apply time even though Terraform's ownvalidation {}blocks only check membership in the known enums, not cross-field combinations).- Verify
key_stateshowsCREATE_COMPLETEandkey_check_valueis populated after apply. - For batches created via
for_each, confirm the request rate stays under the account/RegionCreateKeyquota.
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
alias_names = {
"primary" = "alias/pin-encryption-prod"
}
arn = "arn:aws:payment-cryptography:us-east-1:111122223333:key/qtbojf64yshyvyzf"
id = "arn:aws:payment-cryptography:us-east-1:111122223333:key/qtbojf64yshyvyzf"
key_check_value = "8B3F2A"
key_origin = "AWS_PAYMENT_CRYPTOGRAPHY"
key_state = "CREATE_COMPLETE"
tags_all = {
"DataClass" = "PCI"
"Environment" = "prod"
"ManagedBy" = "Terraform"
}
- Tag drift from
default_tagsoverlap. A key set in bothvar.tagsand providerdefault_tagsshows the resource value intags_all(resource wins). If a plan keeps re-adding a tag, it is defined in both places with different values — remove the duplicate. - Credential-chain failures.
NoCredentialProviders/ExpiredTokenon plan/apply means no valid AWS credentials — setAWS_PROFILE, refresh SSO, or confirm the OIDC role assumption succeeded in CI. - Region errors. AWS Payment Cryptography is not available in every Region — a
ResourceNotFoundException/UnknownOperationException-style error on apply often means the caller's provider Region does not support the service. There is nous-east-1global-resource requirement here (unlike CloudFront/WAFv2/ACM) — check the AWS Payment Cryptography Region-availability table instead. - IAM permission denials. A
payment-cryptography:CreateKey/payment-cryptography:CreateAliasdenial means the Terraform identity lacks the action — see Required IAM Permissions. Remember this is a distinct action namespace fromkms:*. ValidationExceptiononkey_attributes. The combination ofkey_algorithm+key_usage+key_modes_of_useis invalid per the service's valid-attribute-combination table (e.g.sign/verifymodes are only valid withTR31_S0_ASYMMETRIC_KEY_FOR_DIGITAL_SIGNATURE). This module'svalidation {}blocks only check that each field is a recognized enum value — they cannot check cross-field validity — so the service itself will reject an invalid combination at apply time. Consult the AWS Payment Cryptography "key attributes" reference.- Destroy-ordering / recreate surprises. Any change to
exportableor akey_attributesfield silently recreates the key (FORCE-NEW) — a new ARN and new key material result, and any aliases pointing at the old key must be re-pointed. Plan changes to these fields as a deliberate rotation event. - Key seems "hung" mid-delete. Deletion is scheduled, not immediate (mirroring KMS) — the key transitions through a pending-deletion state before it is gone; use
RestoreKeywithin the window to recover if the destroy was unintended.
- Terraform Registry —
aws_paymentcryptography_key,aws_paymentcryptography_key_alias - AWS Payment Cryptography User Guide — understanding key attributes and valid key/mode combinations
- AWS Payment Cryptography User Guide — creating keys, managing aliases, key tagging
- AWS Payment Cryptography User Guide — quotas for control-plane and data-plane operations
- module suite —
tf-mod-aws-iam-role,tf-mod-aws-iam-policy,tf-mod-aws-kms(for general-purpose encryption, a distinct service)
🧡 "Infrastructure as Code should be standardized, consistent, and secure."