Skip to content

Commit 6547f3a

Browse files
committed
Add RemoteS3ConnectionProvider plugin implementations
Added generic implementations for the RemoteS3ConnectionProvider interface: config, file and http. Added a README for the file and http ones as those are a bit more complex. Added more tests to dynamic RemoteS3Facade creation
1 parent f2353a5 commit 6547f3a

31 files changed

+1694
-39
lines changed

trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/credentials/CredentialsProvider.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
import java.util.Optional;
1717

18-
// TODO: Add back file-based provider
1918
public interface CredentialsProvider
2019
{
2120
CredentialsProvider NOOP = (_, _) -> Optional.empty();

trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/remote/RemoteS3ConnectionProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ public interface RemoteS3ConnectionProvider
2424
{
2525
RemoteS3ConnectionProvider NOOP = (_, _, _) -> Optional.empty();
2626

27-
Optional<RemoteS3Connection> remoteConnection(SigningMetadata signingMetadata, Optional<Identity> identity, ParsedS3Request request);
27+
Optional<? extends RemoteS3Connection> remoteConnection(SigningMetadata signingMetadata, Optional<Identity> identity, ParsedS3Request request);
2828
}

trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
import io.trino.aws.proxy.server.credentials.http.HttpCredentialsModule;
3535
import io.trino.aws.proxy.server.remote.DefaultRemoteS3Module;
3636
import io.trino.aws.proxy.server.remote.RemoteS3ConnectionController;
37+
import io.trino.aws.proxy.server.remote.provider.config.ConfigRemoteS3ConnectionProviderModule;
38+
import io.trino.aws.proxy.server.remote.provider.file.FileBasedRemoteS3ConnectionModule;
39+
import io.trino.aws.proxy.server.remote.provider.http.HttpRemoteS3ConnectionProviderModule;
3740
import io.trino.aws.proxy.server.rest.LimitStreamController;
3841
import io.trino.aws.proxy.server.rest.ResourceSecurityDynamicFeature;
3942
import io.trino.aws.proxy.server.rest.RestModule;
@@ -134,6 +137,9 @@ protected void setup(Binder binder)
134137
install(new FileBasedCredentialsModule());
135138
install(new OpaS3SecurityModule());
136139
install(new HttpCredentialsModule());
140+
install(new FileBasedRemoteS3ConnectionModule());
141+
install(new ConfigRemoteS3ConnectionProviderModule());
142+
install(new HttpRemoteS3ConnectionProviderModule());
137143

138144
configBinder(binder).bindConfig(RemoteS3Config.class);
139145
// RemoteS3 provided implementation

trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/remote/DefaultRemoteS3Config.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package io.trino.aws.proxy.server.remote;
1515

16+
import com.fasterxml.jackson.annotation.JsonSetter;
1617
import io.airlift.configuration.Config;
1718
import jakarta.validation.constraints.Max;
1819
import jakarta.validation.constraints.Min;
@@ -29,34 +30,39 @@ public class DefaultRemoteS3Config
2930
private String hostnameTemplate = "${bucket}.s3.${region}.${domain}";
3031

3132
@Config("remoteS3.https")
33+
@JsonSetter("remoteS3.https")
3234
public DefaultRemoteS3Config setHttps(boolean https)
3335
{
3436
this.https = https;
3537
return this;
3638
}
3739

3840
@Config("remoteS3.domain")
41+
@JsonSetter("remoteS3.domain")
3942
public DefaultRemoteS3Config setDomain(String s3Domain)
4043
{
4144
this.domain = s3Domain;
4245
return this;
4346
}
4447

4548
@Config("remoteS3.port")
49+
@JsonSetter("remoteS3.port")
4650
public DefaultRemoteS3Config setPort(Integer port)
4751
{
4852
this.port = Optional.ofNullable(port);
4953
return this;
5054
}
5155

5256
@Config("remoteS3.virtual-host-style")
57+
@JsonSetter("remoteS3.virtual-host-style")
5358
public DefaultRemoteS3Config setVirtualHostStyle(boolean virtualHostStyle)
5459
{
5560
this.virtualHostStyle = virtualHostStyle;
5661
return this;
5762
}
5863

5964
@Config("remoteS3.hostname.template")
65+
@JsonSetter("remoteS3.hostname.template")
6066
public DefaultRemoteS3Config setHostnameTemplate(String hostnameTemplate)
6167
{
6268
this.hostnameTemplate = hostnameTemplate;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.aws.proxy.server.remote.provider;
15+
16+
import com.fasterxml.jackson.annotation.JsonCreator;
17+
import com.fasterxml.jackson.annotation.JsonProperty;
18+
import com.google.inject.ConfigurationException;
19+
import io.airlift.configuration.ConfigurationFactory;
20+
import io.trino.aws.proxy.server.remote.DefaultRemoteS3Config;
21+
import io.trino.aws.proxy.server.remote.PathStyleRemoteS3Facade;
22+
import io.trino.aws.proxy.server.remote.VirtualHostStyleRemoteS3Facade;
23+
import io.trino.aws.proxy.spi.credentials.Credential;
24+
import io.trino.aws.proxy.spi.remote.RemoteS3Connection;
25+
import io.trino.aws.proxy.spi.remote.RemoteS3Facade;
26+
import io.trino.aws.proxy.spi.remote.RemoteSessionRole;
27+
28+
import java.util.Map;
29+
import java.util.Optional;
30+
import java.util.Set;
31+
32+
import static com.google.common.collect.Sets.difference;
33+
import static java.lang.String.format;
34+
import static java.util.Objects.requireNonNull;
35+
36+
public record SerializableRemoteS3Connection(
37+
Credential remoteCredential,
38+
Optional<RemoteSessionRole> remoteSessionRole,
39+
Optional<RemoteS3Facade> remoteS3Facade)
40+
implements RemoteS3Connection
41+
{
42+
public SerializableRemoteS3Connection
43+
{
44+
requireNonNull(remoteCredential, "remoteCredential is null");
45+
requireNonNull(remoteSessionRole, "remoteSessionRole is null");
46+
requireNonNull(remoteS3Facade, "remoteS3Facade is null");
47+
}
48+
49+
@JsonCreator
50+
public static SerializableRemoteS3Connection fromConfig(
51+
@JsonProperty("remoteCredential") Credential remoteCredential,
52+
@JsonProperty("remoteSessionRole") Optional<RemoteSessionRole> remoteSessionRole,
53+
@JsonProperty("remoteS3FacadeConfiguration") Optional<Map<String, String>> remoteS3FacadeConfiguration)
54+
{
55+
Optional<RemoteS3Facade> facade = remoteS3FacadeConfiguration.map(config -> {
56+
ConfigurationFactory configurationFactory = new ConfigurationFactory(config);
57+
DefaultRemoteS3Config parsedConfig;
58+
try {
59+
parsedConfig = configurationFactory.build(DefaultRemoteS3Config.class);
60+
}
61+
catch (ConfigurationException e) {
62+
throw new IllegalArgumentException("Failed create RemoteS3Facade from RemoteS3FacadeConfiguration", e);
63+
}
64+
Set<String> unusedProperties = difference(configurationFactory.getProperties().keySet(), configurationFactory.getUsedProperties());
65+
if (!unusedProperties.isEmpty()) {
66+
throw new IllegalArgumentException(format("Failed to create RemoteS3Facade from RemoteS3FacadeConfiguration. Unused properties when instantiating " +
67+
"DefaultRemoteS3Config: %s", unusedProperties));
68+
}
69+
return parsedConfig.getVirtualHostStyle() ? new VirtualHostStyleRemoteS3Facade(parsedConfig) : new PathStyleRemoteS3Facade(parsedConfig);
70+
});
71+
return new SerializableRemoteS3Connection(remoteCredential, remoteSessionRole, facade);
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.aws.proxy.server.remote.provider.config;
15+
16+
import io.airlift.configuration.Config;
17+
import io.airlift.configuration.ConfigSecuritySensitive;
18+
import jakarta.validation.constraints.NotEmpty;
19+
import jakarta.validation.constraints.NotNull;
20+
21+
public class ConfigRemoteS3ConnectionProviderConfig
22+
{
23+
private String accessKey;
24+
private String secretKey;
25+
26+
@NotNull
27+
@NotEmpty
28+
public String getAccessKey()
29+
{
30+
return accessKey;
31+
}
32+
33+
@Config("remote-s3-connection-provider.access-key")
34+
public ConfigRemoteS3ConnectionProviderConfig setAccessKey(String accessKey)
35+
{
36+
this.accessKey = accessKey;
37+
return this;
38+
}
39+
40+
@NotNull
41+
@NotEmpty
42+
public String getSecretKey()
43+
{
44+
return secretKey;
45+
}
46+
47+
@ConfigSecuritySensitive
48+
@Config("remote-s3-connection-provider.secret-key")
49+
public ConfigRemoteS3ConnectionProviderConfig setSecretKey(String secretKey)
50+
{
51+
this.secretKey = secretKey;
52+
return this;
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.aws.proxy.server.remote.provider.config;
15+
16+
import com.google.inject.Binder;
17+
import io.airlift.configuration.AbstractConfigurationAwareModule;
18+
import io.trino.aws.proxy.spi.credentials.Credential;
19+
import io.trino.aws.proxy.spi.plugin.config.RemoteS3ConnectionProviderConfig;
20+
import io.trino.aws.proxy.spi.remote.RemoteS3Connection.StaticRemoteS3Connection;
21+
import io.trino.aws.proxy.spi.remote.RemoteS3ConnectionProvider;
22+
23+
import java.util.Optional;
24+
25+
import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder;
26+
import static io.airlift.configuration.ConditionalModule.conditionalModule;
27+
28+
public class ConfigRemoteS3ConnectionProviderModule
29+
extends AbstractConfigurationAwareModule
30+
{
31+
public static final String CONFIG_REMOTE_S3_CONNECTION_PROVIDER = "config";
32+
33+
@Override
34+
protected void setup(Binder binder)
35+
{
36+
install(conditionalModule(
37+
RemoteS3ConnectionProviderConfig.class,
38+
config -> config.getPluginIdentifier().map(CONFIG_REMOTE_S3_CONNECTION_PROVIDER::equals).orElse(false),
39+
innerBinder -> {
40+
ConfigRemoteS3ConnectionProviderConfig config = buildConfigObject(ConfigRemoteS3ConnectionProviderConfig.class);
41+
newOptionalBinder(innerBinder, RemoteS3ConnectionProvider.class)
42+
.setBinding()
43+
.toInstance((_, _, _) -> Optional.of(new StaticRemoteS3Connection(new Credential(config.getAccessKey(), config.getSecretKey()))));
44+
}));
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.aws.proxy.server.remote.provider.file;
15+
16+
import com.google.inject.Binder;
17+
import io.airlift.configuration.AbstractConfigurationAwareModule;
18+
import io.trino.aws.proxy.server.remote.provider.SerializableRemoteS3Connection;
19+
20+
import static io.airlift.configuration.ConfigBinder.configBinder;
21+
import static io.airlift.json.JsonCodecBinder.jsonCodecBinder;
22+
import static io.trino.aws.proxy.spi.plugin.TrinoAwsProxyServerBinding.remoteS3ConnectionProviderModule;
23+
24+
public class FileBasedRemoteS3ConnectionModule
25+
extends AbstractConfigurationAwareModule
26+
{
27+
// set as config value for "remote-s3-connection-provider.type"
28+
public static final String FILE_BASED_REMOTE_S3_CONNECTION_PROVIDER = "file";
29+
30+
@Override
31+
protected void setup(Binder binder)
32+
{
33+
install(remoteS3ConnectionProviderModule(
34+
FILE_BASED_REMOTE_S3_CONNECTION_PROVIDER,
35+
FileBasedRemoteS3ConnectionProvider.class,
36+
innerBinder -> {
37+
configBinder(innerBinder).bindConfig(FileBasedRemoteS3ConnectionProviderConfig.class);
38+
innerBinder.bind(FileBasedRemoteS3ConnectionProvider.class);
39+
jsonCodecBinder(innerBinder).bindMapJsonCodec(String.class, SerializableRemoteS3Connection.class);
40+
}));
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.aws.proxy.server.remote.provider.file;
15+
16+
import com.google.common.io.Files;
17+
import com.google.inject.Inject;
18+
import io.airlift.json.JsonCodec;
19+
import io.trino.aws.proxy.server.remote.provider.SerializableRemoteS3Connection;
20+
import io.trino.aws.proxy.spi.credentials.Identity;
21+
import io.trino.aws.proxy.spi.remote.RemoteS3Connection;
22+
import io.trino.aws.proxy.spi.remote.RemoteS3ConnectionProvider;
23+
import io.trino.aws.proxy.spi.rest.ParsedS3Request;
24+
import io.trino.aws.proxy.spi.signing.SigningMetadata;
25+
26+
import java.util.Map;
27+
import java.util.Optional;
28+
29+
/**
30+
* <p>File-based RemoteS3ConnectionProvider that reads a JSON file containing a mapping from emulated access key to
31+
* RemoteS3Connection.</p>
32+
* <pre>{@code
33+
* {
34+
* "emulated-access-key-1": {
35+
* "remoteCredential": {
36+
* "accessKey": "remote-access-key",
37+
* "secretKey": "remote-secret-key"
38+
* },
39+
* "remoteSessionRole": {
40+
* "region": "us-east-1",
41+
* "roleArn": "arn:aws:iam::123456789012:role/role-name",
42+
* "externalId": "external-id",
43+
* "stsEndpoint": "https://sts.us-east-1.amazonaws.com"
44+
* },
45+
* "remoteS3FacadeConfiguration": {
46+
* "remoteS3.https": true,
47+
* "remoteS3.domain": "s3.amazonaws.com",
48+
* "remoteS3.port": 443,
49+
* "remoteS3.virtual-host-style": false,
50+
* "remoteS3.hostname.template": "${domain}"
51+
* }
52+
* }
53+
* }
54+
* }</pre>
55+
*/
56+
public class FileBasedRemoteS3ConnectionProvider
57+
implements RemoteS3ConnectionProvider
58+
{
59+
private final Map<String, SerializableRemoteS3Connection> remoteS3Connections;
60+
61+
@Inject
62+
public FileBasedRemoteS3ConnectionProvider(FileBasedRemoteS3ConnectionProviderConfig config, JsonCodec<Map<String, SerializableRemoteS3Connection>> jsonCodec)
63+
{
64+
try {
65+
this.remoteS3Connections = jsonCodec.fromJson(Files.toByteArray(config.getConnectionsFile()));
66+
}
67+
catch (Exception e) {
68+
throw new RuntimeException("Failed to read remote S3 connections file", e);
69+
}
70+
}
71+
72+
@Override
73+
public Optional<RemoteS3Connection> remoteConnection(SigningMetadata signingMetadata, Optional<Identity> identity, ParsedS3Request request)
74+
{
75+
return Optional.ofNullable(remoteS3Connections.get(signingMetadata.credential().accessKey()));
76+
}
77+
}

0 commit comments

Comments
 (0)