Search in sources :

Example 1 with ProxyMeter

use of org.commonjava.indy.httprox.util.ProxyMeter in project indy by Commonjava.

the class ProxyMITMSSLServer method execute.

private void execute() throws Exception {
    ProxyMeter meter = null;
    SSLServerSocketFactory sslServerSocketFactory = getSSLServerSocketFactory(host);
    serverPort = findOpenPort(FIND_OPEN_PORT_MAX_RETRIES);
    // TODO: What is the performance implication of opening a new server socket each time? Should we try to cache these?
    try (ServerSocket sslServerSocket = sslServerSocketFactory.createServerSocket(serverPort)) {
        // in case the response handler times out
        sslServerSocket.setSoTimeout(ACCEPT_SOCKET_WAIT_TIME_IN_MILLISECONDS);
        started = true;
        if (!isCancelled) {
            try (Socket socket = sslServerSocket.accept()) {
                logger.debug("MITM server started, {}", sslServerSocket);
                long startNanos = System.nanoTime();
                String method = null;
                String requestLine = null;
                meter = meterTemplate.copy(startNanos, method, requestLine);
                socket.setSoTimeout((int) TimeUnit.MINUTES.toMillis(config.getMITMSoTimeoutMinutes()));
                logger.debug("MITM server accepted");
                try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                    // TODO: Should we implement a while loop around this with some sort of read timeout, in case multiple requests are inlined?
                    // In principle, any sort of network communication is permitted over this port, but even if we restrict this to
                    // HTTPS only, couldn't there be multiple requests over the port at a time?
                    String path = null;
                    StringBuilder sb = new StringBuilder();
                    String line;
                    while ((line = in.readLine()) != null) {
                        sb.append(line + "\n");
                        if (// only care about GET/HEAD
                        line.startsWith(GET) || line.startsWith(HEAD)) {
                            String[] toks = line.split("\\s+");
                            method = toks[0];
                            path = toks[1];
                            requestLine = line;
                        } else if (line.isEmpty()) {
                            logger.debug("Get empty line and break");
                            break;
                        }
                    }
                    logger.debug("Request:\n{}", sb.toString());
                    if (path != null) {
                        try {
                            transferRemote(socket, host, port, method, path, meter);
                        } catch (Exception e) {
                            logger.error("Transfer remote failed", e);
                        }
                    } else {
                        logger.debug("MITM server failed to get request from client");
                    }
                }
            } catch (SocketTimeoutException ste) {
                logger.error("Socket read timeout with client hostname: {}, on port: {}.", host, port);
                throw ste;
            }
        }
        logger.debug("MITM server closed");
    } finally {
        if (meter != null) {
            meter.reportResponseSummary();
        }
        isCancelled = false;
        started = false;
    }
}
Also used : SocketTimeoutException(java.net.SocketTimeoutException) InputStreamReader(java.io.InputStreamReader) BufferedReader(java.io.BufferedReader) ProxyMeter(org.commonjava.indy.httprox.util.ProxyMeter) SSLServerSocketFactory(javax.net.ssl.SSLServerSocketFactory) ServerSocket(java.net.ServerSocket) ServerSocket(java.net.ServerSocket) Socket(java.net.Socket) SocketTimeoutException(java.net.SocketTimeoutException) IOException(java.io.IOException) ExecutionException(java.util.concurrent.ExecutionException)

Example 2 with ProxyMeter

use of org.commonjava.indy.httprox.util.ProxyMeter in project indy by Commonjava.

the class ProxyResponseWriter method doHandleEvent.

