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 apacheClient = new AtomicReference<>(); private static final List 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 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; } }