Skip to content

Commit

Permalink
Add status resource with public access
Browse files Browse the repository at this point in the history
Address #157.
Default to path `/api/v1/s3Proxy/status`, returns aws-proxy status.
Refactor `SigningServiceType` binding to used annotation.
  • Loading branch information
mosiac1 committed Oct 1, 2024
1 parent 7fecc58 commit 1dfc344
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class TrinoAwsProxyConfig
private String logsPath = "/api/v1/s3Proxy/logs";
private int requestLoggerSavedQty = 10000;
private Optional<DataSize> maxPayloadSize = Optional.empty();
private String statusPath = "/api/v1/s3Proxy/status";

@Config("aws.proxy.s3.hostname")
@ConfigDescription("Hostname to use for S3 REST operations, virtual-host style addressing is only supported if this is set")
Expand Down Expand Up @@ -147,4 +148,18 @@ public TrinoAwsProxyConfig setMaxPayloadSize(DataSize maxPayloadSize)
this.maxPayloadSize = Optional.of(requireNonNull(maxPayloadSize, "requestByteQuota is null"));
return this;
}

@NotNull
public String getStatusPath()
{
return statusPath;
}

@Config("aws.proxy.status.path")
@ConfigDescription("URL Path for node status, optional")
public TrinoAwsProxyConfig setStatusPath(String statusPath)
{
this.statusPath = statusPath;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
import io.airlift.configuration.AbstractConfigurationAwareModule;
import io.airlift.http.server.HttpServerBinder;
import io.airlift.jaxrs.JaxrsBinder;
Expand All @@ -36,14 +35,16 @@
import io.trino.aws.proxy.server.credentials.http.HttpCredentialsModule;
import io.trino.aws.proxy.server.remote.RemoteS3Module;
import io.trino.aws.proxy.server.rest.LimitStreamController;
import io.trino.aws.proxy.server.rest.RequestFilter;
import io.trino.aws.proxy.server.rest.ParamProvider;
import io.trino.aws.proxy.server.rest.RequestLoggerController;
import io.trino.aws.proxy.server.rest.ResourceSecurityDynamicFeature;
import io.trino.aws.proxy.server.rest.S3PresignController;
import io.trino.aws.proxy.server.rest.ThrowableMapper;
import io.trino.aws.proxy.server.rest.TrinoLogsResource;
import io.trino.aws.proxy.server.rest.TrinoS3ProxyClient;
import io.trino.aws.proxy.server.rest.TrinoS3ProxyClient.ForProxyClient;
import io.trino.aws.proxy.server.rest.TrinoS3Resource;
import io.trino.aws.proxy.server.rest.TrinoStatusResource;
import io.trino.aws.proxy.server.rest.TrinoStsResource;
import io.trino.aws.proxy.server.security.S3SecurityController;
import io.trino.aws.proxy.server.security.opa.OpaS3SecurityModule;
Expand All @@ -60,15 +61,13 @@
import io.trino.aws.proxy.spi.plugin.config.S3SecurityFacadeProviderConfig;
import io.trino.aws.proxy.spi.rest.S3RequestRewriter;
import io.trino.aws.proxy.spi.security.S3SecurityFacadeProvider;
import io.trino.aws.proxy.spi.signing.SigningServiceType;
import org.glassfish.jersey.server.model.Resource;

import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import static com.google.inject.multibindings.MapBinder.newMapBinder;
import static com.google.inject.multibindings.Multibinder.newSetBinder;
import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder;
import static io.airlift.configuration.ConfigBinder.configBinder;
Expand All @@ -89,13 +88,13 @@ protected void setup(Binder binder)

JaxrsBinder jaxrsBinder = jaxrsBinder(binder);

MapBinder<Class<?>, SigningServiceType> signingServiceTypesMapBinder = newMapBinder(binder, new TypeLiteral<>() {}, new TypeLiteral<>() {});

jaxrsBinder.bind(RequestFilter.class);
jaxrsBinder.bind(ThrowableMapper.class);
bindResourceAtPath(jaxrsBinder, signingServiceTypesMapBinder, SigningServiceType.S3, TrinoS3Resource.class, builtConfig.getS3Path());
bindResourceAtPath(jaxrsBinder, signingServiceTypesMapBinder, SigningServiceType.STS, TrinoStsResource.class, builtConfig.getStsPath());
bindResourceAtPath(jaxrsBinder, signingServiceTypesMapBinder, SigningServiceType.LOGS, TrinoLogsResource.class, builtConfig.getLogsPath());
jaxrsBinder.bind(ParamProvider.class);
jaxrsBinder.bind(ResourceSecurityDynamicFeature.class);
bindResourceAtPath(jaxrsBinder, TrinoS3Resource.class, builtConfig.getS3Path());
bindResourceAtPath(jaxrsBinder, TrinoStsResource.class, builtConfig.getStsPath());
bindResourceAtPath(jaxrsBinder, TrinoLogsResource.class, builtConfig.getLogsPath());
bindResourceAtPath(jaxrsBinder, TrinoStatusResource.class, builtConfig.getStatusPath());

binder.bind(CredentialsController.class).in(Scopes.SINGLETON);
binder.bind(RequestLoggerController.class).in(Scopes.SINGLETON);
Expand Down Expand Up @@ -211,11 +210,9 @@ private void installPlugins()
});
}