private void doHandleEvent(final ConduitStreamSinkChannel sinkChannel) {
    if (directed) {
        return;
    }
    ProxyMeter meter = new ProxyMeter(httpRequest.getRequestLine().getMethod(), httpRequest.getRequestLine().toString(), startNanos, sliMetricSet, restLogger, peerAddress);
    try {
        HttpConduitWrapper http = new HttpConduitWrapper(sinkChannel, httpRequest, contentController, cacheProvider);
        if (httpRequest == null) {
            if (error != null) {
                logger.debug("Handling error from request reader: " + error.getMessage(), error);
                handleError(error, http);
            } else {
                logger.debug("Invalid state (no error or request) from request reader. Sending 400.");
                try {
                    http.writeStatus(ApplicationStatus.BAD_REQUEST);
                } catch (final IOException e) {
                    logger.error("Failed to write BAD REQUEST for missing HTTP first-line to response channel.", e);
                }
            }
            return;
        }
        restLogger.info("SERVE {} (from: {})", httpRequest.getRequestLine(), peerAddress);
        // TODO: Can we handle this?
        final String oldThreadName = Thread.currentThread().getName();
        Thread.currentThread().setName("PROXY-" + httpRequest.getRequestLine().toString());
        sinkChannel.getCloseSetter().set((c) -> {
            logger.trace("Sink channel closing.");
            Thread.currentThread().setName(oldThreadName);
            if (sslTunnel != null) {
                logger.trace("Close ssl tunnel");
                sslTunnel.close();
            }
        });
        logger.debug("\n\n\n>>>>>>> Handle write\n\n\n");
        if (error == null) {
            ProxyResponseHelper proxyResponseHelper = new ProxyResponseHelper(httpRequest, config, contentController, repoCreator, storeManager, metricsConfig, metricManager, cls);
            try {
                if (repoCreator == null) {
                    throw new IndyDataException("No valid instance of ProxyRepositoryCreator");
                }
                final UserPass proxyUserPass = parse(ApplicationHeader.proxy_authorization, httpRequest, null);
                logger.info("Using proxy authentication: {}", proxyUserPass);
                mdcManager.putExtraHeaders(httpRequest);
                // FIXME: We cannot trace through this interface currently!
                mdcManager.putExternalID(proxyUserPass == null ? null : proxyUserPass.getUser());
                logger.debug("Proxy UserPass: {}\nConfig secured? {}\nConfig tracking type: {}", proxyUserPass, config.isSecured(), config.getTrackingType());
                if (proxyUserPass == null && (config.isSecured() || TrackingType.ALWAYS == config.getTrackingType())) {
                    String realmInfo = String.format(PROXY_AUTHENTICATE_FORMAT, config.getProxyRealm());
                    logger.info("Not authenticated to proxy. Sending response: {} / {}: {}", PROXY_AUTHENTICATION_REQUIRED, proxy_authenticate, realmInfo);
                    http.writeStatus(PROXY_AUTHENTICATION_REQUIRED);
                    http.writeHeader(proxy_authenticate, realmInfo);
                } else {
                    RequestLine requestLine = httpRequest.getRequestLine();
                    String method = requestLine.getMethod().toUpperCase();
                    String trackingId = null;
                    boolean authenticated = true;
                    if (proxyUserPass != null) {
                        TrackingKey trackingKey = proxyResponseHelper.getTrackingKey(proxyUserPass);
                        if (trackingKey != null) {
                            trackingId = trackingKey.getId();
                            RequestContextHelper.setContext(RequestContextHelper.CONTENT_TRACKING_ID, trackingId);
                        }
                        String authCacheKey = generateAuthCacheKey(proxyUserPass);
                        Boolean isAuthToken = proxyAuthCache.get(authCacheKey);
                        if (Boolean.TRUE.equals(isAuthToken)) {
                            authenticated = true;
                            logger.debug("Found auth key in cache");
                        } else {
                            logger.debug("Passing BASIC authentication credentials to Keycloak bearer-token translation authenticator");
                            authenticated = proxyAuthenticator.authenticate(proxyUserPass, http);
                            if (authenticated) {
                                proxyAuthCache.put(authCacheKey, Boolean.TRUE, config.getAuthCacheExpirationHours(), TimeUnit.HOURS);
                            }
                        }
                        logger.debug("Authentication done, result: {}", authenticated);
                    }
                    if (authenticated) {
                        switch(method) {
                            case GET_METHOD:
                            case HEAD_METHOD:
                                {
                                    final URL url = new URL(requestLine.getUri());
                                    logger.debug("getArtifactStore starts, trackingId: {}, url: {}", trackingId, url);
                                    ArtifactStore store = proxyResponseHelper.getArtifactStore(trackingId, url);
                                    proxyResponseHelper.transfer(http, store, url.getPath(), GET_METHOD.equals(method), proxyUserPass, meter);
                                    break;
                                }
                            case OPTIONS_METHOD:
                                {
                                    http.writeStatus(ApplicationStatus.OK);
                                    http.writeHeader(ApplicationHeader.allow, ALLOW_HEADER_VALUE);
                                    break;
                                }
                            case CONNECT_METHOD:
                                {
                                    if (!config.isMITMEnabled()) {
                                        logger.debug("CONNECT method not supported unless MITM-proxying is enabled.");
                                        http.writeStatus(ApplicationStatus.BAD_REQUEST);
                                        break;
                                    }
                                    // e.g, github.com:443
                                    String uri = requestLine.getUri();
                                    logger.debug("Get CONNECT request, uri: {}", uri);
                                    String[] toks = uri.split(":");
                                    String host = toks[0];
                                    int port = parseInt(toks[1]);
                                    directed = true;
                                    // After this, the proxy simply opens a plain socket to the target server and relays
                                    // everything between the initial client and the target server (including the TLS handshake).
                                    SocketChannel socketChannel;
                                    ProxyMITMSSLServer svr = new ProxyMITMSSLServer(host, port, trackingId, proxyUserPass, proxyResponseHelper, contentController, cacheProvider, config, meter);
                                    tunnelAndMITMExecutor.submit(svr);
                                    socketChannel = svr.getSocketChannel();
                                    if (socketChannel == null) {
                                        logger.debug("Failed to get MITM socket channel");
                                        http.writeStatus(ApplicationStatus.SERVER_ERROR);
                                        svr.stop();
                                        break;
                                    }
                                    sslTunnel = new ProxySSLTunnel(sinkChannel, socketChannel, config);
                                    tunnelAndMITMExecutor.submit(sslTunnel);
                                    // client input will be directed to target socket
                                    proxyRequestReader.setProxySSLTunnel(sslTunnel);
                                    // When all is ready, send the 200 to client. Client send the SSL handshake to reader,
                                    // reader direct it to tunnel to MITM. MITM finish the handshake and read the request data,
                                    // retrieve remote content and send back to tunnel to client.
                                    http.writeStatus(ApplicationStatus.OK);
                                    http.writeHeader("Status", "200 OK\n");
                                    break;
                                }
                            default:
                                {
                                    http.writeStatus(ApplicationStatus.METHOD_NOT_ALLOWED);
                                }
                        }
                    }
                }
                logger.debug("Response complete.");
            } catch (final Throwable e) {
                error = e;
            } finally {
                mdcManager.clear();
            }
        }
        if (error != null) {
            handleError(error, http);
        }
        try {
            if (directed) {
                // do not close sink channel
                ;
            } else {
                http.close();
            }
        } catch (final IOException e) {
            logger.error("Failed to shutdown response", e);
        }
    } finally {
        meter.reportResponseSummary();
        span.ifPresent(SpanAdapter::close);
    }
}
Also used : SocketChannel(java.nio.channels.SocketChannel) HttpConduitWrapper(org.commonjava.indy.httprox.util.HttpConduitWrapper) UserPass(org.commonjava.indy.subsys.http.util.UserPass) IOException(java.io.IOException) URL(java.net.URL) TrackingKey(org.commonjava.indy.folo.model.TrackingKey) IndyDataException(org.commonjava.indy.data.IndyDataException) RequestLine(org.apache.http.RequestLine) ArtifactStore(org.commonjava.indy.model.core.ArtifactStore) ProxyMeter(org.commonjava.indy.httprox.util.ProxyMeter) ProxyResponseHelper(org.commonjava.indy.httprox.util.ProxyResponseHelper) SpanAdapter(org.commonjava.o11yphant.trace.spi.adapter.SpanAdapter)

