summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
-rw-r--r--jdisc_core/abi-spec.json4
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/handler/DelegatedRequestHandler.java22
7 files changed, 140 insertions, 2 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.
diff --git a/jdisc_core/abi-spec.json b/jdisc_core/abi-spec.json
index 0213d382894..acacccb4241 100644
--- a/jdisc_core/abi-spec.json
+++ b/jdisc_core/abi-spec.json
@@ -521,7 +521,9 @@
],
"methods" : [
"public abstract com.yahoo.jdisc.handler.RequestHandler getDelegate()",
- "public com.yahoo.jdisc.handler.RequestHandler getDelegateRecursive()"
+ "public com.yahoo.jdisc.handler.RequestHandler getDelegateRecursive()",
+ "public static com.yahoo.jdisc.handler.RequestHandler resolve(com.yahoo.jdisc.handler.RequestHandler)",
+ "public static java.util.Optional resolve(java.lang.Class, com.yahoo.jdisc.handler.RequestHandler)"
],
"fields" : [ ]
},
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/DelegatedRequestHandler.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/DelegatedRequestHandler.java
index d021eb5a79c..f79bc5fee58 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/DelegatedRequestHandler.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/DelegatedRequestHandler.java
@@ -2,6 +2,8 @@
package com.yahoo.jdisc.handler;
+import java.util.Optional;
+
public interface DelegatedRequestHandler extends RequestHandler {
RequestHandler getDelegate();
@@ -12,4 +14,24 @@ public interface DelegatedRequestHandler extends RequestHandler {
}
return delegate;
}
+
+ /** Find delegated request handler recursively */
+ static RequestHandler resolve(RequestHandler h) {
+ if (h instanceof DelegatedRequestHandler dh) return dh.getDelegateRecursive();
+ return h;
+ }
+
+ /**
+ * Find delegated request handler of specified type recursively
+ * Note that the returned handler may not be the innermost handler.
+ */
+ static <T extends RequestHandler> Optional<T> resolve(Class<T> type, RequestHandler h) {
+ T candidate = type.isInstance(h) ? type.cast(h) : null;
+ while (h instanceof DelegatedRequestHandler) {
+ h = ((DelegatedRequestHandler) h).getDelegate();
+ if (type.isInstance(h)) candidate = type.cast(h);
+ }
+ return Optional.ofNullable(candidate);
+ }
+
}