AWS KMS

This guide shows you how to create and manage signing keys in AWS KMS through the Enterprise Stack KMS service.

What you can do

  • Generate signing keys in AWS KMS and use them across Enterprise Stack services (credential issuance, DID operations, etc.)
  • Use IRSA (IAM Roles for Service Accounts) for secure, credential-free authentication on EKS
  • Create multi-region keys with automatic failover for high-availability deployments

Prerequisites

Setting up AWS KMS involves two separate places: your Enterprise Stack config (step 1) and your own AWS account (step 2, and optionally IRSA below, done via the AWS Console, CLI, or IaC — not via any Enterprise Stack file).

1. Enable the crypto-aws feature

The crypto-aws feature flag is enabled by default, so you usually don't need to touch this — it only needs action if your deployment has explicitly disabled it.

Check disabledFeatures in your _features.conf (found at waltid-enterprise-api/config/_features.conf in a local checkout) and remove crypto-aws from that list if present:

_features.conf
disabledFeatures = [
    # crypto-aws
]

The API must be restarted for feature flag changes to take effect.

2. Set up IAM permissions in your AWS account

Create an IAM role or user in your own AWS account with these KMS permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WaltIDKMSBasic",
      "Effect": "Allow",
      "Action": [
        "kms:CreateKey",
        "kms:CreateAlias",
        "kms:DeleteAlias",
        "kms:Describe*",
        "kms:Get*",
        "kms:List*",
        "kms:Sign",
        "kms:Verify",
        "kms:TagResource",
        "kms:UntagResource"
      ],
      "Resource": "*"
    }
  ]
}

For multi-region keys, add:

"kms:ReplicateKey",
"kms:ScheduleKeyDeletion"

For production EKS deployments, use IRSA instead of long-lived access keys:

1. Create IAM role with the KMS policy above

2. Add trust relationship for your service account:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::YOUR_ACCOUNT:oidc-provider/oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID:sub": "system:serviceaccount:YOUR_NAMESPACE:YOUR_SERVICE_ACCOUNT"
        }
      }
    }
  ]
}

3. Annotate your Kubernetes service account:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: waltid-enterprise-api
  namespace: your-namespace
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::YOUR_ACCOUNT:role/waltid-eks-kms-role

4. Use "backend": "aws" in requests - IRSA credentials are used automatically.

Supported key algorithms

  • secp256r1 (P-256)
  • secp256k1
  • secp384r1 (P-384)
  • secp521r1 (P-521)
  • RSA (2048-bit)
  • RSA3072 (3072-bit)
  • RSA4096 (4096-bit)

Create a basic key

CURL

Endpoint: POST /v1/{target}/kms-service-api/keys/generate

Example Request
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.kms1/kms-service-api/keys/generate' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "backend": "aws",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "region": "eu-central-1"
    },
    "keyName": "my-signing-key",
    "tags": {
      "environment": "production",
      "purpose": "credential-signing"
    }
  }
}'

Body Parameters

  • backend: "aws" for SDK-based (supports IRSA)
  • keyType: One of the supported algorithms above
  • config.auth.region: AWS region where to create the key (e.g., "eu-central-1")
  • config.keyName: (optional) Human-readable alias (must be unique)
  • config.tags: (optional) The metadata tags to add to the key for AWS

Example Response
{
  "_id": "waltid.tenant1.kms1.my-signing-key",
  "key": {
    "type": "aws",
    "config": {
      "auth": { "region": "eu-central-1" }
    },
    "id": "a1b2c3d4-5678-90ab-cdef-EXAMPLE",
    "_keyType": "secp256r1"
  },
  "parent": "waltid.tenant1.kms1"
}

Response codes:

  • 201 Created – Success
  • 400 Bad Request – Invalid configuration
  • 401 Unauthorized – Missing/invalid token
  • 403 Forbidden – Insufficient permissions
  • 409 Conflict – Key alias already exists

With explicit access keys

Use when running outside AWS or without IAM roles. Same endpoint as above — only the request body's backend and config.auth shape change:

{
  "backend": "aws-rest-api",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
      "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
      "region": "eu-central-1"
    }
  }
}

