diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java b/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java index 503d3be7e1..974be3b8aa 100644 --- a/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java +++ b/core/src/main/java/com/predic8/membrane/core/proxies/AbstractServiceProxy.java @@ -17,16 +17,8 @@ import com.predic8.membrane.annot.*; import com.predic8.membrane.core.config.*; import com.predic8.membrane.core.config.security.*; -import com.predic8.membrane.core.config.xml.*; -import com.predic8.membrane.core.exchange.*; -import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.core.lang.*; -import com.predic8.membrane.core.lang.ExchangeExpression.*; -import com.predic8.membrane.core.router.*; import com.predic8.membrane.core.transport.ssl.*; -import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*; - public abstract class AbstractServiceProxy extends SSLableProxy { @Override @@ -89,167 +81,6 @@ public void setPath(Path path) { } } - /** - * @description

- * The destination where the service proxy will send messages to. - * Use the target element if you want to send the messages to a target. - * Supports dynamic destinations through expressions. - *

- */ - @MCElement(name = "target", component = false) - public static class Target implements XMLSupport { - private String host; - private int port = -1; - private String method; - protected String url; - private boolean adjustHostHeader = true; - private ExchangeExpression.Language language = SPEL; - private ExchangeExpression exchangeExpression; - - private SSLParser sslParser; - - protected XmlConfig xmlConfig; - - public void init(Router router) { - if (url != null) { - exchangeExpression = TemplateExchangeExpression.newInstance(new InterceptorAdapter(router, xmlConfig), language, url); - } - - } - - public String compileUrl(Exchange exc, Interceptor.Flow flow) { - /* - * Will always evaluate on every call. This is fine as SpEL is fast enough and performs its own optimizations. - * 1.000.000 calls ~10ms - */ - if (exchangeExpression != null) { - return exchangeExpression.evaluate(exc, flow, String.class); - } - return url; - } - - public Target() { - } - - public Target(String host) { - setHost(host); - } - - public Target(String host, int port) { - setHost(host); - setPort(port); - } - - public String getHost() { - return host; - } - - /** - * @description Host address of the target. - * @example localhost, 192.168.1.1 - */ - @MCAttribute - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return port; - } - - /** - * @description Port number of the target. - * @default 80 - * @example 8080 - */ - @MCAttribute - public void setPort(int port) { - this.port = port; - } - - public String getUrl() { - return url; - } - - /** - * @description Absolute URL of the target. If this is set, host and port will be ignored. - * Supports inline expressions through ${<expression>} elements. - * @example http://membrane-soa.org - */ - @MCAttribute - public void setUrl(String url) { - this.url = url; - } - - public SSLParser getSslParser() { - return sslParser; - } - - /** - * @description Configures outbound SSL (HTTPS). - */ - @MCChildElement(allowForeign = true) - public void setSslParser(SSLParser sslParser) { - this.sslParser = sslParser; - } - - public boolean isAdjustHostHeader() { - return adjustHostHeader; - } - - @MCAttribute - public void setAdjustHostHeader(boolean adjustHostHeader) { - this.adjustHostHeader = adjustHostHeader; - } - - public String getMethod() { - return method; - } - - /** - * @description The method that should be used to make the call to the backend. - * Overwrites the original method. - * @param method - */ - @MCAttribute - public void setMethod(String method) { - this.method = method; - } - - public ExchangeExpression getExchangeExpression() { - return exchangeExpression; - } - - public ExchangeExpression.Language getLanguage() { - return language; - } - - /** - * @description the language of the inline expressions - * @default SpEL - * @example SpEL, groovy, jsonpath, xpath - */ - @MCAttribute - public void setLanguage(ExchangeExpression.Language language) { - this.language = language; - } - - /** - * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ... - * @param xmlConfig - */ - @Override - @MCChildElement(allowForeign = true,order = 10) - public void setXmlConfig(XmlConfig xmlConfig) { - this.xmlConfig = xmlConfig; - } - - @Override - public XmlConfig getXmlConfig() { - return xmlConfig; - } - } - protected Target target = new Target(); public Target getTarget() { diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/RuleManager.java b/core/src/main/java/com/predic8/membrane/core/proxies/RuleManager.java index 7de357e2f3..9b81b61308 100644 --- a/core/src/main/java/com/predic8/membrane/core/proxies/RuleManager.java +++ b/core/src/main/java/com/predic8/membrane/core/proxies/RuleManager.java @@ -121,25 +121,32 @@ public synchronized void openPorts() throws IOException { } private @NotNull HashMap getSSLContexts() throws UnknownHostException { - HashMap sslContexts = new HashMap<>(); + HashMap sslContextBuilders = new HashMap<>(); for (Proxy proxy : proxies) { - if (!(proxy instanceof SSLableProxy sp)) - continue; - - SSLContext sslContext = sp.getSslInboundContext(); - if (sslContext == null) - continue; - - IpPort ipPort = getIpPort(sp); - SSLContextCollection.Builder builder = sslContexts.get(ipPort); - if (builder == null) { - builder = new SSLContextCollection.Builder(); - sslContexts.put(ipPort, builder); + switch (proxy) { + case SSLProxy sslp: + getOrCreateBuilder(sslp, sslContextBuilders).useCollection(); + break; + case SSLableProxy sslap: + SSLContext sslContext = sslap.getSslInboundContext(); + if (sslContext == null) + continue; + getOrCreateBuilder(sslap, sslContextBuilders).add(sslContext); + break; + default: break; } - builder.add(sslContext); + } + return sslContextBuilders; + } + private static SSLContextCollection.@NotNull Builder getOrCreateBuilder(Proxy proxy, HashMap sslContexts) throws UnknownHostException { + IpPort ipPort = getIpPort(proxy); + SSLContextCollection.Builder builder = sslContexts.get(ipPort); + if (builder == null) { + builder = new SSLContextCollection.Builder(); + sslContexts.put(ipPort, builder); } - return sslContexts; + return builder; } diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/SSLProxy.java b/core/src/main/java/com/predic8/membrane/core/proxies/SSLProxy.java index 0d60b3bc0d..83aabdb5fb 100644 --- a/core/src/main/java/com/predic8/membrane/core/proxies/SSLProxy.java +++ b/core/src/main/java/com/predic8/membrane/core/proxies/SSLProxy.java @@ -15,50 +15,49 @@ package com.predic8.membrane.core.proxies; import com.google.common.base.Objects; -import com.predic8.membrane.annot.MCAttribute; -import com.predic8.membrane.annot.MCChildElement; -import com.predic8.membrane.annot.MCElement; -import com.predic8.membrane.core.config.security.SSLParser; -import com.predic8.membrane.core.exchange.Exchange; +import com.predic8.membrane.annot.*; +import com.predic8.membrane.core.config.security.*; +import com.predic8.membrane.core.exchange.*; import com.predic8.membrane.core.interceptor.*; import com.predic8.membrane.core.router.*; -import com.predic8.membrane.core.sslinterceptor.SSLInterceptor; -import com.predic8.membrane.core.stats.RuleStatisticCollector; +import com.predic8.membrane.core.sslinterceptor.*; +import com.predic8.membrane.core.stats.*; import com.predic8.membrane.core.transport.http.*; -import com.predic8.membrane.core.transport.http.client.ConnectionConfiguration; +import com.predic8.membrane.core.transport.http.client.*; import com.predic8.membrane.core.transport.http.streampump.*; -import com.predic8.membrane.core.transport.ssl.SSLContext; -import com.predic8.membrane.core.transport.ssl.SSLExchange; -import com.predic8.membrane.core.transport.ssl.SSLProvider; -import com.predic8.membrane.core.transport.ssl.StaticSSLContext; -import com.predic8.membrane.core.util.DNSCache; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.predic8.membrane.annot.Required; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.List; - -import static com.predic8.membrane.core.interceptor.FlowController.ABORTION_REASON; +import com.predic8.membrane.core.transport.ssl.*; +import com.predic8.membrane.core.util.*; +import org.slf4j.*; + +import java.io.*; +import java.net.*; +import java.util.*; + +import static com.predic8.membrane.core.interceptor.FlowController.*; /** * Proxies SSL connections to a target server without decrypting the traffic. */ -@MCElement(name="sslProxy") +@MCElement(name = "sslProxy", topLevel = true, component = false) public class SSLProxy implements Proxy { private static final Logger log = LoggerFactory.getLogger(SSLProxy.class.getName()); - private Target target; + private SSLProxy.Target target; private ConnectionConfiguration connectionConfiguration = new ConnectionConfiguration(); private final RuleStatisticCollector ruleStatisticCollector = new RuleStatisticCollector(); private boolean useAsDefault = true; private List sslInterceptors = new ArrayList<>(); - @MCElement(id = "sslProxy-target", name="target", component = false) + public ConnectionConfiguration getConnectionConfiguration() { + return connectionConfiguration; + } + + @MCChildElement(order = 0) + public void setConnectionConfiguration(ConnectionConfiguration connectionConfiguration) { + this.connectionConfiguration = connectionConfiguration; + } + + @MCElement(id = "sslProxy-target", name = "target", component = false) public static class Target { private int port = -1; private String host; @@ -82,22 +81,13 @@ public void setHost(String host) { } } - public ConnectionConfiguration getConnectionConfiguration() { - return connectionConfiguration; - } - - @MCChildElement(order = 0) - public void setConnectionConfiguration(ConnectionConfiguration connectionConfiguration) { - this.connectionConfiguration = connectionConfiguration; - } - - public Target getTarget() { + public SSLProxy.Target getTarget() { return target; } @Required @MCChildElement(order = 100) - public void setTarget(Target target) { + public void setTarget(SSLProxy.Target target) { this.target = target; } @@ -115,7 +105,7 @@ public List getSslInterceptors() { return sslInterceptors; } - @MCChildElement(allowForeign=true, order=50) + @MCChildElement(allowForeign = true, order = 50) public void setSslInterceptors(List sslInterceptors) { this.sslInterceptors = sslInterceptors; } @@ -174,7 +164,7 @@ public void setName(String name) { @Override public String getName() { - return "SSL " + getHost() + ":" + getPort(); + return "SSL %s:%d".formatted(getHost(), getPort()); } @Override @@ -361,8 +351,8 @@ public Socket wrap(Socket socket, byte[] buffer, int position) throws IOExceptio log.error("", (Throwable) exc.getProperty(ABORTION_REASON)); byte error = exc.getError().getCode(); - byte[] alert_unrecognized_name = { 21 /* alert */, 3, 1 /* TLS 1.0 */, 0, 2 /* length: 2 bytes */, - 2 /* fatal */, error }; + byte[] alert_unrecognized_name = {21 /* alert */, 3, 1 /* TLS 1.0 */, 0, 2 /* length: 2 bytes */, + 2 /* fatal */, error}; try (socket) { socket.getOutputStream().write(alert_unrecognized_name); diff --git a/core/src/main/java/com/predic8/membrane/core/proxies/Target.java b/core/src/main/java/com/predic8/membrane/core/proxies/Target.java new file mode 100644 index 0000000000..1bcdb1a350 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/proxies/Target.java @@ -0,0 +1,173 @@ +package com.predic8.membrane.core.proxies; + +import com.predic8.membrane.annot.*; +import com.predic8.membrane.core.config.security.*; +import com.predic8.membrane.core.config.xml.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.interceptor.*; +import com.predic8.membrane.core.lang.*; +import com.predic8.membrane.core.router.*; + +import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*; + +/** + * @description

+ * The destination where the service proxy will send messages to. + * Use the target element if you want to send the messages to a target. + * Supports dynamic destinations through expressions. + *

+ */ +@MCElement(name = "target", component = false) +public class Target implements XMLSupport { + private String host; + private int port = -1; + private String method; + protected String url; + private boolean adjustHostHeader = true; + private ExchangeExpression.Language language = SPEL; + private ExchangeExpression exchangeExpression; + + private SSLParser sslParser; + + protected XmlConfig xmlConfig; + + public void init(Router router) { + if (url != null) { + exchangeExpression = TemplateExchangeExpression.newInstance(new ExchangeExpression.InterceptorAdapter(router, xmlConfig), language, url); + } + + } + + public String compileUrl(Exchange exc, Interceptor.Flow flow) { + /* + * Will always evaluate on every call. This is fine as SpEL is fast enough and performs its own optimizations. + * 1.000.000 calls ~10ms + */ + if (exchangeExpression != null) { + return exchangeExpression.evaluate(exc, flow, String.class); + } + return url; + } + + public Target() { + } + + public Target(String host) { + setHost(host); + } + + public Target(String host, int port) { + setHost(host); + setPort(port); + } + + public String getHost() { + return host; + } + + /** + * @description Host address of the target. + * @example localhost, 192.168.1.1 + */ + @MCAttribute + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + /** + * @description Port number of the target. + * @default 80 + * @example 8080 + */ + @MCAttribute + public void setPort(int port) { + this.port = port; + } + + public String getUrl() { + return url; + } + + /** + * @description Absolute URL of the target. If this is set, host and port will be ignored. + * Supports inline expressions through ${<expression>} elements. + * @example http://membrane-soa.org + */ + @MCAttribute + public void setUrl(String url) { + this.url = url; + } + + public SSLParser getSslParser() { + return sslParser; + } + + /** + * @description Configures outbound SSL (HTTPS). + */ + @MCChildElement(allowForeign = true) + public void setSslParser(SSLParser sslParser) { + this.sslParser = sslParser; + } + + public boolean isAdjustHostHeader() { + return adjustHostHeader; + } + + @MCAttribute + public void setAdjustHostHeader(boolean adjustHostHeader) { + this.adjustHostHeader = adjustHostHeader; + } + + public String getMethod() { + return method; + } + + /** + * @param method + * @description The method that should be used to make the call to the backend. + * Overwrites the original method. + */ + @MCAttribute + public void setMethod(String method) { + this.method = method; + } + + public ExchangeExpression getExchangeExpression() { + return exchangeExpression; + } + + public ExchangeExpression.Language getLanguage() { + return language; + } + + /** + * @description the language of the inline expressions + * @default SpEL + * @example SpEL, groovy, jsonpath, xpath + */ + @MCAttribute + public void setLanguage(ExchangeExpression.Language language) { + this.language = language; + } + + /** + * XML Configuration e.g. declaration of XML namespaces for XPath expressions, ... + * + * @param xmlConfig + */ + @Override + @MCChildElement(allowForeign = true, order = 10) + public void setXmlConfig(XmlConfig xmlConfig) { + this.xmlConfig = xmlConfig; + } + + @Override + public XmlConfig getXmlConfig() { + return xmlConfig; + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/security/KeyStoreUtil.java b/core/src/main/java/com/predic8/membrane/core/security/KeyStoreUtil.java index 647f592b38..4edc0857e5 100644 --- a/core/src/main/java/com/predic8/membrane/core/security/KeyStoreUtil.java +++ b/core/src/main/java/com/predic8/membrane/core/security/KeyStoreUtil.java @@ -56,17 +56,16 @@ public static KeyStore filterKeyStoreByAlias(KeyStore ks, char[] keyPass, String } /** - * Generates an SHA-256 digest of the certificate associated with the given alias in the KeyStore. + * Generates an SHA-256 digest of the certificate. * - * @param ks The KeyStore containing the certificate. - * @param alias The alias of the certificate in the KeyStore. + * @param cert The certificate. * @return A String representation of the SHA-256 digest, with bytes separated by colons. * @throws CertificateEncodingException If there's an error encoding the certificate. * @throws KeyStoreException If there's an error accessing the KeyStore. * @throws NoSuchAlgorithmException If the SHA-256 algorithm is not available. */ - public static @org.jetbrains.annotations.NotNull String getDigest(KeyStore ks, String alias) throws CertificateEncodingException, KeyStoreException, NoSuchAlgorithmException { - byte[] pkeEnc = ks.getCertificate(alias).getEncoded(); + public static @org.jetbrains.annotations.NotNull String getDigest(Certificate cert) throws CertificateEncodingException, KeyStoreException, NoSuchAlgorithmException { + byte[] pkeEnc = cert.getEncoded(); MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(pkeEnc); byte[] mdbytes = md.digest(); diff --git a/core/src/main/java/com/predic8/membrane/core/transport/ssl/SSLContextCollection.java b/core/src/main/java/com/predic8/membrane/core/transport/ssl/SSLContextCollection.java index 0a5d719744..591620a1ac 100644 --- a/core/src/main/java/com/predic8/membrane/core/transport/ssl/SSLContextCollection.java +++ b/core/src/main/java/com/predic8/membrane/core/transport/ssl/SSLContextCollection.java @@ -50,11 +50,10 @@ public class SSLContextCollection implements SSLProvider { public static class Builder { private final List dnsNames = new ArrayList<>(); private final List sslContexts = new ArrayList<>(); + private boolean useCollection = false; public SSLProvider build() { - if (sslContexts.isEmpty()) - throw new IllegalStateException("No SSLContext's were added to this Builder before invoking build()."); - if (sslContexts.size() > 1) { + if (sslContexts.size() > 1 || useCollection) { return new SSLContextCollection(sslContexts, dnsNames); } else return sslContexts.getFirst(); @@ -66,6 +65,10 @@ public void add(SSLContext sslContext) { dnsNames.add(sslContext.constructHostNamePattern()); } } + + public void useCollection() { + useCollection = true; + } } private final List sslContexts; diff --git a/core/src/main/java/com/predic8/membrane/core/transport/ssl/StaticSSLContext.java b/core/src/main/java/com/predic8/membrane/core/transport/ssl/StaticSSLContext.java index dcd9f97ef4..bb58293102 100644 --- a/core/src/main/java/com/predic8/membrane/core/transport/ssl/StaticSSLContext.java +++ b/core/src/main/java/com/predic8/membrane/core/transport/ssl/StaticSSLContext.java @@ -227,11 +227,14 @@ private Key getKey(SSLParser sslParser, ResolverMap resourceResolver, String bas return kmf; } - private static @org.jetbrains.annotations.NotNull List getCertificates(SSLParser sslParser, ResolverMap resourceResolver, String baseLocation) throws IOException { + private static @org.jetbrains.annotations.NotNull List getCertificates(SSLParser sslParser, ResolverMap resourceResolver, String baseLocation) throws IOException, CertificateEncodingException, KeyStoreException, NoSuchAlgorithmException { List certs = new ArrayList<>(); - for (com.predic8.membrane.core.config.security.Certificate cert : sslParser.getKey().getCertificates()) - certs.add(PEMSupport.getInstance().parseCertificate(cert.get(resourceResolver, baseLocation))); + for (com.predic8.membrane.core.config.security.Certificate c : sslParser.getKey().getCertificates()) { + X509Certificate cert = PEMSupport.getInstance().parseCertificate(c.get(resourceResolver, baseLocation)); + warnOfOldCertificate(cert); + certs.add(cert); + } if (certs.isEmpty()) throw new RuntimeException("At least one //ssl/key/certificate is required."); return certs; @@ -308,15 +311,21 @@ public int hashCode() { public static KeyStore openKeyStore(Store store, char[] keyPass, ResolverMap resourceResolver, String baseLocation) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, NoSuchProviderException { KeyStore ks = getAndLoadKeyStore(store, resourceResolver, baseLocation, getStoreTypeOrDefault(store), getPassword(store, keyPass)); - if (!defaultCertificateWarned && ks.getCertificate("membrane") != null) { - if (getDigest(ks, "membrane").equals(DEFAULT_CERTIFICATE_SHA256_OLD) || - getDigest(ks, "membrane").equals(DEFAULT_CERTIFICATE_SHA256)) { + if (ks.getCertificate("membrane") != null) + warnOfOldCertificate(ks.getCertificate("membrane")); + return ks; + } + + private static void warnOfOldCertificate(Certificate cert) throws CertificateEncodingException, KeyStoreException, NoSuchAlgorithmException { + if (!defaultCertificateWarned) { + String digest = getDigest(cert); + if (digest.equals(DEFAULT_CERTIFICATE_SHA256_OLD) || + digest.equals(DEFAULT_CERTIFICATE_SHA256)) { log.warn("Using Membrane with the default certificate. This is highly discouraged! " + "Please run the generate-ssl-keys script in the conf directory."); defaultCertificateWarned = true; } } - return ks; } private static char @org.jetbrains.annotations.NotNull [] getPassword(Store store, char[] keyPass) { diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/DispatchingInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/DispatchingInterceptorTest.java index 118784c63c..307c69c54b 100644 --- a/core/src/test/java/com/predic8/membrane/core/interceptor/DispatchingInterceptorTest.java +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/DispatchingInterceptorTest.java @@ -187,7 +187,7 @@ void invalidUriErrorMessageProduction() throws Exception { void validPathWithUnderscore() throws Exception { // An API must be set on the exchange, otherwise in the interceptor a URL is not parsed var api = new APIProxy(); - api.setTarget(new AbstractServiceProxy.Target() {{ + api.setTarget(new Target() {{ setUrl("http://dummy/_tb/?test=21"); }}); var exc = get("/_tb/?test=21").buildExchange(); @@ -207,7 +207,7 @@ void validPathWithUnderscore() throws Exception { private @NotNull APIProxy getApiProxy() { var api = new APIProxy(); - api.setTarget(new AbstractServiceProxy.Target() {{ + api.setTarget(new Target() {{ setHost("localhost"); }}); return api; diff --git a/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java b/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java index 500bfaec48..f7019568e7 100644 --- a/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java +++ b/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java @@ -29,7 +29,7 @@ import com.predic8.membrane.core.interceptor.rewrite.*; import com.predic8.membrane.core.interceptor.xml.*; import com.predic8.membrane.core.openapi.serviceproxy.*; -import com.predic8.membrane.core.proxies.AbstractServiceProxy.*; +import com.predic8.membrane.core.proxies.*; import com.predic8.membrane.core.util.*; import org.jetbrains.annotations.*; import org.junit.jupiter.api.*; @@ -38,7 +38,7 @@ import org.junit.jupiter.params.provider.*; import java.io.*; -import java.lang.reflect.Method; +import java.lang.reflect.*; import java.util.*; import java.util.function.*; import java.util.stream.*; diff --git a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/Swagger20Test.java b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/Swagger20Test.java index bc733c83c3..e1f2237361 100644 --- a/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/Swagger20Test.java +++ b/core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/Swagger20Test.java @@ -18,16 +18,16 @@ import com.predic8.membrane.core.interceptor.flow.*; import com.predic8.membrane.core.interceptor.templating.*; -import com.predic8.membrane.core.proxies.AbstractServiceProxy.*; +import com.predic8.membrane.core.proxies.*; import com.predic8.membrane.core.router.*; import org.hamcrest.*; import org.jetbrains.annotations.*; import org.junit.jupiter.api.*; -import static com.predic8.membrane.test.TestUtil.getPathFromResource; +import static com.predic8.membrane.test.TestUtil.*; import static io.restassured.RestAssured.*; import static io.restassured.filter.log.LogDetail.*; -import static java.util.Collections.singletonList; +import static java.util.Collections.*; public class Swagger20Test { diff --git a/core/src/test/java/com/predic8/membrane/core/security/KeyStoreUtilTest.java b/core/src/test/java/com/predic8/membrane/core/security/KeyStoreUtilTest.java index 8a34c01372..b8f618a364 100644 --- a/core/src/test/java/com/predic8/membrane/core/security/KeyStoreUtilTest.java +++ b/core/src/test/java/com/predic8/membrane/core/security/KeyStoreUtilTest.java @@ -70,7 +70,7 @@ void testFilterKeyStoreByAlias() throws KeyStoreException, NoSuchProviderExcepti @Test void testGetDigest() throws CertificateEncodingException, KeyStoreException, NoSuchAlgorithmException { - String digest = KeyStoreUtil.getDigest(keyStore, ALIAS); + String digest = KeyStoreUtil.getDigest(keyStore.getCertificate(ALIAS)); assertEquals(EXPECTED_DIGEST, digest); } diff --git a/core/src/test/java/com/predic8/membrane/integration/withinternet/interceptor/RewriteInterceptorIntegrationTest.java b/core/src/test/java/com/predic8/membrane/integration/withinternet/interceptor/RewriteInterceptorIntegrationTest.java index 429f351682..103c8a92a3 100644 --- a/core/src/test/java/com/predic8/membrane/integration/withinternet/interceptor/RewriteInterceptorIntegrationTest.java +++ b/core/src/test/java/com/predic8/membrane/integration/withinternet/interceptor/RewriteInterceptorIntegrationTest.java @@ -50,7 +50,7 @@ static void setUp() throws Exception { interceptor.getMappings().add(new Mapping("/city\\?wsdl", "/city-service?wsdl", null)); ServiceProxy proxy = new APIProxy(); - AbstractServiceProxy.Target target = new AbstractServiceProxy.Target(); + Target target = new Target(); target.setUrl("https://www.predic8.de"); proxy.setTarget(target); proxy.setKey(new ServiceProxyKey("localhost", "*", ".*", 3006)); diff --git a/distribution/src/test/java/com/predic8/membrane/load/LoadTester.java b/distribution/src/test/java/com/predic8/membrane/load/LoadTester.java index 2a0a4bfe0b..d8638323b4 100644 --- a/distribution/src/test/java/com/predic8/membrane/load/LoadTester.java +++ b/distribution/src/test/java/com/predic8/membrane/load/LoadTester.java @@ -66,7 +66,7 @@ private void startMembrane() throws Exception { var api = new APIProxy(); api.setKey(new APIProxyKey(2000)); - api.setTarget(new AbstractServiceProxy.Target("localhost", 2010)); + api.setTarget(new Target("localhost", 2010)); r.add(api); r.start(); diff --git a/distribution/tutorials/security/10-TLS-Termination.yaml b/distribution/tutorials/security/10-TLS-Termination.yaml new file mode 100644 index 0000000000..9c08313f83 --- /dev/null +++ b/distribution/tutorials/security/10-TLS-Termination.yaml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://www.membrane-api.io/v7.0.6.json +# +# Tutorial: Terminating TLS +# +# The client connects to Membrane via HTTPS. Membrane terminates TLS, decrypts +# the traffic, and can inspect or modify the request before forwarding it to +# the upstream service. +# +# Notes: +# - The example uses a self-signed certificate. Browsers and curl will not +# trust it unless you add it to your trust store. +# +# Try: +# curl https://localhost:8443 +# => fails because curl does not trust the self-signed certificate +# curl https://localhost:8443 -k +# => works (skips certificate verification; use only for testing) + +api: + # Default port for HTTPS + port: 8443 + ssl: + key: + private: + location: membrane-key.pem + certificates: + - certificate: + location: membrane.pem + flow: + - request: + # Traffic is decrypted and can be logged or transformed + - log: {} + target: + # Traffic is encrypted again and forwarded + url: https://api.predic8.de \ No newline at end of file diff --git a/distribution/tutorials/security/20-Central-SSL-Config.yaml b/distribution/tutorials/security/20-Central-SSL-Config.yaml new file mode 100644 index 0000000000..5459c833dd --- /dev/null +++ b/distribution/tutorials/security/20-Central-SSL-Config.yaml @@ -0,0 +1,41 @@ +# yaml-language-server: $schema=https://www.membrane-api.io/v7.0.6.json +# +# Tutorial: Central TLS Configuration +# +# Try: +# curl https://localhost:8443/foo -k +# curl https://localhost:8443/bar -k + +components: + myssl: + ssl: + key: + private: + location: membrane-key.pem + certificates: + - certificate: + location: membrane.pem + +--- +api: + $ref: "#/components/myssl" + path: + uri: /foo + port: 8443 + flow: + - static: + src: Foo + - return: + status: 200 + +--- +api: + $ref: "#/components/myssl" + path: + uri: /bar + port: 8443 + flow: + - static: + src: Bar + - return: + status: 200 \ No newline at end of file diff --git a/distribution/tutorials/security/membrane-key.pem b/distribution/tutorials/security/membrane-key.pem new file mode 100644 index 0000000000..bf2822b32e --- /dev/null +++ b/distribution/tutorials/security/membrane-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCQBf8/X+sGQP1G +Axp+QR5iUCAHQyyWMZ+UQBGEdBPIkUZKjDb/kRZyl8Wj9J7xcQlbnAfRSX6SJXJB +YonhZjD8X1UlHQN2BWIeeyGk/ez+qiVNpoiHcT60m4XQ5bWfS3EPKgc/87YIWfnX +G2bNM3yQV+FcMhbUGEa+F7yenKgdk4d/Am5Oy2eJVP8qh0ikUgWlVAzNmZZF9Hnr +ocPRd5k4t1XtrBp8XrMpzIH8SVeCtL8g1fIE+Ofl9ffPxOhqlb5XmCdyCW0D+C1M +CjqaWohz320d1Y5d50VkZIwE3KUWbq3OVyPEGxAZ/6MhnLa3jqf93LSMMbD02RQo +bCjfQeV3u1xj2ko8beNr1IMwh9ZmhwY/I5YvWCBdUMABmJQzQR2WEdfxZQfpEVXm +MkiZ/cqf/ftJQaYPMywWYefX0fY45g2EjG6sAea8HeA8kJ8nLIwfXyCjMX/WcxdJ +a9rKzSih3Q6ccJUbS0IVzmNdF/n2VLuhmmY+Ub3/yZBqa4bkoSmatrndVulWivPt +/wEUzgq5LFvT3bYkkdtIPPeWdjoYqpd434UwkzW6obBf/P7BK/dJEUWposeqw+Ri +gfN4fWpUbAP9rQo5E7ygAqw5EvdS+rrm6ICC0PoG1K4JUagp3ElHumhPzJh8DqoR +wWanKgr0PGlhVJTpJseGNr74WuwCjQIDAQABAoICACAbwinMFc7FXuHEr9udY2t6 +HwXXd0F9yyynMvCTW1S/9IqKo3lLDJTcGsMgdm3v7CzpBoIlbysgX45NrF0cpgeh +rk8RnpOphBQ1Ec1hkfw9jQb4fWAqwlbMBGOLzre2fzxo+4hJqlfGzXEkKgFN/POG +MBcYkKfUyLK5AoNe/Ajp+s95SIPXC5xdn2yJEAwYQCt5Z0hP+SVb6XF7lTcmAXoY +P3c3AwmeqDVFo2YOKM0aRDELGYN7xWFqeoauyLz4aODI6mRdTxlPMzQMZL/ENr8U +fNRlp7FuLbWByAiOc1yirKt/mFjwSqDtMd58vGyESJYLZhIKQv/3RGv1nxAU9hXr +gIyA5LBoUtVhTjv3eVeIxBwBNfskcV14txDkRQwONfwkW9a3un+YSpKPUYRNGkW3 +i8AGkM7PaeANBIyGX+Vkad+1euTg54p60PNNGFJV+GAsbcOqIpIMDYh6PsN/VPTl +qADjzSiz/7o81JMLEoWeqAt5EhWZBIjNoRioDxm5nMzWymAhVYhrk/1ABFfpTwBo +HMyFVubkAn7ATYqrt65VPoHSbbAPMOrsFDM/6bHwctoygxmVajOrK9+2s2DU/r1b +HGl+3glRqZdlERhWLnkzfK2L7YGbxF2xJ4c0waNsfQeYagwBlZdpGkGLg3UxUqsQ +r/PpumiyC2a6MFI48SIBAoIBAQDFVGXSk4ZWbX0GkcUftz0A3JLuARJopXb1sJ5B +2WDUgPQjMSFMuCrLCGRmvXlwaXJyR20tyBMB3sObZcf2iB2nwhuj7GrWNzLacvht +DOTFtrbUyJLh107air2gZh8f72vf3Jq486JDtpnoKdWX9THorlHPInj4KE2UnPk8 +LI45ZNsfaHztcY06YdwnSEo2Jb31TH9kMWculxDNOkTP0wMSj/8HQ2xs4VX7OL4T +f5ZHEtQAY+/KzBnVq/0qpu2B1R7ouYR3/bflKM6aiCz6gQOkvPvlQoAny5/sB2cN +icReULIvRfkJMTlawtp9yaGBTLAh+KsqyoqdEa+b4OTR0aIBAoIBAQC62DvABZHR +Xv5q/T7+xypc+xDH1VlOfUwghUhtyxbXMYgRr5XZNF+mX63jtD0ofGwhecH03bPc +T78M6nQQJPsU9QFphoKmxSn3oFa9z0CdIOoBMl6Jd0PlSEcq4srardVVvt5m7lNg +sdakL4kcBe3YAuJcx0va6o88WeFK5pPpCUVWtEObi6I0hCAvC+1ShkvruAgr0ktD +lwJUFNItfHAG9+lVjmaBL99QGRuZqWWG2shvVGkFlxIsskAE7Nc6plEdsE4/reqw +B/PuCgMg6AjE9gdIEK16/bX8lPvJUrdm8uSDX8Lqdypn77zH4ucCaCIP1K6m9RVS +SMI/ea0Z5ciNAoIBACN44XjyJhMBSZlVuO7TnLa77/my3aFMJU9TOY+yyPJKYBi3 +azoEXUT0NEA3zU3E1truNhUlwyzx443axRBvNY2Dmj5kcGwjrhOQ2VGZIMLd7o3f +pG/OqZEloaLk2LF/849hc7rNqRytBYuzgX74h8lBFNm8Y6yQainN8gu8IrD0jWNf +AB4a7H1TPCUfnJ+RN02SO9BCBT9dzppl4BrP3tyGYBzpJOOJulAXyittIxGRs+SC +EogNGpPLgA4bxdQyAIVrXAHJ7TXXmRQ0aIl7ibEie9Ghm7ILq6rdRcwsOM2Pighv +dq4NWDcVkftMtKiupXbl4bcmg7ZqiHvn+JozKgECggEAXIC9ZB3dyWhcUR+71mtI +P+xUrHql0kQaMmeKKkFHt5ZgJVFmwLg7OqVX88nCFY80AYbETbSNXV9l2MEiYPPO +EtwRrOrZODOYyW3qQ+KdvK0U7S7AuxH+3T7hbQwHHii928u5VABd6xtghry6BtOw +oycaZpCMr389FsXw0iJBzDe4ympIpnOFBLb9MA2Zmye94p+j+/4LIRVcI8CDJd3b +oLeQH7l2ajMHPiKQFY1WJGOgo+2IaSoX52UAaO/QxsqckjQ9KS2zaR8m1wzB68K0 +xL6BcF5BZQjC1p/z4AJBpohONfRtDaOdrQN11QgzEm/3nRrQtPvVaH15He/hkVa/ +6QKCAQA8q85EZq9BsofxQQTbYkySNUD+fiNMGoMUUHmZn63D2jiD39wSr7SXOM7a +OeLLjVWJsV+4/GFRNt0Uihef1nrfg2ZLwruWMdkFR+PCQ6CyU/AJERJGfzeijk0M +ClbkN1ddRFMj9I3GO2kdpAqv942VipiTtGZsC8HhEWYE9wx9Mbgdoec6g3PXsTgk +X+4241zTNzDUZOKj4TPFH364IyW3sryFvt0/uy9eHAHju5MqxwiKHZZyf81kJ+qS +s55LCW2V0eyfFKSdtA8EpdFl67hp/03ohq8gjgpkioViWxY0iC6olVYlflWYonTb +j+Z65LJOC77JPJNrd+Vx3khZ+C9L +-----END PRIVATE KEY----- diff --git a/distribution/tutorials/security/membrane.pem b/distribution/tutorials/security/membrane.pem new file mode 100644 index 0000000000..bd25e2ccdb --- /dev/null +++ b/distribution/tutorials/security/membrane.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEyjCCArKgAwIBAgIJALOkZYSVfD4HMA0GCSqGSIb3DQEBDAUAMBMxETAPBgNV +BAMTCG1lbWJyYW5lMB4XDTI1MDkwODE1MjUzOVoXDTM1MDkwNjE1MjUzOVowEzER +MA8GA1UEAxMIbWVtYnJhbmUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCQBf8/X+sGQP1GAxp+QR5iUCAHQyyWMZ+UQBGEdBPIkUZKjDb/kRZyl8Wj9J7x +cQlbnAfRSX6SJXJBYonhZjD8X1UlHQN2BWIeeyGk/ez+qiVNpoiHcT60m4XQ5bWf +S3EPKgc/87YIWfnXG2bNM3yQV+FcMhbUGEa+F7yenKgdk4d/Am5Oy2eJVP8qh0ik +UgWlVAzNmZZF9HnrocPRd5k4t1XtrBp8XrMpzIH8SVeCtL8g1fIE+Ofl9ffPxOhq +lb5XmCdyCW0D+C1MCjqaWohz320d1Y5d50VkZIwE3KUWbq3OVyPEGxAZ/6MhnLa3 +jqf93LSMMbD02RQobCjfQeV3u1xj2ko8beNr1IMwh9ZmhwY/I5YvWCBdUMABmJQz +QR2WEdfxZQfpEVXmMkiZ/cqf/ftJQaYPMywWYefX0fY45g2EjG6sAea8HeA8kJ8n +LIwfXyCjMX/WcxdJa9rKzSih3Q6ccJUbS0IVzmNdF/n2VLuhmmY+Ub3/yZBqa4bk +oSmatrndVulWivPt/wEUzgq5LFvT3bYkkdtIPPeWdjoYqpd434UwkzW6obBf/P7B +K/dJEUWposeqw+RigfN4fWpUbAP9rQo5E7ygAqw5EvdS+rrm6ICC0PoG1K4JUagp +3ElHumhPzJh8DqoRwWanKgr0PGlhVJTpJseGNr74WuwCjQIDAQABoyEwHzAdBgNV +HQ4EFgQUr2XYb/kepP2XjG+dB9wxAa0L2tgwDQYJKoZIhvcNAQEMBQADggIBAB7b +24o52EUsAkDRJLxZbcRfZRs5mbgmm4lIJMgwakh6UkN+vciOgpgELkDcYrY1XYMx +G+4C/OuzJHaEwg11I2gN3nYJ3sVycs/rSzWoAWOlyeJxxnQ55BKjB4/ZAmn37NeZ +JW3C3mMjPxnmP54Twr1iu+elZMKeVnkjJWgskik15P8CjJys71MmtpCJcMIwuH8g +I3i7RNhn3fJG8jFLOnzO84QT+nuX5lGOCx81ebjmi1WaFwg2KXvT5judjFH1CK59 +IAnc71NPhCbsmILQzfUj3+8Eh6CoQkRLFuTGpApk4zaJmOgrQTzdYMbBCYTYQIVA +a00HP7h8FGVsj37DNZG3QL105DV1UTgPWEOq3Uzu29o8RGxh/ICR7VQV8EVp3isA +x85Qnbl+KmSG2FxFvzCzAGKcv0eBS2K8a/340vPczj071+aKSrmRPWo17eqTZJgL +nr0visH877b48IYqAohy4zxIbid0SuzL8KGKqqWd6Yr9tuJxcqc6o/gxJmFyvZq/ +urARF40ls+crkD5EqUpP7yXDwmPj0Hp24DE+Y08asvpfo/o9m8zrRUpHO9x7cf/7 +6OlK3sDC5ULzqEkvGSqicr1r79nzDj0evh4KyYD8ifePhMrn61jG/1PVGEW2jdPO +hJGTSECn2l7bssHM2l3OBDz2DBp9gDdAKGS3Xkg3 +-----END CERTIFICATE-----