From da0c9e059c1c0f2fc54fbb9d32e3b1136183668a Mon Sep 17 00:00:00 2001 From: Jordan Zimmerman Date: Thu, 1 Aug 2024 09:17:10 +0100 Subject: [PATCH] Support for OPA policy security Security decisions are forwarded to an OPA server. Users must provide a mapping from possible table, request, action, etc. into an input document which is sent to OPA. --- .../proxy/spi/security/opa/OpaRequest.java | 38 +++++ .../spi/security/opa/OpaS3SecurityMapper.java | 70 +++++++++ .../server/TrinoAwsProxyServerModule.java | 5 +- .../aws/proxy/server/security/opa/ForOpa.java | 31 ++++ .../security/opa/OpaS3SecurityConfig.java | 37 +++++ .../opa/OpaS3SecurityFacadeProvider.java | 80 ++++++++++ .../security/opa/OpaS3SecurityModule.java | 38 +++++ .../aws/proxy/server/TestOpaSecurity.java | 137 ++++++++++++++++++ .../testing/TestingTrinoAwsProxyServer.java | 13 ++ .../testing/containers/OpaContainer.java | 51 +++++++ 10 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaRequest.java create mode 100644 trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaS3SecurityMapper.java create mode 100644 trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/ForOpa.java create mode 100644 trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityConfig.java create mode 100644 trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityFacadeProvider.java create mode 100644 trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityModule.java create mode 100644 trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/TestOpaSecurity.java create mode 100644 trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/containers/OpaContainer.java diff --git a/trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaRequest.java b/trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaRequest.java new file mode 100644 index 00000000..0c0f951e --- /dev/null +++ b/trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaRequest.java @@ -0,0 +1,38 @@ +/* + * 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.spi.security.opa; + +import com.google.common.collect.ImmutableMap; +import io.trino.aws.proxy.spi.util.ImmutableMultiMap; +import io.trino.aws.proxy.spi.util.MultiMap; + +import java.net.URI; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +public record OpaRequest(URI opaServerUri, Map document, MultiMap additionalHeaders) +{ + public OpaRequest + { + requireNonNull(opaServerUri, "opaServerUri is null"); + document = ImmutableMap.copyOf(document); + additionalHeaders = ImmutableMultiMap.copyOf(additionalHeaders); + } + + public OpaRequest(URI opaServerUri, Map document) + { + this(opaServerUri, document, ImmutableMultiMap.empty()); + } +} diff --git a/trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaS3SecurityMapper.java b/trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaS3SecurityMapper.java new file mode 100644 index 00000000..662741ff --- /dev/null +++ b/trino-aws-proxy-spi/src/main/java/io/trino/aws/proxy/spi/security/opa/OpaS3SecurityMapper.java @@ -0,0 +1,70 @@ +/* + * 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.spi.security.opa; + +import com.google.common.collect.ImmutableMap; +import io.trino.aws.proxy.spi.rest.ParsedS3Request; +import io.trino.aws.proxy.spi.security.SecurityResponse; + +import java.net.URI; +import java.util.Map; +import java.util.Optional; + +import static io.trino.aws.proxy.spi.security.SecurityResponse.FAILURE; +import static io.trino.aws.proxy.spi.security.SecurityResponse.SUCCESS; + +@SuppressWarnings("SameParameterValue") +@FunctionalInterface +public interface OpaS3SecurityMapper +{ + default Map toInputDocument(Map document) + { + /* + Default formats standard input: + + { + "input": { + ... nested document ... + } + } + */ + + return ImmutableMap.of("input", document); + } + + default SecurityResponse toSecurityResponse(Map responseDocument) + { + /* + Default handles standard response: + + { + "result": true/false + } + */ + + return extractBoolean(responseDocument, "result") + .map(allowed -> allowed ? SUCCESS : FAILURE) + .orElse(FAILURE); + } + + OpaRequest toRequest(ParsedS3Request request, Optional lowercaseAction, URI baseUri); + + static Optional extractBoolean(Map map, String key) + { + return switch (map.get(key)) { + case Boolean b -> Optional.of(b); + case null, default -> Optional.empty(); + }; + } +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java index ca6807c8..bc622cb6 100644 --- a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/TrinoAwsProxyServerModule.java @@ -40,6 +40,7 @@ import io.trino.aws.proxy.server.rest.TrinoS3Resource; 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; import io.trino.aws.proxy.server.signing.SigningControllerConfig; import io.trino.aws.proxy.server.signing.SigningModule; import io.trino.aws.proxy.spi.credentials.AssumedRoleProvider; @@ -114,8 +115,10 @@ protected void setup(Binder binder) log.info("Using %s identity type", StandardIdentity.class.getSimpleName()); return StandardIdentity.class; }); - // CredentialsProvider provided implementations + + // provided implementations install(new FileBasedCredentialsModule()); + install(new OpaS3SecurityModule()); // AssumedRoleProvider binder configBinder(binder).bindConfig(AssumedRoleProviderConfig.class); diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/ForOpa.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/ForOpa.java new file mode 100644 index 00000000..e10cdb57 --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/ForOpa.java @@ -0,0 +1,31 @@ +/* + * 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.security.opa; + +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; + +@Retention(RUNTIME) +@Target({FIELD, PARAMETER, METHOD}) +@BindingAnnotation +public @interface ForOpa +{ +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityConfig.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityConfig.java new file mode 100644 index 00000000..2ef2a9a1 --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityConfig.java @@ -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.security.opa; + +import io.airlift.configuration.Config; +import jakarta.validation.constraints.NotNull; + +import java.net.URI; + +public class OpaS3SecurityConfig +{ + private URI opaServerBaseUri; + + @NotNull + public URI getOpaServerBaseUri() + { + return opaServerBaseUri; + } + + @Config("opa-s3-security.server-base-uri") + public OpaS3SecurityConfig setOpaServerBaseUri(String opaServerBaseUri) + { + this.opaServerBaseUri = URI.create(opaServerBaseUri); + return this; + } +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityFacadeProvider.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityFacadeProvider.java new file mode 100644 index 00000000..532aab4f --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityFacadeProvider.java @@ -0,0 +1,80 @@ +/* + * 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.security.opa; + +import com.google.inject.Inject; +import io.airlift.http.client.HttpClient; +import io.airlift.http.client.Request; +import io.airlift.json.JsonCodec; +import io.trino.aws.proxy.spi.rest.ParsedS3Request; +import io.trino.aws.proxy.spi.security.S3SecurityFacade; +import io.trino.aws.proxy.spi.security.S3SecurityFacadeProvider; +import io.trino.aws.proxy.spi.security.SecurityResponse; +import io.trino.aws.proxy.spi.security.opa.OpaRequest; +import io.trino.aws.proxy.spi.security.opa.OpaS3SecurityMapper; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.UriBuilder; + +import java.net.URI; +import java.util.Map; +import java.util.Optional; + +import static io.airlift.http.client.JsonBodyGenerator.jsonBodyGenerator; +import static io.airlift.http.client.JsonResponseHandler.createJsonResponseHandler; +import static io.airlift.http.client.Request.Builder.preparePost; +import static io.airlift.json.JsonCodec.mapJsonCodec; +import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static java.util.Objects.requireNonNull; + +public class OpaS3SecurityFacadeProvider + implements S3SecurityFacadeProvider +{ + private static final JsonCodec> CODEC = mapJsonCodec(String.class, Object.class); + + private final HttpClient httpClient; + private final URI opaServerUri; + private final OpaS3SecurityMapper opaS3SecurityMapper; + + @Inject + public OpaS3SecurityFacadeProvider(@ForOpa HttpClient httpClient, OpaS3SecurityMapper opaS3SecurityMapper, OpaS3SecurityConfig config) + { + this.httpClient = requireNonNull(httpClient, "httpClient is null"); + this.opaS3SecurityMapper = requireNonNull(opaS3SecurityMapper, "opaS3SecurityMapper is null"); + opaServerUri = UriBuilder.fromUri(config.getOpaServerBaseUri()).path("/v1/data").build(); + } + + @Override + public S3SecurityFacade securityFacadeForRequest(ParsedS3Request request) + throws WebApplicationException + { + return lowercaseAction -> facade(request, lowercaseAction); + } + + private SecurityResponse facade(ParsedS3Request parsedS3Request, Optional lowercaseAction) + { + OpaRequest opaRequest = opaS3SecurityMapper.toRequest(parsedS3Request, lowercaseAction, opaServerUri); + + Map inputDocument = opaS3SecurityMapper.toInputDocument(opaRequest.document()); + + Request.Builder builder = preparePost() + .setUri(opaRequest.opaServerUri()) + .addHeader(CONTENT_TYPE, APPLICATION_JSON_TYPE.getType()) + .setBodyGenerator(jsonBodyGenerator(CODEC, inputDocument)); + opaRequest.additionalHeaders().forEach((name, values) -> values.forEach(value -> builder.addHeader(name, value))); + + Map responseDocument = httpClient.execute(builder.build(), createJsonResponseHandler(CODEC)); + return opaS3SecurityMapper.toSecurityResponse(responseDocument); + } +} diff --git a/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityModule.java b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityModule.java new file mode 100644 index 00000000..fcfe3cd3 --- /dev/null +++ b/trino-aws-proxy/src/main/java/io/trino/aws/proxy/server/security/opa/OpaS3SecurityModule.java @@ -0,0 +1,38 @@ +/* + * 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.security.opa; + +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.s3SecurityFacadeProviderModule; + +public class OpaS3SecurityModule + extends AbstractConfigurationAwareModule +{ + // set as config value for "s3-security.type" + // user must bind an OpaS3SecurityMapper + public static final String OPA_S3_SECURITY_IDENTIFIER = "opa"; + + @Override + protected void setup(Binder binder) + { + install(s3SecurityFacadeProviderModule(OPA_S3_SECURITY_IDENTIFIER, OpaS3SecurityFacadeProvider.class, internalBinder -> { + configBinder(internalBinder).bindConfig(OpaS3SecurityConfig.class); + httpClientBinder(internalBinder).bindHttpClient(OPA_S3_SECURITY_IDENTIFIER, ForOpa.class); + })); + } +} diff --git a/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/TestOpaSecurity.java b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/TestOpaSecurity.java new file mode 100644 index 00000000..076985ab --- /dev/null +++ b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/TestOpaSecurity.java @@ -0,0 +1,137 @@ +/* + * 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; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; +import com.google.inject.Scopes; +import io.airlift.http.client.HttpClient; +import io.airlift.http.client.Request; +import io.airlift.http.client.StatusResponseHandler.StatusResponse; +import io.trino.aws.proxy.server.security.opa.ForOpa; +import io.trino.aws.proxy.server.testing.TestingTrinoAwsProxyServer; +import io.trino.aws.proxy.server.testing.containers.OpaContainer; +import io.trino.aws.proxy.server.testing.harness.BuilderFilter; +import io.trino.aws.proxy.server.testing.harness.TrinoAwsProxyTest; +import io.trino.aws.proxy.spi.rest.ParsedS3Request; +import io.trino.aws.proxy.spi.security.opa.OpaRequest; +import io.trino.aws.proxy.spi.security.opa.OpaS3SecurityMapper; +import jakarta.ws.rs.core.UriBuilder; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import static io.airlift.http.client.Request.Builder.preparePut; +import static io.airlift.http.client.StaticBodyGenerator.createStaticBodyGenerator; +import static io.airlift.http.client.StatusResponseHandler.createStatusResponseHandler; +import static io.trino.aws.proxy.server.security.opa.OpaS3SecurityModule.OPA_S3_SECURITY_IDENTIFIER; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@TrinoAwsProxyTest(filters = TestOpaSecurity.Filter.class) +public class TestOpaSecurity +{ + private final HttpClient httpClient; + private final int containerPort; + private final S3Client s3Client; + + public static class Filter + implements BuilderFilter + { + @Override + public TestingTrinoAwsProxyServer.Builder filter(TestingTrinoAwsProxyServer.Builder builder) + { + return builder.withProperty("s3-security.type", OPA_S3_SECURITY_IDENTIFIER) + .withProperty("opa-s3-security.server-base-uri", "http://localhost") + .addModule(binder -> binder.bind(OpaS3SecurityMapper.class).to(OpaMapper.class).in(Scopes.SINGLETON)) + .withS3Container() + .withOpaContainer(); + } + } + + public static class OpaMapper + implements OpaS3SecurityMapper + { + private final int containerPort; + + @Inject + public OpaMapper(OpaContainer container) + { + containerPort = container.getPort(); + } + + @Override + public OpaRequest toRequest(ParsedS3Request request, Optional lowercaseAction, URI baseUri) + { + URI uri = UriBuilder.fromUri(baseUri).port(containerPort).path("test").path("allow").build(); + return new OpaRequest(uri, ImmutableMap.of("table", request.keyInBucket())); + } + } + + private static final String POLICY = """ + package test + + default allow = false + + allow { + input.table = "good" + } + """; + + @Inject + public TestOpaSecurity(@ForOpa HttpClient httpClient, OpaContainer container, S3Client s3Client) + { + this.httpClient = requireNonNull(httpClient, "httpClient is null"); + containerPort = container.getPort(); + this.s3Client = requireNonNull(s3Client, "s3Client is null"); + } + + @BeforeAll + public void setup() + { + Request request = preparePut() + .setUri(URI.create("http://localhost:%s/v1/policies/test".formatted(containerPort))) + .setBodyGenerator(createStaticBodyGenerator(POLICY, StandardCharsets.UTF_8)) + .build(); + + StatusResponse response = httpClient.execute(request, createStatusResponseHandler()); + assertThat(response.getStatusCode()).isEqualTo(200); + } + + @Test + public void testBadTable() + { + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket("hey").key("bad").build())) + .asInstanceOf(InstanceOfAssertFactories.type(S3Exception.class)) + .extracting(S3Exception::statusCode) + .isEqualTo(401); + } + + @Test + public void testGoodTable() + { + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket("hey").key("good").build())) + .asInstanceOf(InstanceOfAssertFactories.type(S3Exception.class)) + .extracting(S3Exception::statusCode) + .isEqualTo(404); // table: good is allowed but does not exist + } +} diff --git a/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/TestingTrinoAwsProxyServer.java b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/TestingTrinoAwsProxyServer.java index e29dbff1..d95a32f5 100644 --- a/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/TestingTrinoAwsProxyServer.java +++ b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/TestingTrinoAwsProxyServer.java @@ -35,6 +35,7 @@ import io.trino.aws.proxy.server.testing.TestingTrinoAwsProxyServerModule.ForTestingRemoteCredentials; import io.trino.aws.proxy.server.testing.TestingUtil.ForTesting; import io.trino.aws.proxy.server.testing.containers.MetastoreContainer; +import io.trino.aws.proxy.server.testing.containers.OpaContainer; import io.trino.aws.proxy.server.testing.containers.PostgresContainer; import io.trino.aws.proxy.server.testing.containers.PySparkContainer; import io.trino.aws.proxy.server.testing.containers.PySparkContainer.PySparkV3Container; @@ -93,6 +94,7 @@ public static class Builder private boolean metastoreContainerAdded; private boolean v3PySparkContainerAdded; private boolean v4PySparkContainerAdded; + private boolean opaContainerAdded; private boolean addTestingCredentialsRoleProviders = true; public Builder addModule(Module module) @@ -195,6 +197,17 @@ public Builder withoutTestingCredentialsRoleProviders() return this; } + public Builder withOpaContainer() + { + if (opaContainerAdded) { + return this; + } + opaContainerAdded = true; + + modules.add(binder -> binder.bind(OpaContainer.class).asEagerSingleton()); + return this; + } + public TestingTrinoAwsProxyServer buildAndStart() { if (addTestingCredentialsRoleProviders) { diff --git a/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/containers/OpaContainer.java b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/containers/OpaContainer.java new file mode 100644 index 00000000..f17d63b4 --- /dev/null +++ b/trino-aws-proxy/src/test/java/io/trino/aws/proxy/server/testing/containers/OpaContainer.java @@ -0,0 +1,51 @@ +/* + * 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.testing.containers; + +import io.airlift.log.Logger; +import jakarta.annotation.PreDestroy; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +public class OpaContainer +{ + private static final Logger log = Logger.get(OpaContainer.class); + + private static final String IMAGE_NAME = "openpolicyagent/opa"; + private static final String IMAGE_TAG = "0.68.0-dev-static-debug"; + + private final GenericContainer container; + + @SuppressWarnings("resource") + public OpaContainer() + { + DockerImageName dockerImageName = DockerImageName.parse(IMAGE_NAME).withTag(IMAGE_TAG); + container = new GenericContainer<>(dockerImageName) + .withCreateContainerCmdModifier(modifier -> modifier.withTty(true)) + .withCommand("run", "--server") + .withExposedPorts(8181); + container.start(); + } + + public int getPort() + { + return container.getFirstMappedPort(); + } + + @PreDestroy + public void shutdown() + { + container.close(); + } +}