From 2e3005c471ba6520b17438c93f4a36369cbc3acd Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Wed, 20 Jul 2022 13:44:00 +0200 Subject: Implement RequireCapabilitiesFilter in jrt + misc Add peerSpec to Target/Connection. Always provide ConnectionAuthContext. Add helper for creating default, all-granting ConnectionAuthContext. --- jrt/src/com/yahoo/jrt/Connection.java | 15 ++++-- jrt/src/com/yahoo/jrt/CryptoSocket.java | 10 +--- jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java | 5 +- .../com/yahoo/jrt/RequireCapabilitiesFilter.java | 54 ++++++++++++++++++++++ jrt/src/com/yahoo/jrt/Target.java | 10 ++-- jrt/src/com/yahoo/jrt/TlsCryptoSocket.java | 8 ++-- 6 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java (limited to 'jrt/src') diff --git a/jrt/src/com/yahoo/jrt/Connection.java b/jrt/src/com/yahoo/jrt/Connection.java index 5f40acad192..1e4092efb75 100644 --- a/jrt/src/com/yahoo/jrt/Connection.java +++ b/jrt/src/com/yahoo/jrt/Connection.java @@ -4,6 +4,7 @@ package com.yahoo.jrt; import com.yahoo.security.tls.ConnectionAuthContext; import java.io.IOException; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; @@ -11,7 +12,6 @@ import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; -import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; @@ -438,9 +438,16 @@ class Connection extends Target { } @Override - public Optional getConnectionAuthContext() { - return Optional.ofNullable(socket) - .flatMap(CryptoSocket::getConnectionAuthContext); + public ConnectionAuthContext connectionAuthContext() { + if (socket == null) throw new IllegalStateException("Not connected"); + return socket.connectionAuthContext(); + } + + @Override + public Spec peerSpec() { + if (socket == null) throw new IllegalStateException("Not connected"); + InetSocketAddress addr = (InetSocketAddress) socket.channel().socket().getRemoteSocketAddress(); + return new Spec(addr.getHostString(), addr.getPort()); } public boolean isClient() { diff --git a/jrt/src/com/yahoo/jrt/CryptoSocket.java b/jrt/src/com/yahoo/jrt/CryptoSocket.java index d7dac8d1023..e30579d2bdc 100644 --- a/jrt/src/com/yahoo/jrt/CryptoSocket.java +++ b/jrt/src/com/yahoo/jrt/CryptoSocket.java @@ -7,7 +7,6 @@ import com.yahoo.security.tls.ConnectionAuthContext; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.util.Optional; /** @@ -104,11 +103,6 @@ public interface CryptoSocket { **/ public void dropEmptyBuffers(); - /** - * Returns the auth context for the current connection (given handshake completed), - * or empty if the current connection is not secure. - */ - default public Optional getConnectionAuthContext() { - return Optional.empty(); - } + /** Returns the auth context for the current connection (given handshake completed) */ + default ConnectionAuthContext connectionAuthContext() { return ConnectionAuthContext.defaultAllCapabilities(); } } diff --git a/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java index 2b5d456da11..ab9d78d2676 100644 --- a/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java +++ b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java @@ -6,7 +6,6 @@ import com.yahoo.security.tls.ConnectionAuthContext; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.util.Optional; /** * A crypto socket for the server side of a connection that @@ -132,7 +131,5 @@ public class MaybeTlsCryptoSocket implements CryptoSocket { @Override public int write(ByteBuffer src) throws IOException { return socket.write(src); } @Override public FlushResult flush() throws IOException { return socket.flush(); } @Override public void dropEmptyBuffers() { socket.dropEmptyBuffers(); } - @Override public Optional getConnectionAuthContext() { - return Optional.ofNullable(socket).flatMap(CryptoSocket::getConnectionAuthContext); - } + @Override public ConnectionAuthContext connectionAuthContext() { return socket.connectionAuthContext(); } } diff --git a/jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java b/jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java new file mode 100644 index 00000000000..bb2eafcf711 --- /dev/null +++ b/jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java @@ -0,0 +1,54 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jrt; + +import com.yahoo.security.tls.Capability; +import com.yahoo.security.tls.CapabilityMode; +import com.yahoo.security.tls.CapabilitySet; +import com.yahoo.security.tls.ConnectionAuthContext; +import com.yahoo.security.tls.TransportSecurityUtils; + +import java.util.logging.Logger; + +import static com.yahoo.security.tls.CapabilityMode.DISABLE; +import static com.yahoo.security.tls.CapabilityMode.LOG_ONLY; + +/** + * @author bjorncs + */ +public class RequireCapabilitiesFilter implements RequestAccessFilter { + + private static final Logger log = Logger.getLogger(RequireCapabilitiesFilter.class.getName()); + private static final CapabilityMode MODE = TransportSecurityUtils.getCapabilityMode(); + + private final CapabilitySet requiredCapabilities; + + public RequireCapabilitiesFilter(CapabilitySet requiredCapabilities) { + this.requiredCapabilities = requiredCapabilities; + } + + public RequireCapabilitiesFilter(Capability... requiredCapabilities) { + this(CapabilitySet.from(requiredCapabilities)); + } + + @Override + public boolean allow(Request r) { + if (MODE == DISABLE) return true; + ConnectionAuthContext ctx = r.target().connectionAuthContext(); + CapabilitySet peerCapabilities = ctx.capabilities(); + boolean authorized = peerCapabilities.has(requiredCapabilities); + if (!authorized) { + String msg = "%sPermission denied for RPC method '%s'. Peer at %s with %s. Call requires %s, but peer has %s" + .formatted(MODE == LOG_ONLY ? "Dry-run: " : "", r.methodName(), r.target().peerSpec(), ctx.peerCertificateString().orElseThrow(), + requiredCapabilities.toNames(), peerCapabilities.toNames()); + if (MODE == LOG_ONLY) { + log.info(msg); + return true; + } else { + log.warning(msg); + return false; + } + } + return true; + } + +} diff --git a/jrt/src/com/yahoo/jrt/Target.java b/jrt/src/com/yahoo/jrt/Target.java index 8bca6069721..0e8c27deac5 100644 --- a/jrt/src/com/yahoo/jrt/Target.java +++ b/jrt/src/com/yahoo/jrt/Target.java @@ -3,8 +3,6 @@ package com.yahoo.jrt; import com.yahoo.security.tls.ConnectionAuthContext; -import java.util.Optional; - /** * A Target represents a connection endpoint with RPC * capabilities. Each such connection has a client and a server @@ -71,9 +69,13 @@ public abstract class Target { public Exception getConnectionLostReason() { return null; } /** - * Returns the connection auth context associated with this target, or empty if no connection or is insecure. + * Returns the connection auth context associated with this target. */ - public abstract Optional getConnectionAuthContext(); + public abstract ConnectionAuthContext connectionAuthContext(); + + + /** @return address spec of socket peer */ + public abstract Spec peerSpec(); /** * Check if this target represents the client side of a diff --git a/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java b/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java index 5ef4d149c6c..13274dc3ba5 100644 --- a/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java +++ b/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java @@ -14,7 +14,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SocketChannel; -import java.util.Optional; +import java.util.Objects; import java.util.logging.Logger; import static javax.net.ssl.SSLEngineResult.Status; @@ -219,9 +219,9 @@ public class TlsCryptoSocket implements CryptoSocket { } @Override - public Optional getConnectionAuthContext() { - if (handshakeState != HandshakeState.COMPLETED) return Optional.empty(); - return Optional.ofNullable(authContext); + public ConnectionAuthContext connectionAuthContext() { + if (handshakeState != HandshakeState.COMPLETED) throw new IllegalStateException("Handshake not complete"); + return Objects.requireNonNull(authContext); } private boolean handshakeWrap() throws IOException { -- cgit v1.2.3