diff options
author | Morten Tokle <mortent@oath.com> | 2017-11-02 11:34:41 +0100 |
---|---|---|
committer | Morten Tokle <mortent@oath.com> | 2017-11-02 14:01:16 +0100 |
commit | fd194925b4b0f9fabea24d783d2114e57aa42069 (patch) | |
tree | da7ea7dca533f48609dff04ca16b53b707cca6f6 /athenz-identity-provider-service | |
parent | 451f936c4d19ae8dc08332682d06c6caa3a39000 (diff) |
Read key from secretstore, refactor config
Diffstat (limited to 'athenz-identity-provider-service')
6 files changed, 126 insertions, 39 deletions
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 e66130332ac..74b697fb004 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 @@ -5,16 +5,17 @@ import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.Zone; +import com.yahoo.jdisc.http.SecretStore; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.AthenzCertificateClient; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.CertificateClient; -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.IdentityDocumentServlet; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.InstanceConfirmationServlet; 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.SecretStoreKeyProvider; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.StatusServlet; import com.yahoo.vespa.hosted.provision.NodeRepository; import org.eclipse.jetty.server.Server; @@ -47,9 +48,9 @@ public class AthenzInstanceProviderService extends AbstractComponent { private final Server jetty; @Inject - public AthenzInstanceProviderService(AthenzProviderServiceConfig config, NodeRepository nodeRepository, Zone zone) { - this(config, new FileBackedKeyProvider(config.keyPathPrefix()), Executors.newSingleThreadScheduledExecutor(), - nodeRepository, zone, new AthenzCertificateClient(config)); + public AthenzInstanceProviderService(AthenzProviderServiceConfig config, 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))); } AthenzInstanceProviderService(AthenzProviderServiceConfig config, @@ -65,7 +66,7 @@ public class AthenzInstanceProviderService extends AbstractComponent { this.jetty = createJettyServer( config, keyProvider, sslContextFactory, nodeRepository, zone); AthenzCertificateUpdater reloader = - new AthenzCertificateUpdater(certificateClient, sslContextFactory, keyProvider, config); + new AthenzCertificateUpdater(certificateClient, sslContextFactory, keyProvider, config, getZoneConfig(config, zone)); // TODO Configurable update frequency scheduler.scheduleAtFixedRate(reloader, 0, 1, TimeUnit.DAYS); try { @@ -95,7 +96,7 @@ public class AthenzInstanceProviderService extends AbstractComponent { handler.addServletWithMapping(new ServletHolder(instanceConfirmationServlet), config.apiPath() + "/instance"); IdentityDocumentServlet identityDocumentServlet = - new IdentityDocumentServlet(new IdentityDocumentGenerator(config, nodeRepository, zone, keyProvider)); + new IdentityDocumentServlet(new IdentityDocumentGenerator(config, getZoneConfig(config, zone), nodeRepository, zone, keyProvider)); handler.addServletWithMapping(new ServletHolder(identityDocumentServlet), config.apiPath() + "/identity-document"); handler.addServletWithMapping(StatusServlet.class, "/status.html"); @@ -104,6 +105,11 @@ public class AthenzInstanceProviderService extends AbstractComponent { } + private static AthenzProviderServiceConfig.Zones getZoneConfig(AthenzProviderServiceConfig config, Zone zone) { + String key = zone.environment().value() + "." + zone.region().value(); + return config.zones(key); + } + private static SslContextFactory createSslContextFactory() { try { SslContextFactory sslContextFactory = new SslContextFactory(); @@ -126,22 +132,25 @@ public class AthenzInstanceProviderService extends AbstractComponent { private final SslContextFactory sslContextFactory; private final KeyProvider keyProvider; private final AthenzProviderServiceConfig config; + private final AthenzProviderServiceConfig.Zones zoneConfig; private AthenzCertificateUpdater(CertificateClient certificateClient, SslContextFactory sslContextFactory, KeyProvider keyProvider, - AthenzProviderServiceConfig config) { + AthenzProviderServiceConfig config, + AthenzProviderServiceConfig.Zones zoneConfig) { this.certificateClient = certificateClient; this.sslContextFactory = sslContextFactory; this.keyProvider = keyProvider; this.config = config; + this.zoneConfig = zoneConfig; } @Override public void run() { try { log.log(LogLevel.INFO, "Updating Athenz certificate through ZTS"); - PrivateKey privateKey = keyProvider.getPrivateKey(config.keyVersion()); + PrivateKey privateKey = keyProvider.getPrivateKey(zoneConfig.secretVersion()); X509Certificate certificate = certificateClient.updateCertificate(privateKey, EXPIRY_TIME); String dummyPassword = "athenz"; diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java index 031133ade19..dab1581f580 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/AthenzCertificateClient.java @@ -21,24 +21,26 @@ public class AthenzCertificateClient implements CertificateClient { private final AthenzProviderServiceConfig config; private final AthenzPrincipalAuthority authority; + private final AthenzProviderServiceConfig.Zones zoneConfig; - public AthenzCertificateClient(AthenzProviderServiceConfig config) { + public AthenzCertificateClient(AthenzProviderServiceConfig config, AthenzProviderServiceConfig.Zones zoneConfig) { this.config = config; this.authority = new AthenzPrincipalAuthority(config.athenzPrincipalHeaderName()); + this.zoneConfig = zoneConfig; } @Override public X509Certificate updateCertificate(PrivateKey privateKey, TemporalAmount expiryTime) { SimpleServiceIdentityProvider identityProvider = new SimpleServiceIdentityProvider( - authority, config.domain(), config.serviceName(), - privateKey, Integer.toString(config.keyVersion()), TimeUnit.MINUTES.toSeconds(10)); + authority, zoneConfig.domain(), zoneConfig.serviceName(), + privateKey, Integer.toString(zoneConfig.secretVersion()), TimeUnit.MINUTES.toSeconds(10)); ZTSClient ztsClient = new ZTSClient( - config.ztsUrl(), config.domain(), config.serviceName(), identityProvider); + config.ztsUrl(), zoneConfig.domain(), zoneConfig.serviceName(), identityProvider); InstanceRefreshRequest req = ZTSClient.generateInstanceRefreshRequest( - config.domain(), config.serviceName(), privateKey, + zoneConfig.domain(), zoneConfig.serviceName(), privateKey, config.certDnsSuffix(), (int)expiryTime.get(ChronoUnit.SECONDS)); - String pemEncoded = ztsClient.postInstanceRefreshRequest(config.domain(), config.serviceName(), req) + String pemEncoded = ztsClient.postInstanceRefreshRequest(zoneConfig.domain(), zoneConfig.serviceName(), req) .getCertificate(); return Crypto.loadX509Certificate(pemEncoded); } 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 index 0e8ca0017f4..55acf0b796c 100644 --- 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 @@ -27,15 +27,18 @@ public class IdentityDocumentGenerator { private final String providerService; private final String ztsUrl; private final String providerDomain; + private final int signingSecretVersion; - public IdentityDocumentGenerator(AthenzProviderServiceConfig config, NodeRepository nodeRepository, Zone zone, KeyProvider keyProvider) { + public IdentityDocumentGenerator(AthenzProviderServiceConfig config, AthenzProviderServiceConfig.Zones zoneConfig, + NodeRepository nodeRepository, Zone zone, KeyProvider keyProvider) { this.nodeRepository = nodeRepository; this.zone = zone; this.keyProvider = keyProvider; this.dnsSuffix = config.certDnsSuffix(); - this.providerService = config.serviceName(); + this.providerService = zoneConfig.serviceName(); this.ztsUrl = config.ztsUrl(); - this.providerDomain = config.domain(); + this.providerDomain = zoneConfig.domain(); + this.signingSecretVersion = zoneConfig.secretVersion(); } public String generateSignedIdentityDocument(String hostname) { @@ -49,7 +52,7 @@ public class IdentityDocumentGenerator { Signature sigGenerator = Signature.getInstance("SHA512withRSA"); // TODO: Get the correct version 0 ok for now - PrivateKey privateKey = keyProvider.getPrivateKey(0); + PrivateKey privateKey = keyProvider.getPrivateKey(signingSecretVersion); sigGenerator.initSign(privateKey); sigGenerator.update(encodedIdentityDocument.getBytes()); String signature = Base64.getEncoder().encodeToString(sigGenerator.sign()); diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java new file mode 100644 index 00000000000..93abda1f9ea --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/SecretStoreKeyProvider.java @@ -0,0 +1,56 @@ +// 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.athenz.auth.util.Crypto; +import com.yahoo.jdisc.http.SecretStore; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.HashMap; +import java.util.Map; + +/** + * @author mortent + */ +public class SecretStoreKeyProvider implements KeyProvider { + + private final SecretStore secretStore; + private final String secretName; + private final Map<Integer, KeyPair> secrets; + + + public SecretStoreKeyProvider(SecretStore secretStore, String secretName) { + this.secretStore = secretStore; + this.secretName = secretName; + this.secrets = new HashMap<>(); + } + + @Override + public PrivateKey getPrivateKey(int version) { + return getKeyPair(version).getPrivate(); + } + + @Override + public PublicKey getPublicKey(int version) { + return getKeyPair(version).getPublic(); + } + + private KeyPair getKeyPair(int version) { + synchronized (secrets) { + KeyPair keyPair = secrets.get(version); + if (keyPair == null) { + keyPair = readKeyPair(version); + secrets.put(version, keyPair); + } + return keyPair; + } + } + + // TODO: Consider moving to cryptoutils + private KeyPair readKeyPair(int version) { + PrivateKey privateKey = Crypto.loadPrivateKey(secretStore.getSecret(secretName, version)); + PublicKey publicKey = Crypto.extractPublicKey(privateKey); + return new KeyPair(publicKey, privateKey); + } +} diff --git a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def index 7e9c19cb86a..4aad9a4eae2 100644 --- a/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def +++ b/athenz-identity-provider-service/src/main/resources/configdefinitions/athenz-provider-service.def @@ -2,28 +2,28 @@ namespace=vespa.hosted.athenz.instanceproviderservice.config # Athenz domain -domain string +zones{}.domain string # Athenz service name -serviceName string +zones{}.serviceName string -# Current key version -keyVersion int default=0 +# Secret name of private Key +zones{}.secretName string -# HTTPS port for Athenz Provider Service endpoint -port int default=8443 +# Secret version +zones{}.secretVersion int -# File name prefix for private and public key. Component assumes suffix .[priv|pub].<version>. -keyPathPrefix string +# HTTPS port for Athenz Provider Service endpoint +port int default=8443 # InstanceConfirmation API path -apiPath string default="/athenz/v1/provider" +apiPath string default="/athenz/v1/provider" # Athenz principal authority header name -athenzPrincipalHeaderName string default="Athenz-Principal-Auth" +athenzPrincipalHeaderName string default="Athenz-Principal-Auth" # Athenz ZTS server url -ztsUrl string +ztsUrl string # Certificate DNS suffix -certDnsSuffix string +certDnsSuffix string 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 c49122a07b8..20a56359eff 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 @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.athenz.instanceproviderservice; import com.fasterxml.jackson.core.JsonProcessingException; 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; @@ -103,7 +104,7 @@ public class AthenzInstanceProviderServiceTest { String service = "service"; AutoGeneratedKeyProvider keyProvider = new AutoGeneratedKeyProvider(); PrivateKey privateKey = keyProvider.getPrivateKey(0); - AthenzProviderServiceConfig config = getAthenzProviderConfig(domain, service, "vespa.dns.suffix"); + AthenzProviderServiceConfig config = getAthenzProviderConfig(domain, service, "vespa.dns.suffix", ZONE); ScheduledExecutorServiceMock executor = new ScheduledExecutorServiceMock(); AthenzInstanceProviderService athenzInstanceProviderService = @@ -112,7 +113,8 @@ public class AthenzInstanceProviderServiceTest { executor, mock(NodeRepository.class), ZONE, - new SelfSignedCertificateClient(keyProvider.getKeyPair(), config)); + new SelfSignedCertificateClient(keyProvider.getKeyPair(), config, + getZoneConfig(config, ZONE))); try (CloseableHttpClient client = createHttpClient(domain, service)) { Runnable certificateRefreshCommand = executor.getCommand() @@ -153,8 +155,10 @@ public class AthenzInstanceProviderServiceTest { AutoGeneratedKeyProvider keyProvider = new AutoGeneratedKeyProvider(); String dnsSuffix = "vespa.dns.suffix"; + AthenzProviderServiceConfig athenzProviderConfig = getAthenzProviderConfig("domain", "service", dnsSuffix, ZONE); IdentityDocumentGenerator identityDocumentGenerator = new IdentityDocumentGenerator( - getAthenzProviderConfig("domain", "service", dnsSuffix), + athenzProviderConfig, + getZoneConfig(athenzProviderConfig, ZONE), nodeRepository, ZONE, keyProvider); @@ -182,19 +186,29 @@ public class AthenzInstanceProviderServiceTest { signedIdentityDocument.signature)); } - private static AthenzProviderServiceConfig getAthenzProviderConfig(String domain, String service, String dnsSuffix) { + private static AthenzProviderServiceConfig getAthenzProviderConfig(String domain, String service, String dnsSuffix, Zone zone) { + AthenzProviderServiceConfig.Zones.Builder zoneConfig = + new AthenzProviderServiceConfig.Zones.Builder() + .serviceName(service) + .secretVersion(0) + .domain(domain) + .secretName("s3cr3t"); + return new AthenzProviderServiceConfig( new AthenzProviderServiceConfig.Builder() - .domain(domain) - .serviceName(service) + .zones(ImmutableMap.of(zone.environment().value() + "." + zone.region().value(), zoneConfig)) .port(PORT) - .keyPathPrefix("dummy-path") .certDnsSuffix(dnsSuffix) .ztsUrl("localhost/zts") .athenzPrincipalHeaderName("Athenz-Principal-Auth") .apiPath("")); } + + private AthenzProviderServiceConfig.Zones getZoneConfig(AthenzProviderServiceConfig config, Zone zone) { + return config.zones(zone.environment().value() + "." + zone.region().value()); + } + private static boolean getStatus(HttpClient client) { try { HttpResponse response = client.execute(new HttpGet("https://localhost:" + PORT + "/status.html")); @@ -288,17 +302,20 @@ public class AthenzInstanceProviderServiceTest { private final KeyPair keyPair; private final AthenzProviderServiceConfig config; + private final AthenzProviderServiceConfig.Zones zoneConfig; - private SelfSignedCertificateClient(KeyPair keyPair, AthenzProviderServiceConfig config) { + private SelfSignedCertificateClient(KeyPair keyPair, AthenzProviderServiceConfig config, + AthenzProviderServiceConfig.Zones zoneConfig) { this.keyPair = keyPair; this.config = config; + this.zoneConfig = zoneConfig; } @Override public X509Certificate updateCertificate(PrivateKey privateKey, TemporalAmount expiryTime) { try { ContentSigner contentSigner = new JcaContentSignerBuilder("SHA512WithRSA").build(keyPair.getPrivate()); - X500Name dnName = new X500Name("CN=" + config.domain() + "." + config.serviceName()); + X500Name dnName = new X500Name("CN=" + zoneConfig.domain() + "." + zoneConfig.serviceName()); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.HOUR, 1); Date endDate = calendar.getTime(); |