Aggregations

IOException (java.io.IOException)2 ProxyMeter (org.commonjava.indy.httprox.util.ProxyMeter)2 BufferedReader (java.io.BufferedReader)1 InputStreamReader (java.io.InputStreamReader)1 ServerSocket (java.net.ServerSocket)1 Socket (java.net.Socket)1 SocketTimeoutException (java.net.SocketTimeoutException)1 URL (java.net.URL)1 SocketChannel (java.nio.channels.SocketChannel)1 ExecutionException (java.util.concurrent.ExecutionException)1 SSLServerSocketFactory (javax.net.ssl.SSLServerSocketFactory)1 RequestLine (org.apache.http.RequestLine)1 IndyDataException (org.commonjava.indy.data.IndyDataException)1 TrackingKey (org.commonjava.indy.folo.model.TrackingKey)1 HttpConduitWrapper (org.commonjava.indy.httprox.util.HttpConduitWrapper)1 ProxyResponseHelper (org.commonjava.indy.httprox.util.ProxyResponseHelper)1 ArtifactStore (org.commonjava.indy.model.core.ArtifactStore)1 UserPass (org.commonjava.indy.subsys.http.util.UserPass)1 SpanAdapter (org.commonjava.o11yphant.trace.spi.adapter.SpanAdapter)1