This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new b9b931f9f552 CAMEL-26336: camel-minio: MinioProducer should apply
configured server-side encryption options (#28553)
b9b931f9f552 is described below
commit b9b931f9f5522dd8d61ad9ded50510c065fe6965
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Oct 27 12:50:43 2026 +0100
CAMEL-26336: camel-minio: MinioProducer should apply configured server-side
encryption options (#28553)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../camel/component/minio/MinioProducer.java | 48 ++++-
.../minio/MinioProducerEncryptionTest.java | 240 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 10 +
3 files changed, 292 insertions(+), 6 deletions(-)
diff --git
a/components/camel-minio/src/main/java/org/apache/camel/component/minio/MinioProducer.java
b/components/camel-minio/src/main/java/org/apache/camel/component/minio/MinioProducer.java
index 13dbf48c6eb9..fa29bd038b93 100644
---
a/components/camel-minio/src/main/java/org/apache/camel/component/minio/MinioProducer.java
+++
b/components/camel-minio/src/main/java/org/apache/camel/component/minio/MinioProducer.java
@@ -67,6 +67,16 @@ public class MinioProducer extends DefaultProducer {
super(endpoint);
}
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ if (getConfiguration().getServerSideEncryption() != null
+ && getConfiguration().getServerSideEncryptionCustomerKey() !=
null) {
+ throw new IllegalArgumentException(
+ "Only one of serverSideEncryption or
serverSideEncryptionCustomerKey can be configured");
+ }
+ }
+
public static Message getMessageForResponse(final Exchange exchange) {
return exchange.getMessage();
}
@@ -208,6 +218,12 @@ public class MinioProducer extends DefaultProducer {
.object(objectName)
.userMetadata(objectMetadata);
+ if (getConfiguration().getServerSideEncryptionCustomerKey() != null) {
+
putObjectRequest.sse(getConfiguration().getServerSideEncryptionCustomerKey());
+ } else if (getConfiguration().getServerSideEncryption() != null) {
+ putObjectRequest.sse(getConfiguration().getServerSideEncryption());
+ }
+
if (!extraHeaders.isEmpty()) {
putObjectRequest.extraHeaders(extraHeaders);
}
@@ -268,11 +284,21 @@ public class MinioProducer extends DefaultProducer {
.bucket(bucketName)
.object(sourceKey);
+ if (getConfiguration().getServerSideEncryptionCustomerKey() !=
null) {
+
copySourceBuilder.ssec(getConfiguration().getServerSideEncryptionCustomerKey());
+ }
+
CopyObjectArgs.Builder copyObjectRequest = CopyObjectArgs.builder()
.bucket(destinationBucketName)
.object(destinationKey)
.source(copySourceBuilder.build());
+ if (getConfiguration().getServerSideEncryptionCustomerKey() !=
null) {
+
copyObjectRequest.sse(getConfiguration().getServerSideEncryptionCustomerKey());
+ } else if (getConfiguration().getServerSideEncryption() != null) {
+
copyObjectRequest.sse(getConfiguration().getServerSideEncryption());
+ }
+
ObjectWriteResponse copyObjectResult =
minioClient.copyObject(copyObjectRequest.build());
Message message = getMessageForResponse(exchange);
@@ -353,10 +379,15 @@ public class MinioProducer extends DefaultProducer {
final String bucketName = determineBucketName(exchange);
final String sourceKey = determineObjectName(exchange);
- InputStream respond = minioClient.getObject(GetObjectArgs.builder()
+ GetObjectArgs.Builder getObjectRequest = GetObjectArgs.builder()
.bucket(bucketName)
- .object(sourceKey)
- .build());
+ .object(sourceKey);
+
+ if (getConfiguration().getServerSideEncryptionCustomerKey() !=
null) {
+
getObjectRequest.ssec(getConfiguration().getServerSideEncryptionCustomerKey());
+ }
+
+ InputStream respond =
minioClient.getObject(getObjectRequest.build());
Message message = getMessageForResponse(exchange);
message.setBody(respond);
@@ -383,12 +414,17 @@ public class MinioProducer extends DefaultProducer {
"A Offset and length header must be configured to
perform a partial get operation.");
}
- InputStream respond = minioClient.getObject(GetObjectArgs.builder()
+ GetObjectArgs.Builder getObjectRequest = GetObjectArgs.builder()
.bucket(bucketName)
.object(sourceKey)
.offset(Long.parseLong(offset))
- .length(Long.parseLong(length))
- .build());
+ .length(Long.parseLong(length));
+
+ if (getConfiguration().getServerSideEncryptionCustomerKey() !=
null) {
+
getObjectRequest.ssec(getConfiguration().getServerSideEncryptionCustomerKey());
+ }
+
+ InputStream respond =
minioClient.getObject(getObjectRequest.build());
Message message = getMessageForResponse(exchange);
message.setBody(respond);
diff --git
a/components/camel-minio/src/test/java/org/apache/camel/component/minio/MinioProducerEncryptionTest.java
b/components/camel-minio/src/test/java/org/apache/camel/component/minio/MinioProducerEncryptionTest.java
new file mode 100644
index 000000000000..72f2d8b02d35
--- /dev/null
+++
b/components/camel-minio/src/test/java/org/apache/camel/component/minio/MinioProducerEncryptionTest.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.minio;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import io.minio.BucketExistsArgs;
+import io.minio.CopyObjectArgs;
+import io.minio.GetObjectArgs;
+import io.minio.GetObjectResponse;
+import io.minio.MinioClient;
+import io.minio.ObjectWriteResponse;
+import io.minio.PutObjectArgs;
+import io.minio.ServerSideEncryption;
+import org.apache.camel.Exchange;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class MinioProducerEncryptionTest extends CamelTestSupport {
+
+ private MinioClient minioClient;
+ private SecretKey secretKey;
+
+ @BeforeEach
+ void init() throws Exception {
+ minioClient = mock(MinioClient.class);
+
when(minioClient.bucketExists(any(BucketExistsArgs.class))).thenReturn(true);
+ KeyGenerator generator = KeyGenerator.getInstance("AES");
+ generator.init(256);
+ secretKey = generator.generateKey();
+ }
+
+ private MinioProducer createProducer(MinioEndpoint endpoint) throws
Exception {
+ endpoint.start();
+ MinioProducer producer = new MinioProducer(endpoint);
+ producer.start();
+ return producer;
+ }
+
+ @Test
+ void putObjectWithServerSideEncryption() throws Exception {
+ ServerSideEncryption.S3 sse = new ServerSideEncryption.S3();
+ MinioComponent component = context.getComponent("minio",
MinioComponent.class);
+ MinioEndpoint endpoint = (MinioEndpoint)
component.createEndpoint("minio://mybucket?autoCreateBucket=false");
+ endpoint.getConfiguration().setMinioClient(minioClient);
+ endpoint.getConfiguration().setServerSideEncryption(sse);
+
+
when(minioClient.putObject(any(PutObjectArgs.class))).thenReturn(mock(ObjectWriteResponse.class));
+
+ MinioProducer producer = createProducer(endpoint);
+ Exchange exchange = endpoint.createExchange();
+ exchange.getIn().setHeader(MinioConstants.OBJECT_NAME, "test.txt");
+ exchange.getIn().setBody("content");
+
+ producer.process(exchange);
+
+ ArgumentCaptor<PutObjectArgs> captor =
ArgumentCaptor.forClass(PutObjectArgs.class);
+ verify(minioClient).putObject(captor.capture());
+ assertSame(sse, captor.getValue().sse());
+ producer.stop();
+ endpoint.stop();
+ }
+
+ @Test
+ void putObjectWithCustomerKey() throws Exception {
+ ServerSideEncryption.CustomerKey ssec = new
ServerSideEncryption.CustomerKey(secretKey);
+ MinioComponent component = context.getComponent("minio",
MinioComponent.class);
+ MinioEndpoint endpoint = (MinioEndpoint)
component.createEndpoint("minio://mybucket?autoCreateBucket=false");
+ endpoint.getConfiguration().setMinioClient(minioClient);
+ endpoint.getConfiguration().setServerSideEncryptionCustomerKey(ssec);
+
+
when(minioClient.putObject(any(PutObjectArgs.class))).thenReturn(mock(ObjectWriteResponse.class));
+
+ MinioProducer producer = createProducer(endpoint);
+ Exchange exchange = endpoint.createExchange();
+ exchange.getIn().setHeader(MinioConstants.OBJECT_NAME, "test.txt");
+ exchange.getIn().setBody("content");
+
+ producer.process(exchange);
+
+ ArgumentCaptor<PutObjectArgs> captor =
ArgumentCaptor.forClass(PutObjectArgs.class);
+ verify(minioClient).putObject(captor.capture());
+ assertSame(ssec, captor.getValue().sse());
+ producer.stop();
+ endpoint.stop();
+ }
+
+ @Test
+ void producerStartWithBothEncryptionOptionsThrows() throws Exception {
+ ServerSideEncryption.S3 sse = new ServerSideEncryption.S3();
+ ServerSideEncryption.CustomerKey ssec = new
ServerSideEncryption.CustomerKey(secretKey);
+
+ MinioComponent component = context.getComponent("minio",
MinioComponent.class);
+ MinioEndpoint endpoint = (MinioEndpoint)
component.createEndpoint("minio://mybucket?autoCreateBucket=false");
+ endpoint.getConfiguration().setMinioClient(minioClient);
+ endpoint.getConfiguration().setServerSideEncryption(sse);
+ endpoint.getConfiguration().setServerSideEncryptionCustomerKey(ssec);
+
+ endpoint.start();
+ MinioProducer producer = new MinioProducer(endpoint);
+ assertThrows(IllegalArgumentException.class, producer::start);
+ endpoint.stop();
+ }
+
+ @Test
+ void copyObjectWithServerSideEncryption() throws Exception {
+ ServerSideEncryption.S3 sse = new ServerSideEncryption.S3();
+
+ MinioComponent component = context.getComponent("minio",
MinioComponent.class);
+ MinioEndpoint endpoint = (MinioEndpoint)
component.createEndpoint("minio://mybucket?autoCreateBucket=false");
+ endpoint.getConfiguration().setMinioClient(minioClient);
+ endpoint.getConfiguration().setServerSideEncryption(sse);
+
+
when(minioClient.copyObject(any(CopyObjectArgs.class))).thenReturn(mock(ObjectWriteResponse.class));
+
+ MinioProducer producer = createProducer(endpoint);
+ Exchange exchange = endpoint.createExchange();
+ exchange.getIn().setHeader(MinioConstants.MINIO_OPERATION,
MinioOperations.copyObject);
+ exchange.getIn().setHeader(MinioConstants.OBJECT_NAME, "source.txt");
+ exchange.getIn().setHeader(MinioConstants.DESTINATION_OBJECT_NAME,
"dest.txt");
+ exchange.getIn().setHeader(MinioConstants.DESTINATION_BUCKET_NAME,
"dest-bucket");
+
+ producer.process(exchange);
+
+ ArgumentCaptor<CopyObjectArgs> captor =
ArgumentCaptor.forClass(CopyObjectArgs.class);
+ verify(minioClient).copyObject(captor.capture());
+ assertSame(sse, captor.getValue().sse());
+ producer.stop();
+ endpoint.stop();
+ }
+
+ @Test
+ void copyObjectWithCustomerKey() throws Exception {
+ ServerSideEncryption.CustomerKey ssec = new
ServerSideEncryption.CustomerKey(secretKey);
+
+ MinioComponent component = context.getComponent("minio",
MinioComponent.class);
+ MinioEndpoint endpoint = (MinioEndpoint)
component.createEndpoint("minio://mybucket?autoCreateBucket=false");
+ endpoint.getConfiguration().setMinioClient(minioClient);
+ endpoint.getConfiguration().setServerSideEncryptionCustomerKey(ssec);
+
+
when(minioClient.copyObject(any(CopyObjectArgs.class))).thenReturn(mock(ObjectWriteResponse.class));
+
+ MinioProducer producer = createProducer(endpoint);
+ Exchange exchange = endpoint.createExchange();
+ exchange.getIn().setHeader(MinioConstants.MINIO_OPERATION,
MinioOperations.copyObject);
+ exchange.getIn().setHeader(MinioConstants.OBJECT_NAME, "source.txt");
+ exchange.getIn().setHeader(MinioConstants.DESTINATION_OBJECT_NAME,
"dest.txt");
+ exchange.getIn().setHeader(MinioConstants.DESTINATION_BUCKET_NAME,
"dest-bucket");
+
+ producer.process(exchange);
+
+ ArgumentCaptor<CopyObjectArgs> captor =
ArgumentCaptor.forClass(CopyObjectArgs.class);
+ verify(minioClient).copyObject(captor.capture());
+ assertSame(ssec, captor.getValue().sse());
+ assertNotNull(captor.getValue().source());
+ assertSame(ssec, captor.getValue().source().ssec());
+ producer.stop();
+ endpoint.stop();
+ }
+
+ @Test
+ void getObjectWithCustomerKey() throws Exception {
+ ServerSideEncryption.CustomerKey ssec = new
ServerSideEncryption.CustomerKey(secretKey);
+
+ MinioComponent component = context.getComponent("minio",
MinioComponent.class);
+ MinioEndpoint endpoint = (MinioEndpoint)
component.createEndpoint("minio://mybucket?autoCreateBucket=false");
+ endpoint.getConfiguration().setMinioClient(minioClient);
+ endpoint.getConfiguration().setServerSideEncryptionCustomerKey(ssec);
+
+
when(minioClient.getObject(any(GetObjectArgs.class))).thenReturn(mock(GetObjectResponse.class));
+
+ MinioProducer producer = createProducer(endpoint);
+ Exchange exchange = endpoint.createExchange();
+ exchange.getIn().setHeader(MinioConstants.MINIO_OPERATION,
MinioOperations.getObject);
+ exchange.getIn().setHeader(MinioConstants.OBJECT_NAME, "test.txt");
+
+ producer.process(exchange);
+
+ ArgumentCaptor<GetObjectArgs> captor =
ArgumentCaptor.forClass(GetObjectArgs.class);
+ verify(minioClient).getObject(captor.capture());
+ assertSame(ssec, captor.getValue().ssec());
+ producer.stop();
+ endpoint.stop();
+ }
+
+ @Test
+ void getObjectRangeWithCustomerKey() throws Exception {
+ ServerSideEncryption.CustomerKey ssec = new
ServerSideEncryption.CustomerKey(secretKey);
+
+ MinioComponent component = context.getComponent("minio",
MinioComponent.class);
+ MinioEndpoint endpoint = (MinioEndpoint)
component.createEndpoint("minio://mybucket?autoCreateBucket=false");
+ endpoint.getConfiguration().setMinioClient(minioClient);
+ endpoint.getConfiguration().setServerSideEncryptionCustomerKey(ssec);
+
+
when(minioClient.getObject(any(GetObjectArgs.class))).thenReturn(mock(GetObjectResponse.class));
+
+ MinioProducer producer = createProducer(endpoint);
+ Exchange exchange = endpoint.createExchange();
+ exchange.getIn().setHeader(MinioConstants.MINIO_OPERATION,
MinioOperations.getObjectRange);
+ exchange.getIn().setHeader(MinioConstants.OBJECT_NAME, "test.txt");
+ exchange.getIn().setHeader(MinioConstants.OFFSET, "10");
+ exchange.getIn().setHeader(MinioConstants.LENGTH, "20");
+
+ producer.process(exchange);
+
+ ArgumentCaptor<GetObjectArgs> captor =
ArgumentCaptor.forClass(GetObjectArgs.class);
+ verify(minioClient).getObject(captor.capture());
+ assertSame(ssec, captor.getValue().ssec());
+ assertEquals(10L, captor.getValue().offset());
+ assertEquals(20L, captor.getValue().length());
+ producer.stop();
+ endpoint.stop();
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 64ee1374f3fd..910e33a8f88c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -2307,6 +2307,16 @@ bucket. The poll still fails when none of the listed
objects could be got, and w
lists `maxMessagesPerPoll` objects, the next poll now continues after the last
listed object (before, after the last
object that was consumed), so an object that cannot be got at the end of a
page does not hold the listing back.
+=== camel-minio - Server-side encryption options now apply to MinioProducer
+
+The `serverSideEncryption` and `serverSideEncryptionCustomerKey` endpoint
options now apply to producer operations:
+- Put operations apply the configured server-side encryption or
customer-supplied key.
+- Copy operations apply the configured encryption to the destination object
(and `serverSideEncryptionCustomerKey` to the
+ source object when configured).
+- Get operations (`getObject` and `getObjectRange`) pass the customer key when
configured.
+- Only one of `serverSideEncryption` or `serverSideEncryptionCustomerKey` can
be configured on an endpoint; configuring
+ both throws `IllegalArgumentException` on producer startup.
+
=== camel-box - getEnterpriseEvents returns the events of every chunk
The `getEnterpriseEvents` operation of the `event-logs` API now returns all
the events between `after` and `before`.