summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Tokle <morten.tokle@gmail.com>2017-11-06 12:28:00 +0100
committerGitHub <noreply@github.com>2017-11-06 12:28:00 +0100
commitdfcd99818b4b0151ad226df03548915f9dddd9fb (patch)
treeb2a9e280d7e14fff88b0b1e0cdb0990b115b1e48
parentc8e3ec213f31627e34a3dc0fc28429aefeebaf6e (diff)
parent03e895730cea1257941f4790438dc347571d9d65 (diff)
Merge pull request #3988 from vespa-engine/freva/instance-validator
Freva/instance validator
-rw-r--r--athenz-identity-provider-service/pom.xml6
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderService.java62
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidator.java64
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderServiceTest.java140
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ScheduledExecutorServiceMock.java115
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGeneratorTest.java98
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidatorTest.java171
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java6
8 files changed, 415 insertions, 247 deletions
diff --git a/athenz-identity-provider-service/pom.xml b/athenz-identity-provider-service/pom.xml
index bb82e40f827..26e24be526c 100644
--- a/athenz-identity-provider-service/pom.xml
+++ b/athenz-identity-provider-service/pom.xml
@@ -82,6 +82,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>config-model-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
<!-- TEST -->
<dependency>
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderService.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderService.java
index 74b697fb004..26a88896fb9 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderService.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderService.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
+import com.yahoo.config.model.api.SuperModelProvider;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.jdisc.http.SecretStore;
@@ -48,25 +49,39 @@ public class AthenzInstanceProviderService extends AbstractComponent {
private final Server jetty;
@Inject
- public AthenzInstanceProviderService(AthenzProviderServiceConfig config, NodeRepository nodeRepository, Zone zone, SecretStore secretStore) {
+ public AthenzInstanceProviderService(AthenzProviderServiceConfig config, SuperModelProvider superModelProvider,
+ NodeRepository nodeRepository, Zone zone, SecretStore secretStore) {
this(config, new SecretStoreKeyProvider(secretStore, getZoneConfig(config, zone).secretName()), Executors.newSingleThreadScheduledExecutor(),
- nodeRepository, zone, new AthenzCertificateClient(config, getZoneConfig(config, zone)));
+ superModelProvider, nodeRepository, zone, new AthenzCertificateClient(config, getZoneConfig(config, zone)), createSslContextFactory());
+ }
+
+ private AthenzInstanceProviderService(AthenzProviderServiceConfig config,
+ KeyProvider keyProvider,
+ ScheduledExecutorService scheduler,
+ SuperModelProvider superModelProvider,
+ NodeRepository nodeRepository,
+ Zone zone,
+ CertificateClient certificateClient,
+ SslContextFactory sslContextFactory) {
+ this(config, scheduler, zone, sslContextFactory,
+ new InstanceValidator(keyProvider, superModelProvider),
+ new IdentityDocumentGenerator(config, getZoneConfig(config, zone), nodeRepository, zone, keyProvider),
+ new AthenzCertificateUpdater(
+ certificateClient, sslContextFactory, keyProvider, config, getZoneConfig(config, zone)));
}
AthenzInstanceProviderService(AthenzProviderServiceConfig config,
- KeyProvider keyProvider,
ScheduledExecutorService scheduler,
- NodeRepository nodeRepository,
Zone zone,
- CertificateClient certificateClient) {
+ SslContextFactory sslContextFactory,
+ InstanceValidator instanceValidator,
+ IdentityDocumentGenerator identityDocumentGenerator,
+ AthenzCertificateUpdater reloader) {
// TODO: Enable for all systems. Currently enabled for CD system only
if (SystemName.cd.equals(zone.system())) {
this.scheduler = scheduler;
- SslContextFactory sslContextFactory = createSslContextFactory();
- this.jetty = createJettyServer(
- config, keyProvider, sslContextFactory, nodeRepository, zone);
- AthenzCertificateUpdater reloader =
- new AthenzCertificateUpdater(certificateClient, sslContextFactory, keyProvider, config, getZoneConfig(config, zone));
+ this.jetty = createJettyServer(config, sslContextFactory, instanceValidator, identityDocumentGenerator);
+
// TODO Configurable update frequency
scheduler.scheduleAtFixedRate(reloader, 0, 1, TimeUnit.DAYS);
try {
@@ -81,22 +96,19 @@ public class AthenzInstanceProviderService extends AbstractComponent {
}
private static Server createJettyServer(AthenzProviderServiceConfig config,
- KeyProvider keyProvider,
SslContextFactory sslContextFactory,
- NodeRepository nodeRepository,
- Zone zone) {
+ InstanceValidator instanceValidator,
+ IdentityDocumentGenerator identityDocumentGenerator) {
Server server = new Server();
ServerConnector connector = new ServerConnector(server, sslContextFactory);
connector.setPort(config.port());
server.addConnector(connector);
ServletHandler handler = new ServletHandler();
- InstanceConfirmationServlet instanceConfirmationServlet =
- new InstanceConfirmationServlet(new InstanceValidator(keyProvider));
+ InstanceConfirmationServlet instanceConfirmationServlet = new InstanceConfirmationServlet(instanceValidator);
handler.addServletWithMapping(new ServletHolder(instanceConfirmationServlet), config.apiPath() + "/instance");
- IdentityDocumentServlet identityDocumentServlet =
- new IdentityDocumentServlet(new IdentityDocumentGenerator(config, getZoneConfig(config, zone), nodeRepository, zone, keyProvider));
+ IdentityDocumentServlet identityDocumentServlet = new IdentityDocumentServlet(identityDocumentGenerator);
handler.addServletWithMapping(new ServletHolder(identityDocumentServlet), config.apiPath() + "/identity-document");
handler.addServletWithMapping(StatusServlet.class, "/status.html");
@@ -110,7 +122,7 @@ public class AthenzInstanceProviderService extends AbstractComponent {
return config.zones(key);
}
- private static SslContextFactory createSslContextFactory() {
+ static SslContextFactory createSslContextFactory() {
try {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setWantClientAuth(true);
@@ -122,7 +134,7 @@ public class AthenzInstanceProviderService extends AbstractComponent {
}
}
- private static class AthenzCertificateUpdater implements Runnable {
+ static class AthenzCertificateUpdater implements Runnable {
// TODO Make expiry a configuration parameter
private static final TemporalAmount EXPIRY_TIME = Duration.ofDays(30);
@@ -134,11 +146,11 @@ public class AthenzInstanceProviderService extends AbstractComponent {
private final AthenzProviderServiceConfig config;
private final AthenzProviderServiceConfig.Zones zoneConfig;
- private AthenzCertificateUpdater(CertificateClient certificateClient,
- SslContextFactory sslContextFactory,
- KeyProvider keyProvider,
- AthenzProviderServiceConfig config,
- AthenzProviderServiceConfig.Zones zoneConfig) {
+ AthenzCertificateUpdater(CertificateClient certificateClient,
+ SslContextFactory sslContextFactory,
+ KeyProvider keyProvider,
+ AthenzProviderServiceConfig config,
+ AthenzProviderServiceConfig.Zones zoneConfig) {
this.certificateClient = certificateClient;
this.sslContextFactory = sslContextFactory;
this.keyProvider = keyProvider;
@@ -179,7 +191,7 @@ public class AthenzInstanceProviderService extends AbstractComponent {
log.log(LogLevel.INFO, "Deconstructing Athenz provider service");
if(scheduler != null)
scheduler.shutdown();
- if(jetty !=null)
+ if(jetty != null)
jetty.stop();
if (scheduler != null && !scheduler.awaitTermination(1, TimeUnit.MINUTES)) {
log.log(LogLevel.ERROR, "Failed to stop certificate updater");
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidator.java
index 8d76300c2bb..427f35c41d8 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidator.java
@@ -1,6 +1,10 @@
// 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.athenz.instanceproviderservice.impl;
+import com.yahoo.config.model.api.ApplicationInfo;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.config.model.api.SuperModelProvider;
+import com.yahoo.config.provision.ApplicationId;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.InstanceConfirmation;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.ProviderUniqueId;
@@ -12,6 +16,7 @@ import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;
+import java.util.Optional;
import java.util.logging.Logger;
/**
@@ -22,19 +27,29 @@ import java.util.logging.Logger;
public class InstanceValidator {
private static final Logger log = Logger.getLogger(InstanceValidator.class.getName());
+ static final String SERVICE_PROPERTIES_DOMAIN_KEY = "identity.domain";
+ static final String SERVICE_PROPERTIES_SERVICE_KEY = "identity.service";
private final KeyProvider keyProvider;
+ private final SuperModelProvider superModelProvider;
- public InstanceValidator(KeyProvider keyProvider) {
+ public InstanceValidator(KeyProvider keyProvider, SuperModelProvider superModelProvider) {
this.keyProvider = keyProvider;
+ this.superModelProvider = superModelProvider;
}
public boolean isValidInstance(InstanceConfirmation instanceConfirmation) {
SignedIdentityDocument signedIdentityDocument = instanceConfirmation.signedIdentityDocument;
ProviderUniqueId providerUniqueId = signedIdentityDocument.identityDocument.providerUniqueId;
+ ApplicationId applicationId = ApplicationId.from(
+ providerUniqueId.tenant, providerUniqueId.application, providerUniqueId.instance);
+
+ if (! isSameIdentityAsInServicesXml(applicationId, instanceConfirmation.domain, instanceConfirmation.service)) {
+ return false;
+ }
+
log.log(LogLevel.INFO, () -> String.format("Validating instance %s.", providerUniqueId));
- PublicKey publicKey = keyProvider.getPublicKey(signedIdentityDocument.signingKeyVersion);
- if (isSignatureValid(publicKey, signedIdentityDocument.rawIdentityDocument, signedIdentityDocument.signature)) {
+ if (isInstanceSignatureValid(instanceConfirmation)) {
log.log(LogLevel.INFO, () -> String.format("Instance %s is valid.", providerUniqueId));
return true;
}
@@ -42,7 +57,14 @@ public class InstanceValidator {
return false;
}
- public static boolean isSignatureValid(PublicKey publicKey, String rawIdentityDocument, String signature) {
+ boolean isInstanceSignatureValid(InstanceConfirmation instanceConfirmation) {
+ SignedIdentityDocument signedIdentityDocument = instanceConfirmation.signedIdentityDocument;
+
+ PublicKey publicKey = keyProvider.getPublicKey(signedIdentityDocument.signingKeyVersion);
+ return isSignatureValid(publicKey, signedIdentityDocument.rawIdentityDocument, signedIdentityDocument.signature);
+ }
+
+ static boolean isSignatureValid(PublicKey publicKey, String rawIdentityDocument, String signature) {
try {
Signature signatureVerifier = Signature.getInstance("SHA512withRSA");
signatureVerifier.initVerify(publicKey);
@@ -52,4 +74,38 @@ public class InstanceValidator {
throw new RuntimeException(e);
}
}
+
+ // If/when we dont care about logging exactly whats wrong, this can be simplified
+ boolean isSameIdentityAsInServicesXml(ApplicationId applicationId, String domain, String service) {
+ Optional<ApplicationInfo> applicationInfo = superModelProvider.getSuperModel().getApplicationInfo(applicationId);
+
+ if (!applicationInfo.isPresent()) {
+ log.info(String.format("Could not find application info for %s", applicationId.serializedForm()));
+ return false;
+ }
+
+ Optional<ServiceInfo> matchingServiceInfo = applicationInfo.get()
+ .getModel()
+ .getHosts()
+ .stream()
+ .flatMap(hostInfo -> hostInfo.getServices().stream())
+ .filter(serviceInfo -> serviceInfo.getProperty(SERVICE_PROPERTIES_DOMAIN_KEY).isPresent())
+ .filter(serviceInfo -> serviceInfo.getProperty(SERVICE_PROPERTIES_SERVICE_KEY).isPresent())
+ .findFirst();
+
+ if (!matchingServiceInfo.isPresent()) {
+ log.info(String.format("Application %s has not specified domain/service", applicationId.serializedForm()));
+ return false;
+ }
+
+ String domainInConfig = matchingServiceInfo.get().getProperty(SERVICE_PROPERTIES_DOMAIN_KEY).get();
+ String serviceInConfig = matchingServiceInfo.get().getProperty(SERVICE_PROPERTIES_SERVICE_KEY).get();
+ if (!domainInConfig.equals(domain) || !serviceInConfig.equals(service)) {
+ log.warning(String.format("domain '%s' or service '%s' does not match the one in config for application %s",
+ domain, service, applicationId.serializedForm()));
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderServiceTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderServiceTest.java
index 20a56359eff..bf0746aee7e 100644
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderServiceTest.java
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderServiceTest.java
@@ -1,22 +1,14 @@
// 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.athenz.instanceproviderservice;
-import com.fasterxml.jackson.core.JsonProcessingException;
+import athenz.shade.zts.jersey.repackaged.com.google.common.collect.ImmutableMap;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.yahoo.component.Version;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.InstanceName;
-import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.AthenzInstanceProviderService.AthenzCertificateUpdater;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.CertificateClient;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.IdentityDocumentGenerator;
@@ -27,11 +19,6 @@ import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.Identity
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.InstanceConfirmation;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.ProviderUniqueId;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.SignedIdentityDocument;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
-import com.yahoo.vespa.hosted.provision.node.Allocation;
-import com.yahoo.vespa.hosted.provision.node.Generation;
-import com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
@@ -53,13 +40,12 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Test;
import javax.net.ssl.SSLContext;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
-import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -68,7 +54,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
-import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
@@ -76,16 +61,15 @@ import java.time.temporal.TemporalAmount;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
-import java.util.HashSet;
-import java.util.Optional;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import static org.hamcrest.CoreMatchers.equalTo;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -102,28 +86,35 @@ public class AthenzInstanceProviderServiceTest {
public void provider_service_hosts_endpoint_secured_with_tls() throws Exception {
String domain = "domain";
String service = "service";
+
AutoGeneratedKeyProvider keyProvider = new AutoGeneratedKeyProvider();
PrivateKey privateKey = keyProvider.getPrivateKey(0);
AthenzProviderServiceConfig config = getAthenzProviderConfig(domain, service, "vespa.dns.suffix", ZONE);
- ScheduledExecutorServiceMock executor = new ScheduledExecutorServiceMock();
+ SslContextFactory sslContextFactory = AthenzInstanceProviderService.createSslContextFactory();
+ AthenzCertificateUpdater certificateUpdater = new AthenzCertificateUpdater(
+ new SelfSignedCertificateClient(keyProvider.getKeyPair(), config, getZoneConfig(config, ZONE)),
+ sslContextFactory,
+ keyProvider,
+ config,
+ getZoneConfig(config, ZONE));
+
+ ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+ when(executor.awaitTermination(anyLong(), any())).thenReturn(true);
+
+ InstanceValidator instanceValidator = mock(InstanceValidator.class);
+ when(instanceValidator.isValidInstance(any())).thenReturn(true);
- AthenzInstanceProviderService athenzInstanceProviderService =
- new AthenzInstanceProviderService(config,
- keyProvider,
- executor,
- mock(NodeRepository.class),
- ZONE,
- new SelfSignedCertificateClient(keyProvider.getKeyPair(), config,
- getZoneConfig(config, ZONE)));
+ IdentityDocumentGenerator identityDocumentGenerator = mock(IdentityDocumentGenerator.class);
+
+ AthenzInstanceProviderService athenzInstanceProviderService = new AthenzInstanceProviderService(
+ config, executor, ZONE, sslContextFactory, instanceValidator, identityDocumentGenerator, certificateUpdater);
try (CloseableHttpClient client = createHttpClient(domain, service)) {
- Runnable certificateRefreshCommand = executor.getCommand()
- .orElseThrow(() -> new AssertionError("Command not present"));
assertFalse(getStatus(client));
- certificateRefreshCommand.run();
+ certificateUpdater.run();
assertTrue(getStatus(client));
assertInstanceConfirmationSucceeds(client, privateKey);
- certificateRefreshCommand.run();
+ certificateUpdater.run();
assertTrue(getStatus(client));
assertInstanceConfirmationSucceeds(client, privateKey);
} finally {
@@ -131,62 +122,7 @@ public class AthenzInstanceProviderServiceTest {
}
}
- @Test
- public void generates_valid_identity_document() throws Exception {
- String hostname = "x.y.com";
-
- ApplicationId appid = ApplicationId.from(
- TenantName.from("tenant"), ApplicationName.from("application"), InstanceName.from("default"));
- Allocation allocation = new Allocation(appid,
- ClusterMembership.from("container/default/0/0", Version.fromString("1.2.3")),
- Generation.inital(),
- false);
- Node n = Node.create("ostkid",
- ImmutableSet.of("127.0.0.1"),
- new HashSet<>(),
- hostname,
- Optional.empty(),
- new MockNodeFlavors().getFlavorOrThrow("default"),
- NodeType.tenant)
- .with(allocation);
-
- NodeRepository nodeRepository = mock(NodeRepository.class);
- when(nodeRepository.getNode(eq(hostname))).thenReturn(Optional.of(n));
- AutoGeneratedKeyProvider keyProvider = new AutoGeneratedKeyProvider();
-
- String dnsSuffix = "vespa.dns.suffix";
- AthenzProviderServiceConfig athenzProviderConfig = getAthenzProviderConfig("domain", "service", dnsSuffix, ZONE);
- IdentityDocumentGenerator identityDocumentGenerator = new IdentityDocumentGenerator(
- athenzProviderConfig,
- getZoneConfig(athenzProviderConfig, ZONE),
- nodeRepository,
- ZONE,
- keyProvider);
- String rawSignedIdentityDocument = identityDocumentGenerator.generateSignedIdentityDocument(hostname);
-
-
- SignedIdentityDocument signedIdentityDocument =
- Utils.getMapper().readValue(rawSignedIdentityDocument, SignedIdentityDocument.class);
-
- // Verify attributes
- assertEquals(hostname, signedIdentityDocument.identityDocument.instanceHostname);
-
- String environment = "dev";
- String region = "us-north-1";
- String expectedZoneDnsSuffix = environment + "-" + region + "." + dnsSuffix;
- assertEquals(expectedZoneDnsSuffix, signedIdentityDocument.dnsSuffix);
-
- ProviderUniqueId expectedProviderUniqueId =
- new ProviderUniqueId("tenant", "application", environment, region, "default", "default", 0);
- assertEquals(expectedProviderUniqueId, signedIdentityDocument.identityDocument.providerUniqueId);
-
- // Validate signature
- assertTrue("Message", InstanceValidator.isSignatureValid(keyProvider.getPublicKey(0),
- signedIdentityDocument.rawIdentityDocument,
- signedIdentityDocument.signature));
- }
-
- private static AthenzProviderServiceConfig getAthenzProviderConfig(String domain, String service, String dnsSuffix, Zone zone) {
+ public static AthenzProviderServiceConfig getAthenzProviderConfig(String domain, String service, String dnsSuffix, Zone zone) {
AthenzProviderServiceConfig.Zones.Builder zoneConfig =
new AthenzProviderServiceConfig.Zones.Builder()
.serviceName(service)
@@ -205,7 +141,7 @@ public class AthenzInstanceProviderServiceTest {
}
- private AthenzProviderServiceConfig.Zones getZoneConfig(AthenzProviderServiceConfig config, Zone zone) {
+ public static AthenzProviderServiceConfig.Zones getZoneConfig(AthenzProviderServiceConfig config, Zone zone) {
return config.zones(zone.environment().value() + "." + zone.region().value());
}
@@ -264,23 +200,23 @@ public class AthenzInstanceProviderServiceTest {
"localhost/zts",
1));
return new StringEntity(mapper.writeValueAsString(instanceConfirmation));
- } catch (JsonProcessingException
- | NoSuchAlgorithmException
- | UnsupportedEncodingException
- | SignatureException
- | InvalidKeyException e) {
+ } catch (Exception e) {
throw new RuntimeException(e);
}
}
- private static class AutoGeneratedKeyProvider implements KeyProvider {
+ public static class AutoGeneratedKeyProvider implements KeyProvider {
private final KeyPair keyPair;
- public AutoGeneratedKeyProvider() throws IOException, NoSuchAlgorithmException {
- KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
- rsa.initialize(2048);
- keyPair = rsa.genKeyPair();
+ public AutoGeneratedKeyProvider() {
+ try {
+ KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
+ rsa.initialize(2048);
+ keyPair = rsa.genKeyPair();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
}
@Override
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ScheduledExecutorServiceMock.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ScheduledExecutorServiceMock.java
deleted file mode 100644
index 45cb82a0c0a..00000000000
--- a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ScheduledExecutorServiceMock.java
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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.athenz.instanceproviderservice;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * @author bjorncs
- */
-public class ScheduledExecutorServiceMock implements ScheduledExecutorService {
-
- private Runnable runnable;
-
- public Optional<Runnable> getCommand() {
- return Optional.ofNullable(runnable);
- }
-
- @Override
- public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
- if (runnable != null) {
- throw new IllegalStateException("Can only register single command");
- }
- runnable = Objects.requireNonNull(command);
- return null;
- }
-
- @Override
- public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void shutdown() {
- // do nothing
- }
-
- @Override
- public List<Runnable> shutdownNow() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isShutdown() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isTerminated() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
- return true;
- }
-
- @Override
- public <T> Future<T> submit(Callable<T> task) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public <T> Future<T> submit(Runnable task, T result) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Future<?> submit(Runnable task) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void execute(Runnable command) {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGeneratorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGeneratorTest.java
new file mode 100644
index 00000000000..d77757374ce
--- /dev/null
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGeneratorTest.java
@@ -0,0 +1,98 @@
+package com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl;
+
+
+import com.google.common.collect.ImmutableSet;
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.AthenzInstanceProviderServiceTest.AutoGeneratedKeyProvider;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.ProviderUniqueId;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.SignedIdentityDocument;
+import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.node.Allocation;
+import com.yahoo.vespa.hosted.provision.node.Generation;
+import com.yahoo.vespa.hosted.provision.testutils.MockNodeFlavors;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Optional;
+
+import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.AthenzInstanceProviderServiceTest.getAthenzProviderConfig;
+import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.AthenzInstanceProviderServiceTest.getZoneConfig;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author valerijf
+ */
+public class IdentityDocumentGeneratorTest {
+ private static final Zone ZONE = new Zone(SystemName.cd, Environment.dev, RegionName.from("us-north-1"));
+
+ @Test
+ public void generates_valid_identity_document() throws Exception {
+ String hostname = "x.y.com";
+
+ ApplicationId appid = ApplicationId.from(
+ TenantName.from("tenant"), ApplicationName.from("application"), InstanceName.from("default"));
+ Allocation allocation = new Allocation(appid,
+ ClusterMembership.from("container/default/0/0", Version.fromString("1.2.3")),
+ Generation.inital(),
+ false);
+ Node n = Node.create("ostkid",
+ ImmutableSet.of("127.0.0.1"),
+ new HashSet<>(),
+ hostname,
+ Optional.empty(),
+ new MockNodeFlavors().getFlavorOrThrow("default"),
+ NodeType.tenant)
+ .with(allocation);
+
+ NodeRepository nodeRepository = mock(NodeRepository.class);
+ when(nodeRepository.getNode(eq(hostname))).thenReturn(Optional.of(n));
+ AutoGeneratedKeyProvider keyProvider = new AutoGeneratedKeyProvider();
+
+ String dnsSuffix = "vespa.dns.suffix";
+ AthenzProviderServiceConfig config = getAthenzProviderConfig("domain", "service", dnsSuffix, ZONE);
+ IdentityDocumentGenerator identityDocumentGenerator = new IdentityDocumentGenerator(
+ config,
+ getZoneConfig(config, ZONE),
+ nodeRepository,
+ ZONE,
+ keyProvider);
+ String rawSignedIdentityDocument = identityDocumentGenerator.generateSignedIdentityDocument(hostname);
+
+
+ SignedIdentityDocument signedIdentityDocument =
+ Utils.getMapper().readValue(rawSignedIdentityDocument, SignedIdentityDocument.class);
+
+ // Verify attributes
+ assertEquals(hostname, signedIdentityDocument.identityDocument.instanceHostname);
+
+ String environment = "dev";
+ String region = "us-north-1";
+ String expectedZoneDnsSuffix = environment + "-" + region + "." + dnsSuffix;
+ assertEquals(expectedZoneDnsSuffix, signedIdentityDocument.dnsSuffix);
+
+ ProviderUniqueId expectedProviderUniqueId =
+ new ProviderUniqueId("tenant", "application", environment, region, "default", "default", 0);
+ assertEquals(expectedProviderUniqueId, signedIdentityDocument.identityDocument.providerUniqueId);
+
+ // Validate signature
+ assertTrue("Message", InstanceValidator.isSignatureValid(keyProvider.getPublicKey(0),
+ signedIdentityDocument.rawIdentityDocument,
+ signedIdentityDocument.signature));
+ }
+} \ No newline at end of file
diff --git a/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidatorTest.java b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidatorTest.java
new file mode 100644
index 00000000000..c1fab319ebf
--- /dev/null
+++ b/athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/InstanceValidatorTest.java
@@ -0,0 +1,171 @@
+package com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.config.model.api.ApplicationInfo;
+import com.yahoo.config.model.api.HostInfo;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.config.model.api.SuperModel;
+import com.yahoo.config.model.api.SuperModelProvider;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.AthenzInstanceProviderServiceTest.AutoGeneratedKeyProvider;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.IdentityDocument;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.InstanceConfirmation;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.ProviderUniqueId;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.SignedIdentityDocument;
+import org.junit.Test;
+
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.time.Instant;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.InstanceValidator.SERVICE_PROPERTIES_DOMAIN_KEY;
+import static com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.InstanceValidator.SERVICE_PROPERTIES_SERVICE_KEY;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author valerijf
+ */
+public class InstanceValidatorTest {
+
+ private final ApplicationId applicationId = ApplicationId.from("tenant", "application", "instance");
+ private final String domain = "domain";
+ private final String service = "service";
+
+ @Test
+ public void valid_signature() throws Exception {
+ KeyProvider keyProvider = new AutoGeneratedKeyProvider();
+ InstanceValidator instanceValidator = new InstanceValidator(keyProvider, null);
+ InstanceConfirmation instanceConfirmation = createInstanceConfirmation(
+ keyProvider.getPrivateKey(0), applicationId, domain, service);
+
+ assertTrue(instanceValidator.isInstanceSignatureValid(instanceConfirmation));
+ }
+
+ @Test
+ public void invalid_signature() throws Exception {
+ KeyProvider keyProvider = new AutoGeneratedKeyProvider();
+ InstanceValidator instanceValidator = new InstanceValidator(keyProvider, null);
+
+ KeyProvider fakeKeyProvider = new AutoGeneratedKeyProvider();
+ InstanceConfirmation instanceConfirmation = createInstanceConfirmation(
+ fakeKeyProvider.getPrivateKey(0), applicationId, domain, service);
+
+ assertFalse(instanceValidator.isInstanceSignatureValid(instanceConfirmation));
+ }
+
+ @Test
+ public void application_does_not_exist() {
+ SuperModelProvider superModelProvider = mockSuperModelProvider();
+ InstanceValidator instanceValidator = new InstanceValidator(null, superModelProvider);
+
+ assertFalse(instanceValidator.isSameIdentityAsInServicesXml(applicationId, domain, service));
+ }
+
+ @Test
+ public void application_does_not_have_domain_set() {
+ SuperModelProvider superModelProvider = mockSuperModelProvider(
+ mockApplicationInfo(applicationId, 5, Collections.emptyList()));
+ InstanceValidator instanceValidator = new InstanceValidator(null, superModelProvider);
+
+ assertFalse(instanceValidator.isSameIdentityAsInServicesXml(applicationId, domain, service));
+ }
+
+ @Test
+ public void application_has_wrong_domain() {
+ ServiceInfo serviceInfo = new ServiceInfo("serviceName", "type", Collections.emptyList(),
+ Collections.singletonMap(SERVICE_PROPERTIES_DOMAIN_KEY, "not-domain"), "confId", "hostName");
+
+ SuperModelProvider superModelProvider = mockSuperModelProvider(
+ mockApplicationInfo(applicationId, 5, Collections.singletonList(serviceInfo)));
+ InstanceValidator instanceValidator = new InstanceValidator(null, superModelProvider);
+
+ assertFalse(instanceValidator.isSameIdentityAsInServicesXml(applicationId, domain, service));
+ }
+
+ @Test
+ public void application_has_same_domain_and_service() {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(SERVICE_PROPERTIES_DOMAIN_KEY, domain);
+ properties.put(SERVICE_PROPERTIES_SERVICE_KEY, service);
+
+ ServiceInfo serviceInfo = new ServiceInfo("serviceName", "type", Collections.emptyList(),
+ properties, "confId", "hostName");
+
+ SuperModelProvider superModelProvider = mockSuperModelProvider(
+ mockApplicationInfo(applicationId, 5, Collections.singletonList(serviceInfo)));
+ InstanceValidator instanceValidator = new InstanceValidator(null, superModelProvider);
+
+ assertTrue(instanceValidator.isSameIdentityAsInServicesXml(applicationId, domain, service));
+ }
+
+ private static InstanceConfirmation createInstanceConfirmation(PrivateKey privateKey, ApplicationId applicationId,
+ String domain, String service) {
+ IdentityDocument identityDocument = new IdentityDocument(
+ new ProviderUniqueId(applicationId.tenant().value(), applicationId.application().value(),
+ "environment", "region", applicationId.instance().value(), "cluster-id", 0),
+ "hostname",
+ "instance-hostname",
+ Instant.now());
+
+ try {
+ ObjectMapper mapper = Utils.getMapper();
+ String encodedIdentityDocument =
+ Base64.getEncoder().encodeToString(mapper.writeValueAsString(identityDocument).getBytes());
+ Signature sigGenerator = Signature.getInstance("SHA512withRSA");
+ sigGenerator.initSign(privateKey);
+ sigGenerator.update(encodedIdentityDocument.getBytes());
+
+ return new InstanceConfirmation(
+ "provider", domain, service,
+ new SignedIdentityDocument(encodedIdentityDocument,
+ Base64.getEncoder().encodeToString(sigGenerator.sign()),
+ 0,
+ identityDocument.providerUniqueId.asString(),
+ "dnssuffix",
+ "service",
+ "localhost/zts",
+ 1));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private SuperModelProvider mockSuperModelProvider(ApplicationInfo... appInfos) {
+ SuperModel superModel = new SuperModel(Stream.of(appInfos)
+ .collect(Collectors.groupingBy(
+ appInfo -> appInfo.getApplicationId().tenant(),
+ Collectors.toMap(
+ ApplicationInfo::getApplicationId,
+ Function.identity()
+ )
+ )));
+
+ SuperModelProvider superModelProvider = mock(SuperModelProvider.class);
+ when(superModelProvider.getSuperModel()).thenReturn(superModel);
+ return superModelProvider;
+ }
+
+ private ApplicationInfo mockApplicationInfo(ApplicationId appId, int numHosts, List<ServiceInfo> serviceInfo) {
+ List<HostInfo> hosts = IntStream.range(0, numHosts)
+ .mapToObj(i -> new HostInfo("host-" + i + "." + appId.toShortString() + ".yahoo.com", serviceInfo))
+ .collect(Collectors.toList());
+
+ Model model = mock(Model.class);
+ when(model.getHosts()).thenReturn(hosts);
+
+ return new ApplicationInfo(appId, 0, model);
+ }
+} \ No newline at end of file
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
index fb7ad137c22..ce9d0ed27f1 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
@@ -162,6 +162,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
addLegacyFilters(spec, cluster); // TODO: Remove for Vespa 7
// Athenz copper argos
+ // NOTE: Must be done after addNodes()
addIdentity(spec, cluster, context.getDeployState().getProperties().configServerSpecs());
//TODO: overview handler, see DomQrserverClusterBuilder
@@ -703,7 +704,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
Identity identity = new Identity(domain.trim(), service.trim(), cfgHostName);
cluster.addComponent(identity);
-
+ cluster.getContainers().forEach(container -> {
+ container.setProp("identity.domain", domain);
+ container.setProp("identity.service", service);
+ });
}
}