diff options
Diffstat (limited to 'jrt/src')
-rw-r--r-- | jrt/src/com/yahoo/jrt/Acceptor.java | 2 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/Connection.java | 20 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/CryptoSocket.java | 12 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/ErrorCode.java | 3 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/InvocationServer.java | 6 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java | 5 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/Method.java | 6 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/RequestAccessFilter.java | 17 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java | 34 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/SecurityContext.java | 24 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/Target.java | 27 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/TlsCryptoSocket.java | 46 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/slobrok/api/Mirror.java | 3 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/slobrok/api/Register.java | 3 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/slobrok/server/Slobrok.java | 3 | ||||
-rw-r--r-- | jrt/src/com/yahoo/jrt/tool/RpcInvoker.java | 3 |
16 files changed, 129 insertions, 85 deletions
diff --git a/jrt/src/com/yahoo/jrt/Acceptor.java b/jrt/src/com/yahoo/jrt/Acceptor.java index 1cbfd36e8c5..14b35c5893f 100644 --- a/jrt/src/com/yahoo/jrt/Acceptor.java +++ b/jrt/src/com/yahoo/jrt/Acceptor.java @@ -1,14 +1,12 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jrt; - import java.nio.channels.ClosedChannelException; import java.nio.channels.ServerSocketChannel; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; - /** * A class used to listen on a network socket. A separate thread is * used to accept connections and register them with the underlying diff --git a/jrt/src/com/yahoo/jrt/Connection.java b/jrt/src/com/yahoo/jrt/Connection.java index 00aceb7e352..8a185907aae 100644 --- a/jrt/src/com/yahoo/jrt/Connection.java +++ b/jrt/src/com/yahoo/jrt/Connection.java @@ -1,7 +1,10 @@ // 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.ConnectionAuthContext; + import java.io.IOException; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; @@ -9,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; @@ -436,9 +438,16 @@ class Connection extends Target { } @Override - public Optional<SecurityContext> getSecurityContext() { - return Optional.ofNullable(socket) - .flatMap(CryptoSocket::getSecurityContext); + 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() { @@ -455,8 +464,7 @@ class Connection extends Target { waiter.waitDone(); } - public void invokeAsync(Request req, double timeout, - RequestWaiter waiter) { + public void invokeAsync(Request req, double timeout, RequestWaiter waiter) { if (timeout < 0.0) { timeout = 0.0; } diff --git a/jrt/src/com/yahoo/jrt/CryptoSocket.java b/jrt/src/com/yahoo/jrt/CryptoSocket.java index 78308b76624..e30579d2bdc 100644 --- a/jrt/src/com/yahoo/jrt/CryptoSocket.java +++ b/jrt/src/com/yahoo/jrt/CryptoSocket.java @@ -2,10 +2,11 @@ package com.yahoo.jrt; +import com.yahoo.security.tls.ConnectionAuthContext; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.util.Optional; /** @@ -102,11 +103,6 @@ public interface CryptoSocket { **/ public void dropEmptyBuffers(); - /** - * Returns the security context for the current connection (given handshake completed), - * or empty if the current connection is not secure. - */ - default public Optional<SecurityContext> getSecurityContext() { - 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/ErrorCode.java b/jrt/src/com/yahoo/jrt/ErrorCode.java index beaabcea316..8e129cfef98 100644 --- a/jrt/src/com/yahoo/jrt/ErrorCode.java +++ b/jrt/src/com/yahoo/jrt/ErrorCode.java @@ -49,4 +49,7 @@ public class ErrorCode /** Method failed (111) **/ public static final int METHOD_FAILED = 111; + + /** Permission denied (112) **/ + public static final int PERMISSION_DENIED = 112; } diff --git a/jrt/src/com/yahoo/jrt/InvocationServer.java b/jrt/src/com/yahoo/jrt/InvocationServer.java index 9df92eb20a6..7704c0019ed 100644 --- a/jrt/src/com/yahoo/jrt/InvocationServer.java +++ b/jrt/src/com/yahoo/jrt/InvocationServer.java @@ -31,7 +31,11 @@ class InvocationServer { public void invoke() { if (method != null) { if (method.checkParameters(request)) { - method.invoke(request); + if (method.requestAccessFilter().allow(request)) { + method.invoke(request); + } else { + request.setError(ErrorCode.PERMISSION_DENIED, "Permission denied"); + } } else { request.setError(ErrorCode.WRONG_PARAMS, "Parameters in " + request + " does not match " + method); } diff --git a/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java index df01f4f2fa7..ab9d78d2676 100644 --- a/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java +++ b/jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java @@ -1,10 +1,11 @@ // 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.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 @@ -130,5 +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<SecurityContext> getSecurityContext() { return Optional.ofNullable(socket).flatMap(CryptoSocket::getSecurityContext); } + @Override public ConnectionAuthContext connectionAuthContext() { return socket.connectionAuthContext(); } } diff --git a/jrt/src/com/yahoo/jrt/Method.java b/jrt/src/com/yahoo/jrt/Method.java index 4fc9f0714da..89c66747e0b 100644 --- a/jrt/src/com/yahoo/jrt/Method.java +++ b/jrt/src/com/yahoo/jrt/Method.java @@ -40,6 +40,8 @@ public class Method { private String[] returnName; private String[] returnDesc; + private RequestAccessFilter filter = RequestAccessFilter.ALLOW_ALL; + private static final String undocumented = "???"; @@ -147,6 +149,10 @@ public class Method { return this; } + public Method requestAccessFilter(RequestAccessFilter filter) { this.filter = filter; return this; } + + public RequestAccessFilter requestAccessFilter() { return filter; } + /** * Obtain the name of a parameter * diff --git a/jrt/src/com/yahoo/jrt/RequestAccessFilter.java b/jrt/src/com/yahoo/jrt/RequestAccessFilter.java new file mode 100644 index 00000000000..6701436d6ce --- /dev/null +++ b/jrt/src/com/yahoo/jrt/RequestAccessFilter.java @@ -0,0 +1,17 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jrt; + +/** + * Request access filter is invoked before any call to {@link Method#invoke(Request)}. + * If {@link #allow(Request)} returns false, the method is not invoked, and the request is failed with error + * {@link ErrorCode#PERMISSION_DENIED}. + * + * @author bjorncs + */ +public interface RequestAccessFilter { + + RequestAccessFilter ALLOW_ALL = __ -> true; + + boolean allow(Request r); + +} diff --git a/jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java b/jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java new file mode 100644 index 00000000000..9bb497e96ed --- /dev/null +++ b/jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java @@ -0,0 +1,34 @@ +// 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.CapabilitySet; +import com.yahoo.security.tls.MissingCapabilitiesException; + +/** + * @author bjorncs + */ +public class RequireCapabilitiesFilter implements RequestAccessFilter { + + 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) { + try { + r.target().connectionAuthContext() + .verifyCapabilities(requiredCapabilities, "RPC", r.methodName(), r.target().peerSpec().toString()); + return true; + } catch (MissingCapabilitiesException e) { + return false; + } + } + +} diff --git a/jrt/src/com/yahoo/jrt/SecurityContext.java b/jrt/src/com/yahoo/jrt/SecurityContext.java deleted file mode 100644 index 4eef99cb93f..00000000000 --- a/jrt/src/com/yahoo/jrt/SecurityContext.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jrt; - -import java.security.cert.X509Certificate; -import java.util.List; - -/** - * @author bjorncs - */ -public class SecurityContext { - - private final List<X509Certificate> peerCertificateChain; - - public SecurityContext(List<X509Certificate> peerCertificateChain) { - this.peerCertificateChain = peerCertificateChain; - } - - /** - * @return the peer certificate chain if the peer was authenticated, empty list if not. - */ - public List<X509Certificate> peerCertificateChain() { - return peerCertificateChain; - } -} diff --git a/jrt/src/com/yahoo/jrt/Target.java b/jrt/src/com/yahoo/jrt/Target.java index a59aa341fe0..6cb9d432e03 100644 --- a/jrt/src/com/yahoo/jrt/Target.java +++ b/jrt/src/com/yahoo/jrt/Target.java @@ -1,7 +1,9 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jrt; -import java.util.Optional; +import com.yahoo.security.tls.ConnectionAuthContext; + +import java.time.Duration; /** * A Target represents a connection endpoint with RPC @@ -69,9 +71,13 @@ public abstract class Target { public Exception getConnectionLostReason() { return null; } /** - * Returns the security 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<SecurityContext> getSecurityContext(); + public abstract ConnectionAuthContext connectionAuthContext(); + + + /** @return address spec of socket peer */ + public abstract Spec peerSpec(); /** * Check if this target represents the client side of a @@ -97,6 +103,10 @@ public abstract class Target { */ public abstract void invokeSync(Request req, double timeout); + public void invokeSync(Request req, Duration timeout) { + invokeSync(req, toSeconds(timeout)); + } + /** * Invoke a request on this target and let the completion be * signalled with a callback. @@ -105,8 +115,15 @@ public abstract class Target { * @param timeout timeout in seconds * @param waiter callback handler */ - public abstract void invokeAsync(Request req, double timeout, - RequestWaiter waiter); + public abstract void invokeAsync(Request req, double timeout, RequestWaiter waiter); + + public void invokeAsync(Request req, Duration timeout, RequestWaiter waiter) { + invokeAsync(req, toSeconds(timeout), waiter); + } + + private static double toSeconds(Duration duration) { + return ((double)duration.toMillis())/1000.0; + } /** * Invoke a request on this target, but ignore the return diff --git a/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java b/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java index a899938dd45..d83c1ee8baa 100644 --- a/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java +++ b/jrt/src/com/yahoo/jrt/TlsCryptoSocket.java @@ -1,27 +1,23 @@ // 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.authz.AuthorizationResult; -import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager; +import com.yahoo.security.tls.ConnectionAuthContext; +import com.yahoo.security.tls.PeerAuthorizationFailedException; +import com.yahoo.security.tls.TransportSecurityUtils; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SocketChannel; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.Objects; import java.util.logging.Logger; -import static java.util.stream.Collectors.toList; import static javax.net.ssl.SSLEngineResult.Status; /** @@ -46,7 +42,7 @@ public class TlsCryptoSocket implements CryptoSocket { private int sessionApplicationBufferSize; private ByteBuffer handshakeDummyBuffer; private HandshakeState handshakeState; - private AuthorizationResult authorizationResult; + private ConnectionAuthContext authContext; public TlsCryptoSocket(SocketChannel channel, SSLEngine sslEngine) { this.channel = channel; @@ -102,15 +98,6 @@ public class TlsCryptoSocket implements CryptoSocket { channelRead(); break; case NEED_WORK: - if (authorizationResult == null) { - PeerAuthorizerTrustManager.getAuthorizationResult(sslEngine) // only available during handshake - .ifPresent(result -> { - if (!result.succeeded()) { - metrics.incrementPeerAuthorizationFailures(); - } - authorizationResult = result; - }); - } break; case COMPLETED: return HandshakeState.COMPLETED; @@ -127,6 +114,10 @@ public class TlsCryptoSocket implements CryptoSocket { SSLSession session = sslEngine.getSession(); sessionApplicationBufferSize = session.getApplicationBufferSize(); sessionPacketBufferSize = session.getPacketBufferSize(); + authContext = TransportSecurityUtils.getConnectionAuthContext(session).orElseThrow(); + if (!authContext.authorized()) { + metrics.incrementPeerAuthorizationFailures(); + } log.fine(() -> String.format("Handshake complete: protocol=%s, cipherSuite=%s", session.getProtocol(), session.getCipherSuite())); if (sslEngine.getUseClientMode()) { metrics.incrementClientTlsConnectionsEstablished(); @@ -148,8 +139,7 @@ public class TlsCryptoSocket implements CryptoSocket { } } } catch (SSLHandshakeException e) { - // sslEngine.getDelegatedTask().run() and handshakeWrap() may throw SSLHandshakeException, potentially handshakeUnwrap() and sslEngine.beginHandshake() as well. - if (authorizationResult == null || authorizationResult.succeeded()) { // don't include handshake failures due from PeerAuthorizerTrustManager + if (!(e.getCause() instanceof PeerAuthorizationFailedException)) { metrics.incrementTlsCertificateVerificationFailures(); } throw e; @@ -224,19 +214,9 @@ public class TlsCryptoSocket implements CryptoSocket { } @Override - public Optional<SecurityContext> getSecurityContext() { - try { - if (handshakeState != HandshakeState.COMPLETED) { - return Optional.empty(); - } - List<X509Certificate> peerCertificateChain = - Arrays.stream(sslEngine.getSession().getPeerCertificates()) - .map(X509Certificate.class::cast) - .collect(toList()); - return Optional.of(new SecurityContext(peerCertificateChain)); - } catch (SSLPeerUnverifiedException e) { // unverified peer: non-certificate based ciphers or peer did not provide a certificate - return Optional.of(new SecurityContext(List.of())); // secure connection, but peer does not have a certificate chain. - } + public ConnectionAuthContext connectionAuthContext() { + if (handshakeState != HandshakeState.COMPLETED) throw new IllegalStateException("Handshake not complete"); + return Objects.requireNonNull(authContext); } private boolean handshakeWrap() throws IOException { diff --git a/jrt/src/com/yahoo/jrt/slobrok/api/Mirror.java b/jrt/src/com/yahoo/jrt/slobrok/api/Mirror.java index b37e78490f1..dd2fd5a8242 100644 --- a/jrt/src/com/yahoo/jrt/slobrok/api/Mirror.java +++ b/jrt/src/com/yahoo/jrt/slobrok/api/Mirror.java @@ -11,6 +11,7 @@ import com.yahoo.jrt.Task; import com.yahoo.jrt.TransportThread; import com.yahoo.jrt.Values; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -203,7 +204,7 @@ public class Mirror implements IMirror { req = new Request("slobrok.incremental.fetch"); req.parameters().add(new Int32Value(specsGeneration)); // gencnt req.parameters().add(new Int32Value(5000)); // mstimeout - target.invokeAsync(req, 40.0, reqWait); + target.invokeAsync(req, Duration.ofSeconds(40), reqWait); } private void handleUpdate() { diff --git a/jrt/src/com/yahoo/jrt/slobrok/api/Register.java b/jrt/src/com/yahoo/jrt/slobrok/api/Register.java index 14afea396bf..e529dea2eff 100644 --- a/jrt/src/com/yahoo/jrt/slobrok/api/Register.java +++ b/jrt/src/com/yahoo/jrt/slobrok/api/Register.java @@ -15,6 +15,7 @@ import com.yahoo.jrt.Task; import com.yahoo.jrt.TransportThread; import com.yahoo.jrt.Values; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -279,7 +280,7 @@ public class Register { req.parameters().add(new StringValue(name)); req.parameters().add(new StringValue(mySpec)); log.log(Level.FINE, logMessagePrefix() + " now"); - target.invokeAsync(req, 35.0, reqWait); + target.invokeAsync(req, Duration.ofSeconds(35), reqWait); } private String logMessagePrefix() { diff --git a/jrt/src/com/yahoo/jrt/slobrok/server/Slobrok.java b/jrt/src/com/yahoo/jrt/slobrok/server/Slobrok.java index 24ab63c1d2f..5fd8beb3cc7 100644 --- a/jrt/src/com/yahoo/jrt/slobrok/server/Slobrok.java +++ b/jrt/src/com/yahoo/jrt/slobrok/server/Slobrok.java @@ -17,6 +17,7 @@ import com.yahoo.jrt.TargetWatcher; import com.yahoo.jrt.Task; import com.yahoo.jrt.Transport; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -225,7 +226,7 @@ public class Slobrok { this.spec = spec; target = orb.connect(new Spec(spec)); Request cbReq = new Request("slobrok.callback.listNamesServed"); - target.invokeAsync(cbReq, 5.0, this); + target.invokeAsync(cbReq, Duration.ofSeconds(5), this); } @Override diff --git a/jrt/src/com/yahoo/jrt/tool/RpcInvoker.java b/jrt/src/com/yahoo/jrt/tool/RpcInvoker.java index 71049673d90..67933cfafde 100644 --- a/jrt/src/com/yahoo/jrt/tool/RpcInvoker.java +++ b/jrt/src/com/yahoo/jrt/tool/RpcInvoker.java @@ -16,6 +16,7 @@ import com.yahoo.jrt.Transport; import com.yahoo.jrt.Value; import com.yahoo.jrt.Values; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.ArrayList; @@ -80,7 +81,7 @@ public class RpcInvoker { supervisor = new Supervisor(new Transport("invoker")); target = supervisor.connect(new Spec(connectspec)); Request request = createRequest(method,arguments); - target.invokeSync(request,10.0); + target.invokeSync(request, Duration.ofSeconds(10)); if (request.isError()) { System.err.println("error(" + request.errorCode() + "): " + request.errorMessage()); return; |