CROSS-CLOUD · SECRETS AND KMS
Secrets, encryption and KMS migration to STACKIT
Keys and secrets do not move to STACKIT, they are re-created. Key material generated in a cloud KMS cannot be exported, so every dataset it protects is re-encrypted under a new STACKIT KMS key, and secret values are written afresh into STACKIT Secrets Manager. Rotation jobs, aliases and SSE-KMS on buckets have no one-to-one counterpart. This guide follows an AWS estate through a StackToIT run and shows what the draft carries, what it leaves to you and in which order to work.
The migration, in order
1. Inventory keys, secrets and their consumers
List every key, which resources use it (databases, buckets, secrets) and every secret with the applications that read it. The run reads the links from the Terraform, so a key's consumers are visible before anything is built.
2. Create the keyring and key
STACKIT KMS groups keys in key rings. A symmetric key uses AES-256-GCM; asymmetric, signing and MAC purposes exist too. Currently only software protection is available, and the protection level is a variable the draft leaves to you.
3. Decide who can reach the key
The access scope of the key is PUBLIC or SNA. The source key states no network, so the run leaves the variable without a default; left unset, the STACKIT API makes the key public.
4. Re-encrypt the data
Data protected by the old key is loaded into the new service and encrypted there. For PostgreSQL Flex the draft binds the instance to the new key; the service accepts that setting for enabled accounts only, so request access before the apply.
5. Re-create the secrets
The draft creates one Secrets Manager instance and a user that can write; the values themselves are written by an API step from a document you supply. Delete the writer user afterwards.
6. Encrypt the instance with a key, if you need it
Secrets Manager can encrypt the values of an instance with a KMS key, set through the API (the Portal cannot configure it yet), with a service account that holds at least the Secrets Manager Reader role (docs). The run records it as one key for all secret values of the instance, not one per secret.
7. Replace rotation
Neither service rotates for you: KMS creates a new key version when you trigger Rotate key, which the documentation describes as a fee-based order, and Secrets Manager has no automatic rotation. The job that the source's rotation function did becomes your job, and the run says so.
8. Switch the applications
Applications move from the cloud SDK to a Vault-compatible client, or receive secrets through the platform, for example External Secrets Operator on SKE. Look up secrets by their new path.
A key and a secret, converted
An estate written for this page and assessed by StackToIT on 4 November 2026. Left: a KMS key with an alias, a secret with a value and a rotation rule, and a parameter. Right: the STACKIT blocks of security.tf (review comments left out). The alias, the rotation rule and the secret value have no block; the draft gives the reason in its review comments.
resource "aws_kms_key" "data" {
description = "data encryption key"
enable_key_rotation = true
deletion_window_in_days = 30
}
resource "aws_kms_alias" "data" {
name = "alias/data"
target_key_id = aws_kms_key.data.key_id
}
resource "aws_secretsmanager_secret" "db" {
name = "prod/orders/db"
kms_key_id = aws_kms_key.data.arn
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({ username = "orders", password = var.db_password })
}
resource "aws_secretsmanager_secret_rotation" "db" {
secret_id = aws_secretsmanager_secret.db.id
rotation_lambda_arn = "arn:aws:lambda:eu-central-1:111122223333:function:rotate-db"
rotation_rules {
automatically_after_days = 30
}
}
resource "aws_ssm_parameter" "feature_flags" {
name = "/prod/orders/feature-flags"
type = "SecureString"
value = "checkout_v2=on"
}resource "stackit_kms_keyring" "migrated" {
project_id = var.project_id
display_name = "migrated-keys"
}
resource "stackit_kms_key" "data" {
project_id = var.project_id
keyring_id = stackit_kms_keyring.migrated.keyring_id
display_name = "data-encryption-key"
algorithm = "aes_256_gcm"
purpose = "symmetric_encrypt_decrypt"
protection = var.kms_protection
access_scope = var.kms_access_scope == "SNA" ? "SNA" : null
}
resource "stackit_secretsmanager_instance" "migrated_secrets" {
project_id = var.project_id
name = "migrated-secrets"
}
resource "stackit_secretsmanager_user" "migration" {
project_id = var.project_id
instance_id = stackit_secretsmanager_instance.migrated_secrets.instance_id
description = "StackToIT migration user: writes the migrated secrets; delete it afterwards"
write_enabled = true
}A key, not a copy
The AWS key becomes a keyring and an AES-256-GCM key. The draft notes that AWS key material cannot be exported, so data encrypted with the old key is re-encrypted during migration.
Rotation and alias are not carried
stackit_kms_key has no rotation attribute and no alias in the pinned provider. The key never rotates by itself, and clients that addressed the key by alias must use the new key ID.
One instance holds the secrets
The secret and the parameter land in one Secrets Manager instance. The customer key of the secret is not set on the instance, because the parameter in the same instance states no key; you set it once the keys of the sources are decided.
The writer is a temporary user
The migration user has write access to the secrets engine of the whole instance. The draft's own description says to delete it after the secrets are written.
What maps, and what is left to you
| Source | STACKIT target | Class and what is not carried |
|---|---|---|
| KMS key with rotation on | KMS keyring and key | RECONFIGURE: key material cannot be exported; rotation is not carried, a rotation becomes an operational process (the key version is named in every decrypt call). |
| KMS alias | No alias on the key | Not carried: clients must use the new key ID. |
| Secret with value and rotation function | Secrets Manager instance, value written by an API step | RECONFIGURE: the value comes from a document you supply; the rotation function is not carried and Secrets Manager rotates nothing by itself. |
| SecureString parameter | Secrets Manager | RECONFIGURE: a name with a leading slash becomes a path without one, so lookups by name are a decision. |
| Database encrypted with a customer key | PostgreSQL Flex with an encryption block | REPLATFORM: loaded into the new instance and encrypted with the new key; available to enabled accounts only. |
| Bucket with SSE-KMS | Object Storage | REDESIGN, low confidence: no KMS-backed encryption; accept platform encryption or bring your own keys per request. |
Classes as the run decided them. The key version is part of a STACKIT decrypt call, where the source service needs none.
The bucket that has no KMS mode
From the same run. A bucket encrypted with a customer key is the one unit the run sends to redesign. Left: how assessment.json records it, together with the key unit (id, class, confidence and target; the finding is summarised below). Right: the bucket block the draft writes, without any key.
{
"id": "aws_s3_bucket.exports",
"class": "REDESIGN",
"confidence": "LOW",
"target": {
"service": "STACKIT Object Storage"
}
}
{
"id": "aws_kms_key.data",
"class": "RECONFIGURE",
"confidence": "MEDIUM",
"target": {
"service": "STACKIT KMS"
}
}resource "stackit_objectstorage_bucket" "exports" {
project_id = var.project_id
name = "acme-orders-exports"
}What the run found
The run records that STACKIT Object Storage documents two encryption modes only, AES-256 at rest and SSE-C. A test on a STACKIT project in eu01 on 2026-10-16 saw the put-encryption call answer 501 for aws:kms and an object put with aws:kms answer 400, so an operation being listed does not mean the mode is supported.
Two options
Accept platform encryption at rest, or have clients supply SSE-C keys with every request. The second is a redesign of the encryption and of every client that writes.
The key stays elsewhere
The draft carries no link from the bucket to the new key. Decide whether the exports are sensitive enough to justify SSE-C before the cutover, not after.
The database bound to the new key
The PostgreSQL instance of the same run. Left: the AWS database with its customer key. Right: the instance block the draft writes, with an encryption object that points at the new key, key version and service account.
resource "aws_s3_bucket_server_side_encryption_configuration" "exports" {
bucket = aws_s3_bucket.exports.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.data.arn
}
}
}
resource "aws_db_instance" "orders" {
identifier = "orders-db"
engine = "postgres"
engine_version = "15.7"
instance_class = "db.m5.large"
allocated_storage = 100
username = "orders"
password = var.db_password
storage_encrypted = true
kms_key_id = aws_kms_key.data.arn
skip_final_snapshot = true
}resource "stackit_postgresflex_instance" "orders" {
project_id = var.project_id
name = "orders-db"
version = "15"
flavor_id = "2.16"
storage = {
class = var.postgresflex_storage_class
size = 108
}
backup_schedule = "0 2 * * *"
network = {
acl = var.orders_allowed_cidrs
access_scope = var.orders_access_scope == "SNA" ? "SNA" : null
}
encryption = {
kek_key_id = stackit_kms_key.data.key_id
kek_key_version = tostring(var.data_key_version)
kek_keyring_id = stackit_kms_keyring.migrated.keyring_id
service_account = var.data_service_account
}
retention_days = 32
}Variables for the identity
The key version and the service account that performs the KMS operations are variables for you to set. The run states what the object needs, not which account you use.
Access first, apply second
The service accepts this object for enabled accounts only and rejects the request otherwise. The run raises it as a warning on the unit.
Data moves by dump or replication
The new instance is empty and encrypted from the start; the data playbook covers how it is filled.
Decisions the architect owns
Public or network-area key
The scope of every key, decided per key. No default exists in the draft.
One key or several
Secrets Manager takes one key per instance. If secrets used different keys, decide which share an instance.
Rotation process
Who rotates keys and secrets, how often, and with what job. KMS rotation creates a new key version on demand; an AppRole secret ID is rotated without interruption by generating a new one, pointing the workload at it, checking the login and deleting the old one (docs).
Deletion of old keys
A STACKIT key you delete is removed after 30 days. Keep the old cloud keys until no backup or snapshot still needs them; that rule is practice, not a run finding.
Bucket encryption
Platform encryption or SSE-C, as above.
Secrets and KMS questions
Can I import my existing key into STACKIT KMS?
STACKIT KMS can import an externally created key, bound to a key version. The run states that key material generated in AWS KMS cannot be exported, so it plans a new key and re-encryption.
Does Secrets Manager rotate secrets?
No. The run records that it has no automatic rotation, so the logic of the source's rotation function must be rebuilt as a job against the new store.
Is Secrets Manager compatible with Vault clients?
The run describes it as a Vault-compatible key-value store, and the STACKIT provider documents writing secrets with the HashiCorp Vault provider. Applications switch from the cloud SDK to a Vault client.
Which key encrypts the secrets?
The draft sets no kms_key block on this instance, and the pages read do not say which key applies without one. A KMS key is set through the API; the Portal cannot configure it yet. If you later change the key version, the secrets already stored are re-encrypted automatically.
How limited is a Secrets Manager instance?
The documentation lists up to 100 users and 10,000 API accesses per hour per instance, and no fixed service plans.
Sources
Your next move: STACKIT.
Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.