aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@yahooinc.com>2023-02-09 15:07:44 +0100
committerBjørn Christian Seime <bjorncs@yahooinc.com>2023-02-09 15:18:43 +0100
commit488a3986b3271e49c56945f84bca6bc68bf289bc (patch)
tree70c6e7f8dd08a4c223c1889a6b0023eac039020b /container-core/src/main/java
parent6ef87df07fa0c0ffdd595e39647e8253cb558d92 (diff)
Introduce capability support for jdisc request handlers
Add trait like interface to define capability mapping based on HTTP method and uri path. Enforce required capabilities through wrapper handler through existing filtering request handler.
Diffstat (limited to 'container-core/src/main/java')
-rw-r--r--container-core/src/main/java/com/yahoo/container/jdisc/utils/CapabilityRequiringRequestHandler.java19
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/CapabilityEnforcingRequestHandler.java89
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/FilteringRequestHandler.java3
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestFactory.java3
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/RequestUtils.java2
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.