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:
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"
IRSA setup for EKS (recommended)
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)secp256k1secp384r1(P-384)secp521r1(P-521)RSA(2048-bit)RSA3072(3072-bit)RSA4096(4096-bit)
Create a basic key
With IRSA / environment credentials (recommended)
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 aboveconfig.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– Success400 Bad Request– Invalid configuration401 Unauthorized– Missing/invalid token403 Forbidden– Insufficient permissions409 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-1vsus-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
| Field | Required | Description |
|---|---|---|
multiRegion | Yes | Set to true |
replicaRegions | Optional | AWS regions for replicas, e.g., ["eu-west-1", "us-east-1"] |
enableFailover | Optional | Auto-retry in replicas when primary unavailable |
failoverOrder | Optional | Custom region order. Default: [primary, ...replicaRegions] |
Validation rules
Configuration is validated before creating any keys:
- All regions must be valid AWS regions
multiRegion: truerequired for replica/failover config- Replica regions cannot duplicate primary
failoverOrderregions must exist in primary + replicasenableFailoverrequiresreplicaRegions
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:
- Primary key in
eu-central-1 - Replica in
eu-west-1 - Replica in
us-east-1 - 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.
