Skip to content

Commit

Permalink
Import Minio signing classes
Browse files Browse the repository at this point in the history
- Copied signing classes from Minio: https://github.com/minio/minio-java
- Added Notice file for Minio
- Replaced OkHttp, etc. classes with emulated equivalents
- Added an initial test for a simple `aws s3 ls` based on this doc: https://min.io/docs/minio/linux/integrations/aws-cli-with-minio.html

Closes #5
  • Loading branch information
Randgalt committed May 17, 2024
1 parent 4b59e9d commit 4245318
Show file tree
Hide file tree
Showing 17 changed files with 1,111 additions and 10 deletions.
1 change: 1 addition & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This product includes software developed by Minio. (https://github.com/minio/minio-java)
30 changes: 29 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@

<air.java.version>22.0.0</air.java.version>
<air.main.basedir>${project.basedir}</air.main.basedir>
<air.modernizer.java-version>8</air.modernizer.java-version>
<air.check.skip-spotbugs>true</air.check.skip-spotbugs>
<air.check.skip-pmd>true</air.check.skip-pmd>
<air.modernizer.java-version>8</air.modernizer.java-version>
<air.check.skip-modernizer>true</air.check.skip-modernizer>

<dep.airlift.version>245</dep.airlift.version>
<dep.aws-sdk.version>2.25.32</dep.aws-sdk.version>
</properties>

<dependencyManagement>
Expand All @@ -56,6 +58,32 @@
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${dep.aws-sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<configuration>
<licenseSets>
<licenseSet>
<excludes>
<exclude>**/io/trino/s3/proxy/server/minio/*</exclude>
</excludes>
</licenseSet>
</licenseSets>
</configuration>
</plugin>
</plugins>
</build>
</project>
6 changes: 6 additions & 0 deletions trino-s3-proxy/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
package io.trino.s3.proxy.server;

import com.google.common.collect.ImmutableList;
import com.google.inject.Injector;
import com.google.inject.Module;
import io.airlift.bootstrap.Bootstrap;
import io.airlift.event.client.EventModule;
import io.airlift.http.server.HttpServerModule;
import io.airlift.http.server.testing.TestingHttpServer;
import io.airlift.jaxrs.JaxrsModule;
import io.airlift.json.JsonModule;
import io.airlift.log.Logger;
Expand All @@ -39,8 +41,13 @@ public static void main(String[] args)
.add(new JaxrsModule());

Bootstrap app = new Bootstrap(modules.build());
app.initialize();
Injector injector = app.initialize();

log.info("======== SERVER STARTED ========");

TestingHttpServer httpServer = injector.getInstance(TestingHttpServer.class);
log.info("");
log.info("Endpoint: %s", httpServer.getBaseUrl());
log.info("");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Licensed 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 io.trino.s3.proxy.server.credentials;

import static java.util.Objects.requireNonNull;

public record Credentials(CredentialsEntry emulated, CredentialsEntry real)
{
public Credentials
{
requireNonNull(emulated, "emulated is null");
requireNonNull(real, "real is null");
}

public record CredentialsEntry(String accessKey, String secretKey)
{
public CredentialsEntry
{
requireNonNull(accessKey, "accessKey is null");
requireNonNull(secretKey, "secretKey is null");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.s3.proxy.server;
package io.trino.s3.proxy.server.credentials;

import org.junit.jupiter.api.Test;
import java.util.Optional;

public class DummyTest
public interface CredentialsController
{
@Test
public void testDummy()
{
// stub test for now
}
Optional<Credentials> credentials(String emulatedAccessKey);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed 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 io.trino.s3.proxy.server.credentials;

import com.google.inject.Inject;
import io.trino.s3.proxy.server.minio.Signer;
import io.trino.s3.proxy.server.minio.emulation.MinioRequest;
import io.trino.s3.proxy.server.minio.emulation.MinioUrl;
import jakarta.ws.rs.core.MultivaluedMap;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import static java.util.Objects.requireNonNull;

public class SigningController
{
private final CredentialsController credentialsController;

@Inject
public SigningController(CredentialsController credentialsController)
{
this.credentialsController = requireNonNull(credentialsController, "credentialsController is null");
}

public String signRequest(String method, MultivaluedMap<String, String> requestHeaders, String encodedPath, String encodedQuery, String region, String accessKey)
{
// TODO
Credentials credentials = credentialsController.credentials(accessKey).orElseThrow();

MinioUrl minioUrl = new MinioUrl(encodedPath, encodedQuery);
MinioRequest minioRequest = new MinioRequest(requestHeaders, method, minioUrl);

// TODO
String sha256 = minioRequest.headerValue("x-amz-content-sha256").orElseThrow();

try {
return Signer.signV4S3(minioRequest, region, accessKey, credentials.emulated().secretKey(), sha256);
}
catch (NoSuchAlgorithmException | InvalidKeyException e) {
// TODO
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
*
* Licensed 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 io.trino.s3.proxy.server.minio;

import com.google.common.io.BaseEncoding;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Locale;

/**
* Various global static functions used.
*/
public final class Digest
{
// MD5 hash of zero length byte array.
public static final String ZERO_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg==";
// SHA-256 hash of zero length byte array.
public static final String ZERO_SHA256_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";

private Digest() {}

/**
* Returns MD5 hash of byte array.
*/
public static String md5Hash(byte[] data, int length)
throws NoSuchAlgorithmException
{
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
md5Digest.update(data, 0, length);
return Base64.getEncoder().encodeToString(md5Digest.digest());
}

/**
* Returns SHA-256 hash of byte array.
*/
public static String sha256Hash(byte[] data, int length)
throws NoSuchAlgorithmException
{
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
sha256Digest.update(data, 0, length);
return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US);
}

/**
* Returns SHA-256 hash of given string.
*/
public static String sha256Hash(String string)
throws NoSuchAlgorithmException
{
byte[] data = string.getBytes(StandardCharsets.UTF_8);
return sha256Hash(data, data.length);
}

/**
* Updated MessageDigest with bytes read from file and stream.
*/
private static int updateDigests(
Object inputStream, int len, MessageDigest sha256Digest, MessageDigest md5Digest)
throws IOException, InsufficientDataException
{
RandomAccessFile file = null;
BufferedInputStream stream = null;
if (inputStream instanceof RandomAccessFile) {
file = (RandomAccessFile) inputStream;
}
else if (inputStream instanceof BufferedInputStream) {
stream = (BufferedInputStream) inputStream;
}

// hold current position of file/stream to reset back to this position.
long pos = 0;
if (file != null) {
pos = file.getFilePointer();
}
else if (stream != null) {
stream.mark(len);
}

// 16KiB buffer for optimization
byte[] buf = new byte[16384];
int bytesToRead = buf.length;
int bytesRead = 0;
int totalBytesRead = 0;
while (totalBytesRead < len) {
if ((len - totalBytesRead) < bytesToRead) {
bytesToRead = len - totalBytesRead;
}

if (file != null) {
bytesRead = file.read(buf, 0, bytesToRead);
}
else if (stream != null) {
bytesRead = stream.read(buf, 0, bytesToRead);
}

if (bytesRead < 0) {
// reached EOF
throw new InsufficientDataException(
"Insufficient data. bytes read " + totalBytesRead + " expected " + len);
}

if (bytesRead > 0) {
if (sha256Digest != null) {
sha256Digest.update(buf, 0, bytesRead);
}

if (md5Digest != null) {
md5Digest.update(buf, 0, bytesRead);
}

totalBytesRead += bytesRead;
}
}

// reset back to saved position.
if (file != null) {
file.seek(pos);
}
else if (stream != null) {
stream.reset();
}

return totalBytesRead;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
*
* Licensed 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 io.trino.s3.proxy.server.minio;

/**
* Thrown to indicate that reading given InputStream gets EOFException before reading given length.
*/
public class InsufficientDataException
extends MinioException
{
/**
* Constructs a new InsufficientDataException with given error message.
*/
public InsufficientDataException(String message)
{
super(message);
}
}
Loading

0 comments on commit 4245318

Please sign in to comment.