JingsongLi commented on code in PR #8460:
URL: https://github.com/apache/paimon/pull/8460#discussion_r3525095730
##########
paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSFileIO.java:
##########
@@ -273,112 +280,205 @@ private static OSSClient
getOssClient(AliyunOSSFileSystem fs) throws Exception {
return ReflectionUtils.getPrivateFieldValue(store, "ossClient");
}
- /**
- * Enable SSE-KMS with a customer CMK. hadoop-aliyun exposes no CMK key-id
option, so swap the
- * OSS client's object/multipart operations for subclasses that stamp the
OSS-native SSE-KMS
- * headers on the write paths: PutObject, server-side CopyObject
(rename/commit) and
- * InitiateMultipartUpload. UploadPart/HeadObject/GetObject are left
untouched (stamping SSE
- * headers on those makes OSS reject the request with 400).
- */
- private void enableSseKms(AliyunOSSFileSystem fs, String kmsKeyId) {
+ /** The Paimon SSE config from the three keys, or null when none is set. */
+ private SseConfig configuredSse() {
+ return resolveSse(
+ hadoopOptions.get(OSS_SSE_METHOD),
+ hadoopOptions.get(OSS_SSE_KMS_KEY_ID),
+ hadoopOptions.get(OSS_SSE_DATA_ENCRYPTION));
+ }
+
+ /** Parse the three SSE keys into an {@link SseConfig}, or null. Throws on
bad input. */
+ static SseConfig resolveSse(String method, String keyId, String
dataEncryption) {
+ // Reject present-but-blank values fail-fast instead of silently
writing plaintext.
+ checkNotBlankIfSet(method, OSS_SSE_METHOD);
+ checkNotBlankIfSet(keyId, OSS_SSE_KMS_KEY_ID);
+ checkNotBlankIfSet(dataEncryption, OSS_SSE_DATA_ENCRYPTION);
+ method = method == null ? null : method.trim();
+ keyId = keyId == null ? null : keyId.trim();
+ dataEncryption = dataEncryption == null ? null : dataEncryption.trim();
+ if (method == null && keyId == null && dataEncryption == null) {
+ return null;
+ }
+ // A key id / data-encryption implies KMS; default the method to KMS
when unset.
+ method = canonicalSseMethod(method == null ? SSE_METHOD_KMS : method);
+ if (keyId != null) {
+ checkArgument(
+ keyId.chars().noneMatch(Character::isWhitespace),
+ "Invalid value for '%s': the CMK key id must not contain
whitespace/newlines.",
+ OSS_SSE_KMS_KEY_ID);
+ checkArgument(
+ SSE_METHOD_KMS.equals(method),
+ "'%s' requires '%s=KMS', but got '%s'.",
+ OSS_SSE_KMS_KEY_ID,
+ OSS_SSE_METHOD,
+ method);
+ }
+ if (dataEncryption != null) {
+ checkArgument(
+ SSE_METHOD_KMS.equals(method),
+ "'%s' requires '%s=KMS', but got '%s'.",
+ OSS_SSE_DATA_ENCRYPTION,
+ OSS_SSE_METHOD,
+ method);
+ checkArgument(
+ SSE_DATA_SM4.equalsIgnoreCase(dataEncryption),
+ "'%s' only supports 'SM4', but got '%s'.",
+ OSS_SSE_DATA_ENCRYPTION,
+ dataEncryption);
+ dataEncryption = SSE_DATA_SM4;
+ }
+ return new SseConfig(method, keyId, dataEncryption);
+ }
+
+ /** Reject a config value that is present but blank (encryption requested
yet misconfigured). */
+ private static void checkNotBlankIfSet(String value, String key) {
+ checkArgument(
+ value == null || !StringUtils.isNullOrWhitespaceOnly(value),
+ "'%s' is set but blank.",
+ key);
+ }
+
+ /** Canonicalize the SSE method to the exact OSS header value; reject
unknown values. */
+ private static String canonicalSseMethod(String method) {
+ if (SSE_METHOD_AES256.equalsIgnoreCase(method)) {
+ return SSE_METHOD_AES256;
+ }
+ if (SSE_METHOD_KMS.equalsIgnoreCase(method)) {
+ return SSE_METHOD_KMS;
+ }
+ if (SSE_DATA_SM4.equalsIgnoreCase(method)) {
+ return SSE_DATA_SM4;
+ }
+ throw new IllegalArgumentException(
+ "'"
+ + OSS_SSE_METHOD
+ + "' must be one of AES256/KMS/SM4, but got '"
+ + method
+ + "'.");
+ }
+
+ /** Resolved OSS server-side-encryption settings. */
+ static final class SseConfig {
+ final String method;
+ final String keyId;
+ final String dataEnc;
+
+ SseConfig(String method, String keyId, String dataEnc) {
+ this.method = method;
+ this.keyId = keyId;
+ this.dataEnc = dataEnc;
+ }
+ }
+
+ /** Swap in the SSE-stamping object/multipart operations (write paths
only). */
+ private void enableSse(AliyunOSSFileSystem fs, SseConfig sse) {
try {
- swapSseKmsOperations(getOssClient(fs), kmsKeyId);
+ swapSseOperations(getOssClient(fs), sse);
} catch (Exception e) {
- LOG.error("Failed to enable SSE-KMS BYOK.", e);
- throw new RuntimeException("Failed to enable SSE-KMS BYOK.", e);
+ LOG.error("Failed to enable OSS server-side encryption.", e);
+ throw new RuntimeException("Failed to enable OSS server-side
encryption.", e);
}
}
- /**
- * Swap the OSS client's object/multipart operations for the CMK-stamping
subclasses. Package
- * private so a unit test can pin the reflected field names and getters
against the bundled
- * aliyun-oss-sdk version.
- */
- static void swapSseKmsOperations(OSSClient ossClient, String kmsKeyId)
throws Exception {
+ /** Swap in the SSE-stamping operations; package-private so a test pins
reflected names. */
+ static void swapSseOperations(OSSClient ossClient, SseConfig sse) throws
Exception {
ServiceClient serviceClient =
ReflectionUtils.getPrivateFieldValue(ossClient,
"serviceClient");
CredentialsProvider credsProvider =
ReflectionUtils.getPrivateFieldValue(ossClient,
"credsProvider");
// Replacement operations must inherit the endpoint or requests NPE.
URI endpoint = ossClient.getEndpoint();
- SseKmsObjectOperation objectOperation =
- new SseKmsObjectOperation(serviceClient, credsProvider,
kmsKeyId);
+ SseObjectOperation objectOperation =
+ new SseObjectOperation(serviceClient, credsProvider, sse);
objectOperation.setEndpoint(endpoint);
- SseKmsMultipartOperation multipartOperation =
- new SseKmsMultipartOperation(serviceClient, credsProvider,
kmsKeyId);
+ SseMultipartOperation multipartOperation =
+ new SseMultipartOperation(serviceClient, credsProvider, sse);
multipartOperation.setEndpoint(endpoint);
ReflectionUtils.setPrivateFieldValue(ossClient, "objectOperation",
objectOperation);
ReflectionUtils.setPrivateFieldValue(ossClient, "multipartOperation",
multipartOperation);
- // Fail closed: if a future SDK change makes the swap a no-op, fail
loudly
- // rather than silently writing objects without the customer CMK.
+ // Fail closed: if the swap didn't take effect, refuse to write
unencrypted.
if (ossClient.getObjectOperation() != objectOperation
|| ossClient.getMultipartOperation() != multipartOperation) {
throw new IllegalStateException(
- "SSE-KMS BYOK operation swap did not take effect; refusing
to write "
- + "without server-side encryption. The
aliyun-oss-sdk internals "
- + "may have changed.");
+ "OSS SSE operation swap did not take effect; refusing to
write without "
+ + "server-side encryption. The aliyun-oss-sdk
internals may have changed.");
}
}
- /** Stamps the customer CMK on the given metadata, allocating one if
absent. */
- private static ObjectMetadata applySseKms(ObjectMetadata metadata, String
kmsKeyId) {
+ /** Stamp the SSE headers on object metadata (PutObject / multipart init).
*/
+ static ObjectMetadata applySse(ObjectMetadata metadata, SseConfig sse) {
if (metadata == null) {
metadata = new ObjectMetadata();
}
String existing = metadata.getServerSideEncryption();
- if (existing != null && !SSE_KMS_ALGORITHM.equals(existing)) {
+ if (existing != null && !sse.method.equals(existing)) {
LOG.warn(
- "Customer CMK SSE-KMS is overriding the previously-set
server-side "
- + "encryption algorithm '{}' with 'KMS'.",
- existing);
+ "Paimon SSE is overriding the previously-set server-side
encryption "
+ + "method '{}' with '{}'.",
+ existing,
+ sse.method);
+ }
+ metadata.setServerSideEncryption(sse.method);
+ if (sse.keyId != null) {
+ metadata.setServerSideEncryptionKeyId(sse.keyId);
+ }
+ if (sse.dataEnc != null) {
+ metadata.setServerSideDataEncryption(sse.dataEnc);
}
- metadata.setServerSideEncryption(SSE_KMS_ALGORITHM);
- metadata.setServerSideEncryptionKeyId(kmsKeyId);
return metadata;
}
- /** Stamps the customer CMK on every simple PutObject. */
- static class SseKmsObjectOperation extends OSSObjectOperation {
- private final String kmsKeyId;
+ /** Stamp the SSE headers on a server-side CopyObject request
(rename/commit). */
+ static CopyObjectRequest applySse(CopyObjectRequest request, SseConfig
sse) {
+ // Use request fields, not newObjectMetadata (forces REPLACE, drops
source metadata).
+ // No data-encryption field; addHeader works since doOperation merges
headers pre-sign.
+ request.setServerSideEncryption(sse.method);
+ if (sse.keyId != null) {
+ request.setServerSideEncryptionKeyId(sse.keyId);
+ }
+ if (sse.dataEnc != null) {
+ request.addHeader(OSSHeaders.OSS_SERVER_SIDE_DATA_ENCRYPTION,
sse.dataEnc);
Review Comment:
This does not seem to reach the actual CopyObject HTTP request. In
aliyun-sdk-oss 3.13.2, `OSSObjectOperation.copyObject()` creates a fresh local
headers map and populates it only through
`populateCopyObjectHeaders(copyObjectRequest, headers)`, which reads the
dedicated CopyObjectRequest fields for SSE method/key-id but does not merge
`WebServiceRequest.getHeaders()`. So `fs.oss.server-side-data-encryption=SM4`
will be recorded on the request object but dropped before signing/sending
CopyObject, meaning rename/commit copies are not stamped as documented. The
current unit test only checks `kms.getHeaders()`, so it misses this SDK
request-building path. We probably need to override/build the CopyObject
request headers directly, or use an SDK API that has a dedicated
data-encryption field, and add a test that covers the final headers passed to
the operation.
--
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]