aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@yahooinc.com>2023-02-06 13:01:18 +0100
committerGitHub <noreply@github.com>2023-02-06 13:01:18 +0100
commit95e694413c0d4638c95e1d1285b6a45727dc616a (patch)
treea7d34e7d915d9a99a19a2d795d611f8438e1a8e5
parentd7173a4ef8190a8def529794b4f072771c784722 (diff)
parentbc3ce2d5e0b385c6c469ce174a1ee746fb813000 (diff)
Merge pull request #25862 from vespa-engine/bjorncs/cluster-type-in-node-cert
Bjorncs/cluster type in node cert
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/IdentityDocumentGenerator.java18
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidator.java91
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java9
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidatorTest.java44
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java15
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/Capability.java1
-rw-r--r--vespa-athenz/pom.xml60
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/ClusterType.java40
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java25
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java118
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java96
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java86
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java9
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/CsrGenerator.java20
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java14
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java42
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/InstanceCsrGeneratorTest.java17
19 files changed, 323 insertions, 388 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/IdentityDocumentGenerator.java
index 5b1a909e109..58a9e0130d6 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/IdentityDocumentGenerator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/IdentityDocumentGenerator.java
@@ -5,6 +5,7 @@ import com.yahoo.component.annotation.Inject;
import com.yahoo.config.provision.Zone;
import com.yahoo.net.HostName;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
@@ -65,21 +66,14 @@ public class IdentityDocumentGenerator {
String configServerHostname = HostName.getLocalhost();
Instant createdAt = Instant.now();
+ var clusterType = ClusterType.from(allocation.membership().cluster().type().name());
String signature = signer.generateSignature(
providerUniqueId, providerService, configServerHostname,
- node.hostname(), createdAt, ips, identityType, privateKey);
-
+ node.hostname(), createdAt, ips, identityType, clusterType, privateKey);
return new SignedIdentityDocument(
- signature,
- athenzProviderServiceConfig.secretVersion(),
- providerUniqueId,
- providerService,
- SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION,
- configServerHostname,
- node.hostname(),
- createdAt,
- ips,
- identityType);
+ signature, athenzProviderServiceConfig.secretVersion(), providerUniqueId, providerService,
+ SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION, configServerHostname, node.hostname(),
+ createdAt, ips, identityType, clusterType);
} catch (Exception e) {
throw new RuntimeException("Exception generating identity document: " + e.getMessage(), e);
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidator.java
index 6b7a4835aee..d8bbf743d8c 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidator.java
@@ -8,6 +8,7 @@ import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.model.api.SuperModelProvider;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
@@ -17,14 +18,17 @@ import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import java.net.InetAddress;
+import java.net.URI;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Stream;
+import java.util.stream.Collectors;
/**
* Verifies that the instance's identity document is valid
@@ -41,6 +45,7 @@ public class InstanceValidator {
public static final String SAN_IPS_ATTRNAME = "sanIP";
public static final String SAN_DNS_ATTRNAME = "sanDNS";
+ public static final String SAN_URI_ATTRNAME = "sanURI";
private final AthenzService tenantDockerContainerIdentity;
private final IdentityDocumentSigner signer;
@@ -69,34 +74,42 @@ public class InstanceValidator {
}
public boolean isValidInstance(InstanceConfirmation instanceConfirmation) {
- SignedIdentityDocument signedIdentityDocument = EntityBindingsMapper.toSignedIdentityDocument(instanceConfirmation.signedIdentityDocument);
+ try {
+ validateInstance(instanceConfirmation);
+ return true;
+ } catch (ValidationException e) {
+ log.log(e.logLevel(), e.messageSupplier());
+ return false;
+ }
+ }
+
+ public void validateInstance(InstanceConfirmation req) throws ValidationException {
+ SignedIdentityDocument signedIdentityDocument = EntityBindingsMapper.toSignedIdentityDocument(req.signedIdentityDocument);
VespaUniqueInstanceId providerUniqueId = signedIdentityDocument.providerUniqueId();
ApplicationId applicationId = ApplicationId.from(
providerUniqueId.tenant(), providerUniqueId.application(), providerUniqueId.instance());
- VespaUniqueInstanceId csrProviderUniqueId = getVespaUniqueInstanceId(instanceConfirmation);
+ VespaUniqueInstanceId csrProviderUniqueId = getVespaUniqueInstanceId(req);
if(! providerUniqueId.equals(csrProviderUniqueId)) {
- log.log(Level.WARNING, String.format("Instance %s has invalid provider unique ID in CSR (%s)", providerUniqueId, csrProviderUniqueId));
- return false;
+ var msg = String.format("Instance %s has invalid provider unique ID in CSR (%s)", providerUniqueId, csrProviderUniqueId);
+ throw new ValidationException(Level.WARNING, () -> msg);
}
- if (! isSameIdentityAsInServicesXml(applicationId, instanceConfirmation.domain, instanceConfirmation.service)) {
- return false;
+ if (! isSameIdentityAsInServicesXml(applicationId, req.domain, req.service)) {
+ Supplier<String> msg = () -> "Invalid identity '%s.%s' in services.xml".formatted(req.domain, req.service);
+ throw new ValidationException(Level.FINE, msg);
}
log.log(Level.FINE, () -> String.format("Validating instance %s.", providerUniqueId));
PublicKey publicKey = keyProvider.getPublicKey(signedIdentityDocument.signingKeyVersion());
if (! signer.hasValidSignature(signedIdentityDocument, publicKey)) {
- log.log(Level.SEVERE, () -> String.format("Instance %s has invalid signature.", providerUniqueId));
- return false;
+ var msg = String.format("Instance %s has invalid signature.", providerUniqueId);
+ throw new ValidationException(Level.SEVERE, () -> msg);
}
- if(validateAttributes(instanceConfirmation, providerUniqueId)) {
- log.log(Level.FINE, () -> String.format("Instance %s is valid.", providerUniqueId));
- return true;
- }
- return false;
+ validateAttributes(req, providerUniqueId);
+ log.log(Level.FINE, () -> String.format("Instance %s is valid.", providerUniqueId));
}
// TODO Add actual validation. Cannot reuse isValidInstance as identity document is not part of the refresh request.
@@ -108,7 +121,11 @@ public class InstanceValidator {
confirmation.provider,
confirmation.attributes.get(SAN_DNS_ATTRNAME)));
try {
- return validateAttributes(confirmation, getVespaUniqueInstanceId(confirmation));
+ validateAttributes(confirmation, getVespaUniqueInstanceId(confirmation));
+ return true;
+ } catch (ValidationException e) {
+ log.log(e.logLevel(), e.messageSupplier());
+ return false;
} catch (Exception e) {
log.log(Level.WARNING, "Encountered exception while refreshing certificate for confirmation: " + confirmation, e);
return false;
@@ -129,10 +146,11 @@ public class InstanceValidator {
.orElse(null);
}
- private boolean validateAttributes(InstanceConfirmation confirmation, VespaUniqueInstanceId vespaUniqueInstanceId) {
+ private void validateAttributes(InstanceConfirmation confirmation, VespaUniqueInstanceId vespaUniqueInstanceId)
+ throws ValidationException {
if(vespaUniqueInstanceId == null) {
- log.log(Level.WARNING, "Unable to find unique instance ID in refresh request: " + confirmation.toString());
- return false;
+ var msg = "Unable to find unique instance ID in refresh request: " + confirmation.toString();
+ throw new ValidationException(Level.WARNING, () -> msg);
}
// Find node matching vespa unique id
@@ -142,8 +160,8 @@ public class InstanceValidator {
.findFirst() // Should be only one
.orElse(null);
if(node == null) {
- log.log(Level.WARNING, "Invalid InstanceConfirmation, No nodes matching uniqueId: " + vespaUniqueInstanceId);
- return false;
+ var msg = "Invalid InstanceConfirmation, No nodes matching uniqueId: " + vespaUniqueInstanceId;
+ throw new ValidationException(Level.WARNING, () -> msg);
}
// Find list of ipaddresses
@@ -160,10 +178,25 @@ public class InstanceValidator {
// Validate that ipaddresses in request are valid for node
if(! nodeIpAddresses.containsAll(ips)) {
- log.log(Level.WARNING, "Invalid InstanceConfirmation, wrong ip in : " + vespaUniqueInstanceId);
- return false;
+ var msg = "Invalid InstanceConfirmation, wrong ip in : " + vespaUniqueInstanceId;
+ throw new ValidationException(Level.WARNING, () -> msg);
+ }
+
+ var urisCommaSeparated = confirmation.attributes.get(SAN_URI_ATTRNAME);
+ Set<URI> requestedUris;
+ try {
+ requestedUris = Optional.ofNullable(urisCommaSeparated).stream()
+ .flatMap(s -> Arrays.stream(s.split(","))).map(URI::create).collect(Collectors.toSet());
+ } catch (IllegalArgumentException e) {
+ throw new ValidationException(Level.WARNING, () -> "Invalid SAN URIs: " + urisCommaSeparated, e);
+ }
+ var clusterType = node.allocation().map(a -> a.membership().cluster().type()).orElse(null);
+ Set<URI> allowedUris = clusterType != null
+ ? Set.of(ClusterType.from(clusterType.name()).asCertificateSanUri()) : Set.of();
+ if (!allowedUris.containsAll(requestedUris)) {
+ Supplier<String> msg = () -> "Illegal SAN URIs: expected '%s' found '%s'".formatted(allowedUris, requestedUris);
+ throw new ValidationException(Level.WARNING, msg);
}
- return true;
}
private boolean nodeMatchesVespaUniqueId(Node node, VespaUniqueInstanceId vespaUniqueInstanceId) {
@@ -217,4 +250,16 @@ public class InstanceValidator {
return true;
}
+
+ public static class ValidationException extends Exception {
+ private final Level logLevel;
+ private final Supplier<String> msg;
+
+ public ValidationException(Level logLevel, Supplier<String> msg) { this(logLevel, msg, null); }
+ public ValidationException(Level logLevel, Supplier<String> msg, Throwable cause) { super(cause); this.logLevel = logLevel; this.msg = msg; }
+
+ @Override public String getMessage() { return msg.get(); }
+ public Level logLevel() { return logLevel; }
+ public Supplier<String> messageSupplier() { return msg; }
+ }
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java
index 83a7b850365..fec03afab69 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java
@@ -8,12 +8,13 @@ import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
import com.yahoo.text.StringUtilities;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
-import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.ca.instance.InstanceIdentity;
import com.yahoo.vespa.hosted.ca.instance.InstanceRefresh;
import com.yahoo.vespa.hosted.ca.instance.InstanceRegistration;
@@ -47,6 +48,7 @@ public class InstanceSerializer {
private static final String IDD_CREATED_AT_FIELD = "created-at";
private static final String IDD_IPADDRESSES_FIELD = "ip-addresses";
private static final String IDD_IDENTITY_TYPE_FIELD = "identity-type";
+ private static final String IDD_CLUSTER_TYPE_FIELD = "cluster-type";
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
@@ -96,9 +98,12 @@ public class InstanceSerializer {
Set<String> ips = new HashSet<>();
requireField(IDD_IPADDRESSES_FIELD, root).traverse((ArrayTraverser) (__, entry) -> ips.add(entry.asString()));
IdentityType identityType = IdentityType.fromId(requireField(IDD_IDENTITY_TYPE_FIELD, root).asString());
+ var clusterTypeField = root.field(IDD_CLUSTER_TYPE_FIELD);
+ var clusterType = clusterTypeField.valid() ? ClusterType.from(clusterTypeField.asString()) : null;
+
return new SignedIdentityDocument(signature, (int)signingKeyVersion, providerUniqueId, athenzService, (int)documentVersion,
- configserverHostname, instanceHostname, createdAt, ips, identityType);
+ configserverHostname, instanceHostname, createdAt, ips, identityType, clusterType);
}
private static Instant getJsr310Instant(double v) {
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidatorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidatorTest.java
index 2b5165a815a..b996f52fe28 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidatorTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/InstanceValidatorTest.java
@@ -13,11 +13,13 @@ import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
import com.yahoo.vespa.athenz.identityprovider.client.IdentityDocumentSigner;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.InstanceValidator.ValidationException;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -41,7 +43,9 @@ import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.InstanceValidator.SERVICE_PROPERTIES_DOMAIN_KEY;
import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.InstanceValidator.SERVICE_PROPERTIES_SERVICE_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -59,6 +63,7 @@ public class InstanceValidatorTest {
private final String service = "service";
private final AthenzService vespaTenantDomain = new AthenzService("vespa.vespa.tenant");
+ private final AutoGeneratedKeyProvider keyProvider = new AutoGeneratedKeyProvider();
@Test
void application_does_not_exist() {
@@ -133,6 +138,19 @@ public class InstanceValidatorTest {
}
@Test
+ void rejects_invalid_cluster_type_in_csr() {
+ var props = Map.of(SERVICE_PROPERTIES_DOMAIN_KEY, domain, SERVICE_PROPERTIES_SERVICE_KEY, service);
+ var info = new ServiceInfo("serviceName", "type", List.of(), props, "confId", "hostName");
+ var provider = mockSuperModelProvider(mockApplicationInfo(applicationId, 5, List.of(info)));
+ var instanceValidator = new InstanceValidator(keyProvider, provider, mockNodeRepo(), new IdentityDocumentSigner(), vespaTenantDomain);
+ var instanceConfirmation = createRegisterInstanceConfirmation(applicationId, domain, service);
+ instanceConfirmation.set("sanURI", "vespa://cluster-type/content");
+ var exception = assertThrows(ValidationException.class, () -> instanceValidator.validateInstance(instanceConfirmation));
+ var expectedMsg = "Illegal SAN URIs: expected '[vespa://cluster-type/container]' found '[vespa://cluster-type/content]'";
+ assertEquals(expectedMsg, exception.getMessage());
+ }
+
+ @Test
void accepts_valid_refresh_requests() {
NodeRepository nodeRepository = mock(NodeRepository.class);
Nodes nodes = mock(Nodes.class);
@@ -188,8 +206,6 @@ public class InstanceValidatorTest {
NodeRepository nodeRepository = mock(NodeRepository.class);
Nodes nodes = mock(Nodes.class);
when(nodeRepository.nodes()).thenReturn(nodes);
- InstanceValidator instanceValidator = new InstanceValidator(null, null, nodeRepository, new IdentityDocumentSigner(), vespaTenantDomain);
-
List<Node> nodeList = createNodes(10);
Node node = nodeList.get(0);
nodeList = allocateNode(nodeList, node, applicationId);
@@ -197,18 +213,19 @@ public class InstanceValidatorTest {
return nodeRepository;
}
- private InstanceConfirmation createRegisterInstanceConfirmation(ApplicationId applicationId, String domain, String service) {
+ private InstanceConfirmation createRegisterInstanceConfirmation(
+ ApplicationId applicationId, String domain, String service) {
VespaUniqueInstanceId vespaUniqueInstanceId = new VespaUniqueInstanceId(0, "default", applicationId.instance().value(), applicationId.application().value(), applicationId.tenant().value(), "us-north-1", "dev", IdentityType.NODE);
- SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(null,
- 0,
- vespaUniqueInstanceId,
- new AthenzService(domain, service),
- 0,
- "localhost",
- "localhost",
- Instant.now(),
- Collections.emptySet(),
- IdentityType.NODE);
+ var domainService = new AthenzService(domain, service);
+ var clock = Instant.now();
+ var clusterType = ClusterType.CONTAINER;
+ var signature = new IdentityDocumentSigner()
+ .generateSignature(
+ vespaUniqueInstanceId, domainService, "localhost", "localhost", clock, Set.of(),
+ IdentityType.NODE, clusterType, keyProvider.getPrivateKey(0));
+ SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(
+ signature, 0, vespaUniqueInstanceId, domainService, 0, "localhost", "localhost",
+ clock, Collections.emptySet(), IdentityType.NODE, clusterType);
return createInstanceConfirmation(vespaUniqueInstanceId, domain, service, signedIdentityDocument);
}
@@ -228,6 +245,7 @@ public class InstanceValidatorTest {
.map(EntityBindingsMapper::toSignedIdentityDocumentEntity)
.orElse(null));
instanceConfirmation.set("sanDNS", vespaUniqueInstanceId.asDottedString() + ".instanceid.athenz.dev-us-north-1.vespa.yahoo.cloud");
+ instanceConfirmation.set("sanURI", "vespa://cluster-type/container");
return instanceConfirmation;
}
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java
index df75e09b957..ca624918beb 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializerTest.java
@@ -7,6 +7,7 @@ import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.text.StringUtilities;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
@@ -46,7 +47,8 @@ public class InstanceSerializerTest {
"instancehostname",
Instant.now().truncatedTo(ChronoUnit.MICROS), // Truncate to the precision given from EntityBindingsMapper.toAttestationData()
Collections.emptySet(),
- IdentityType.NODE);
+ IdentityType.NODE,
+ ClusterType.CONTAINER);
var json = String.format("{\n" +
" \"provider\": \"provider_prod_us-north-1\",\n" +
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
index b903712254b..2e5d269b720 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
@@ -14,7 +14,6 @@ import com.yahoo.vespa.athenz.client.zts.ZtsClientException;
import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocumentClient;
-import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.client.CsrGenerator;
import com.yahoo.vespa.athenz.identityprovider.client.DefaultIdentityDocumentClient;
import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
@@ -180,9 +179,9 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
private void registerIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, ContainerPath identityDocumentFile) {
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
- SignedIdentityDocument signedIdentityDocument = identityDocumentClient.getNodeIdentityDocument(context.hostname().value());
+ var doc = identityDocumentClient.getNodeIdentityDocument(context.hostname().value());
Pkcs10Csr csr = csrGenerator.generateInstanceCsr(
- context.identity(), signedIdentityDocument.providerUniqueId(), signedIdentityDocument.ipAddresses(), keyPair);
+ context.identity(), doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair);
// Set up a hostname verified for zts if this is configured to use the config server (internal zts) apis
HostnameVerifier ztsHostNameVerifier = useInternalZts
@@ -193,19 +192,19 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
ztsClient.registerInstance(
configserverIdentity,
context.identity(),
- EntityBindingsMapper.toAttestationData(signedIdentityDocument),
+ EntityBindingsMapper.toAttestationData(doc),
csr);
- EntityBindingsMapper.writeSignedIdentityDocumentToFile(identityDocumentFile, signedIdentityDocument);
+ EntityBindingsMapper.writeSignedIdentityDocumentToFile(identityDocumentFile, doc);
writePrivateKeyAndCertificate(privateKeyFile, keyPair.getPrivate(), certificateFile, instanceIdentity.certificate());
context.log(logger, "Instance successfully registered and credentials written to file");
}
}
private void refreshIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, ContainerPath identityDocumentFile) {
- SignedIdentityDocument identityDocument = EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile);
+ var doc = EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile);
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
Pkcs10Csr csr = csrGenerator.generateInstanceCsr(
- context.identity(), identityDocument.providerUniqueId(), identityDocument.ipAddresses(), keyPair);
+ context.identity(), doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair);
SSLContext containerIdentitySslContext = new SslContextBuilder().withKeyStore(privateKeyFile, certificateFile)
.withTrustStore(ztsTrustStorePath)
@@ -221,7 +220,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
ztsClient.refreshInstance(
configserverIdentity,
context.identity(),
- identityDocument.providerUniqueId().asDottedString(),
+ doc.providerUniqueId().asDottedString(),
csr);
writePrivateKeyAndCertificate(privateKeyFile, keyPair.getPrivate(), certificateFile, instanceIdentity.certificate());
context.log(logger, "Instance successfully refreshed and credentials written to file");
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
index 0ae253985a6..3931f6ab7b7 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/Capability.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/Capability.java
@@ -7,6 +7,7 @@ import java.util.Arrays;
* @author bjorncs
*/
public enum Capability {
+ NONE("vespa.none"), // placeholder for no capabilities
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"),
diff --git a/vespa-athenz/pom.xml b/vespa-athenz/pom.xml
index d71948b59d3..29a4058e180 100644
--- a/vespa-athenz/pom.xml
+++ b/vespa-athenz/pom.xml
@@ -18,16 +18,23 @@
<dependencies>
<!-- provided -->
<dependency>
+ <!-- required for bundle-plugin to generate import-package statements for Java's standard library -->
<groupId>com.yahoo.vespa</groupId>
- <artifactId>container-dev</artifactId>
+ <artifactId>jdisc_core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>container-disc</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
- <exclusions>
- <exclusion>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- </exclusion>
- </exclusions>
</dependency>
<dependency>
<groupId>com.yahoo.vespa</groupId>
@@ -46,6 +53,45 @@
<artifactId>jna</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespajlib</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>defaults</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>component</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>config-lib</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
<!-- test -->
<dependency>
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/ClusterType.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/ClusterType.java
new file mode 100644
index 00000000000..3702f693a7b
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/ClusterType.java
@@ -0,0 +1,40 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.vespa.athenz.identityprovider.api;
+
+import java.net.URI;
+
+/**
+ * Vespa cluster type
+ *
+ * @author bjorncs
+ */
+public enum ClusterType {
+ ADMIN,
+ CONTAINER,
+ CONTENT,
+ COMBINED;
+
+ public static ClusterType from(String cfgValue) {
+ return switch (cfgValue) {
+ case "admin" -> ADMIN;
+ case "container" -> CONTAINER;
+ case "content" -> CONTENT;
+ case "combined" -> COMBINED;
+ default -> throw new IllegalArgumentException("Illegal cluster type '" + cfgValue + "'");
+ };
+ }
+
+ public String toConfigValue() {
+ return switch (this) {
+ case ADMIN -> "admin";
+ case CONTAINER -> "container";
+ case CONTENT -> "content";
+ case COMBINED -> "combined";
+ };
+ }
+
+ public URI asCertificateSanUri() { return URI.create("vespa://cluster-type/%s".formatted(toConfigValue())); }
+
+}
+
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
index 08afdd91542..201b550f6ae 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
@@ -14,6 +14,7 @@ import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.util.Optional;
import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.fromDottedString;
@@ -38,16 +39,17 @@ public class EntityBindingsMapper {
public static SignedIdentityDocument toSignedIdentityDocument(SignedIdentityDocumentEntity entity) {
return new SignedIdentityDocument(
- entity.signature,
- entity.signingKeyVersion,
- fromDottedString(entity.providerUniqueId),
- new AthenzService(entity.providerService),
- entity.documentVersion,
- entity.configServerHostname,
- entity.instanceHostname,
- entity.createdAt,
- entity.ipAddresses,
- IdentityType.fromId(entity.identityType));
+ entity.signature(),
+ entity.signingKeyVersion(),
+ fromDottedString(entity.providerUniqueId()),
+ new AthenzService(entity.providerService()),
+ entity.documentVersion(),
+ entity.configServerHostname(),
+ entity.instanceHostname(),
+ entity.createdAt(),
+ entity.ipAddresses(),
+ IdentityType.fromId(entity.identityType()),
+ ClusterType.from(entity.clusterType()));
}
public static SignedIdentityDocumentEntity toSignedIdentityDocumentEntity(SignedIdentityDocument model) {
@@ -61,7 +63,8 @@ public class EntityBindingsMapper {
model.instanceHostname(),
model.createdAt(),
model.ipAddresses(),
- model.identityType().id());
+ model.identityType().id(),
+ Optional.ofNullable(model.clusterType()).map(ClusterType::toConfigValue).orElse(null));
}
public static SignedIdentityDocument readSignedIdentityDocumentFromFile(Path file) {
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
index 81c298efb74..e331fc1f6e8 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.athenz.identityprovider.api;
import com.yahoo.vespa.athenz.api.AthenzService;
import java.time.Instant;
-import java.util.Objects;
import java.util.Set;
/**
@@ -12,117 +11,10 @@ import java.util.Set;
*
* @author bjorncs
*/
-public class SignedIdentityDocument {
- public static final int DEFAULT_DOCUMENT_VERSION = 1;
+public record SignedIdentityDocument(String signature, int signingKeyVersion, VespaUniqueInstanceId providerUniqueId,
+ AthenzService providerService, int documentVersion, String configServerHostname,
+ String instanceHostname, Instant createdAt, Set<String> ipAddresses,
+ IdentityType identityType, ClusterType clusterType) {
+ public static final int DEFAULT_DOCUMENT_VERSION = 2;
- private final String signature;
- private final int signingKeyVersion;
- private final VespaUniqueInstanceId providerUniqueId;
- private final AthenzService providerService;
- private final int documentVersion;
- private final String configServerHostname;
- private final String instanceHostname;
- private final Instant createdAt;
- private final Set<String> ipAddresses;
- private final IdentityType identityType;
-
- public SignedIdentityDocument(String signature,
- int signingKeyVersion,
- VespaUniqueInstanceId providerUniqueId,
- AthenzService providerService,
- int documentVersion,
- String configServerHostname,
- String instanceHostname,
- Instant createdAt,
- Set<String> ipAddresses,
- IdentityType identityType) {
- this.signature = signature;
- this.signingKeyVersion = signingKeyVersion;
- this.providerUniqueId = providerUniqueId;
- this.providerService = providerService;
- this.documentVersion = documentVersion;
- this.configServerHostname = configServerHostname;
- this.instanceHostname = instanceHostname;
- this.createdAt = createdAt;
- this.ipAddresses = ipAddresses;
- this.identityType = identityType;
- }
-
- public String signature() {
- return signature;
- }
-
- public int signingKeyVersion() {
- return signingKeyVersion;
- }
-
- public VespaUniqueInstanceId providerUniqueId() {
- return providerUniqueId;
- }
-
- public AthenzService providerService() {
- return providerService;
- }
-
- public int documentVersion() {
- return documentVersion;
- }
-
- public String configServerHostname() {
- return configServerHostname;
- }
-
- public String instanceHostname() {
- return instanceHostname;
- }
-
- public Instant createdAt() {
- return createdAt;
- }
-
- public Set<String> ipAddresses() {
- return ipAddresses;
- }
-
- public IdentityType identityType() {
- return identityType;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- SignedIdentityDocument that = (SignedIdentityDocument) o;
- return signingKeyVersion == that.signingKeyVersion &&
- documentVersion == that.documentVersion &&
- Objects.equals(signature, that.signature) &&
- Objects.equals(providerUniqueId, that.providerUniqueId) &&
- Objects.equals(providerService, that.providerService) &&
- Objects.equals(configServerHostname, that.configServerHostname) &&
- Objects.equals(instanceHostname, that.instanceHostname) &&
- Objects.equals(createdAt, that.createdAt) &&
- Objects.equals(ipAddresses, that.ipAddresses) &&
- identityType == that.identityType;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(signature, signingKeyVersion, providerUniqueId, providerService, documentVersion, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType);
- }
-
- @Override
- public String toString() {
- return "SignedIdentityDocument{" +
- "signature='" + signature + '\'' +
- ", signingKeyVersion=" + signingKeyVersion +
- ", providerUniqueId=" + providerUniqueId +
- ", providerService=" + providerService +
- ", documentVersion=" + documentVersion +
- ", configServerHostname='" + configServerHostname + '\'' +
- ", instanceHostname='" + instanceHostname + '\'' +
- ", createdAt=" + createdAt +
- ", ipAddresses=" + ipAddresses +
- ", identityType=" + identityType +
- '}';
- }
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java
index 97181e44747..e74c929223e 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/VespaUniqueInstanceId.java
@@ -1,41 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identityprovider.api;
-import java.util.Objects;
-
/**
* Represents the unique instance id as used in Vespa's integration with Athenz Copper Argos
*
* @author bjorncs
*/
-public class VespaUniqueInstanceId {
-
- private final int clusterIndex;
- private final String clusterId;
- private final String instance;
- private final String application;
- private final String tenant;
- private final String region;
- private final String environment;
- private final IdentityType type;
-
- public VespaUniqueInstanceId(int clusterIndex,
- String clusterId,
- String instance,
- String application,
- String tenant,
- String region,
- String environment,
- IdentityType type) {
- this.clusterIndex = clusterIndex;
- this.clusterId = clusterId;
- this.instance = instance;
- this.application = application;
- this.tenant = tenant;
- this.region = region;
- this.environment = environment;
- this.type = type;
- }
+public record VespaUniqueInstanceId(int clusterIndex, String clusterId, String instance, String application,
+ String tenant, String region, String environment, IdentityType type) {
public static VespaUniqueInstanceId fromDottedString(String instanceId) {
@@ -52,68 +24,4 @@ public class VespaUniqueInstanceId {
"%d.%s.%s.%s.%s.%s.%s.%s",
clusterIndex, clusterId, instance, application, tenant, region, environment, type.id());
}
-
- public int clusterIndex() {
- return clusterIndex;
- }
-
- public String clusterId() {
- return clusterId;
- }
-
- public String instance() {
- return instance;
- }
-
- public String application() {
- return application;
- }
-
- public String tenant() {
- return tenant;
- }
-
- public String region() {
- return region;
- }
-
- public String environment() {
- return environment;
- }
-
- public IdentityType type() { return type; }
-
- @Override
- public String toString() {
- return "VespaUniqueInstanceId{" +
- "clusterIndex=" + clusterIndex +
- ", clusterId='" + clusterId + '\'' +
- ", instance='" + instance + '\'' +
- ", application='" + application + '\'' +
- ", tenant='" + tenant + '\'' +
- ", region='" + region + '\'' +
- ", environment='" + environment + '\'' +
- ", type=" + type +
- '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- VespaUniqueInstanceId that = (VespaUniqueInstanceId) o;
- return clusterIndex == that.clusterIndex &&
- Objects.equals(clusterId, that.clusterId) &&
- Objects.equals(instance, that.instance) &&
- Objects.equals(application, that.application) &&
- Objects.equals(tenant, that.tenant) &&
- Objects.equals(region, that.region) &&
- Objects.equals(environment, that.environment) &&
- type == that.type;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(clusterIndex, clusterId, instance, application, tenant, region, environment, type);
- }
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
index c9448a2d97a..2fb709615da 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
@@ -1,89 +1,25 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identityprovider.api.bindings;
-import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
-import java.util.Objects;
import java.util.Set;
/**
* @author bjorncs
*/
@JsonIgnoreProperties(ignoreUnknown = true)
-public class SignedIdentityDocumentEntity {
-
- @JsonProperty("signature") public final String signature;
- @JsonProperty("signing-key-version") public final int signingKeyVersion;
- @JsonProperty("provider-unique-id") public final String providerUniqueId; // String representation
- @JsonProperty("provider-service") public final String providerService;
- @JsonProperty("document-version") public final int documentVersion;
- @JsonProperty("configserver-hostname") public final String configServerHostname;
- @JsonProperty("instance-hostname") public final String instanceHostname;
- @JsonProperty("created-at") public final Instant createdAt;
- @JsonProperty("ip-addresses") public final Set<String> ipAddresses;
- @JsonProperty("identity-type") public final String identityType;
-
- @JsonCreator
- public SignedIdentityDocumentEntity(@JsonProperty("signature") String signature,
- @JsonProperty("signing-key-version") int signingKeyVersion,
- @JsonProperty("provider-unique-id") String providerUniqueId,
- @JsonProperty("provider-service") String providerService,
- @JsonProperty("document-version") int documentVersion,
- @JsonProperty("configserver-hostname") String configServerHostname,
- @JsonProperty("instance-hostname") String instanceHostname,
- @JsonProperty("created-at") Instant createdAt,
- @JsonProperty("ip-addresses") Set<String> ipAddresses,
- @JsonProperty("identity-type") String identityType) {
- this.signature = signature;
- this.signingKeyVersion = signingKeyVersion;
- this.providerUniqueId = providerUniqueId;
- this.providerService = providerService;
- this.documentVersion = documentVersion;
- this.configServerHostname = configServerHostname;
- this.instanceHostname = instanceHostname;
- this.createdAt = createdAt;
- this.ipAddresses = ipAddresses;
- this.identityType = identityType;
- }
-
- @Override
- public String toString() {
- return "SignedIdentityDocumentEntity{" +
- ", signature='" + signature + '\'' +
- ", signingKeyVersion=" + signingKeyVersion +
- ", providerUniqueId='" + providerUniqueId + '\'' +
- ", providerService='" + providerService + '\'' +
- ", documentVersion=" + documentVersion +
- ", configServerHostname='" + configServerHostname + '\'' +
- ", instanceHostname='" + instanceHostname + '\'' +
- ", createdAt=" + createdAt +
- ", ipAddresses=" + ipAddresses +
- ", identityType=" + identityType +
- '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- SignedIdentityDocumentEntity that = (SignedIdentityDocumentEntity) o;
- return signingKeyVersion == that.signingKeyVersion &&
- documentVersion == that.documentVersion &&
- Objects.equals(signature, that.signature) &&
- Objects.equals(providerUniqueId, that.providerUniqueId) &&
- Objects.equals(providerService, that.providerService) &&
- Objects.equals(configServerHostname, that.configServerHostname) &&
- Objects.equals(instanceHostname, that.instanceHostname) &&
- Objects.equals(createdAt, that.createdAt) &&
- Objects.equals(ipAddresses, that.ipAddresses) &&
- Objects.equals(identityType, that.identityType);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(signature, signingKeyVersion, providerUniqueId, providerService, documentVersion, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType);
- }
+public record SignedIdentityDocumentEntity(@JsonProperty("signature") String signature,
+ @JsonProperty("signing-key-version") int signingKeyVersion,
+ @JsonProperty("provider-unique-id") String providerUniqueId,
+ @JsonProperty("provider-service") String providerService,
+ @JsonProperty("document-version") int documentVersion,
+ @JsonProperty("configserver-hostname") String configServerHostname,
+ @JsonProperty("instance-hostname") String instanceHostname,
+ @JsonProperty("created-at") Instant createdAt,
+ @JsonProperty("ip-addresses") Set<String> ipAddresses,
+ @JsonProperty("identity-type") String identityType,
+ @JsonProperty("cluster-type") String clusterType) {
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
index 91b95cb6084..cc9d3b2be65 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
@@ -79,6 +79,7 @@ class AthenzCredentialsService {
tenantIdentity,
document.providerUniqueId(),
document.ipAddresses(),
+ document.clusterType(),
keyPair);
try (ZtsClient ztsClient = new DefaultZtsClient.Builder(ztsEndpoint).withIdentityProvider(nodeIdentityProvider).build()) {
@@ -100,6 +101,7 @@ class AthenzCredentialsService {
tenantIdentity,
document.providerUniqueId(),
document.ipAddresses(),
+ document.clusterType(),
newKeyPair);
try (ZtsClient ztsClient = new DefaultZtsClient.Builder(ztsEndpoint).withSslContext(sslContext).build()) {
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java
index 0caf3bdfd0b..b9f9f3862c2 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java
@@ -4,18 +4,18 @@ package com.yahoo.vespa.athenz.identityprovider.client;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
-import com.yahoo.component.annotation.Inject;
import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.annotation.Inject;
import com.yahoo.container.core.identity.IdentityConfig;
import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider;
import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException;
import com.yahoo.jdisc.Metric;
import com.yahoo.metrics.ContainerMetrics;
import com.yahoo.security.KeyStoreBuilder;
+import com.yahoo.security.MutableX509KeyManager;
import com.yahoo.security.Pkcs10Csr;
import com.yahoo.security.SslContextBuilder;
import com.yahoo.security.X509CertificateWithKey;
-import com.yahoo.security.MutableX509KeyManager;
import com.yahoo.vespa.athenz.api.AthenzAccessToken;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzRole;
@@ -48,7 +48,6 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
import static com.yahoo.security.KeyStoreType.PKCS12;
@@ -299,7 +298,9 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
}
private X509Certificate requestRoleCertificate(AthenzRole role) {
- Pkcs10Csr csr = csrGenerator.generateRoleCsr(identity, role, credentials.getIdentityDocument().providerUniqueId(), credentials.getKeyPair());
+ var doc = credentials.getIdentityDocument();
+ Pkcs10Csr csr = csrGenerator.generateRoleCsr(
+ identity, role, doc.providerUniqueId(), doc.clusterType(), credentials.getKeyPair());
try (ZtsClient client = createZtsClient()) {
X509Certificate roleCertificate = client.getRoleCertificate(role, csr);
updateRoleKeyManager(role, roleCertificate);
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/CsrGenerator.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/CsrGenerator.java
index 21ce30fd244..353f0fdf067 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/CsrGenerator.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/CsrGenerator.java
@@ -1,12 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identityprovider.client;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.api.AthenzRole;
-import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
import com.yahoo.security.Pkcs10Csr;
import com.yahoo.security.Pkcs10CsrBuilder;
import com.yahoo.security.SubjectAlternativeName;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzRole;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
+import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
import javax.security.auth.x500.X500Principal;
import java.security.KeyPair;
@@ -14,8 +15,9 @@ import java.util.Set;
import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_RSA;
import static com.yahoo.security.SubjectAlternativeName.Type.DNS;
-import static com.yahoo.security.SubjectAlternativeName.Type.IP;
import static com.yahoo.security.SubjectAlternativeName.Type.EMAIL;
+import static com.yahoo.security.SubjectAlternativeName.Type.IP;
+import static com.yahoo.security.SubjectAlternativeName.Type.URI;
/**
* Generates a {@link Pkcs10Csr} for an instance.
@@ -35,6 +37,7 @@ public class CsrGenerator {
public Pkcs10Csr generateInstanceCsr(AthenzIdentity instanceIdentity,
VespaUniqueInstanceId instanceId,
Set<String> ipAddresses,
+ ClusterType clusterType,
KeyPair keyPair) {
X500Principal subject = new X500Principal(String.format("OU=%s, CN=%s", providerService, instanceIdentity.getFullName()));
// Add SAN dnsname <service>.<domain-with-dashes>.<provider-dnsname-suffix>
@@ -48,6 +51,7 @@ public class CsrGenerator {
instanceIdentity.getDomainName().replace(".", "-"),
dnsSuffix))
.addSubjectAlternativeName(DNS, getIdentitySAN(instanceId));
+ if (clusterType != null) pkcs10CsrBuilder.addSubjectAlternativeName(URI, clusterType.asCertificateSanUri().toString());
ipAddresses.forEach(ip -> pkcs10CsrBuilder.addSubjectAlternativeName(new SubjectAlternativeName(IP, ip)));
return pkcs10CsrBuilder.build();
}
@@ -55,12 +59,14 @@ public class CsrGenerator {
public Pkcs10Csr generateRoleCsr(AthenzIdentity identity,
AthenzRole role,
VespaUniqueInstanceId instanceId,
+ ClusterType clusterType,
KeyPair keyPair) {
X500Principal principal = new X500Principal(String.format("OU=%s, cn=%s:role.%s", providerService, role.domain().getName(), role.roleName()));
- return Pkcs10CsrBuilder.fromKeypair(principal, keyPair, SHA256_WITH_RSA)
+ var b = Pkcs10CsrBuilder.fromKeypair(principal, keyPair, SHA256_WITH_RSA)
.addSubjectAlternativeName(DNS, getIdentitySAN(instanceId))
- .addSubjectAlternativeName(EMAIL, String.format("%s.%s@%s", identity.getDomainName(), identity.getName(), dnsSuffix))
- .build();
+ .addSubjectAlternativeName(EMAIL, String.format("%s.%s@%s", identity.getDomainName(), identity.getName(), dnsSuffix));
+ if (clusterType != null) b.addSubjectAlternativeName(URI, clusterType.asCertificateSanUri().toString());
+ return b.build();
}
private String getIdentitySAN(VespaUniqueInstanceId instanceId) {
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java
index 1c1dcb655c0..bfc1b3aad46 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.athenz.identityprovider.client;
import com.yahoo.security.SignatureUtils;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
@@ -34,11 +35,14 @@ public class IdentityDocumentSigner {
Instant createdAt,
Set<String> ipAddresses,
IdentityType identityType,
+ ClusterType clusterType,
PrivateKey privateKey) {
try {
Signature signer = SignatureUtils.createSigner(privateKey);
signer.initSign(privateKey);
- writeToSigner(signer, providerUniqueId, providerService, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType);
+ writeToSigner(
+ signer, providerUniqueId, providerService, configServerHostname, instanceHostname, createdAt,
+ ipAddresses, identityType, clusterType);
byte[] signature = signer.sign();
return Base64.getEncoder().encodeToString(signature);
} catch (GeneralSecurityException e) {
@@ -50,7 +54,9 @@ public class IdentityDocumentSigner {
try {
Signature signer = SignatureUtils.createVerifier(publicKey);
signer.initVerify(publicKey);
- writeToSigner(signer, doc.providerUniqueId(), doc.providerService(), doc.configServerHostname(), doc.instanceHostname(), doc.createdAt(), doc.ipAddresses(), doc.identityType());
+ writeToSigner(
+ signer, doc.providerUniqueId(), doc.providerService(), doc.configServerHostname(),
+ doc.instanceHostname(), doc.createdAt(), doc.ipAddresses(), doc.identityType(), doc.clusterType());
return signer.verify(Base64.getDecoder().decode(doc.signature()));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
@@ -64,7 +70,8 @@ public class IdentityDocumentSigner {
String instanceHostname,
Instant createdAt,
Set<String> ipAddresses,
- IdentityType identityType) throws SignatureException {
+ IdentityType identityType,
+ ClusterType clusterType) throws SignatureException {
signer.update(providerUniqueId.asDottedString().getBytes(UTF_8));
signer.update(providerService.getFullName().getBytes(UTF_8));
signer.update(configServerHostname.getBytes(UTF_8));
@@ -76,5 +83,6 @@ public class IdentityDocumentSigner {
signer.update(ipAddress.getBytes(UTF_8));
}
signer.update(identityType.id().getBytes(UTF_8));
+ if (clusterType != null) signer.update(clusterType.toConfigValue().getBytes(UTF_8));
}
}
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java
index 5253fee0802..f53518d9a07 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.athenz.identityprovider.client;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyUtils;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
@@ -24,24 +25,41 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class IdentityDocumentSignerTest {
public static final int KEY_VERSION = 0;
+ private static final IdentityType identityType = TENANT;
+ private static final VespaUniqueInstanceId id =
+ new VespaUniqueInstanceId(1, "cluster-id", "instance", "application", "tenant", "region", "environment", identityType);
+ private static final AthenzService providerService = new AthenzService("vespa", "service");
+ private static final KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
+ private static final String configserverHostname = "configserverhostname";
+ private static final String instanceHostname = "instancehostname";
+ private static final Instant createdAt = Instant.EPOCH;
+ private static final HashSet<String> ipAddresses = new HashSet<>(Arrays.asList("1.2.3.4", "::1"));
+ private static final ClusterType clusterType = ClusterType.CONTAINER;
+
@Test
void generates_and_validates_signature() {
IdentityDocumentSigner signer = new IdentityDocumentSigner();
- IdentityType identityType = TENANT;
- VespaUniqueInstanceId id =
- new VespaUniqueInstanceId(1, "cluster-id", "instance", "application", "tenant", "region", "environment", identityType);
- AthenzService providerService = new AthenzService("vespa", "service");
- KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
- String configserverHostname = "configserverhostname";
- String instanceHostname = "instancehostname";
- Instant createdAt = Instant.EPOCH;
- HashSet<String> ipAddresses = new HashSet<>(Arrays.asList("1.2.3.4", "::1"));
String signature =
- signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt, ipAddresses, identityType, keyPair.getPrivate());
+ signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt,
+ ipAddresses, identityType, clusterType, keyPair.getPrivate());
+
+ SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(
+ signature, KEY_VERSION, id, providerService, DEFAULT_DOCUMENT_VERSION, configserverHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, clusterType);
+
+ assertTrue(signer.hasValidSignature(signedIdentityDocument, keyPair.getPublic()));
+ }
+
+ @Test
+ void handles_missing_cluster_type() {
+ IdentityDocumentSigner signer = new IdentityDocumentSigner();
+ String signature =
+ signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt,
+ ipAddresses, identityType, /*clusterType*/null, keyPair.getPrivate());
SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(
- signature, KEY_VERSION, id, providerService,
- DEFAULT_DOCUMENT_VERSION, configserverHostname, instanceHostname, createdAt, ipAddresses, identityType);
+ signature, KEY_VERSION, id, providerService, DEFAULT_DOCUMENT_VERSION, configserverHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, /*clusterType*/null);
assertTrue(signer.hasValidSignature(signedIdentityDocument, keyPair.getPublic()));
}
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/InstanceCsrGeneratorTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/InstanceCsrGeneratorTest.java
index 3e4f8541aaa..5124f11fd05 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/InstanceCsrGeneratorTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/InstanceCsrGeneratorTest.java
@@ -3,15 +3,20 @@ package com.yahoo.vespa.athenz.identityprovider.client;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyUtils;
+import com.yahoo.security.Pkcs10Csr;
+import com.yahoo.security.SubjectAlternativeName;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
-import com.yahoo.security.Pkcs10Csr;
import org.junit.jupiter.api.Test;
import javax.security.auth.x500.X500Principal;
import java.security.KeyPair;
import java.util.Collections;
+import java.util.Set;
+import static com.yahoo.security.SubjectAlternativeName.Type.DNS;
+import static com.yahoo.security.SubjectAlternativeName.Type.URI;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@@ -24,14 +29,20 @@ public class InstanceCsrGeneratorTest {
private static final String ATHENZ_SERVICE = "foo.bar";
@Test
- void it_generates_csr_with_correct_subject() {
+ void generates_correct_subject_and_alternative_names() {
CsrGenerator csrGenerator = new CsrGenerator(DNS_SUFFIX, PROVIDER_SERVICE);
AthenzService service = new AthenzService(ATHENZ_SERVICE);
VespaUniqueInstanceId vespaUniqueInstanceId = VespaUniqueInstanceId.fromDottedString("0.default.default.foo-app.vespa.us-north-1.prod.node");
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
- Pkcs10Csr csr = csrGenerator.generateInstanceCsr(service, vespaUniqueInstanceId, Collections.emptySet(), keyPair);
+ Pkcs10Csr csr = csrGenerator.generateInstanceCsr(service, vespaUniqueInstanceId, Collections.emptySet(), ClusterType.CONTAINER, keyPair);
assertEquals(new X500Principal(String.format("OU=%s, CN=%s", PROVIDER_SERVICE, ATHENZ_SERVICE)), csr.getSubject());
+ var actualSans = Set.copyOf(csr.getSubjectAlternativeNames());
+ var expectedSans = Set.of(
+ new SubjectAlternativeName(DNS, "bar.foo.prod-us-north-1.vespa.yahoo.cloud"),
+ new SubjectAlternativeName(DNS, "0.default.default.foo-app.vespa.us-north-1.prod.node.instanceid.athenz.prod-us-north-1.vespa.yahoo.cloud"),
+ new SubjectAlternativeName(URI, "vespa://cluster-type/container"));
+ assertEquals(expectedSans, actualSans);
}
}