Note: Rotate access keys regularly. IRSA is preferred for production. Instead of passing keys inline in every request, you can also define a reusable KMS profile in resource-access.conf and reference it by name via configRef — see KMS Profiles for details.

Alternative: instance role name (EC2/ECS)

If the host running the Enterprise Stack already has an IAM role attached (EC2/ECS instance profile), you can omit accessKeyId/secretAccessKey and provide config.auth.roleName (matching that role's name) instead. The aws-rest-api backend then fetches temporary credentials from the instance metadata service (IMDSv2) for that role:

{
  "backend": "aws-rest-api",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "roleName": "my-instance-role",
      "region": "eu-central-1"
    }
  }
}

Always set roleName explicitly to the instance's actual attached role — if it doesn't match, the request fails with a role mismatch error.

Error handling

409 Conflict - Alias already exists

{
  "exception": true,
  "id": "KeyAlreadyExistsException",
  "status": "Conflict",
  "code": "409",
  "message": "A key with name 'my-signing-key' already exists."
}

The Enterprise Stack attempts to clean up the orphaned key. If cleanup fails due to missing kms:ScheduleKeyDeletion permission, the error is logged but 409 is still returned.

400 Bad Request - Invalid configuration

{
  "exception": true,
  "id": "IllegalArgumentException",
  "status": "Bad Request",
  "code": "400",
  "message": "Invalid replica region 'us-central-1'. Valid AWS regions: ..."
}

Common causes:

  • Typo in region name (e.g., us-central-1 vs us-west-1)
  • Multi-region config without multiRegion: true
  • Invalid failover configuration

Advanced: Multi-region keys with failover

For high-availability deployments, AWS KMS supports multi-region keys - keys that share the same cryptographic material across multiple regions.

Benefits

  • Disaster recovery: Continue operations if a region becomes unavailable
  • Lower latency: Place replicas closer to workloads
  • Compliance: Keep keys in required regions while maintaining DR

How it works

Primary key: Created in your chosen region. Supports all operations.

Replica keys: Created in additional regions. Share:

  • Same key material (can verify signatures from any region)
  • Same key ID (starts with mrk-)
  • Independent policies and metadata

Failover: When enabled, signing operations automatically retry in replica regions if the primary is unavailable.

Configuration

FieldRequiredDescription
multiRegionYesSet to true
replicaRegionsOptionalAWS regions for replicas, e.g., ["eu-west-1", "us-east-1"]
enableFailoverOptionalAuto-retry in replicas when primary unavailable
failoverOrderOptionalCustom region order. Default: [primary, ...replicaRegions]

Validation rules

Configuration is validated before creating any keys:

  • All regions must be valid AWS regions
  • multiRegion: true required for replica/failover config
  • Replica regions cannot duplicate primary
  • failoverOrder regions must exist in primary + replicas
  • enableFailover requires replicaRegions

Validation failures return 400 Bad Request with a helpful message.

Example: Multi-region key with failover

curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/waltid.tenant1.kms1/kms-service-api/keys/generate' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "backend": "aws",
  "keyType": "secp256r1",
  "config": {
    "auth": {
      "region": "eu-central-1"
    },
    "keyName": "production-signing-key",
    "multiRegion": true,
    "replicaRegions": [
      "eu-west-1",
      "us-east-1"
    ],
    "enableFailover": true,
    "failoverOrder": [
      "eu-central-1",
      "eu-west-1",
      "us-east-1"
    ],
    "tags": {
      "environment": "production",
      "ha-enabled": "true"
    }
  }
}'

What this creates:

  1. Primary key in eu-central-1
  2. Replica in eu-west-1
  3. Replica in us-east-1
  4. Automatic failover: tries regions in order

Failover behavior

Normal operation:

  • All requests use the primary region
  • No performance impact

Primary unavailable:

  • Automatically retries in first replica
  • Transparent to the caller

Multiple regions unavailable:

  • Continues through failoverOrder
  • Returns error only if all regions fail

Performance: Failover latency is added only during actual outages.

Next steps

Last updated on July 8, 2026