Loading…
Loading…
Explains how the platform uses pgcrypto for symmetric AES-256 encryption of backend profile credentials. Covers configuration, data flow, and the storage
The CredentialsCipher service provides symmetric encryption for sensitive credentials associated with backend profiles. It ensures that credentials, such as API keys or tokens for upstream services, are stored securely at rest in the platform's database.
This component leverages the pgcrypto extension in PostgreSQL to perform all cryptographic operations directly within the database server. The encryption key is supplied to the service at startup and is never persisted, logged, or exposed in query plans.
The CredentialsCipher service is an internal component of the Admin API. It is invoked when API publishers create or update backend profiles that require credentials. Its primary functions are to encrypt plaintext credentials before they are written to the database and to decrypt them when they are read for use or management.
The service manages the entire lifecycle of credential encryption, from startup validation to runtime operations.
On application startup, the CredentialsCipher component performs a critical preflight check to ensure it is configured correctly and the database environment is ready.
IMPORTANT
The service requires the S2R_CREDENTIAL_KEY environment variable to be set. If this variable is missing or blank, the application will fail to start and log the error code S2R-ADM-0419. This is a fail-safe mechanism to prevent the use of weak or default keys.
The startup sequence is as follows:
S2R_CREDENTIAL_KEY environment variable.{"probe":"s2r-preflight"}).pgcrypto extension is available and functioning correctly in the database.pgcrypto.S2R-ADM-0419 with details about the cause (e.g., pgcrypto unavailable or key invalid).CredentialsCipher preflight OK.When a caller needs to store a credential, the encrypt method is used.
select encode(pgp_sym_encrypt(?, ?), 'base64') against the database.pg_stat_statements, or query execution plans.pgp_sym_encrypt function encrypts the data using the AES-256 algorithm. The result is a bytea (byte array).encode function then converts this bytea ciphertext into a Base64-encoded string.CredentialsCipher service wraps this Base64 string in a JSON object called an "envelope," which includes metadata about the encryption.jsonb database column.When a stored credential needs to be accessed, the decrypt method is used.
enc field. The parser is designed to defensively handle malformed input or raw ciphertext strings that are not wrapped in an envelope.select pgp_sym_decrypt(decode(?, 'base64'), ?) against the database.decode function converts the Base64 string back into a bytea.pgp_sym_decrypt function uses the key to decrypt the bytea, returning the original plaintext JSON string.The following diagram illustrates the encryption and decryption flows.
flowchart TD
subgraph Encryption
direction LR
A[Admin API with<br/>Plaintext Credential] --> B(CredentialsCipher Service);
B -- "1. encrypt(plaintext)" --> C{PostgreSQL Database};
C -- "2. pgp_sym_encrypt" --> D[Base64 Ciphertext];
D -- "3. Wrap in envelope" --> B;
B -- "4. Return JSON envelope" --> E[Stored in DB];
end
subgraph Decryption
direction LR
F[Admin API reads<br/>Encrypted Credential] --> G(CredentialsCipher Service);
G -- "1. decrypt(envelope)" --> H{PostgreSQL Database};
H -- "2. pgp_sym_decrypt" --> I[Plaintext Credential];
I -- "3. Return plaintext" --> G;
G -- "4. Return to caller" --> F;
endThe CredentialsCipher service is configured via a single, mandatory property, which can be provided as a Spring property or an environment variable.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
s2r.credential.key | String | — | Yes | The symmetric encryption key. Can also be set via the S2R_CREDENTIAL_KEY environment variable, which takes precedence. The application will not start if this key is not provided. |
S2R_CREDENTIAL_KEY | String | — | Yes | The symmetric encryption key, provided as an environment variable. The application will not start if this key is not provided. |
Encrypted credentials are stored in the database as a JSON object, referred to as an "envelope." This format provides versioning and algorithm information alongside the ciphertext.
Example Envelope:
{
"v": 1,
"alg": "pgcrypto-aes256",
"enc": "<base64 pgp_sym_encrypt output>"
}
The fields of the envelope are described below.
| Field | Type | Description |
|---|---|---|
v | Integer | The version of the envelope format. Currently 1. |
alg | String | The encryption algorithm used. The only supported value is pgcrypto-aes256. |
enc | String | The Base64-encoded ciphertext generated by pgp_sym_encrypt. |
The CredentialsCipher component exposes several Java methods for internal use by the Admin API.
| Method | Description |
|---|---|
String encrypt(String plaintextJson) | Encrypts a plaintext JSON string and returns the full JSON envelope as a string. Returns null if the input is null. |
String decrypt(String envelopeJson) | Decrypts a JSON envelope string and returns the original plaintext JSON string. It can defensively handle bare ciphertext. Returns null for null or blank input. |
static boolean isEnvelope(String stored) | A static utility method that returns true if the given string is a well-formed encryption envelope. This is used to differentiate between encrypted credentials and legacy plaintext credentials during data migration. |
String encryptSecret(String plaintext) | A write-path helper that encrypts any non-blank input string. It always encrypts the input, even if it appears to be an existing envelope, to prevent crafted ciphertext from being stored. Throws an IllegalStateException if the encryption key is not configured. |
String decryptSecret(String stored) | A read-path helper that decrypts the input string if it is an envelope. If the input is not a valid envelope (i.e., it is a legacy plaintext value), it is returned unchanged. |
Map<String, Object> decryptToMap(String envelopeJson) | Decrypts an envelope string and parses the resulting plaintext JSON into a Map<String, Object>. Returns an empty map if the input is null, blank, or if parsing fails. |
The most common issues with this service occur at application startup.
| Error Code | Condition | Cause and Resolution |
|---|---|---|
S2R-ADM-0419 | Application fails to start. | Cause 1: The S2R_CREDENTIAL_KEY environment variable is not set or is empty. <br/> Resolution: Set the S2R_CREDENTIAL_KEY variable to a valid, non-empty string before starting the application. |
S2R-ADM-0419 | Application fails to start with a "preflight failed" or "round-trip mismatch" message. | Cause 2: The startup self-test could not complete successfully. This usually indicates one of two problems: <br/> 1. The pgcrypto extension is not installed or enabled in the PostgreSQL database. <br/> 2. The key provided in S2R_CREDENTIAL_KEY is invalid or not in a format that pgcrypto can use. <br/> Resolution: Check the application logs for the specific DataAccessException to identify the root cause. Ensure the pgcrypto extension is installed and available. Verify that the encryption key is correct. |
IllegalStateException | Attempt to store a credential at runtime fails. | Cause: The encryptSecret method was called when the encryption key was not available. While the preflight check should prevent this, this runtime check acts as a safeguard. <br/> Resolution: This indicates a misconfiguration. Ensure the application is started with the S2R_CREDENTIAL_KEY properly set. |
CredentialsCipher service depends on the PostgreSQL pgcrypto extension.isEnvelope and decryptSecret methods support a gradual migration from legacy plaintext credentials to encrypted ones. See the guide on migrating legacy credentials for more information.