Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CredentialsController, add RemoteConnection plugin #190

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

mosiac1
Copy link
Contributor

@mosiac1 mosiac1 commented Apr 1, 2025

This PR moves to a more dynamic approach to resolving remote credentials, where the whole Request object can be used to determine the endpoint it should be proxied to, the remote credentials to use and STS mechanism.

RemoteS3ConnectionProvider returns remote credentials, optional STS role and optional RemoteS3Facade configs (used to provide and endpoint but also change the path-style if needed). It takes as arguments the ParsedRequest, Identity and SigningMetadata.

The request flow with this change goes:

Client request to aws-proxy
-> CredentialsProvider to get emulated secret key and Identity
-> Signature verification (nothing changed)
-> SecurityFacade (nothing changed)
-> S3RequestRewriter (nothing changed)
-> RemoteS3ConnectionProvider to get remote credentials, role and remote configs
-> (Optional) Assume Remote Role
-> Sing request to remote (nothing changed)
-> Send request to remote (nothing changed)

This PR moves to a more dynamic approach to resolving remote credentials, where the whole Request object can be used to determine the endpoint it should be proxied to, the remote credentials to use and STS mechanism.

RemoteS3ConnectionProvider returns remote credentials, optional STS role and optional RemoteS3Facade configs (used to provide and endpoint but also change the path-style if needed). It takes as arguments the ParsedRequest, Identity and SigningMetadata.

The request flow with this change goes:

Client request to aws-proxy -> CredentialsProvider to get emulated secret key and Identity -> SecurityFacade -> S3RequestRewriter -> RemoteS3ConnectionProvider to get remote credentials, role and remote configs -> (Optional) Assume Remote Role -> Sing request to remote -> Send request to remote
@mosiac1 mosiac1 force-pushed the feat/refactor-remote branch from 00c4af6 to abd2f9d Compare April 2, 2025 14:40
Copy link
Member

@Randgalt Randgalt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a large change and I want to spend some time on it. Blocking for that reason.

@Randgalt Randgalt requested a review from Laonel April 8, 2025 06:26
@Randgalt
Copy link
Member

Randgalt commented Apr 8, 2025

@Laonel please review this to see how it affects our codebase

