From 92d287613d48cf4d58eadf2a8e2ade6c106b1a91 Mon Sep 17 00:00:00 2001 From: Ola Aunronning Date: Thu, 23 Mar 2023 14:40:45 +0100 Subject: Tenant certificate creation in AthenzCredentialsMaintainer --- .../identity/AthenzCredentialsMaintainer.java | 83 ++++++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) (limited to 'node-admin/src/main/java/com') 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 15be7accb7d..89e6dbc74d6 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 @@ -47,6 +47,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import static com.yahoo.vespa.hosted.node.admin.maintenance.identity.AthenzCredentialsMaintainer.IdentityType.NODE; +import static com.yahoo.vespa.hosted.node.admin.maintenance.identity.AthenzCredentialsMaintainer.IdentityType.TENANT; + /** * A maintainer that is responsible for providing and refreshing Athenz credentials for a container. * @@ -61,6 +64,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { private static final Duration REFRESH_BACKOFF = Duration.ofHours(1); // Backoff when refresh fails to ensure ZTS is not DDoS'ed. private static final String CONTAINER_SIA_DIRECTORY = "/var/lib/sia"; + private static final String VESPA_SIA_DIRECTORY = "/var/vespa/sia"; private final URI ztsEndpoint; private final Path ztsTrustStorePath; @@ -68,7 +72,6 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { private final String certificateDnsSuffix; private final ServiceIdentityProvider hostIdentityProvider; private final IdentityDocumentClient identityDocumentClient; - private final boolean useInternalZts; // Used as an optimization to ensure ZTS is not DDoS'ed on continuously failing refresh attempts private final Map lastRefreshAttempt = new ConcurrentHashMap<>(); @@ -78,7 +81,6 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { ConfigServerInfo configServerInfo, String certificateDnsSuffix, ServiceIdentityProvider hostIdentityProvider, - boolean useInternalZts, Clock clock) { this.ztsEndpoint = ztsEndpoint; this.ztsTrustStorePath = ztsTrustStorePath; @@ -89,24 +91,31 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { hostIdentityProvider, new AthenzIdentityVerifier(Set.of(configServerInfo.getConfigServerIdentity()))); this.clock = clock; - this.useInternalZts = useInternalZts; } public boolean converge(NodeAgentContext context) { + var modified = false; + modified |= maintain(context, NODE); + modified |= maintain(context, TENANT); + return modified; + } + + private boolean maintain(NodeAgentContext context, IdentityType identityType) { if (context.isDisabled(NodeAgentTask.CredentialsMaintainer)) return false; try { context.log(logger, Level.FINE, "Checking certificate"); - ContainerPath containerSiaDirectory = context.paths().of(CONTAINER_SIA_DIRECTORY, context.users().vespa()); - ContainerPath privateKeyFile = (ContainerPath) SiaUtils.getPrivateKeyFile(containerSiaDirectory, context.identity()); - ContainerPath certificateFile = (ContainerPath) SiaUtils.getCertificateFile(containerSiaDirectory, context.identity()); - ContainerPath identityDocumentFile = containerSiaDirectory.resolve("vespa-node-identity-document.json"); + ContainerPath siaDirectory = context.paths().of(identityType.getSiaDirectory(), context.users().vespa()); + ContainerPath identityDocumentFile = siaDirectory.resolve(identityType.getIdentityDocument()); + AthenzIdentity athenzIdentity = getAthenzIdentity(context, identityType, identityDocumentFile); + ContainerPath privateKeyFile = (ContainerPath) SiaUtils.getPrivateKeyFile(siaDirectory, athenzIdentity); + ContainerPath certificateFile = (ContainerPath) SiaUtils.getCertificateFile(siaDirectory, athenzIdentity); if (!Files.exists(privateKeyFile) || !Files.exists(certificateFile) || !Files.exists(identityDocumentFile)) { context.log(logger, "Certificate/private key/identity document file does not exist"); Files.createDirectories(privateKeyFile.getParent()); Files.createDirectories(certificateFile.getParent()); Files.createDirectories(identityDocumentFile.getParent()); - registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile); + registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType); return true; } @@ -116,11 +125,11 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { var doc = EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile); if (doc.outdated()) { context.log(logger, "Identity document is outdated (version=%d)", doc.documentVersion()); - registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile); + registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType); return true; } else if (isCertificateExpired(expiry, now)) { context.log(logger, "Certificate has expired (expiry=%s)", expiry.toString()); - registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile); + registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType); return true; } @@ -134,7 +143,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { return false; } else { lastRefreshAttempt.put(context.containerName(), now); - refreshIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, doc); + refreshIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, doc, identityType); return true; } } @@ -148,6 +157,8 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { public void clearCredentials(NodeAgentContext context) { FileFinder.files(context.paths().of(CONTAINER_SIA_DIRECTORY)) .deleteRecursively(context); + FileFinder.files(context.paths().of(VESPA_SIA_DIRECTORY)) + .deleteRecursively(context); lastRefreshAttempt.remove(context.containerName()); } @@ -182,9 +193,9 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { now)) > 0; } - private void registerIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, ContainerPath identityDocumentFile) { + private void registerIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, ContainerPath identityDocumentFile, IdentityType identityType) { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); - SignedIdentityDocument doc = identityDocumentClient.getNodeIdentityDocument(context.hostname().value()); + SignedIdentityDocument doc = signedIdentityDocument(context, identityType); CsrGenerator csrGenerator = new CsrGenerator(certificateDnsSuffix, doc.providerService().getFullName()); Pkcs10Csr csr = csrGenerator.generateInstanceCsr( context.identity(), doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair); @@ -214,7 +225,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { .orElse(ztsEndpoint); } private void refreshIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, - ContainerPath identityDocumentFile, SignedIdentityDocument doc) { + ContainerPath identityDocumentFile, SignedIdentityDocument doc, IdentityType identityType) { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); CsrGenerator csrGenerator = new CsrGenerator(certificateDnsSuffix, doc.providerService().getFullName()); Pkcs10Csr csr = csrGenerator.generateInstanceCsr( @@ -239,7 +250,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { } catch (ZtsClientException e) { if (e.getErrorCode() == 403 && e.getDescription().startsWith("Certificate revoked")) { context.log(logger, Level.SEVERE, "Certificate cannot be refreshed as it is revoked by ZTS - re-registering the instance now", e); - registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile); + registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType); } else { throw e; } @@ -272,4 +283,46 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { private static boolean isCertificateExpired(Instant expiry, Instant now) { return now.isAfter(expiry.minus(EXPIRY_MARGIN)); } + + private SignedIdentityDocument signedIdentityDocument(NodeAgentContext context, IdentityType identityType) { + return switch (identityType) { + case NODE -> identityDocumentClient.getNodeIdentityDocument(context.hostname().value()); + case TENANT -> identityDocumentClient.getTenantIdentityDocument(context.hostname().value()); + }; + } + + private AthenzIdentity getAthenzIdentity(NodeAgentContext context, IdentityType identityType, ContainerPath identityDocumentFile) { + return switch (identityType) { + case NODE -> context.identity(); + case TENANT -> getTenantIdentity(context, identityDocumentFile); + }; + } + + private AthenzIdentity getTenantIdentity(NodeAgentContext context, ContainerPath identityDocumentFile) { + if (Files.exists(identityDocumentFile)) { + return EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile).serviceIdentity(); + } else { + return identityDocumentClient.getTenantIdentityDocument(context.hostname().value()).serviceIdentity(); + } + } + + enum IdentityType { + NODE(CONTAINER_SIA_DIRECTORY, "vespa-node-identity-document.json"), + TENANT(VESPA_SIA_DIRECTORY, "vespa-tenant-identity-document.json"); + + private String siaDirectory; + private String identityDocument; + IdentityType(String siaDirectory, String identityDocument) { + this.siaDirectory = siaDirectory; + this.identityDocument = identityDocument; + } + + public String getSiaDirectory() { + return siaDirectory; + } + + public String getIdentityDocument() { + return identityDocument; + } + } } -- cgit v1.2.3 From f7600f631791e8eb37c52e0e8c6357ce84433bde Mon Sep 17 00:00:00 2001 From: Ola Aunronning Date: Thu, 13 Apr 2023 12:29:12 +0200 Subject: Flag controlled service identity generation. Unify folder for all identity types --- .../src/main/java/com/yahoo/vespa/flags/Flags.java | 8 ++++++ .../identity/AthenzCredentialsMaintainer.java | 33 +++++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) (limited to 'node-admin/src/main/java/com') diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index f24e9b9b7a5..fcf059c2836 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -385,6 +385,14 @@ public class Flags { "Takes effect on next config server container start", ZONE_ID); + public static final UnboundBooleanFlag NODE_ADMIN_TENANT_SERVICE_REGISTRY = defineFeatureFlag( + "node-admin-tenant-service-registry", false, + List.of("olaa"), "2023-04-12", "2023-06-12", + "Whether AthenzCredentialsMaintainer in node-admin should create tenant service identity certificate", + "Takes effect on next tick", + ZONE_ID, HOSTNAME + ); + /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List owners, String createdAt, String expiresAt, String description, 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 89e6dbc74d6..d22fd667202 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,10 @@ import com.yahoo.vespa.athenz.identityprovider.client.CsrGenerator; import com.yahoo.vespa.athenz.identityprovider.client.DefaultIdentityDocumentClient; import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier; import com.yahoo.vespa.athenz.utils.SiaUtils; +import com.yahoo.vespa.flags.BooleanFlag; +import com.yahoo.vespa.flags.FetchVector; +import com.yahoo.vespa.flags.FlagSource; +import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo; import com.yahoo.vespa.hosted.node.admin.container.ContainerName; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; @@ -64,7 +68,6 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { private static final Duration REFRESH_BACKOFF = Duration.ofHours(1); // Backoff when refresh fails to ensure ZTS is not DDoS'ed. private static final String CONTAINER_SIA_DIRECTORY = "/var/lib/sia"; - private static final String VESPA_SIA_DIRECTORY = "/var/vespa/sia"; private final URI ztsEndpoint; private final Path ztsTrustStorePath; @@ -72,6 +75,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { private final String certificateDnsSuffix; private final ServiceIdentityProvider hostIdentityProvider; private final IdentityDocumentClient identityDocumentClient; + private final BooleanFlag tenantServiceIdentityFlag; // Used as an optimization to ensure ZTS is not DDoS'ed on continuously failing refresh attempts private final Map lastRefreshAttempt = new ConcurrentHashMap<>(); @@ -81,6 +85,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { ConfigServerInfo configServerInfo, String certificateDnsSuffix, ServiceIdentityProvider hostIdentityProvider, + FlagSource flagSource, Clock clock) { this.ztsEndpoint = ztsEndpoint; this.ztsTrustStorePath = ztsTrustStorePath; @@ -91,12 +96,14 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { hostIdentityProvider, new AthenzIdentityVerifier(Set.of(configServerInfo.getConfigServerIdentity()))); this.clock = clock; + this.tenantServiceIdentityFlag = Flags.NODE_ADMIN_TENANT_SERVICE_REGISTRY.bindTo(flagSource); } public boolean converge(NodeAgentContext context) { var modified = false; modified |= maintain(context, NODE); - modified |= maintain(context, TENANT); + if (shouldWriteTenantServiceIdentity(context)) + modified |= maintain(context, TENANT); return modified; } @@ -105,7 +112,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { try { context.log(logger, Level.FINE, "Checking certificate"); - ContainerPath siaDirectory = context.paths().of(identityType.getSiaDirectory(), context.users().vespa()); + ContainerPath siaDirectory = context.paths().of(CONTAINER_SIA_DIRECTORY, context.users().vespa()); ContainerPath identityDocumentFile = siaDirectory.resolve(identityType.getIdentityDocument()); AthenzIdentity athenzIdentity = getAthenzIdentity(context, identityType, identityDocumentFile); ContainerPath privateKeyFile = (ContainerPath) SiaUtils.getPrivateKeyFile(siaDirectory, athenzIdentity); @@ -157,8 +164,6 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { public void clearCredentials(NodeAgentContext context) { FileFinder.files(context.paths().of(CONTAINER_SIA_DIRECTORY)) .deleteRecursively(context); - FileFinder.files(context.paths().of(VESPA_SIA_DIRECTORY)) - .deleteRecursively(context); lastRefreshAttempt.remove(context.containerName()); } @@ -306,21 +311,21 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { } } + private boolean shouldWriteTenantServiceIdentity(NodeAgentContext context) { + return tenantServiceIdentityFlag + .with(FetchVector.Dimension.HOSTNAME, context.hostname().value()) + .value(); + } + enum IdentityType { - NODE(CONTAINER_SIA_DIRECTORY, "vespa-node-identity-document.json"), - TENANT(VESPA_SIA_DIRECTORY, "vespa-tenant-identity-document.json"); + NODE("vespa-node-identity-document.json"), + TENANT("vespa-tenant-identity-document.json"); - private String siaDirectory; private String identityDocument; - IdentityType(String siaDirectory, String identityDocument) { - this.siaDirectory = siaDirectory; + IdentityType(String identityDocument) { this.identityDocument = identityDocument; } - public String getSiaDirectory() { - return siaDirectory; - } - public String getIdentityDocument() { return identityDocument; } -- cgit v1.2.3