Skip to content

Commit

Permalink
Add http credentials provider
Browse files Browse the repository at this point in the history
  • Loading branch information
pranavr12 committed Aug 6, 2024
1 parent da0c9e0 commit f882b28
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 0 deletions.
6 changes: 6 additions & 0 deletions trino-aws-proxy/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@
<artifactId>utils</artifactId>
</dependency>

<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.airlift</groupId>
<artifactId>log-manager</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.airlift.log.Logger;
import io.trino.aws.proxy.server.credentials.CredentialsController;
import io.trino.aws.proxy.server.credentials.file.FileBasedCredentialsModule;
import io.trino.aws.proxy.server.credentials.http.HttpCredentialsModule;
import io.trino.aws.proxy.server.remote.RemoteS3Module;
import io.trino.aws.proxy.server.rest.RequestFilter;
import io.trino.aws.proxy.server.rest.RequestLoggerController;
Expand Down Expand Up @@ -119,6 +120,7 @@ protected void setup(Binder binder)
// provided implementations
install(new FileBasedCredentialsModule());
install(new OpaS3SecurityModule());
install(new HttpCredentialsModule());

// AssumedRoleProvider binder
configBinder(binder).bindConfig(AssumedRoleProviderConfig.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.aws.proxy.server.credentials.http;

import com.google.inject.BindingAnnotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface ForHttpCredentialsProvider {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.aws.proxy.server.credentials.http;

import com.google.inject.Binder;
import io.airlift.configuration.AbstractConfigurationAwareModule;

import static io.airlift.configuration.ConfigBinder.configBinder;
import static io.airlift.http.client.HttpClientBinder.httpClientBinder;
import static io.trino.aws.proxy.spi.plugin.TrinoAwsProxyServerBinding.credentialsProviderModule;

public class HttpCredentialsModule
extends AbstractConfigurationAwareModule
{
// set as config value for "credentials-provider.type"
public static final String HTTP_CREDENTIALS_IDENTIFIER = "http";

@Override
protected void setup(Binder binder)
{
install(credentialsProviderModule(
HTTP_CREDENTIALS_IDENTIFIER,
HttpCredentialsProvider.class,
innerBinder -> {
configBinder(innerBinder).bindConfig(HttpCredentialsProviderConfig.class);
innerBinder.bind(HttpCredentialsProvider.class);
httpClientBinder(innerBinder).bindHttpClient(HTTP_CREDENTIALS_IDENTIFIER, ForHttpCredentialsProvider.class);
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.aws.proxy.server.credentials.http;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.inject.Inject;
import io.airlift.http.client.HttpClient;
import io.airlift.http.client.Request;
import io.airlift.http.client.StringResponseHandler;
import io.airlift.log.Logger;
import io.trino.aws.proxy.server.rest.RequestLoggerController;
import io.trino.aws.proxy.spi.credentials.Credentials;
import io.trino.aws.proxy.spi.credentials.CredentialsProvider;
import io.trino.aws.proxy.spi.credentials.Identity;
import jakarta.ws.rs.core.UriBuilder;

import java.util.Optional;

import static io.airlift.http.client.Request.Builder.prepareGet;
import static io.airlift.http.client.StringResponseHandler.createStringResponseHandler;
import static java.util.Objects.requireNonNull;

public class HttpCredentialsProvider
implements CredentialsProvider
{
private static final Logger log = Logger.get(RequestLoggerController.class);

private final HttpClient httpClient;
private final HttpCredentialsProviderConfig httpCredentialsProviderConfig;
private final ObjectMapper objectMapper;

@Inject
public HttpCredentialsProvider(@ForHttpCredentialsProvider HttpClient httpClient, HttpCredentialsProviderConfig config, ObjectMapper objectMapper, Class<? extends Identity> identityClass)
{
requireNonNull(config, "Config is null");
requireNonNull(httpClient, "httpClient is null");
requireNonNull(objectMapper, "objectMapper is null");
objectMapper = objectMapper.registerModule(new SimpleModule().addAbstractTypeMapping(Identity.class, identityClass));
this.httpClient = httpClient;
this.httpCredentialsProviderConfig = config;
this.objectMapper = objectMapper;
}

@Override
public Optional<Credentials> credentials(String emulatedAccessKey, Optional<String> session)
{
Request request = prepareGet()
.setUri(UriBuilder.fromUri(httpCredentialsProviderConfig.getEndpoint()).path(emulatedAccessKey).build())
.build();
StringResponseHandler.StringResponse response = httpClient.execute(request, createStringResponseHandler());
Optional<Credentials> credentials = Optional.empty();
try {
credentials = objectMapper.readValue(response.getBody(), new TypeReference<>() {});
}
catch (JsonProcessingException e) {
log.error("Failed to parse credentials", e);
}
return credentials;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.aws.proxy.server.credentials.http;

import io.airlift.configuration.Config;
import jakarta.validation.constraints.NotNull;

import java.net.URI;

public class HttpCredentialsProviderConfig
{
private URI endpoint;

@NotNull
public URI getEndpoint()
{
return endpoint;
}

@Config("credentials-provider.http.endpoint")
public HttpCredentialsProviderConfig setEndpoint(String endpoint)
{
this.endpoint = URI.create(endpoint);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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.aws.proxy.server.credentials.http;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import io.airlift.http.server.HttpServerConfig;
import io.airlift.http.server.HttpServerInfo;
import io.airlift.http.server.testing.TestingHttpServer;
import io.airlift.json.ObjectMapperProvider;
import io.airlift.node.NodeInfo;
import io.trino.aws.proxy.server.testing.TestingIdentity;
import io.trino.aws.proxy.server.testing.TestingTrinoAwsProxyServer;
import io.trino.aws.proxy.server.testing.harness.BuilderFilter;
import io.trino.aws.proxy.server.testing.harness.TrinoAwsProxyTest;
import io.trino.aws.proxy.spi.credentials.Credential;
import io.trino.aws.proxy.spi.credentials.Credentials;
import io.trino.aws.proxy.spi.credentials.CredentialsProvider;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Optional;

import static io.trino.aws.proxy.server.credentials.http.HttpCredentialsModule.HTTP_CREDENTIALS_IDENTIFIER;
import static io.trino.aws.proxy.spi.plugin.TrinoAwsProxyServerBinding.bindIdentityType;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;

@TrinoAwsProxyTest(filters = TestHttpCredentialsProvider.Filter.class)
public class TestHttpCredentialsProvider
{
private final CredentialsProvider credentialsProvider;

public static class Filter
implements BuilderFilter
{
@Override
public TestingTrinoAwsProxyServer.Builder filter(TestingTrinoAwsProxyServer.Builder builder)
{
TestingHttpServer httpCredentialsServer;
try {
httpCredentialsServer = createTestingHttpCredentialsServer();
httpCredentialsServer.start();
}
catch (Exception e) {
throw new RuntimeException("Failed to start test http credentials provider server", e);
}
return builder.withoutTestingCredentialsRoleProviders()
.addModule(new HttpCredentialsModule())
.addModule(binder -> bindIdentityType(binder, TestingIdentity.class))
.withProperty("credentials-provider.type", HTTP_CREDENTIALS_IDENTIFIER)
.withProperty("credentials-provider.http.endpoint", httpCredentialsServer.getBaseUrl().toString());
}
}

@Inject
public TestHttpCredentialsProvider(CredentialsProvider credentialsProvider)
{
this.credentialsProvider = requireNonNull(credentialsProvider, "credentialsProvider is null");
}

@Test
public void testValidCredentials()
{
Credential emulated = new Credential("test-emulated-access-key", "test-emulated-secret");
Credential remote = new Credential("test-remote-access-key", "test-remote-secret");
Credentials expected = new Credentials(emulated, Optional.of(remote), Optional.empty(), Optional.of(new TestingIdentity("test-username", ImmutableList.of(), "xyzpdq")));
Optional<Credentials> actual = credentialsProvider.credentials("test-emulated-access-key", Optional.empty());
assertThat(actual).contains(expected);
}

@Test
public void testInvalidCredentials()
{
Optional<Credentials> actual = credentialsProvider.credentials("non-existent-key", Optional.empty());
assertThat(actual).isEmpty();
}

private static TestingHttpServer createTestingHttpCredentialsServer()
throws IOException
{
NodeInfo nodeInfo = new NodeInfo("test");
HttpServerConfig config = new HttpServerConfig().setHttpPort(0);
HttpServerInfo httpServerInfo = new HttpServerInfo(config, nodeInfo);
return new TestingHttpServer(httpServerInfo, nodeInfo, config, new HttpCredentialsServlet(), ImmutableMap.of());
}

private static class HttpCredentialsServlet
extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException
{
switch (request.getPathInfo()) {
case "/test-emulated-access-key": {
Credential emulated = new Credential("test-emulated-access-key", "test-emulated-secret");
Credential remote = new Credential("test-remote-access-key", "test-remote-secret");
Credentials credentials = new Credentials(emulated, Optional.of(remote), Optional.empty(), Optional.of(new TestingIdentity("test-username", ImmutableList.of(), "xyzpdq")));
String jsonCredentials = new ObjectMapperProvider().get().writeValueAsString(credentials);
response.setContentType(APPLICATION_JSON);
response.getWriter().print(jsonCredentials);
break;
}
default: {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.aws.proxy.server.credentials.http;

import com.google.common.collect.ImmutableMap;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Map;

import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping;

public class TestHttpCredentialsProviderConfig
{
@Test
public void testExplicitPropertyMappings()
throws IOException
{
Map<String, String> properties = ImmutableMap.of(
"credentials-provider.http.endpoint", "http://usersvc:9000/api/v1/users");

HttpCredentialsProviderConfig expected = new HttpCredentialsProviderConfig()
.setEndpoint("http://usersvc:9000/api/v1/users");
assertFullMapping(properties, expected);
}
}

0 comments on commit f882b28

Please sign in to comment.