Skip to content

Connect a regional system ​

This guide is for the teams who build a cloud service that encrypts customer data with keys managed in CMK. It explains how your service registers its systems, which tasks CMK sends to it when a customer connects, changes or withdraws a key, and how your service must answer.

How it works ​

A system is an instance of your service for one tenant in one region, for example the database of tenant acme in region eu10. Your service registers its systems in the Registry. The customer's key administrators then connect them to a key configuration in CMK, and CMK sends your service a task in the system's region for every change.

Your service is responsible for carrying out each task: start or stop using a key, switch to another key or key version. CMK tracks the result and shows it to the customer.

Step 1: Register your systems ​

Register each system with the Registry's RegisterSystem gRPC call, defined in api-sdk under proto/kms/api/cmk/registry/system/v1/system.proto.

FieldContent
external_idYour identifier of the system. Keep it unique across all system types
typeSYSTEM or SUBACCOUNT. CMK supports only these types
regionThe system region the system runs in, for example eu10. The CMK of the tenant's region must have an eventProcessor.targets entry for it
l2key_idThe identifier of the system's key below the customer key
statusSTATUS_AVAILABLE for a system that is ready
labelsOptional key–value labels

The system must then be mapped to the customer's tenant (MapSystemToTenant), typically when your service is provisioned for that tenant. Only mapped systems appear in the customer's CMK. The tenant must be active, and the system must be available and not mapped to another tenant.

The Registry is central: register all systems there, whichever region they run in. The tenant was registered with a CMK region, and the CMK of that region reads the tenant's systems from the Registry and sends their tasks to your system region. See Connect a regional tenant.

Step 2: Receive tasks from CMK ​

CMK sends tasks with Orbital over an AMQP 1.0 broker, separately for each region. For your region, the platform operator gives you:

  • the broker URL and your credentials (mutual TLS or user name and password),
  • the request address on which CMK sends tasks for your region, and
  • the response address on which CMK reads the answers.

Your service runs an Orbital operator that listens on the request address and answers on the response address. Requests and responses are encoded in protobuf. A sketch in Go, with Orbital v0.6:

go
client, err := amqp.NewClient(ctx, &codec.Proto{}, amqp.ConnectionInfo{
    URL:    "amqps://broker.eu10.example.com:5671",
    Source: "<request address>",  // CMK sends tasks here
    Target: "<response address>", // CMK reads answers here
}, amqp.WithExternalMTLS("tls.crt", "tls.key", "ca.crt", "broker.eu10.example.com"))
if err != nil {
    log.Fatal(err)
}
defer client.Close(ctx)

operator, err := orbital.NewOperator(orbital.TargetOperator{Client: client})
if err != nil {
    log.Fatal(err)
}

for _, taskType := range []string{
    "SYSTEM_LINK", "SYSTEM_UNLINK", "SYSTEM_SWITCH", "SYSTEM_KEY_ROTATE",
    "KEY_ENABLE", "KEY_DISABLE", "KEY_DETACH",
} {
    if err := operator.RegisterHandler(taskType, handleTask); err != nil {
        log.Fatal(err)
    }
}

log.Fatal(operator.ListenAndRespond(ctx))
go
func handleTask(ctx context.Context, req orbital.HandlerRequest, resp *orbital.HandlerResponse) {
    var data taskv1.Data // github.com/openkcm/api-sdk/proto/kms/api/cmk/eventprocessor/task/v1
    if err := proto.Unmarshal(req.TaskData, &data); err != nil {
        resp.Fail("INVALID_TASK_DATA:" + err.Error())
        return
    }

    if a := data.GetSystemAction(); a != nil {
        // Apply a.GetKeyIdTo() / a.GetKeyIdFrom() for system a.GetSystemId(),
        // using a.GetKeyAccessMetaData() to reach the key.
    }
    if a := data.GetKeyAction(); a != nil {
        // Stop or resume using key a.GetKeyId().
    }

    resp.Complete()
}

Step 3: Handle the tasks ​

