Edge Delta's EDXEncrypt and EDXDecrypt Extensions

Protect your data in transit with field-level encryption.

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 like body["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 your keys.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 like attributes["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 of XXX-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[...] with log_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.