Edge Delta's EDXEncrypt and EDXDecrypt Extensions
10 minute read
Overview
EDXEncrypt
and EDXDecrypt
enable field‑level protection for sensitive values (for example, PII, credentials, tokens) as they move through your pipelines. Data can be encrypted before it leaves a node or cluster and, when appropriate, decrypted downstream for authorized use cases. These functions are designed for high‑throughput processing and fail safely without exposing plaintext.
Prerequisites
To follow this guide, you should have the Edge Delta Agent v2.5.0 or later, a Helm‑based deployment (or equivalent) where you can mount a keys file, and a pipeline that parses JSON into the body
field (for example, by using log_parsing_mode: full
). You must also provide a keys.json
file and set the ED_CRYPTO_PATH
environment variable to the directory containing that file.
Key Configuration
Edge Delta reads encryption material from a single keys.json
file. The file must be an array of key objects; each object describes one usable key. Keys can be scoped by an integer keyclass so you can selectively encrypt and decrypt different categories of data. The format supports both AES‑256‑GCM (recommended) and AES‑256‑CBC (legacy) with optional expiration.
[
{
"keyId": "gcm-key-001",
"algorithm": "aes-256-gcm",
"keyclass": 1,
"created": 1699123456789,
"cipherKey": "<base64 32-byte key>",
"ivSize": 12
},
{
"keyId": "cbc-key-001",
"algorithm": "aes-256-cbc",
"keyclass": 2,
"created": 1699123456789,
"cipherKey": "<base64 32-byte key>",
"useIV": true
}
]
The example shows a GCM key (with a 12‑byte IV size) and a CBC key. AES‑256 keys must be 32 bytes (base64‑encoded in the file). GCM keys require ivSize
; CBC keys can control IV behavior via useIV
. You may optionally add an expires
timestamp (Unix epoch ms) to retire a key automatically.
Required fields
Field | Purpose |
---|---|
keyId |
Unique identifier referenced by OTTL statements. |
algorithm |
Encryption algorithm (aes-256-gcm or aes-256-cbc ). |
keyclass |
Integer category for encryption/decryption matching. |
created |
Creation timestamp in milliseconds since epoch. |
cipherKey |
Base64‑encoded 32‑byte key material. |
Optional fields
Field | Purpose |
---|---|
ivSize |
IV size in bytes (required for GCM; 12 is commonly used). |
useIV |
Enable/disable IV use for CBC mode. |
expires |
Expiration timestamp (ms). Expired keys are ignored during encryption. |
These field semantics govern how the crypto functions validate inputs and select keys at runtime. GCM provides authenticated encryption and is recommended for new deployments; CBC is available for interoperability and migration scenarios.
Deploying Keys in Kubernetes
Most teams mount keys.json
via a ConfigMap and point the agent at that directory. The example below creates a ConfigMap named crypto-keys
that contains the keys file.
apiVersion: v1
kind: ConfigMap
metadata:
name: crypto-keys
namespace: edgedelta
data:
keys.json: |
[
{
"keyId": "main-key",
"algorithm": "aes-256-gcm",
"keyclass": 1,
"created": 1726500000000,
"cipherKey": "YOUR_BASE64_KEY_HERE",
"ivSize": 12
}
]
Mount the ConfigMap into agent pods and set ED_CRYPTO_PATH
to the mount path. The snippet below shows typical Helm values.
edgeAgent:
envs:
- name: ED_CRYPTO_PATH
value: /crypto
volumeProps:
volumes:
- name: crypto-keys
configMap:
name: crypto-keys
defaultMode: 0444
volumeMounts:
- name: crypto-keys
mountPath: /crypto
readOnly: true
After applying your values, redeploy the chart. To verify the configuration is working, check the agent logs for successful startup and test encryption with a simple OTTL statement. Edge Delta pods use a minimal image without a shell, so traditional kubectl exec
commands are not available.
How the Functions Work
EDXEncrypt
is used to protect a plaintext string using a specific keyId
and keyclass
. On success it returns a marker‑wrapped value that contains the key ID, IV (if any), and ciphertext. On failure it returns a caller‑supplied mask value so that plaintext is never exposed. EDXDecrypt
reverses the operation when given a matching keyclass
and a properly formatted encrypted string. Both functions fail silently by design to avoid leaking operational details; you should verify behavior by inspecting outputs.
EDXEncrypt
EDXEncrypt
protects sensitive string values using field-level encryption. The function selects an encryption key based on the provided key ID and class, then returns either an encrypted value or a mask value on failure. The encrypted format preserves the key ID for future decryption while preventing plaintext exposure even when encryption cannot proceed.
Syntax
EDXEncrypt(value, keyclass, keyId, maskValue)
value
: The string to encrypt. Can be a field reference likebody["user"]["ssn"]
or a literal string.keyclass
: An integer (1-99) that must match the key’s configured class. Used to scope encryption to specific data categories.keyId
: The unique identifier of the encryption key to use, as defined in yourkeys.json
file.maskValue
: A fallback string returned when encryption fails. Should obscure the data format (e.g.,"<SSN-MASKED>"
instead of"XXX-XX-XXXX"
).
Return Value
- Success: Returns encrypted string in the format
#<keyId>|<base64_iv>|<base64_ciphertext>#
- Failure: Returns the provided
maskValue
when the key is missing, expired, has wrong keyclass, or has invalid configuration
set(attributes["enc_ssn"], EDXEncrypt(body["user"]["ssn"], 1, "gcm-key-001", "<SSN-MASKED>"))
This statement reads a social security number from the parsed event body and writes an encrypted representation to an attribute while masking on failure. The same pattern can be repeated for multiple fields, potentially with different keys per environment.
set(attributes["enc_cc"], EDXEncrypt(body["user"]["credit_card"], 1, "gcm-key-001", "<CC-MASKED>"))
set(attributes["enc_email"], EDXEncrypt(body["user"]["email"], 2, "cbc-key-001", "<EMAIL-MASKED>"))
set(attributes["enc_data"], EDXEncrypt(body["sensitive_data"], 1, body["environment"] == "prod" ? "prod-key" : "dev-key", "<REDACTED>"))
EDXDecrypt
EDXDecrypt
restores original values from encrypted strings produced by EDXEncrypt. The function automatically extracts the key ID from the encrypted format and uses it along with the provided keyclass to locate the correct decryption key. To maintain JSON safety, the decrypted value is returned as a double-escaped string.
Syntax
EDXDecrypt(encryptedValue, keyclass)
encryptedValue
: The encrypted string to decrypt, in the format#<keyId>|<base64_iv>|<base64_ciphertext>#
. Typically a field reference likeattributes["enc_ssn"]
.keyclass
: An integer (1-99) that must match the key’s configured class. Used to verify you have authorization to decrypt this data category.
Return Value
- Success: Returns the original string, double-escaped for JSON safety (e.g.,
"\"123-45-6789\""
) - Failure: Returns the original encrypted value unchanged when the key is missing, has wrong keyclass, or the encrypted format is invalid
set(attributes["decrypted_ssn"], EDXDecrypt(attributes["enc_ssn"], 1))
If a downstream system requires an unescaped string, add a follow‑up transformation to normalize the output. The example below strips backslashes produced by JSON escaping.
set(attributes["decrypted_ssn_clean"], ReplaceAllStrings(attributes["decrypted_ssn"], "\\", ""))
Field Paths in OTTL
When a pipeline uses log_parsing_mode: full
, the incoming JSON payload is parsed into body[...]
. Point your statements at the correct path for each field. If you use a JSON parser processor with a custom output_field
, adjust the OTTL paths accordingly. Consistent field paths between processors and OTTL statements are essential for predictable results.
set(attributes["encrypted_ssn"], EDXEncrypt(body["user"]["ssn"], 1, "gcm-key-001", "<SSN-MASKED>"))
End‑to‑End Example
The snippet below shows a minimal configuration that parses JSON from a production namespace, encrypts multiple user fields, removes plaintext from the event, and forwards data to a secure destination.
- name: kubernetes_input_e389
type: kubernetes_input
user_description: Kubernetes Log Source
include:
- k8s.namespace.name=production
log_parsing_mode: full
With the input defined, add an OTTL transform that copies original values for audit (optional) and encrypts sensitive fields. Note that the encrypted values are stored in attributes while the original body fields remain unchanged, so you may need to use additional processors to remove sensitive data from the body or route based on attributes instead of body fields.
- name: sequence_c5fa
type: sequence
user_description: Multi Processor
processors:
- type: ottl_transform
metadata: '{"id":"lHY_GJcE5wmCaNheRRHrz","type":"ottl_transform","name":"Custom
Processor"}'
data_types:
- log
statements: |-
set(attributes["original_ssn"], body["user"]["ssn"]) where body["user"]["ssn"] != nil
set(attributes["enc_ssn"], EDXEncrypt(body["user"]["ssn"], 1, "prod-key", "<SSN-MASKED>")) where body["user"]["ssn"] != nil
set(attributes["enc_credit_card"], EDXEncrypt(body["user"]["credit_card"], 1, "prod-key", "<CC-MASKED>")) where body["user"]["credit_card"] != nil
For authorized downstream processing, you can add a separate processor to decrypt specific fields when needed:
- name: sequence_auth_decrypt
type: sequence
user_description: Authorized Decryption
processors:
- type: ottl_transform
metadata: '{"id":"decrypt_auth","type":"ottl_transform","name":"Decrypt for Authorized Use"}'
data_types:
- log
statements: |-
set(attributes["decrypted_ssn"], EDXDecrypt(attributes["enc_ssn"], 1)) where attributes["authorized"] == true and attributes["enc_ssn"] != nil
set(attributes["decrypted_ssn_clean"], ReplaceAllStrings(attributes["decrypted_ssn"], "\\", "")) where attributes["decrypted_ssn"] != nil
This processor decrypts the SSN only when an authorized
attribute is true. The second statement removes the double-escaping from the decrypted value to provide a clean string for downstream use.
Send the transformed events to the Edge Delta Platform or your destination in OpenTelemetry format so attributes are preserved. When using use_legacy_formatting: false
, attributes are included within the OTel payload structure rather than at the top level of the JSON, ensuring they are properly transmitted to downstream systems.
- name: http_output_691f
type: http_output
user_description: HTTP Destination
endpoint: https://secure-log-store.example.com
use_legacy_formatting: false
Input and Output Samples
This input illustrates typical fields you might protect during ingestion.
{
"user": {
"ssn": "214-16-0214",
"credit_card": "4111-0214-1498-2782",
"email": "user14@example.com"
}
}
After the OTTL statements run, encrypted attributes appear alongside other metadata. Encrypted values take the form #keyId|iv|ciphertext#
. If a value shows the mask (for example, <SSN-MASKED>
), encryption did not proceed due to validation.
{
"attributes": {
"enc_ssn": "#gcm-key-001|ixpb3NRQfrsV+bPR|qKCj8Vr1SjRBE6A6lIRVtOyHPrlemQMDQpsM#",
"enc_credit_card": "#gcm-key-001|JhQrm2bGM8+svrHw|Mx8yg/iXwqQuWGoZ0rU8ETUk3JRFiao6BgR/6yu93L1ybJE=#",
"enc_email": "#cbc-key-001|ycGiY/KyaFVot82+1Oxjyg==|VWed3mk0QClv0FDj9tpWYAzcxL4ae6mmLTmq96BjXCo=#"
}
}
If you decrypt, expect a double‑escaped result. Apply a follow‑up normalization step if a plain string is required by downstream systems.
{
"attributes": {
"enc_ssn": "#prod-key|ixpb3NRQfrsV+bPR|qKCj8Vr1SjRBE6A6lIRVtOyHPrlemQMDQpsM#",
"decrypted_ssn": "\"214-16-0214\"",
"decrypted_ssn_clean": "214-16-0214"
}
}
The decrypted_ssn
shows the double-escaped format with extra quotes and backslashes, while decrypted_ssn_clean
shows the result after normalization with ReplaceAllStrings
.
Algorithm Variations
AES‑256‑GCM is typically used with a 12‑byte IV and is recommended for new deployments. If ivSize
is missing from a GCM key, encryption returns the mask value. AES‑256‑CBC is available with or without an IV; when no IV is used, the encrypted format contains an empty IV field between the pipes.
set(attributes["ssn_gcm"], EDXEncrypt(body["user"]["ssn"], 1, "gcm-14", "<MASK>"))
set(attributes["ssn_cbc_iv"], EDXEncrypt(body["user"]["ssn"], 2, "cbc-iv", "<MASK>"))
set(attributes["ssn_cbc_noiv"], EDXEncrypt(body["user"]["ssn"], 2, "cbc-no-iv", "<MASK>"))
These statements illustrate the expected output formats across algorithms and IV strategies, allowing you to validate behavior quickly in a test environment.
Key Expiration
Keys can be rotated and retired by adding an expires
timestamp. When a key is past its expiration, EDXEncrypt
returns the provided mask value. This allows you to keep stale material in the file for legacy decryption while preventing new encryption with that key.
[
{
"keyId": "active-key",
"algorithm": "aes-256-gcm",
"keyclass": 1,
"created": 1726600000000,
"cipherKey": "...",
"ivSize": 12
},
{
"keyId": "expired-key",
"algorithm": "aes-256-gcm",
"keyclass": 3,
"created": 1000000000000,
"expires": 1000000000000,
"cipherKey": "...",
"ivSize": 12
}
]
With these keys in place, encrypting with active-key
succeeds, while expired-key
yields the mask. There are no explicit error logs for crypto operations, so confirm behavior by inspecting outputs.
Best Practices
- Plan for key rotation and environment separation to limit exposure if a key is compromised and prevent production keys from being used in development.
- Store key material in secure Kubernetes objects (Secrets or protected ConfigMaps) rather than in application code or unprotected files to maintain proper access controls.
- Encrypt only data that truly requires protection to minimize performance overhead and reduce the complexity of key management.
- Prefer GCM for authenticated encryption because it provides both confidentiality and integrity protection, detecting any tampering attempts.
- Provide meaningful mask values that don’t expose data patterns, such as using
<SSN-MASKED>
instead ofXXX-XX-XXXX
which reveals the format. - Restrict decryption to points where it is strictly necessary since every decryption point is a potential exposure risk that should be minimized.
- Build lightweight auditing around encryption and decryption operations (without logging sensitive values) to track when and where sensitive data is being processed.
- Remember that crypto operations are intentionally quiet—no error logs are generated to prevent information leakage through log files.
- For gradual rollouts or migration from other systems, operate old and new keys side by side to ensure continuity during the transition period.
- Encrypt new data with the new system while retaining legacy keys for historical decryption to maintain access to existing data during migration.
- When re-processing historical data, proceed in batches and validate decryption before removing the legacy system to prevent data loss from premature system retirement.
Troubleshooting
Pre-flight Checks
- Verify the mount path matches
ED_CRYPTO_PATH
- Confirm
keys.json
is present and correctly formatted as an array - Ensure OTTL field paths match your parsing strategy (e.g.,
body[...]
withlog_parsing_mode: full
) - Check that encrypted outputs follow the
#keyId|iv|ciphertext#
format - If needed, normalize decrypted strings for downstream systems
Key Loading
- Encryption keys are loaded at agent startup only—there is no hot reload capability
- You must restart agents after updating the ConfigMap to ensure changes are picked up
If Values Come Back Masked
Common causes when encryption returns the mask value:
- Missing key with the specified
keyId
- Wrong
keyclass
(doesn’t match the key’s configuration) - Invalid base64 key material in
cipherKey
- For GCM algorithms: missing
ivSize
field in the key configuration
If Nothing Encrypts
When encryption doesn’t work at all:
- Confirm
ED_CRYPTO_PATH
environment variable is set - Verify the file is mounted at the correct path
- Test with a minimal statement that encrypts a fixed string:
set(attributes["crypto_smoke_test"], EDXEncrypt("test-value", 1, "your-key-id", "MASK"))
Feedback Loop Issues
- If your destination’s logs are being re-ingested, exclude the destination pod in your input configuration
- Add exclusion rules like
k8s.pod.name=http-server.*
to prevent loops
Summary
EDXEncrypt
and EDXDecrypt
provide reliable, stream‑friendly protection for sensitive fields inside Edge Delta Telemetry Pipelines. With a simple key file, a small amount of OTTL, and Kubernetes‑friendly deployment, you can protect data at the source and still retain the option to decrypt when authorized to do so.