Skip to content

Commit

Permalink
Allow insecure HTTPS/TLS connection for HttpWaitStrategy
Browse files Browse the repository at this point in the history
Signed-off-by: Andriy Redko <andriy.redko@aiven.io>
  • Loading branch information
reta committed Jan 24, 2022
1 parent f9538d1 commit b0db8d9
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,26 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess;

@Slf4j
Expand All @@ -47,6 +58,7 @@ public class HttpWaitStrategy extends AbstractWaitStrategy {
private Predicate<Integer> statusCodePredicate = null;
private Optional<Integer> livenessPort = Optional.empty();
private Duration readTimeout = Duration.ofSeconds(1);
private boolean allowInsecure;

/**
* Waits for the given status code.
Expand Down Expand Up @@ -112,6 +124,16 @@ public HttpWaitStrategy withMethod(String method) {
return this;
}

/**
* Indicates that HTTPS connection could use untrusted (self signed) certificate chains.
*
* @return this
*/
public HttpWaitStrategy allowInsecure() {
this.allowInsecure = true;
return this;
}

/**
* Authenticate with HTTP Basic Authorization credentials.
*
Expand Down Expand Up @@ -206,7 +228,7 @@ protected void waitUntilReady() {
retryUntilSuccess((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> {
getRateLimiter().doWhenReady(() -> {
try {
final HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
final HttpURLConnection connection = openConnection(uri);
connection.setReadTimeout(Math.toIntExact(readTimeout.toMillis()));

// authenticate
Expand Down Expand Up @@ -267,6 +289,45 @@ protected void waitUntilReady() {
}
}

private HttpURLConnection openConnection(final String uri) throws IOException, MalformedURLException {
if (tlsEnabled) {
final HttpsURLConnection connection = (HttpsURLConnection)new URL(uri).openConnection();
if (allowInsecure) {
// Create a trust manager that does not validate certificate chains
// and trust all certificates
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}

@Override
public void checkClientTrusted(final X509Certificate[] certs, final String authType) {
}

@Override
public void checkServerTrusted(final X509Certificate[] certs, final String authType) {
}
}
};

try {
// Create custom SSL context and set the "trust all certificates" trust manager
final SSLContext sc = SSLContext.getInstance("SSL");
sc.init(new KeyManager[0], trustAllCerts, new SecureRandom());
connection.setSSLSocketFactory(sc.getSocketFactory());
} catch (final NoSuchAlgorithmException | KeyManagementException ex) {
throw new IOException("Unable to create custom SSL factory instance", ex);
}
}

return connection;
} else {
return (HttpURLConnection) new URL(uri).openConnection();
}
}

/**
* Build the URI on which to check if the container is ready.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ public void testWaitUntilReadyWithSuccessWithCustomHeaders() {
assertThat(logs, containsString("baz: boo"));
}

/**
* Ensures that HTTPS requests made with the HttpWaitStrategy can skip the
* certificate validation chains (to support self-signed certificates for example).
*/
@Test
public void testWaitUntilReadyWithTlsAndAllowUnsecure() {
waitUntilReadyAndSucceed(startContainerWithCommand(createHttpsShellCommand("200 OK", GOOD_RESPONSE_BODY, 8080),
createHttpWaitStrategy(ready).usingTls().allowInsecure()
));
}

/**
* Expects that the WaitStrategy returns successfully after receiving an HTTP 401 response from the container.
* This 401 response is checked with a lambda using {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)}
Expand Down Expand Up @@ -206,4 +217,12 @@ private String createShellCommand(String header, String responseBody, int port)
"Content-Length: " + length + NEWLINE + "\";"
+ " echo \"" + responseBody + "\";} | nc -lp " + port + "; done";
}

private String createHttpsShellCommand(String header, String responseBody, int port) {
int length = responseBody.getBytes().length;
return "apk add nmap-ncat; while true; do { echo -e \"HTTP/1.1 " + header + NEWLINE +
"Content-Type: text/html" + NEWLINE +
"Content-Length: " + length + NEWLINE + "\";"
+ " echo \"" + responseBody + "\";} | ncat -lp " + port + " --ssl; done";
}
}

0 comments on commit b0db8d9

Please sign in to comment.