Repository navigation
Aliyun: Add AliyunKeyManagementClient for client-side encryption - #18210
Conversation
Add an Alibaba Cloud KMS implementation of the KeyManagementClient SPI so tables using Iceberg envelope encryption can wrap/unwrap data keys with Aliyun KMS, at parity with AwsKeyManagementClient. The aliyun module previously supported only OSS. - AliyunKeyManagementClient: wrapKey, unwrapKey, and server-side generateKey; supportsKeyGeneration() returns true. - AliyunClientFactory#newKmsClient: build the KMS client from client.region, resolving credentials from access key/secret/token and falling back to the default chain (env vars, RRSA/OIDC, ECS RAM role). - AliyunProperties: add client.region and kms.data-key-spec (default AES_256). - Build: add com.aliyun:kms20160120 (compileOnly) and mockito-core (test). - Tests: cover wrap/unwrap mapping, key generation, and region-based endpoint resolution.
- Add kms.client.aliyun.key.generation.enabled (default true) to drive supportsKeyGeneration(); reject wrapKey when generation is enabled. - unwrapKey verifies the wrapping key id (ids/ARNs checked, aliases skipped). - Issue KMS calls via *WithOptions with configurable retry/timeouts; cache client + options in ClientState. - Resolve KMS credentials like newOSSClient (RRSA/OIDC, then static keys). - Rename kms.data-key-spec to kms.client.aliyun.generation.data_key_spec. - Tests: key-id verification, wrapKey rejection, retry wiring, serialization.
Add a client.kms-endpoint property that overrides the region-derived kms.<region>.aliyuncs.com endpoint, so keys stored in a KMS Instance (DKMS) endpoint can be used. Defaults to the region-based endpoint when unset.
|
Per #15272 , do you want to add the new KMS type? |
|
|
||
| @Override | ||
| public ByteBuffer wrapKey(ByteBuffer key, String wrappingKeyId) { | ||
| if (enableKeyGeneration) { |
There was a problem hiding this comment.
today the generateKey is not exercised in the iceberg code base, if enableKeyGeneration is default to true , the wrapKey called by StandardEncryptionManager.keyEncryptionKeyID() will always throw exception
|
|
||
| /** | ||
| * Enables server-side data key generation. When enabled (the default), Iceberg calls {@link | ||
| * #generateKey(String)}; set to {@code false} to have Iceberg generate keys locally and {@link |
There was a problem hiding this comment.
iceberg does not seem to call generateKey in existing code base today
StandardEncryptionManager always wrapped new key encryption keys via KeyManagementClient.wrapKey, so supportsKeyGeneration()/generateKey() were never consulted. This breaks KMS clients whose wrapKey rejects calls when key generation is enabled. Branch key encryption key creation on kmsClient.supportsKeyGeneration(): generate via KMS when supported, otherwise generate locally and wrap. AwsKeyManagementClient now reports supportsKeyGeneration() = false so it keeps using wrapKey; its behavior is unchanged. Aliyun: - Register "aliyun" as a pre-defined encryption.kms-type. - Default key generation to false so the client uses wrapKey by default, consistent with AWS/Azure/GCP; generateKey is opt-in via kms.client.aliyun.key.generation.enabled=true. - Move the KMS client property keys and defaults into AliyunProperties and read them through it, matching AwsProperties; hyphenate the data key spec property (kms.client.aliyun.generation.data-key-spec) and document that it sizes the key encryption key on the generateKey path. - Bump credentials-java to 1.0.3 to match the KMS SDK's requirement.
|
@ggershinsky @dramaticlly Comments addressed. Please take another look. Thanks. |
| @Override | ||
| public boolean supportsKeyGeneration() { | ||
| return true; | ||
| return false; |
There was a problem hiding this comment.
This is changed probably because the encryption manager now use generate key instead of wrapping key if keyGeneration is supported. Capability wise AWS KMS does support the key generation, so we just need a graceful way to rollout the behavior change, maybe a devlist email if we want to bundle this together in this PR, @ggershinsky what do you think?
There was a problem hiding this comment.
The reason why I made the default value false is that I want to keep the behavior consistent after the change. Shall we make this configurable in this PR or a separate PR?
There was a problem hiding this comment.
Yep, it was my suggestion to add leveraging key generation here, but I missed the existing support in AwsKms, which makes this a behavior change. The generation leverage probably should be implemented in a separate PR.
There was a problem hiding this comment.
In that case @mengna-lin let's just focus on AliyunKeyManagementClient change for now, I would suggest to leave core/src/main/java/org/apache/iceberg/encryption/StandardEncryptionManager.java and aws/src/main/java/org/apache/iceberg/aws/AwsKeyManagementClient.java unchanged for now and handle it in a separate PR
There was a problem hiding this comment.
@dramaticlly I have made this PR that Aliyun always goes with wrapKey. AliyunKeyManagementClient supportsKeyGeneration is hardcoded to be false. The same as AwsKeyManagementClient.
Also update the scope of #18300.
Following review, scope this PR to the Aliyun KMS client only: - Revert the StandardEncryptionManager key-generation branch and the AwsKeyManagementClient supportsKeyGeneration change; wiring StandardEncryptionManager to use generateKey is deferred to a separate PR. - Hardcode AliyunKeyManagementClient.supportsKeyGeneration() to false and remove the enable/disable flag (kms.client.aliyun.key.generation.enabled) and the wrapKey guard, so the client always wraps keys via KMS. generateKey and the data key spec property are kept as a dormant capability, mirroring AwsKeyManagementClient.
Set kms.client.aliyun.generation.data-key-spec to AES_128 (non-default) in testKmsClientSerialization and assert the round-tripped client sends it, so the test verifies a non-default spec survives serialization.
StandardEncryptionManager always wraps key encryption keys via wrapKey and never calls generateKey, so the Aliyun client's generateKey() / supportsKeyGeneration() override and the kms.client.aliyun.generation.data-key-spec property were dead code. Remove them, leaving a wrap-only client consistent with the Azure and GCP KMS clients.
|
An obvious observation - out of the platform modules in Iceberg, AWS, GCP and Azure already have a built-in KMS client. One should be added for Aliyun too, of course. |
|
I'd suggest tracking #18337, since it will affect this one. |
The scope of this PR does not cover key generation. I will tackle it in a separate PR. Thanks for the reminder. |
|
@ggershinsky @dramaticlly I have addressed all comments and applied real env tests. Real env tests details are in PR description. Please take another look when you get a chance. Thanks! |
Makes sense, the PR split will make it easier for the community to review this. |
ggershinsky
left a comment
There was a problem hiding this comment.
Thanks @mengna-lin ! Encryption-wise, LGTM.
|
Tagging committers @RussellSpitzer @singhpk234 @huaxingao |
There was a problem hiding this comment.
Thanks Mengna for the Aliyun KMS work and E2E verification in a real aliyun environment. I think this complement our encryption support on all major cloud provider.
@nastra, I saw you helped a few aliyun change in the past, do you want to take a look as well?
singhpk234
left a comment
There was a problem hiding this comment.
LGTM too, seems like a straight forward impl for the CSP
Make the new SPI method a default throwing UnsupportedOperationException so existing custom factories wired via client.factory-impl keep compiling. Generated-by: Claude Code
|
@huaxingao @singhpk234 thanks for the review, wondering if this one is ready to merge? |
|
Thanks for the contribution @mengna-lin and thanks for review @ggershinsky @dramaticlly @mengna-lin |
What
Adds
AliyunKeyManagementClient, an Aliyun KMS implementation of theKeyManagementClientSPI, so tables using Iceberg's envelope encryption can wrap/unwrap data keys with Alibaba Cloud KMS..Why
The
aliyunmodule previously supported only OSS; there was no way to use Iceberg client-side (envelope) encryption on Alibaba Cloud. This closes that gap.Scope
This PR adds a wrap-only KMS client (
wrapKey/unwrapKey). Server-side key generation (generateKey/GenerateDataKey) is intentionally out of scope here — Iceberg'sStandardEncryptionManageralways creates key encryption keys viawrapKeyand never callsgenerateKey, so wiring that path is a separate, core-level change tracked in #18300.Changes
AliyunKeyManagementClient—wrapKey(KMSEncrypt) andunwrapKey(KMSDecrypt). On unwrap, the wrapping key is verified against theDecryptresponse for immutable references (bare key id / key ARN); aliases are skipped (a mutable pointer may differ from the key that wrapped older data). The client is built lazily from serializable properties so it survives Java/Kryo serialization to Spark/Flink executors.AliyunClientFactories#newKmsClient— builds the KMS client fromclient.region(the SDK resolveskms.<region>.aliyuncs.com, the same region-based model AWS uses), with an optionalclient.kms-endpointoverride for a KMS Instance (DKMS). Credential resolution follows the same order asnewOSSClient:ALIBABA_CLOUD_OIDC_PROVIDER_ARN,ALIBABA_CLOUD_ROLE_ARN,ALIBABA_CLOUD_OIDC_TOKEN_FILE). Takes precedence over the static access-key properties.client.access-key-id/client.access-key-secret(+ optionalclient.security-token).AliyunProperties— addsclient.region,client.kms-endpoint, and KMS client tuning (kms.client.aliyun.max.attempts,kms.client.aliyun.connect.timeout.ms,kms.client.aliyun.read.timeout.ms).aliyunas a pre-definedencryption.kms-type(alongsideaws/azure/gcp).com.aliyun:kms20160120(aliyun-sdk-kms2.3.0) ascompileOnly; bumpscredentials-javato 1.0.3 andteato match the KMS SDK's transitive requirement; addsmockito-coreandkryoto the aliyun test scope.encryption.mdlistsaliyunas a supportedencryption.kms-type.TestAliyunKeyManagementClientcovers wrap/unwrap mapping, selection viaencryption.kms-type=aliyun, wrapping-key verification (bare id / ARN / alias), and Java+Kryo serialization;TestAliyunClientFactoriescovers region-based endpoint resolution and the missing-region error.Configuration
Activate encryption on a table with the master key id (stored in Aliyun KMS):
Testing
./gradlew :iceberg-aliyun:test --tests "org.apache.iceberg.aliyun.TestAliyunKeyManagementClient" --tests "org.apache.iceberg.aliyun.TestAliyunClientFactories"— all pass.End-to-end validation against real Alibaba Cloud KMS
Validated
AliyunKeyManagementClientend-to-end against a real Alibaba Cloud KMS Instance (DKMS) key, in-cluster, with keyless RRSA credentials. Every operation runs through the Iceberg table (StandardEncryptionManager+EncryptingFileIO),format-version=3,HadoopFileIOwith a file-based warehouse. (Encryption was injected via a smallHadoopTableOperationssubclass, since upstream currently wires client-side encryption only inHiveCatalog.)table.encryption()resolves to aStandardEncryptionManagerbacked byAliyunKeyManagementClient.OutputFileFactory+appendFile().commit()) → the table key is wrapped via KMSEncrypt; on disk, data files arePARE(Parquet modular encryption, encrypted footer), manifest + manifest-list.avroareAGS1(Iceberg AES-GCM stream), and rootmetadata.jsonis plaintext (expected — it holds only the KMS-wrapped key + table structure and bootstraps decryption).IcebergGenerics.read(table)scan) → decrypts manifest-list → manifests → data via KMSunwrapKey; decrypted rows exactly match what was written.metadata.json, rebuilds theStandardEncryptionManager, and scan-reads the decrypted rows — proving decryption bootstraps from table metadata with no in-process key cache.unwrapKey(KMSDecryptfails — no silent garbage).client.kms-endpointis required for an instance (key-…) key — with the override the client reaches the instance crypto endpoint and succeeds; without it the request falls back to the shared regional KMS and failsThis action is not supported.