Skip to content

Commit

Permalink
Support custom prefixes for S3 and STS
Browse files Browse the repository at this point in the history
Closes trinodb#57
  • Loading branch information
vagaerg committed Jun 20, 2024
1 parent 4da2f10 commit c6dacca
Show file tree
Hide file tree
Showing 21 changed files with 262 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.trino.s3.proxy.server.security.SecurityResponse;
import io.trino.s3.proxy.server.signing.SigningController;
import io.trino.s3.proxy.server.signing.SigningControllerConfig;
import org.glassfish.jersey.server.model.Resource;

import java.util.Optional;
import java.util.ServiceLoader;
Expand All @@ -52,11 +53,16 @@ public class TrinoS3ProxyServerModule
@Override
protected void setup(Binder binder)
{
configBinder(binder).bindConfig(SigningControllerConfig.class);
configBinder(binder).bindConfig(TrinoS3ProxyConfig.class);

TrinoS3ProxyConfig builtConfig = buildConfigObject(TrinoS3ProxyConfig.class);

jaxrsBinder(binder).bind(TrinoS3ProxyResource.class);
jaxrsBinder(binder).bindInstance(buildPrefixedResource(TrinoS3ProxyResource.class, builtConfig.getS3Prefix()));
jaxrsBinder(binder).bind(TrinoStsResource.class);
jaxrsBinder(binder).bindInstance(buildPrefixedResource(TrinoStsResource.class, builtConfig.getStsPrefix()));

configBinder(binder).bindConfig(SigningControllerConfig.class);
configBinder(binder).bindConfig(TrinoS3ProxyConfig.class);
binder.bind(SigningController.class).in(Scopes.SINGLETON);
binder.bind(CredentialsController.class).in(Scopes.SINGLETON);
binder.bind(SecurityController.class).in(Scopes.SINGLETON);
Expand Down Expand Up @@ -96,4 +102,9 @@ private void installPlugins()
install(plugin.module());
});
}