The task data is the protobuf message kms.api.cmk.eventprocessor.task.v1.Data. It contains either a SystemAction or a KeyAction.

Task typeWhen CMK sends itWhat your service does
SYSTEM_LINKA key administrator connects the system to a key configurationStart using the key key_id_to for this system
SYSTEM_SWITCHThe system moves to another key configuration, or its key configuration gets a new primary keySwitch from key_id_from to key_id_to
SYSTEM_UNLINKA key administrator disconnects the system, or the tenant is offboardedStop using the key key_id_from
SYSTEM_KEY_ROTATEThe primary key has a new version in the customer's keystoreStart using the new version. key_id_from and key_id_to are the same key
KEY_DISABLEThe key was disabled, in CMK or in the customer's keystoreStop using the key key_id in all systems of the tenant in your region
KEY_ENABLEThe key was enabled againResume using the key
KEY_DETACHThe tenant is being terminatedRelease the key

SystemAction carries:

FieldContent
system_idYour external_id of the system
system_region, system_typeRegion and type of the system
key_id_from, key_id_toThe previous and the new key
key_providerThe keystore provider of the key, for example aws
key_access_meta_dataThe access data for the key in this region; see below
tenant_id, tenant_owner_id, tenant_owner_typeThe customer's tenant
cmk_regionThe region of the CMK that sent the task

KeyAction carries key_id, tenant_id and cmk_region.

Task type names

Register your handler for SYSTEM_KEY_ROTATE, which is the name CMK sends. In the api-sdk enum, the same value is called KEY_ROTATE.

Key access data ​

key_access_meta_data tells your service how to reach the key in the keystore, for the system's region. Its format is defined by the keystore plugin of the key provider. For HYOK keys it also names the newest key version.

Step 4: Answer ​

Every task needs an answer. With Orbital, the handler sets one of these:

Handler callMeaningWhat CMK does
resp.Complete()The task is doneMarks the change as done, for example the system as Connected
resp.Fail("CODE:message")The task cannot be doneMarks the system Failed and shows the code and message to the customer
resp.ContinueAndWaitFor(d)Still workingAsks again after the duration d
  • Use error messages of the form CODE:message, with the code in upper case, for example KEY_NOT_ACCESSIBLE:role cannot use the key. CMK shows the code and the message separately.
  • For SYSTEM_KEY_ROTATE, answer KEY_VERSION_MISMATCH:<message> if you do not see the new version yet. CMK then tries again at its next key check instead of marking the system failed.
  • Be idempotent. CMK sends the same task again until it receives your answer, so the same task ID can arrive more than once.
  • Answer promptly. If CMK gets no answer, it sends the task again with an increasing interval, starting at ten seconds. After 18 unanswered attempts, about one day, the task fails and the system shows Failed. The customer's key administrators can then retry.

Region configuration ​

The platform operator configures one entry per system region in the eventProcessor.targets of each regional CMK whose tenants have systems there, with the broker URL and the two addresses. Tasks for a system go to the entry whose region matches the system's region.

Error the customer seesCause
UNCONFIGURED_REGIONCMK has no eventProcessor.targets entry for the system's region
UNSUPPORTED_REGIONThe key's access data has no entry for the system's region
System stays Processing, then FailedNo service answers on the region's request address

Checklist ​

  • [ ] Each system is registered with a unique external_id, type SYSTEM or SUBACCOUNT, and a region that CMK serves.
  • [ ] The systems are mapped to the customer's tenant.
  • [ ] Your operator listens on the request address of your region and answers on the response address, with mutual TLS in production.
  • [ ] Handlers exist for all task types, including SYSTEM_KEY_ROTATE.
  • [ ] Handlers are idempotent and answer with CODE:message on failure.
  • [ ] Your service stops using a key as soon as it receives KEY_DISABLE or SYSTEM_UNLINK.

See also ​

EU and German government funding logos

Funded by the European Union – NextGenerationEU.

The views and opinions expressed are solely those of the author(s) and do not necessarily reflect the views of the European Union or the European Commission. Neither the European Union nor the European Commission can be held responsible for them.