summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/rpc/security/MultiTenantRpcAuthorizerTest.java4
-rw-r--r--jrt/src/com/yahoo/jrt/Connection.java15
-rw-r--r--jrt/src/com/yahoo/jrt/CryptoSocket.java10
-rw-r--r--jrt/src/com/yahoo/jrt/MaybeTlsCryptoSocket.java5
-rw-r--r--jrt/src/com/yahoo/jrt/RequireCapabilitiesFilter.java54
-rw-r--r--jrt/src/com/yahoo/jrt/Target.java10
-rw-r--r--jrt/src/com/yahoo/jrt/TlsCryptoSocket.java8
-rw-r--r--jrt/tests/com/yahoo/jrt/EchoTest.java5
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/ConnectionAuthContext.java10
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizer.java4
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java3
11 files changed, 90 insertions, 38 deletions
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/security/MultiTenantRpcAuthorizerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/security/MultiTenantRpcAuthorizerTest.java
index efac33a740b..bffed6eb0b1 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/security/MultiTenantRpcAuthorizerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/rpc/security/MultiTenantRpcAuthorizerTest.java
@@ -250,9 +250,9 @@ public class MultiTenantRpcAuthorizerTest {
private static Request mockJrtRpcRequest(String payload) {
ConnectionAuthContext authContext =
- new ConnectionAuthContext(PEER_CERTIFICATE_CHAIN, CapabilitySet.none(), Set.of());
+ new ConnectionAuthContext(PEER_CERTIFICATE_CHAIN, CapabilitySet.all(), Set.of());
Target target = mock(Target.class);
- when(target.getConnectionAuthContext()).thenReturn(Optional.of(authContext));
+ when(target.connectionAuthContext()).thenReturn(authContext);
Request request = mock(Request.class);
when(request.target()).thenReturn(target);
Values values = new Values();
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<ConnectionAuthContext> 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<ConnectionAuthContext> 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<ConnectionAuthContext> 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<ConnectionAuthContext> 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<ConnectionAuthContext> 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 {
diff --git a/jrt/tests/com/yahoo/jrt/EchoTest.java b/jrt/tests/com/yahoo/jrt/EchoTest.java
index e65422bc66e..47c6e806635 100644
--- a/jrt/tests/com/yahoo/jrt/EchoTest.java
+++ b/jrt/tests/com/yahoo/jrt/EchoTest.java
@@ -16,7 +16,6 @@ import java.util.List;
import static com.yahoo.jrt.CryptoUtils.createTestTlsContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
@@ -147,7 +146,7 @@ public class EchoTest {
for (int i = 0; i < p.size(); i++) {
r.add(p.get(i));
}
- connAuthCtx = req.target().getConnectionAuthContext().orElse(null);
+ connAuthCtx = req.target().connectionAuthContext();
}
@org.junit.Test
@@ -168,8 +167,6 @@ public class EchoTest {
if (connAuthCtxAssertion != null) {
assertNotNull(connAuthCtx);
connAuthCtxAssertion.assertConnectionAuthContext(connAuthCtx);
- } else {
- assertNull(connAuthCtx);
}
}
}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/ConnectionAuthContext.java b/security-utils/src/main/java/com/yahoo/security/tls/ConnectionAuthContext.java
index e244d5ad23f..3ee6ed1dcaa 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/ConnectionAuthContext.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/ConnectionAuthContext.java
@@ -18,8 +18,10 @@ public record ConnectionAuthContext(List<X509Certificate> peerCertificateChain,
CapabilitySet capabilities,
Set<String> matchedPolicies) {
+ private static final ConnectionAuthContext DEFAULT_ALL_CAPABILITIES =
+ new ConnectionAuthContext(List.of(), CapabilitySet.all(), Set.of());
+
public ConnectionAuthContext {
- if (peerCertificateChain.isEmpty()) throw new IllegalArgumentException("Peer certificate chain is empty");
peerCertificateChain = List.copyOf(peerCertificateChain);
matchedPolicies = Set.copyOf(matchedPolicies);
}
@@ -33,7 +35,7 @@ public record ConnectionAuthContext(List<X509Certificate> peerCertificateChain,
public Optional<String> peerCertificateString() {
X509Certificate cert = peerCertificate().orElse(null);
if (cert == null) return Optional.empty();
- StringBuilder b = new StringBuilder("X.509Cert{");
+ StringBuilder b = new StringBuilder("[");
String cn = X509CertificateUtils.getSubjectCommonName(cert).orElse(null);
if (cn != null) {
b.append("CN='").append(cn).append("'");
@@ -55,7 +57,9 @@ public record ConnectionAuthContext(List<X509Certificate> peerCertificateChain,
if (cn != null || !dnsNames.isEmpty()) b.append(", ");
b.append("SAN_URI=").append(uris);
}
- return Optional.of(b.append("}").toString());
+ return Optional.of(b.append("]").toString());
}
+ public static ConnectionAuthContext defaultAllCapabilities() { return DEFAULT_ALL_CAPABILITIES; }
+
}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizer.java b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizer.java
index 608a8c9c933..99787725063 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizer.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizer.java
@@ -35,9 +35,7 @@ public class PeerAuthorizer {
public ConnectionAuthContext authorizePeer(X509Certificate cert) { return authorizePeer(List.of(cert)); }
public ConnectionAuthContext authorizePeer(List<X509Certificate> certChain) {
- if (authorizedPeers.isEmpty()) {
- return new ConnectionAuthContext(certChain, CapabilitySet.all(), Set.of());
- }
+ if (authorizedPeers.isEmpty()) return ConnectionAuthContext.defaultAllCapabilities();
X509Certificate cert = certChain.get(0);
Set<String> matchedPolicies = new HashSet<>();
Set<CapabilitySet> grantedCapabilities = new HashSet<>();
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java
index 089023e55f1..e6239e3f694 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java
@@ -14,7 +14,6 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
import java.util.logging.Logger;
/**
@@ -106,7 +105,7 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager {
log.fine(() -> "Verifying certificate: " + createInfoString(certChain[0], authType, isVerifyingClient));
ConnectionAuthContext result = mode != AuthorizationMode.DISABLE
? authorizer.authorizePeer(List.of(certChain))
- : new ConnectionAuthContext(List.of(certChain), CapabilitySet.all(), Set.of());
+ : ConnectionAuthContext.defaultAllCapabilities();
if (sslEngine != null) { // getHandshakeSession() will never return null in this context
sslEngine.getHandshakeSession().putValue(HANDSHAKE_SESSION_AUTH_CONTEXT_PROPERTY, result);
}