private static Resource buildPrefixedResource(Class<?> resourceClass, String resourcePathPrefix)
{
return Resource.builder(resourceClass).path(resourcePathPrefix).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
public class TrinoS3ProxyConfig
{
private Optional<String> hostName = Optional.empty();
private String s3Prefix = TrinoS3ProxyRestConstants.DEFAULT_S3_PATH;
private String stsPrefix = TrinoS3ProxyRestConstants.DEFAULT_STS_PATH;

@Config("s3proxy.hostname")
@ConfigDescription("Hostname to use for REST operations, virtual-host style addressing is only supported if this is set")
@Config("s3proxy.s3.hostname")
@ConfigDescription("Hostname to use for S3 REST operations, virtual-host style addressing is only supported if this is set")
public TrinoS3ProxyConfig setHostName(String hostName)
{
this.hostName = Optional.ofNullable(hostName);
Expand All @@ -36,4 +38,32 @@ public Optional<String> getHostName()
{
return hostName;
}

@Config("s3proxy.s3.prefix")
@ConfigDescription("URL Prefix for S3 operations, optional")
public TrinoS3ProxyConfig setS3Prefix(String s3Prefix)
{
this.s3Prefix = s3Prefix;
return this;
}

@NotNull
public String getS3Prefix()
{
return s3Prefix;
}

@Config("s3proxy.sts.prefix")
@ConfigDescription("URL Prefix for STS operations, optional")
public TrinoS3ProxyConfig setStsPrefix(String stsPrefix)
{
this.stsPrefix = stsPrefix;
return this;
}

@NotNull
public String getStsPrefix()
{
return stsPrefix;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import static io.trino.s3.proxy.server.rest.RequestBuilder.fromRequest;
import static java.util.Objects.requireNonNull;

@Path(TrinoS3ProxyRestConstants.S3_PATH)
public class TrinoS3ProxyResource
{
private final SigningController signingController;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public final class TrinoS3ProxyRestConstants
{
private TrinoS3ProxyRestConstants() {}

public static final String BASE_PATH = "/api/v1/s3Proxy/";
public static final String S3_PATH = BASE_PATH + "s3";
public static final String STS_PATH = BASE_PATH + "sts";
private static final String BASE_PATH = "/api/v1/s3Proxy/";
public static final String DEFAULT_S3_PATH = BASE_PATH + "s3";
public static final String DEFAULT_STS_PATH = BASE_PATH + "sts";
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import io.trino.s3.proxy.server.signing.SigningMetadata;
import io.trino.s3.proxy.server.signing.SigningServiceType;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
Expand All @@ -43,7 +42,6 @@
import static io.trino.s3.proxy.server.signing.SigningController.formatResponseInstant;
import static java.util.Objects.requireNonNull;

@Path(TrinoS3ProxyRestConstants.STS_PATH)
public class TrinoStsResource
{
private static final Logger log = Logger.get(TrinoStsResource.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@
*/
package io.trino.s3.proxy.server;

import com.google.inject.Inject;
import io.airlift.http.server.testing.TestingHttpServer;
import io.trino.s3.proxy.server.credentials.Credential;
import io.trino.s3.proxy.server.credentials.Credentials;
import io.trino.s3.proxy.server.credentials.CredentialsProvider;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants;
import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.endpoints.Endpoint;
Expand All @@ -36,18 +33,16 @@
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.assertj.core.api.Assertions.assertThat;

@TrinoS3ProxyTest
public class TestStsRequests
public abstract class AbstractTestStsRequests
{
private final StsClient stsClient;
private final CredentialsProvider credentialsProvider;

@Inject
public TestStsRequests(@ForTesting Credentials testingCredentials, TestingHttpServer httpServer, CredentialsProvider credentialsProvider)
public AbstractTestStsRequests(Credentials testingCredentials, TestingHttpServer httpServer, CredentialsProvider credentialsProvider, TrinoS3ProxyConfig s3ProxyConfig)
{
this.credentialsProvider = requireNonNull(credentialsProvider, "credentialsProvider is null");

URI localProxyServerUri = httpServer.getBaseUrl().resolve(TrinoS3ProxyRestConstants.STS_PATH);
URI localProxyServerUri = httpServer.getBaseUrl().resolve(s3ProxyConfig.getStsPrefix());
Endpoint endpoint = Endpoint.builder().url(localProxyServerUri).build();

stsClient = StsClient.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import io.airlift.http.server.testing.TestingHttpServer;
import io.airlift.log.Logger;
import io.trino.s3.proxy.server.credentials.Credentials;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig;
import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer;
import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting;

Expand All @@ -38,9 +38,10 @@ public static void main(String[] args)

TestingHttpServer httpServer = trinoS3ProxyServer.getInjector().getInstance(TestingHttpServer.class);
Credentials testingCredentials = trinoS3ProxyServer.getInjector().getInstance(Key.get(Credentials.class, ForTesting.class));
TrinoS3ProxyConfig s3ProxyConfig = trinoS3ProxyServer.getInjector().getInstance(TrinoS3ProxyConfig.class);

log.info("");
log.info("Endpoint: %s", httpServer.getBaseUrl().resolve(TrinoS3ProxyRestConstants.S3_PATH));
log.info("Endpoint: %s", httpServer.getBaseUrl().resolve(s3ProxyConfig.getS3Prefix()));
log.info("Access Key: %s", testingCredentials.emulated().accessKey());
log.info("Secret Key: %s", testingCredentials.emulated().secretKey());
log.info("");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import io.airlift.units.Duration;
import io.trino.s3.proxy.server.credentials.Credential;
import io.trino.s3.proxy.server.credentials.Credentials;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig;
import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer;
import io.trino.s3.proxy.server.testing.TestingCredentialsRolesProvider;
import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer;
Expand Down Expand Up @@ -91,9 +91,10 @@ public TestGenericRestRequests(
TestingCredentialsRolesProvider credentialsRolesProvider,
@ForTesting HttpClient httpClient,
@ForTesting Credentials testingCredentials,
@ForS3MockContainer S3Client storageClient)
@ForS3MockContainer S3Client storageClient,
TrinoS3ProxyConfig trinoS3ProxyConfig)
{
baseUri = httpServer.getBaseUrl();
baseUri = httpServer.getBaseUrl().resolve(trinoS3ProxyConfig.getS3Prefix());
this.credentialsRolesProvider = requireNonNull(credentialsRolesProvider, "credentialsRolesProvider is null");
this.httpClient = requireNonNull(httpClient, "httpClient is null");
this.testingCredentials = requireNonNull(testingCredentials, "testingCredentials is null");
Expand All @@ -117,7 +118,6 @@ public void testAwsChunkedUpload()
private StatusResponse doAwsChunkedUpload(String content)
{
URI uri = UriBuilder.fromUri(baseUri)
.replacePath(TrinoS3ProxyRestConstants.S3_PATH)
.path("two")
.path("test")
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import com.google.inject.Inject;
import io.airlift.http.server.testing.TestingHttpServer;
import io.trino.s3.proxy.server.credentials.Credentials;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig;
import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer;
import io.trino.s3.proxy.server.testing.TestingCredentialsRolesProvider;
import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting;
Expand Down Expand Up @@ -51,9 +51,10 @@ public TestProxiedAssumedRoleRequests(
@ForTesting Credentials testingCredentials,
TestingCredentialsRolesProvider credentialsController,
@ForS3MockContainer S3Client storageClient,
@ForS3MockContainer List<String> configuredBuckets)
@ForS3MockContainer List<String> configuredBuckets,
TrinoS3ProxyConfig trinoS3ProxyConfig)
{
this(buildClient(httpServer, testingCredentials), testingCredentials, credentialsController, storageClient, configuredBuckets);
this(buildClient(httpServer, testingCredentials, trinoS3ProxyConfig.getS3Prefix(), trinoS3ProxyConfig.getStsPrefix()), testingCredentials, credentialsController, storageClient, configuredBuckets);
}

protected TestProxiedAssumedRoleRequests(
Expand All @@ -75,11 +76,11 @@ public void validateCount()
credentialsController.resetAssumedRoles();
}

protected static S3Client buildClient(TestingHttpServer httpServer, Credentials credentials)
protected static S3Client buildClient(TestingHttpServer httpServer, Credentials credentials, String s3Prefix, String stsPrefix)
{
URI baseUrl = httpServer.getBaseUrl();
URI localProxyServerUri = baseUrl.resolve(TrinoS3ProxyRestConstants.S3_PATH);
URI localStsServerUri = baseUrl.resolve(TrinoS3ProxyRestConstants.STS_PATH);
URI localProxyServerUri = baseUrl.resolve(s3Prefix);
URI localStsServerUri = baseUrl.resolve(stsPrefix);

AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(credentials.emulated().accessKey(), credentials.emulated().secretKey());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.google.inject.Inject;
import io.airlift.http.server.testing.TestingHttpServer;
import io.trino.s3.proxy.server.credentials.Credentials;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig;
import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer;
import io.trino.s3.proxy.server.testing.TestingCredentialsRolesProvider;
import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServerModule.ForTestingRemoteCredentials;
Expand All @@ -34,8 +35,9 @@ public TestProxiedEmulatedAndRemoteAssumedRoleRequests(
TestingCredentialsRolesProvider credentialsController,
@ForS3MockContainer S3Client storageClient,
@ForS3MockContainer List<String> configuredBuckets,
@ForTestingRemoteCredentials Credentials remoteCredentials)
@ForTestingRemoteCredentials Credentials remoteCredentials,
TrinoS3ProxyConfig trinoS3ProxyConfig)
{
super(buildClient(httpServer, remoteCredentials), testingCredentials, credentialsController, storageClient, configuredBuckets);
super(buildClient(httpServer, remoteCredentials, trinoS3ProxyConfig.getS3Prefix(), trinoS3ProxyConfig.getStsPrefix()), testingCredentials, credentialsController, storageClient, configuredBuckets);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,28 @@

import com.google.inject.Inject;
import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer;
import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer;
import io.trino.s3.proxy.server.testing.harness.BuilderFilter;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules.WithConfiguredBuckets;
import software.amazon.awssdk.services.s3.S3Client;

import java.util.List;

@TrinoS3ProxyTest(filters = WithConfiguredBuckets.class)
@TrinoS3ProxyTest(filters = {WithConfiguredBuckets.class, TestProxiedRequests.Filter.class})
public class TestProxiedRequests
extends AbstractTestProxiedRequests
{
public static class Filter
implements BuilderFilter
{
@Override
public TestingTrinoS3ProxyServer.Builder filter(TestingTrinoS3ProxyServer.Builder builder)
{
return builder.withProperty("s3proxy.s3.prefix", "/api/some/s3/prefix");
}
}

@Inject
public TestProxiedRequests(S3Client s3Client, @ForS3MockContainer S3Client storageClient, @ForS3MockContainer List<String> configuredBuckets)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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;

import com.google.inject.Inject;
import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer;
import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer;
import io.trino.s3.proxy.server.testing.harness.BuilderFilter;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules.WithConfiguredBuckets;
import software.amazon.awssdk.services.s3.S3Client;

import java.util.List;

@TrinoS3ProxyTest(filters = {WithConfiguredBuckets.class, TestProxiedRequestsWithoutPrefix.Filter.class})
public class TestProxiedRequestsWithoutPrefix
extends AbstractTestProxiedRequests
{
public static class Filter
implements BuilderFilter
{
@Override
public TestingTrinoS3ProxyServer.Builder filter(TestingTrinoS3ProxyServer.Builder builder)
{
return builder.withProperty("s3proxy.s3.prefix", "");
}
}

@Inject
public TestProxiedRequestsWithoutPrefix(S3Client s3Client, @ForS3MockContainer S3Client storageClient, @ForS3MockContainer List<String> configuredBuckets)
{
super(s3Client, storageClient, configuredBuckets);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,34 @@
import com.google.inject.Inject;
import io.airlift.http.server.testing.TestingHttpServer;
import io.trino.s3.proxy.server.credentials.Credentials;
import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig;
import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer;
import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServerModule.ForTestingRemoteCredentials;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules;
import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules.WithConfiguredBuckets;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.services.s3.S3Client;

import java.util.List;
import java.util.Optional;

import static io.trino.s3.proxy.server.testing.TestingUtil.clientBuilder;

@TrinoS3ProxyTest(filters = TrinoS3ProxyTestCommonModules.WithConfiguredBuckets.class)
@TrinoS3ProxyTest(filters = WithConfiguredBuckets.class)
public class TestRemoteSessionProxiedRequests
extends AbstractTestProxiedRequests
{
@Inject
public TestRemoteSessionProxiedRequests(@ForS3MockContainer S3Client storageClient, @ForTestingRemoteCredentials Credentials remoteCredentials, TestingHttpServer httpServer, @ForS3MockContainer List<String> configuredBuckets)
public TestRemoteSessionProxiedRequests(@ForS3MockContainer S3Client storageClient, @ForTestingRemoteCredentials Credentials remoteCredentials, TestingHttpServer httpServer, @ForS3MockContainer List<String> configuredBuckets, TrinoS3ProxyConfig trinoS3ProxyConfig)
{
super(buildInternalClient(remoteCredentials, httpServer), storageClient, configuredBuckets);
super(buildInternalClient(remoteCredentials, httpServer, trinoS3ProxyConfig.getS3Prefix()), storageClient, configuredBuckets);
}

private static S3Client buildInternalClient(Credentials credentials, TestingHttpServer httpServer)
private static S3Client buildInternalClient(Credentials credentials, TestingHttpServer httpServer, String s3Prefix)
{
AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(credentials.emulated().accessKey(), credentials.emulated().secretKey());

return clientBuilder(httpServer.getBaseUrl())
return clientBuilder(httpServer.getBaseUrl(), Optional.of(s3Prefix))
.credentialsProvider(() -> awsBasicCredentials)
.build();
}
Expand Down
Loading

0 comments on commit c6dacca

Please sign in to comment.