@@ -59,7 +59,7 @@ public TrinoGlueResource(ObjectMapper objectMapper, GlueRequestHandler requestHa
@Produces(MediaType.APPLICATION_JSON)
public Response gluePost(@Context Request request, @Context SigningMetadata signingMetadata, @Context RequestLoggingSession requestLoggingSession)
{
requestLoggingSession.logProperty("request.glue.emulated.key", signingMetadata.credentials().emulated().secretKey());
requestLoggingSession.logProperty("request.glue.emulated.key", signingMetadata.credential().secretKey());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put this in a separate commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean changes to the Glue pieces? I can't really split it, interfaces and records changed so classes wouldn't compile.

In this line specifically, the change is due to the fact that SigningMetadata doesn't have a Credentials field anymore (since Credentials was deleted) but only a simple Credential which in the emulated credential. So no behaviour change.

Copy link
Member

@Laonel Laonel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, it looks like the right direction, and it's also consistent with what we are doing on our side, which I think is an additional validation :) But I think there are some changes required around RemoteS3Facade plugin to avoid regressions.

this(emulatedCredential, Optional.empty());
}

public static IdentityCredential of(Credential emulatedCredential)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would decide on a single way for public clients to create instances. At the moment, there is a public constructor and a factory method with the same signature.

requireNonNull(emulatedCredential, "emulatedCredential is null");
requireNonNull(emulatedCredential, "credential is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

{
requireNonNull(remoteCredential, "remoteCredential is null");
requireNonNull(remoteSessionRole, "remoteSessionRole is null");
remoteS3FacadeConfiguration = requireNonNull(remoteS3FacadeConfiguration, "remoteS3FacadeConfiguration is null").map(ImmutableMap::copyOf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: redundant rnn

Comment on lines +143 to +147
RemoteS3Facade contextRemoteS3Facade = remoteConnection.remoteS3FacadeConfiguration().map(config -> {
// TODO: This should respect the plugin installed for the RemoteS3Facade somehow
Injector subInjector = new Bootstrap(new DefaultRemoteS3Module()).doNotInitializeLogging().quiet().setRequiredConfigurationProperties(config).initialize();
return subInjector.getInstance(RemoteS3Facade.class);
}).orElse(defaultS3Facade);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How this will work without implementing the TODO? If there is config provided, we will initialize a new RemoteS3Facade provided by the DefaultRemoteS3Module. Otherwise, we are using defaultS3Facade instance, which is provided by the installed RemoteS3Facade plugin.

I see two possible solutions:

  • change remote S3 plugin to provide RemoteS3FacadeProvider that can consume config and return RemoteS3Facade instance
  • change the plugin to provide a RemoteS3Module supplier that we would use to create an injector here. Seems a bit hacky, but that would allow to utilize Airlift for injecting remoteS3FacadeConfiguration into that instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now this would work, but only with the default path-style and virtual-host stye RemoteS3Facade implementations.

I was looking for some inspiration on how to do include plugins, so thanks for the suggestions!

1 makes sense, it might just be the cleanest, but I am not sure how we would resolve what configs to pass to the RemoteS3FacadeFactory (calling it a factory here instead of a Provider to match Trino naming which I'm more used to). We can:

  1. Have the configs for this in a separate file like remote-s3-facade.properties (like Trino does). This would make this plugin stand out from the other that don't have this approach, but I don't think that would be an issue
  2. We grab all the configs from the main config.properties that start with remote-s3. and feed them to the factory. This feels more hacky tbh.

for 2, I had an idea in a similar direction, but I am not sure that would work. A Plugin instance returns a Module that may be a combination of multiple modules, we don't an easy was to find RemoteS3Module instances among those.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now I am thinking if they following would work:

  • A plugin binds RemoteS3FacadeFactory, and can bind additional config it needs to inject to the factory. That config would then be defined in config.properties.
  • On each call, RemoteS3FacadeFactory is creating a new instance of RemoteS3Facade using the config defined in the implementation.

Am I missing something? I see you have remote S3 config in RemoteS3Connection and I am not sure where that config would be coming from.

That being said, I think having separate files for remote S3 facade plugins would be good too, if that is required or makes things easier. I like the similarity with Trino as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i may not have fully gotten what you meant in the first comment.

My goal with this change is to make it so the RemoteS3Connection (which you get from the RemoteS3ConnectionProvider which might be calling an external http service) has control over the endpoint that the request ends up being proxied to. The way we control the endpoint in aws-proxy is though the RemoteS3Facade abstraction - so we need a way to create a RemoteS3Facade instance from a RemoteS3Connection object.

RemoteS3Connection has a set of configs (like the ones in config.properties) which are used through Airlift to inject a RemoteS3Facade instance. That Facade is used once to resolve the URI for this request and then is discarded (we could cache these, might be a decent idea).

On each call, RemoteS3FacadeFactory is creating a new instance of RemoteS3Facade using the config defined in the implementation.

I my mind, this would be "using the config from the RemoteS3ConnectionProvider".

I will have a go at a draft implementation

@@ -68,10 +70,9 @@ public void filter(ContainerRequestContext requestContext)
RequestLoggingSession requestLoggingSession = requestLoggerController.newRequestSession(request, signingServiceType);
containerRequest.setProperty(RequestLoggingSession.class.getName(), requestLoggingSession);

SigningMetadata signingMetadata;
SigningController.SigningIdentity signingIdentity;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: static import?

this.s3SecurityController = requireNonNull(s3SecurityController, "securityController is null");
this.s3PresignController = requireNonNull(s3PresignController, "presignController is null");
this.limitStreamController = requireNonNull(limitStreamController, "quotaStreamController is null");
this.s3RequestRewriter = requireNonNull(s3RequestRewriter, "s3RequestRewriter is null");
this.remoteS3ConnectionController = requireNonNull(credentialsProvider, "credentialsController is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix rnn message and / or param name

{
this.credentialsController = requireNonNull(credentialsController, "credentialsController is null");
this.credentialsProvider = requireNonNull(credentialsProvider, "signingCredentialProvider is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.credentialsProvider = requireNonNull(credentialsProvider, "signingCredentialProvider is null");
this.credentialsProvider = requireNonNull(credentialsProvider, "credentialsProvider is null");

{
this.initialBuckets = requireNonNull(initialBuckets, "initialBuckets is null");
this.credential = requireNonNull(credentials, "credentials is null").requiredRemoteCredential();
this.credential = requireNonNull(remoteCredentials, "credentials is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.credential = requireNonNull(remoteCredentials, "credentials is null");
this.credential = requireNonNull(remoteCredentials, "remoteCredentials is null");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants