LiJie20190102 opened a new issue, #13314:
URL: https://github.com/apache/gravitino/issues/13314

   ### Describe the feature
   
   Support encrypting sensitive configuration values (e.g., 
`gravitino.entity.store.relational.jdbcPassword`) in `gravitino.conf` so that 
plaintext passwords are not exposed in configuration files.
   
   Encrypted values are wrapped in `ENC(...)` format. The server transparently 
decrypts them at startup when a master encryption key is provided via 
environment variable or system property. Plain text values continue to work 
unchanged (backward compatible).
   
   ### Motivation
   
   Currently, `gravitino.conf` stores the JDBC password in plaintext:
   
   ```properties
   gravitino.entity.store.relational.jdbcPassword = gravitino
   ```
   
   This is a security risk in production deployments:
   
   - Anyone with read access to the config file can see the database password.
   - Config files are often committed to version control, copied to shared 
storage, or included in container images — all of which widen the exposure 
surface.
   - Auditing and compliance requirements (e.g., SOC2, GDPR) typically mandate 
that credentials at rest be encrypted.
   
   The password is read via 
`config.get(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD)` in 3 locations:
   
   - `H2Database.java:54` — embedded H2 initialization
   - `SqlSessionFactoryHelper.java:91` — MyBatis data source (MySQL/PostgreSQL)
   - `StatisticManager.java:116` — partition statistics storage
   
   None of these call sites perform any decryption today.
   
   ### Describe the solution
   
   **Approach: Java-native AES-256-GCM (no external dependencies)**
   
   Rather than introducing Jasypt (last updated 2014, requires Apache LICENSE 
review) or another third-party library, use the JDK built-in `javax.crypto` 
package. This keeps the dependency footprint at zero — important for an Apache 
project.
   
   ### Encryption design
   
   | Parameter             | Value                                              
              |
   | --------------------- | 
---------------------------------------------------------------- |
   | Cipher algorithm      | `AES/GCM/NoPadding` (256-bit key)                  
              |
   | Key derivation        | `PBKDF2WithHmacSHA256` (65536 iterations)          
              |
   | Salt                  | 16 random bytes (per-encryption)                   
              |
   | IV                    | 12 random bytes (per-encryption)                   
              |
   | Authentication tag    | 128 bits                                           
              |
   | Encrypted format      | `ENC(base64(salt[16] + iv[12] + ciphertext + 
tag))`             |
   
   Each encryption produces a different ciphertext (random salt + IV), and GCM 
provides integrity protection against tampering.
   
   ### Master key resolution
   
   The master encryption key is **never** stored in `gravitino.conf`. It is 
resolved at startup from (in priority order):
   
   1. Environment variable: `GRAVITINO_PASSWORD_ENCRYPTION_KEY`
   2. System property: `gravitino.password.encryption.key`
   
   ### User workflow
   
   ```bash
   # 1. Encrypt the password (choose either tool)
   python3 scripts/encrypt_password.py encrypt "myDbPassword" --key 
"mySecretMasterKey"
   # or
   java -cp gravitino-common-*.jar 
org.apache.gravitino.utils.PasswordEncryptorCLI \
     encrypt "myDbPassword" --key "mySecretMasterKey"
   # Output: ENC(xxxbase64xxx)
   
   # 2. Put the encrypted value in gravitino.conf
   gravitino.entity.store.relational.jdbcPassword = ENC(xxxbase64xxx)
   
   # 3. Set the master key as an environment variable and start the server
   export GRAVITINO_PASSWORD_ENCRYPTION_KEY="mySecretMasterKey"
   ./bin/gravitino.sh start
   ```
   
   ### Code changes
   
   | File                                              | Change   | Description 
                                                               |
   | ------------------------------------------------- | -------- | 
-------------------------------------------------------------------------- |
   | `common/.../utils/PasswordEncryptor.java`         | **New**  | AES-256-GCM 
encrypt/decrypt utility, `isEncrypted()`, `decryptIfNeeded()` |
   | `common/.../utils/PasswordEncryptorCLI.java`     | **New**  | Java CLI 
tool (`encrypt` / `decrypt` subcommands, interactive mode)        |
   | `common/.../utils/TestPasswordEncryptor.java`     | **New**  | 15 unit 
tests (roundtrip, wrong key, tampered ciphertext, backward compat) |
   | `core/.../database/H2Database.java`               | **Modified** | Wrap 
password read with `decryptIfNeeded()`                           |
   | `core/.../session/SqlSessionFactoryHelper.java`   | **Modified** | Wrap 
password read with `decryptIfNeeded()`                           |
   | `core/.../stats/StatisticManager.java`            | **Modified** | Wrap 
password read with `decryptIfNeeded()`                           |
   | `scripts/encrypt_password.py`                    | **New**  | Python 
encryption script (requires `cryptography` package), interoperable   |
   | `conf/gravitino.conf.template`                    | **Modified** | Add 
usage instructions for encrypted passwords                        |
   
   ### Backward compatibility
   
   - Plain text passwords work unchanged — `decryptIfNeeded()` returns the 
original value if it does not start with `ENC(`.
   - No configuration changes required for existing deployments.
   - If an encrypted value is detected but no master key is configured, the 
server fails fast with a clear error message.
   
   ### Cross-language interoperability
   
   The Python script and Java CLI produce/consume the same `ENC(base64(...))` 
format. Verified:
   
   - Java encrypt → Python decrypt ✅
   - Python encrypt → Java decrypt ✅
   
   ### Additional context
   
   **Why not Jasypt?**
   
   - Jasypt's last release was 1.9.3 in 2014; the project is inactive.
   - Default algorithm `PBEWithMD5AndDES` is weak (can be changed, but defaults 
matter).
   - Introducing a third-party dependency in an Apache project requires 
LICENSE/NOTICE review.
   - Java 17's built-in `javax.crypto` provides everything needed with zero 
overhead.
   
   **Future extensibility**
   
   - The same `PasswordEncryptor.decryptIfNeeded()` pattern can be applied to 
other sensitive config values (e.g., authenticator secrets, auxiliary service 
tokens) with minimal changes.
   - A `gravitino.password.encryption.algorithm` config key could be added 
later to allow pluggable algorithms if needed.
   
   **Scope of this issue**
   
   - The PR implements encryption for 
`gravitino.entity.store.relational.jdbcPassword` only.
   - Other password-like config keys can be added incrementally in follow-up 
PRs.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to