summaryrefslogtreecommitdiffstats
path: root/controller-api
diff options
context:
space:
mode:
authorAndreas Eriksen <andreer@verizonmedia.com>2021-02-04 16:18:12 +0100
committerGitHub <noreply@github.com>2021-02-04 16:18:12 +0100
commite244a4b57e0641c2882104cf114fd2fb2058d608 (patch)
treec572e070d51f985db20f1fa17212d725f03a0cc8 /controller-api
parent21dcc03fb48657b38d490d3708dc9a473c1735e6 (diff)
eliminate validate-endpoint-certificates feature flag (#16385)
Diffstat (limited to 'controller-api')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateException.java26
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidator.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java75
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java16
5 files changed, 129 insertions, 0 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java
index d35f8f00fd1..7100baea5a0 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/ServiceRegistry.java
@@ -6,6 +6,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.aws.AwsEventFetcher;
import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidator;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.container.ContainerRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationStore;
@@ -46,6 +47,8 @@ public interface ServiceRegistry {
EndpointCertificateProvider endpointCertificateProvider();
+ EndpointCertificateValidator endpointCertificateValidator();
+
MeteringClient meteringService();
ContactRetriever contactRetriever();
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateException.java
new file mode 100644
index 00000000000..17e8087ca07
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateException.java
@@ -0,0 +1,26 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.certificates;
+
+public class EndpointCertificateException extends RuntimeException {
+
+ private final Type type;
+
+ public EndpointCertificateException(Type type, String message) {
+ super(message);
+ this.type = type;
+ }
+
+ public EndpointCertificateException(Type type, String message, Throwable cause) {
+ super(message, cause);
+ this.type = type;
+ }
+
+ public Type type() {
+ return type;
+ }
+
+ public enum Type {
+ CERT_NOT_AVAILABLE,
+ VERIFICATION_FAILURE
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidator.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidator.java
new file mode 100644
index 00000000000..2c56cbf580d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidator.java
@@ -0,0 +1,9 @@
+package com.yahoo.vespa.hosted.controller.api.integration.certificates;
+
+import com.yahoo.config.provision.zone.ZoneId;
+
+import java.util.List;
+
+public interface EndpointCertificateValidator {
+ void validate(EndpointCertificateMetadata endpointCertificateMetadata, String serializedInstanceId, ZoneId zone, List<String> requiredNamesForZone);
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java
new file mode 100644
index 00000000000..90cbaecb628
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java
@@ -0,0 +1,75 @@
+package com.yahoo.vespa.hosted.controller.api.integration.certificates;
+
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.container.jdisc.secretstore.SecretNotFoundException;
+import com.yahoo.container.jdisc.secretstore.SecretStore;
+import com.yahoo.security.SubjectAlternativeName;
+import com.yahoo.security.X509CertificateUtils;
+
+import java.security.cert.X509Certificate;
+import java.time.Clock;
+import java.time.Instant;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+public class EndpointCertificateValidatorImpl implements EndpointCertificateValidator {
+ private final SecretStore secretStore;
+ private final Clock clock;
+
+ private static final Logger log = Logger.getLogger(EndpointCertificateValidator.class.getName());
+
+ public EndpointCertificateValidatorImpl(SecretStore secretStore, Clock clock) {
+ this.secretStore = secretStore;
+ this.clock = clock;
+ }
+
+ @Override
+ public void validate(EndpointCertificateMetadata endpointCertificateMetadata, String serializedInstanceId, ZoneId zone, List<String> requiredNamesForZone) {
+ try {
+ var pemEncodedEndpointCertificate = secretStore.getSecret(endpointCertificateMetadata.certName(), endpointCertificateMetadata.version());
+
+ if (pemEncodedEndpointCertificate == null)
+ throw new EndpointCertificateException(EndpointCertificateException.Type.VERIFICATION_FAILURE, "Secret store returned null for certificate");
+
+ List<X509Certificate> x509CertificateList = X509CertificateUtils.certificateListFromPem(pemEncodedEndpointCertificate);
+
+ if (x509CertificateList.isEmpty())
+ throw new EndpointCertificateException(EndpointCertificateException.Type.VERIFICATION_FAILURE, "Empty certificate list");
+ if (x509CertificateList.size() < 2)
+ throw new EndpointCertificateException(EndpointCertificateException.Type.VERIFICATION_FAILURE, "Only a single certificate found in chain - intermediate certificates likely missing");
+
+ Instant now = clock.instant();
+ Instant firstExpiry = Instant.MAX;
+ for (X509Certificate x509Certificate : x509CertificateList) {
+ Instant notBefore = x509Certificate.getNotBefore().toInstant();
+ Instant notAfter = x509Certificate.getNotAfter().toInstant();
+ if (now.isBefore(notBefore))
+ throw new EndpointCertificateException(EndpointCertificateException.Type.VERIFICATION_FAILURE, "Certificate is not yet valid");
+ if (now.isAfter(notAfter))
+ throw new EndpointCertificateException(EndpointCertificateException.Type.VERIFICATION_FAILURE, "Certificate has expired");
+ if (notAfter.isBefore(firstExpiry)) firstExpiry = notAfter;
+ }
+
+ X509Certificate endEntityCertificate = x509CertificateList.get(0);
+ Set<String> subjectAlternativeNames = X509CertificateUtils.getSubjectAlternativeNames(endEntityCertificate).stream()
+ .filter(san -> san.getType().equals(SubjectAlternativeName.Type.DNS_NAME))
+ .map(SubjectAlternativeName::getValue).collect(Collectors.toSet());
+
+ if (!subjectAlternativeNames.containsAll(requiredNamesForZone))
+ throw new EndpointCertificateException(EndpointCertificateException.Type.VERIFICATION_FAILURE, "Certificate is missing required SANs for zone " + zone.value());
+
+ } catch (SecretNotFoundException s) {
+ // Normally because the cert is in the process of being provisioned - this will cause a retry in InternalStepRunner
+ throw new EndpointCertificateException(EndpointCertificateException.Type.CERT_NOT_AVAILABLE, "Certificate not found in secret store");
+ } catch (EndpointCertificateException e) {
+ log.log(Level.WARNING, "Certificate validation failure for " + serializedInstanceId, e);
+ throw e;
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Certificate validation failure for " + serializedInstanceId, e);
+ throw new EndpointCertificateException(EndpointCertificateException.Type.VERIFICATION_FAILURE, "Certificate validation failure for app " + serializedInstanceId, e);
+ }
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java
new file mode 100644
index 00000000000..fb0121d3612
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorMock.java
@@ -0,0 +1,16 @@
+package com.yahoo.vespa.hosted.controller.api.integration.certificates;
+
+import com.yahoo.config.provision.zone.ZoneId;
+
+import java.util.List;
+
+public class EndpointCertificateValidatorMock implements EndpointCertificateValidator {
+ @Override
+ public void validate(
+ EndpointCertificateMetadata endpointCertificateMetadata,
+ String serializedApplicationId,
+ ZoneId zone,
+ List<String> requiredNamesForZone) {
+ // Mock does no validation - for unit tests only!
+ }
+}