diff --git a/doorman/build.gradle b/doorman/build.gradle index b78e7ce979..a50eb042f4 100644 --- a/doorman/build.gradle +++ b/doorman/build.gradle @@ -41,6 +41,7 @@ dependencies { compile project(":core") compile project(":node") + compile project(":node-api") testCompile project(":test-utils") // Log4J: logging framework (with SLF4J bindings) diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt index 9d4e2f167c..9f6dcc514c 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanParameters.kt @@ -5,8 +5,8 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import net.corda.core.div -import net.corda.node.services.config.getOrElse -import net.corda.node.services.config.getValue +import net.corda.nodeapi.config.getOrElse +import net.corda.nodeapi.config.getValue import java.nio.file.Path import java.util.* @@ -30,10 +30,10 @@ class DoormanParameters(vararg args: String) { private val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))).resolve() val keystorePath: Path by config.getOrElse { basedir / "certificates" / "caKeystore.jks" } val rootStorePath: Path by config.getOrElse { basedir / "certificates" / "rootCAKeystore.jks" } - val keystorePassword: String? by config.getOrElse { null } - val caPrivateKeyPassword: String? by config.getOrElse { null } - val rootKeystorePassword: String? by config.getOrElse { null } - val rootPrivateKeyPassword: String? by config.getOrElse { null } + val keystorePassword: String? by config + val caPrivateKeyPassword: String? by config + val rootKeystorePassword: String? by config + val rootPrivateKeyPassword: String? by config val host: String by config val port: Int by config val dataSourceProperties: Properties by config diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt index 73c17789e6..6c35b132d1 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/DoormanWebService.kt @@ -8,6 +8,7 @@ import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest +import org.codehaus.jackson.map.ObjectMapper import java.io.ByteArrayOutputStream import java.io.InputStream import java.security.cert.Certificate @@ -25,7 +26,7 @@ import javax.ws.rs.core.Response.Status.UNAUTHORIZED * Provides functionality for asynchronous submission of certificate signing requests and retrieval of the results. */ @Path("") -class DoormanWebService(val intermediateCACertAndKey: CACertAndKey, val rootCert: Certificate, val storage: CertificationRequestStorage) { +class DoormanWebService(val intermediateCACertAndKey: CACertAndKey, val rootCert: Certificate, val storage: CertificationRequestStorage, val serverStatus: DoormanServerStatus) { @Context lateinit var request: HttpServletRequest /** * Accept stream of [PKCS10CertificationRequest] from user and persists in [CertificationRequestStorage] for approval. @@ -81,4 +82,11 @@ class DoormanWebService(val intermediateCACertAndKey: CACertAndKey, val rootCert is CertificateResponse.Unauthorised -> status(UNAUTHORIZED).entity(response.message) }.build() } + + @GET + @Path("status") + @Produces(MediaType.APPLICATION_JSON) + fun status(): Response { + return ok(ObjectMapper().writeValueAsString(serverStatus)).build() + } } \ No newline at end of file diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt index 4c50804601..96dbf7dd45 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt @@ -36,6 +36,7 @@ import java.lang.Thread.sleep import java.net.InetSocketAddress import java.net.URI import java.security.cert.Certificate +import java.time.Instant import kotlin.concurrent.thread import kotlin.system.exitProcess @@ -44,8 +45,9 @@ import kotlin.system.exitProcess * The server will require keystorePath, keystore password and key password via command line input. * The Intermediate CA certificate,Intermediate CA private key and Root CA Certificate should use alias name specified in [X509Utilities] */ - class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CACertAndKey, val rootCACert: Certificate, val storage: CertificationRequestStorage) : Closeable { + val serverStatus = DoormanServerStatus() + companion object { val logger = loggerFor() } @@ -72,6 +74,31 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CACertAndKey, logger.info("Starting Doorman Web Services...") server.start() logger.info("Doorman Web Services started on $hostAndPort") + serverStatus.serverStartTime = Instant.now() + + // Thread approving request periodically. + thread(name = "Request Approval Thread") { + while (true) { + try { + sleep(10.seconds.toMillis()) + // TODO: Handle rejected request? + serverStatus.lastRequestCheckTime = Instant.now() + for (id in storage.getApprovedRequestIds()) { + storage.approveRequest(id) { + val request = JcaPKCS10CertificationRequest(request) + createServerCert(request.subject, request.publicKey, caCertAndKey, + if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress)) + } + logger.info("Approved request $id") + serverStatus.lastApprovalTime = Instant.now() + serverStatus.approvedRequests++ + } + } catch (e: Exception) { + // Log the error and carry on. + logger.error("Error encountered when approving request.", e) + } + } + } } private fun buildServletContextHandler(): ServletContextHandler { @@ -79,7 +106,7 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CACertAndKey, contextPath = "/" val resourceConfig = ResourceConfig().apply { // Add your API provider classes (annotated for JAX-RS) here - register(DoormanWebService(caCertAndKey, rootCACert, storage)) + register(DoormanWebService(caCertAndKey, rootCACert, storage, serverStatus)) } val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 // Initialise at server start @@ -89,6 +116,11 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CACertAndKey, } } +data class DoormanServerStatus(var serverStartTime: Instant? = null, + var lastRequestCheckTime: Instant? = null, + var lastApprovalTime: Instant? = null, + var approvedRequests: Int = 0) + /** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */ private fun readPassword(fmt: String): String { return if (System.console() != null) { @@ -178,21 +210,6 @@ private fun DoormanParameters.startDoorman() { JiraCertificateRequestStorage(requestStorage, jiraClient, jiraConfig.projectCode, jiraConfig.doneTransitionCode) } - // Daemon thread approving request periodically. - thread(name = "Request Approval Daemon") { - while (true) { - sleep(10.seconds.toMillis()) - // TODO: Handle rejected request? - for (id in storage.getApprovedRequestIds()) { - storage.approveRequest(id) { - val request = JcaPKCS10CertificationRequest(request) - createServerCert(request.subject, request.publicKey, caCertAndKey, - if (ipAddress == hostName) listOf() else listOf(hostName), listOf(ipAddress)) - } - logger.info("Approved request $id") - } - } - } val doorman = DoormanServer(HostAndPort.fromParts(host, port), caCertAndKey, rootCACert, storage) doorman.start() Runtime.getRuntime().addShutdownHook(thread(start = false) { doorman.close() }) diff --git a/settings.gradle b/settings.gradle index 0368341586..725aa137ed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -28,3 +28,4 @@ include 'samples:network-visualiser' include 'samples:simm-valuation-demo' include 'samples:raft-notary-demo' include 'samples:bank-of-corda-demo' +include 'doorman'