Skip to main content
Version: 3.19.0

Data Encryption with Keyring

Data encryption is a key consideration of APISIX. Sensitive information, such as user passwords and private keys, is often present in gateway configurations when implementing authentication or integrating with another system. APISIX can encrypt this information before storage and decrypt it with the configured keyring when the gateway needs to use it.

APISIX can encrypt the following values before saving them to etcd:

  • Sensitive plugin fields when enable_encrypt_fields is true and a keyring is configured
  • PEM TLS private keys, including upstream client private keys, whenever a keyring is configured

A keyring must retain every key still needed by stored ciphertext. Losing a required key can make the affected secrets unrecoverable.

Enable Data Encryption​

By default, APISIX has data encryption enabled with two default keys:

apisix/cli/config.lua
apisix ={
...,
data_encryption = {
enable_encrypt_fields = true,
keyring = { "qeddd145sfvddff3", "edd1c9f0985e76a2" }
},
...
}

enable_encrypt_fields controls encryption for fields listed in plugin schemas. The keyring also encrypts PEM private keys independently of that setting. As long as the keyring remains configured, APISIX encrypts new PEM private keys even when plugin field encryption is disabled.

To see data encryption in effect, create a consumer JohnDoe:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "JohnDoe"
}'

Configure the key-auth credential for JohnDoe:

curl "http://127.0.0.1:9180/apisix/admin/consumers/JohnDoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'

In this configuration, the user key is sensitive information that should not be stored in plaintext.

You should see a response when the credential is created, showing an encrypted key:

{
"key": "/apisix/consumers/JohnDoe/credentials/cred-john-key-auth",
"value": {
"create_time": 1726300662,
"update_time": 1726300662,
"plugins": {
"key-auth": {
"key": "a/TGSySA4i1LGn4ZXlYuew=="
}
},
"id": "cred-john-key-auth"
}
}

To further verify that the key is encrypted, you can also examine the item saved to etcd:

etcdctl get /apisix/consumers/JohnDoe/credentials/cred-john-key-auth

You should see the key has been encrypted:

{
"update_time":1726300662,
"create_time":1726300662,
"plugins":{
"key-auth":{
"key":"a/TGSySA4i1LGn4ZXlYuew=="
}
},
"id": "cred-john-key-auth"
}

APISIX encrypts PEM values in the key and keys fields of SSL resources before saving them to etcd. To verify, follow the steps in Configure HTTPS Between Client and APISIX, then inspect the create or update response or the stored resource in etcd. Admin API GET responses omit SSL private-key fields.

APISIX also encrypts upstream.tls.client_key in Upstreams and inline Stream Route upstreams. The Admin API does not return an inline Stream Route client key in plaintext. See Authenticate to a TLS Upstream with mTLS for the Stream configuration.

Verify Decryption​

To verify the consumer key-auth key will be decrypted and used as intended in authentication, create a route with key-auth enabled:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "auth-route",
"uri": "/get",
"plugins": {
"key-auth": {}
},
"upstream" : {
"nodes": {
"httpbin.org":1
}
}
}'

Send a request to the route with the consumer credential:

curl -i "http://127.0.0.1:9080/get" -H 'apikey: john-key'

You should receive an HTTP/1.1 200 OK response, verifying the key has been successfully decrypted and used in authentication.

Update Keyring​

The encrypted data can be reversed to plaintext with the correct keyring. Therefore, it is strongly recommended that you use a customized keyring in production.

Each key must contain exactly 16 or 32 bytes. Use ASCII characters so the character and byte lengths are identical. A 16-byte key selects AES-128-CBC, and a 32-byte key selects AES-256-CBC. APISIX rejects other lengths while validating config.yaml, before startup or reload.

A keyring can contain both lengths during rotation. APISIX encrypts new values with the first key and tries every key when decrypting existing values. Changing the key order does not rewrite existing ciphertext. To rotate from AES-128 to AES-256, add the new 32-byte key first and retain the old keys afterward:

config.yaml
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- 0123456789abcdef0123456789abcdef
- qeddd145sfvddff3
- edd1c9f0985e76a2

❶ Enable the encryption for sensitive plugin fields.

❷ Add the new 32-byte ASCII key first. Replace this example with a securely generated key.

❸ Add the first and second old keys.

caution

If your APISIX is already running and has data encrypted, do not remove the old keys. Add the new key at the beginning of the array and keep every old key afterward in its existing order so APISIX can still decrypt existing values.

Retire an old key only after every plugin field and TLS private key encrypted with it has been re-submitted in plaintext, encrypted with the new first key, and verified. Admin API GET requests return protected plugin fields in plaintext while decryption is enabled, but they omit SSL private keys. Retain the original PEM keys in a secure backup so SSL resources can be re-submitted. If you cannot confirm that every encrypted value has been rewritten, keep the old key.

If no data has been encrypted, you may directly configure the section with your custom keys.

If your APISIX is already running, reload APISIX for configuration changes to take effect.

Troubleshoot Decryption After an Upgrade​

An upgrade can add existing plugin fields to encrypt_fields. Configuration written by an earlier version may therefore contain a value that was not encrypted where the current version expects encrypted data. APISIX can also fail to decrypt existing data when the configured keyring no longer contains the key that encrypted it.

If the error log reports a decryption failure:

  1. Preserve the complete old keyring. Do not remove or reorder old keys while investigating.
  2. Add any new key at the beginning of apisix.data_encryption.keyring, keeping every old key afterward.
  3. Reload APISIX and confirm it can read the existing configuration.
  4. For plugin fields, fetch the affected resource with an Admin API GET request. With the old key available and plugin field encryption enabled, APISIX returns protected fields in plaintext. Re-submit that plaintext in an update request so APISIX encrypts it with the first key in the current keyring. Do not reuse an encrypted value from an earlier write response or directly from etcd.
  5. For SSL resources and upstream client private keys, re-submit the original plaintext private key from a secure backup. Admin API reads do not expose these private keys.
  6. Verify the resource works before considering any later key retirement.

