diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-05-29 16:35:01 +0200 |
---|---|---|
committer | Morten Tokle <mortent@oath.com> | 2018-06-11 14:15:11 +0200 |
commit | b267b3642c34720e8a6353d9afaf324f64ff2f71 (patch) | |
tree | a78bbf5b2deac0c1d8bffaea0ffaa5357f6e8b04 /vespa-athenz | |
parent | 673d1bba562da999ea3c92327a0504e0a9691e35 (diff) |
Use mutual TLS auth when retrieving identity document
Diffstat (limited to 'vespa-athenz')
4 files changed, 39 insertions, 116 deletions
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java index e8ef2d9f97e..eedb7a2cc07 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java @@ -1,13 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.athenz.identityprovider.client; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.yahoo.container.core.identity.IdentityConfig; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; +import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocumentClient; import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; -import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity; import com.yahoo.vespa.athenz.tls.KeyAlgorithm; import com.yahoo.vespa.athenz.tls.KeyUtils; import com.yahoo.vespa.athenz.tls.Pkcs10Csr; @@ -16,8 +14,6 @@ import com.yahoo.vespa.athenz.tls.SslContextBuilder; import javax.net.ssl.SSLContext; import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; import java.security.KeyPair; import java.security.PrivateKey; import java.security.cert.X509Certificate; @@ -25,31 +21,32 @@ import java.security.cert.X509Certificate; import static com.yahoo.vespa.athenz.tls.KeyStoreType.JKS; /** + * A service that provides method for initially registering the instance and refreshing it. + * * @author bjorncs */ class AthenzCredentialsService { - - private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); - private final IdentityConfig identityConfig; private final IdentityDocumentClient identityDocumentClient; private final ZtsClient ztsClient; private final File trustStoreJks; + private final String hostname; AthenzCredentialsService(IdentityConfig identityConfig, IdentityDocumentClient identityDocumentClient, ZtsClient ztsClient, - File trustStoreJks) { + File trustStoreJks, + String hostname) { this.identityConfig = identityConfig; this.identityDocumentClient = identityDocumentClient; this.ztsClient = ztsClient; this.trustStoreJks = trustStoreJks; + this.hostname = hostname; } AthenzCredentials registerInstance() { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); - String rawDocument = identityDocumentClient.getSignedIdentityDocument(); - SignedIdentityDocument document = parseSignedIdentityDocument(rawDocument); + SignedIdentityDocument document = identityDocumentClient.getTenantIdentityDocument(hostname); InstanceCsrGenerator instanceCsrGenerator = new InstanceCsrGenerator(document.dnsSuffix()); Pkcs10Csr csr = instanceCsrGenerator.generateCsr( new AthenzService(identityConfig.domain(), identityConfig.service()), @@ -60,7 +57,7 @@ class AthenzCredentialsService { new InstanceRegisterInformation(document.providerService().getFullName(), identityConfig.domain(), identityConfig.service(), - rawDocument, + EntityBindingsMapper.toAttestationData(document), Pkcs10CsrUtils.toPem(csr)); InstanceIdentity instanceIdentity = ztsClient.sendInstanceRegisterRequest(instanceRegisterInformation, document.ztsEndpoint()); @@ -103,11 +100,4 @@ class AthenzCredentialsService { .build(); } - private static SignedIdentityDocument parseSignedIdentityDocument(String rawDocument) { - try { - return EntityBindingsMapper.toSignedIdentityDocument(mapper.readValue(rawDocument, SignedIdentityDocumentEntity.class)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java index 813941ac9b2..32eb32ed366 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java @@ -16,12 +16,16 @@ import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import com.yahoo.vespa.athenz.identity.ServiceIdentityProviderListenerHelper; +import com.yahoo.vespa.athenz.identity.SiaIdentityProvider; +import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier; import com.yahoo.vespa.athenz.tls.KeyStoreType; import com.yahoo.vespa.athenz.tls.SslContextBuilder; +import com.yahoo.vespa.athenz.utils.SiaUtils; import com.yahoo.vespa.defaults.Defaults; import javax.net.ssl.SSLContext; import java.io.File; +import java.net.URI; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.time.Clock; @@ -32,6 +36,8 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import static java.util.Collections.singleton; + /** * @author mortent * @author bjorncs @@ -65,14 +71,16 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen this(config, metric, new AthenzCredentialsService(config, - new IdentityDocumentClient(config.loadBalancerAddress()), + createIdentityDocumentClient(config), new ZtsClient(), - getDefaultTrustStoreLocation()), + getDefaultTrustStoreLocation(), + Defaults.getDefaults().vespaHostname()), new ScheduledThreadPoolExecutor(1), Clock.systemUTC()); } // Test only + AthenzIdentityProviderImpl(IdentityConfig config, Metric metric, AthenzCredentialsService athenzCredentialsService, @@ -193,6 +201,14 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen } } + private static DefaultIdentityDocumentClient createIdentityDocumentClient(IdentityConfig config) { + return new DefaultIdentityDocumentClient( + URI.create(config.loadBalancerAddress()), + new SiaIdentityProvider( + new AthenzService(config.nodeIdentityName()), SiaUtils.DEFAULT_SIA_DIRECTORY, getDefaultTrustStoreLocation()), + new AthenzIdentityVerifier(singleton(new AthenzService(config.configserverIdentityName())))); + } + private static File getDefaultTrustStoreLocation() { return new File(Defaults.getDefaults().underVespaHome("share/ssl/certs/yahoo_certificate_bundle.jks")); } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentClient.java deleted file mode 100644 index dfc89431ce4..00000000000 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentClient.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.athenz.identityprovider.client; - -import com.yahoo.vespa.defaults.Defaults; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.util.EntityUtils; -import org.eclipse.jetty.http.HttpStatus; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.security.GeneralSecurityException; - -/** - * @author mortent - * @author bjorncs - */ -public class IdentityDocumentClient { - - private final URI identityDocumentApiUri; - - public IdentityDocumentClient(String loadBalancerName) { - this.identityDocumentApiUri = createIdentityDocumentApiUri(loadBalancerName); - } - - /** - * Get signed identity document from config server - */ - public String getSignedIdentityDocument() { - try (CloseableHttpClient httpClient = createHttpClient()) { - CloseableHttpResponse idDocResponse = httpClient.execute(new HttpGet(identityDocumentApiUri)); - String responseContent = EntityUtils.toString(idDocResponse.getEntity()); - if (HttpStatus.isSuccess(idDocResponse.getStatusLine().getStatusCode())) { - return responseContent; - } else { - // TODO make sure we have retried a few times (AND logged) before giving up - throw new RuntimeException( - "Failed to initialize Athenz instance provider: " + - idDocResponse.getStatusLine() + ": " + responseContent); - } - } catch (IOException e) { - throw new RuntimeException("Failed getting signed identity document", e); - } - } - - // TODO Use client side auth to establish trusted secure channel - // TODO Validate TLS certifcate of config server - private static CloseableHttpClient createHttpClient() { - try { - SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); - sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy()); - SSLConnectionSocketFactory sslSocketFactory = - new SSLConnectionSocketFactory(sslContextBuilder.build(), - NoopHostnameVerifier.INSTANCE); - return HttpClientBuilder.create().setSSLSocketFactory(sslSocketFactory).setUserAgent("identity-document-client").build(); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - } - - private static URI createIdentityDocumentApiUri(String loadBalancerName) { - try { - // TODO Figure out a proper way of determining the hostname matching what's registred in node-repository - return new URIBuilder() - .setScheme("https") - .setHost(loadBalancerName) - .setPort(4443) - .setPath("/athenz/v1/provider/identity-document/tenant/" + Defaults.getDefaults().vespaHostname()) - .build(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java index 7ad465a7d80..8b87c93f051 100644 --- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java +++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java @@ -1,17 +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.athenz.identityprovider.client; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.yahoo.container.core.identity.IdentityConfig; import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException; import com.yahoo.jdisc.Metric; import com.yahoo.test.ManualClock; import com.yahoo.vespa.athenz.api.AthenzService; -import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.IdentityType; +import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocumentClient; import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument; import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId; import com.yahoo.vespa.athenz.tls.KeyStoreBuilder; @@ -55,7 +52,13 @@ public class AthenzIdentityProviderImplTest { private static final IdentityConfig IDENTITY_CONFIG = new IdentityConfig(new IdentityConfig.Builder() - .service("tenantService").domain("tenantDomain").loadBalancerAddress("cfg").ztsUrl("https:localhost:4443/zts/v1").athenzDnsSuffix("vespa.cloud")); + .service("tenantService") + .domain("tenantDomain") + .nodeIdentityName("vespa.tenant") + .configserverIdentityName("vespa.configserver") + .loadBalancerAddress("cfg") + .ztsUrl("https:localhost:4443/zts/v1") + .athenzDnsSuffix("dev-us-north-1.vespa.cloud")); @Test(expected = AthenzIdentityProviderException.class) public void component_creation_fails_when_credentials_not_found() { @@ -73,7 +76,7 @@ public class AthenzIdentityProviderImplTest { ManualClock clock = new ManualClock(Instant.EPOCH); Metric metric = mock(Metric.class); - when(identityDocumentClient.getSignedIdentityDocument()).thenReturn(getIdentityDocument()); + when(identityDocumentClient.getTenantIdentityDocument(any())).thenReturn(getIdentityDocument()); when(ztsClient.sendInstanceRegisterRequest(any(), any())).then(new Answer<InstanceIdentity>() { @Override public InstanceIdentity answer(InvocationOnMock invocationOnMock) throws Throwable { @@ -87,7 +90,7 @@ public class AthenzIdentityProviderImplTest { .thenReturn(new InstanceIdentity(getCertificate(getExpirationSupplier(clock)), "TOKEN")); AthenzCredentialsService credentialService = - new AthenzCredentialsService(IDENTITY_CONFIG, identityDocumentClient, ztsClient, createDummyTrustStore()); + new AthenzCredentialsService(IDENTITY_CONFIG, identityDocumentClient, ztsClient, createDummyTrustStore(), "localhost"); AthenzIdentityProviderImpl identityProvider = new AthenzIdentityProviderImpl(IDENTITY_CONFIG, metric, credentialService, mock(ScheduledExecutorService.class), clock); @@ -132,9 +135,9 @@ public class AthenzIdentityProviderImplTest { return file; } - private static String getIdentityDocument() throws JsonProcessingException { + private static SignedIdentityDocument getIdentityDocument() { VespaUniqueInstanceId instanceId = new VespaUniqueInstanceId(0, "default", "default", "application", "tenant", "us-north-1", "dev", IdentityType.TENANT); - SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument( + return new SignedIdentityDocument( new IdentityDocument(instanceId, "localhost", "x.y.com", Instant.EPOCH, Collections.emptySet()), "dummysignature", 0, @@ -148,8 +151,5 @@ public class AthenzIdentityProviderImplTest { Instant.EPOCH, Collections.emptySet(), IdentityType.TENANT); - - return new ObjectMapper().registerModule(new JavaTimeModule()) - .writeValueAsString(EntityBindingsMapper.toSignedIdentityDocumentEntity(signedIdentityDocument)); } } |