aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Tokle <mortent@oath.com>2017-10-20 11:06:26 +0200
committerMorten Tokle <mortent@oath.com>2017-10-20 11:13:01 +0200
commitce5b8db39f64101d7ac9fae2847f3db614f14638 (patch)
tree215f43b5d6c5c5ed3a9fc0b6d9a16639a9f90877
parent36476464f1eea5627d8975ea18fd647080dc93f8 (diff)
Add Athenz identity provider and configuration
-rw-r--r--athenz-identity-provider-service/pom.xml22
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderService.java20
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java79
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/ProviderServiceServlet.java20
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/IdentityDocument.java19
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/ProviderUniqueId.java51
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/SignedIdentityDocument.java3
-rw-r--r--athenz-identity-provider-service/src/test/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/AthenzInstanceProviderServiceTest.java10
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/Container.java16
-rwxr-xr-xconfig-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java20
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/Identity.java22
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java28
-rw-r--r--config-model/src/main/resources/schema/containercluster.rnc7
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java4
-rw-r--r--config-model/src/test/schema-test-files/services.xml4
-rw-r--r--configdefinitions/src/vespa/configserver.def3
-rw-r--r--configserver/src/main/resources/configserver-app/services.xml2
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/identity/package-info.java7
-rw-r--r--container-core/src/main/resources/configdefinitions/identity.def4
-rw-r--r--container-disc/pom.xml11
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java121
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java47
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java47
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java38
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java47
-rw-r--r--standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala1
28 files changed, 599 insertions, 58 deletions
diff --git a/athenz-identity-provider-service/pom.xml b/athenz-identity-provider-service/pom.xml
index e3998b02ad0..074d4aa54f4 100644
--- a/athenz-identity-provider-service/pom.xml
+++ b/athenz-identity-provider-service/pom.xml
@@ -78,6 +78,23 @@
<artifactId>jetty-servlet</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>node-repository</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>config-provisioning</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>container-disc</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
<!-- TEST -->
<dependency>
@@ -103,6 +120,11 @@
<version>4.4.1</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
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 e3b31263421..e862717d1d1 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
@@ -8,16 +8,19 @@ import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.athenz.zts.InstanceRefreshRequest;
import com.yahoo.athenz.zts.ZTSClient;
import com.yahoo.component.AbstractComponent;
+import com.yahoo.config.provision.Zone;
import com.yahoo.jdisc.http.ssl.ReaderForPath;
import com.yahoo.jdisc.http.ssl.pem.PemKeyStore;
import com.yahoo.jdisc.http.ssl.pem.PemSslKeyStore;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.athenz.identityproviderservice.config.AthenzProviderServiceConfig;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.FileBackedKeyProvider;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.IdentityDocumentGenerator;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.InstanceValidator;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.KeyProvider;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.ProviderServiceServlet;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.StatusServlet;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletHandler;
@@ -45,16 +48,18 @@ public class AthenzInstanceProviderService extends AbstractComponent {
private final Server jetty;
@Inject
- public AthenzInstanceProviderService(AthenzProviderServiceConfig config) {
- this(config, new FileBackedKeyProvider(config.keyPathPrefix()), Executors.newSingleThreadScheduledExecutor());
+ public AthenzInstanceProviderService(AthenzProviderServiceConfig config, NodeRepository nodeRepository, Zone zone) {
+ this(config, new FileBackedKeyProvider(config.keyPathPrefix()), Executors.newSingleThreadScheduledExecutor(),
+ nodeRepository, zone);
}
AthenzInstanceProviderService(AthenzProviderServiceConfig config,
KeyProvider keyProvider,
- ScheduledExecutorService scheduler) {
+ ScheduledExecutorService scheduler, NodeRepository nodeRepository, Zone zone) {
this.scheduler = scheduler;
SslContextFactory sslContextFactory = createSslContextFactory();
- this.jetty = createJettyServer(config.port(), config.apiPath(), keyProvider, sslContextFactory);
+ this.jetty = createJettyServer(config.port(), config.apiPath(), keyProvider, sslContextFactory,
+ nodeRepository, zone);
AthenzCertificateUpdater reloader = new AthenzCertificateUpdater(
sslContextFactory, keyProvider, config);
scheduler.scheduleAtFixedRate(reloader, 0, 1, TimeUnit.DAYS);
@@ -66,7 +71,10 @@ public class AthenzInstanceProviderService extends AbstractComponent {
}
private static Server createJettyServer(int port, String apiPath,
- KeyProvider keyProvider, SslContextFactory sslContextFactory) {
+ KeyProvider keyProvider,
+ SslContextFactory sslContextFactory,
+ NodeRepository nodeRepository,
+ Zone zone) {
Server server = new Server();
ServerConnector connector = new ServerConnector(server, sslContextFactory);
connector.setPort(port);
@@ -74,7 +82,7 @@ public class AthenzInstanceProviderService extends AbstractComponent {
ServletHandler handler = new ServletHandler();
ProviderServiceServlet providerServiceServlet =
- new ProviderServiceServlet(new InstanceValidator(keyProvider));
+ new ProviderServiceServlet(new InstanceValidator(keyProvider), new IdentityDocumentGenerator(nodeRepository, zone, keyProvider));
handler.addServletWithMapping(new ServletHolder(providerServiceServlet), apiPath);
handler.addServletWithMapping(StatusServlet.class, "/status.html");
server.setHandler(handler);
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java
new file mode 100644
index 00000000000..833f1348338
--- /dev/null
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java
@@ -0,0 +1,79 @@
+package com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl;
+
+import com.yahoo.athenz.auth.util.Crypto;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.IdentityDocument;
+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 java.security.PrivateKey;
+import java.security.Signature;
+import java.time.Instant;
+import java.util.Base64;
+
+/**
+ * @author mortent
+ */
+public class IdentityDocumentGenerator {
+
+ private final NodeRepository nodeRepository;
+ private final Zone zone;
+ private final KeyProvider keyProvider;
+
+ public IdentityDocumentGenerator(NodeRepository nodeRepository, Zone zone, KeyProvider keyProvider) {
+ this.nodeRepository = nodeRepository;
+ this.zone = zone;
+ this.keyProvider = keyProvider;
+ }
+
+ public String generateSignedIdentityDocument(String hostname) {
+ Node node = nodeRepository.getNode(hostname).orElseThrow(() -> new RuntimeException("Unable to find node " + hostname));
+ try {
+ IdentityDocument identityDocument = generateIdDocument(node);
+ String identityDocumentString = Utils.getMapper().writeValueAsString(identityDocument);
+
+ String encodedIdentityDocument =
+ Base64.getEncoder().encodeToString(identityDocumentString.getBytes());
+ Signature sigGenerator = Signature.getInstance("SHA512withRSA");
+
+ // TODO: Get the correct version 0 ok for now
+ PrivateKey privateKey = Crypto.loadPrivateKey(keyProvider.getPrivateKey(0));
+ sigGenerator.initSign(privateKey);
+ sigGenerator.update(encodedIdentityDocument.getBytes());
+ String signature = Base64.getEncoder().encodeToString(sigGenerator.sign());
+
+ SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(
+ encodedIdentityDocument,
+ signature,
+ SignedIdentityDocument.DEFAULT_KEY_VERSION,
+ SignedIdentityDocument.DEFAILT_DOCUMENT_VERSION
+ );
+ return Utils.getMapper().writeValueAsString(signedIdentityDocument);
+ } catch (Exception e) {
+ throw new RuntimeException("Exception generating identity document: " + e.getMessage());
+ }
+ }
+
+ private IdentityDocument generateIdDocument(Node node) {
+ Allocation allocation = node.allocation().get();
+ ProviderUniqueId providerUniqueId = new ProviderUniqueId(
+ allocation.owner().tenant().value(),
+ allocation.owner().application().value(),
+ zone.environment().value(),
+ zone.region().value(),
+ allocation.owner().instance().value(),
+ allocation.membership().cluster().id().value(),
+ allocation.membership().index());
+
+ return new IdentityDocument(
+ providerUniqueId,
+ "localhost",
+ node.hostname(),
+ Instant.now());
+ }
+
+}
+
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/ProviderServiceServlet.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/ProviderServiceServlet.java
index 62b2d13cda0..7766dc9cc3c 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/ProviderServiceServlet.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/ProviderServiceServlet.java
@@ -12,6 +12,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.PrintWriter;
import java.io.Reader;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -26,9 +27,11 @@ public class ProviderServiceServlet extends HttpServlet {
private static final Logger log = Logger.getLogger(ProviderServiceServlet.class.getName());
private final InstanceValidator instanceValidator;
+ private final IdentityDocumentGenerator identityDocumentGenerator;
- public ProviderServiceServlet(InstanceValidator instanceValidator) {
+ public ProviderServiceServlet(InstanceValidator instanceValidator, IdentityDocumentGenerator identityDocumentGenerator) {
this.instanceValidator = instanceValidator;
+ this.identityDocumentGenerator = identityDocumentGenerator;
}
@Override
@@ -54,6 +57,21 @@ public class ProviderServiceServlet extends HttpServlet {
}
}
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ // TODO verify tls client cert
+ String hostname = req.getParameter("hostname");
+ try {
+ String signedIdentityDocument = identityDocumentGenerator.generateSignedIdentityDocument(hostname);
+ resp.setContentType("application/json");
+ PrintWriter writer = resp.getWriter();
+ writer.print(signedIdentityDocument);
+ writer.flush();
+ } catch (Exception e) {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND, String.format("Unable to generate identity doument [%s]", e.getMessage()));
+ }
+ }
+
private static String toString(Reader reader) throws IOException {
try (BufferedReader bufferedReader = new BufferedReader(reader)) {
return bufferedReader.lines().collect(Collectors.joining("\n"));
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/IdentityDocument.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/IdentityDocument.java
index 0b4fc38b00d..41ce5d969a7 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/IdentityDocument.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/IdentityDocument.java
@@ -11,10 +11,6 @@ import java.util.Objects;
*/
public class IdentityDocument {
- @JsonProperty("athens-domain")
- public final String athensDomain;
- @JsonProperty("athens-service")
- public final String athensService;
@JsonProperty("provider-unique-id")
public final ProviderUniqueId providerUniqueId;
@JsonProperty("configserver-hostname")
@@ -24,14 +20,11 @@ public class IdentityDocument {
@JsonProperty("created-at")
public final Instant createdAt;
- public IdentityDocument(@JsonProperty("athens-domain") String athensDomain,
- @JsonProperty("athens-service") String athensService,
+ public IdentityDocument(
@JsonProperty("provider-unique-id") ProviderUniqueId providerUniqueId,
@JsonProperty("configserver-hostname") String configServerHostname,
@JsonProperty("instance-hostname") String instanceHostname,
@JsonProperty("created-at") Instant createdAt) {
- this.athensDomain = athensDomain;
- this.athensService = athensService;
this.providerUniqueId = providerUniqueId;
this.configServerHostname = configServerHostname;
this.instanceHostname = instanceHostname;
@@ -41,9 +34,7 @@ public class IdentityDocument {
@Override
public String toString() {
return "IdentityDocument{" +
- "athensDomain='" + athensDomain + '\'' +
- ", athensService='" + athensService + '\'' +
- ", providerUniqueId=" + providerUniqueId +
+ "providerUniqueId=" + providerUniqueId +
", configServerHostname='" + configServerHostname + '\'' +
", instanceHostname='" + instanceHostname + '\'' +
", createdAt=" + createdAt +
@@ -55,9 +46,7 @@ public class IdentityDocument {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IdentityDocument that = (IdentityDocument) o;
- return Objects.equals(athensDomain, that.athensDomain) &&
- Objects.equals(athensService, that.athensService) &&
- Objects.equals(providerUniqueId, that.providerUniqueId) &&
+ return Objects.equals(providerUniqueId, that.providerUniqueId) &&
Objects.equals(configServerHostname, that.configServerHostname) &&
Objects.equals(instanceHostname, that.instanceHostname) &&
Objects.equals(createdAt, that.createdAt);
@@ -65,6 +54,6 @@ public class IdentityDocument {
@Override
public int hashCode() {
- return Objects.hash(athensDomain, athensService, providerUniqueId, configServerHostname, instanceHostname, createdAt);
+ return Objects.hash(providerUniqueId, configServerHostname, instanceHostname, createdAt);
}
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/ProviderUniqueId.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/ProviderUniqueId.java
index 4c09dd917a4..ec699120802 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/ProviderUniqueId.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/ProviderUniqueId.java
@@ -10,13 +10,20 @@ import java.util.Objects;
*/
public class ProviderUniqueId {
- @JsonProperty("tenant") public final String tenant;
- @JsonProperty("application") public final String application;
- @JsonProperty("environment") public final String environment;
- @JsonProperty("region") public final String region;
- @JsonProperty("instance") public final String instance;
- @JsonProperty("cluster-id") public final String clusterId;
- @JsonProperty("cluster-index") public final int clusterIndex;
+ @JsonProperty("tenant")
+ public final String tenant;
+ @JsonProperty("application")
+ public final String application;
+ @JsonProperty("environment")
+ public final String environment;
+ @JsonProperty("region")
+ public final String region;
+ @JsonProperty("instance")
+ public final String instance;
+ @JsonProperty("cluster-id")
+ public final String clusterId;
+ @JsonProperty("cluster-index")
+ public final int clusterIndex;
public ProviderUniqueId(@JsonProperty("tenant") String tenant,
@JsonProperty("application") String application,
@@ -37,14 +44,14 @@ public class ProviderUniqueId {
@Override
public String toString() {
return "ProviderUniqueId{" +
- "tenant='" + tenant + '\'' +
- ", application='" + application + '\'' +
- ", environment='" + environment + '\'' +
- ", region='" + region + '\'' +
- ", instance='" + instance + '\'' +
- ", clusterId='" + clusterId + '\'' +
- ", clusterIndex=" + clusterIndex +
- '}';
+ "tenant='" + tenant + '\'' +
+ ", application='" + application + '\'' +
+ ", environment='" + environment + '\'' +
+ ", region='" + region + '\'' +
+ ", instance='" + instance + '\'' +
+ ", clusterId='" + clusterId + '\'' +
+ ", clusterIndex=" + clusterIndex +
+ '}';
}
@Override
@@ -53,16 +60,16 @@ public class ProviderUniqueId {
if (o == null || getClass() != o.getClass()) return false;
ProviderUniqueId that = (ProviderUniqueId) o;
return clusterIndex == that.clusterIndex &&
- Objects.equals(tenant, that.tenant) &&
- Objects.equals(application, that.application) &&
- Objects.equals(environment, that.environment) &&
- Objects.equals(region, that.region) &&
- Objects.equals(instance, that.instance) &&
- Objects.equals(clusterId, that.clusterId);
+ Objects.equals(tenant, that.tenant) &&
+ Objects.equals(application, that.application) &&
+ Objects.equals(environment, that.environment) &&
+ Objects.equals(region, that.region) &&
+ Objects.equals(instance, that.instance) &&
+ Objects.equals(clusterId, that.clusterId);
}
@Override
public int hashCode() {
return Objects.hash(tenant, application, environment, region, instance, clusterId, clusterIndex);
}
-}
+} \ No newline at end of file
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/SignedIdentityDocument.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/SignedIdentityDocument.java
index df1bfe772e8..09d57582fa3 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/SignedIdentityDocument.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/model/SignedIdentityDocument.java
@@ -16,6 +16,9 @@ import java.util.Objects;
*/
public class SignedIdentityDocument {
+ public static final int DEFAULT_KEY_VERSION = 0;
+ public static final int DEFAILT_DOCUMENT_VERSION = 1;
+
@JsonProperty("identity-document")public final String rawIdentityDocument;
@JsonIgnore public final IdentityDocument identityDocument;
@JsonProperty("signature") public final String signature;
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 5a700c32f7e..900bdec6855 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
@@ -4,6 +4,9 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.athenz.auth.util.Crypto;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.athenz.identityproviderservice.config.AthenzProviderServiceConfig;
import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.KeyProvider;
@@ -12,6 +15,7 @@ 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.NodeRepository;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
@@ -44,6 +48,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
/**
* @author bjorncs
@@ -74,7 +79,9 @@ public class AthenzInstanceProviderServiceTest {
.apiPath("/"));
ScheduledExecutorServiceMock executor = new ScheduledExecutorServiceMock();
- AthenzInstanceProviderService athenzInstanceProviderService = new AthenzInstanceProviderService(config, keyProvider, executor);
+ NodeRepository nodeRepository = mock(NodeRepository.class);
+ Zone zone = new Zone(Environment.dev, RegionName.from("us-north-1"));
+ AthenzInstanceProviderService athenzInstanceProviderService = new AthenzInstanceProviderService(config, keyProvider, executor, nodeRepository, zone);
try (CloseableHttpClient client = createHttpClient(domain, service)) {
Runnable certificateRefreshCommand = executor.getCommand().orElseThrow(() -> new AssertionError("Command not present"));
@@ -122,7 +129,6 @@ public class AthenzInstanceProviderServiceTest {
private static HttpEntity createInstanceConfirmation(PrivateKey privateKey) {
IdentityDocument identityDocument = new IdentityDocument(
- "domain", "service",
new ProviderUniqueId(
"tenant", "application", "environment", "region", "instance", "cluster-id", 0),
"hostname", "instance-hostname", Instant.now());
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
index 8e2c6b8532d..ddd998c8816 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
@@ -7,6 +7,7 @@ import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.QrConfig;
import com.yahoo.container.core.ContainerHttpConfig;
+import com.yahoo.container.core.identity.IdentityConfig;
import com.yahoo.container.jdisc.ContainerMbusConfig;
import com.yahoo.container.jdisc.JdiscBindingsConfig;
import com.yahoo.search.config.QrStartConfig;
@@ -48,7 +49,8 @@ public class Container extends AbstractService implements
ComponentsConfig.Producer,
JdiscBindingsConfig.Producer,
ContainerHttpConfig.Producer,
- ContainerMbusConfig.Producer {
+ ContainerMbusConfig.Producer,
+ IdentityConfig.Producer {
public static final int BASEPORT = Defaults.getDefaults().vespaWebServicePort();
public static final String SINGLENODE_CONTAINER_SERVICESPEC = "default_singlenode_container";
@@ -394,6 +396,18 @@ public class Container extends AbstractService implements
this.httpServerEnabled = httpServerEnabled;
}
+ @Override
+ public void getConfig(IdentityConfig.Builder builder) {
+ if(parent instanceof ContainerCluster) {
+ ContainerCluster containerCluster = (ContainerCluster) parent;
+ Optional<Identity> identity = containerCluster.getIdentity();
+ identity.ifPresent(id -> {
+ builder.serviceName(id.getService());
+ builder.domain(id.getDomain());
+ });
+ }
+ }
+
public static final class PortOverride {
public final ComponentSpecification serverId;
public final int port;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
index 77029360593..2437562c56a 100755
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
@@ -7,11 +7,11 @@ import com.yahoo.cloud.config.RoutingProviderConfig;
import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.FileReference;
+import com.yahoo.config.application.api.ApplicationMetaData;
+import com.yahoo.config.application.api.ComponentInfo;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.docproc.DocprocConfig;
import com.yahoo.config.docproc.SchemamappingConfig;
-import com.yahoo.config.application.api.ApplicationMetaData;
-import com.yahoo.config.application.api.ComponentInfo;
import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.config.model.producer.AbstractConfigProducerRoot;
@@ -65,8 +65,8 @@ import com.yahoo.vespa.model.container.component.ConfigProducerGroup;
import com.yahoo.vespa.model.container.component.DiscBindingsConfigGenerator;
import com.yahoo.vespa.model.container.component.FileStatusHandlerComponent;
import com.yahoo.vespa.model.container.component.Handler;
-import com.yahoo.vespa.model.container.component.SimpleComponent;
import com.yahoo.vespa.model.container.component.Servlet;
+import com.yahoo.vespa.model.container.component.SimpleComponent;
import com.yahoo.vespa.model.container.component.StatisticsComponent;
import com.yahoo.vespa.model.container.component.chain.ProcessingHandler;
import com.yahoo.vespa.model.container.docproc.ContainerDocproc;
@@ -82,7 +82,6 @@ import com.yahoo.vespa.model.content.Content;
import com.yahoo.vespa.model.search.AbstractSearchCluster;
import com.yahoo.vespa.model.utils.FileSender;
import com.yahoo.vespaclient.config.FeederConfig;
-
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -187,6 +186,8 @@ public final class ContainerCluster
private Optional<String> hostClusterId = Optional.empty();
private Optional<Integer> memoryPercentage = Optional.empty();
+ private Identity identity;
+
private static class AcceptAllVerifier implements ContainerClusterVerifier {
@Override
public boolean acceptComponent(Component component) { return true; }
@@ -239,6 +240,9 @@ public final class ContainerCluster
public void setZone(Zone zone) {
this.zone = zone;
}
+ public Zone getZone() {
+ return zone;
+ }
public void addMetricStateHandler() {
Handler<AbstractConfigProducer<?>> stateHandler = new Handler<>(
@@ -837,6 +841,14 @@ public final class ContainerCluster
*/
public Optional<Integer> getMemoryPercentage() { return memoryPercentage; }
+ public Optional<Identity> getIdentity() {
+ return Optional.ofNullable(identity);
+ }
+
+ public void setIdentity(Identity identity) {
+ this.identity = identity;
+ }
+
@Override
public String toString() {
return "container cluster '" + getName() + "'";
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Identity.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Identity.java
new file mode 100644
index 00000000000..e872cb7a73b
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Identity.java
@@ -0,0 +1,22 @@
+package com.yahoo.vespa.model.container;
+
+/**
+ * @author mortent
+ */
+public class Identity {
+ private final String domain;
+ private final String service;
+
+ public Identity(String domain, String service) {
+ this.domain = domain;
+ this.service = service;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public String getService() {
+ return service;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
index c3361426d64..f7caa0c734c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
@@ -141,6 +141,9 @@ public class ConfigserverCluster extends AbstractConfigProducer
if (options.dockerVespaBaseImage().isPresent()) {
builder.dockerVespaBaseImage(options.dockerVespaBaseImage().get());
}
+ if (options.serviceProviderEndpoint().isPresent()) {
+ builder.serviceProviderEndpoint(options.serviceProviderEndpoint().get());
+ }
}
private String[] getConfigModelPluginDirs() {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java
index 8acab12414d..ae29a912c11 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/option/CloudConfigOptions.java
@@ -43,4 +43,5 @@ public interface CloudConfigOptions {
Optional<Integer> numParallelTenantLoaders();
Optional<String> dockerRegistry();
Optional<String> dockerVespaBaseImage();
+ Optional<String> serviceProviderEndpoint();
}
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 49229fcc87a..0d221b57748 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
@@ -4,16 +4,16 @@ package com.yahoo.vespa.model.container.xml;
import com.google.common.collect.ImmutableList;
import com.yahoo.component.Version;
import com.yahoo.config.application.Xml;
-import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.application.provider.IncludeDirs;
import com.yahoo.config.model.builder.xml.ConfigModelBuilder;
import com.yahoo.config.model.builder.xml.ConfigModelId;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.config.provision.Capacity;
-import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.NodeType;
import com.yahoo.container.jdisc.config.MetricDefaultsConfig;
@@ -22,14 +22,15 @@ import com.yahoo.text.XML;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.model.AbstractService;
import com.yahoo.vespa.model.HostResource;
+import com.yahoo.vespa.model.container.Identity;
import com.yahoo.vespa.model.builder.xml.dom.DomClientProviderBuilder;
import com.yahoo.vespa.model.builder.xml.dom.DomComponentBuilder;
import com.yahoo.vespa.model.builder.xml.dom.DomFilterBuilder;
import com.yahoo.vespa.model.builder.xml.dom.DomHandlerBuilder;
import com.yahoo.vespa.model.builder.xml.dom.ModelElement;
import com.yahoo.vespa.model.builder.xml.dom.NodesSpecification;
-import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
import com.yahoo.vespa.model.builder.xml.dom.ServletBuilder;
+import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
import com.yahoo.vespa.model.builder.xml.dom.chains.docproc.DomDocprocChainsBuilder;
import com.yahoo.vespa.model.builder.xml.dom.chains.processing.DomProcessingBuilder;
import com.yahoo.vespa.model.builder.xml.dom.chains.search.DomSearchChainsBuilder;
@@ -52,12 +53,15 @@ import com.yahoo.vespa.model.container.search.QueryProfiles;
import com.yahoo.vespa.model.container.search.SemanticRules;
import com.yahoo.vespa.model.container.search.searchchain.SearchChains;
import com.yahoo.vespa.model.container.xml.document.DocumentFactoryBuilder;
-
import com.yahoo.vespa.model.content.StorageGroup;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -108,7 +112,6 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
ContainerCluster cluster = createContainerCluster(spec, modelContext);
addClusterContent(cluster, spec, modelContext);
addBundlesForPlatformComponents(cluster);
-
model.setCluster(cluster);
}
@@ -157,6 +160,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
addServerProviders(spec, cluster);
addLegacyFilters(spec, cluster); // TODO: Remove for Vespa 7
+ // Athenz copper argos
+ addIdentity(spec, cluster);
+
//TODO: overview handler, see DomQrserverClusterBuilder
}
@@ -683,6 +689,16 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
}
}
+ private void addIdentity(Element element, ContainerCluster cluster) {
+ Element identityElement = XML.getChild(element, "identity");
+ if(identityElement != null) {
+ String domain = XML.getValue(XML.getChild(identityElement, "domain"));
+ String service = XML.getValue(XML.getChild(identityElement, "service"));
+ Identity identity = new Identity(domain.trim(), service.trim());
+ cluster.setIdentity(identity);
+ }
+ }
+
/**
* Disallow renderers named "DefaultRenderer" or "JsonRenderer"
*/
diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc
index 85219bdb46a..af9b89b8553 100644
--- a/config-model/src/main/resources/schema/containercluster.rnc
+++ b/config-model/src/main/resources/schema/containercluster.rnc
@@ -7,7 +7,8 @@ ContainerCluster = element container | jdisc {
ContainerServices &
DocumentBinding* &
Aliases? &
- NodesOfContainerCluster?
+ NodesOfContainerCluster? &
+ Identity?
}
ContainerServices =
@@ -225,3 +226,7 @@ DocumentBinding = element document {
attribute bundle { xsd:NCName }
}
+Identity = element identity {
+ element domain { xsd:NCName } &
+ element service { xsd:NCName }
+}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java
index e2ed57251d9..4c09fd8d713 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java
@@ -62,6 +62,10 @@ public class DedicatedAdminV4Test {
" <metric name='nginx.upstreams.down.last' output-name='nginx.upstreams.down'/>" +
" </consumer>" +
" </metric-consumers>" +
+ " <identity>" +
+ " <domain>mydomain</domain>" +
+ " <service>myservice</service>" +
+ " </identity>" +
" </admin>" +
"</services>";
diff --git a/config-model/src/test/schema-test-files/services.xml b/config-model/src/test/schema-test-files/services.xml
index bf2a924e62d..380ce7f5a3d 100644
--- a/config-model/src/test/schema-test-files/services.xml
+++ b/config-model/src/test/schema-test-files/services.xml
@@ -36,6 +36,10 @@
</config>
<jdisc id='qrsCluster_1' version='1.0'>
+ <identity>
+ <domain>mydomain</domain>
+ <service>myservice</service>
+ </identity>
<rest-api path="jersey1">
<components bundle="my-bundle" />
<components bundle="other-bundle">
diff --git a/configdefinitions/src/vespa/configserver.def b/configdefinitions/src/vespa/configserver.def
index 28081f8323c..346d4b9c862 100644
--- a/configdefinitions/src/vespa/configserver.def
+++ b/configdefinitions/src/vespa/configserver.def
@@ -40,3 +40,6 @@ useVespaVersionInRequest bool default=false
# Docker config
dockerRegistry string default=""
dockerVespaBaseImage string default=""
+
+# Athenz config
+serviceProviderEndpoint string default="" \ No newline at end of file
diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml
index 7aec27a96f5..db95cb17530 100644
--- a/configserver/src/main/resources/configserver-app/services.xml
+++ b/configserver/src/main/resources/configserver-app/services.xml
@@ -134,5 +134,7 @@
<server port="19071" id="configserver" />
<preprocess:include file='hosted-vespa/http-server.xml' required='false' />
</http>
+
+ <preprocess:include file='athenz-identity-provider.xml' required='false' />
</jdisc>
</services>
diff --git a/container-core/src/main/java/com/yahoo/container/core/identity/package-info.java b/container-core/src/main/java/com/yahoo/container/core/identity/package-info.java
new file mode 100644
index 00000000000..f0d45f003c2
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/identity/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * @author mortent
+ */
+@ExportPackage
+package com.yahoo.container.core.identity;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/container-core/src/main/resources/configdefinitions/identity.def b/container-core/src/main/resources/configdefinitions/identity.def
new file mode 100644
index 00000000000..0f5080632b2
--- /dev/null
+++ b/container-core/src/main/resources/configdefinitions/identity.def
@@ -0,0 +1,4 @@
+namespace=container.core.identity
+
+domain string
+serviceName string
diff --git a/container-disc/pom.xml b/container-disc/pom.xml
index 44afe74459d..5409d9b1a40 100644
--- a/container-disc/pom.xml
+++ b/container-disc/pom.xml
@@ -130,6 +130,17 @@
<scope>provided</scope>
</dependency>
<!-- end WARNING -->
+ <!-- Athenz -->
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.athenz</groupId>
+ <artifactId>athenz-auth-core</artifactId>
+ <version>${athenz.version}</version>
+ </dependency>
</dependencies>
<properties>
<!-- These versions must be the ones used by the current jersey version (see vespa_jersey2/target/dependency).
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java
new file mode 100644
index 00000000000..f67afdfc458
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java
@@ -0,0 +1,121 @@
+package com.yahoo.container.jdisc.athenz;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.yahoo.athenz.auth.util.Crypto;
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.container.core.identity.IdentityConfig;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.operator.OperatorCreationException;
+
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @author mortent
+ */
+public final class AthenzIdentityProvider extends AbstractComponent {
+
+ private InstanceIdentity instanceIdentity;
+
+ private final String athenzUrl;
+
+ private final String dnsSuffix;
+ private final String providerUniqueId;
+ private final String providerServiceName;
+
+ @Inject
+ public AthenzIdentityProvider(IdentityConfig config, ConfigserverConfig configserverConfig) throws IOException {
+ this(config, new ServiceProviderApi(configserverConfig.serviceProviderEndpoint()), new AthenzService());
+ }
+
+ // Test only
+ public AthenzIdentityProvider(IdentityConfig config, ServiceProviderApi serviceProviderApi, AthenzService athenzService) throws IOException {
+ KeyPair keyPair = createKeyPair();
+ String signedIdentityDocument = serviceProviderApi.getSignedIdentityDocument();
+ this.athenzUrl = getZtsEndpoint(signedIdentityDocument);
+ dnsSuffix = getDnsSuffix(signedIdentityDocument);
+ providerUniqueId = getProviderUniqueId(signedIdentityDocument);
+ providerServiceName = getProviderServiceName(signedIdentityDocument);
+
+ InstanceRegisterInformation instanceRegisterInformation = new InstanceRegisterInformation(
+ providerServiceName,
+ config.domain(),
+ config.serviceName(),
+ signedIdentityDocument,
+ createCSR(keyPair, config),
+ true
+ );
+ instanceIdentity = athenzService.sendInstanceRegisterRequest(instanceRegisterInformation, athenzUrl);
+ }
+
+ private String getProviderUniqueId(String signedIdentityDocument) throws IOException {
+ return getJsonNode(signedIdentityDocument, "provider-unique-id");
+ }
+
+ private String getDnsSuffix(String signedIdentityDocument) throws IOException {
+ return getJsonNode(signedIdentityDocument, "dns-suffix");
+ }
+
+ private String getProviderServiceName(String signedIdentityDocument) throws IOException {
+ return getJsonNode(signedIdentityDocument, "provider-service");
+ }
+
+ private String getZtsEndpoint(String signedIdentityDocument) throws IOException {
+ return getJsonNode(signedIdentityDocument, "zts-endpoint");
+ }
+
+ private String getJsonNode(String jsonString, String path) throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(jsonString);
+ return jsonNode.get(path).asText();
+ }
+
+ private KeyPair createKeyPair() {
+ try {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ return kpg.generateKeyPair();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String createCSR(KeyPair keyPair, IdentityConfig identityConfig) throws IOException {
+
+ try {
+ // Add SAN dnsname <service>.<domain-with-dashes>.<provider-dnsname-suffix>
+ // and SAN dnsname <provider-unique-instance-id>.instanceid.athenz.<provider-dnsname-suffix>
+ GeneralName[] sanDnsNames = new GeneralName[]{
+ new GeneralName(GeneralName.dNSName, String.format("%s.%s.%s",
+ identityConfig.serviceName(),
+ identityConfig.domain().replace(".", "-"),
+ dnsSuffix)),
+ new GeneralName(GeneralName.dNSName, String.format("%s.instanceid.athenz.%s",
+ providerUniqueId,
+ dnsSuffix))
+ };
+
+ return Crypto.generateX509CSR(keyPair.getPrivate(),
+ keyPair.getPublic(),
+ String.format("CN=%s.%s", identityConfig.domain(), identityConfig.serviceName()),
+ sanDnsNames);
+ } catch (OperatorCreationException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String getNToken() {
+ return instanceIdentity.getServiceToken();
+ }
+
+ public String getX509Cert() {
+ return instanceIdentity.getX509Certificate();
+ }
+}
+
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java
new file mode 100644
index 00000000000..2acd630aa7d
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzService.java
@@ -0,0 +1,47 @@
+package com.yahoo.container.jdisc.athenz;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.eclipse.jetty.http.HttpStatus;
+
+import java.io.IOException;
+
+/**
+ * @author mortent
+ */
+public class AthenzService {
+
+ /**
+ * Send instance register request to ZTS, get InstanceIdentity
+ *
+ * @param instanceRegisterInformation
+ */
+ InstanceIdentity sendInstanceRegisterRequest(InstanceRegisterInformation instanceRegisterInformation, String athenzUrl) {
+ try(CloseableHttpClient client = HttpClientBuilder.create().build()) {
+ ObjectMapper objectMapper = new ObjectMapper();
+ System.out.println(objectMapper.writeValueAsString(instanceRegisterInformation));
+ HttpUriRequest postRequest = RequestBuilder.post()
+ .setUri(athenzUrl + "/instance")
+ .setEntity(new StringEntity(objectMapper.writeValueAsString(instanceRegisterInformation), ContentType.APPLICATION_JSON))
+ .build();
+ CloseableHttpResponse response = client.execute(postRequest);
+ if(HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) {
+ return objectMapper.readValue(response.getEntity().getContent(), InstanceIdentity.class);
+ } else {
+ String s = EntityUtils.toString(response.getEntity());
+ System.out.println("s = " + s);
+ throw new RuntimeException(response.toString());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java
new file mode 100644
index 00000000000..e2b65685cdb
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceIdentity.java
@@ -0,0 +1,47 @@
+package com.yahoo.container.jdisc.athenz;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+
+/**
+ * Used for deserializing response from ZTS
+ *
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+class InstanceIdentity {
+ @JsonProperty("attributes")
+ Map<String, String> attributes;
+ @JsonProperty("provider")
+ private String provider;
+ @JsonProperty("name")
+ private String name;
+ @JsonProperty("instanceId")
+ private String instanceId;
+ @JsonProperty("x509Certificate")
+ private String x509Certificate;
+ @JsonProperty("x509CertificateSigner")
+ private String x509CertificateSigner;
+ @JsonProperty("sshCertificate")
+ private String sshCertificate;
+ @JsonProperty("sshCertificateSigner")
+ private String sshCertificateSigner;
+ @JsonProperty("serviceToken")
+ private String serviceToken;
+
+ public String getX509Certificate() {
+ return x509Certificate;
+ }
+
+ public String getServiceToken() {
+ return serviceToken;
+ }
+
+ public void setServiceToken(String serviceToken) {
+ this.serviceToken = serviceToken;
+ }
+}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java
new file mode 100644
index 00000000000..2fa3c598c58
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/InstanceRegisterInformation.java
@@ -0,0 +1,38 @@
+package com.yahoo.container.jdisc.athenz;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Used for serializing request to ZTS
+ *
+ * @author mortent
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+class InstanceRegisterInformation {
+ @JsonProperty("provider")
+ private final String provider;
+ @JsonProperty("domain")
+ private final String domain;
+ @JsonProperty("service")
+ private final String service;
+ @JsonProperty("attestationData")
+ private final String attestationData;
+ @JsonProperty("ssh")
+ private final String ssh = null; // Not needed
+ @JsonProperty("csr")
+ private final String csr;
+ @JsonProperty("token")
+ private final boolean token;
+
+ public InstanceRegisterInformation(String provider, String domain, String service, String attestationData, String csr, boolean token) {
+ this.provider = provider;
+ this.domain = domain;
+ this.service = service;
+ this.attestationData = attestationData;
+ this.csr = csr;
+ this.token = token;
+ }
+}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java
new file mode 100644
index 00000000000..0b417a4d440
--- /dev/null
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/ServiceProviderApi.java
@@ -0,0 +1,47 @@
+package com.yahoo.container.jdisc.athenz;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.eclipse.jetty.http.HttpStatus;
+
+import java.io.IOException;
+
+/**
+ * @author mortent
+ */
+public class ServiceProviderApi {
+
+ private final String providerEndpoint;
+
+ public ServiceProviderApi(String providerEndpoint) {
+ this.providerEndpoint = providerEndpoint;
+ }
+
+
+ /**
+ * Get signed identity document from config server
+ *
+ * @return
+ */
+ String getSignedIdentityDocument() {
+
+ // TODO Use client side auth to establish trusted secure channel
+ try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
+
+ CloseableHttpResponse idDocResponse = httpClient.execute(RequestBuilder.get().setUri(providerEndpoint + "/identity-document").build());
+ if (HttpStatus.isSuccess(idDocResponse.getStatusLine().getStatusCode())) {
+ return EntityUtils.toString(idDocResponse.getEntity());
+ } else {
+ // make sure we have retried a few times (AND logged) before giving up
+ throw new RuntimeException("Failed to initialize Athenz instance provider");
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala b/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala
index 0a888dc1208..009de6fd541 100644
--- a/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala
+++ b/standalone-container/src/main/scala/com/yahoo/container/standalone/CloudConfigYinstVariables.scala
@@ -38,6 +38,7 @@ class CloudConfigYinstVariables extends CloudConfigOptions {
override val numParallelTenantLoaders = optionalYinstVar[java.lang.Integer]("num_parallel_tenant_loaders")
override val dockerRegistry = optionalYinstVar[java.lang.String]("docker_registry")
override val dockerVespaBaseImage = optionalYinstVar[java.lang.String]("docker_vespa_base_image")
+ override val serviceProviderEndpoint = optionalYinstVar[java.lang.String]("service_provider_endpoint")
}
object CloudConfigYinstVariables {