Do not delete an old key first. Once APISIX can no longer decrypt a value, re-saving the unreadable data cannot recover the original secret.

Disable Plugin Field Encryption​

The enable_encrypt_fields option applies only to fields listed in plugin schemas. Before disabling it, keep the complete keyring configured and use authenticated Admin API GET requests to securely capture the plaintext value of every encrypted plugin field that you must retain. Plan a maintenance window so you can rewrite those fields before resuming traffic.

Set enable_encrypt_fields to false without removing the keyring:

config.yaml (continuing the rotated-key example)
apisix:
data_encryption:
enable_encrypt_fields: false
keyring:
- 0123456789abcdef0123456789abcdef
- qeddd145sfvddff3
- edd1c9f0985e76a2
caution

Do not remove the keyring when disabling plugin field encryption. APISIX still needs it to decrypt existing TLS private keys and to preserve recovery of plugin ciphertext that has not been rewritten. Plugin ciphertext is not decrypted while enable_encrypt_fields is false. If you did not capture its plaintext first, re-enable plugin field encryption with the complete keyring before fetching and recovering it. Removing required keys can break TLS and make encrypted values unrecoverable.

If your APISIX is already running, reload APISIX for configuration changes to take effect.

After the reload, APISIX no longer encrypts or decrypts plugin fields. It uses existing ciphertext as the plugin value until you re-submit the plaintext captured before the change. New plugin values are stored in plaintext.

TLS private-key encryption remains enabled as long as the keyring is configured. APISIX continues to encrypt and decrypt SSL resource keys and upstream client private keys.

Now if you configure the consumer credential with key-auth again:

curl "http://127.0.0.1:9180/apisix/admin/consumers/JohnDoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'

You should see a response where the key is saved in plaintext:

{
"key": "/apisix/consumers/JohnDoe/credentials/cred-john-key-auth",
"value": {
"create_time": 1726300662,
"update_time": 1726300668,
"plugins": {
"key-auth": {
"key": "john-key"
}
},
"id": "cred-john-key-auth"
}
}

Understand Plugin Encrypt Fields​

Encrypt plugin fields are defined in the encrypt_fields attribute of each plugin schema. The field path can point to a top-level field or a nested field. For example:

apisix/plugins/basic-auth.lua
local consumer_schema = {
type = "object",
title = "work with consumer object",
properties = {
username = { type = "string" },
password = { type = "string" },
},
# highlight-next-line
encrypt_fields = {"password"},
required = {"username", "password"},
}

When enable_encrypt_fields is true in an etcd-backed deployment and the keyring is configured, APISIX encrypts these fields before storing them.

For nested fields, use dot-separated paths such as session.secret, redis.password, or auth_config.private_key. Paths can have any nesting depth. When an intermediate segment is an array, APISIX traverses every object in the array. This supports fields such as instances.auth.aws.secret_access_key in ai-proxy-multi.

At the final path segment, APISIX encrypts these value shapes:

  • a string;
  • an array of strings; or
  • an object whose values are strings.

Non-string items at the final segment are left unchanged. Add only fields that contain secrets and whose runtime code expects the same value shape after decryption.

The table below summarizes encrypt plugin fields of all plugins:

For logger plugins, an encrypted field can be an object whose string values are encrypted, such as headers in elasticsearch-logger and loki-logger. Encryption protects values at rest. While plugin field encryption and the required keyring are enabled, authorized Admin API reads can return decrypted plugin configuration.

PluginField(s)
authz-casdoorclient_secret
authz-keycloakclient_secret
azure-functionsauthorization.apikey, master_apikey
basic-authpassword
cas-authcookie.secret
clickhouse-loggerpassword
dingtalk-authapp_secret, secret, secret_fallbacks
elasticsearch-loggerauth.password, headers
feishu-authapp_secret, secret, secret_fallbacks
rocketmq-loggersecret_key
sls-loggeraccess_key_secret
error-log-loggerclickhouse.password, kafka.brokers.sasl_config.password
google-cloud-loggingauth_config.private_key
csrfkey
hmac-authsecret_key
http-loggerauth_header
jwt-authsecret
kafka-loggerbrokers.sasl_config.password
key-authkey
lagotoken
logglycustomer_token
loki-loggerheaders
openwhiskservice_token
openfunctionauthorization.service_token
tencent-cloud-clssecret_key
openid-connectclient_secret, client_rsa_private_key, dpop.private_key, session.secret, session.redis.password
kafka-proxysasl.password
jwe-decryptkey, secret
ai-cacheredis_password, semantic.embedding.openai.api_key, semantic.embedding.azure_openai.api_key
ai-aliyun-content-moderationaccess_key_secret
ai-aws-content-moderationcomprehend.secret_access_key
ai-lakera-guardapi_key
ai-proxyauth.header, auth.query, auth.gcp.service_account_json, auth.aws.secret_access_key, auth.aws.session_token
ai-proxy-multiinstances.auth.header, instances.auth.query, instances.auth.gcp.service_account_json, instances.auth.aws.secret_access_key, instances.auth.aws.session_token
ai-ragembeddings_provider.azure_openai.api_key, vector_search_provider.azure_ai_search.api_key
ai-rate-limitingredis_password, sentinel_password
aws-lambdaauthorization.apikey, authorization.iam.accesskey, authorization.iam.secretkey
ldap-auth-advancedldap_password
limit-connredis_password
limit-countredis_password, sentinel_password
limit-reqredis_password
saml-authsp_private_key, secret, secret_fallbacks
splunk-hec-loggingendpoint.token