Merged in pat-doorman-status (pull request #27)

Doorman server status

* added doorman server status for monitoring

* minor changes

* Addressed PR issues
    Fixed project dependency after rebased from corda master

Approved-by: Shams Asari <shams.asari@r3.com>
This commit is contained in:
Patrick Kuo 2017-03-23 11:58:39 +00:00
parent 6bb47e1214
commit 523c37079e
5 changed files with 51 additions and 24 deletions

View File

@ -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)

View File

@ -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

View File

@ -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()
}
}

View File

@ -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<DoormanServer>()
}
@ -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() })

View File

@ -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'