Introduction: the credential nobody owns
A service key is usually created in thirty seconds, pasted into a .env file or a CI variable or Destination or any 3rd Party applications, and then forgotten for years. That is exactly why it becomes one of the most dangerous objects in your SAP BTP landscape.
If you integrate anything with BTP from outside the platform (a local development machine, an on-premise system, a GitHub Actions pipeline, a low-code tool such as n8n), you almost certainly rely on service keys. They give you a client ID and secret, or a certificate, that unlocks a service instance: XSUAA, Destination, Object Store, Cloud Logging, SAP AI Core and many more.
This blogpost explains what service keys really are, why you must rotate them, and how to do it without downtime, using the cf CLI, the btp CLI and a small automation pattern you can drop into a pipeline.
What a service key is (and is not)
A service key is a set of credentials generated on demand for an existing service instance. It is meant for clients that live outside the platform. Credentials meant for an application running inside Cloud Foundry or Kyma are called service bindings, and they reach the app through VCAP_SERVICES or a Kubernetes secret. The mechanics are the same, which is why this post applies to both.
For an XSUAA instance, a key typically contains the following fields:
clientidandclientsecret: the OAuth 2.0 client credentialsurlanduaadomain: the token endpoint hostxsappname,identityzone,tenantid: identifiers of the application and subaccountverificationkey: the public key used to validate issued JWTs
Whoever holds the clientid and clientsecret can request tokens with the scopes of the service instance, from anywhere on the internet. There is no IP restriction and no user attached. The key is the identity.
Credential types
Many BTP services let you choose how a key authenticates. The choice decides how painful rotation will be.
Credential type | What you get | Rotation effort | Typical risk |
Client secret (default, |
| Create a new key, replace the secret everywhere | A leaked string works until you delete the key |
X.509 certificate | Client certificate and private key (mTLS) | Keys expire by design; re-issue before expiry | Needs the private key, not just a string |
Instance secret | One secret shared by the instance | Rotation touches every consumer at once | Large blast radius |
For X.509 on XSUAA you request the type when creating the key, with a short validity:
cf create-service-key my-xsuaa key-x509 -c ‘{
“credential-type”: “x509”,
“x509”: { “key-length”: 2048, “validity”: 7, “validity-type”: “DAYS” }
}'Supported parameters differ per service, so check the documentation of the service you use on SAP Help Portal .
Why rotation is needed
A service key never expires on its own (unless you chose X.509 with a validity). Every day it exists, the chance that it has leaked grows, and you never learn about the leak until it is abused. Rotation puts a ceiling on that exposure.
- Leaks are silent. Keys end up in shell history, screenshots, support tickets, Postman collections, Git commits and CI logs. A leaked key looks identical to a legitimate one in the logs.
- People and projects leave. The developer who created a key for a proof of concept may have left the company. Rotation, combined with an inventory, forces someone to claim every key or let it die.
- Blast radius. A key carries the scopes of the whole instance. Short-lived or frequently replaced keys limit how long an attacker can use what they stole.
- Compliance. Frameworks such as ISO 27001, SOC 2 and internal security policies commonly require periodic credential rotation and evidence that it happens.
- Proof that you can do it. The worst time to learn that rotating a credential breaks three undocumented consumers is during an incident. A routine rotation is a fire drill you run on your own schedule.
What it buys you in an incident
If rotation is a routine, a suspected leak becomes a normal operation: create a new key, move consumers over, delete the old key. If it is not a routine, you face a decision between a possible breach and a possible outage, usually at night.
Deleting a key revokes its client secret at the service, so a rotation also invalidates any copy someone made earlier. Tokens that were already issued stay valid until they expire, which is why short token lifetimes matter as well.
The zero-downtime pattern: overlap, switch, retire
The key point is that a service instance can have several keys at the same time. You never edit a key in place. You add a second one next to the first, and you retire the first only after every consumer has moved.
- Inventory. List the existing keys and find out who uses each one.
- Create a new key with a versioned or dated name, for example
ci-2026-10. - Distribute the new credentials to your secret store or CI system.
- Switch the consumers: redeploy, restart or let them reload the secret.
- Verify that the consumers obtain tokens with the new key and that errors stay flat.
- Wait a grace period, so that slow consumers (nightly jobs, a colleague's laptop) fail loudly rather than silently.
- Delete the old key. This is the step that actually closes the leak.
Two habits make this reliable. First, name keys with a purpose and a date (n8n-prod-2026-10), so age and owner are visible in a listing. Second, give each consumer its own key. If five systems share one key, you can rotate it only by switching five systems on the same day, and you cannot tell which of them used it last.
Hands-on: rotating a key with the CLI
Cloud Foundry (cf CLI)
Start with an inventory of the keys on the instance:
cf service-keys my-xsuaaCreate the replacement key and read its credentials through the API, which is more robust to script than the formatted CLI output:
NEW_KEY=”n8n-prod-$(date +%Y-%m)”
cf create-service-key my-xsuaa “$NEW_KEY”
GUID=$(cf service-key my-xsuaa “$NEW_KEY” –guid)
cf curl “/v3/service_credential_bindings/$GUID/details” | jq ‘.credentials'
After the consumers have switched and the grace period is over, remove the old key:
cf delete-service-key my-xsuaa n8n-prod-2026-09 -fFor service bindings used by apps, the equivalent is to create a new binding and restart the app so it reads the new VCAP_SERVICES. A rolling restart avoids downtime:
cf restart my-app –strategy rollingOther environments (btp CLI and Service Manager)
In Kyma or any non-Cloud-Foundry environment, keys are called bindings and are managed through Service Manager. The btp CLI exposes them: btp CLI
btp list services/binding
btp create services/binding –name ci-2026-10 –service-instance my-xsuaa
btp delete services/binding –name ci-2026-09 –confirmIn Kyma, the equivalent is a ServiceBinding resource that writes the credentials to a Kubernetes secret. Creating a second ServiceBinding and pointing the workload at the new secret follows the same overlap pattern. Command flags change between CLI versions, so confirm them with btp help before you script anything.
Automating rotation and storing the result
Manual rotation works once. A scheduled job works every time. The script below creates a fresh key, hands the credentials to your secret store, and deletes keys older than a grace period. It is a starting point, not a drop-in product: adapt the secret-store call and test it on a non-production instance first.
#!/usr/bin/env bash
set -euo pipefail
INSTANCE=”$1″ # service instance name
PREFIX=”$2″ # key name prefix, e.g. n8n-prod
GRACE_DAYS=”${3:-14}” # how long old keys stay alive
NEW_KEY=”${PREFIX}-$(date +%Y%m%d)”
cf create-service-key “$INSTANCE” “$NEW_KEY”
GUID=$(cf service-key “$INSTANCE” “$NEW_KEY” –guid)
CREDS=$(cf curl “/v3/service_credential_bindings/$GUID/details” | jq -c ‘.credentials')
# 1. Push to your secret store (never echo or log $CREDS)
# e.g. vault kv put secret/btp/$PREFIX value=”$CREDS”
# 2. Retire old keys of this prefix beyond the grace period
INST_GUID=$(cf service “$INSTANCE” –guid)
CUTOFF=$(date -u -d “-${GRACE_DAYS} days” +%Y-%m-%dT%H:%M:%SZ)
cf curl “/v3/service_credential_bindings?type=key&service_instance_guids=${INST_GUID}” \
| jq -r –arg p “$PREFIX” –arg c “$CUTOFF” –arg n “$NEW_KEY” \
‘.resources[] | select((.name|startswith($p)) and .name != $n and .created_at < $c) | .name' \
| while read -r OLD; do
echo “Deleting expired key: $OLD”
cf delete-service-key “$INSTANCE” “$OLD” -f
done
Where the new credentials should live
- A dedicated secret manager. HashiCorp Vault, Azure Key Vault, AWS Secrets Manager, or the SAP Credential Store service on BTP. Consumers fetch the secret at start-up or on a refresh interval, so a rotation needs no redeploy.
- CI/CD secrets. GitHub Actions, GitLab CI or Jenkins credentials, updated by the rotation job through their API.
- Not in Git, not in images, not in chat. Not even in a private repository, and not as a plain environment variable baked into a container image.
Run the job on a schedule, for example every 60 or 90 days, and make the grace period shorter than the interval. Alert when a rotation fails, because a silent failure is worse than no automation: it creates the false impression that the problem is handled.
The cleanest long-term step is to remove the shared secret entirely. Where the service supports it, prefer X.509 credentials with a short validity, or an identity federation flow such as OIDC trust between your CI provider and BTP, so that no long-lived secret exists to rotate.
Pitfalls, checklist and conclusion
Common pitfalls
- Deleting before switching. Remove the old key only after you have seen the new one working in production.
- Forgotten consumers. Cron jobs, notebooks and a colleague's local
default-env.jsonbreak after the delete. Use the grace period and watch authentication failures. - One key for everything. It makes rotation a coordinated big-bang change and hides who uses what.
- Cached tokens and clients. Applications that read the secret once at start-up need a restart, or a reload mechanism, to pick up the new value.
- Rotating the key but not the access around it. A new key with the same wide scopes only shortens the exposure. Combine rotation with least-privilege scopes and role collections.
- No monitoring. Check the audit logs and authentication errors after each rotation; SAP Audit Log service records management operations on service instances.
Checklist
- Every service key has an owner, a purpose and a date in its name
- One key per consumer, no shared keys
- Rotation interval defined (for example 90 days) and automated
- Credentials live in a secret manager, not in code or images
- Old keys are deleted after a fixed grace period
- Failures of the rotation job raise an alert
- Short-lived X.509 or federated identity used wherever the service supports it
Conclusion
Service key rotation is cheap insurance. It limits how long a leaked credential is useful, it forces you to know who depends on what, and it turns an emergency into a routine. Create the new key beside the old one, move the consumers, verify, wait, delete. Put that on a schedule and the quiet backbone of your BTP integrations stops being its quietest risk.
