diff options
Diffstat (limited to 'container-core/src')
5 files changed, 115 insertions, 1 deletions
diff --git a/container-core/src/main/java/com/yahoo/container/jdisc/utils/CapabilityRequiringRequestHandler.java b/container-core/src/main/java/com/yahoo/container/jdisc/utils/CapabilityRequiringRequestHandler.java new file mode 100644 index 00000000000..1dd866ae571 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/container/jdisc/utils/CapabilityRequiringRequestHandler.java @@ -0,0 +1,19 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.container.jdisc.utils; + +import com.yahoo.container.jdisc.RequestView; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.security.tls.Capability; +import com.yahoo.security.tls.CapabilitySet; + +/** + * @author bjorncs + */ +public interface CapabilityRequiringRequestHandler extends RequestHandler { + + CapabilitySet DEFAULT_REQUIRED_CAPABILITIES = CapabilitySet.from(Capability.HTTP_UNCLASSIFIED); + + default CapabilitySet requiredCapabilities(RequestView req) { return DEFAULT_REQUIRED_CAPABILITIES; } + +} diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/CapabilityEnforcingRequestHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/CapabilityEnforcingRequestHandler.java new file mode 100644 index 00000000000..c5bffbf6008 --- /dev/null +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/CapabilityEnforcingRequestHandler.java @@ -0,0 +1,89 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.jdisc.http.server.jetty; + +import com.yahoo.container.jdisc.RequestView; +import com.yahoo.container.jdisc.utils.CapabilityRequiringRequestHandler; +import com.yahoo.jdisc.Request; +import com.yahoo.jdisc.ResourceReference; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.DelegatedRequestHandler; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.HttpResponse; +import com.yahoo.security.tls.MissingCapabilitiesException; +import com.yahoo.security.tls.TransportSecurityUtils; +import com.yahoo.slime.Slime; +import com.yahoo.slime.SlimeUtils; + +import javax.net.ssl.SSLSession; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Optional; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * Request handler wrapper that enforced required capabilities as specified by underlying {@link CapabilityRequiringRequestHandler}. + * + * @author bjorncs + */ +class CapabilityEnforcingRequestHandler implements DelegatedRequestHandler { + + private final RequestHandler wrapped; + + CapabilityEnforcingRequestHandler(RequestHandler wrapped) { this.wrapped = wrapped; } + + @Override + public ContentChannel handleRequest(Request req, ResponseHandler responseHandler) { + var capabilityRequiringHandler = + DelegatedRequestHandler.resolve(CapabilityRequiringRequestHandler.class, wrapped).orElse(null); + var requiredCapabilities = capabilityRequiringHandler != null + ? capabilityRequiringHandler.requiredCapabilities(new View(req)) + : CapabilityRequiringRequestHandler.DEFAULT_REQUIRED_CAPABILITIES; + var authCtx = Optional.ofNullable(req.context().get(RequestUtils.JDISC_REQUEST_SSLSESSION)) + .flatMap(s -> TransportSecurityUtils.getConnectionAuthContext((SSLSession) s)) + .orElse(null); + + // Connection auth context will not be available if handler is bound to: + // 1) server with custom TLS configuration. + // 2) server without TLS (http) + if (authCtx != null) { + var peer = Optional.ofNullable(((HttpRequest)req).getRemoteAddress()) + .map(Object::toString).orElse("<unknown>"); + String method = ((HttpRequest) req).getMethod().name(); + try { + authCtx.verifyCapabilities(requiredCapabilities, method, req.getUri().getPath(), peer); + } catch (MissingCapabilitiesException e) { + int code = HttpResponse.Status.FORBIDDEN; + var resp = new Response(code); + resp.headers().add("Content-Type", "application/json"); + ContentChannel ch = responseHandler.handleResponse(resp); + var slime = new Slime(); + var root = slime.setObject(); + root.setString("error-code", Integer.toString(code)); + root.setString("message", "Missing required capabilities"); + ch.write(ByteBuffer.wrap(uncheck(() -> SlimeUtils.toJsonBytes(slime))), null); + ch.close(null); + return null; + } + } + return wrapped.handleRequest(req, responseHandler); + } + + @Override public void release() { wrapped.release(); } + @Override public RequestHandler getDelegate() { return wrapped; } + @Override public void handleTimeout(Request request, ResponseHandler handler) { wrapped.handleRequest(request, handler); } + @Override public ResourceReference refer() { return wrapped.refer(); } + @Override public ResourceReference refer(Object context) { return wrapped.refer(context); } + + private static class View implements RequestView { + private final HttpRequest req; + View(Request req) { this.req = (HttpRequest) req; } + @Override public HttpRequest.Method method() { return req.getMethod(); } + @Override public URI uri() { return req.getUri(); } + } + +} diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java index df606894b14..fe7b30ac7b3 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java @@ -72,7 +72,8 @@ class FilteringRequestHandler extends AbstractRequestHandler { getRequestHandlerSpec(resolvedRequestHandler) .ifPresent(requestHandlerSpec -> request.context().put(RequestHandlerSpec.ATTRIBUTE_NAME, requestHandlerSpec)); - RequestHandler requestHandler = new ReferenceCountingRequestHandler(resolvedRequestHandler); + RequestHandler requestHandler = new ReferenceCountingRequestHandler( + new CapabilityEnforcingRequestHandler(resolvedRequestHandler)); ResponseHandler responseHandler; if (responseFilter != null) { diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java index d45a8789e4c..1aa12808f71 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java @@ -7,6 +7,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.Utf8Appendable; +import javax.net.ssl.SSLSession; import java.net.InetSocketAddress; import java.net.URI; import java.security.cert.X509Certificate; @@ -34,6 +35,8 @@ class HttpRequestFactory { getConnection((Request) servletRequest).getCreatedTimeStamp()); httpRequest.context().put(RequestUtils.JDISC_REQUEST_X509CERT, getCertChain(servletRequest)); httpRequest.context().put(RequestUtils.JDICS_REQUEST_PORT, servletRequest.getLocalPort()); + SSLSession sslSession = (SSLSession) servletRequest.getAttribute(RequestUtils.JETTY_REQUEST_SSLSESSION); + httpRequest.context().put(RequestUtils.JDISC_REQUEST_SSLSESSION, sslSession); servletRequest.setAttribute(HttpRequest.class.getName(), httpRequest); return httpRequest; } catch (Utf8Appendable.NotUtf8Exception e) { diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java index da4de957739..2769f1f6729 100644 --- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java +++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java @@ -13,9 +13,11 @@ import org.eclipse.jetty.server.SecureRequestCustomizer; */ public class RequestUtils { public static final String JDISC_REQUEST_X509CERT = "jdisc.request.X509Certificate"; + public static final String JDISC_REQUEST_SSLSESSION = "jdisc.request.SSLSession"; public static final String JDISC_REQUEST_CHAIN = "jdisc.request.chain"; public static final String JDISC_RESPONSE_CHAIN = "jdisc.response.chain"; public static final String SERVLET_REQUEST_X509CERT = SecureRequestCustomizer.JAKARTA_SERVLET_REQUEST_X_509_CERTIFICATE; + public static final String JETTY_REQUEST_SSLSESSION = new SecureRequestCustomizer().getSslSessionAttribute(); // The local port as reported by servlet spec. This will be influenced by Host header and similar mechanisms. // The request URI uses the local listen port as the URI is used for handler routing/bindings. |