diff options
author | Valerij Fredriksen <valerijf@oath.com> | 2017-11-09 15:45:02 +0100 |
---|---|---|
committer | Valerij Fredriksen <valerijf@oath.com> | 2017-11-09 15:45:02 +0100 |
commit | 8e04239b219b26b7cc34a6f8e9173b902eaaf0b6 (patch) | |
tree | 2bcfaf32da103facdc286dd13179309da2e3d721 /athenz-identity-provider-service | |
parent | b008228e620c180b2f78542c9104a4eb6d0fd48c (diff) |
Add http endpoint to sign certificate
Diffstat (limited to 'athenz-identity-provider-service')
5 files changed, 180 insertions, 2 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 26a88896fb9..8ac26938633 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,6 +8,9 @@ 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.net.HostName; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca.CertificateSigner; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca.CertificateSignerServlet; 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; @@ -64,6 +67,7 @@ public class AthenzInstanceProviderService extends AbstractComponent { CertificateClient certificateClient, SslContextFactory sslContextFactory) { this(config, scheduler, zone, sslContextFactory, + new CertificateSigner(keyProvider, getZoneConfig(config, zone), HostName.getLocalhost()), new InstanceValidator(keyProvider, superModelProvider), new IdentityDocumentGenerator(config, getZoneConfig(config, zone), nodeRepository, zone, keyProvider), new AthenzCertificateUpdater( @@ -74,13 +78,15 @@ public class AthenzInstanceProviderService extends AbstractComponent { ScheduledExecutorService scheduler, Zone zone, SslContextFactory sslContextFactory, + CertificateSigner certificateSigner, 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; - this.jetty = createJettyServer(config, sslContextFactory, instanceValidator, identityDocumentGenerator); + this.jetty = createJettyServer(config, sslContextFactory, + certificateSigner, instanceValidator, identityDocumentGenerator); // TODO Configurable update frequency scheduler.scheduleAtFixedRate(reloader, 0, 1, TimeUnit.DAYS); @@ -97,6 +103,7 @@ public class AthenzInstanceProviderService extends AbstractComponent { private static Server createJettyServer(AthenzProviderServiceConfig config, SslContextFactory sslContextFactory, + CertificateSigner certificateSigner, InstanceValidator instanceValidator, IdentityDocumentGenerator identityDocumentGenerator) { Server server = new Server(); @@ -105,6 +112,10 @@ public class AthenzInstanceProviderService extends AbstractComponent { server.addConnector(connector); ServletHandler handler = new ServletHandler(); + + CertificateSignerServlet certificateSignerServlet = new CertificateSignerServlet(certificateSigner); + handler.addServletWithMapping(new ServletHolder(certificateSignerServlet), config.apiPath() + "/sign"); + InstanceConfirmationServlet instanceConfirmationServlet = new InstanceConfirmationServlet(instanceValidator); handler.addServletWithMapping(new ServletHolder(instanceConfirmationServlet), config.apiPath() + "/instance"); diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerServlet.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerServlet.java new file mode 100644 index 00000000000..ad69ffa652a --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/CertificateSignerServlet.java @@ -0,0 +1,79 @@ +// 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.ca; + +import com.yahoo.log.LogLevel; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca.model.SignedCertificate; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.ca.model.SigningRequest; +import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.Utils; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.util.io.pem.PemObject; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.security.cert.X509Certificate; +import java.util.logging.Logger; + +/** + * @author freva + */ +public class CertificateSignerServlet extends HttpServlet { + + private static final Logger log = Logger.getLogger(CertificateSignerServlet.class.getName()); + + private final CertificateSigner certificateSigner; + + public CertificateSignerServlet(CertificateSigner certificateSigner) { + this.certificateSigner = certificateSigner; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + String remoteHostname = getRemoteHostname(req); + SigningRequest signingRequest = Utils.getMapper().readValue(req.getReader(), SigningRequest.class); + + PKCS10CertificationRequest csr = getPKCS10CertRequest(new StringReader(signingRequest.csr)); + log.log(LogLevel.DEBUG, "Certification request from " + remoteHostname + ": " + csr); + + X509Certificate certificate = certificateSigner.generateX509Certificate(csr, remoteHostname); + SignedCertificate signedCertificate = new SignedCertificate(x509CertificateToString(certificate)); + + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType("application/json"); + resp.getWriter().write(Utils.getMapper().writeValueAsString(signedCertificate)); + } catch (RuntimeException e) { + log.log(LogLevel.ERROR, e.getMessage(), e); + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); + } + } + + private String getRemoteHostname(HttpServletRequest req) { + return req.getRemoteHost(); + } + + private static PKCS10CertificationRequest getPKCS10CertRequest(Reader csrReader) { + try (PEMParser pemParser = new PEMParser(csrReader)) { + return (PKCS10CertificationRequest) pemParser.readObject(); + } catch (IOException e) { + throw new RuntimeException("Failed to parse CSR", e); + } + } + + private static String x509CertificateToString(X509Certificate cert) { + try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(new PemObject("CERTIFICATE", cert.getEncoded())); + pemWriter.flush(); + return stringWriter.toString(); + } catch (Exception e) { + throw new RuntimeException("Failed to convert X509Certificate to PEM format", e); + } + } +} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/model/SignedCertificate.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/model/SignedCertificate.java new file mode 100644 index 00000000000..1229cebe0c1 --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/model/SignedCertificate.java @@ -0,0 +1,42 @@ +// 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.ca.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Contains PEM formatted signed certificate + * + * @author freva + */ +public class SignedCertificate { + + @JsonProperty("certificate") public final String certificate; + + @JsonCreator + public SignedCertificate(@JsonProperty("certificate") String certificate) { + this.certificate = certificate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SignedCertificate that = (SignedCertificate) o; + + return certificate.equals(that.certificate); + } + + @Override + public int hashCode() { + return certificate.hashCode(); + } + + @Override + public String toString() { + return "SignedCertificate{" + + "certificate='" + certificate + '\'' + + '}'; + } +} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/model/SigningRequest.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/model/SigningRequest.java new file mode 100644 index 00000000000..a4d9f276b28 --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/ca/model/SigningRequest.java @@ -0,0 +1,42 @@ +// 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.ca.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Contains PEM formatted Certificate Signing Request (CSR) + * + * @author freva + */ +public class SigningRequest { + + @JsonProperty("csr") public final String csr; + + @JsonCreator + public SigningRequest(@JsonProperty("csr") String csr) { + this.csr = csr; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SigningRequest that = (SigningRequest) o; + + return csr.equals(that.csr); + } + + @Override + public int hashCode() { + return csr.hashCode(); + } + + @Override + public String toString() { + return "SigningRequest{" + + "csr='" + csr + '\'' + + '}'; + } +} 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 bf0746aee7e..c8c3826fc39 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 @@ -9,6 +9,7 @@ import com.yahoo.config.provision.SystemName; 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.ca.CertificateSigner; 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; @@ -101,13 +102,16 @@ public class AthenzInstanceProviderServiceTest { ScheduledExecutorService executor = mock(ScheduledExecutorService.class); when(executor.awaitTermination(anyLong(), any())).thenReturn(true); + CertificateSigner certificateSigner = mock(CertificateSigner.class); + InstanceValidator instanceValidator = mock(InstanceValidator.class); when(instanceValidator.isValidInstance(any())).thenReturn(true); IdentityDocumentGenerator identityDocumentGenerator = mock(IdentityDocumentGenerator.class); AthenzInstanceProviderService athenzInstanceProviderService = new AthenzInstanceProviderService( - config, executor, ZONE, sslContextFactory, instanceValidator, identityDocumentGenerator, certificateUpdater); + config, executor, ZONE, sslContextFactory, certificateSigner, instanceValidator, + identityDocumentGenerator, certificateUpdater); try (CloseableHttpClient client = createHttpClient(domain, service)) { assertFalse(getStatus(client)); |