diff options
author | andreer <andreer@verizonmedia.com> | 2020-02-10 12:42:10 +0100 |
---|---|---|
committer | andreer <andreer@verizonmedia.com> | 2020-02-10 12:42:10 +0100 |
commit | 9d07146178edc7892bb09506f468c836a9de30d4 (patch) | |
tree | f69fda94a2b6d6741b7482e6ae00f66b454da666 /controller-server | |
parent | 1dcf7ea5baf95393c28cb83646957d0793def6b8 (diff) |
backfill certificate metadata from provider
Diffstat (limited to 'controller-server')
2 files changed, 88 insertions, 2 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/endpointcertificates/EndpointCertificateManager.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/endpointcertificates/EndpointCertificateManager.java index c90d5886777..f42ab95ed89 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/endpointcertificates/EndpointCertificateManager.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/endpointcertificates/EndpointCertificateManager.java @@ -14,6 +14,7 @@ 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.flags.StringFlag; import com.yahoo.vespa.hosted.controller.Instance; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata; @@ -27,11 +28,15 @@ import java.time.Clock; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -52,6 +57,7 @@ public class EndpointCertificateManager { private final EndpointCertificateProvider endpointCertificateProvider; private final Clock clock; private final BooleanFlag useRefreshedEndpointCertificate; + private final StringFlag endpointCertificateBackfill; public EndpointCertificateManager(ZoneRegistry zoneRegistry, CuratorDb curator, @@ -64,6 +70,8 @@ public class EndpointCertificateManager { this.endpointCertificateProvider = endpointCertificateProvider; this.clock = clock; this.useRefreshedEndpointCertificate = Flags.USE_REFRESHED_ENDPOINT_CERTIFICATE.bindTo(flagSource); + this.endpointCertificateBackfill = Flags.ENDPOINT_CERTIFICATE_BACKFILL.bindTo(flagSource); + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(this::backfillCertificateMetadata, 1, 10, TimeUnit.MINUTES); } public Optional<EndpointCertificateMetadata> getEndpointCertificateMetadata(Instance instance, ZoneId zone) { @@ -97,6 +105,71 @@ public class EndpointCertificateManager { return Optional.of(endpointCertificateMetadata); } + enum BackfillMode { + DISABLE, + DRYRUN, + ENABLE; + + BackfillMode fromString(String backfillMode) { + switch (backfillMode) { + case "disable": + return DISABLE; + case "dryrun": + return DRYRUN; + case "enable": + return ENABLE; + default: + throw new RuntimeException("Uknown backfill mode!"); + } + } + } + + private void backfillCertificateMetadata() { + BackfillMode mode = BackfillMode.valueOf(endpointCertificateBackfill.value()); + if (mode == BackfillMode.DISABLE) return; + + List<EndpointCertificateMetadata> allProviderCertificateMetadata = endpointCertificateProvider.listCertificates(); + Map<String, EndpointCertificateMetadata> sanToEndpointCertificate = new HashMap<>(); + + allProviderCertificateMetadata.forEach((providerMetadata -> { + if (providerMetadata.request_id().isEmpty()) + throw new RuntimeException("Backfill failed - provider metadata missing request_id"); + if (providerMetadata.requestedDnsSans().isEmpty()) + throw new RuntimeException("Backfill failed - provider metadata missing DNS SANs for " + providerMetadata.request_id().get()); + providerMetadata.requestedDnsSans().get().forEach(san -> { + var previous = sanToEndpointCertificate.put(san, providerMetadata); + if (previous != null) + throw new RuntimeException("Backfill failed - Overlapping SANs in certificates " + + providerMetadata.request_id() + " and " + previous.request_id()); + } + ); + })); + + Map<ApplicationId, EndpointCertificateMetadata> allEndpointCertificateMetadata = curator.readAllEndpointCertificateMetadata(); + + allEndpointCertificateMetadata.forEach((applicationId, storedMetaData) -> { + if (storedMetaData.requestedDnsSans().isPresent() && storedMetaData.request_id().isPresent()) + return; + + var hashedCn = Endpoint.createHashedCn(applicationId, zoneRegistry.system()); // use as join key + EndpointCertificateMetadata providerMetadata = sanToEndpointCertificate.get(hashedCn); + + EndpointCertificateMetadata backfilledMetadata = + new EndpointCertificateMetadata( + storedMetaData.keyName(), + storedMetaData.certName(), + storedMetaData.version(), + providerMetadata.request_id(), + providerMetadata.requestedDnsSans()); + + if (mode == BackfillMode.DRYRUN) { + log.log(LogLevel.INFO, "Would update stored metadata " + storedMetaData + " with data from provider: " + backfilledMetadata); + } else if (mode == BackfillMode.ENABLE) { + curator.writeEndpointCertificateMetadata(applicationId, backfilledMetadata); + } + }); + } + private OptionalInt latestVersionInSecretStore(EndpointCertificateMetadata originalCertificateMetadata) { var certVersions = new HashSet<>(secretStore.listSecretVersions(originalCertificateMetadata.certName())); var keyVersions = new HashSet<>(secretStore.listSecretVersions(originalCertificateMetadata.keyName())); @@ -140,7 +213,7 @@ public class EndpointCertificateManager { .filter(san -> san.getType().equals(SubjectAlternativeName.Type.DNS_NAME)) .map(SubjectAlternativeName::getValue).collect(Collectors.toSet()); - if(Sets.intersection(subjectAlternativeNames, Set.copyOf(dnsNamesOf(instance.id(), List.of(zone)))).isEmpty()) { + if (Sets.intersection(subjectAlternativeNames, Set.copyOf(dnsNamesOf(instance.id(), List.of(zone)))).isEmpty()) { return logWarning(warningPrefix, "No overlap between SANs in certificate and expected SANs"); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java index 368621cac96..ae3f3da8066 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java @@ -40,7 +40,9 @@ import java.nio.ByteBuffer; import java.time.Duration; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NavigableMap; @@ -512,7 +514,7 @@ public class CuratorDb { .orElse(new ZoneRoutingPolicy(zone, GlobalRouting.DEFAULT_STATUS)); } - // -------------- Application web certificates ---------------------------- + // -------------- Application endpoint certificates ---------------------------- public void writeEndpointCertificateMetadata(ApplicationId applicationId, EndpointCertificateMetadata endpointCertificateMetadata) { curator.set(endpointCertificatePath(applicationId), asJson(EndpointCertificateMetadataSerializer.toSlime(endpointCertificateMetadata))); @@ -523,6 +525,17 @@ public class CuratorDb { return zkData.map(EndpointCertificateMetadataSerializer::fromJsonOrTlsSecretsKeysString); } + public Map<ApplicationId, EndpointCertificateMetadata> readAllEndpointCertificateMetadata() { + Map<ApplicationId, EndpointCertificateMetadata> allEndpointCertificateMetadata = new HashMap<>(); + Iterator<String> zkNodes = endpointCertificateRoot.iterator(); + while(zkNodes.hasNext()) { + ApplicationId applicationId = ApplicationId.fromSerializedForm(zkNodes.next()); + Optional<EndpointCertificateMetadata> endpointCertificateMetadata = readEndpointCertificateMetadata(applicationId); + allEndpointCertificateMetadata.put(applicationId, endpointCertificateMetadata.orElseThrow()); + } + return allEndpointCertificateMetadata; + } + // -------------- Paths --------------------------------------------------- private Path lockPath(TenantName tenant) { |