diff options
11 files changed, 119 insertions, 22 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java index c19ae86157a..5005ddf309d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java @@ -126,7 +126,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment { log.log(LogLevel.DEBUG, "Trying to acquire lock " + activateLock + " for session " + sessionId); boolean acquired = activateLock.acquire(timeoutBudget, ignoreLockFailure); if ( ! acquired) { - throw new InternalServerException("Did not get activate lock for session " + sessionId + " within " + timeout); + throw new ActivationConflictException("Did not get activate lock for session " + sessionId + " within " + timeout); } log.log(LogLevel.DEBUG, "Lock acquired " + activateLock + " for session " + sessionId); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityVerifier.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityVerifier.java index bfaa6c2acda..527efaab946 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityVerifier.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzIdentityVerifier.java @@ -1,21 +1,26 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.athenz; +import org.apache.http.conn.ssl.X509HostnameVerifier; + import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import java.security.cert.X509Certificate; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** - * A {@link HostnameVerifier} that validates Athenz x509 certificates using the identity in the Common Name attribute. + * A {@link HostnameVerifier} / {@link X509HostnameVerifier} that validates + * Athenz x509 certificates using the identity in the Common Name attribute. * * @author bjorncs */ // TODO Move to dedicated Athenz bundle -public class AthenzIdentityVerifier implements HostnameVerifier { +public class AthenzIdentityVerifier implements X509HostnameVerifier { private static final Logger log = Logger.getLogger(AthenzIdentityVerifier.class.getName()); @@ -29,13 +34,36 @@ public class AthenzIdentityVerifier implements HostnameVerifier { public boolean verify(String hostname, SSLSession session) { try { X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0]; - AthenzIdentity certificateIdentity = AthenzUtils.createAthenzIdentity(cert); - return allowedIdentities.contains(certificateIdentity); + return isTrusted(AthenzUtils.createAthenzIdentity(cert)); } catch (SSLPeerUnverifiedException e) { log.log(Level.WARNING, "Unverified client: " + hostname); return false; } } + @Override + public void verify(String host, SSLSocket ssl) { + // all sockets allowed + } + + @Override + public void verify(String hostname, X509Certificate certificate) throws SSLException { + AthenzIdentity identity = AthenzUtils.createAthenzIdentity(certificate); + if (!isTrusted(identity)) { + throw new SSLException("Athenz identity is not trusted: " + identity.getFullName()); + } + } + + @Override + public void verify(String hostname, String[] cns, String[] subjectAlts) throws SSLException { + AthenzIdentity identity = AthenzUtils.createAthenzIdentity(cns[0]); + if (!isTrusted(identity)) { + throw new SSLException("Athenz identity is not trusted: " + identity.getFullName()); + } + } + + private boolean isTrusted(AthenzIdentity identity) { + return allowedIdentities.contains(identity); + } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java index 03207d86983..e95c297b593 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java @@ -5,6 +5,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; import java.net.URI; import java.time.Duration; @@ -52,4 +53,7 @@ public interface ZoneRegistry { /** Returns the system of this registry. */ SystemName system(); + /** Return the configserver's Athenz service identity */ + AthenzService getConfigserverAthenzService(ZoneId zoneId); + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index e2fca415f4e..44e4cf0740f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -13,6 +13,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.integration.MetricsService; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerClient; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; @@ -23,7 +24,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingSe import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; -import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; @@ -156,9 +156,17 @@ public class Controller extends AbstractComponent { return zoneRegistry.getLogServerUri(deploymentId); } + /** + * @deprecated Use {@link #getSecureConfigServerUris(ZoneId)} instead + */ + @Deprecated public List<URI> getConfigServerUris(ZoneId zoneId) { return zoneRegistry.getConfigServerUris(zoneId); } + + public List<URI> getSecureConfigServerUris(ZoneId zoneId) { + return zoneRegistry.getConfigServerSecureUris(zoneId); + } public ZoneRegistry zoneRegistry() { return zoneRegistry; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java index e8689a06162..7d06bbde081 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java @@ -3,10 +3,13 @@ package com.yahoo.vespa.hosted.controller.proxy; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.Inject; import com.yahoo.config.provision.Environment; -import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.io.IOUtils; import com.yahoo.jdisc.http.HttpRequest.Method; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzIdentityVerifier; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzSslContextProvider; +import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneList; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import org.apache.http.Header; @@ -34,8 +37,11 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import static java.util.Collections.singleton; + /** * @author Haakon Dybdahl + * @author bjorncs */ @SuppressWarnings("unused") // Injected public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { @@ -43,9 +49,13 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { private static final Duration PROXY_REQUEST_TIMEOUT = Duration.ofSeconds(10); private final ZoneRegistry zoneRegistry; + private final AthenzSslContextProvider sslContextProvider; - public ConfigServerRestExecutorImpl(ZoneRegistry zoneRegistry) { + @Inject + public ConfigServerRestExecutorImpl(ZoneRegistry zoneRegistry, + AthenzSslContextProvider sslContextProvider) { this.zoneRegistry = zoneRegistry; + this.sslContextProvider = sslContextProvider; } @Override @@ -57,10 +67,10 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { ZoneId zoneId = ZoneId.from(proxyRequest.getEnvironment(), proxyRequest.getRegion()); // Make a local copy of the list as we want to manipulate it in case of ping problems. - List<URI> allServers = new ArrayList<>(zoneRegistry.getConfigServerUris(zoneId)); + List<URI> allServers = new ArrayList<>(zoneRegistry.getConfigServerSecureUris(zoneId)); StringBuilder errorBuilder = new StringBuilder(); - if (queueFirstServerIfDown(allServers)) { + if (queueFirstServerIfDown(allServers, proxyRequest)) { errorBuilder.append("Change ordering due to failed ping."); } for (URI uri : allServers) { @@ -115,7 +125,7 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { .setConnectionRequestTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis()) .setSocketTimeout((int) PROXY_REQUEST_TIMEOUT.toMillis()).build(); try ( - CloseableHttpClient client = createHttpClient(config); + CloseableHttpClient client = createHttpClient(config, sslContextProvider, zoneRegistry, proxyRequest); CloseableHttpResponse response = client.execute(requestBase); ) { if (response.getStatusLine().getStatusCode() / 100 == 5) { @@ -202,7 +212,7 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { * if it is not responding, we try the other servers first. False positive/negatives are not critical, * but will increase latency to some extent. */ - private boolean queueFirstServerIfDown(List<URI> allServers) { + private boolean queueFirstServerIfDown(List<URI> allServers, ProxyRequest proxyRequest) { if (allServers.size() < 2) { return false; } @@ -215,7 +225,7 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { .setConnectionRequestTimeout(timeout) .setSocketTimeout(timeout).build(); try ( - CloseableHttpClient client = createHttpClient(config); + CloseableHttpClient client = createHttpClient(config, sslContextProvider, zoneRegistry, proxyRequest); CloseableHttpResponse response = client.execute(httpget); ) { @@ -232,9 +242,19 @@ public class ConfigServerRestExecutorImpl implements ConfigServerRestExecutor { return true; } - private static CloseableHttpClient createHttpClient(RequestConfig config) { + private static CloseableHttpClient createHttpClient(RequestConfig config, + AthenzSslContextProvider sslContextProvider, + ZoneRegistry zoneRegistry, + ProxyRequest proxyRequest) { + AthenzIdentityVerifier hostnameVerifier = + new AthenzIdentityVerifier( + singleton( + zoneRegistry.getConfigserverAthenzService( + ZoneId.from(proxyRequest.getEnvironment(), proxyRequest.getRegion())))); return HttpClientBuilder.create() .setUserAgent("config-server-client") + .setSslcontext(sslContextProvider.get()) + .setHostnameVerifier(hostnameVerifier) .setDefaultRequestConfig(config) .build(); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java index 647000aebab..13eec52b97a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java @@ -122,7 +122,7 @@ public class VersionStatus { List<URI> configServers = controller.zoneRegistry().zones() .controllerManaged() .ids().stream() - .flatMap(zoneId -> controller.getConfigServerUris(zoneId).stream()) + .flatMap(zoneId -> controller.getSecureConfigServerUris(zoneId).stream()) .collect(Collectors.toList()); ListMap<Version, String> versions = new ListMap<>(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java index 82434b6260c..21072f0b162 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzService; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneFilter; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneFilterMock; @@ -68,6 +69,10 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry return ZoneFilterMock.from(Collections.unmodifiableList(zones)); } + public AthenzService getConfigserverAthenzService(ZoneId zone) { + return new AthenzService("vespadomain", "provider-" + zone.environment().value() + "-" + zone.region().value()); + } + @Override public boolean hasZone(ZoneId zoneId) { return zones.contains(zoneId); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java index 4f97c078c9b..1c1e2df2c94 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java @@ -11,8 +11,6 @@ import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; -import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; -import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence; @@ -21,7 +19,6 @@ import org.junit.Test; import java.net.URI; import java.net.URISyntaxException; import java.time.Duration; -import java.util.Collections; import java.util.List; import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType.component; @@ -31,7 +28,6 @@ import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobTy import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType.systemTest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** @@ -61,7 +57,7 @@ public class VersionStatusTest { public void testSystemVersionIsVersionOfOldestConfigServer() throws URISyntaxException { ControllerTester tester = new ControllerTester(); Version oldest = new Version(5); - tester.configServer().versions().put(new URI("http://cfg.prod.corp-us-east-1.test"), oldest); + tester.configServer().versions().put(new URI("https://cfg.prod.corp-us-east-1.test:4443"), oldest); VersionStatus versionStatus = VersionStatus.compute(tester.controller()); assertEquals(oldest, versionStatus.systemVersion().get().versionNumber()); } diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java index 031506487a8..d3715a1ff89 100644 --- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java +++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java @@ -93,7 +93,7 @@ public class FileReferenceDownloader { downloads.remove(fileReference); download.future().set(Optional.of(file)); } else { - log.log(LogLevel.WARNING, "Received a file " + fileReference + " I did not ask for. Impossible"); + log.log(LogLevel.INFO, "Received '" + fileReference + "', which was not requested. Can be ignored if happening during upgrades/restarts"); } } } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java index 981d4219158..1f2fb40f42f 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java @@ -94,7 +94,7 @@ public class ConnectorFactory { private SslConnectionFactory newSslConnectionFactory() { Ssl sslConfig = connectorConfig.ssl(); - SslContextFactory factory = new SslContextFactory(); + SslContextFactory factory = new JDiscSslContextFactory(); sslKeyStoreConfigurator.configure(new DefaultSslKeyStoreContext(factory)); sslTrustStoreConfigurator.configure(new DefaultSslTrustStoreContext(factory)); diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscSslContextFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscSslContextFactory.java new file mode 100644 index 00000000000..81a6a0c8048 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscSslContextFactory.java @@ -0,0 +1,36 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty; + +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.security.CertificateUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import java.security.KeyStore; +import java.util.Objects; + +/** + * A modified {@link SslContextFactory} that allows passwordless truststore in combination with password protected keystore. + * + * @author bjorncs + */ +class JDiscSslContextFactory extends SslContextFactory { + + private String trustStorePassword; + + @Override + public void setTrustStorePassword(String password) { + super.setTrustStorePassword(password); + this.trustStorePassword = password; + } + + + // Overriden to stop Jetty from using the keystore password if no truststore password is specified. + @Override + protected KeyStore loadTrustStore(Resource resource) throws Exception { + return CertificateUtils.getKeyStore( + resource != null ? resource : getKeyStoreResource(), + Objects.toString(getTrustStoreType(), getKeyStoreType()), + Objects.toString(getTrustStoreProvider(), getKeyStoreProvider()), + trustStorePassword); + } +} |