shangxinli opened a new issue, #988:
URL: https://github.com/apache/iceberg-cpp/issues/988

   ### Feature Description
   
   **What the feature achieves:**
   Adds Iceberg table encryption to iceberg-cpp, wire-compatible with the Java 
implementation (`StandardEncryptionManager`). iceberg-cpp can read tables that 
Java encrypted, and Java can read encrypted tables that iceberg-cpp writes. 
Data files, delete files, deletion vectors, manifests and manifest lists are 
encrypted; `metadata.json` is not, as in Java.
   
   **Design: follow the Java scheme exactly**
   
   Java defines the formats, so the C++ design matches it rather than adding 
anything new:
   
   - **Key hierarchy (envelope encryption).** The table master key stays in the 
KMS (`encryption.key-id`). It wraps key encryption keys (KEKs), which are kept 
in the table metadata `encryption-keys` with a `KEY_TIMESTAMP` and rotated 
every 730 days. A KEK AES-GCM-encrypts the key metadata of each manifest list 
(AAD = KEK timestamp); the snapshot `key-id` points to that entry. Manifest 
DEKs live in the manifest list `key_metadata`, and data/delete file DEKs live 
in the manifest `key_metadata`, both in plaintext inside an encrypted file.
   - **Key metadata.** `StandardKeyMetadata`: version byte `1` followed by an 
