From a9ed9de8d20a22059f9894a27147788980f31d04 Mon Sep 17 00:00:00 2001 From: Remi Baptiste Date: Tue, 24 Dec 2024 12:03:10 +0100 Subject: [PATCH] feat: add secret support for http proxy endpoint --- .../gravitee-apim-definition-model/pom.xml | 4 + .../model/v4/ssl/jks/JKSKeyStore.java | 7 + .../model/v4/ssl/jks/JKSTrustStore.java | 5 + .../model/v4/ssl/pem/PEMKeyStore.java | 5 + .../model/v4/ssl/pkcs12/PKCS12KeyStore.java | 5 + .../model/v4/ssl/pkcs12/PKCS12TrustStore.java | 5 + .../secrets/SecretsV4IntegrationTest.java | 180 ++++++++++++++++++ ...ecured-jks-password-secret-static-ref.json | 73 +++++++ .../pom.xml | 38 ++++ .../HttpProxyEndpointConnectorFactory.java | 28 ++- ...tpProxyEndpointConnectorConfiguration.java | 2 + ...yEndpointConnectorSharedConfiguration.java | 2 + ...HttpProxyEndpointConnectorFactoryTest.java | 16 +- .../proxy/HttpProxyEndpointConnectorTest.java | 12 +- .../proxy/client/HttpClientFactoryTest.java | 4 +- .../proxy/connector/HttpConnectorTest.java | 12 +- pom.xml | 2 +- 17 files changed, 364 insertions(+), 36 deletions(-) create mode 100644 gravitee-apim-integration-tests/src/test/java/io/gravitee/apim/integration/tests/http/secrets/SecretsV4IntegrationTest.java create mode 100644 gravitee-apim-integration-tests/src/test/resources/apis/v4/http/secrets/api-secured-jks-password-secret-static-ref.json diff --git a/gravitee-apim-definition/gravitee-apim-definition-model/pom.xml b/gravitee-apim-definition/gravitee-apim-definition-model/pom.xml index d09b4d0ad38..73f1a1003f0 100644 --- a/gravitee-apim-definition/gravitee-apim-definition-model/pom.xml +++ b/gravitee-apim-definition/gravitee-apim-definition-model/pom.xml @@ -47,6 +47,10 @@ io.gravitee.node gravitee-node-vertx + + io.gravitee.secret + gravitee-secret-api + diff --git a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSKeyStore.java b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSKeyStore.java index d456d440e1c..470bc36ef32 100644 --- a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSKeyStore.java +++ b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSKeyStore.java @@ -17,6 +17,8 @@ import io.gravitee.definition.model.v4.ssl.KeyStore; import io.gravitee.definition.model.v4.ssl.KeyStoreType; +import io.gravitee.secrets.api.annotation.Secret; +import io.gravitee.secrets.api.el.FieldKind; import java.io.Serial; import lombok.Builder; import lombok.Getter; @@ -36,8 +38,13 @@ public class JKSKeyStore extends KeyStore { private String path; private String content; + + @Secret(FieldKind.PASSWORD) private String password; + private String alias; + + @Secret(FieldKind.PASSWORD) private String keyPassword; public JKSKeyStore() { diff --git a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSTrustStore.java b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSTrustStore.java index baec82a1e6a..f9a1d02efa2 100644 --- a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSTrustStore.java +++ b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/jks/JKSTrustStore.java @@ -17,6 +17,8 @@ import io.gravitee.definition.model.v4.ssl.TrustStore; import io.gravitee.definition.model.v4.ssl.TrustStoreType; +import io.gravitee.secrets.api.annotation.Secret; +import io.gravitee.secrets.api.el.FieldKind; import java.io.Serial; import lombok.Builder; import lombok.Getter; @@ -36,7 +38,10 @@ public class JKSTrustStore extends TrustStore { private String path; private String content; + + @Secret(FieldKind.PASSWORD) private String password; + private String alias; public JKSTrustStore() { diff --git a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pem/PEMKeyStore.java b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pem/PEMKeyStore.java index a34b8da40a9..a240e9ba6c6 100644 --- a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pem/PEMKeyStore.java +++ b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pem/PEMKeyStore.java @@ -17,6 +17,8 @@ import io.gravitee.definition.model.v4.ssl.KeyStore; import io.gravitee.definition.model.v4.ssl.KeyStoreType; +import io.gravitee.secrets.api.annotation.Secret; +import io.gravitee.secrets.api.el.FieldKind; import java.io.Serial; import lombok.Builder; import lombok.Getter; @@ -35,7 +37,10 @@ public class PEMKeyStore extends KeyStore { private static final long serialVersionUID = 1051430527272519608L; private String keyPath; + + @Secret(FieldKind.PRIVATE_KEY) private String keyContent; + private String certPath; private String certContent; diff --git a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12KeyStore.java b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12KeyStore.java index 9e4c26de7e4..8ada39027ce 100644 --- a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12KeyStore.java +++ b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12KeyStore.java @@ -17,6 +17,8 @@ import io.gravitee.definition.model.v4.ssl.KeyStore; import io.gravitee.definition.model.v4.ssl.KeyStoreType; +import io.gravitee.secrets.api.annotation.Secret; +import io.gravitee.secrets.api.el.FieldKind; import java.io.Serial; import lombok.Builder; import lombok.Getter; @@ -36,7 +38,10 @@ public class PKCS12KeyStore extends KeyStore { private String path; private String content; + + @Secret(FieldKind.PASSWORD) private String password; + private String alias; /** diff --git a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12TrustStore.java b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12TrustStore.java index 5b9dd22a6d0..8d3367db4de 100644 --- a/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12TrustStore.java +++ b/gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/ssl/pkcs12/PKCS12TrustStore.java @@ -17,6 +17,8 @@ import io.gravitee.definition.model.v4.ssl.TrustStore; import io.gravitee.definition.model.v4.ssl.TrustStoreType; +import io.gravitee.secrets.api.annotation.Secret; +import io.gravitee.secrets.api.el.FieldKind; import java.io.Serial; import lombok.Builder; import lombok.Getter; @@ -36,7 +38,10 @@ public class PKCS12TrustStore extends TrustStore { private String path; private String content; + + @Secret(FieldKind.PASSWORD) private String password; + private String alias; public PKCS12TrustStore() { diff --git a/gravitee-apim-integration-tests/src/test/java/io/gravitee/apim/integration/tests/http/secrets/SecretsV4IntegrationTest.java b/gravitee-apim-integration-tests/src/test/java/io/gravitee/apim/integration/tests/http/secrets/SecretsV4IntegrationTest.java new file mode 100644 index 00000000000..84de79c64d4 --- /dev/null +++ b/gravitee-apim-integration-tests/src/test/java/io/gravitee/apim/integration/tests/http/secrets/SecretsV4IntegrationTest.java @@ -0,0 +1,180 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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.gravitee.apim.integration.tests.http.secrets; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.graviteesource.entrypoint.http.get.HttpGetEntrypointConnectorFactory; +import com.graviteesource.secretprovider.hcvault.HCVaultSecretProvider; +import com.graviteesource.secretprovider.hcvault.HCVaultSecretProviderFactory; +import com.graviteesource.secretprovider.hcvault.config.manager.VaultConfig; +import com.graviteesource.service.secrets.SecretsService; +import io.gravitee.apim.gateway.tests.sdk.AbstractGatewayTest; +import io.gravitee.apim.gateway.tests.sdk.annotations.DeployApi; +import io.gravitee.apim.gateway.tests.sdk.annotations.GatewayTest; +import io.gravitee.apim.gateway.tests.sdk.configuration.GatewayConfigurationBuilder; +import io.gravitee.apim.gateway.tests.sdk.connector.EndpointBuilder; +import io.gravitee.apim.gateway.tests.sdk.connector.EntrypointBuilder; +import io.gravitee.apim.gateway.tests.sdk.secrets.SecretProviderBuilder; +import io.gravitee.apim.gateway.tests.sdk.utils.ResourceUtils; +import io.gravitee.common.service.AbstractService; +import io.gravitee.definition.model.v4.Api; +import io.gravitee.definition.model.v4.endpointgroup.Endpoint; +import io.gravitee.gateway.reactor.ReactableApi; +import io.gravitee.node.secrets.plugins.SecretProviderPlugin; +import io.gravitee.plugin.endpoint.EndpointConnectorPlugin; +import io.gravitee.plugin.endpoint.http.proxy.HttpProxyEndpointConnectorFactory; +import io.gravitee.plugin.entrypoint.EntrypointConnectorPlugin; +import io.gravitee.plugin.entrypoint.http.proxy.HttpProxyEntrypointConnectorFactory; +import io.gravitee.secrets.api.plugin.SecretManagerConfiguration; +import io.gravitee.secrets.api.plugin.SecretProviderFactory; +import io.vertx.core.http.HttpMethod; +import io.vertx.rxjava3.core.Vertx; +import io.vertx.rxjava3.core.http.HttpClient; +import io.vertx.rxjava3.core.http.HttpClientRequest; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.vault.VaultContainer; + +/** + * @author Remi Baptiste (remi.baptiste at graviteesource.com) + * @author GraviteeSource Team + */ +@Testcontainers +@GatewayTest +public class SecretsV4IntegrationTest extends AbstractGatewayTest { + + private static final String VAULT_TOKEN = UUID.randomUUID().toString(); + + @org.testcontainers.junit.jupiter.Container + protected static final VaultContainer vaultContainer = new VaultContainer<>("hashicorp/vault:1.13.3") + .withVaultToken(VAULT_TOKEN) + .withInitCommand( + "kv put secret/test private-key='-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCMOMvaM1XZzR1H\nPp127syCvuxnljnxsMLCvfM+8QsdyeuYVURei3z502zxWVTwNbxUbTxOkgIuNYRT\nyypEMpYajSEv0sMt4d7KBchE0eeQlcMDQ2J6kzfVjHyxLMMu8JscBQ5KGvF1vW3Q\np16w6C6JebF21Y0LumL9cMEToN6OrDKx9BrUkbTXHfSf+5rvkrnGfIPMulNJS2On\nl4zmT0bHs8a/zSIGmcwNIG243LmXTFu67TKbknJahICrRz0uZ8h1HMFbe7yY12s0\nd0xGJDJcPIBcdWHhB8z6iduTxCYZ2vY3EdFcC2RMO99zJPlWeDGY4lTM8TRFEAvE\nj4a21CltAgMBAAECggEADu4VNnx0zaX7UhSmq30tpVYy0ay7KrLJafbTqYX8ywUu\n4p9hkjeD7Q3H8cKzOoheLxcabrs5JDZqiol9TJmeReF1ASSNx5rfH9+RvVIkN87a\nXsST/b0jGsfElxDPD3Zq7YbUSKupvgGXaboIaQmvus+MR7zhMbh8xcN1q2NbjxE6\nCLXV+uu8kVPLsUbGFGK53vK7+MHnEKJIbjHR+LRNMxZRzoX7h57UO6uoXwWes2Kr\nopMzIJdv2ZRvXS62NNCTK9BoPb4OreXSoEJZkXS26ispOLLnM36D4UpNeDl6hITN\noCSHKiRGFqY5QOir/MP8uqNmGEtprMDmUQt/K5Z8OQKBgQDVvEi7SmHFJLNSsY2l\nvvxlwIa9rr0d/TFtTXcGMUdLYg7rQtaZ3e0geJv4wU58KahmpgYqpX4s0/Gc4L37\nyG1kCtPUjHvxxauLRr0OcXz35pTqlGhVGyls9onCxM/nDpoiYDvueuse69rVVGDE\nTKg2pxqS0Rxh7FARKY1RpJprjwKBgQCn8xsrRSc5QGF/L7b87MK1sf+lQ7Updubt\nv/XRFq5lhHp888MrIsqcqsDqRChoY2Qoi0vuNv52pgpha6lXkxL5Djd1ypX457KR\ntaJmx7qoolYW7INU4mvcryyIiW9ELiFrGb8XrwhXwmUBWOGGEDJkgYOj6W7Co38T\n2RbkBvBNQwKBgGTniv7A0v+bn/0+Tb0eOVJgXjxWrnnl+tu7YqHNyfbQyHJRD7d8\nimJ2DkyWFlOP5yzu3KJtlu/a74o8n/SqXtqIMhF6cVlnFOGf98lF0tXGSi+k+MyV\nEi2bBtaoy+4tep8YB7NC3JWwi5ODTlveRNvocCc4CcpBIlu33jvZFf4JAoGATJ0R\nn8OECRHdZ++UQfyfNdNlEza3xZp/7aTLtf3qwFSWq7lnJp5QXvdl2XgOFtCAOB6T\nHK/plKZZxece8Nweo45grlMj5s+LHf0FgG1MMPEc5IgvwOEo4xrl7cMEBs4kYH72\nNQ+bdq0u9lZdSpLI6iBKtNMfu5pptdwqHQstQ5ECgYBQ+XWJHuKLadcMAoAdG+gW\n5KxlSlin7PqgmzobXggp2aUGVLHs4pvIGA+7Sf/kAPfJ6bimv4dIODqEe8TZnmWp\n4ZG0jYjFXVnbwbh+hU5EF03ntO9WYqL6rYU3zujz/ZuKBEByIFowTX9uaVKEgML6\n5oHAF4Sb7zxa2jSEehGQ5Q==\n-----END PRIVATE KEY-----'" + ); + + private static String token; + + private final int backendPort = getAvailablePort(); + + @Override + protected void configureWireMock(WireMockConfiguration configuration) { + configuration + .httpsPort(backendPort) + .needClientAuth(true) + .keystorePath(ResourceUtils.toPath("certs/keystore01.jks")) + .keystorePassword("password") + .trustStorePath(ResourceUtils.toPath("certs/truststore01.jks")) + .trustStorePassword("password"); + } + + @Override + public void configureEntrypoints(Map> entrypoints) { + entrypoints.putIfAbsent("http-proxy", EntrypointBuilder.build("http-proxy", HttpProxyEntrypointConnectorFactory.class)); + } + + @Override + public void configureEndpoints(Map> endpoints) { + endpoints.putIfAbsent("http-proxy", EndpointBuilder.build("http-proxy", HttpProxyEndpointConnectorFactory.class)); + } + + @Override + public void configureGateway(GatewayConfigurationBuilder configurationBuilder) { + super.configureGateway(configurationBuilder); + + // create a renewable token so the plugin does not start panicking + Container.ExecResult execResult = null; + try { + execResult = vaultContainer.execInContainer("vault", "token", "create", "-period=10m", "-field", "token"); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + token = execResult.getStdout(); + + configurationBuilder.setYamlProperty("api.secrets.providers[0].plugin", "vault"); + configurationBuilder.setYamlProperty("api.secrets.providers[0].configuration.enabled", true); + configurationBuilder.setYamlProperty("api.secrets.providers[0].configuration.host", vaultContainer.getHost()); + configurationBuilder.setYamlProperty("api.secrets.providers[0].configuration.port", vaultContainer.getMappedPort(8200)); + configurationBuilder.setYamlProperty("api.secrets.providers[0].configuration.ssl.enabled", "false"); + configurationBuilder.setYamlProperty("api.secrets.providers[0].configuration.auth.method", "token"); + configurationBuilder.setYamlProperty("api.secrets.providers[0].configuration.auth.config.token", token); + } + + @Override + public void configureSecretProviders( + Set, ? extends SecretManagerConfiguration>> secretProviderPlugins + ) { + secretProviderPlugins.add( + SecretProviderBuilder.build(HCVaultSecretProvider.PLUGIN_ID, HCVaultSecretProviderFactory.class, VaultConfig.class) + ); + } + + @Override + public void configureServices(Set>> services) { + super.configureServices(services); + services.add(SecretsService.class); + } + + @AfterAll + static void cleanup() { + vaultContainer.close(); + } + + @Override + public void configureApi(ReactableApi reactableApi, Class definitionClass) { + if (!isV4Api(definitionClass)) { + throw new AssertionError("TCP api should only be v4 api"); + } + final Api apiDefinition = (Api) reactableApi.getDefinition(); + + updateEndpointsPort(apiDefinition, backendPort); + } + + @Test + @DeployApi({ "/apis/v4/http/secrets/api-secured-jks-password-secret-static-ref.json" }) + void should_retrieve_password_from_secret_provider(HttpClient httpClient, Vertx vertx) { + wiremock.stubFor(get("/endpoint").willReturn(ok("response from backend"))); + + httpClient + .rxRequest(HttpMethod.GET, "/test") + .flatMap(HttpClientRequest::rxSend) + .flatMap(response -> { + // just asserting we get a response (hence no SSL errors), no need for an API. + assertThat(response.statusCode()).isEqualTo(200); + return response.body(); + }) + .test() + .awaitDone(10, TimeUnit.SECONDS) + .assertComplete(); + //wiremock.verify(1, getRequestedFor(urlPathEqualTo("/endpoint")).withHeader("Authorization", equalTo("ApiKey ".concat(apiKey)))); + } +} diff --git a/gravitee-apim-integration-tests/src/test/resources/apis/v4/http/secrets/api-secured-jks-password-secret-static-ref.json b/gravitee-apim-integration-tests/src/test/resources/apis/v4/http/secrets/api-secured-jks-password-secret-static-ref.json new file mode 100644 index 00000000000..e15ece194d8 --- /dev/null +++ b/gravitee-apim-integration-tests/src/test/resources/apis/v4/http/secrets/api-secured-jks-password-secret-static-ref.json @@ -0,0 +1,73 @@ +{ + "id": "static-secret-api-v4", + "name": "my-secret-api-v4", + "gravitee": "4.0.0", + "type": "proxy", + "listeners": [ + { + "type": "http", + "paths": [ + { + "path": "/test" + } + ], + "entrypoints": [ + { + "type": "http-proxy" + } + ] + } + ], + "endpointGroups": [ + { + "name": "default-group", + "type": "http-proxy", + "endpoints": [ + { + "name": "default", + "type": "http-proxy", + "weight": 1, + "inheritConfiguration": false, + "configuration": { + "target": "https://localhost:8080/endpoint" + }, + "sharedConfigurationOverride": { + "http": { + "connectTimeout": 3000, + "readTimeout": 60000 + }, + "ssl": { + "trustAll": false, + "keyStore": { + "type": "PEM", + "certContent": "-----BEGIN CERTIFICATE-----\nMIICxzCCAa+gAwIBAgIEPvlKwjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwls\nb2NhbGhvc3QwHhcNMTgxMDA5MTU0MTAyWhcNMTkxMDA0MTU0MTAyWjAUMRIwEAYD\nVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCM\nOMvaM1XZzR1HPp127syCvuxnljnxsMLCvfM+8QsdyeuYVURei3z502zxWVTwNbxU\nbTxOkgIuNYRTyypEMpYajSEv0sMt4d7KBchE0eeQlcMDQ2J6kzfVjHyxLMMu8Jsc\nBQ5KGvF1vW3Qp16w6C6JebF21Y0LumL9cMEToN6OrDKx9BrUkbTXHfSf+5rvkrnG\nfIPMulNJS2Onl4zmT0bHs8a/zSIGmcwNIG243LmXTFu67TKbknJahICrRz0uZ8h1\nHMFbe7yY12s0d0xGJDJcPIBcdWHhB8z6iduTxCYZ2vY3EdFcC2RMO99zJPlWeDGY\n4lTM8TRFEAvEj4a21CltAgMBAAGjITAfMB0GA1UdDgQWBBQmmZF9umT5DGh4RgYX\nBQhzb6EkQTANBgkqhkiG9w0BAQsFAAOCAQEAW1QaHW4iYjUtjQik+nWD3Xktbm50\ns9PeAYSCp9an757dvzfO/vwJZE+1+grmsS0l/jxh8L0qsdjM5Qt4VmjK5CbikE2v\ne4Vt4o40tQOz8A7fNVVp5S33njgNbp1UMhnrFsHVZ6Aa8HHxisjliluVK1/YPl80\nKRs57GL4SyvELzmWhh7egndxdGYR9nbAbg1RQ+kJClqSS0BL5oQ4Xn4AGmU5839/\nZ1+N5qgNq2/BYOi6FsltL91US0FOLNxDBYqjwShGOJ1V6Lvh27YmSHViscph6GeZ\nkZ2xybRANymp0DSVER5J+D2RuJNtzp/zl//BJ3b19tpVpDTQ1ndzcSGPLg==\n-----END CERTIFICATE-----", + "keyContent": "{#secrets.get('/vault/secret/test:private-key')}" + }, + "trustStore": { + "type": "PEM", + "content": "-----BEGIN CERTIFICATE-----\nMIICxzCCAa+gAwIBAgIEPvlKwjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwls\nb2NhbGhvc3QwHhcNMTgxMDA5MTU0MTAyWhcNMTkxMDA0MTU0MTAyWjAUMRIwEAYD\nVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCM\nOMvaM1XZzR1HPp127syCvuxnljnxsMLCvfM+8QsdyeuYVURei3z502zxWVTwNbxU\nbTxOkgIuNYRTyypEMpYajSEv0sMt4d7KBchE0eeQlcMDQ2J6kzfVjHyxLMMu8Jsc\nBQ5KGvF1vW3Qp16w6C6JebF21Y0LumL9cMEToN6OrDKx9BrUkbTXHfSf+5rvkrnG\nfIPMulNJS2Onl4zmT0bHs8a/zSIGmcwNIG243LmXTFu67TKbknJahICrRz0uZ8h1\nHMFbe7yY12s0d0xGJDJcPIBcdWHhB8z6iduTxCYZ2vY3EdFcC2RMO99zJPlWeDGY\n4lTM8TRFEAvEj4a21CltAgMBAAGjITAfMB0GA1UdDgQWBBQmmZF9umT5DGh4RgYX\nBQhzb6EkQTANBgkqhkiG9w0BAQsFAAOCAQEAW1QaHW4iYjUtjQik+nWD3Xktbm50\ns9PeAYSCp9an757dvzfO/vwJZE+1+grmsS0l/jxh8L0qsdjM5Qt4VmjK5CbikE2v\ne4Vt4o40tQOz8A7fNVVp5S33njgNbp1UMhnrFsHVZ6Aa8HHxisjliluVK1/YPl80\nKRs57GL4SyvELzmWhh7egndxdGYR9nbAbg1RQ+kJClqSS0BL5oQ4Xn4AGmU5839/\nZ1+N5qgNq2/BYOi6FsltL91US0FOLNxDBYqjwShGOJ1V6Lvh27YmSHViscph6GeZ\nkZ2xybRANymp0DSVER5J+D2RuJNtzp/zl//BJ3b19tpVpDTQ1ndzcSGPLg==\n-----END CERTIFICATE-----" + } + } + } + } + ] + } + ], + "flows": [ + { + "name": "flow-1", + "enabled": true, + "selectors": [ + { + "type": "http", + "path": "/", + "pathOperator": "START_WITH", + "methods": ["GET"] + } + ] + } + ], + "analytics": { + "enabled": false + } +} diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/pom.xml b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/pom.xml index bcf9e99c289..7d844ebb9a6 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/pom.xml +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/pom.xml @@ -60,6 +60,25 @@ ${project.version} provided + + io.gravitee.plugin + gravitee-plugin-annotation-processors + ${gravitee-plugin.version} + provided + + + + com.fasterxml.jackson.core + jackson-databind + provided + + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + provided + @@ -92,6 +111,25 @@ maven-assembly-plugin + + org.codehaus.mojo + build-helper-maven-plugin + 1.7 + + + add-source + generate-sources + + add-source + + + + generated-sources + + + + + diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactory.java b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactory.java index 047dfa2d290..b398bb36395 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactory.java +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactory.java @@ -16,14 +16,15 @@ package io.gravitee.plugin.endpoint.http.proxy; import io.gravitee.common.http.HttpHeader; -import io.gravitee.definition.model.v4.http.HttpProxyOptions; import io.gravitee.el.TemplateEngine; import io.gravitee.gateway.reactive.api.ConnectorMode; import io.gravitee.gateway.reactive.api.connector.endpoint.sync.HttpEndpointSyncConnectorFactory; import io.gravitee.gateway.reactive.api.context.DeploymentContext; import io.gravitee.gateway.reactive.api.helper.PluginConfigurationHelper; import io.gravitee.plugin.endpoint.http.proxy.configuration.HttpProxyEndpointConnectorConfiguration; +import io.gravitee.plugin.endpoint.http.proxy.configuration.HttpProxyEndpointConnectorConfigurationEvaluator; import io.gravitee.plugin.endpoint.http.proxy.configuration.HttpProxyEndpointConnectorSharedConfiguration; +import io.gravitee.plugin.endpoint.http.proxy.configuration.HttpProxyEndpointConnectorSharedConfigurationEvaluator; import java.util.List; import java.util.Set; import lombok.AllArgsConstructor; @@ -51,15 +52,17 @@ public HttpProxyEndpointConnector createConnector( final String sharedConfiguration ) { try { - return new HttpProxyEndpointConnector( - eval( - deploymentContext, - connectorFactoryHelper.readConfiguration(HttpProxyEndpointConnectorConfiguration.class, configuration) - ), - eval( - deploymentContext, + HttpProxyEndpointConnectorConfigurationEvaluator configurationEvaluator = new HttpProxyEndpointConnectorConfigurationEvaluator( + connectorFactoryHelper.readConfiguration(HttpProxyEndpointConnectorConfiguration.class, configuration) + ); + HttpProxyEndpointConnectorSharedConfigurationEvaluator sharedConfigurationEvaluator = + new HttpProxyEndpointConnectorSharedConfigurationEvaluator( connectorFactoryHelper.readConfiguration(HttpProxyEndpointConnectorSharedConfiguration.class, sharedConfiguration) - ) + ); + return new HttpProxyEndpointConnector( + configurationEvaluator.evalNow(deploymentContext), + //Still need to use this method here since the evaluator do not support EL on Headers + eval(deploymentContext, sharedConfigurationEvaluator.evalNow(deploymentContext)) ); } catch (Exception e) { log.error("Can't create connector because no valid configuration", e); @@ -80,15 +83,8 @@ private HttpProxyEndpointConnectorSharedConfiguration eval( final HttpProxyEndpointConnectorSharedConfiguration groupConfiguration ) { final TemplateEngine templateEngine = deploymentContext.getTemplateEngine(); - final HttpProxyOptions proxyOptions = groupConfiguration.getProxyOptions(); final List headers = groupConfiguration.getHeaders(); - if (proxyOptions != null) { - proxyOptions.setHost(eval(templateEngine, proxyOptions.getHost())); - proxyOptions.setUsername(eval(templateEngine, proxyOptions.getUsername())); - proxyOptions.setPassword(eval(templateEngine, proxyOptions.getPassword())); - } - if (headers != null && !headers.isEmpty()) { headers.forEach(httpHeader -> httpHeader.setValue(eval(templateEngine, httpHeader.getValue()))); } diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorConfiguration.java b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorConfiguration.java index bb5078252b8..08803bf25f4 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorConfiguration.java +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorConfiguration.java @@ -16,6 +16,7 @@ package io.gravitee.plugin.endpoint.http.proxy.configuration; import io.gravitee.gateway.reactive.api.connector.endpoint.EndpointConnectorConfiguration; +import io.gravitee.plugin.annotation.ConfigurationEvaluator; import lombok.Getter; import lombok.Setter; @@ -25,6 +26,7 @@ */ @Getter @Setter +@ConfigurationEvaluator(attributePrefix = "gravitee.attributes.endpoint.httpProxy") public class HttpProxyEndpointConnectorConfiguration implements EndpointConnectorConfiguration { private String target; diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorSharedConfiguration.java b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorSharedConfiguration.java index 202a5a8bc9c..01a0a096750 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorSharedConfiguration.java +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/main/java/io/gravitee/plugin/endpoint/http/proxy/configuration/HttpProxyEndpointConnectorSharedConfiguration.java @@ -21,6 +21,7 @@ import io.gravitee.definition.model.v4.http.HttpProxyOptions; import io.gravitee.definition.model.v4.ssl.SslOptions; import io.gravitee.gateway.reactive.api.connector.endpoint.EndpointConnectorSharedConfiguration; +import io.gravitee.plugin.annotation.ConfigurationEvaluator; import java.util.List; import lombok.Getter; import lombok.Setter; @@ -31,6 +32,7 @@ */ @Getter @Setter +@ConfigurationEvaluator(attributePrefix = "gravitee.attributes.endpoint.httpProxy") public class HttpProxyEndpointConnectorSharedConfiguration implements EndpointConnectorSharedConfiguration { @JsonProperty("proxy") diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactoryTest.java b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactoryTest.java index 6ed10e36829..417defda620 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactoryTest.java +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorFactoryTest.java @@ -21,11 +21,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.gravitee.definition.model.v4.http.HttpClientOptions; +import io.gravitee.el.TemplateContext; import io.gravitee.el.TemplateEngine; import io.gravitee.gateway.reactive.api.ApiType; import io.gravitee.gateway.reactive.api.ConnectorMode; import io.gravitee.gateway.reactive.api.context.DeploymentContext; import io.gravitee.gateway.reactive.api.helper.PluginConfigurationHelper; +import io.reactivex.rxjava3.core.Maybe; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -46,13 +48,17 @@ class HttpProxyEndpointConnectorFactoryTest { @Mock private TemplateEngine templateEngine; + @Mock + private TemplateContext templateContext; + @Mock private DeploymentContext deploymentContext; @BeforeEach void beforeEach() { lenient().when(deploymentContext.getTemplateEngine()).thenReturn(templateEngine); - lenient().when(templateEngine.convert(anyString())).thenAnswer(i -> i.getArgument(0)); + lenient().when(templateEngine.getTemplateContext()).thenReturn(templateContext); + lenient().when(templateEngine.eval(anyString(), any())).thenAnswer(i -> Maybe.just(i.getArgument(0))); cut = new HttpProxyEndpointConnectorFactory(new PluginConfigurationHelper(null, new ObjectMapper())); } @@ -85,10 +91,10 @@ void shouldCreateConnectorWithRightConfiguration() { HttpProxyEndpointConnector connector = cut.createConnector(deploymentContext, CONFIG, SHARED_CONFIG); assertThat(connector).isNotNull(); assertThat(connector.configuration).isNotNull(); - verify(templateEngine).convert("https://localhost:8082/echo?foo=bar"); - verify(templateEngine).convert("localhost"); - verify(templateEngine).convert("user"); - verify(templateEngine).convert("pwd"); + verify(templateEngine).eval("https://localhost:8082/echo?foo=bar", String.class); + verify(templateEngine).eval("localhost", String.class); + verify(templateEngine).eval("user", String.class); + verify(templateEngine).eval("pwd", String.class); verify(templateEngine).convert("Value1"); verify(templateEngine).convert("Value2"); } diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorTest.java b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorTest.java index 00d8003b524..25e84d227c5 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorTest.java +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/HttpProxyEndpointConnectorTest.java @@ -29,9 +29,9 @@ import io.gravitee.gateway.reactive.api.ApiType; import io.gravitee.gateway.reactive.api.ConnectorMode; import io.gravitee.gateway.reactive.api.context.DeploymentContext; -import io.gravitee.gateway.reactive.api.context.ExecutionContext; -import io.gravitee.gateway.reactive.api.context.Request; -import io.gravitee.gateway.reactive.api.context.Response; +import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; +import io.gravitee.gateway.reactive.api.context.http.HttpRequest; +import io.gravitee.gateway.reactive.api.context.http.HttpResponse; import io.gravitee.gateway.reactive.api.tracing.Tracer; import io.gravitee.node.opentelemetry.tracer.noop.NoOpTracer; import io.gravitee.plugin.endpoint.http.proxy.client.GrpcHttpClientFactory; @@ -69,13 +69,13 @@ class HttpProxyEndpointConnectorTest { private TemplateEngine templateEngine; @Mock - private ExecutionContext ctx; + private HttpExecutionContext ctx; @Mock - private Request request; + private HttpRequest request; @Mock - private Response response; + private HttpResponse response; @Mock private Metrics metrics; diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/client/HttpClientFactoryTest.java b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/client/HttpClientFactoryTest.java index 44fe66d9580..8f68ee1c0a5 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/client/HttpClientFactoryTest.java +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/client/HttpClientFactoryTest.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.gravitee.gateway.reactive.api.context.ExecutionContext; +import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; import io.gravitee.node.api.configuration.Configuration; import io.gravitee.plugin.endpoint.http.proxy.configuration.HttpProxyEndpointConnectorConfiguration; import io.gravitee.plugin.endpoint.http.proxy.configuration.HttpProxyEndpointConnectorSharedConfiguration; @@ -47,7 +47,7 @@ class HttpClientFactoryTest { public static final int TIMEOUT_SECONDS = 60; @Mock - protected ExecutionContext ctx; + protected HttpExecutionContext ctx; protected HttpClientFactory cut; protected HttpProxyEndpointConnectorConfiguration configuration; diff --git a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/connector/HttpConnectorTest.java b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/connector/HttpConnectorTest.java index 2a912650ab2..2d64ef676a6 100644 --- a/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/connector/HttpConnectorTest.java +++ b/gravitee-apim-plugin/gravitee-apim-plugin-endpoint/gravitee-apim-plugin-endpoint-http-proxy/src/test/java/io/gravitee/plugin/endpoint/http/proxy/connector/HttpConnectorTest.java @@ -46,9 +46,9 @@ import io.gravitee.gateway.api.http.HttpHeaders; import io.gravitee.gateway.http.vertx.VertxHttpHeaders; import io.gravitee.gateway.reactive.api.context.DeploymentContext; -import io.gravitee.gateway.reactive.api.context.ExecutionContext; -import io.gravitee.gateway.reactive.api.context.Request; -import io.gravitee.gateway.reactive.api.context.Response; +import io.gravitee.gateway.reactive.api.context.http.HttpExecutionContext; +import io.gravitee.gateway.reactive.api.context.http.HttpRequest; +import io.gravitee.gateway.reactive.api.context.http.HttpResponse; import io.gravitee.gateway.reactive.api.tracing.Tracer; import io.gravitee.node.api.configuration.Configuration; import io.gravitee.node.opentelemetry.tracer.noop.NoOpTracer; @@ -96,13 +96,13 @@ class HttpConnectorTest { private TemplateEngine templateEngine; @Mock - private ExecutionContext ctx; + private HttpExecutionContext ctx; @Mock - private Request request; + private HttpRequest request; @Mock - private Response response; + private HttpResponse response; @Mock private Metrics metrics; diff --git a/pom.xml b/pom.xml index 2abffc552b6..9b5a22e38c3 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 7.0.1 1.4.3 1.4.0 - 4.3.1 + 4.4.2 1.11.0 1.31.2 1.1.0