package org.openslx.dozmod.util;
import java.net.ProxySelector;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.util.Timeout;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openslx.bwlp.thrift.iface.MasterServer;
import org.openslx.dozmod.App;
import org.openslx.dozmod.authentication.ShibbolethEcp;
import org.openslx.thrifthelper.ThriftManager;
import org.openslx.util.Util;
import com.btr.proxy.search.ProxySearch;
import com.btr.proxy.search.wpad.WpadProxySearchStrategy;
import com.btr.proxy.util.Logger.LogBackEnd;
import com.btr.proxy.util.Logger.LogLevel;
/**
* Configures the proxy
*
* @author Jonathan Bauer
*/
public class ProxyConfigurator {
/**
* Logger for this class
*/
private final static Logger LOGGER = LogManager.getLogger(ProxyConfigurator.class);
private static final AtomicReference<CloseableHttpClient> apacheClient = new AtomicReference<>();
private static final List<TLS[]> TLS_CHECKLIST;
private static final Timeout TIMEOUT_CONNECT = Timeout.ofSeconds(8);
private static final Timeout TIMEOUT_SOCKET = Timeout.ofSeconds(8);
private static final Timeout TIMEOUT_REQUEST = Timeout.ofSeconds(3);
private static SSLContext thriftCtx;
static {
List<TLS[]> res = new ArrayList<>();
res.add(null);
boolean ok = false;
try {
SSLContext.getInstance("TLSv1.3");
ok = true;
} catch (Exception e) {
}
if (ok) {
res.add(new TLS[] { TLS.V_1_3, TLS.V_1_2 });
}
res.add(new TLS[] { TLS.V_1_2 });
res.add(new TLS[] { TLS.V_1_2, TLS.V_1_1 });
TLS_CHECKLIST = Collections.unmodifiableList(res);
try {
if (ok) {
thriftCtx = SSLContext.getInstance("TLSv1.3");
} else {
thriftCtx = SSLContext.getInstance("TLSv1.2");
}
thriftCtx.init(null, null, null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
LOGGER.warn("Error creating default SSL context for thrift", e);
}
}
private static void tryAllThriftVariants() {
thriftCtx = null;
for (TLS[] tls : TLS_CHECKLIST) {
if (tls == null)
continue;
SSLContext ctx;
MasterServer.Client masterClient;
try {
ctx = SSLContext.getInstance(tls[0].id);
ctx.init(null, null, null);
masterClient = ThriftManager.getNewMasterClient(ctx,
App.getMasterServerAddress(),
App.THRIFT_SSL_PORT, 4000);
if (masterClient != null) {
masterClient.ping();
try {
masterClient.getInputProtocol().getTransport().close();
masterClient.getOutputProtocol().getTransport().close();
} catch (Throwable e) {
}
thriftCtx = ctx;
break;
}
} catch (Exception e) {
}
}
}
private static void tryAllHttpsVariants() {
apacheClient.set(null);
for (TLS[] tls : TLS_CHECKLIST) {
HttpClientBuilder builder;
try {
if (tls == null) {
builder = createDefaultBuilder();
} else {
builder = createSlxBuilder(tls);
}
CloseableHttpClient client = builder.build();
if (testHttpsMaster(client)) {
apacheClient.set(client);
break;
}
} catch (Exception e) {
}
}
}
/**
* Initialization method.
*/
public static void init() {
tryAllThriftVariants();
// Only try HTTPS if thrift succeeded
if (thriftCtx != null) {
tryAllHttpsVariants();
}
// To be discussed: Let's bail out if the master server is reachable via thrift and https
if (apacheClient.get() != null) {
LOGGER.info("Not setting up proxy because master server seems reachable.");
return;
}
// first setup the logger of proxy-vole
com.btr.proxy.util.Logger.setBackend(new LogBackEnd() {
public void log(Class<?> clazz, LogLevel loglevel, String msg, Object... params) {
Level priority;
switch (loglevel) {
case ERROR:
priority = Level.ERROR;
break;
case WARNING:
priority = Level.WARN;
break;
case INFO:
priority = Level.INFO;
break;
default:
priority = Level.DEBUG;
}
LogManager.getLogger(clazz).log(priority, MessageFormat.format(msg, params));
}
public boolean isLogginEnabled(LogLevel logLevel) {
return true;
}
});
LOGGER.info("Master server not directly reachable; trying to determine proxy");
// try to find local proxy settings
ProxySearch proxySearch = ProxySearch.getDefaultProxySearch();
ProxySelector myProxySelector = proxySearch.getProxySelector();
if (myProxySelector == null) {
// didn't work, try WPAD detection
LOGGER.error("No suitable proxy settings found, trying WPAD...");
WpadProxySearchStrategy ss = new WpadProxySearchStrategy();
myProxySelector = ss.getProxySelector();
}
// final check to see if WPAD actually worked
if (myProxySelector == null) {
LOGGER.error("Could not find a suitable proxy!");
return;
}
// Seems to have worked
ProxySelector.setDefault(myProxySelector);
tryAllThriftVariants();
tryAllHttpsVariants();
if (thriftCtx == null || apacheClient.get() == null) {
LOGGER.warn("Could not establish Thrift/HTTPS connection after auto-configuring Proxy config");
return;
}
LOGGER.info("Proxy initialised.");
Util.sleep(10);
}
/**
* Get the HttpClient to be used with apache
* httpclient.
*
* @return client
*/
public static CloseableHttpClient getClient() {
CloseableHttpClient inst = apacheClient.get();
if (inst != null)
return inst;
inst = createDefaultBuilder().build();
apacheClient.compareAndSet(null, inst);
return inst;
}
private static HttpClientBuilder createDefaultBuilder() {
return HttpClientBuilder.create()
.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
.setDefaultConnectionConfig(ConnectionConfig.custom()
.setConnectTimeout(ProxyConfigurator.TIMEOUT_CONNECT)
.setSocketTimeout(ProxyConfigurator.TIMEOUT_SOCKET)
.build())
.setMaxConnPerRoute(4)
.build());
}
private static HttpClientBuilder createSlxBuilder(TLS[] tlsVersions) {
return HttpClientBuilder.create()
.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(SSLConnectionSocketFactoryBuilder.create()
.setTlsVersions(tlsVersions)
.build())
.setDefaultTlsConfig(TlsConfig.custom()
.setSupportedProtocols(tlsVersions)
.build())
.setDefaultConnectionConfig(ConnectionConfig.custom()
.setConnectTimeout(ProxyConfigurator.TIMEOUT_CONNECT)
.setSocketTimeout(ProxyConfigurator.TIMEOUT_SOCKET)
.build())
.setMaxConnPerRoute(4)
.build());
}
private static boolean testHttpsMaster(CloseableHttpClient client) {
final RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(ProxyConfigurator.TIMEOUT_REQUEST)
.build();
HttpGet httpGet = new HttpGet(ShibbolethEcp.BWLP_SP.toString());
httpGet.setConfig(requestConfig);
httpGet.setHeader("Accept", "text/html, application/vnd.paos+xml");
httpGet.setHeader("PAOS",
"ver=\"urn:liberty:paos:2003-08\";\"urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp\"");
try (CloseableHttpResponse response = client.execute(httpGet)) {
LOGGER.debug("Master-server replies with " + response.getCode());
int rc = response.getCode();
return rc >= 200 && rc < 300;
} catch (Exception e) {
LOGGER.debug("Cannot reach master server via HTTPS", e);
return false;
}
}
public static SSLContext getThriftSslContext() {
return thriftCtx;
}
}