diff options
Diffstat (limited to 'security-utils/src')
63 files changed, 875 insertions, 658 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/AutoReloadingX509KeyManager.java b/security-utils/src/main/java/com/yahoo/security/AutoReloadingX509KeyManager.java index 259d4b50d3f..243343240cb 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/AutoReloadingX509KeyManager.java +++ b/security-utils/src/main/java/com/yahoo/security/AutoReloadingX509KeyManager.java @@ -1,11 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; - -import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; -import com.yahoo.security.KeyUtils; -import com.yahoo.security.X509CertificateUtils; -import com.yahoo.security.X509CertificateWithKey; +package com.yahoo.security; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedKeyManager; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/KeyManagerUtils.java b/security-utils/src/main/java/com/yahoo/security/KeyManagerUtils.java index c9216d7273c..5611ef5162b 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/KeyManagerUtils.java +++ b/security-utils/src/main/java/com/yahoo/security/KeyManagerUtils.java @@ -1,8 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; - -import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; +package com.yahoo.security; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/MutableX509KeyManager.java b/security-utils/src/main/java/com/yahoo/security/MutableX509KeyManager.java index 6d784efc3e8..3ba6c8f2723 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/MutableX509KeyManager.java +++ b/security-utils/src/main/java/com/yahoo/security/MutableX509KeyManager.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; +package com.yahoo.security; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedKeyManager; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/MutableX509TrustManager.java b/security-utils/src/main/java/com/yahoo/security/MutableX509TrustManager.java index 6db43ef94a9..afbd0a6fa86 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/MutableX509TrustManager.java +++ b/security-utils/src/main/java/com/yahoo/security/MutableX509TrustManager.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; +package com.yahoo.security; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; diff --git a/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java b/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java index 9b999e056e0..d7353711a2a 100644 --- a/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java +++ b/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java @@ -21,7 +21,7 @@ import java.security.KeyPair; import java.util.ArrayList; import java.util.List; -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; +import static com.yahoo.security.SubjectAlternativeName.Type.DNS; /** * @author bjorncs @@ -49,7 +49,7 @@ public class Pkcs10CsrBuilder { } public Pkcs10CsrBuilder addSubjectAlternativeName(String dns) { - this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dns)); + this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS, dns)); return this; } diff --git a/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java b/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java index 5c16e4ed70d..d91c47e5eed 100644 --- a/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java +++ b/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java @@ -1,10 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import com.yahoo.security.tls.KeyManagerUtils; -import com.yahoo.security.tls.TlsContext; -import com.yahoo.security.tls.TrustManagerUtils; - import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; @@ -133,7 +129,7 @@ public class SslContextBuilder { public SSLContext build() { try { - SSLContext sslContext = SSLContext.getInstance(TlsContext.SSL_CONTEXT_VERSION); + SSLContext sslContext = SSLContext.getInstance("TLS"); X509ExtendedTrustManager trustManager = this.trustManager != null ? this.trustManager : trustManagerFactory.createTrustManager(trustStoreSupplier.get()); diff --git a/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java b/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java index 92dd41f7f88..c01de58987c 100644 --- a/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java +++ b/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java @@ -99,15 +99,15 @@ public class SubjectAlternativeName { } public enum Type { - OTHER_NAME(0), - RFC822_NAME(1), - DNS_NAME(2), - X400_ADDRESS(3), - DIRECTORY_NAME(4), - EDI_PARITY_NAME(5), - UNIFORM_RESOURCE_IDENTIFIER(6), - IP_ADDRESS(7), - REGISTERED_ID(8); + OTHER(0), + EMAIL(1), + DNS(2), + X400(3), + DIRECTORY(4), + EDI_PARITY(5), + URI(6), + IP(7), + REGISTERED(8); final int tag; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/TrustAllX509TrustManager.java b/security-utils/src/main/java/com/yahoo/security/TrustAllX509TrustManager.java index b0303620cf7..89a737b1ef7 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/TrustAllX509TrustManager.java +++ b/security-utils/src/main/java/com/yahoo/security/TrustAllX509TrustManager.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; +package com.yahoo.security; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/TrustManagerUtils.java b/security-utils/src/main/java/com/yahoo/security/TrustManagerUtils.java index 4172e337789..bb852ee89a3 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/TrustManagerUtils.java +++ b/security-utils/src/main/java/com/yahoo/security/TrustManagerUtils.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; +package com.yahoo.security; import com.yahoo.security.KeyStoreBuilder; import com.yahoo.security.KeyStoreType; diff --git a/security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java b/security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java index 6ec10a2f803..f59d34ebb10 100644 --- a/security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java +++ b/security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java @@ -28,7 +28,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; +import static com.yahoo.security.SubjectAlternativeName.Type.DNS; /** @@ -116,7 +116,7 @@ public class X509CertificateBuilder { } public X509CertificateBuilder addSubjectAlternativeName(String dnsName) { - this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dnsName)); + this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS, dnsName)); return this; } diff --git a/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java index f9f23ee1eb2..feb3b4df3e0 100644 --- a/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java +++ b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java @@ -32,10 +32,10 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.Random; import static com.yahoo.security.Extension.SUBJECT_ALTERNATIVE_NAMES; @@ -115,6 +115,12 @@ public class X509CertificateUtils { return getCommonNames(certificate.getSubjectX500Principal()); } + public static Optional<String> getSubjectCommonName(X509Certificate c) { + List<String> names = getSubjectCommonNames(c); + if (names.isEmpty()) return Optional.empty(); + return Optional.of(names.get(names.size() - 1)); + } + public static List<String> getIssuerCommonNames(X509Certificate certificate) { return getCommonNames(certificate.getIssuerX500Principal()); } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/AuthorizedPeers.java b/security-utils/src/main/java/com/yahoo/security/tls/AuthorizedPeers.java new file mode 100644 index 00000000000..9631ab32334 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/AuthorizedPeers.java @@ -0,0 +1,32 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import java.util.Set; + +/** + * @author bjorncs + */ +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) + .distinct() + .count(); + if (distinctNames != peerPolicies.size()) { + throw new IllegalArgumentException("'authorized-peers' contains entries with duplicate names"); + } + return Set.copyOf(peerPolicies); + } + + public boolean isEmpty() { return peerPolicies.isEmpty(); } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/Capability.java b/security-utils/src/main/java/com/yahoo/security/tls/Capability.java new file mode 100644 index 00000000000..0ae253985a6 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/Capability.java @@ -0,0 +1,32 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import java.util.Arrays; + +/** + * @author bjorncs + */ +public enum Capability { + CONTENT__CLUSTER_CONTROLLER__INTERNAL_STATE_API("vespa.content.cluster_controller.internal_state_api"), + CONTENT__DOCUMENT_API("vespa.content.document_api"), + CONTENT__METRICS_API("vespa.content.metrics_api"), + CONTENT__SEARCH_API("vespa.content.search_api"), + CONTENT__STATUS_PAGES("vespa.content.status_pages"), + CONTENT__STORAGE_API("vespa.content.storage_api"), + SLOBROK__API("vespa.slobrok.api"), + ; + + private final String name; + + Capability(String name) { this.name = name; } + + public String asString() { return name; } + + public static Capability fromName(String name) { + return Arrays.stream(values()) + .filter(c -> c.name.equals(name)) + .findAny().orElseThrow(() -> + new IllegalArgumentException("Cannot find predefined capability set with name '" + name + "'")); + } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/CapabilityMode.java b/security-utils/src/main/java/com/yahoo/security/tls/CapabilityMode.java new file mode 100644 index 00000000000..c2fa11ce7f7 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/CapabilityMode.java @@ -0,0 +1,26 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import java.util.Arrays; + +/** + * @author bjorncs + */ +public enum CapabilityMode { + DISABLE("disable"), LOG_ONLY("log_only"), ENFORCE("enforce"); + + private final String configValue; + + CapabilityMode(String configValue) { this.configValue = configValue; } + + public String configValue() { return configValue; } + + /** @return Default value when mode is not explicitly specified */ + public static CapabilityMode defaultValue() { return DISABLE; } + + public static CapabilityMode fromConfigValue(String configValue) { + return Arrays.stream(values()) + .filter(c -> c.configValue.equals(configValue)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown value: " + configValue)); + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/CapabilitySet.java b/security-utils/src/main/java/com/yahoo/security/tls/CapabilitySet.java new file mode 100644 index 00000000000..7e6c7f394cd --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/CapabilitySet.java @@ -0,0 +1,106 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +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; + +/** + * @author bjorncs + */ +public class CapabilitySet { + public enum Predefined { + CONTENT_NODE("vespa.content_node", + Capability.CONTENT__STORAGE_API, Capability.CONTENT__DOCUMENT_API, Capability.SLOBROK__API), + CONTAINER_NODE("vespa.container_node", + Capability.CONTENT__DOCUMENT_API, Capability.CONTENT__SEARCH_API, Capability.SLOBROK__API), + TELEMETRY("vespa.telemetry", + Capability.CONTENT__STATUS_PAGES, Capability.CONTENT__METRICS_API), + CLUSTER_CONTROLLER_NODE("vespa.cluster_controller_node", + Capability.CONTENT__CLUSTER_CONTROLLER__INTERNAL_STATE_API, Capability.SLOBROK__API), + CONFIG_SERVER("vespa.config_server"), + ; + + private final String name; + private final CapabilitySet set; + + Predefined(String name, Capability... caps) { + this.name = name; + this.set = caps.length == 0 ? CapabilitySet.none() : CapabilitySet.from(caps); } + + public static Optional<Predefined> fromName(String name) { + return Arrays.stream(values()).filter(p -> p.name.equals(name)).findAny(); + } + + public CapabilitySet capabilities() { return set; } + } + + private static final CapabilitySet ALL_CAPABILITIES = new CapabilitySet(EnumSet.allOf(Capability.class)); + private static final CapabilitySet NO_CAPABILITIES = new CapabilitySet(EnumSet.noneOf(Capability.class)); + + private final EnumSet<Capability> caps; + + private CapabilitySet(EnumSet<Capability> caps) { this.caps = caps; } + + public static CapabilitySet fromNames(Collection<String> names) { + EnumSet<Capability> caps = EnumSet.noneOf(Capability.class); + for (String name : names) { + Predefined predefined = Predefined.fromName(name).orElse(null); + if (predefined != null) caps.addAll(predefined.set.caps); + else caps.add(Capability.fromName(name)); + } + 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 hasAll() { return this.caps.equals(ALL_CAPABILITIES.caps); } + public boolean hasNone() { return this.caps.equals(NO_CAPABILITIES.caps); } + public boolean has(CapabilitySet caps) { return this.caps.containsAll(caps.caps); } + public boolean has(Collection<Capability> caps) { return this.caps.containsAll(caps); } + public boolean has(Capability... caps) { return this.caps.containsAll(List.of(caps)); } + + 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{" + + "caps=" + caps + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CapabilitySet that = (CapabilitySet) o; + return Objects.equals(caps, that.caps); + } + + @Override + public int hashCode() { + return Objects.hash(caps); + } +} 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..69635b92e74 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 @@ -4,10 +4,10 @@ package com.yahoo.security.tls; import com.yahoo.security.KeyStoreBuilder; import com.yahoo.security.KeyStoreType; import com.yahoo.security.KeyUtils; +import com.yahoo.security.MutableX509KeyManager; +import com.yahoo.security.MutableX509TrustManager; 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 +19,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 +111,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/ConnectionAuthContext.java b/security-utils/src/main/java/com/yahoo/security/tls/ConnectionAuthContext.java new file mode 100644 index 00000000000..f231e8429ce --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/ConnectionAuthContext.java @@ -0,0 +1,122 @@ +package com.yahoo.security.tls; + +import com.yahoo.security.SubjectAlternativeName; +import com.yahoo.security.X509CertificateUtils; + +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; + +import static com.yahoo.security.SubjectAlternativeName.Type.DNS; +import static com.yahoo.security.SubjectAlternativeName.Type.URI; +import static com.yahoo.security.tls.CapabilityMode.DISABLE; +import static com.yahoo.security.tls.CapabilityMode.LOG_ONLY; + +/** + * @author bjorncs + */ +public record ConnectionAuthContext(List<X509Certificate> peerCertificateChain, + CapabilitySet capabilities, + Set<String> matchedPolicies, + CapabilityMode capabilityMode) { + + private static final Logger log = Logger.getLogger(ConnectionAuthContext.class.getName()); + + public ConnectionAuthContext { + peerCertificateChain = List.copyOf(peerCertificateChain); + matchedPolicies = Set.copyOf(matchedPolicies); + } + + private ConnectionAuthContext(List<X509Certificate> certs, CapabilityMode capabilityMode) { + this(certs, CapabilitySet.all(), Set.of(), capabilityMode); + } + + public boolean authorized() { return !capabilities.hasNone(); } + + /** Throws checked exception to force caller to handle verification failed. */ + public void verifyCapabilities(CapabilitySet requiredCapabilities) throws MissingCapabilitiesException { + verifyCapabilities(requiredCapabilities, null, null, null); + } + + /** + * Throws checked exception to force caller to handle verification failed. + * Provided strings are used for improved logging only + * */ + public void verifyCapabilities(CapabilitySet requiredCapabilities, String action, String resource, String peer) + throws MissingCapabilitiesException { + if (capabilityMode == DISABLE) return; + boolean hasCapabilities = capabilities.has(requiredCapabilities); + if (!hasCapabilities) { + String msg = createPermissionDeniedErrorMessage(requiredCapabilities, action, resource, peer); + if (capabilityMode == LOG_ONLY) { + log.info(msg); + } else { + // Ideally log as warning, but we have no mechanism for de-duplicating repeated log spamming. + log.fine(msg); + throw new MissingCapabilitiesException(msg); + } + } + } + + String createPermissionDeniedErrorMessage( + CapabilitySet required, String action, String resource, String peer) { + StringBuilder b = new StringBuilder(); + if (capabilityMode == LOG_ONLY) b.append("Dry-run: "); + b.append("Permission denied"); + if (resource != null) { + b.append(" for '"); + if (action != null) { + b.append(action).append("' on '"); + } + b.append(resource).append("'"); + } + b.append(". Peer "); + if (peer != null) b.append("'").append(peer).append("' "); + return b.append("with ").append(peerCertificateString().orElse("<missing-certificate>")).append(". Requires capabilities ") + .append(required.toNames()).append(" but peer has ").append(capabilities.toNames()) + .append(".").toString(); + } + + public Optional<X509Certificate> peerCertificate() { + return peerCertificateChain.isEmpty() ? Optional.empty() : Optional.of(peerCertificateChain.get(0)); + } + + public Optional<String> peerCertificateString() { + X509Certificate cert = peerCertificate().orElse(null); + if (cert == null) return Optional.empty(); + StringBuilder b = new StringBuilder("["); + String cn = X509CertificateUtils.getSubjectCommonName(cert).orElse(null); + if (cn != null) { + b.append("CN='").append(cn).append("'"); + } + var sans = X509CertificateUtils.getSubjectAlternativeNames(cert); + List<String> dnsNames = sans.stream() + .filter(s -> s.getType() == DNS) + .map(SubjectAlternativeName::getValue) + .toList(); + if (!dnsNames.isEmpty()) { + if (cn != null) b.append(", "); + b.append("SAN_DNS=").append(dnsNames); + } + List<String> uris = sans.stream() + .filter(s -> s.getType() == URI) + .map(SubjectAlternativeName::getValue) + .toList(); + if (!uris.isEmpty()) { + if (cn != null || !dnsNames.isEmpty()) b.append(", "); + b.append("SAN_URI=").append(uris); + } + return Optional.of(b.append("]").toString()); + } + + /** Construct instance with all capabilities */ + public static ConnectionAuthContext defaultAllCapabilities() { return new ConnectionAuthContext(List.of(), DISABLE); } + + /** Construct instance with all capabilities */ + public static ConnectionAuthContext defaultAllCapabilities(List<X509Certificate> certs) { + return new ConnectionAuthContext(certs, DISABLE); + } + +} 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..88e4f409260 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 @@ -2,8 +2,6 @@ package com.yahoo.security.tls; import com.yahoo.security.SslContextBuilder; -import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager; -import com.yahoo.security.tls.policy.AuthorizedPeers; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -11,7 +9,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 +133,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/policy/GlobPattern.java b/security-utils/src/main/java/com/yahoo/security/tls/GlobPattern.java index 46a38a77844..c945e48a361 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/GlobPattern.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/GlobPattern.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.policy; +package com.yahoo.security.tls; import java.util.Arrays; import java.util.Objects; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/HostGlobPattern.java b/security-utils/src/main/java/com/yahoo/security/tls/HostGlobPattern.java index cb9ba13cae4..7e2c40182f0 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/HostGlobPattern.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/HostGlobPattern.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.policy; +package com.yahoo.security.tls; import java.util.Objects; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/MissingCapabilitiesException.java b/security-utils/src/main/java/com/yahoo/security/tls/MissingCapabilitiesException.java new file mode 100644 index 00000000000..1c3ad9444e4 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/MissingCapabilitiesException.java @@ -0,0 +1,13 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +/** + * Intentionally checked to force caller to handle missing permissions at call site. + * + * @author bjorncs + */ +public class MissingCapabilitiesException extends Exception { + + public MissingCapabilitiesException(String message) { super(message); } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizationFailedException.java b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizationFailedException.java new file mode 100644 index 00000000000..02dbf3bb8e7 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizationFailedException.java @@ -0,0 +1,23 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * @author bjorncs + */ +public class PeerAuthorizationFailedException extends CertificateException { + private final List<X509Certificate> certChain; + + public PeerAuthorizationFailedException(String msg, List<X509Certificate> certChain) { + super(msg); + this.certChain = certChain; + } + + public PeerAuthorizationFailedException(String msg) { this(msg, List.of()); } + + public List<X509Certificate> peerCertificateChain() { return certChain; } +} + 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/PeerAuthorizer.java index 40f3817c5f9..951b5c57c9e 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizer.java @@ -1,23 +1,18 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.authz; +package com.yahoo.security.tls; import com.yahoo.security.SubjectAlternativeName; import com.yahoo.security.X509CertificateUtils; -import com.yahoo.security.tls.policy.AuthorizedPeers; -import com.yahoo.security.tls.policy.PeerPolicy; -import com.yahoo.security.tls.policy.RequiredPeerCredential; -import com.yahoo.security.tls.policy.Role; import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.logging.Logger; -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; -import static com.yahoo.security.SubjectAlternativeName.Type.IP_ADDRESS; -import static com.yahoo.security.SubjectAlternativeName.Type.UNIFORM_RESOURCE_IDENTIFIER; +import static com.yahoo.security.SubjectAlternativeName.Type.DNS; +import static com.yahoo.security.SubjectAlternativeName.Type.IP; +import static com.yahoo.security.SubjectAlternativeName.Type.URI; import static java.util.stream.Collectors.toList; /** @@ -35,19 +30,27 @@ public class PeerAuthorizer { this.authorizedPeers = authorizedPeers; } - public AuthorizationResult authorizePeer(X509Certificate peerCertificate) { - Set<Role> assumedRoles = new HashSet<>(); + + public ConnectionAuthContext authorizePeer(X509Certificate cert) { return authorizePeer(List.of(cert)); } + + public ConnectionAuthContext authorizePeer(List<X509Certificate> certChain) { + if (authorizedPeers.isEmpty()) return ConnectionAuthContext.defaultAllCapabilities(certChain); + 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 = X509CertificateUtils.getSubjectCommonName(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)) { - assumedRoles.addAll(peerPolicy.assumedRoles()); matchedPolicies.add(peerPolicy.policyName()); + grantedCapabilities.add(peerPolicy.capabilities()); } } - return new AuthorizationResult(assumedRoles, matchedPolicies); + // TODO Pass this through constructor + CapabilityMode capabilityMode = TransportSecurityUtils.getCapabilityMode(); + return new ConnectionAuthContext( + certChain, CapabilitySet.unionOf(grantedCapabilities), matchedPolicies, capabilityMode); } private static boolean matchesPolicy(PeerPolicy peerPolicy, String cn, List<String> sans) { @@ -68,14 +71,9 @@ public class PeerAuthorizer { } } - private static Optional<String> getCommonName(X509Certificate peerCertificate) { - return X509CertificateUtils.getSubjectCommonNames(peerCertificate).stream() - .findFirst(); - } - private static List<String> getSubjectAlternativeNames(X509Certificate peerCertificate) { return X509CertificateUtils.getSubjectAlternativeNames(peerCertificate).stream() - .filter(san -> san.getType() == DNS_NAME || san.getType() == IP_ADDRESS || san.getType() == UNIFORM_RESOURCE_IDENTIFIER) + .filter(san -> san.getType() == DNS || san.getType() == IP || san.getType() == URI) .map(SubjectAlternativeName::getValue) .collect(toList()); } 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/PeerAuthorizerTrustManager.java index e8d558205c4..c3dcdf1dc9e 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java @@ -1,32 +1,33 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.authz; +package com.yahoo.security.tls; +import com.yahoo.security.TrustManagerUtils; import com.yahoo.security.X509CertificateUtils; -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 javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.X509ExtendedTrustManager; import java.net.Socket; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.Optional; +import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; /** * A {@link X509ExtendedTrustManager} that performs additional certificate verification through {@link PeerAuthorizer}. * + * Implementation assumes that provided {@link X509ExtendedTrustManager} will throw {@link IllegalArgumentException} + * when chain is empty or null. + * * @author bjorncs */ -// Note: Implementation assumes that provided X509ExtendedTrustManager will throw IllegalArgumentException when chain is empty or null -public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { +class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { - public static final String HANDSHAKE_SESSION_AUTHZ_RESULT_PROPERTY = "vespa.tls.authorization.result"; + static final String AUTH_CONTEXT_PROPERTY = "vespa.tls.auth.ctx"; private static final Logger log = Logger.getLogger(PeerAuthorizerTrustManager.class.getName()); @@ -35,59 +36,55 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { private final AuthorizationMode mode; private final HostnameVerification hostnameVerification; - public PeerAuthorizerTrustManager(AuthorizedPeers authorizedPeers, - AuthorizationMode mode, - HostnameVerification hostnameVerification, - X509ExtendedTrustManager defaultTrustManager) { + PeerAuthorizerTrustManager(AuthorizedPeers authorizedPeers, AuthorizationMode mode, + HostnameVerification hostnameVerification, X509ExtendedTrustManager defaultTrustManager) { this.authorizer = new PeerAuthorizer(authorizedPeers); this.mode = mode; this.hostnameVerification = hostnameVerification; this.defaultTrustManager = defaultTrustManager; } - public PeerAuthorizerTrustManager(AuthorizedPeers authorizedPeers, - AuthorizationMode mode, - HostnameVerification hostnameVerification, - KeyStore truststore) { + PeerAuthorizerTrustManager(AuthorizedPeers authorizedPeers, AuthorizationMode mode, + HostnameVerification hostnameVerification, KeyStore truststore) { this(authorizedPeers, mode, hostnameVerification, TrustManagerUtils.createDefaultX509TrustManager(truststore)); } @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, ((SSLSocket)socket).getHandshakeSession()); } @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, ((SSLSocket)socket).getHandshakeSession()); } @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.getHandshakeSession()); } @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.getHandshakeSession()); } @Override @@ -95,36 +92,33 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { return defaultTrustManager.getAcceptedIssuers(); } - /** - * 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) { - return Optional.ofNullable(sslEngine.getHandshakeSession()) - .flatMap(session -> Optional.ofNullable((AuthorizationResult) session.getValue(HANDSHAKE_SESSION_AUTHZ_RESULT_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); - if (sslEngine != null) { // getHandshakeSession() will never return null in this context - sslEngine.getHandshakeSession().putValue(HANDSHAKE_SESSION_AUTHZ_RESULT_PROPERTY, result); + private void authorizePeer(X509Certificate[] certChain, String authType, boolean isVerifyingClient, + SSLSession handshakeSessionOrNull) throws PeerAuthorizationFailedException { + log.fine(() -> "Verifying certificate: " + createInfoString(certChain[0], authType, isVerifyingClient)); + ConnectionAuthContext result = mode != AuthorizationMode.DISABLE + ? authorizer.authorizePeer(List.of(certChain)) + : ConnectionAuthContext.defaultAllCapabilities(List.of(certChain)); + if (handshakeSessionOrNull != null) { + handshakeSessionOrNull.putValue(AUTH_CONTEXT_PROPERTY, result); + } else { + log.log(Level.FINE, + () -> "Warning: unable to provide ConnectionAuthContext as no SSLSession is available"); } - 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); + throw new PeerAuthorizationFailedException(errorMessage, List.of(certChain)); } } } - 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/PeerPolicy.java b/security-utils/src/main/java/com/yahoo/security/tls/PeerPolicy.java new file mode 100644 index 00000000000..ea3d4cfe002 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/PeerPolicy.java @@ -0,0 +1,24 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import java.util.List; +import java.util.Optional; + +/** + * @author bjorncs + */ +public record PeerPolicy(String policyName, Optional<String> description, CapabilitySet capabilities, + List<RequiredPeerCredential> requiredCredentials) { + + public PeerPolicy { + requiredCredentials = List.copyOf(requiredCredentials); + } + + public PeerPolicy(String policyName, List<RequiredPeerCredential> requiredCredentials) { + this(policyName, Optional.empty(), CapabilitySet.all(), requiredCredentials); + } + + public PeerPolicy(String policyName, String description, List<RequiredPeerCredential> requiredCredentials) { + this(policyName, Optional.ofNullable(description), CapabilitySet.all(), requiredCredentials); + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/RequiredPeerCredential.java b/security-utils/src/main/java/com/yahoo/security/tls/RequiredPeerCredential.java index 4c96a2935f8..9a18da9dffd 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/RequiredPeerCredential.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/RequiredPeerCredential.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.policy; +package com.yahoo.security.tls; import java.util.Objects; 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..4397f27ebb7 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 @@ -1,9 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security.tls; -import com.yahoo.security.tls.json.TransportSecurityOptionsJsonSerializer; -import com.yahoo.security.tls.policy.AuthorizedPeers; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -55,9 +52,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 +91,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/json/TransportSecurityOptionsEntity.java b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptionsEntity.java index 12ac75aae80..f1799a64a57 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsEntity.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptionsEntity.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.json; +package com.yahoo.security.tls; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -36,7 +36,7 @@ class TransportSecurityOptionsEntity { @JsonProperty("required-credentials") List<RequiredCredential> requiredCredentials; @JsonProperty("name") String name; @JsonProperty("description") @JsonInclude(NON_NULL) String description; - @JsonProperty("roles") @JsonInclude(NON_EMPTY) List<String> roles; + @JsonProperty("capabilities") @JsonInclude(NON_EMPTY) List<String> capabilities; } @JsonIgnoreProperties(ignoreUnknown = true) 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/TransportSecurityOptionsJsonSerializer.java index 516a0c83d37..0349d4085db 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializer.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptionsJsonSerializer.java @@ -1,16 +1,11 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.json; +package com.yahoo.security.tls; import com.fasterxml.jackson.databind.ObjectMapper; -import com.yahoo.security.tls.TransportSecurityOptions; -import com.yahoo.security.tls.json.TransportSecurityOptionsEntity.AuthorizedPeer; -import com.yahoo.security.tls.json.TransportSecurityOptionsEntity.CredentialField; -import com.yahoo.security.tls.json.TransportSecurityOptionsEntity.Files; -import com.yahoo.security.tls.json.TransportSecurityOptionsEntity.RequiredCredential; -import com.yahoo.security.tls.policy.AuthorizedPeers; -import com.yahoo.security.tls.policy.PeerPolicy; -import com.yahoo.security.tls.policy.RequiredPeerCredential; -import com.yahoo.security.tls.policy.Role; +import com.yahoo.security.tls.TransportSecurityOptionsEntity.AuthorizedPeer; +import com.yahoo.security.tls.TransportSecurityOptionsEntity.CredentialField; +import com.yahoo.security.tls.TransportSecurityOptionsEntity.Files; +import com.yahoo.security.tls.TransportSecurityOptionsEntity.RequiredCredential; import java.io.IOException; import java.io.InputStream; @@ -18,9 +13,9 @@ import java.io.OutputStream; import java.io.UncheckedIOException; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.Set; import static java.util.stream.Collectors.toList; @@ -29,11 +24,11 @@ import static java.util.stream.Collectors.toSet; /** * @author bjorncs */ -public class TransportSecurityOptionsJsonSerializer { +class TransportSecurityOptionsJsonSerializer { private static final ObjectMapper mapper = new ObjectMapper(); - public TransportSecurityOptions deserialize(InputStream in) { + TransportSecurityOptions deserialize(InputStream in) { try { TransportSecurityOptionsEntity entity = mapper.readValue(in, TransportSecurityOptionsEntity.class); return toTransportSecurityOptions(entity); @@ -42,7 +37,7 @@ public class TransportSecurityOptionsJsonSerializer { } } - public void serialize(OutputStream out, TransportSecurityOptions options) { + void serialize(OutputStream out, TransportSecurityOptions options) { try { mapper.writerWithDefaultPrettyPrinter().writeValue(out, toTransportSecurityOptionsEntity(options)); } catch (IOException e) { @@ -101,14 +96,16 @@ public class TransportSecurityOptionsJsonSerializer { if (authorizedPeer.requiredCredentials == null) { throw missingFieldException("required-credentials"); } - return new PeerPolicy(authorizedPeer.name, authorizedPeer.description, toRoles(authorizedPeer.roles), toRequestPeerCredentials(authorizedPeer.requiredCredentials)); + return new PeerPolicy(authorizedPeer.name, Optional.ofNullable(authorizedPeer.description), + toCapabilities(authorizedPeer.capabilities), toRequestPeerCredentials(authorizedPeer.requiredCredentials)); } - private static Set<Role> toRoles(List<String> roles) { - if (roles == null) return Collections.emptySet(); - return roles.stream() - .map(Role::new) - .collect(toSet()); + private static CapabilitySet toCapabilities(List<String> capabilities) { + if (capabilities == null) return CapabilitySet.all(); + if (capabilities.isEmpty()) + throw new IllegalArgumentException("\"capabilities\" array must either be not present " + + "(implies all capabilities) or contain at least one capability name"); + return CapabilitySet.fromNames(capabilities); } private static List<RequiredPeerCredential> toRequestPeerCredentials(List<RequiredCredential> requiredCredentials) { @@ -142,29 +139,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); - for (RequiredPeerCredential requiredPeerCredential : peerPolicy.requiredCredentials()) { - RequiredCredential requiredCredential = new RequiredCredential(); - requiredCredential.field = toField(requiredPeerCredential.field()); - requiredCredential.matchExpression = requiredPeerCredential.pattern().asString(); - authorizedPeer.requiredCredentials.add(requiredCredential); - } - if (!peerPolicy.assumedRoles().isEmpty()) { - authorizedPeer.roles = new ArrayList<>(); - peerPolicy.assumedRoles().forEach(role -> authorizedPeer.roles.add(role.name())); - } - - 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/TransportSecurityUtils.java b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java index cbd3857d2d5..ae6cef65156 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java @@ -1,6 +1,9 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security.tls; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; @@ -18,6 +21,7 @@ public class TransportSecurityUtils { public static final String CONFIG_FILE_ENVIRONMENT_VARIABLE = "VESPA_TLS_CONFIG_FILE"; public static final String INSECURE_MIXED_MODE_ENVIRONMENT_VARIABLE = "VESPA_TLS_INSECURE_MIXED_MODE"; public static final String INSECURE_AUTHORIZATION_MODE_ENVIRONMENT_VARIABLE = "VESPA_TLS_INSECURE_AUTHORIZATION_MODE"; + public static final String CAPABILITIES_ENV_VAR = "VESPA_TLS_CAPABILITIES_ENFORCEMENT_MODE"; private TransportSecurityUtils() {} @@ -49,6 +53,12 @@ public class TransportSecurityUtils { .orElse(AuthorizationMode.defaultValue()); } + public static CapabilityMode getCapabilityMode() { + return getEnvironmentVariable(System.getenv(), CAPABILITIES_ENV_VAR) + .map(CapabilityMode::fromConfigValue) + .orElse(CapabilityMode.defaultValue()); + } + public static Optional<Path> getConfigFile() { return getConfigFile(System.getenv()); } @@ -80,6 +90,24 @@ public class TransportSecurityUtils { } } + /** + * @return {@link ConnectionAuthContext} instance if {@link SSLEngine} was constructed by a {@link TlsContext}. + * Only available after TLS handshake is completed. + */ + public static Optional<ConnectionAuthContext> getConnectionAuthContext(SSLSession s) { + return Optional.ofNullable((ConnectionAuthContext) s.getValue(PeerAuthorizerTrustManager.AUTH_CONTEXT_PROPERTY)); + } + + /** @see #getConnectionAuthContext(SSLSession) */ + public static Optional<ConnectionAuthContext> getConnectionAuthContext(SSLEngine e) { + return getConnectionAuthContext(e.getSession()); + } + + /** @see #getConnectionAuthContext(SSLSession) */ + public static Optional<ConnectionAuthContext> getConnectionAuthContext(SSLSocket s) { + return getConnectionAuthContext(s.getSession()); + } + private static Optional<String> getEnvironmentVariable(Map<String, String> environmentVariables, String variableName) { return Optional.ofNullable(environmentVariables.get(variableName)) .filter(var -> !var.isEmpty()); diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/UriGlobPattern.java b/security-utils/src/main/java/com/yahoo/security/tls/UriGlobPattern.java index b2cc0688bb9..18d18a5ab3c 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/UriGlobPattern.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/UriGlobPattern.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.policy; +package com.yahoo.security.tls; import java.util.Objects; 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 963bb469d6e..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/authz/AuthorizationResult.java +++ /dev/null @@ -1,55 +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 com.yahoo.security.tls.policy.Role; - -import java.util.Collections; -import java.util.Objects; -import java.util.Set; - -/** - * @author bjorncs - */ -public class AuthorizationResult { - private final Set<Role> assumedRoles; - private final Set<String> matchedPolicies; - - public AuthorizationResult(Set<Role> assumedRoles, Set<String> matchedPolicies) { - this.assumedRoles = Collections.unmodifiableSet(assumedRoles); - this.matchedPolicies = Collections.unmodifiableSet(matchedPolicies); - } - - public Set<Role> assumedRoles() { - return assumedRoles; - } - - public Set<String> matchedPolicies() { - return matchedPolicies; - } - - public boolean succeeded() { - return matchedPolicies.size() > 0; - } - - @Override - public String toString() { - return "AuthorizationResult{" + - "assumedRoles=" + assumedRoles + - ", 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(assumedRoles, that.assumedRoles) && - Objects.equals(matchedPolicies, that.matchedPolicies); - } - - @Override - public int hashCode() { - return Objects.hash(assumedRoles, matchedPolicies); - } -} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/package-info.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/package-info.java deleted file mode 100644 index 5066026757d..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/authz/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ -@ExportPackage -package com.yahoo.security.tls.authz; - -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/security-utils/src/main/java/com/yahoo/security/tls/https/package-info.java b/security-utils/src/main/java/com/yahoo/security/tls/https/package-info.java deleted file mode 100644 index 91a1672e19f..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/https/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ -@ExportPackage -package com.yahoo.security.tls.https; - -import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/security-utils/src/main/java/com/yahoo/security/tls/json/package-info.java b/security-utils/src/main/java/com/yahoo/security/tls/json/package-info.java deleted file mode 100644 index be7ec33bf04..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/json/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ -@ExportPackage -package com.yahoo.security.tls.json; - -import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file 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 deleted file mode 100644 index 66621224906..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/AuthorizedPeers.java +++ /dev/null @@ -1,53 +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.policy; - -import java.util.Collections; -import java.util.Objects; -import java.util.Set; - -/** - * @author bjorncs - */ -public class AuthorizedPeers { - - private final Set<PeerPolicy> peerPolicies; - - public AuthorizedPeers(Set<PeerPolicy> peerPolicies) { - this.peerPolicies = verifyPeerPolicies(peerPolicies); - } - - private Set<PeerPolicy> verifyPeerPolicies(Set<PeerPolicy> peerPolicies) { - long distinctNames = peerPolicies.stream() - .map(PeerPolicy::policyName) - .distinct() - .count(); - if (distinctNames != peerPolicies.size()) { - throw new IllegalArgumentException("'authorized-peers' contains entries with duplicate names"); - } - return Collections.unmodifiableSet(peerPolicies); - } - - public Set<PeerPolicy> peerPolicies() { - return peerPolicies; - } - - @Override - public String toString() { - return "AuthorizedPeers{" + - "peerPolicies=" + peerPolicies + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AuthorizedPeers that = (AuthorizedPeers) o; - return Objects.equals(peerPolicies, that.peerPolicies); - } - - @Override - public int hashCode() { - return Objects.hash(peerPolicies); - } -} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/PeerPolicy.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/PeerPolicy.java deleted file mode 100644 index 4783889ec62..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/PeerPolicy.java +++ /dev/null @@ -1,71 +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.policy; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -/** - * @author bjorncs - */ -public class PeerPolicy { - - private final String policyName; - private final String description; - private final Set<Role> assumedRoles; - private final List<RequiredPeerCredential> requiredCredentials; - - public PeerPolicy(String policyName, Set<Role> assumedRoles, List<RequiredPeerCredential> requiredCredentials) { - this(policyName, null, assumedRoles, requiredCredentials); - } - - public PeerPolicy( - String policyName, String description, Set<Role> assumedRoles, List<RequiredPeerCredential> requiredCredentials) { - this.policyName = policyName; - this.description = description; - this.assumedRoles = assumedRoles; - this.requiredCredentials = Collections.unmodifiableList(requiredCredentials); - } - - public String policyName() { - return policyName; - } - - public Optional<String> description() { return Optional.ofNullable(description); } - - public Set<Role> assumedRoles() { - return assumedRoles; - } - - public List<RequiredPeerCredential> requiredCredentials() { - return requiredCredentials; - } - - @Override - public String toString() { - return "PeerPolicy{" + - "policyName='" + policyName + '\'' + - ", description='" + description + '\'' + - ", assumedRoles=" + assumedRoles + - ", requiredCredentials=" + requiredCredentials + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PeerPolicy that = (PeerPolicy) o; - return Objects.equals(policyName, that.policyName) && - Objects.equals(description, that.description) && - Objects.equals(assumedRoles, that.assumedRoles) && - Objects.equals(requiredCredentials, that.requiredCredentials); - } - - @Override - public int hashCode() { - return Objects.hash(policyName, description, assumedRoles, requiredCredentials); - } -} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/Role.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/Role.java deleted file mode 100644 index b35bf328847..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/Role.java +++ /dev/null @@ -1,40 +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.policy; - -import java.util.Objects; - -/** - * @author bjorncs - */ -public class Role { - - private final String name; - - public Role(String name) { - this.name = name; - } - - public String name() { - return name; - } - - @Override - public String toString() { - return "Role{" + - "name='" + name + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Role role = (Role) o; - return Objects.equals(name, role.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } -} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/package-info.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/package-info.java deleted file mode 100644 index 61ce90654f8..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/policy/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ -@ExportPackage -package com.yahoo.security.tls.policy; - -import com.yahoo.osgi.annotation.ExportPackage;
\ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/tls/AutoReloadingX509KeyManagerTest.java b/security-utils/src/test/java/com/yahoo/security/AutoReloadingX509KeyManagerTest.java index f5bc2a9c84e..c335acc12be 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/AutoReloadingX509KeyManagerTest.java +++ b/security-utils/src/test/java/com/yahoo/security/AutoReloadingX509KeyManagerTest.java @@ -1,18 +1,20 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; +package com.yahoo.security; +import com.yahoo.security.AutoReloadingX509KeyManager; import com.yahoo.security.KeyAlgorithm; import com.yahoo.security.KeyUtils; import com.yahoo.security.SignatureAlgorithm; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.security.X509CertificateUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import javax.security.auth.x500.X500Principal; + +import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; @@ -35,16 +37,16 @@ import static org.mockito.Mockito.verify; public class AutoReloadingX509KeyManagerTest { private static final X500Principal SUBJECT = new X500Principal("CN=dummy"); - @Rule - public TemporaryFolder tempDirectory = new TemporaryFolder(); + @TempDir + public File tempDirectory; @Test - public void crypto_material_is_reloaded_when_scheduler_task_is_executed() throws IOException { + void crypto_material_is_reloaded_when_scheduler_task_is_executed() throws IOException { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); - Path privateKeyFile = tempDirectory.newFile().toPath(); + Path privateKeyFile = File.createTempFile("junit", null, tempDirectory).toPath(); Files.write(privateKeyFile, KeyUtils.toPem(keyPair.getPrivate()).getBytes()); - Path certificateFile = tempDirectory.newFile().toPath(); + Path certificateFile = File.createTempFile("junit", null, tempDirectory).toPath(); BigInteger serialNumberInitialCertificate = BigInteger.ONE; X509Certificate initialCertificate = generateCertificate(keyPair, serialNumberInitialCertificate); Files.write(certificateFile, X509CertificateUtils.toPem(initialCertificate).getBytes()); diff --git a/security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java index 0d3924063f3..d9ca9731065 100644 --- a/security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java +++ b/security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java @@ -1,10 +1,10 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import java.io.File; import java.nio.file.Path; import java.security.KeyPair; import java.security.cert.X509Certificate; @@ -20,11 +20,11 @@ public class KeyStoreBuilderTest { private static final char[] PASSWORD = new char[0]; - @Rule - public TemporaryFolder tempDirectory = new TemporaryFolder(); + @TempDir + public File tempDirectory; @Test - public void can_create_jks_keystore_from_privatekey_and_certificate() throws Exception { + void can_create_jks_keystore_from_privatekey_and_certificate() throws Exception { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); X509Certificate certificate = createCertificate(keyPair); KeyStoreBuilder.withType(KeyStoreType.JKS) @@ -33,8 +33,8 @@ public class KeyStoreBuilderTest { } @Test - public void can_build_jks_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); + void can_build_jks_keystore_from_file() throws Exception { + Path keystoreFile = File.createTempFile("junit", null, tempDirectory).toPath(); createKeystoreFile(keystoreFile, KeyStoreType.JKS, PASSWORD); KeyStoreBuilder.withType(KeyStoreType.JKS) @@ -43,8 +43,8 @@ public class KeyStoreBuilderTest { } @Test - public void can_build_pcks12_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); + void can_build_pcks12_keystore_from_file() throws Exception { + Path keystoreFile = File.createTempFile("junit", null, tempDirectory).toPath(); createKeystoreFile(keystoreFile, KeyStoreType.PKCS12, PASSWORD); KeyStoreBuilder.withType(KeyStoreType.PKCS12) diff --git a/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java index f6f48d8b1b8..afaa25ce606 100644 --- a/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java +++ b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java @@ -1,15 +1,15 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author bjorncs @@ -17,41 +17,41 @@ import static org.junit.Assert.assertTrue; public class KeyUtilsTest { @Test - public void can_extract_public_key_from_rsa_private() { + void can_extract_public_key_from_rsa_private() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); PublicKey publicKey = KeyUtils.extractPublicKey(keyPair.getPrivate()); assertNotNull(publicKey); } @Test - public void can_extract_public_key_from_ecdsa_private() { + void can_extract_public_key_from_ecdsa_private() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); PublicKey publicKey = KeyUtils.extractPublicKey(keyPair.getPrivate()); assertNotNull(publicKey); } @Test - public void can_serialize_and_deserialize_rsa_privatekey_using_pkcs1_pem_format() { + void can_serialize_and_deserialize_rsa_privatekey_using_pkcs1_pem_format() { testPrivateKeySerialization(KeyAlgorithm.RSA, KeyFormat.PKCS1, "RSA PRIVATE KEY"); } @Test - public void can_serialize_and_deserialize_rsa_privatekey_using_pkcs8_pem_format() { + void can_serialize_and_deserialize_rsa_privatekey_using_pkcs8_pem_format() { testPrivateKeySerialization(KeyAlgorithm.RSA, KeyFormat.PKCS8, "PRIVATE KEY"); } @Test - public void can_serialize_and_deserialize_ec_privatekey_using_pkcs1_pem_format() { + void can_serialize_and_deserialize_ec_privatekey_using_pkcs1_pem_format() { testPrivateKeySerialization(KeyAlgorithm.EC, KeyFormat.PKCS1, "EC PRIVATE KEY"); } @Test - public void can_serialize_and_deserialize_ec_privatekey_using_pkcs8_pem_format() { + void can_serialize_and_deserialize_ec_privatekey_using_pkcs8_pem_format() { testPrivateKeySerialization(KeyAlgorithm.EC, KeyFormat.PKCS8, "PRIVATE KEY"); } @Test - public void can_serialize_and_deserialize_rsa_publickey_using_pem_format() { + void can_serialize_and_deserialize_rsa_publickey_using_pem_format() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); String pem = KeyUtils.toPem(keyPair.getPublic()); assertTrue(pem.contains("BEGIN PUBLIC KEY")); @@ -62,7 +62,7 @@ public class KeyUtilsTest { } @Test - public void can_serialize_and_deserialize_ec_publickey_using_pem_format() { + void can_serialize_and_deserialize_ec_publickey_using_pem_format() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); String pem = KeyUtils.toPem(keyPair.getPublic()); assertTrue(pem.contains("BEGIN PUBLIC KEY")); diff --git a/security-utils/src/test/java/com/yahoo/security/tls/MutableX509KeyManagerTest.java b/security-utils/src/test/java/com/yahoo/security/MutableX509KeyManagerTest.java index 3f45d22d8ed..ddceb762d2a 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/MutableX509KeyManagerTest.java +++ b/security-utils/src/test/java/com/yahoo/security/MutableX509KeyManagerTest.java @@ -1,13 +1,14 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; +package com.yahoo.security; import com.yahoo.security.KeyAlgorithm; import com.yahoo.security.KeyStoreBuilder; import com.yahoo.security.KeyStoreType; import com.yahoo.security.KeyUtils; +import com.yahoo.security.MutableX509KeyManager; import com.yahoo.security.SignatureAlgorithm; import com.yahoo.security.X509CertificateBuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; @@ -28,7 +29,7 @@ public class MutableX509KeyManagerTest { private static final X500Principal SUBJECT = new X500Principal("CN=dummy"); @Test - public void key_manager_can_be_updated_with_new_certificate() { + void key_manager_can_be_updated_with_new_certificate() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); BigInteger serialNumberInitialCertificate = BigInteger.ONE; diff --git a/security-utils/src/test/java/com/yahoo/security/tls/MutableX509TrustManagerTest.java b/security-utils/src/test/java/com/yahoo/security/MutableX509TrustManagerTest.java index 1d04ed86322..ea9f9a4a68a 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/MutableX509TrustManagerTest.java +++ b/security-utils/src/test/java/com/yahoo/security/MutableX509TrustManagerTest.java @@ -1,13 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; +package com.yahoo.security; -import com.yahoo.security.KeyAlgorithm; -import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; -import com.yahoo.security.KeyUtils; -import com.yahoo.security.SignatureAlgorithm; -import com.yahoo.security.X509CertificateBuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; @@ -25,7 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class MutableX509TrustManagerTest { @Test - public void key_manager_can_be_updated_with_new_certificate() { + void key_manager_can_be_updated_with_new_certificate() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); X509Certificate initialCertificate = generateCertificate(new X500Principal("CN=issuer1"), keyPair); diff --git a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java index 5ad49bbc599..ca8fb280ec7 100644 --- a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java +++ b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java @@ -1,12 +1,12 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Test; +import org.junit.jupiter.api.Test; import javax.security.auth.x500.X500Principal; import java.security.KeyPair; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author bjorncs @@ -14,7 +14,7 @@ import static org.junit.Assert.assertEquals; public class Pkcs10CsrBuilderTest { @Test - public void can_build_csr_with_sans() { + void can_build_csr_with_sans() { X500Principal subject = new X500Principal("CN=subject"); KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) diff --git a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java index 6dd5eb52373..3fe36dc6a7c 100644 --- a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java +++ b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java @@ -1,16 +1,16 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Test; +import org.junit.jupiter.api.Test; import javax.security.auth.x500.X500Principal; import java.security.KeyPair; import java.util.Arrays; import java.util.List; -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static com.yahoo.security.SubjectAlternativeName.Type.DNS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author bjorncs @@ -18,11 +18,11 @@ import static org.junit.Assert.assertTrue; public class Pkcs10CsrTest { @Test - public void can_read_subject_alternative_names() { + void can_read_subject_alternative_names() { X500Principal subject = new X500Principal("CN=subject"); KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - SubjectAlternativeName san1 = new SubjectAlternativeName(DNS_NAME, "san1.com"); - SubjectAlternativeName san2 = new SubjectAlternativeName(DNS_NAME, "san2.com"); + SubjectAlternativeName san1 = new SubjectAlternativeName(DNS, "san1.com"); + SubjectAlternativeName san2 = new SubjectAlternativeName(DNS, "san2.com"); Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) .addSubjectAlternativeName(san1) .addSubjectAlternativeName(san2) @@ -31,7 +31,7 @@ public class Pkcs10CsrTest { } @Test - public void can_read_basic_constraints() { + void can_read_basic_constraints() { X500Principal subject = new X500Principal("CN=subject"); KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) @@ -42,7 +42,7 @@ public class Pkcs10CsrTest { } @Test - public void can_read_extensions() { + void can_read_extensions() { X500Principal subject = new X500Principal("CN=subject"); KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) diff --git a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java index 32b8dfc5bcd..48ff3e9a6fd 100644 --- a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java +++ b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java @@ -1,13 +1,13 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Test; +import org.junit.jupiter.api.Test; import javax.security.auth.x500.X500Principal; import java.security.KeyPair; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author bjorncs @@ -15,7 +15,7 @@ import static org.junit.Assert.assertTrue; public class Pkcs10CsrUtilsTest { @Test - public void can_deserialize_serialized_pem_csr() { + void can_deserialize_serialized_pem_csr() { X500Principal subject = new X500Principal("CN=subject"); KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA).build(); diff --git a/security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java index 3c40cb89e6c..b08494bb8da 100644 --- a/security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java +++ b/security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java @@ -1,10 +1,10 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import java.io.File; import java.nio.file.Path; import java.security.KeyPair; import java.security.cert.X509Certificate; @@ -20,25 +20,25 @@ public class SslContextBuilderTest { private static final char[] PASSWORD = new char[0]; - @Rule - public TemporaryFolder tempDirectory = new TemporaryFolder(); + @TempDir + public File tempDirectory; @Test - public void can_build_sslcontext_with_truststore_only() throws Exception { + void can_build_sslcontext_with_truststore_only() throws Exception { new SslContextBuilder() .withTrustStore(createKeystore(KeyStoreType.JKS, PASSWORD)) .build(); } @Test - public void can_build_sslcontext_with_keystore_only() throws Exception { + void can_build_sslcontext_with_keystore_only() throws Exception { new SslContextBuilder() .withKeyStore(createKeystore(KeyStoreType.JKS, PASSWORD), PASSWORD) .build(); } @Test - public void can_build_sslcontext_with_truststore_and_keystore() throws Exception { + void can_build_sslcontext_with_truststore_and_keystore() throws Exception { new SslContextBuilder() .withKeyStore(createKeystore(KeyStoreType.JKS, PASSWORD), PASSWORD) .withTrustStore(createKeystore(KeyStoreType.JKS, PASSWORD)) @@ -46,7 +46,7 @@ public class SslContextBuilderTest { } @Test - public void can_build_sslcontext_with_keystore_from_private_key_and_certificate() throws Exception { + void can_build_sslcontext_with_keystore_from_private_key_and_certificate() throws Exception { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); X509Certificate certificate = createCertificate(keyPair); new SslContextBuilder() @@ -55,8 +55,8 @@ public class SslContextBuilderTest { } @Test - public void can_build_sslcontext_with_jks_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); + void can_build_sslcontext_with_jks_keystore_from_file() throws Exception { + Path keystoreFile = File.createTempFile("junit", null, tempDirectory).toPath(); createKeystoreFile(keystoreFile, KeyStoreType.JKS, PASSWORD); new SslContextBuilder() @@ -65,8 +65,8 @@ public class SslContextBuilderTest { } @Test - public void can_build_sslcontext_with_pcks12_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); + void can_build_sslcontext_with_pcks12_keystore_from_file() throws Exception { + Path keystoreFile = File.createTempFile("junit", null, tempDirectory).toPath(); createKeystoreFile(keystoreFile, KeyStoreType.PKCS12, PASSWORD); new SslContextBuilder() diff --git a/security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java index eb6fe4286d8..1a9c4999146 100644 --- a/security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java +++ b/security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java @@ -1,9 +1,8 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; @@ -14,35 +13,35 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collection; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author bjorncs */ -@RunWith(Parameterized.class) public class X509CertificateBuilderTest { - @Parameterized.Parameters(name = "{0}") public static Collection<Object[]> data() { - return Arrays.asList(new Object[][] { + return Arrays.asList(new Object[][]{ {KeyAlgorithm.RSA, 2048, SignatureAlgorithm.SHA512_WITH_RSA}, {KeyAlgorithm.EC, 256, SignatureAlgorithm.SHA512_WITH_ECDSA}}); } - private final KeyAlgorithm keyAlgorithm; - private final int keySize; - private final SignatureAlgorithm signatureAlgorithm; + private KeyAlgorithm keyAlgorithm; + private int keySize; + private SignatureAlgorithm signatureAlgorithm; - public X509CertificateBuilderTest(KeyAlgorithm keyAlgorithm, - int keySize, - SignatureAlgorithm signatureAlgorithm) { + public void initX509CertificateBuilderTest(KeyAlgorithm keyAlgorithm, + int keySize, + SignatureAlgorithm signatureAlgorithm) { this.keyAlgorithm = keyAlgorithm; this.keySize = keySize; this.signatureAlgorithm = signatureAlgorithm; } - @Test - public void can_build_self_signed_certificate() { + @MethodSource("data") + @ParameterizedTest(name = "{0}") + void can_build_self_signed_certificate(KeyAlgorithm keyAlgorithm, int keySize, SignatureAlgorithm signatureAlgorithm) { + initX509CertificateBuilderTest(keyAlgorithm, keySize, signatureAlgorithm); KeyPair keyPair = KeyUtils.generateKeypair(keyAlgorithm, keySize); X500Principal subject = new X500Principal("CN=myservice"); X509Certificate cert = @@ -53,13 +52,15 @@ public class X509CertificateBuilderTest { Instant.now().plus(1, ChronoUnit.DAYS), signatureAlgorithm, BigInteger.valueOf(1)) - .setBasicConstraints(true, true) - .build(); + .setBasicConstraints(true, true) + .build(); assertEquals(subject, cert.getSubjectX500Principal()); } - @Test - public void can_build_certificate_from_csr() { + @MethodSource("data") + @ParameterizedTest(name = "{0}") + void can_build_certificate_from_csr(KeyAlgorithm keyAlgorithm, int keySize, SignatureAlgorithm signatureAlgorithm) { + initX509CertificateBuilderTest(keyAlgorithm, keySize, signatureAlgorithm); X500Principal subject = new X500Principal("CN=subject"); X500Principal issuer = new X500Principal("CN=issuer"); KeyPair csrKeypair = KeyUtils.generateKeypair(keyAlgorithm, keySize); diff --git a/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java index b2e800542b8..c0560627661 100644 --- a/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java +++ b/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java @@ -1,7 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security; -import org.junit.Test; +import org.junit.jupiter.api.Test; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; @@ -12,18 +12,18 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.List; -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; +import static com.yahoo.security.SubjectAlternativeName.Type.DNS; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author bjorncs */ public class X509CertificateUtilsTest { @Test - public void can_deserialize_serialized_pem_certificate() { + void can_deserialize_serialized_pem_certificate() { KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); X500Principal subject = new X500Principal("CN=myservice"); X509Certificate cert = TestUtils.createCertificate(keypair, subject); @@ -36,7 +36,7 @@ public class X509CertificateUtilsTest { } @Test - public void can_deserialize_serialized_pem_certificate_list() { + void can_deserialize_serialized_pem_certificate_list() { KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); X500Principal subject1 = new X500Principal("CN=myservice1"); X509Certificate cert1 = TestUtils.createCertificate(keypair, subject1); @@ -51,10 +51,10 @@ public class X509CertificateUtilsTest { } @Test - public void can_list_subject_alternative_names() { + void can_list_subject_alternative_names() { KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); X500Principal subject = new X500Principal("CN=myservice"); - SubjectAlternativeName san = new SubjectAlternativeName(DNS_NAME, "dns-san"); + SubjectAlternativeName san = new SubjectAlternativeName(DNS, "dns-san"); X509Certificate cert = X509CertificateBuilder .fromKeypair( keypair, @@ -72,7 +72,7 @@ public class X509CertificateUtilsTest { } @Test - public void verifies_matching_cert_and_key() { + void verifies_matching_cert_and_key() { KeyPair ecKeypairA = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); KeyPair ecKeypairB = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); KeyPair rsaKeypairA = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 1024); diff --git a/security-utils/src/test/java/com/yahoo/security/tls/AuthorizedPeersTest.java b/security-utils/src/test/java/com/yahoo/security/tls/AuthorizedPeersTest.java new file mode 100644 index 00000000000..2a7149ba2e3 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/tls/AuthorizedPeersTest.java @@ -0,0 +1,27 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import org.junit.jupiter.api.Test; + +import java.util.HashSet; + +import static com.yahoo.security.tls.RequiredPeerCredential.Field.CN; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author bjorncs + */ +public class AuthorizedPeersTest { + + @Test + void throws_exception_on_peer_policies_with_duplicate_names() { + assertThrows(IllegalArgumentException.class, () -> { + PeerPolicy peerPolicy1 = new PeerPolicy("duplicate-name", singletonList(RequiredPeerCredential.of(CN, "mycfgserver"))); + PeerPolicy peerPolicy2 = new PeerPolicy("duplicate-name", singletonList(RequiredPeerCredential.of(CN, "myclient"))); + new AuthorizedPeers(new HashSet<>(asList(peerPolicy1, peerPolicy2))); + }); + } + +} diff --git a/security-utils/src/test/java/com/yahoo/security/tls/CapabilitySetTest.java b/security-utils/src/test/java/com/yahoo/security/tls/CapabilitySetTest.java new file mode 100644 index 00000000000..87b16dbff1f --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/tls/CapabilitySetTest.java @@ -0,0 +1,27 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author bjorncs + */ +class CapabilitySetTest { + + @Test + void contains_all_capabilities() { + SortedSet<String> expectedNames = Arrays.stream(Capability.values()) + .map(Capability::asString) + .collect(Collectors.toCollection(TreeSet::new)); + SortedSet<String> actualNames = CapabilitySet.all().toNames(); + assertEquals(expectedNames, actualNames); + } + +} diff --git a/security-utils/src/test/java/com/yahoo/security/tls/ConfigFileBasedTlsContextTest.java b/security-utils/src/test/java/com/yahoo/security/tls/ConfigFileBasedTlsContextTest.java index 0af6d231701..7b70c842a4c 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/ConfigFileBasedTlsContextTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/ConfigFileBasedTlsContextTest.java @@ -4,12 +4,13 @@ package com.yahoo.security.tls; import com.yahoo.security.KeyUtils; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.security.X509CertificateUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import javax.net.ssl.SSLEngine; import javax.security.auth.x500.X500Principal; + +import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; @@ -28,23 +29,23 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class ConfigFileBasedTlsContextTest { - @Rule - public TemporaryFolder tempDirectory = new TemporaryFolder(); + @TempDir + public File tempDirectory; @Test - public void can_create_sslcontext_from_credentials() throws IOException, InterruptedException { + void can_create_sslcontext_from_credentials() throws IOException, InterruptedException { KeyPair keyPair = KeyUtils.generateKeypair(EC); - Path privateKeyFile = tempDirectory.newFile().toPath(); + Path privateKeyFile = File.createTempFile("junit", null, tempDirectory).toPath(); Files.write(privateKeyFile, KeyUtils.toPem(keyPair.getPrivate()).getBytes()); X509Certificate certificate = X509CertificateBuilder .fromKeypair(keyPair, new X500Principal("CN=dummy"), EPOCH, EPOCH.plus(1, DAYS), SHA256_WITH_ECDSA, BigInteger.ONE) .build(); - Path certificateChainFile = tempDirectory.newFile().toPath(); + Path certificateChainFile = File.createTempFile("junit", null, tempDirectory).toPath(); String certificatePem = X509CertificateUtils.toPem(certificate); Files.write(certificateChainFile, certificatePem.getBytes()); - Path caCertificatesFile = tempDirectory.newFile().toPath(); + Path caCertificatesFile = File.createTempFile("junit", null, tempDirectory).toPath(); Files.write(caCertificatesFile, certificatePem.getBytes()); TransportSecurityOptions options = new TransportSecurityOptions.Builder() @@ -52,7 +53,7 @@ public class ConfigFileBasedTlsContextTest { .withCaCertificates(caCertificatesFile) .build(); - Path optionsFile = tempDirectory.newFile().toPath(); + Path optionsFile = File.createTempFile("junit", null, tempDirectory).toPath(); options.toJsonFile(optionsFile); try (TlsContext tlsContext = new ConfigFileBasedTlsContext(optionsFile, AuthorizationMode.ENFORCE)) { diff --git a/security-utils/src/test/java/com/yahoo/security/tls/ConnectionAuthContextTest.java b/security-utils/src/test/java/com/yahoo/security/tls/ConnectionAuthContextTest.java new file mode 100644 index 00000000000..c30a812a30d --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/tls/ConnectionAuthContextTest.java @@ -0,0 +1,62 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import com.yahoo.security.KeyAlgorithm; +import com.yahoo.security.KeyUtils; +import com.yahoo.security.X509CertificateBuilder; +import org.junit.jupiter.api.Test; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Set; + +import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author bjorncs + */ +class ConnectionAuthContextTest { + + @Test + void fails_on_missing_capabilities() { + ConnectionAuthContext ctx = createConnectionAuthContext(); + assertThrows(MissingCapabilitiesException.class, + () -> ctx.verifyCapabilities(CapabilitySet.from(Capability.CONTENT__STATUS_PAGES))); + } + + @Test + void creates_correct_error_message() { + ConnectionAuthContext ctx = createConnectionAuthContext(); + CapabilitySet requiredCaps = CapabilitySet.from(Capability.CONTENT__STATUS_PAGES); + String expectedMessage = """ + Permission denied for 'myaction' on 'myresource'. Peer 'mypeer' with [CN='myidentity']. + Requires capabilities [vespa.content.status_pages] but peer has + [vespa.content.document_api, vespa.content.search_api, vespa.slobrok.api]. + """; + String actualMessage = ctx.createPermissionDeniedErrorMessage(requiredCaps, "myaction", "myresource", "mypeer"); + assertThat(actualMessage).isEqualToIgnoringWhitespace(expectedMessage); + } + + private static ConnectionAuthContext createConnectionAuthContext() { + return new ConnectionAuthContext( + List.of(createCertificate()), CapabilitySet.Predefined.CONTAINER_NODE.capabilities(), Set.of(), + CapabilityMode.ENFORCE); + } + + private static X509Certificate createCertificate() { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + return X509CertificateBuilder.fromKeypair( + keyPair, new X500Principal("CN=myidentity"), Instant.EPOCH, + Instant.EPOCH.plus(100000, ChronoUnit.DAYS), SHA256_WITH_ECDSA, BigInteger.ONE) + .build(); + } + + +} diff --git a/security-utils/src/test/java/com/yahoo/security/tls/DefaultTlsContextTest.java b/security-utils/src/test/java/com/yahoo/security/tls/DefaultTlsContextTest.java index 8236ce081ba..bf4a618d9ce 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/DefaultTlsContextTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/DefaultTlsContextTest.java @@ -3,11 +3,7 @@ package com.yahoo.security.tls; import com.yahoo.security.KeyUtils; import com.yahoo.security.X509CertificateBuilder; -import com.yahoo.security.tls.policy.AuthorizedPeers; -import com.yahoo.security.tls.policy.PeerPolicy; -import com.yahoo.security.tls.policy.RequiredPeerCredential; -import com.yahoo.security.tls.policy.Role; -import org.junit.Test; +import org.junit.jupiter.api.Test; import javax.net.ssl.SSLEngine; import javax.security.auth.x500.X500Principal; @@ -30,7 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class DefaultTlsContextTest { @Test - public void can_create_sslcontext_from_credentials() { + void can_create_sslcontext_from_credentials() { KeyPair keyPair = KeyUtils.generateKeypair(EC); X509Certificate certificate = X509CertificateBuilder @@ -41,7 +37,6 @@ public class DefaultTlsContextTest { singleton( new PeerPolicy( "dummy-policy", - singleton(new Role("dummy-role")), singletonList(RequiredPeerCredential.of(RequiredPeerCredential.Field.CN, "dummy"))))); DefaultTlsContext tlsContext = diff --git a/security-utils/src/test/java/com/yahoo/security/tls/policy/GlobPatternTest.java b/security-utils/src/test/java/com/yahoo/security/tls/GlobPatternTest.java index 4350aa2b0a9..a93bffe6961 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/policy/GlobPatternTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/GlobPatternTest.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.policy; +package com.yahoo.security.tls; import org.junit.jupiter.api.Test; diff --git a/security-utils/src/test/java/com/yahoo/security/tls/policy/HostGlobPatternTest.java b/security-utils/src/test/java/com/yahoo/security/tls/HostGlobPatternTest.java index a42eaaf74b0..b63b1dfeaa0 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/policy/HostGlobPatternTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/HostGlobPatternTest.java @@ -1,10 +1,10 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.policy; +package com.yahoo.security.tls; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -13,28 +13,28 @@ import static org.junit.Assert.assertTrue; public class HostGlobPatternTest { @Test - public void glob_without_wildcards_matches_entire_string() { + void glob_without_wildcards_matches_entire_string() { assertTrue(globMatches("foo", "foo")); assertFalse(globMatches("foo", "fooo")); assertFalse(globMatches("foo", "ffoo")); } @Test - public void wildcard_glob_can_match_prefix() { + void wildcard_glob_can_match_prefix() { assertTrue(globMatches("foo*", "foo")); assertTrue(globMatches("foo*", "foobar")); assertFalse(globMatches("foo*", "ffoo")); } @Test - public void wildcard_glob_can_match_suffix() { + void wildcard_glob_can_match_suffix() { assertTrue(globMatches("*foo", "foo")); assertTrue(globMatches("*foo", "ffoo")); assertFalse(globMatches("*foo", "fooo")); } @Test - public void wildcard_glob_can_match_substring() { + void wildcard_glob_can_match_substring() { assertTrue(globMatches("f*o", "fo")); assertTrue(globMatches("f*o", "foo")); assertTrue(globMatches("f*o", "ffoo")); @@ -42,7 +42,7 @@ public class HostGlobPatternTest { } @Test - public void wildcard_glob_does_not_cross_multiple_dot_delimiter_boundaries() { + void wildcard_glob_does_not_cross_multiple_dot_delimiter_boundaries() { assertTrue(globMatches("*.bar.baz", "foo.bar.baz")); assertTrue(globMatches("*.bar.baz", ".bar.baz")); assertFalse(globMatches("*.bar.baz", "zoid.foo.bar.baz")); @@ -51,7 +51,7 @@ public class HostGlobPatternTest { } @Test - public void single_char_glob_matches_non_dot_characters() { + void single_char_glob_matches_non_dot_characters() { assertTrue(globMatches("f?o", "foo")); assertFalse(globMatches("f?o", "fooo")); assertFalse(globMatches("f?o", "ffoo")); @@ -59,7 +59,7 @@ public class HostGlobPatternTest { } @Test - public void other_regex_meta_characters_are_matched_as_literal_characters() { + void other_regex_meta_characters_are_matched_as_literal_characters() { assertTrue(globMatches("<([{\\^-=$!|]})+.>", "<([{\\^-=$!|]})+.>")); } 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/PeerAuthorizerTest.java index fdfed781286..bea5c6108f2 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/authz/PeerAuthorizerTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/PeerAuthorizerTest.java @@ -1,16 +1,12 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.authz; +package com.yahoo.security.tls; import com.yahoo.security.KeyAlgorithm; 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.PeerPolicy; -import com.yahoo.security.tls.policy.RequiredPeerCredential; -import com.yahoo.security.tls.policy.RequiredPeerCredential.Field; -import com.yahoo.security.tls.policy.Role; -import org.junit.Test; +import com.yahoo.security.tls.RequiredPeerCredential.Field; +import org.junit.jupiter.api.Test; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; @@ -20,20 +16,20 @@ 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; -import static com.yahoo.security.tls.policy.RequiredPeerCredential.Field.SAN_DNS; -import static com.yahoo.security.tls.policy.RequiredPeerCredential.Field.SAN_URI; +import static com.yahoo.security.tls.RequiredPeerCredential.Field.CN; +import static com.yahoo.security.tls.RequiredPeerCredential.Field.SAN_DNS; +import static com.yahoo.security.tls.RequiredPeerCredential.Field.SAN_URI; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author bjorncs @@ -41,60 +37,57 @@ import static org.junit.Assert.assertTrue; public class PeerAuthorizerTest { private static final KeyPair KEY_PAIR = KeyUtils.generateKeypair(KeyAlgorithm.EC); - private static final String ROLE_1 = "role-1", ROLE_2 = "role-2", ROLE_3 = "role-3", POLICY_1 = "policy-1", POLICY_2 = "policy-2"; + private static final String POLICY_1 = "policy-1", POLICY_2 = "policy-2"; @Test - public void certificate_must_match_both_san_and_cn_pattern() { + void certificate_must_match_both_san_and_cn_pattern() { RequiredPeerCredential cnRequirement = createRequiredCredential(CN, "*.matching.cn"); RequiredPeerCredential sanRequirement = createRequiredCredential(SAN_DNS, "*.matching.san"); - PeerAuthorizer authorizer = createPeerAuthorizer(createPolicy(POLICY_1, createRoles(ROLE_1), cnRequirement, sanRequirement)); + 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.assumedRoles()).extracting(Role::name).containsOnly(ROLE_1); assertThat(result.matchedPolicies()).containsOnly(POLICY_1); assertUnauthorized(authorizer.authorizePeer(createCertificate("foo.invalid.cn", singletonList("foo.matching.san"), emptyList()))); - assertUnauthorized(authorizer.authorizePeer(createCertificate("foo.invalid.cn", asList("foo.matching.san", "foo.invalid.san"),emptyList()))); + assertUnauthorized(authorizer.authorizePeer(createCertificate("foo.invalid.cn", asList("foo.matching.san", "foo.invalid.san"), emptyList()))); assertUnauthorized(authorizer.authorizePeer(createCertificate("foo.matching.cn", singletonList("foo.invalid.san"), emptyList()))); } @Test - public void can_match_multiple_policies() { + void can_match_multiple_policies() { RequiredPeerCredential cnRequirement = createRequiredCredential(CN, "*.matching.cn"); RequiredPeerCredential sanRequirement = createRequiredCredential(SAN_DNS, "*.matching.san"); PeerAuthorizer peerAuthorizer = createPeerAuthorizer( - createPolicy(POLICY_1, createRoles(ROLE_1, ROLE_2), cnRequirement, sanRequirement), - createPolicy(POLICY_2, createRoles(ROLE_2, ROLE_3), cnRequirement, sanRequirement)); + 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.assumedRoles()).extracting(Role::name).containsOnly(ROLE_1, ROLE_2, ROLE_3); assertThat(result.matchedPolicies()).containsOnly(POLICY_1, POLICY_2); } @Test - public void can_match_subset_of_policies() { + void can_match_subset_of_policies() { PeerAuthorizer peerAuthorizer = createPeerAuthorizer( - createPolicy(POLICY_1, createRoles(ROLE_1), createRequiredCredential(CN, "*.matching.cn")), - createPolicy(POLICY_2, createRoles(ROLE_1, ROLE_2), createRequiredCredential(SAN_DNS, "*.matching.san"))); + 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.assumedRoles()).extracting(Role::name).containsOnly(ROLE_1, ROLE_2); assertThat(result.matchedPolicies()).containsOnly(POLICY_2); } @Test - public void must_match_all_cn_and_san_patterns() { + void must_match_all_cn_and_san_patterns() { RequiredPeerCredential cnSuffixRequirement = createRequiredCredential(CN, "*.*.matching.suffix.cn"); RequiredPeerCredential cnPrefixRequirement = createRequiredCredential(CN, "matching.prefix.*.*.*"); RequiredPeerCredential sanPrefixRequirement = createRequiredCredential(SAN_DNS, "*.*.matching.suffix.san"); RequiredPeerCredential sanSuffixRequirement = createRequiredCredential(SAN_DNS, "matching.prefix.*.*.*"); PeerAuthorizer peerAuthorizer = createPeerAuthorizer( - createPolicy(POLICY_1, emptySet(), cnSuffixRequirement, cnPrefixRequirement, sanPrefixRequirement, sanSuffixRequirement)); + createPolicy(POLICY_1, cnSuffixRequirement, cnPrefixRequirement, sanPrefixRequirement, sanSuffixRequirement)); assertAuthorized(peerAuthorizer.authorizePeer(createCertificate("matching.prefix.matching.suffix.cn", singletonList("matching.prefix.matching.suffix.san"), emptyList()))); assertUnauthorized(peerAuthorizer.authorizePeer(createCertificate("matching.prefix.matching.suffix.cn", singletonList("matching.prefix.invalid.suffix.san"), emptyList()))); @@ -102,19 +95,33 @@ public class PeerAuthorizerTest { } @Test - public void can_match_policy_with_san_uri_pattern() { + void can_match_policy_with_san_uri_pattern() { RequiredPeerCredential cnRequirement = createRequiredCredential(CN, "*.matching.cn"); RequiredPeerCredential sanUriRequirement = createRequiredCredential(SAN_URI, "myscheme://my/*/uri"); - PeerAuthorizer authorizer = createPeerAuthorizer(createPolicy(POLICY_1, createRoles(ROLE_1), cnRequirement, sanUriRequirement)); + 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.assumedRoles()).extracting(Role::name).containsOnly(ROLE_1); assertThat(result.matchedPolicies()).containsOnly(POLICY_1); assertUnauthorized(authorizer.authorizePeer(createCertificate("foo.matching.cn", emptyList(), singletonList("myscheme://my/nonmatching/url")))); } + @Test + 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( @@ -124,8 +131,8 @@ public class PeerAuthorizerTest { Instant.EPOCH.plus(100000, ChronoUnit.DAYS), SHA256_WITH_ECDSA, BigInteger.ONE); - sanDns.forEach(san -> builder.addSubjectAlternativeName(Type.DNS_NAME, san)); - sanUri.forEach(san -> builder.addSubjectAlternativeName(Type.UNIFORM_RESOURCE_IDENTIFIER, san)); + sanDns.forEach(san -> builder.addSubjectAlternativeName(Type.DNS, san)); + sanUri.forEach(san -> builder.addSubjectAlternativeName(Type.URI, san)); return builder.build(); } @@ -133,24 +140,28 @@ public class PeerAuthorizerTest { return RequiredPeerCredential.of(field, pattern); } - private static Set<Role> createRoles(String... roleNames) { - return Arrays.stream(roleNames).map(Role::new).collect(toSet()); - } - private static PeerAuthorizer createPeerAuthorizer(PeerPolicy... policies) { return new PeerAuthorizer(new AuthorizedPeers(Arrays.stream(policies).collect(toSet()))); } - private static PeerPolicy createPolicy(String name, Set<Role> roles, RequiredPeerCredential... requiredCredentials) { - return new PeerPolicy(name, roles, asList(requiredCredentials)); + private static PeerPolicy createPolicy(String name, RequiredPeerCredential... requiredCredentials) { + return new PeerPolicy(name, asList(requiredCredentials)); + } + + 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.authorized()); } - private static void assertAuthorized(AuthorizationResult result) { - assertTrue(result.succeeded()); + private static void assertUnauthorized(ConnectionAuthContext result) { + assertFalse(result.authorized()); } - private static void assertUnauthorized(AuthorizationResult result) { - assertFalse(result.succeeded()); + private static void assertCapabiltiesGranted(ConnectionAuthContext ctx, Set<Capability> expected) { + assertThat(ctx.capabilities().asSet()).containsOnly(expected.toArray(new Capability[0])); } } diff --git a/security-utils/src/test/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializerTest.java b/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsJsonSerializerTest.java index 6bca49aee83..895428037ed 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializerTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsJsonSerializerTest.java @@ -1,14 +1,10 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.json; +package com.yahoo.security.tls; -import com.yahoo.security.tls.TransportSecurityOptions; -import com.yahoo.security.tls.policy.AuthorizedPeers; -import com.yahoo.security.tls.policy.PeerPolicy; -import com.yahoo.security.tls.policy.RequiredPeerCredential; -import com.yahoo.security.tls.policy.Role; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -21,25 +17,26 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.Optional; -import static com.yahoo.security.tls.policy.RequiredPeerCredential.Field.CN; -import static com.yahoo.security.tls.policy.RequiredPeerCredential.Field.SAN_DNS; -import static com.yahoo.security.tls.policy.RequiredPeerCredential.Field.SAN_URI; -import static com.yahoo.test.json.JsonTestHelper.assertJsonEquals; -import static java.util.Collections.singleton; -import static org.junit.Assert.assertEquals; +import static com.yahoo.security.tls.RequiredPeerCredential.Field.CN; +import static com.yahoo.security.tls.RequiredPeerCredential.Field.SAN_DNS; +import static com.yahoo.security.tls.RequiredPeerCredential.Field.SAN_URI; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author bjorncs */ public class TransportSecurityOptionsJsonSerializerTest { - @Rule public TemporaryFolder tempDirectory = new TemporaryFolder(); + @TempDir + public File tempDirectory; private static final Path TEST_CONFIG_FILE = Paths.get("src/test/resources/transport-security-options.json"); + private static final ObjectMapper mapper = new ObjectMapper(); @Test - public void can_serialize_and_deserialize_transport_security_options() throws IOException { + void can_serialize_and_deserialize_transport_security_options() throws IOException { TransportSecurityOptions options = new TransportSecurityOptions.Builder() .withCaCertificates(Paths.get("/path/to/ca-certs.pem")) .withCertificates(Paths.get("/path/to/cert.pem"), Paths.get("/path/to/key.pem")) @@ -47,11 +44,13 @@ public class TransportSecurityOptionsJsonSerializerTest { .withAuthorizedPeers( new AuthorizedPeers( new LinkedHashSet<>(Arrays.asList( - new PeerPolicy("cfgserver", "cfgserver policy description", singleton(new Role("myrole")), Arrays.asList( + new PeerPolicy("cfgserver", "cfgserver policy description", Arrays.asList( RequiredPeerCredential.of(CN, "mycfgserver"), RequiredPeerCredential.of(SAN_DNS, "*.suffix.com"), RequiredPeerCredential.of(SAN_URI, "myscheme://resource/path/"))), - new PeerPolicy("node", singleton(new Role("anotherrole")), Collections.singletonList(RequiredPeerCredential.of(CN, "hostname"))))))) + new PeerPolicy("node", Optional.empty(), + CapabilitySet.from(Capability.SLOBROK__API), + Collections.singletonList(RequiredPeerCredential.of(CN, "hostname"))))))) .build(); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -64,15 +63,15 @@ public class TransportSecurityOptionsJsonSerializerTest { } @Test - public void can_serialize_options_without_authorized_peers() throws IOException { + void can_serialize_options_without_authorized_peers() throws IOException { TransportSecurityOptions options = new TransportSecurityOptions.Builder() .withCertificates(Paths.get("certs.pem"), Paths.get("myhost.key")) .withCaCertificates(Paths.get("my_cas.pem")) - .withAcceptedCiphers(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" , "TLS_AES_256_GCM_SHA384")) + .withAcceptedCiphers(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_AES_256_GCM_SHA384")) .withAcceptedProtocols(Collections.singletonList("TLSv1.2")) .withHostnameValidationDisabled(true) .build(); - File outputFile = tempDirectory.newFile(); + File outputFile = File.createTempFile("junit", null, tempDirectory); try (OutputStream out = Files.newOutputStream(outputFile.toPath())) { new TransportSecurityOptionsJsonSerializer().serialize(out, options); } @@ -82,13 +81,13 @@ public class TransportSecurityOptionsJsonSerializerTest { } @Test - public void disable_hostname_validation_is_not_serialized_if_false() throws IOException { + void disable_hostname_validation_is_not_serialized_if_false() throws IOException { TransportSecurityOptions options = new TransportSecurityOptions.Builder() .withCertificates(Paths.get("certs.pem"), Paths.get("myhost.key")) .withCaCertificates(Paths.get("my_cas.pem")) .withHostnameValidationDisabled(false) .build(); - File outputFile = tempDirectory.newFile(); + File outputFile = File.createTempFile("junit", null, tempDirectory); try (OutputStream out = Files.newOutputStream(outputFile.toPath())) { new TransportSecurityOptionsJsonSerializer().serialize(out, options); } @@ -99,4 +98,8 @@ public class TransportSecurityOptionsJsonSerializerTest { assertJsonEquals(expectedOutput, actualOutput); } + private static void assertJsonEquals(String inputJson, String expectedJson) throws JsonProcessingException { + assertEquals(mapper.readTree(expectedJson), mapper.readTree(inputJson)); + } + } diff --git a/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java b/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java index 7d67c492170..089a4ca6de5 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java @@ -1,7 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.security.tls; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -11,7 +11,7 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author bjorncs @@ -28,13 +28,13 @@ public class TransportSecurityOptionsTest { .build(); @Test - public void can_read_options_from_json_file() { + void can_read_options_from_json_file() { TransportSecurityOptions actualOptions = TransportSecurityOptions.fromJsonFile(TEST_CONFIG_FILE); assertEquals(OPTIONS, actualOptions); } @Test - public void can_read_options_from_json() throws IOException { + void can_read_options_from_json() throws IOException { String tlsJson = new String(Files.readAllBytes(TEST_CONFIG_FILE), StandardCharsets.UTF_8); TransportSecurityOptions actualOptions = TransportSecurityOptions.fromJson(tlsJson); assertEquals(OPTIONS, actualOptions); diff --git a/security-utils/src/test/java/com/yahoo/security/tls/policy/UriGlobPatternTest.java b/security-utils/src/test/java/com/yahoo/security/tls/UriGlobPatternTest.java index c60c782da14..4d89d71cf85 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/policy/UriGlobPatternTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/UriGlobPatternTest.java @@ -1,5 +1,5 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls.policy; +package com.yahoo.security.tls; import org.junit.jupiter.api.Test; diff --git a/security-utils/src/test/java/com/yahoo/security/tls/policy/AuthorizedPeersTest.java b/security-utils/src/test/java/com/yahoo/security/tls/policy/AuthorizedPeersTest.java deleted file mode 100644 index c44a23ecf2b..00000000000 --- a/security-utils/src/test/java/com/yahoo/security/tls/policy/AuthorizedPeersTest.java +++ /dev/null @@ -1,27 +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.policy; - -import org.junit.Test; - -import java.util.HashSet; -import java.util.List; - -import static com.yahoo.security.tls.policy.RequiredPeerCredential.Field.CN; -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; - -/** - * @author bjorncs - */ -public class AuthorizedPeersTest { - - @Test(expected = IllegalArgumentException.class) - public void throws_exception_on_peer_policies_with_duplicate_names() { - List<RequiredPeerCredential> requiredPeerCredential = singletonList(RequiredPeerCredential.of(CN, "mycfgserver")); - PeerPolicy peerPolicy1 = new PeerPolicy("duplicate-name", singleton(new Role("role")), requiredPeerCredential); - PeerPolicy peerPolicy2 = new PeerPolicy("duplicate-name", singleton(new Role("anotherrole")), requiredPeerCredential); - new AuthorizedPeers(new HashSet<>(asList(peerPolicy1, peerPolicy2))); - } - -} diff --git a/security-utils/src/test/resources/transport-security-options-with-authz-rules.json b/security-utils/src/test/resources/transport-security-options-with-authz-rules.json index ea0bee38c8a..85c3a78311e 100644 --- a/security-utils/src/test/resources/transport-security-options-with-authz-rules.json +++ b/security-utils/src/test/resources/transport-security-options-with-authz-rules.json @@ -16,14 +16,13 @@ "must-match" : "myscheme://resource/path/" } ], "name" : "cfgserver", - "description" : "cfgserver policy description", - "roles" : [ "myrole" ] + "description" : "cfgserver policy description" }, { "required-credentials" : [ { "field" : "CN", "must-match" : "hostname" } ], "name" : "node", - "roles" : [ "anotherrole" ] + "capabilities" : [ "vespa.slobrok.api" ] } ] }
\ No newline at end of file |