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-----
+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
+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-----