Avro record `{encryption_key, aad_prefix, file_length}`. The stored length is 
recorded for every encrypted file, including Parquet, because Java requires it 
for AGS1 reads (iceberg-rust found this in 
[apache/iceberg-rust#3236](https://github.com/apache/iceberg-rust/pull/3236)).
   - **File formats.** Parquet data and delete files use Parquet modular 
encryption (encrypted footer, `PARE`, footer key = DEK, AAD prefix not stored 
in the file). Manifests, manifest lists, Avro data files and Puffin deletion 
vectors use AES GCM Streams (`AGS1`, 
[spec](https://github.com/apache/iceberg/blob/main/format/gcm-stream-spec.md)).
   - **Trust model.** Encryption requires format v3. The table key ID cannot 
change once set. Catalogs must take the key ID from a trusted source, not only 
from `metadata.json`; the REST catalog creates its KMS client from client-side 
properties only.
   
   **Library layering:** the core library `iceberg` stays free of crypto 
dependencies. It defines the interfaces (`KeyManagementClient`, 
`EncryptionManager`, `EncryptingFileIO`) and reaches the implementations 
through registries, the same pattern as `ReaderFactoryRegistry` and 
`FileIORegistry`. The bundle library `iceberg_bundle` (Arrow, Avro, Parquet) 
adds OpenSSL and the standard implementation. Cloud KMS clients are an optional 
library, `iceberg_kms`.
   
   **Why this feature is needed:**
   Problem: encrypted Iceberg tables written by Java (Spark, Flink, Trino with 
the Java library) cannot be used from C++ engines today.
   
   Current gaps in iceberg-cpp:
   - Table metadata `encryption-keys` is parsed, but there is no snapshot 
`key-id`, so encrypted snapshots cannot be resolved
   - No crypto: no AES-GCM, no AGS1 streams, no key metadata, no KMS integration
   - The vendored Arrow builds Parquet with encryption compiled out 
(`encryption_internal_nossl.cc`)
   - Writers have no guard: writing to an encrypted table would silently write 
plaintext files
   - Table-level encryption is listed as "not yet planned" in the v3 roadmap 
([#959](https://github.com/apache/iceberg-cpp/issues/959))
   
   Other implementations: Java has the core in main (Hive catalog wired end to 
end); iceberg-rust has merged an 18-step PR series 
([apache/iceberg-rust#2034](https://github.com/apache/iceberg-rust/issues/2034));
 Python and Go have started.
   
   ### User Experience
   
   **How users will use this feature:**
   
   Build:
   ```cmake
   # Bundle library with encryption (ON by default; requires OpenSSL)
   -DICEBERG_ENCRYPTION=ON
   # Optional: AWS KMS, Google Cloud KMS and Azure Key Vault clients
   -DICEBERG_BUILD_KMS=ON
   ```
   
   Configuration (same property names as Java):
   
   | Level | Property | Description |
   |---|---|---|
   | Catalog | `encryption.kms-type` | Built-in KMS client: `aws`, `gcp` or 
`azure` |
   | Catalog | `encryption.kms-impl` | Name of a KMS client registered with 
`KmsRegistry::Register` |
   | Table | `encryption.key-id` | ID of the table master key in the KMS; 
enables encryption |
   | Table | `encryption.data-key-length` | Length of data keys: 16 (default), 
24 or 32 bytes |
   
   Usage example:
   ```cpp
   // Built-in client from the (client-side) catalog properties, e.g. for REST:
   //   encryption.kms-type = aws
   //   client.region       = us-west-2
   auto catalog = iceberg::rest::RestCatalog::Make(config);
   
   // Or a custom KMS client
   class MyKms : public iceberg::KeyManagementClient { /* WrapKey / UnwrapKey 
*/ };
   iceberg::KmsRegistry::Register("my-kms", [] { return 
std::make_unique<MyKms>(); });
   
   // Tables loaded from the catalog get an EncryptingFileIO; scans, writers and
   // commits encrypt and decrypt transparently. No API changes for 
readers/writers.
   ```
   
   Read/write behavior:
   - Scans of encrypted tables decrypt manifest lists, manifests, data files, 
delete files and deletion vectors
   - Commits write encrypted files and add the new KEK and manifest list key 
entries to `encryption-keys`
   - A catalog without a KMS client can load an encrypted table but fails 
cleanly on read or commit
   - A wrong key fails with an error, not an exception or garbage data
   
   ### Known Limitations (not supported)
   
   These follow the Java implementation, which does not support them yet either:
   - **Column-level encryption** (per-column keys) — 
[apache/iceberg#14768](https://github.com/apache/iceberg/issues/14768)
   - **Encrypted table and partition statistics files** — 
[apache/iceberg#17533](https://github.com/apache/iceberg/pull/17533)
   - **Removing unused keys when snapshots expire** — 
[apache/iceberg#16352](https://github.com/apache/iceberg/issues/16352)
   - **Re-wrapping keys with a new master key** — 
[apache/iceberg#18240](https://github.com/apache/iceberg/issues/18240)
   - **REST KMS credential vending** — 
[apache/iceberg#17155](https://github.com/apache/iceberg/pull/17155)
   - **Hive catalog**: table operations are not implemented in iceberg-cpp yet, 
so there is nothing to wire
   - **Cloud credential providers**: the KMS clients take explicit credentials 
or tokens; instance profiles, Google application default credentials and Azure 
managed identities are not supported (register a custom client instead)
   
   API Changes:
   
   New public APIs:
   ```cpp
   // Snapshot
   std::optional<std::string> Snapshot::key_id;  // JSON "key-id"
   
   // Key management
   class KeyManagementClient {
     virtual Result<std::vector<uint8_t>> WrapKey(std::span<const uint8_t> key,
                                                  std::string_view 
wrapping_key_id) = 0;
     virtual Result<std::vector<uint8_t>> UnwrapKey(std::span<const uint8_t> 
wrapped_key,
                                                    std::string_view 
wrapping_key_id) = 0;
   };
   class KmsRegistry;               // Register / Load / FromCatalogProperties
   
   // Encryption
   class EncryptionManager;         // StandardEncryptionManager in the bundle 
library
   class EncryptionManagerRegistry;
   class EncryptingFileIO;          // FileIO wrapper used by tables
   struct StandardKeyMetadata;
   
   // Reader/writer options
   ReaderOptions::key_metadata;
   WriterOptions::key_metadata;
   ```
   
   ### Spec and Compatibility
   
   - Does this change public interfaces/APIs? **Yes** — new classes above, 
`Snapshot::key_id`, `ReaderOptions`/`WriterOptions::key_metadata`
   - Does this change storage format? **No** — it implements the existing v3 
spec fields (`encryption-keys`, snapshot `key-id`, `key_metadata`) and the AGS1 
spec, byte-compatible with Java
   - Interoperability is verified both ways with Java:
     - C++ reads a Java-written encrypted table (AGS1 manifest lists and 
manifests, PARE Parquet, AGS1 Avro data files, an encrypted deletion vector)
     - Java reads a C++-written encrypted table and decrypts its deletion vector
   - Small Java-generated fixtures (key metadata, AGS1 vectors, a wrapped KEK, 
a small encrypted table) are checked in as test resources
   
   ### Task Breakdown
   
   Proposed as a stacked PR series, following the order of the iceberg-rust 
series. A working implementation of every step is on 
[shangxinli/iceberg-cpp](https://github.com/shangxinli/iceberg-cpp/branches) 
(branches `enc/01` … `enc/17`, each built on the previous one, all passing CI 
locally).
   
   **Phase 1: Foundations (no behavior change)**
   - [ ] 1. Build: Parquet modular encryption and OpenSSL in the vendored Arrow 
(plus zstd) — `enc/01-build`
   - [ ] 2. `Snapshot::key_id` and JSON `key-id` — `enc/02-snapshot-key-id`
   - [ ] 3. `StandardKeyMetadata` with Java test vectors — `enc/03-key-metadata`
   - [ ] 4. AES-GCM cipher and AGS1 input/output files — `enc/04-aes-gcm-stream`
   - [ ] 5. `KeyManagementClient`, `KmsRegistry`, `InMemoryKms` (tests only) — 
`enc/05-kms`
   - [ ] 6. `EncryptionManager` and `StandardEncryptionManager` (KEK rotation, 
1-hour unwrap cache) — `enc/06-encryption-manager`
   - [ ] 7. `EncryptingFileIO` — `enc/07-encrypting-file-io`
   
   **Phase 2: Read path**
   - [ ] 8. Read encrypted manifest lists — `enc/08-read-manifest-list`
   - [ ] 9. Read encrypted manifests — `enc/09-read-manifest`
   - [ ] 10. Read encrypted Parquet/Avro data and delete files — 
`enc/10-read-data-files`
   
   **Phase 3: Write path**
   - [ ] 11. Block writes to encrypted tables until supported (no silent 
plaintext) — `enc/11-block-writes`
   - [ ] 12. KMS from catalog properties; in-memory, SQL and REST catalog 
wiring with a trusted key ID — `enc/12-catalog-wiring`
   - [ ] 13. Write encrypted manifests and manifest lists (stored length 
through the Avro writer) — `enc/13-write-manifests`
   - [ ] 14. Write encrypted data and delete files — `enc/14-write-data-files`
   - [ ] 15. Encrypted Puffin deletion vectors — `enc/15-puffin-dv`
   - [ ] 16. Enable encrypted writes, end-to-end commit test, docs; also fixes 
manifest `partition-spec` metadata to be the fields array, which Java requires 
— `enc/16-enable-writes`
   
   **Phase 4: KMS clients**
   - [ ] 17. AWS KMS (SigV4), Google Cloud KMS and Azure Key Vault clients in 
optional `iceberg_kms` — `enc/17-kms-clients`
   
   Feedback welcome on the library layering, the registry approach and the PR 
split before the PRs are opened.
   
   ### Related Issues
   - [#959](https://github.com/apache/iceberg-cpp/issues/959) — Track remaining 
Iceberg v3 feature support (table-level encryption metadata)
   - 
[apache/iceberg-rust#2034](https://github.com/apache/iceberg-rust/issues/2034) 
— Support client-side encryption (iceberg-rust epic)
   - 
[apache/iceberg-rust#3236](https://github.com/apache/iceberg-rust/pull/3236) — 
File length in key metadata for Java interop
   - [apache/iceberg#13225](https://github.com/apache/iceberg/pull/13225) — 
REST catalog encryption support (Java)
   - [apache/iceberg#16527](https://github.com/apache/iceberg/pull/16527) — Key 
metadata format in the spec
   - [apache/iceberg#16157](https://github.com/apache/iceberg/issues/16157) — 
Reading encrypted deletion vectors (Java)
   


-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to