summaryrefslogtreecommitdiffstats
path: root/athenz-identity-provider-service/src/main/java/com/yahoo
diff options
context:
space:
mode:
Diffstat (limited to 'athenz-identity-provider-service/src/main/java/com/yahoo')
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java25
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java34
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java11
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java72
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/InstanceSerializer.java53
5 files changed, 169 insertions, 26 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
index f1a93e58526..89e7e340641 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java
@@ -14,6 +14,7 @@ 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.KeyProvider;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -34,12 +35,15 @@ import java.util.stream.Stream;
*/
public class InstanceValidator {
- private static final AthenzService TENANT_DOCKER_CONTAINER_IDENTITY = new AthenzService("vespa.vespa.tenant");
private static final Logger log = Logger.getLogger(InstanceValidator.class.getName());
static final String SERVICE_PROPERTIES_DOMAIN_KEY = "identity.domain";
static final String SERVICE_PROPERTIES_SERVICE_KEY = "identity.service";
static final String INSTANCE_ID_DELIMITER = ".instanceid.athenz.";
+ public static final String SAN_IPS_ATTRNAME = "sanIP";
+ public static final String SAN_DNS_ATTRNAME = "sanDNS";
+
+ private final AthenzService tenantDockerContainerIdentity;
private final IdentityDocumentSigner signer;
private final KeyProvider keyProvider;
private final SuperModelProvider superModelProvider;
@@ -48,18 +52,21 @@ public class InstanceValidator {
@Inject
public InstanceValidator(KeyProvider keyProvider,
SuperModelProvider superModelProvider,
- NodeRepository nodeRepository) {
- this(keyProvider, superModelProvider, nodeRepository, new IdentityDocumentSigner());
+ NodeRepository nodeRepository,
+ AthenzProviderServiceConfig config) {
+ this(keyProvider, superModelProvider, nodeRepository, new IdentityDocumentSigner(), new AthenzService(config.tenantService()));
}
public InstanceValidator(KeyProvider keyProvider,
SuperModelProvider superModelProvider,
NodeRepository nodeRepository,
- IdentityDocumentSigner identityDocumentSigner){
+ IdentityDocumentSigner identityDocumentSigner,
+ AthenzService tenantIdentity){
this.keyProvider = keyProvider;
this.superModelProvider = superModelProvider;
this.nodeRepository = nodeRepository;
this.signer = identityDocumentSigner;
+ this.tenantDockerContainerIdentity = tenantIdentity;
}
public boolean isValidInstance(InstanceConfirmation instanceConfirmation) {
@@ -96,7 +103,7 @@ public class InstanceValidator {
log.log(LogLevel.INFO, () -> String.format("Accepting refresh for instance with identity '%s', provider '%s', instanceId '%s'.",
new AthenzService(confirmation.domain, confirmation.service).getFullName(),
confirmation.provider,
- confirmation.attributes.get("sanDNS")));
+ confirmation.attributes.get(SAN_DNS_ATTRNAME)));
try {
return validateAttributes(confirmation, getVespaUniqueInstanceId(confirmation));
} catch (Exception e) {
@@ -107,7 +114,7 @@ public class InstanceValidator {
private VespaUniqueInstanceId getVespaUniqueInstanceId(InstanceConfirmation instanceConfirmation) {
// Find a list of SAN DNS
- List<String> sanDNS = Optional.ofNullable(instanceConfirmation.attributes.get("sanDNS"))
+ List<String> sanDNS = Optional.ofNullable(instanceConfirmation.attributes.get(SAN_DNS_ATTRNAME))
.map(s -> s.split(","))
.map(Arrays::asList)
.map(List::stream)
@@ -124,7 +131,7 @@ public class InstanceValidator {
private boolean validateAttributes(InstanceConfirmation confirmation, VespaUniqueInstanceId vespaUniqueInstanceId) {
if(vespaUniqueInstanceId == null) {
- log.log(LogLevel.WARNING, "Unabe to find unique instance ID in refresh request: " + confirmation.toString());
+ log.log(LogLevel.WARNING, "Unable to find unique instance ID in refresh request: " + confirmation.toString());
return false;
}
@@ -140,7 +147,7 @@ public class InstanceValidator {
}
// Find list of ipaddresses
- List<InetAddress> ips = Optional.ofNullable(confirmation.attributes.get("sanIP"))
+ List<InetAddress> ips = Optional.ofNullable(confirmation.attributes.get(SAN_IPS_ATTRNAME))
.map(s -> s.split(","))
.map(Arrays::asList)
.map(List::stream)
@@ -184,7 +191,7 @@ public class InstanceValidator {
return false;
}
- if (TENANT_DOCKER_CONTAINER_IDENTITY.equals(new AthenzService(domain, service))) {
+ if (tenantDockerContainerIdentity.equals(new AthenzService(domain, service))) {
return true;
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java
index 308127e29c7..b5a2405167a 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/Certificates.java
@@ -4,14 +4,17 @@ package com.yahoo.vespa.hosted.ca;
import com.yahoo.security.Pkcs10Csr;
import com.yahoo.security.SubjectAlternativeName;
import com.yahoo.security.X509CertificateBuilder;
+import com.yahoo.security.X509CertificateUtils;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Clock;
import java.time.Duration;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA;
import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME;
@@ -53,14 +56,22 @@ public class Certificates {
/** Returns instance ID parsed from the Subject Alternative Names in given csr */
public static String instanceIdFrom(Pkcs10Csr csr) {
- return csr.getSubjectAlternativeNames().stream()
- .filter(san -> san.getType() == DNS_NAME)
- .map(SubjectAlternativeName::getValue)
- .map(Certificates::parseInstanceId)
- .flatMap(Optional::stream)
- .map(VespaUniqueInstanceId::asDottedString)
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException("No instance ID found in CSR"));
+ return getInstanceIdFromSAN(csr.getSubjectAlternativeNames())
+ .orElseThrow(() -> new IllegalArgumentException("No instance ID found in CSR"));
+ }
+
+ public static Optional<String> instanceIdFrom(X509Certificate certificate) {
+ return getInstanceIdFromSAN(X509CertificateUtils.getSubjectAlternativeNames(certificate));
+ }
+
+ private static Optional<String> getInstanceIdFromSAN(List<SubjectAlternativeName> subjectAlternativeNames) {
+ return subjectAlternativeNames.stream()
+ .filter(san -> san.getType() == DNS_NAME)
+ .map(SubjectAlternativeName::getValue)
+ .map(Certificates::parseInstanceId)
+ .flatMap(Optional::stream)
+ .map(VespaUniqueInstanceId::asDottedString)
+ .findFirst();
}
private static Optional<VespaUniqueInstanceId> parseInstanceId(String dnsName) {
@@ -74,4 +85,11 @@ public class Certificates {
}
}
+ public static String getSubjectAlternativeNames(Pkcs10Csr csr, SubjectAlternativeName.Type sanType) {
+ return csr.getSubjectAlternativeNames().stream()
+ .map(SubjectAlternativeName::decode)
+ .filter(san -> san.getType() == sanType)
+ .map(SubjectAlternativeName::getValue)
+ .collect(Collectors.joining(","));
+ }
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java
index 2a2b702d21b..564848fd5c8 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/instance/InstanceRegistration.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.ca.instance;
import com.yahoo.security.Pkcs10Csr;
+import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import java.util.Objects;
@@ -16,10 +17,10 @@ public class InstanceRegistration {
private final String provider;
private final String domain;
private final String service;
- private final String attestationData;
+ private final SignedIdentityDocument attestationData;
private final Pkcs10Csr csr;
- public InstanceRegistration(String provider, String domain, String service, String attestationData, Pkcs10Csr csr) {
+ public InstanceRegistration(String provider, String domain, String service, SignedIdentityDocument attestationData, Pkcs10Csr csr) {
this.provider = Objects.requireNonNull(provider, "provider must be non-null");
this.domain = Objects.requireNonNull(domain, "domain must be non-null");
this.service = Objects.requireNonNull(service, "service must be non-null");
@@ -43,7 +44,7 @@ public class InstanceRegistration {
}
/** Host document describing this instance (received from config server) */
- public String attestationData() {
+ public SignedIdentityDocument attestationData() {
return attestationData;
}
@@ -75,8 +76,8 @@ public class InstanceRegistration {
"provider='" + provider + '\'' +
", domain='" + domain + '\'' +
", service='" + service + '\'' +
- ", attestationData='" + attestationData + '\'' +
- ", csr=" + csr +
+ ", attestationData='" + attestationData.toString() + '\'' +
+ ", csr=" + csr.toString() +
'}';
}
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java
index 1b9bdcdb987..4c01b0943e4 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/ca/restapi/CertificateAuthorityApiHandler.java
@@ -6,16 +6,24 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.container.jdisc.secretstore.SecretStore;
+import com.yahoo.jdisc.http.servlet.ServletRequest;
+import com.yahoo.log.LogLevel;
import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SubjectAlternativeName;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Slime;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.instanceconfirmation.InstanceConfirmation;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.instanceconfirmation.InstanceValidator;
import com.yahoo.vespa.hosted.ca.Certificates;
import com.yahoo.vespa.hosted.ca.instance.InstanceIdentity;
+import com.yahoo.vespa.hosted.ca.instance.InstanceRefresh;
import com.yahoo.yolean.Exceptions;
import java.io.IOException;
@@ -23,6 +31,10 @@ import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Clock;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.logging.Level;
@@ -43,18 +55,20 @@ public class CertificateAuthorityApiHandler extends LoggingRequestHandler {
private final Certificates certificates;
private final String caPrivateKeySecretName;
private final String caCertificateSecretName;
+ private final InstanceValidator instanceValidator;
@Inject
- public CertificateAuthorityApiHandler(Context ctx, SecretStore secretStore, AthenzProviderServiceConfig athenzProviderServiceConfig) {
- this(ctx, secretStore, new Certificates(Clock.systemUTC()), athenzProviderServiceConfig);
+ public CertificateAuthorityApiHandler(Context ctx, SecretStore secretStore, AthenzProviderServiceConfig athenzProviderServiceConfig, InstanceValidator instanceValidator) {
+ this(ctx, secretStore, new Certificates(Clock.systemUTC()), athenzProviderServiceConfig, instanceValidator);
}
- CertificateAuthorityApiHandler(Context ctx, SecretStore secretStore, Certificates certificates, AthenzProviderServiceConfig athenzProviderServiceConfig) {
+ CertificateAuthorityApiHandler(Context ctx, SecretStore secretStore, Certificates certificates, AthenzProviderServiceConfig athenzProviderServiceConfig, InstanceValidator instanceValidator) {
super(ctx);
this.secretStore = secretStore;
this.certificates = certificates;
this.caPrivateKeySecretName = athenzProviderServiceConfig.secretName();
this.caCertificateSecretName = athenzProviderServiceConfig.domain() + ".ca.cert";
+ this.instanceValidator = instanceValidator;
}
@Override
@@ -81,6 +95,14 @@ public class CertificateAuthorityApiHandler extends LoggingRequestHandler {
private HttpResponse registerInstance(HttpRequest request) {
var instanceRegistration = deserializeRequest(request, InstanceSerializer::registrationFromSlime);
+
+ InstanceConfirmation confirmation = new InstanceConfirmation(instanceRegistration.provider(), instanceRegistration.domain(), instanceRegistration.service(), EntityBindingsMapper.toSignedIdentityDocumentEntity(instanceRegistration.attestationData()));
+ confirmation.set(InstanceValidator.SAN_IPS_ATTRNAME, Certificates.getSubjectAlternativeNames(instanceRegistration.csr(), SubjectAlternativeName.Type.IP_ADDRESS));
+ confirmation.set(InstanceValidator.SAN_DNS_ATTRNAME, Certificates.getSubjectAlternativeNames(instanceRegistration.csr(), SubjectAlternativeName.Type.DNS_NAME));
+ if (!instanceValidator.isValidInstance(confirmation)) {
+ log.log(LogLevel.INFO, "Invalid instance registration for " + instanceRegistration.toString());
+ return ErrorResponse.forbidden("Unable to launch service: " +instanceRegistration.service());
+ }
var certificate = certificates.create(instanceRegistration.csr(), caCertificate(), caPrivateKey());
var instanceId = Certificates.instanceIdFrom(instanceRegistration.csr());
var identity = new InstanceIdentity(instanceRegistration.provider(), instanceRegistration.service(), instanceId,
@@ -91,21 +113,65 @@ public class CertificateAuthorityApiHandler extends LoggingRequestHandler {
private HttpResponse refreshInstance(HttpRequest request, String provider, String service, String instanceId) {
var instanceRefresh = deserializeRequest(request, InstanceSerializer::refreshFromSlime);
var instanceIdFromCsr = Certificates.instanceIdFrom(instanceRefresh.csr());
+ var athenzService = new AthenzService(request.getJDiscRequest().getUserPrincipal().getName());
if (!instanceIdFromCsr.equals(instanceId)) {
throw new IllegalArgumentException("Mismatch between instance ID in URL path and instance ID in CSR " +
"[instanceId=" + instanceId + ",instanceIdFromCsr=" + instanceIdFromCsr +
"]");
}
+
+ // Verify that the csr instance id matches one of the certificates in the chain
+ refreshesSameInstanceId(instanceIdFromCsr, request);
+
+
+ // Validate that there is no privilege escalation (can only refresh same service)
+ refreshesSameService(instanceRefresh, athenzService);
+
+ InstanceConfirmation instanceConfirmation = new InstanceConfirmation(provider, athenzService.getDomain().getName(), athenzService.getName(), null);
+ instanceConfirmation.set(InstanceValidator.SAN_IPS_ATTRNAME, Certificates.getSubjectAlternativeNames(instanceRefresh.csr(), SubjectAlternativeName.Type.IP_ADDRESS));
+ instanceConfirmation.set(InstanceValidator.SAN_DNS_ATTRNAME, Certificates.getSubjectAlternativeNames(instanceRefresh.csr(), SubjectAlternativeName.Type.DNS_NAME));
+ if(!instanceValidator.isValidRefresh(instanceConfirmation)) {
+ return ErrorResponse.forbidden("Unable to refresh cert: " + instanceRefresh.csr().getSubject().toString());
+ }
+
var certificate = certificates.create(instanceRefresh.csr(), caCertificate(), caPrivateKey());
var identity = new InstanceIdentity(provider, service, instanceIdFromCsr, Optional.of(certificate));
return new SlimeJsonResponse(InstanceSerializer.identityToSlime(identity));
}
+ public void refreshesSameInstanceId(String csrInstanceId, HttpRequest request) {
+ String certificateInstanceId = getRequestCertificateChain(request).stream()
+ .map(Certificates::instanceIdFrom)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findAny().orElseThrow(() -> new IllegalArgumentException("No client certificate with instance id in request."));
+
+ if(! Objects.equals(certificateInstanceId, csrInstanceId)) {
+ throw new IllegalArgumentException("Mismatch between instance ID in client certificate and instance ID in CSR " +
+ "[instanceId=" + certificateInstanceId + ",instanceIdFromCsr=" + csrInstanceId +
+ "]");
+ }
+ }
+
+ private void refreshesSameService(InstanceRefresh instanceRefresh, AthenzService athenzService) {
+ List<String> commonNames = X509CertificateUtils.getCommonNames(instanceRefresh.csr().getSubject());
+ if(commonNames.size() != 1 && !Objects.equals(commonNames.get(0), athenzService.getFullName())) {
+ throw new IllegalArgumentException(String.format("Invalid request, trying to refresh service %s using service %s.", instanceRefresh.csr().getSubject().getName(), athenzService.getFullName()));
+ }
+ }
+
/** Returns CA certificate from secret store */
private X509Certificate caCertificate() {
return X509CertificateUtils.fromPem(secretStore.getSecret(caCertificateSecretName));
}
+ private List<X509Certificate> getRequestCertificateChain(HttpRequest request) {
+ return Optional.ofNullable(request.getJDiscRequest().context().get(ServletRequest.JDISC_REQUEST_X509CERT))
+ .map(X509Certificate[].class::cast)
+ .map(Arrays::asList)
+ .orElse(Collections.emptyList());
+ }
+
/** Returns CA private key from secret store */
private PrivateKey caPrivateKey() {
return KeyUtils.fromPemEncodedPrivateKey(secretStore.getSecret(caPrivateKeySecretName));
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 a2537cd68f1..c989f85b167 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
@@ -3,12 +3,25 @@ package com.yahoo.vespa.hosted.ca.restapi;
import com.yahoo.security.Pkcs10CsrUtils;
import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
+import com.yahoo.text.StringUtilities;
+import com.yahoo.vespa.athenz.api.AthenzService;
+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.config.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;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* @author mpolden
*/
@@ -23,6 +36,17 @@ public class InstanceSerializer {
private static final String INSTANCE_ID_FIELD = "instanceId";
private static final String X509_CERTIFICATE_FIELD = "x509Certificate";
+ private static final String IDD_SIGNATURE_FIELD = "signature";
+ private static final String IDD_SIGNING_KEY_VERSION_FIELD = "signing-key-version";
+ private static final String IDD_PROVIDER_UNIQUE_ID_FIELD = "provider-unique-id";
+ private static final String IDD_PROVIDER_SERVICE_FIELD = "provider-service";
+ private static final String IDD_DOCUMENT_VERSION_FIELD = "document-version";
+ private static final String IDD_CONFIGSERVER_HOSTNAME_FIELD = "configserver-hostname";
+ private static final String IDD_INSTANCE_HOSTNAME_FIELD = "instance-hostname";
+ 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 InstanceSerializer() {}
public static InstanceRegistration registrationFromSlime(Slime slime) {
@@ -30,7 +54,7 @@ public class InstanceSerializer {
return new InstanceRegistration(requireField(PROVIDER_FIELD, root).asString(),
requireField(DOMAIN_FIELD, root).asString(),
requireField(SERVICE_FIELD, root).asString(),
- requireField(ATTESTATION_DATA_FIELD, root).asString(),
+ attestationDataToIdentityDocument(StringUtilities.unescape(requireField(ATTESTATION_DATA_FIELD, root).asString())),
Pkcs10CsrUtils.fromPem(requireField(CSR_FIELD, root).asString()));
}
@@ -51,6 +75,33 @@ public class InstanceSerializer {
return slime;
}
+ public static SignedIdentityDocument attestationDataToIdentityDocument(String attestationData) {
+ Slime slime = SlimeUtils.jsonToSlime(attestationData);
+ Cursor root = slime.get();
+ String signature = requireField(IDD_SIGNATURE_FIELD, root).asString();
+ long signingKeyVersion = requireField(IDD_SIGNING_KEY_VERSION_FIELD, root).asLong();
+ VespaUniqueInstanceId providerUniqueId = VespaUniqueInstanceId.fromDottedString(requireField(IDD_PROVIDER_UNIQUE_ID_FIELD, root).asString());
+ AthenzService athenzService = new AthenzService(requireField(IDD_PROVIDER_SERVICE_FIELD, root).asString());
+ long documentVersion = requireField(IDD_DOCUMENT_VERSION_FIELD, root).asLong();
+ String configserverHostname = requireField(IDD_CONFIGSERVER_HOSTNAME_FIELD, root).asString();
+ String instanceHostname = requireField(IDD_INSTANCE_HOSTNAME_FIELD, root).asString();
+ double createdAtTimestamp = requireField(IDD_CREATED_AT_FIELD, root).asDouble();
+ Instant createdAt = getJsr310Instant(createdAtTimestamp);
+ 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());
+
+ return new SignedIdentityDocument(signature, (int)signingKeyVersion, providerUniqueId, athenzService, (int)documentVersion,
+ configserverHostname, instanceHostname, createdAt, ips, identityType);
+ }
+
+ private static Instant getJsr310Instant(double v) {
+ var val = new BigDecimal(v);
+ var seconds = val.longValue();
+ var nanos = val.subtract(new BigDecimal(seconds)).scaleByPowerOfTen(9).longValue();
+ return Instant.ofEpochSecond(seconds, nanos).truncatedTo(ChronoUnit.MILLIS);
+ }
+
private static Cursor requireField(String fieldName, Cursor root) {
var field = root.field(fieldName);
if (!field.valid()) throw new IllegalArgumentException("Missing required field '" + fieldName + "'");