summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Tokle <morten.tokle@gmail.com>2018-06-08 10:29:44 +0200
committerGitHub <noreply@github.com>2018-06-08 10:29:44 +0200
commit6ca9b8c4df2733acb7298b85a2ef70cb6bc00282 (patch)
tree491cc93b3ebe18dcfd044c03f0ada14569739181
parentc565029fd89405ae4f238108e811177ef80eb930 (diff)
parent91fb7bb0efe55996878a81dedb836abeee1fb4f8 (diff)
Merge pull request #6125 from vespa-engine/bjorncs/node-agent-sia
Bjorncs/node agent sia
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java105
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java23
3 files changed, 70 insertions, 60 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..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;
@@ -19,9 +21,9 @@ 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;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
import javax.net.ssl.SSLContext;
@@ -38,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;
@@ -53,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;
@@ -66,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,
@@ -82,8 +84,9 @@ 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.identityDocumentFile = containerSiaDirectory.resolve("vespa-node-identity-document.json");
this.hostIdentityProvider = hostIdentityProvider;
this.identityDocumentClient =
new DefaultIdentityDocumentClient(
@@ -91,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");
@@ -107,26 +107,25 @@ public class AthenzCredentialsMaintainer {
}
log.debug("Checking certificate");
Instant now = clock.instant();
- VespaUniqueInstanceId instanceId = getVespaUniqueInstanceId(nodeSpec);
- Set<String> 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");
@@ -148,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;
}
@@ -175,32 +160,32 @@ public class AthenzCredentialsMaintainer {
return now.isAfter(expiry.minus(EXPIRY_MARGIN));
}
- private void registerIdentity(VespaUniqueInstanceId instanceId, Set<String> 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) {
throw new UncheckedIOException(e);
- } catch (Exception e) {
- // TODO Change close() in ZtsClient to not throw checked exception
- throw new RuntimeException(e);
}
}
- private void refreshIdentity(VespaUniqueInstanceId instanceId, Set<String> 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())
@@ -211,16 +196,34 @@ public class AthenzCredentialsMaintainer {
ztsClient.refreshInstance(
configserverIdentity,
containerIdentity,
- instanceId.asDottedString(),
+ identityDocument.providerUniqueId().asDottedString(),
false,
csr);
writePrivateKeyAndCertificate(keyPair.getPrivate(), instanceIdentity.certificate());
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);
+ }
+ }
+
+ 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);
}
}
@@ -238,18 +241,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()));
- }
-
}
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);
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);
+ }
+ }
+
}