diff options
author | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2022-07-13 10:48:24 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2022-07-13 10:49:05 +0200 |
commit | 1f693c55dcc6f7a4fbf9fae288085bd2dea2c63e (patch) | |
tree | 9256738699d0bf0f3b7d88b2de868146a74c1934 /security-utils | |
parent | b9b314f5c55b3b09e12d5407415640a149844767 (diff) |
Add Capability and CapabilitySet including JSON serialization
Diffstat (limited to 'security-utils')
8 files changed, 182 insertions, 6 deletions
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/json/TransportSecurityOptionsEntity.java index 02c6fe2ab99..b80a7e4f2fb 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsEntity.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsEntity.java @@ -36,6 +36,7 @@ class TransportSecurityOptionsEntity { @JsonProperty("required-credentials") List<RequiredCredential> requiredCredentials; @JsonProperty("name") String name; @JsonProperty("description") @JsonInclude(NON_NULL) String description; + @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/json/TransportSecurityOptionsJsonSerializer.java index 5deebf48d1b..195e200febb 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializer.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializer.java @@ -8,6 +8,7 @@ import com.yahoo.security.tls.json.TransportSecurityOptionsEntity.CredentialFiel 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.CapabilitySet; import com.yahoo.security.tls.policy.PeerPolicy; import com.yahoo.security.tls.policy.RequiredPeerCredential; @@ -19,6 +20,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.Set; import static java.util.stream.Collectors.toList; @@ -99,7 +101,16 @@ public class TransportSecurityOptionsJsonSerializer { if (authorizedPeer.requiredCredentials == null) { throw missingFieldException("required-credentials"); } - return new PeerPolicy(authorizedPeer.name, authorizedPeer.description, toRequestPeerCredentials(authorizedPeer.requiredCredentials)); + return new PeerPolicy(authorizedPeer.name, Optional.ofNullable(authorizedPeer.description), + toCapabilities(authorizedPeer.capabilities), toRequestPeerCredentials(authorizedPeer.requiredCredentials)); + } + + 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,6 +153,10 @@ public class TransportSecurityOptionsJsonSerializer { authorizedPeer.name = peerPolicy.policyName(); authorizedPeer.requiredCredentials = new ArrayList<>(); authorizedPeer.description = peerPolicy.description().orElse(null); + CapabilitySet caps = peerPolicy.capabilities(); + if (!caps.hasAllCapabilities()) { + authorizedPeer.capabilities = List.copyOf(caps.toCapabilityNames()); + } for (RequiredPeerCredential requiredPeerCredential : peerPolicy.requiredCredentials()) { RequiredCredential requiredCredential = new RequiredCredential(); requiredCredential.field = toField(requiredPeerCredential.field()); diff --git a/security-utils/src/main/java/com/yahoo/security/tls/policy/Capability.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/Capability.java new file mode 100644 index 00000000000..09d4de37831 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/policy/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.policy; + +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/policy/CapabilitySet.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/CapabilitySet.java new file mode 100644 index 00000000000..44ff1eedfb0 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/policy/CapabilitySet.java @@ -0,0 +1,90 @@ +// 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.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +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 EnumSet<Capability> caps; + + Predefined(String name, Capability... caps) { + this.name = name; + this.caps = caps.length == 0 ? EnumSet.noneOf(Capability.class) : EnumSet.copyOf(List.of(caps)); } + + public static Optional<Predefined> fromName(String name) { + return Arrays.stream(values()).filter(p -> p.name.equals(name)).findAny(); + } + } + + 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.caps); + else caps.add(Capability.fromName(name)); + } + return new CapabilitySet(caps); + } + + public static CapabilitySet from(EnumSet<Capability> caps) { return new CapabilitySet(EnumSet.copyOf(caps)); } + public static CapabilitySet from(Collection<Capability> caps) { return new CapabilitySet(EnumSet.copyOf(caps)); } + public static CapabilitySet from(Capability... caps) { return new CapabilitySet(EnumSet.copyOf(List.of(caps))); } + public static CapabilitySet all() { return ALL_CAPABILITIES; } + public static CapabilitySet none() { return NO_CAPABILITIES; } + + public boolean hasAllCapabilities() { return this.caps.equals(ALL_CAPABILITIES.caps); } + + public SortedSet<String> toCapabilityNames() { + return caps.stream().map(Capability::asString).collect(Collectors.toCollection(TreeSet::new)); + } + + @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/policy/PeerPolicy.java b/security-utils/src/main/java/com/yahoo/security/tls/policy/PeerPolicy.java index 332c0d1a43f..cb39e5e9c3c 100644 --- 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 @@ -7,13 +7,18 @@ import java.util.Optional; /** * @author bjorncs */ -public record PeerPolicy(String policyName, Optional<String> description, List<RequiredPeerCredential> requiredCredentials) { +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(), List.copyOf(requiredCredentials)); + this(policyName, Optional.empty(), CapabilitySet.all(), requiredCredentials); } public PeerPolicy(String policyName, String description, List<RequiredPeerCredential> requiredCredentials) { - this(policyName, Optional.ofNullable(description), List.copyOf(requiredCredentials)); + this(policyName, Optional.ofNullable(description), CapabilitySet.all(), requiredCredentials); } } 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/json/TransportSecurityOptionsJsonSerializerTest.java index 2cb262cecc0..852d6ae94c9 100644 --- a/security-utils/src/test/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializerTest.java +++ b/security-utils/src/test/java/com/yahoo/security/tls/json/TransportSecurityOptionsJsonSerializerTest.java @@ -3,6 +3,8 @@ package com.yahoo.security.tls.json; import com.yahoo.security.tls.TransportSecurityOptions; import com.yahoo.security.tls.policy.AuthorizedPeers; +import com.yahoo.security.tls.policy.Capability; +import com.yahoo.security.tls.policy.CapabilitySet; import com.yahoo.security.tls.policy.PeerPolicy; import com.yahoo.security.tls.policy.RequiredPeerCredential; import org.junit.Rule; @@ -20,6 +22,7 @@ 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; @@ -49,7 +52,9 @@ public class TransportSecurityOptionsJsonSerializerTest { RequiredPeerCredential.of(CN, "mycfgserver"), RequiredPeerCredential.of(SAN_DNS, "*.suffix.com"), RequiredPeerCredential.of(SAN_URI, "myscheme://resource/path/"))), - new PeerPolicy("node", 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(); diff --git a/security-utils/src/test/java/com/yahoo/security/tls/policy/CapabilitySetTest.java b/security-utils/src/test/java/com/yahoo/security/tls/policy/CapabilitySetTest.java new file mode 100644 index 00000000000..3379c37e918 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/tls/policy/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.policy; + +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().toCapabilityNames(); + assertEquals(expectedNames, actualNames); + } + +} 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 06ed6e0943c..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 @@ -22,6 +22,7 @@ "field" : "CN", "must-match" : "hostname" } ], - "name" : "node" + "name" : "node", + "capabilities" : [ "vespa.slobrok.api" ] } ] }
\ No newline at end of file |