private static void bindResourceAtPath(JaxrsBinder jaxrsBinder, MapBinder<Class<?>, SigningServiceType> signingServiceTypesMapBinder, SigningServiceType signingServiceType, Class<?> resourceClass, String resourcePathPrefix)
private static void bindResourceAtPath(JaxrsBinder jaxrsBinder, Class<?> resourceClass, String resourcePrefix)
{
Resource resource = Resource.builder(resourceClass).path(resourcePathPrefix).build();
jaxrsBinder.bind(resourceClass);
jaxrsBinder.bindInstance(resource);
signingServiceTypesMapBinder.addBinding(resourceClass).toInstance(signingServiceType);
jaxrsBinder.bindInstance(Resource.builder(resourceClass).path(resourcePrefix).build());
}
}
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.rest;

import io.airlift.units.Duration;

public record NodeStatus(
String nodeId,
String environment,
Duration uptime,
String externalAddress,
String internalAddress,
int processors,
double processCpuLoad,
double systemCpuLoad,
long heapUsed,
long heapAvailable,
long nonHeapUsed)
{ }
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.rest;

import io.trino.aws.proxy.spi.rest.Request;
import io.trino.aws.proxy.spi.signing.SigningMetadata;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;

import java.util.function.Function;

import static io.trino.aws.proxy.server.rest.SecurityFilter.unwrap;
import static org.glassfish.jersey.server.spi.internal.ValueParamProvider.Priority.HIGH;

public class ParamProvider
implements ValueParamProvider
{
@Override
public Function<ContainerRequest, ?> getValueProvider(Parameter parameter)
{
if (Request.class.isAssignableFrom(parameter.getRawType()) || SigningMetadata.class.isAssignableFrom(parameter.getRawType()) || RequestLoggingSession.class.isAssignableFrom(parameter.getRawType())) {
return containerRequest -> unwrap(containerRequest, parameter.getRawType());
}
return null;
}

@Override
public PriorityType getPriority()
{
return HIGH;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.rest;

import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.PublicAccess;
import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.SigV4Access;
import io.trino.aws.proxy.spi.signing.SigningServiceType;

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

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.Objects.requireNonNull;

@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface ResourceSecurity
{
enum AccessType
{
PUBLIC(new PublicAccess()),
S3(new SigV4Access(SigningServiceType.S3)),
STS(new SigV4Access(SigningServiceType.STS)),
LOGS(new SigV4Access(SigningServiceType.LOGS));

public sealed interface Access
{
record PublicAccess() implements Access {}

record SigV4Access(SigningServiceType signingServiceType) implements Access {}
}

private final Access access;

AccessType(Access access)
{
this.access = requireNonNull(access, "access is null");
}

public Access access()
{
return access;
}
}

AccessType value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.rest;

import com.google.inject.Inject;
import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType;
import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.PublicAccess;
import io.trino.aws.proxy.server.rest.ResourceSecurity.AccessType.Access.SigV4Access;
import io.trino.aws.proxy.spi.signing.SigningController;
import io.trino.aws.proxy.spi.signing.SigningServiceType;
import jakarta.ws.rs.container.DynamicFeature;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.FeatureContext;

import java.lang.reflect.AnnotatedElement;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

public class ResourceSecurityDynamicFeature
implements DynamicFeature
{
private final SigningController signingController;
private final RequestLoggerController requestLoggerController;

@Inject
public ResourceSecurityDynamicFeature(SigningController signingController, RequestLoggerController requestLoggerController)
{
this.signingController = requireNonNull(signingController);
this.requestLoggerController = requireNonNull(requestLoggerController);
}

@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context)
{
if (resourceInfo.getResourceClass().getPackageName().startsWith("io.trino.aws")) {
AccessType accessType = getAccessType(resourceInfo);
switch (accessType.access()) {
case PublicAccess _ -> {}
case SigV4Access(SigningServiceType signingServiceType) ->
context.register(new SecurityFilter(signingController, signingServiceType, requestLoggerController));
}
}
}

private static AccessType getAccessType(ResourceInfo resourceInfo)
{
return getAccessTypeFromAnnotation(resourceInfo.getResourceMethod())
.or(() -> getAccessTypeFromAnnotation(resourceInfo.getResourceClass()))
.orElseThrow(() -> new IllegalArgumentException("Proxy resource is not annotated with @" + ResourceSecurity.class.getSimpleName() + ": " + resourceInfo.getResourceMethod()));
}

private static Optional<AccessType> getAccessTypeFromAnnotation(AnnotatedElement annotatedElement)
{
return Optional.ofNullable(annotatedElement.getAnnotation(ResourceSecurity.class))
.map(ResourceSecurity::value);
}
}
Loading

0 comments on commit 1dfc344

Please sign in to comment.