From b2d67c8961b5a517c30b6720f9063bab674c1013 Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Thu, 7 Jun 2018 16:04:00 +0200 Subject: Fix misc todos --- .../identity/AthenzCredentialsMaintainer.java | 25 +++------------------- 1 file changed, 3 insertions(+), 22 deletions(-) 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 bd75368a0dc..a16493e3d83 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 @@ -19,6 +19,7 @@ import com.yahoo.vespa.athenz.tls.KeyUtils; import com.yahoo.vespa.athenz.tls.Pkcs10Csr; import com.yahoo.vespa.athenz.tls.SslContextBuilder; import com.yahoo.vespa.athenz.tls.X509CertificateUtils; +import com.yahoo.vespa.athenz.utils.SiaUtils; import com.yahoo.vespa.hosted.dockerapi.ContainerName; import com.yahoo.vespa.hosted.node.admin.component.Environment; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec; @@ -82,8 +83,8 @@ public class AthenzCredentialsMaintainer { this.configserverIdentity = environment.getConfigserverAthenzIdentity(); this.csrGenerator = new InstanceCsrGenerator(environment.getCertificateDnsSuffix()); this.trustStorePath = environment.getTrustStorePath(); - this.privateKeyFile = getPrivateKeyFile(containerSiaDirectory, containerIdentity); - this.certificateFile = getCertificateFile(containerSiaDirectory, containerIdentity); + this.privateKeyFile = SiaUtils.getPrivateKeyFile(containerSiaDirectory, containerIdentity); + this.certificateFile = SiaUtils.getCertificateFile(containerSiaDirectory, containerIdentity); this.hostIdentityProvider = hostIdentityProvider; this.identityDocumentClient = new DefaultIdentityDocumentClient( @@ -192,9 +193,6 @@ public class AthenzCredentialsMaintainer { log.info("Instance successfully registered and credentials written to file"); } catch (IOException e) { throw new UncheckedIOException(e); - } catch (Exception e) { - // TODO Change close() in ZtsClient to not throw checked exception - throw new RuntimeException(e); } } @@ -218,9 +216,6 @@ public class AthenzCredentialsMaintainer { log.info("Instance successfully refreshed and credentials written to file"); } catch (IOException e) { throw new UncheckedIOException(e); - } catch (Exception e) { - // TODO Change close() in ZtsClient to not throw checked exception - throw new RuntimeException(e); } } @@ -238,18 +233,4 @@ public class AthenzCredentialsMaintainer { return Paths.get(file.toAbsolutePath().toString() + ".tmp"); } - // TODO Move to vespa-athenz - private static Path getPrivateKeyFile(Path root, AthenzService service) { - return root - .resolve("keys") - .resolve(String.format("%s.%s.key.pem", service.getDomain().getName(), service.getName())); - } - - // TODO Move to vespa-athenz - private static Path getCertificateFile(Path root, AthenzService service) { - return root - .resolve("certs") - .resolve(String.format("%s.%s.cert.pem", service.getDomain().getName(), service.getName())); - } - } -- cgit v1.2.3 From 4dcf8f382f9e9580d288e26e727ea72a6914aab4 Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Thu, 7 Jun 2018 16:36:05 +0200 Subject: Use identity document to determine instance id and ip addresses - Store identity document to file on instance registered - Read identity document from file on instance refresh --- .../identity/AthenzCredentialsMaintainer.java | 80 ++++++++++++---------- .../hosted/node/admin/nodeagent/NodeAgentImpl.java | 2 +- 2 files changed, 45 insertions(+), 37 deletions(-) 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 a16493e3d83..ff85c49bb13 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 @@ -1,6 +1,8 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.maintenance.identity; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient; import com.yahoo.vespa.athenz.client.zts.InstanceIdentity; @@ -9,7 +11,7 @@ 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.api.VespaUniqueInstanceId; +import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity; import com.yahoo.vespa.athenz.identityprovider.client.DefaultIdentityDocumentClient; import com.yahoo.vespa.athenz.identityprovider.client.InstanceCsrGenerator; import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier; @@ -22,7 +24,6 @@ import com.yahoo.vespa.athenz.tls.X509CertificateUtils; import com.yahoo.vespa.athenz.utils.SiaUtils; import com.yahoo.vespa.hosted.dockerapi.ContainerName; import com.yahoo.vespa.hosted.node.admin.component.Environment; -import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec; import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger; import javax.net.ssl.SSLContext; @@ -39,7 +40,6 @@ import java.security.cert.X509Certificate; import java.time.Clock; import java.time.Duration; import java.time.Instant; -import java.util.Set; import static java.util.Collections.singleton; @@ -54,12 +54,15 @@ public class AthenzCredentialsMaintainer { private static final Duration REFRESH_PERIOD = Duration.ofDays(1); private static final Path CONTAINER_SIA_DIRECTORY = Paths.get("/var/lib/sia"); + private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + private final boolean enabled; private final PrefixLogger log; private final String hostname; private final Path trustStorePath; private final Path privateKeyFile; private final Path certificateFile; + private final Path identityDocumentFile; private final AthenzService containerIdentity; private final URI ztsEndpoint; private final Clock clock; @@ -67,8 +70,6 @@ public class AthenzCredentialsMaintainer { private final IdentityDocumentClient identityDocumentClient; private final InstanceCsrGenerator csrGenerator; private final AthenzService configserverIdentity; - private final String zoneRegion; - private final String zoneEnvironment; public AthenzCredentialsMaintainer(String hostname, Environment environment, @@ -85,6 +86,7 @@ public class AthenzCredentialsMaintainer { this.trustStorePath = environment.getTrustStorePath(); this.privateKeyFile = SiaUtils.getPrivateKeyFile(containerSiaDirectory, containerIdentity); this.certificateFile = SiaUtils.getCertificateFile(containerSiaDirectory, containerIdentity); + this.identityDocumentFile = containerSiaDirectory.resolve("vespa-node-identity-document.json"); this.hostIdentityProvider = hostIdentityProvider; this.identityDocumentClient = new DefaultIdentityDocumentClient( @@ -92,15 +94,12 @@ public class AthenzCredentialsMaintainer { hostIdentityProvider, new AthenzIdentityVerifier(singleton(configserverIdentity))); this.clock = Clock.systemUTC(); - this.zoneRegion = environment.getRegion(); - this.zoneEnvironment = environment.getEnvironment(); } /** - * @param nodeSpec Node specification * @return Returns true if credentials were updated */ - public boolean converge(NodeSpec nodeSpec) { + public boolean converge() { try { if (!enabled) { log.debug("Feature disabled on this host - not fetching certificate"); @@ -108,26 +107,25 @@ public class AthenzCredentialsMaintainer { } log.debug("Checking certificate"); Instant now = clock.instant(); - VespaUniqueInstanceId instanceId = getVespaUniqueInstanceId(nodeSpec); - Set ipAddresses = nodeSpec.getIpAddresses(); - if (!Files.exists(privateKeyFile) || !Files.exists(certificateFile)) { - log.info("Certificate and/or private key file does not exist"); + if (!Files.exists(privateKeyFile) || !Files.exists(certificateFile) || !Files.exists(identityDocumentFile)) { + log.info("Certificate/private key/identity document file does not exist"); Files.createDirectories(privateKeyFile.getParent()); Files.createDirectories(certificateFile.getParent()); - registerIdentity(instanceId, ipAddresses); + Files.createDirectories(identityDocumentFile.getParent()); + registerIdentity(); return true; } X509Certificate certificate = readCertificateFromFile(); Instant expiry = certificate.getNotAfter().toInstant(); if (isCertificateExpired(expiry, now)) { log.info(String.format("Certificate has expired (expiry=%s)", expiry.toString())); - registerIdentity(instanceId, ipAddresses); + registerIdentity(); return true; } Duration age = Duration.between(certificate.getNotBefore().toInstant(), now); if (shouldRefreshCredentials(age)) { log.info(String.format("Certificate is ready to be refreshed (age=%s)", age.toString())); - refreshIdentity(instanceId, ipAddresses); + refreshIdentity(); return true; } log.debug("Certificate is still valid"); @@ -149,20 +147,6 @@ public class AthenzCredentialsMaintainer { } } - @SuppressWarnings("deprecation") - private VespaUniqueInstanceId getVespaUniqueInstanceId(NodeSpec nodeSpec) { - NodeSpec.Membership membership = nodeSpec.getMembership().get(); - NodeSpec.Owner owner = nodeSpec.getOwner().get(); - return new VespaUniqueInstanceId( - membership.getIndex(), - membership.getClusterId(), - owner.getInstance(), - owner.getApplication(), - owner.getTenant(), - zoneRegion, - zoneEnvironment); - } - private boolean shouldRefreshCredentials(Duration age) { return age.compareTo(REFRESH_PERIOD) >= 0; } @@ -176,19 +160,21 @@ public class AthenzCredentialsMaintainer { return now.isAfter(expiry.minus(EXPIRY_MARGIN)); } - private void registerIdentity(VespaUniqueInstanceId instanceId, Set ipAddresses) { + private void registerIdentity() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); - Pkcs10Csr csr = csrGenerator.generateCsr(containerIdentity, instanceId, ipAddresses, keyPair); SignedIdentityDocument signedIdentityDocument = identityDocumentClient.getNodeIdentityDocument(hostname); + Pkcs10Csr csr = csrGenerator.generateCsr( + containerIdentity, signedIdentityDocument.providerUniqueId(), signedIdentityDocument.ipAddresses(), keyPair); try (ZtsClient ztsClient = new DefaultZtsClient(ztsEndpoint, hostIdentityProvider)) { InstanceIdentity instanceIdentity = ztsClient.registerInstance( configserverIdentity, containerIdentity, - instanceId.asDottedString(), + signedIdentityDocument.providerUniqueId().asDottedString(), EntityBindingsMapper.toAttestationData(signedIdentityDocument), false, csr); + writeIdentityDocument(signedIdentityDocument); writePrivateKeyAndCertificate(keyPair.getPrivate(), instanceIdentity.certificate()); log.info("Instance successfully registered and credentials written to file"); } catch (IOException e) { @@ -196,9 +182,10 @@ public class AthenzCredentialsMaintainer { } } - private void refreshIdentity(VespaUniqueInstanceId instanceId, Set ipAddresses) { + private void refreshIdentity() { + SignedIdentityDocument identityDocument = readIdentityDocument(); KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); - Pkcs10Csr csr = csrGenerator.generateCsr(containerIdentity, instanceId, ipAddresses, keyPair); + Pkcs10Csr csr = csrGenerator.generateCsr(containerIdentity, identityDocument.providerUniqueId(), identityDocument.ipAddresses(), keyPair); SSLContext containerIdentitySslContext = new SslContextBuilder() .withKeyStore(privateKeyFile.toFile(), certificateFile.toFile()) @@ -209,7 +196,7 @@ public class AthenzCredentialsMaintainer { ztsClient.refreshInstance( configserverIdentity, containerIdentity, - instanceId.asDottedString(), + identityDocument.providerUniqueId().asDottedString(), false, csr); writePrivateKeyAndCertificate(keyPair.getPrivate(), instanceIdentity.certificate()); @@ -219,6 +206,27 @@ public class AthenzCredentialsMaintainer { } } + private SignedIdentityDocument readIdentityDocument() { + try { + SignedIdentityDocumentEntity entity = mapper.readValue(identityDocumentFile.toFile(), SignedIdentityDocumentEntity.class); + return EntityBindingsMapper.toSignedIdentityDocument(entity); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void writeIdentityDocument(SignedIdentityDocument signedIdentityDocument) { + try { + SignedIdentityDocumentEntity entity = + EntityBindingsMapper.toSignedIdentityDocumentEntity(signedIdentityDocument); + Path tempIdentityDocumentFile = toTempPath(identityDocumentFile); + mapper.writeValue(tempIdentityDocumentFile.toFile(), entity); + Files.move(tempIdentityDocumentFile, identityDocumentFile, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + private void writePrivateKeyAndCertificate(PrivateKey privateKey, X509Certificate certificate) throws IOException { Path tempPrivateKeyFile = toTempPath(privateKeyFile); Files.write(tempPrivateKeyFile, KeyUtils.toPem(privateKey).getBytes()); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java index 7fa9a90b744..5f1b7aefcfe 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java @@ -498,7 +498,7 @@ public class NodeAgentImpl implements NodeAgent { runLocalResumeScriptIfNeeded(node); - athenzCredentialsMaintainer.converge(node); + athenzCredentialsMaintainer.converge(); doBeforeConverge(node); -- cgit v1.2.3 From 91fb7bb0efe55996878a81dedb836abeee1fb4f8 Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Thu, 7 Jun 2018 16:41:58 +0200 Subject: Add utility methods to (de)serialize signed identity document from/to file --- .../identityprovider/api/EntityBindingsMapper.java | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) 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 12389712976..ab127b19bf1 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 @@ -10,9 +10,12 @@ import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocume import com.yahoo.vespa.athenz.identityprovider.api.bindings.VespaUniqueInstanceIdEntity; import com.yahoo.vespa.athenz.utils.AthenzIdentities; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; import java.util.Base64; -import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.*; +import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.fromDottedString; /** * Utility class for mapping objects model types and their Jackson binding versions. @@ -102,4 +105,22 @@ public class EntityBindingsMapper { } } + public static SignedIdentityDocument readSignedIdentityDocumentFromFile(Path file) { + try { + SignedIdentityDocumentEntity entity = mapper.readValue(file.toFile(), SignedIdentityDocumentEntity.class); + return EntityBindingsMapper.toSignedIdentityDocument(entity); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void writeSignedIdentityDocumentToFile(Path file, SignedIdentityDocument document) { + try { + SignedIdentityDocumentEntity entity = EntityBindingsMapper.toSignedIdentityDocumentEntity(document); + mapper.writeValue(file.toFile(), entity); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } -- cgit v1.2.3