diff options
author | Andreas Eriksen <andreer@verizonmedia.com> | 2021-02-04 16:18:12 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-04 16:18:12 +0100 |
commit | e244a4b57e0641c2882104cf114fd2fb2058d608 (patch) | |
tree | c572e070d51f985db20f1fa17212d725f03a0cc8 /controller-api | |
parent | 21dcc03fb48657b38d490d3708dc9a473c1735e6 (diff) |
eliminate validate-endpoint-certificates feature flag (#16385)
Diffstat (limited to 'controller-api')
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! + } +} |