diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2022-07-19 14:30:27 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-19 14:30:27 +0200 |
commit | 46ba1b00aa19e937e2c257b34c23417adeef56eb (patch) | |
tree | 7e595f7ca0c17bc74b07c18472f4cd2d4f57c4d4 /security-utils/src/main/java/com/yahoo | |
parent | 8be6dd28753425126507b68c93a24607124871eb (diff) | |
parent | 529a26d7e1062a006196366454f1a047ca31202c (diff) |
Merge pull request #23496 from vespa-engine/bjorncs/capabilitiesv8.21.11
Bjorncs/capabilities
Diffstat (limited to 'security-utils/src/main/java/com/yahoo')
10 files changed, 111 insertions, 110 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/ConfigFileBasedTlsContext.java b/security-utils/src/main/java/com/yahoo/security/tls/ConfigFileBasedTlsContext.java index 6d4684666ea..cc664786734 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/ConfigFileBasedTlsContext.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/ConfigFileBasedTlsContext.java @@ -7,7 +7,6 @@ import com.yahoo.security.KeyUtils; import com.yahoo.security.SslContextBuilder; import com.yahoo.security.X509CertificateUtils; import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager; -import com.yahoo.security.tls.policy.AuthorizedPeers; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -19,7 +18,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStore; import java.time.Duration; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -112,9 +110,8 @@ public class ConfigFileBasedTlsContext implements TlsContext { PeerAuthentication peerAuthentication) { HostnameVerification hostnameVerification = options.isHostnameValidationDisabled() ? HostnameVerification.DISABLED : HostnameVerification.ENABLED; - PeerAuthorizerTrustManager authorizerTrustManager = options.getAuthorizedPeers() - .map(authorizedPeers -> new PeerAuthorizerTrustManager(authorizedPeers, mode, hostnameVerification, mutableTrustManager)) - .orElseGet(() -> new PeerAuthorizerTrustManager(new AuthorizedPeers(Collections.emptySet()), AuthorizationMode.DISABLE, hostnameVerification, mutableTrustManager)); + PeerAuthorizerTrustManager authorizerTrustManager = + new PeerAuthorizerTrustManager(options.getAuthorizedPeers(), mode, hostnameVerification, mutableTrustManager); SSLContext sslContext = new SslContextBuilder() .withKeyManager(mutableKeyManager) .withTrustManager(authorizerTrustManager) diff --git a/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java b/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java index a01283318b6..c2ee573dfc6 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java @@ -11,7 +11,6 @@ import javax.net.ssl.SSLParameters; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Set; import java.util.logging.Level; @@ -136,13 +135,9 @@ public class DefaultTlsContext implements TlsContext { if (!caCertificates.isEmpty()) { builder.withTrustStore(caCertificates); } - if (authorizedPeers != null) { - builder.withTrustManagerFactory(truststore -> new PeerAuthorizerTrustManager(authorizedPeers, mode, hostnameVerification, truststore)); - } else { - builder.withTrustManagerFactory(truststore -> new PeerAuthorizerTrustManager( - new AuthorizedPeers(Collections.emptySet()), AuthorizationMode.DISABLE, hostnameVerification, truststore)); - } - return builder.build(); + return builder.withTrustManagerFactory(truststore -> + new PeerAuthorizerTrustManager(authorizedPeers, mode, hostnameVerification, truststore)) + .build(); } } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java index 65c87bee0b9..a8802b7f0d3 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java @@ -55,9 +55,7 @@ public class TransportSecurityOptions { return Optional.ofNullable(caCertificatesFile); } - public Optional<AuthorizedPeers> getAuthorizedPeers() { - return Optional.ofNullable(authorizedPeers); - } + public AuthorizedPeers getAuthorizedPeers() { return authorizedPeers; } public List<String> getAcceptedCiphers() { return acceptedCiphers; } @@ -96,7 +94,7 @@ public class TransportSecurityOptions { private Path privateKeyFile; private Path certificatesFile; private Path caCertificatesFile; - private AuthorizedPeers authorizedPeers; + private AuthorizedPeers authorizedPeers = AuthorizedPeers.empty(); private List<String> acceptedCiphers = new ArrayList<>(); private boolean isHostnameValidationDisabled; private List<String> acceptedProtocols = new ArrayList<>(); 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..877ba4e74bd --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/ConnectionAuthContext.java @@ -0,0 +1,26 @@ +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.Set; + +/** + * @author bjorncs + */ +public record ConnectionAuthContext(List<X509Certificate> peerCertificateChain, + CapabilitySet capabilities, + Set<String> matchedPolicies) { + + public ConnectionAuthContext { + if (peerCertificateChain.isEmpty()) throw new IllegalArgumentException("Peer certificate chain is empty"); + peerCertificateChain = List.copyOf(peerCertificateChain); + matchedPolicies = Set.copyOf(matchedPolicies); + } + + public boolean authorized() { return !capabilities.hasNone(); } + + public X509Certificate peerCertificate() { return peerCertificateChain.get(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..a87c578f8c6 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; @@ -34,17 +35,26 @@ public class PeerAuthorizer { this.authorizedPeers = authorizedPeers; } - public AuthorizationResult authorizePeer(X509Certificate peerCertificate) { + + 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()); + } + X509Certificate cert = certChain.get(0); Set<String> matchedPolicies = new HashSet<>(); - String cn = getCommonName(peerCertificate).orElse(null); - List<String> sans = getSubjectAlternativeNames(peerCertificate); + Set<CapabilitySet> grantedCapabilities = new HashSet<>(); + String cn = getCommonName(cert).orElse(null); + List<String> sans = getSubjectAlternativeNames(cert); 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(certChain, 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..334216a2c19 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 @@ -6,6 +6,7 @@ import com.yahoo.security.tls.AuthorizationMode; import com.yahoo.security.tls.HostnameVerification; import com.yahoo.security.tls.TrustManagerUtils; import com.yahoo.security.tls.policy.AuthorizedPeers; +import com.yahoo.security.tls.policy.CapabilitySet; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; @@ -15,7 +16,9 @@ import java.net.Socket; import java.security.KeyStore; 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; /** @@ -26,7 +29,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()); @@ -55,39 +58,39 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { defaultTrustManager.checkClientTrusted(chain, authType); - authorizePeer(chain[0], authType, true, null); + authorizePeer(chain, authType, true, null); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { defaultTrustManager.checkServerTrusted(chain, authType); - authorizePeer(chain[0], authType, false, null); + authorizePeer(chain, authType, false, null); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { defaultTrustManager.checkClientTrusted(chain, authType, socket); - authorizePeer(chain[0], authType, true, null); + authorizePeer(chain, authType, true, null); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { overrideHostnameVerificationForClient(socket); defaultTrustManager.checkServerTrusted(chain, authType, socket); - authorizePeer(chain[0], authType, false, null); + authorizePeer(chain, authType, false, null); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { defaultTrustManager.checkClientTrusted(chain, authType, sslEngine); - authorizePeer(chain[0], authType, true, sslEngine); + authorizePeer(chain, authType, true, sslEngine); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { overrideHostnameVerificationForClient(sslEngine); defaultTrustManager.checkServerTrusted(chain, authType, sslEngine); - authorizePeer(chain[0], authType, false, sslEngine); + authorizePeer(chain, authType, false, sslEngine); } @Override @@ -98,23 +101,23 @@ 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> getConnectionAuthContext(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); + private void authorizePeer(X509Certificate[] certChain, String authType, boolean isVerifyingClient, SSLEngine sslEngine) throws CertificateException { + 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()); 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()) { + if (result.authorized()) { log.fine(() -> String.format("Verification result: %s", result)); } else { - String errorMessage = "Authorization failed: " + createInfoString(certificate, authType, isVerifyingClient); + String errorMessage = "Authorization failed: " + createInfoString(certChain[0], authType, isVerifyingClient); log.warning(errorMessage); if (mode == AuthorizationMode.ENFORCE) { throw new CertificateException(errorMessage); @@ -122,9 +125,10 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { } } - private static String createInfoString(X509Certificate certificate, String authType, boolean isVerifyingClient) { - return String.format("DN='%s', SANs=%s, authType='%s', isVerifyingClient='%b'", - certificate.getSubjectX500Principal(), X509CertificateUtils.getSubjectAlternativeNames(certificate), authType, isVerifyingClient); + private String createInfoString(X509Certificate certificate, String authType, boolean isVerifyingClient) { + return String.format("DN='%s', SANs=%s, authType='%s', isVerifyingClient='%b', mode=%s", + certificate.getSubjectX500Principal(), X509CertificateUtils.getSubjectAlternativeNames(certificate), + authType, isVerifyingClient, mode); } private void overrideHostnameVerificationForClient(SSLEngine engine) { diff --git a/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializer.java b/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializer.java index 195e200febb..fcd84056212 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializer.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializer.java @@ -144,28 +144,27 @@ public class TransportSecurityOptionsJsonSerializer { options.getCaCertificatesFile().ifPresent(value -> entity.files.caCertificatesFile = value.toString()); options.getCertificatesFile().ifPresent(value -> entity.files.certificatesFile = value.toString()); options.getPrivateKeyFile().ifPresent(value -> entity.files.privateKeyFile = value.toString()); - options.getAuthorizedPeers().ifPresent( authorizedPeers -> entity.authorizedPeers = - authorizedPeers.peerPolicies().stream() - // Makes tests stable - .sorted(Comparator.comparing(PeerPolicy::policyName)) - .map(peerPolicy -> { - AuthorizedPeer authorizedPeer = new AuthorizedPeer(); - authorizedPeer.name = peerPolicy.policyName(); - authorizedPeer.requiredCredentials = new ArrayList<>(); - authorizedPeer.description = peerPolicy.description().orElse(null); - CapabilitySet caps = peerPolicy.capabilities(); - if (!caps.hasAllCapabilities()) { - authorizedPeer.capabilities = List.copyOf(caps.toCapabilityNames()); - } - for (RequiredPeerCredential requiredPeerCredential : peerPolicy.requiredCredentials()) { - RequiredCredential requiredCredential = new RequiredCredential(); - requiredCredential.field = toField(requiredPeerCredential.field()); - requiredCredential.matchExpression = requiredPeerCredential.pattern().asString(); - authorizedPeer.requiredCredentials.add(requiredCredential); - } - return authorizedPeer; - }) - .collect(toList())); + entity.authorizedPeers = options.getAuthorizedPeers().peerPolicies().stream() + // Makes tests stable + .sorted(Comparator.comparing(PeerPolicy::policyName)) + .map(peerPolicy -> { + AuthorizedPeer authorizedPeer = new AuthorizedPeer(); + authorizedPeer.name = peerPolicy.policyName(); + authorizedPeer.requiredCredentials = new ArrayList<>(); + authorizedPeer.description = peerPolicy.description().orElse(null); + CapabilitySet caps = peerPolicy.capabilities(); + if (!caps.hasAll()) { + authorizedPeer.capabilities = List.copyOf(caps.toNames()); + } + for (RequiredPeerCredential requiredPeerCredential : peerPolicy.requiredCredentials()) { + RequiredCredential requiredCredential = new RequiredCredential(); + requiredCredential.field = toField(requiredPeerCredential.field()); + requiredCredential.matchExpression = requiredPeerCredential.pattern().asString(); + authorizedPeer.requiredCredentials.add(requiredCredential); + } + return authorizedPeer; + }) + .toList(); if (!options.getAcceptedCiphers().isEmpty()) { entity.acceptedCiphers = options.getAcceptedCiphers(); } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/AuthorizedPeers.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/AuthorizedPeers.java index 136022e2ed9..5e49a5b341c 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/AuthorizedPeers.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/policy/AuthorizedPeers.java @@ -8,10 +8,14 @@ import java.util.Set; */ public record AuthorizedPeers(Set<PeerPolicy> peerPolicies) { + private static final AuthorizedPeers EMPTY = new AuthorizedPeers(Set.of()); + public AuthorizedPeers { peerPolicies = verifyPeerPolicies(peerPolicies); } + public static AuthorizedPeers empty() { return EMPTY; } + private static Set<PeerPolicy> verifyPeerPolicies(Set<PeerPolicy> peerPolicies) { long distinctNames = peerPolicies.stream() .map(PeerPolicy::policyName) @@ -23,4 +27,6 @@ public record AuthorizedPeers(Set<PeerPolicy> peerPolicies) { return Set.copyOf(peerPolicies); } + public boolean isEmpty() { return peerPolicies.isEmpty(); } + } 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..50de98c621c 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,18 +58,27 @@ 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))); } public static CapabilitySet all() { return ALL_CAPABILITIES; } public static CapabilitySet none() { return NO_CAPABILITIES; } - public boolean hasAllCapabilities() { return this.caps.equals(ALL_CAPABILITIES.caps); } + public boolean hasAll() { return this.caps.equals(ALL_CAPABILITIES.caps); } + public boolean hasNone() { return this.caps.equals(NO_CAPABILITIES.caps); } - public SortedSet<String> toCapabilityNames() { + public SortedSet<String> toNames() { 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{" + |