Skip to content

Foreign Environment Claim

URI: https://docs.ankatech.co/errors/foreign-environment-claim
HTTP Status: 409 Conflict

When you see this

The PKCS#11 security domain this deployment provisions key-encryption keys on is not this environment's. One of two conditions holds, and both refuse:

  • the token carries another environment's claim marker, or
  • the token carries no claim marker and already holds key-encryption keys under this platform's fixed bootstrap labels.

Nothing was created and nothing was adopted. The claim gate is the first thing a bind does on the token, ahead of the only creator of a KEK_TENANT_* object, so a refusal here leaves the domain exactly as it found it.

Why this exists

A bind used to resolve its two bootstrap KEKs by label presence alone, and the bootstrap tenant UUIDs are fixed platform-wide — so those labels are byte-identical in every environment ever created against the same security domain. A fresh environment landing on a partition that still held another deployment's keys therefore concluded it was bound, adopted them, and reported success having created nothing. It certified custody of key material another deployment created and may still be using.

Presence cannot be the discriminator; provenance has to be, and it has to live on the token, because the token is the only thing that survives the environment. Neither free attribute of the KEK object was available — CKA_ID is what core-api resolves a KEK by, and CKA_LABEL is the adoption lookup key — so the marker is a separate, token-scoped object: an AES CKO_SECRET_KEY labelled ANKASECURE_ENV_CLAIM, carrying this environment's opaque fingerprint in its own CKA_ID, generated through a marker template that sets all four usage attributes false.

The marker can prove "these objects are not mine" and can never prove "these objects are mine". It sits on a partition readable and writable by anyone holding the token credential, so it is forgeable and replayable by construction. Adoption is therefore gated on a positive match, and every other outcome — a foreign fingerprint, and an unclaimed token holding pre-existing keys — declines. That asymmetry is the security control, not an excess of caution.

Common Causes

  • Recreating an environment on a partition a previous deployment used. destroy-env.sh cannot reach inside an appliance: the KEK_TENANT_<uuid> objects live in the partition and survive the destroy as orphans. The next environment meets them.
  • Two live deployments pointed at one partition. The second one to bind refuses.
  • A partition restored, cloned or re-pointed from another deployment's security domain.

Response Example

{
  "type": "https://docs.ankatech.co/errors/foreign-environment-claim",
  "title": "Foreign Environment Claim",
  "status": 409,
  "detail": "The key-material token is claimed by another environment, or already holds key-encryption keys under this platform's bootstrap labels that no environment has claimed. Nothing was created and nothing was adopted: this deployment cannot show that those objects are its own, and adopting them would certify custody of key material it did not create. Remove the pre-existing objects from the security domain out of band, or bind this deployment to a security domain of its own.",
  "instance": "/api/v3/admin/tenants/3fa85f64-5717-4562-b3fc-2c963f66afa6/keys",
  "timestamp": 1730000000,
  "extensions": {
    "requestId": "d6194e72-3f0b-4a85-81c9-4b70e2ad5163"
  }
}

Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.

How to Resolve

Two remedies, both out of band and both deliberate. There is no in-place repair, and re-running the operation returns the same refusal until the domain changes.

  1. Remove the pre-existing objects from the security domain, then retry. Delete them by label with the vendor's own tooling:

    pkcs11-tool --module <vendor-pkcs11-library> --login \
      --delete-object --type secrkey --label KEK_TENANT_<uuid>
    

    Do this before recreating an environment on a partition a previous deployment used — it is the step destroy-env.sh warns about and cannot perform itself.

  2. Bind this deployment to a security domain of its own — a separate partition, Security World or cluster.

Why 409 and not 503. Nothing is defective in this deployment and nothing is defective in the request. The domain is in a state that contradicts the operation and stays in it until an operator acts, so the same call a minute later fails identically. A 503 would instruct the caller to retry a condition that can never resolve itself. This takes the same classification as the ambiguous-coordinate refusal, for the same reason.

Neither fingerprint appears, and neither does anything else. detail is a fixed sentence — the exception's only constructor takes no arguments, so no caller can put anything into it. It names no environment, hostname, IP, stack name, tenant identifier or timestamp, and deliberately neither environment's marker value. The marker is opaque precisely so that it correlates nothing; returning the foreign value would hand back the one correlation handle the design exists to withhold. Neither value is logged either. The remedy is the same whichever environment owns the token, so withholding them costs the operator nothing.

A partially-completed activation is still re-enterable. The claim is written first, in the same PKCS#11 session as the key work, so the orphans a partial run leaves sit under this environment's own claim — the repairing run adopts them instead of refusing its own material. Writing the claim after the keys would have made that state unrepairable.

Where it is raised

Raised by the PKCS#11 key-encryption-key provisioner, on any operation that creates or adopts a tenant KEK on the deployment's key-material token. The five Cloud KMS families carry no such marker and this refusal cannot occur on them.

Two envelopes, and the operation you called does not decide which one you get. The cause, the disclosure contract and the two remedies are identical on both; only the envelope differs.

The typed 409 needs two conditions together, and the second is the one easy to miss: the deployment declares no key-backend descriptor, and a PKCS#11 token is configured through properties. The on-demand paths consult the declared descriptor first and reach the properties-configured token only when no descriptor is declared — so on a deployment that does declare one whose family is PKCS#11, that path catches this condition and the very same operations answer 422 instead. Naming the operations without the declaration condition would tell you to expect a 409 you may never see.

  • As this typed 409 — on a deployment with no declared key-backend descriptor — from the three operations that provision a key-encryption key on demand:

    • POST /api/v3/admin/tenants/{tenantId}/keys (createKeyWithPermissions) — the first key operation for a tenant that has none.
    • POST /api/v3/admin/mfa/setup (setupMfa).
    • POST /api/v3/admin/tenants/resolve (resolveTenant) — on the create branch only. An existing tenant is returned without provisioning anything, and a caller passing provisionKekNow = false takes the skip branch and cannot meet it at all. This operation is not published: it is absent from the OpenAPI document and from every generated client, so its declaration of this 409 reaches no SDK. It is the service-to-service contract, reached by ankasecure-core-api and not by an edge caller.
  • As a 422, on POST /api/v3/admin/platform/setup/backend/activate and the host init-hsm.sh <env> --activate that delegates to it — and, on a deployment that declares a PKCS#11 descriptor, on the three on-demand operations above as well. There the deployment provisioner catches the condition and re-presents it as backend-kek-provisioning-failed carrying extensions.reason: REFUSEDREFUSED and not UNREACHABLE, because the backend answered and the remedy is on the token rather than in this deployment's networking or credentials. The cause and the remedy are the ones on this page.