summaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorOla Aunronning <olaa@yahooinc.com>2023-04-28 12:11:01 +0200
committerOla Aunronning <olaa@yahooinc.com>2023-04-28 14:20:54 +0200
commit0aea87ce6347b9c2e4d3a09caf58dfb3ceb44931 (patch)
tree3a3a677181cef412d9ad6c991b2ef1441a95aed3 /node-admin
parent5eed7c893f438d0c643e56133e87a8895d138698 (diff)
AthenzCredentialsMaintainer maintains role certificates
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java70
1 files changed, 63 insertions, 7 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 d5b48bc2609..3f8107ae830 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
@@ -9,6 +9,7 @@ import com.yahoo.security.Pkcs10Csr;
import com.yahoo.security.SslContextBuilder;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzRole;
import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient;
import com.yahoo.vespa.athenz.client.zts.InstanceIdentity;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
@@ -122,6 +123,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
if (context.isDisabled(NodeAgentTask.CredentialsMaintainer)) return false;
try {
+ var modified = false;
context.log(logger, Level.FINE, "Checking certificate");
ContainerPath siaDirectory = context.paths().of(CONTAINER_SIA_DIRECTORY, context.users().vespa());
ContainerPath identityDocumentFile = siaDirectory.resolve(identityType.getIdentityDocument());
@@ -137,7 +139,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
Files.createDirectories(certificateFile.getParent());
Files.createDirectories(identityDocumentFile.getParent());
registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType, athenzIdentity);
- return true;
+ modified = true;
}
X509Certificate certificate = readCertificateFromFile(certificateFile);
@@ -147,11 +149,11 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
if (refreshIdentityDocument(doc, context)) {
context.log(logger, "Identity document is outdated (version=%d)", doc.documentVersion());
registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType, athenzIdentity);
- return true;
+ modified = true;
} else if (isCertificateExpired(expiry, now)) {
context.log(logger, "Certificate has expired (expiry=%s)", expiry.toString());
registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType, athenzIdentity);
- return true;
+ modified = true;
}
Duration age = Duration.between(certificate.getNotBefore().toInstant(), now);
@@ -161,20 +163,74 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
context.log(logger, Level.WARNING, String.format(
"Skipping refresh attempt as last refresh was on %s (less than %s ago)",
lastRefreshAttempt.get(context.containerName()).toString(), REFRESH_BACKOFF.toString()));
- return false;
} else {
lastRefreshAttempt.put(context.containerName(), now);
refreshIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, doc.identityDocument(), identityType, athenzIdentity);
- return true;
+ modified = true;
}
}
- context.log(logger, Level.FINE, "Certificate is still valid");
- return false;
+
+ modified |= maintainRoleCertificates(context, siaDirectory, privateKeyFile, certificateFile, athenzIdentity, doc.identityDocument());
+ return modified;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
+ private boolean maintainRoleCertificates(NodeAgentContext context, ContainerPath siaDirectory, ContainerPath privateKeyFile, ContainerPath certificateFile, AthenzIdentity identity, IdentityDocument identityDocument) {
+ var modified = false;
+
+ for (var role : identityDocumentClient.getNodeRoles(context.hostname().value())) {
+ try {
+ var roleCertificatePath = siaDirectory.resolve("certs")
+ .resolve(String.format("%s.cert.pem", role));
+ if (!Files.exists(roleCertificatePath)) {
+ writeRoleCertificate(context, privateKeyFile, certificateFile, roleCertificatePath, identity, identityDocument, role);
+ modified = true;
+ } else if (shouldRefreshCertificate(context, roleCertificatePath)) {
+ writeRoleCertificate(context, privateKeyFile, certificateFile, roleCertificatePath, identity, identityDocument, role);
+ modified = true;
+ }
+ } catch (IOException e) {
+ context.log(logger, Level.WARNING, "Failed to maintain role certificate " + role, e);
+ }
+ }
+ return modified;
+ }
+
+ private boolean shouldRefreshCertificate(NodeAgentContext context, ContainerPath certificatePath) throws IOException {
+ var certificate = readCertificateFromFile(certificatePath);
+ var now = clock.instant();
+ var shouldRefresh = now.isAfter(certificate.getNotAfter().toInstant()) ||
+ now.isBefore(certificate.getNotBefore().toInstant().plus(REFRESH_PERIOD));
+ return !shouldThrottleRefreshAttempts(context.containerName(), now) &&
+ shouldRefresh;
+ }
+
+ private void writeRoleCertificate(NodeAgentContext context,
+ ContainerPath privateKeyFile,
+ ContainerPath certificateFile,
+ ContainerPath roleCertificatePath,
+ AthenzIdentity identity,
+ IdentityDocument identityDocument,
+ String role) throws IOException {
+ HostnameVerifier ztsHostNameVerifier = (hostname, sslSession) -> true;
+ var athenzRole = AthenzRole.fromResourceNameString(role);
+ var privateKey = KeyUtils.fromPemEncodedPrivateKey(new String(Files.readAllBytes(privateKeyFile)));
+
+ var containerIdentitySslContext = new SslContextBuilder().withKeyStore(privateKeyFile, certificateFile)
+ .withTrustStore(ztsTrustStorePath)
+ .build();
+ try (ZtsClient ztsClient = new DefaultZtsClient.Builder(ztsEndpoint(identityDocument)).withSslContext(containerIdentitySslContext).withHostnameVerifier(ztsHostNameVerifier).build()) {
+ var csrGenerator = new CsrGenerator(certificateDnsSuffix, identityDocument.providerService().getFullName());
+ var csr = csrGenerator.generateRoleCsr(
+ identity, athenzRole, identityDocument.providerUniqueId(), identityDocument.clusterType(), KeyUtils.toKeyPair(privateKey));
+ var roleCertificate = ztsClient.getRoleCertificate(athenzRole, csr);
+ writeFile(roleCertificatePath, X509CertificateUtils.toPem(roleCertificate));
+ context.log(logger, "Role certificate successfully retrieved written to file " + roleCertificatePath.pathInContainer());
+ }
+ }
+
private boolean refreshIdentityDocument(SignedIdentityDocument signedIdentityDocument, NodeAgentContext context) {
int expectedVersion = documentVersion(context);
return signedIdentityDocument.outdated() || signedIdentityDocument.documentVersion() != expectedVersion;