summaryrefslogtreecommitdiffstats
path: root/security-utils
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@yahooinc.com>2022-07-13 16:53:43 +0200
committerBjørn Christian Seime <bjorncs@yahooinc.com>2022-07-15 15:35:10 +0200
commiteed3e5deaf3fd13c353361e45420735a93d0f3d0 (patch)
treeb4e738c5cf85775153237ec07ea08f4e97d224f1 /security-utils
parentff26daaf31ec0567dc6a9049d5e275cf7c4810dc (diff)
Return granted capabilities from PeerAuthorizer
Introduce new ConnectionAuthContext as replacement for AuthorizationResult/SecurityContext.
Diffstat (limited to 'security-utils')
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/authz/AuthorizationResult.java45
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/authz/ConnectionAuthContext.java23
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java11
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java10
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/policy/CapabilitySet.java10
-rw-r--r--security-utils/src/test/java/com/yahoo/security/tls/authz/PeerAuthorizerTest.java39
6 files changed, 79 insertions, 59 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/AuthorizationResult.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/AuthorizationResult.java
deleted file mode 100644
index 6fa97e30d63..00000000000
--- a/security-utils/src/main/java/com/yahoo/security/tls/authz/AuthorizationResult.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.security.tls.authz;
-
-import java.util.Collections;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * @author bjorncs
- */
-public class AuthorizationResult {
- private final Set<String> matchedPolicies;
-
- public AuthorizationResult(Set<String> matchedPolicies) {
- this.matchedPolicies = Collections.unmodifiableSet(matchedPolicies);
- }
-
- public Set<String> matchedPolicies() {
- return matchedPolicies;
- }
-
- public boolean succeeded() {
- return matchedPolicies.size() > 0;
- }
-
- @Override
- public String toString() {
- return "AuthorizationResult{" +
- ", matchedPolicies=" + matchedPolicies +
- '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- AuthorizationResult that = (AuthorizationResult) o;
- return Objects.equals(matchedPolicies, that.matchedPolicies);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(matchedPolicies);
- }
-}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/ConnectionAuthContext.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/ConnectionAuthContext.java
new file mode 100644
index 00000000000..a5fb51da763
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/ConnectionAuthContext.java
@@ -0,0 +1,23 @@
+package com.yahoo.security.tls.authz;
+
+import com.yahoo.security.tls.policy.CapabilitySet;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * @author bjorncs
+ */
+public record ConnectionAuthContext(List<X509Certificate> peerCertificate,
+ CapabilitySet capabilities,
+ SortedSet<String> matchedPolicies) {
+
+ public ConnectionAuthContext {
+ matchedPolicies = new TreeSet<>(matchedPolicies);
+ }
+
+ public boolean succeeded() { return matchedPolicies.size() > 0; }
+
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java
index 8c4e87c1de2..353f704b136 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java
@@ -4,6 +4,7 @@ package com.yahoo.security.tls.authz;
import com.yahoo.security.SubjectAlternativeName;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.security.tls.policy.AuthorizedPeers;
+import com.yahoo.security.tls.policy.CapabilitySet;
import com.yahoo.security.tls.policy.PeerPolicy;
import com.yahoo.security.tls.policy.RequiredPeerCredential;
@@ -12,6 +13,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
import java.util.logging.Logger;
import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME;
@@ -34,17 +37,19 @@ public class PeerAuthorizer {
this.authorizedPeers = authorizedPeers;
}
- public AuthorizationResult authorizePeer(X509Certificate peerCertificate) {
- Set<String> matchedPolicies = new HashSet<>();
+ public ConnectionAuthContext authorizePeer(X509Certificate peerCertificate) {
+ SortedSet<String> matchedPolicies = new TreeSet<>();
+ Set<CapabilitySet> grantedCapabilities = new HashSet<>();
String cn = getCommonName(peerCertificate).orElse(null);
List<String> sans = getSubjectAlternativeNames(peerCertificate);
log.fine(() -> String.format("Subject info from x509 certificate: CN=[%s], 'SAN=%s", cn, sans));
for (PeerPolicy peerPolicy : authorizedPeers.peerPolicies()) {
if (matchesPolicy(peerPolicy, cn, sans)) {
matchedPolicies.add(peerPolicy.policyName());
+ grantedCapabilities.add(peerPolicy.capabilities());
}
}
- return new AuthorizationResult(matchedPolicies);
+ return new ConnectionAuthContext(List.of(peerCertificate), CapabilitySet.unionOf(grantedCapabilities), matchedPolicies);
}
private static boolean matchesPolicy(PeerPolicy peerPolicy, String cn, List<String> sans) {
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java
index e8d558205c4..925e21c63ff 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java
@@ -26,7 +26,7 @@ import java.util.logging.Logger;
// Note: Implementation assumes that provided X509ExtendedTrustManager will throw IllegalArgumentException when chain is empty or null
public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager {
- public static final String HANDSHAKE_SESSION_AUTHZ_RESULT_PROPERTY = "vespa.tls.authorization.result";
+ public static final String HANDSHAKE_SESSION_AUTH_CONTEXT_PROPERTY = "vespa.tls.auth.ctx";
private static final Logger log = Logger.getLogger(PeerAuthorizerTrustManager.class.getName());
@@ -98,18 +98,18 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager {
/**
* Note: The authorization result is only available during handshake. The underlying handshake session is removed once handshake is complete.
*/
- public static Optional<AuthorizationResult> getAuthorizationResult(SSLEngine sslEngine) {
+ public static Optional<ConnectionAuthContext> getAuthorizationResult(SSLEngine sslEngine) {
return Optional.ofNullable(sslEngine.getHandshakeSession())
- .flatMap(session -> Optional.ofNullable((AuthorizationResult) session.getValue(HANDSHAKE_SESSION_AUTHZ_RESULT_PROPERTY)));
+ .flatMap(session -> Optional.ofNullable((ConnectionAuthContext) session.getValue(HANDSHAKE_SESSION_AUTH_CONTEXT_PROPERTY)));
}
private void authorizePeer(X509Certificate certificate, String authType, boolean isVerifyingClient, SSLEngine sslEngine) throws CertificateException {
if (mode == AuthorizationMode.DISABLE) return;
log.fine(() -> "Verifying certificate: " + createInfoString(certificate, authType, isVerifyingClient));
- AuthorizationResult result = authorizer.authorizePeer(certificate);
+ ConnectionAuthContext result = authorizer.authorizePeer(certificate);
if (sslEngine != null) { // getHandshakeSession() will never return null in this context
- sslEngine.getHandshakeSession().putValue(HANDSHAKE_SESSION_AUTHZ_RESULT_PROPERTY, result);
+ sslEngine.getHandshakeSession().putValue(HANDSHAKE_SESSION_AUTH_CONTEXT_PROPERTY, result);
}
if (result.succeeded()) {
log.fine(() -> String.format("Verification result: %s", result));
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/CapabilitySet.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/CapabilitySet.java
index 44ff1eedfb0..46a07972fc3 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/policy/CapabilitySet.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/policy/CapabilitySet.java
@@ -3,10 +3,12 @@ package com.yahoo.security.tls.policy;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
@@ -56,6 +58,12 @@ public class CapabilitySet {
return new CapabilitySet(caps);
}
+ public static CapabilitySet unionOf(Collection<CapabilitySet> capSets) {
+ EnumSet<Capability> union = EnumSet.noneOf(Capability.class);
+ capSets.forEach(cs -> union.addAll(cs.caps));
+ return new CapabilitySet(union);
+ }
+
public static CapabilitySet from(EnumSet<Capability> caps) { return new CapabilitySet(EnumSet.copyOf(caps)); }
public static CapabilitySet from(Collection<Capability> caps) { return new CapabilitySet(EnumSet.copyOf(caps)); }
public static CapabilitySet from(Capability... caps) { return new CapabilitySet(EnumSet.copyOf(List.of(caps))); }
@@ -68,6 +76,8 @@ public class CapabilitySet {
return caps.stream().map(Capability::asString).collect(Collectors.toCollection(TreeSet::new));
}
+ public Set<Capability> asSet() { return Collections.unmodifiableSet(caps); }
+
@Override
public String toString() {
return "CapabilitySet{" +
diff --git a/security-utils/src/test/java/com/yahoo/security/tls/authz/PeerAuthorizerTest.java b/security-utils/src/test/java/com/yahoo/security/tls/authz/PeerAuthorizerTest.java
index 5a4ae1f4ff6..a2f27ba42bc 100644
--- a/security-utils/src/test/java/com/yahoo/security/tls/authz/PeerAuthorizerTest.java
+++ b/security-utils/src/test/java/com/yahoo/security/tls/authz/PeerAuthorizerTest.java
@@ -6,6 +6,8 @@ import com.yahoo.security.KeyUtils;
import com.yahoo.security.SubjectAlternativeName.Type;
import com.yahoo.security.X509CertificateBuilder;
import com.yahoo.security.tls.policy.AuthorizedPeers;
+import com.yahoo.security.tls.policy.Capability;
+import com.yahoo.security.tls.policy.CapabilitySet;
import com.yahoo.security.tls.policy.PeerPolicy;
import com.yahoo.security.tls.policy.RequiredPeerCredential;
import com.yahoo.security.tls.policy.RequiredPeerCredential.Field;
@@ -19,6 +21,8 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA;
import static com.yahoo.security.tls.policy.RequiredPeerCredential.Field.CN;
@@ -46,7 +50,7 @@ public class PeerAuthorizerTest {
RequiredPeerCredential sanRequirement = createRequiredCredential(SAN_DNS, "*.matching.san");
PeerAuthorizer authorizer = createPeerAuthorizer(createPolicy(POLICY_1, cnRequirement, sanRequirement));
- AuthorizationResult result = authorizer.authorizePeer(createCertificate("foo.matching.cn", asList("foo.matching.san", "foo.invalid.san"), emptyList()));
+ ConnectionAuthContext result = authorizer.authorizePeer(createCertificate("foo.matching.cn", asList("foo.matching.san", "foo.invalid.san"), emptyList()));
assertAuthorized(result);
assertThat(result.matchedPolicies()).containsOnly(POLICY_1);
@@ -64,7 +68,7 @@ public class PeerAuthorizerTest {
createPolicy(POLICY_1, cnRequirement, sanRequirement),
createPolicy(POLICY_2, cnRequirement, sanRequirement));
- AuthorizationResult result = peerAuthorizer
+ ConnectionAuthContext result = peerAuthorizer
.authorizePeer(createCertificate("foo.matching.cn", singletonList("foo.matching.san"), emptyList()));
assertAuthorized(result);
assertThat(result.matchedPolicies()).containsOnly(POLICY_1, POLICY_2);
@@ -76,7 +80,7 @@ public class PeerAuthorizerTest {
createPolicy(POLICY_1, createRequiredCredential(CN, "*.matching.cn")),
createPolicy(POLICY_2, createRequiredCredential(SAN_DNS, "*.matching.san")));
- AuthorizationResult result = peerAuthorizer.authorizePeer(createCertificate("foo.invalid.cn", singletonList("foo.matching.san"), emptyList()));
+ ConnectionAuthContext result = peerAuthorizer.authorizePeer(createCertificate("foo.invalid.cn", singletonList("foo.matching.san"), emptyList()));
assertAuthorized(result);
assertThat(result.matchedPolicies()).containsOnly(POLICY_2);
}
@@ -101,13 +105,28 @@ public class PeerAuthorizerTest {
RequiredPeerCredential sanUriRequirement = createRequiredCredential(SAN_URI, "myscheme://my/*/uri");
PeerAuthorizer authorizer = createPeerAuthorizer(createPolicy(POLICY_1, cnRequirement, sanUriRequirement));
- AuthorizationResult result = authorizer.authorizePeer(createCertificate("foo.matching.cn", singletonList("foo.irrelevant.san"), singletonList("myscheme://my/matching/uri")));
+ ConnectionAuthContext result = authorizer.authorizePeer(createCertificate("foo.matching.cn", singletonList("foo.irrelevant.san"), singletonList("myscheme://my/matching/uri")));
assertAuthorized(result);
assertThat(result.matchedPolicies()).containsOnly(POLICY_1);
assertUnauthorized(authorizer.authorizePeer(createCertificate("foo.matching.cn", emptyList(), singletonList("myscheme://my/nonmatching/url"))));
}
+ @Test
+ public void auth_context_contains_union_of_granted_capabilities_from_policies() {
+ RequiredPeerCredential cnRequirement = createRequiredCredential(CN, "*.matching.cn");
+ RequiredPeerCredential sanRequirement = createRequiredCredential(SAN_DNS, "*.matching.san");
+
+ PeerAuthorizer peerAuthorizer = createPeerAuthorizer(
+ createPolicy(POLICY_1, List.of(Capability.SLOBROK__API, Capability.CONTENT__DOCUMENT_API), List.of(cnRequirement)),
+ createPolicy(POLICY_2, List.of(Capability.SLOBROK__API, Capability.CONTENT__SEARCH_API), List.of(sanRequirement)));
+
+ var result = peerAuthorizer
+ .authorizePeer(createCertificate("foo.matching.cn", List.of("foo.matching.san"), List.of()));
+ assertAuthorized(result);
+ assertCapabiltiesGranted(result, Set.of(Capability.SLOBROK__API, Capability.CONTENT__DOCUMENT_API, Capability.CONTENT__SEARCH_API));
+ }
+
private static X509Certificate createCertificate(String subjectCn, List<String> sanDns, List<String> sanUri) {
X509CertificateBuilder builder =
X509CertificateBuilder.fromKeypair(
@@ -134,12 +153,20 @@ public class PeerAuthorizerTest {
return new PeerPolicy(name, asList(requiredCredentials));
}
- private static void assertAuthorized(AuthorizationResult result) {
+ private static PeerPolicy createPolicy(String name, List<Capability> caps, List<RequiredPeerCredential> creds) {
+ return new PeerPolicy(name, Optional.empty(), CapabilitySet.from(caps), creds);
+ }
+
+ private static void assertAuthorized(ConnectionAuthContext result) {
assertTrue(result.succeeded());
}
- private static void assertUnauthorized(AuthorizationResult result) {
+ private static void assertUnauthorized(ConnectionAuthContext result) {
assertFalse(result.succeeded());
}
+ private static void assertCapabiltiesGranted(ConnectionAuthContext ctx, Set<Capability> expected) {
+ assertThat(ctx.capabilities().asSet()).containsOnly(expected.toArray(new Capability[0]));
+ }
+
}