some refactoring and more comments.

This commit is contained in:
Patrick Kuo 2017-02-09 10:41:27 +00:00
parent 011dee09d8
commit b651074523
2 changed files with 44 additions and 38 deletions

View File

@ -4,6 +4,7 @@ import com.google.common.net.HostAndPort
import com.r3.corda.doorman.OptionParserHelper.toConfigWithOptions import com.r3.corda.doorman.OptionParserHelper.toConfigWithOptions
import com.r3.corda.doorman.persistence.CertificationRequestStorage import com.r3.corda.doorman.persistence.CertificationRequestStorage
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
import net.corda.core.createDirectories
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.CACertAndKey import net.corda.core.crypto.X509Utilities.CACertAndKey
import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.core.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
@ -42,16 +43,14 @@ import kotlin.concurrent.thread
import kotlin.system.exitProcess import kotlin.system.exitProcess
/** /**
* CertificateSigningServer runs on Jetty server and provide certificate signing service via http. * DoormanServer runs on Jetty server and provide certificate signing service via http.
* The server will require keystorePath, keystore password and key password via command line input. * 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] * 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 logger = loggerFor<DoormanServer>()
companion object {
val log = loggerFor<DoormanServer>()
}
val server: Server = Server(InetSocketAddress(webServerAddr.hostText, webServerAddr.port)).apply { class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CACertAndKey, val rootCACert: Certificate, val storage: CertificationRequestStorage) : Closeable {
private val server: Server = Server(InetSocketAddress(webServerAddr.hostText, webServerAddr.port)).apply {
server.handler = HandlerCollection().apply { server.handler = HandlerCollection().apply {
addHandler(buildServletContextHandler()) addHandler(buildServletContextHandler())
} }
@ -64,15 +63,15 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CACertAndKey,
.first() .first()
override fun close() { override fun close() {
log.info("Shutting down Doorman Web Services...") logger.info("Shutting down Doorman Web Services...")
server.stop() server.stop()
server.join() server.join()
} }
fun start() { fun start() {
log.info("Starting Doorman Web Services...") logger.info("Starting Doorman Web Services...")
server.start() server.start()
log.info("Doorman Web Services started on $hostAndPort") logger.info("Doorman Web Services started on $hostAndPort")
} }
private fun buildServletContextHandler(): ServletContextHandler { private fun buildServletContextHandler(): ServletContextHandler {
@ -90,7 +89,7 @@ class DoormanServer(webServerAddr: HostAndPort, val caCertAndKey: CACertAndKey,
} }
} }
class DoormanParameters(args: Array<String>) { private class DoormanParameters(args: Array<String>) {
private val argConfig = args.toConfigWithOptions { private val argConfig = args.toConfigWithOptions {
accepts("basedir", "Overriding configuration filepath, default to current directory.").withRequiredArg().describedAs("filepath") accepts("basedir", "Overriding configuration filepath, default to current directory.").withRequiredArg().describedAs("filepath")
accepts("keygen", "Generate CA keypair and certificate using provide Root CA key.").withOptionalArg() accepts("keygen", "Generate CA keypair and certificate using provide Root CA key.").withOptionalArg()
@ -113,7 +112,7 @@ class DoormanParameters(args: Array<String>) {
val caPrivateKeyPassword: String? by config.getOrElse { null } val caPrivateKeyPassword: String? by config.getOrElse { null }
val rootKeystorePassword: String? by config.getOrElse { null } val rootKeystorePassword: String? by config.getOrElse { null }
val rootPrivateKeyPassword: String? by config.getOrElse { null } val rootPrivateKeyPassword: String? by config.getOrElse { null }
val approveAll: Boolean by config val approveAll: Boolean by config.getOrElse { false }
val host: String by config val host: String by config
val port: Int by config val port: Int by config
val dataSourceProperties: Properties by config val dataSourceProperties: Properties by config
@ -127,21 +126,25 @@ class DoormanParameters(args: Array<String>) {
} }
} }
fun main(args: Array<String>) { /** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */
fun readPassword(fmt: String): String { private fun readPassword(fmt: String): String {
return if (System.console() != null) { return if (System.console() != null) {
String(System.console().readPassword(fmt)) String(System.console().readPassword(fmt))
} else { } else {
print(fmt) print(fmt)
readLine()!! readLine()!!
}
} }
}
fun main(args: Array<String>) {
DoormanParameters(args).run { DoormanParameters(args).run {
val log = DoormanServer.log
when (mode) { when (mode) {
DoormanParameters.Mode.ROOT_KEYGEN -> { DoormanParameters.Mode.ROOT_KEYGEN -> {
println("Generating Root CA keypair and certificate.") println("Generating Root CA keypair and certificate.")
// Get password from console if not in config.
val rootKeystorePassword = rootKeystorePassword ?: readPassword("Root Keystore Password : ") val rootKeystorePassword = rootKeystorePassword ?: readPassword("Root Keystore Password : ")
// Ensure folder exists.
rootStorePath.parent.createDirectories()
val rootStore = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword) val rootStore = loadOrCreateKeyStore(rootStorePath, rootKeystorePassword)
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password : ") val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password : ")
@ -160,7 +163,8 @@ fun main(args: Array<String>) {
println(loadKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA_PRIVATE_KEY).publicKey) println(loadKeyStore(rootStorePath, rootKeystorePassword).getCertificate(CORDA_ROOT_CA_PRIVATE_KEY).publicKey)
} }
DoormanParameters.Mode.CA_KEYGEN -> { DoormanParameters.Mode.CA_KEYGEN -> {
println("Generating Intermediate CA keypair and certificate.") println("Generating Intermediate CA keypair and certificate using root keystore $rootStorePath.")
// Get password from console if not in config.
val rootKeystorePassword = rootKeystorePassword ?: readPassword("Root Keystore Password : ") val rootKeystorePassword = rootKeystorePassword ?: readPassword("Root Keystore Password : ")
val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password : ") val rootPrivateKeyPassword = rootPrivateKeyPassword ?: readPassword("Root Private Key Password : ")
val rootKeyStore = loadKeyStore(rootStorePath, rootKeystorePassword) val rootKeyStore = loadKeyStore(rootStorePath, rootKeystorePassword)
@ -169,6 +173,8 @@ fun main(args: Array<String>) {
val keystorePassword = keystorePassword ?: readPassword("Keystore Password : ") val keystorePassword = keystorePassword ?: readPassword("Keystore Password : ")
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password : ") val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password : ")
// Ensure folder exists.
keystorePath.parent.createDirectories()
val keyStore = loadOrCreateKeyStore(keystorePath, keystorePassword) val keyStore = loadOrCreateKeyStore(keystorePath, keystorePassword)
if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA_PRIVATE_KEY)) { if (keyStore.containsAlias(CORDA_INTERMEDIATE_CA_PRIVATE_KEY)) {
@ -186,21 +192,20 @@ fun main(args: Array<String>) {
println(loadKeyStore(keystorePath, keystorePassword).getCertificate(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).publicKey) println(loadKeyStore(keystorePath, keystorePassword).getCertificate(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).publicKey)
} }
DoormanParameters.Mode.DOORMAN -> { DoormanParameters.Mode.DOORMAN -> {
log.info("Starting certificate signing server.") logger.info("Starting Doorman server.")
// Get password from console if not in config.
val keystorePassword = keystorePassword ?: readPassword("Keystore Password : ") val keystorePassword = keystorePassword ?: readPassword("Keystore Password : ")
val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password : ") val caPrivateKeyPassword = caPrivateKeyPassword ?: readPassword("CA Private Key Password : ")
val keystore = X509Utilities.loadKeyStore(keystorePath, keystorePassword) val keystore = X509Utilities.loadKeyStore(keystorePath, keystorePassword)
val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).last() val rootCACert = keystore.getCertificateChain(CORDA_INTERMEDIATE_CA_PRIVATE_KEY).last()
val caCertAndKey = X509Utilities.loadCertificateAndKey(keystore, caPrivateKeyPassword, CORDA_INTERMEDIATE_CA_PRIVATE_KEY) val caCertAndKey = X509Utilities.loadCertificateAndKey(keystore, caPrivateKeyPassword, CORDA_INTERMEDIATE_CA_PRIVATE_KEY)
// Create DB connection. // Create DB connection.
val (datasource, database) = configureDatabase(dataSourceProperties) val (datasource, database) = configureDatabase(dataSourceProperties)
val storage = DBCertificateRequestStorage(database) val storage = DBCertificateRequestStorage(database)
// Daemon thread approving all request periodically. // Daemon thread approving all request periodically.
val approvalThread = if (approveAll) { if (approveAll) {
thread(name = "Request Approval Daemon") { thread(name = "Request Approval Daemon", isDaemon = true) {
logger.warn("Doorman server is in 'Approve All' mode, this will approve all incoming certificate signing request.")
while (true) { while (true) {
sleep(10.seconds.toMillis()) sleep(10.seconds.toMillis())
for (id in storage.getPendingRequestIds()) { for (id in storage.getPendingRequestIds()) {
@ -210,18 +215,15 @@ fun main(args: Array<String>) {
if (it.ipAddress == it.hostName) listOf() else listOf(it.hostName), listOf(it.ipAddress)) if (it.ipAddress == it.hostName) listOf() else listOf(it.hostName), listOf(it.ipAddress))
} }
}) })
log.info("Approved $id") logger.info("Approved request $id")
} }
} }
} }
} else null
DoormanServer(HostAndPort.fromParts(host, port), caCertAndKey, rootCACert, storage).use {
it.start()
it.server.join()
approvalThread?.interrupt()
approvalThread?.join()
} }
val doorman = DoormanServer(HostAndPort.fromParts(host, port), caCertAndKey, rootCACert, storage)
doorman.start()
Runtime.getRuntime().addShutdownHook(thread(start = false) { doorman.close() })
} }
} }
} }
} }

View File

@ -6,17 +6,21 @@ import joptsimple.ArgumentAcceptingOptionSpec
import joptsimple.OptionParser import joptsimple.OptionParser
import kotlin.system.exitProcess import kotlin.system.exitProcess
/**
* Convert commandline arguments to [Config] object will allow us to use kotlin delegate with [ConfigHelper].
*/
object OptionParserHelper { object OptionParserHelper {
fun Array<String>.toConfigWithOptions(options: OptionParser.() -> Unit): Config { fun Array<String>.toConfigWithOptions(registerOptions: OptionParser.() -> Unit): Config {
val parser = OptionParser() val parser = OptionParser()
val helpOption = parser.acceptsAll(listOf("h", "?", "help"), "show help").forHelp(); val helpOption = parser.acceptsAll(listOf("h", "?", "help"), "show help").forHelp();
options(parser) registerOptions(parser)
val optionSet = parser.parse(*this) val optionSet = parser.parse(*this)
// Print help and exit on help option.
if (optionSet.has(helpOption)) { if (optionSet.has(helpOption)) {
parser.printHelpOn(System.out) parser.printHelpOn(System.out)
exitProcess(0) exitProcess(0)
} }
// Convert all command line options to Config.
return ConfigFactory.parseMap(parser.recognizedOptions().mapValues { return ConfigFactory.parseMap(parser.recognizedOptions().mapValues {
val optionSpec = it.value val optionSpec = it.value
if (optionSpec is ArgumentAcceptingOptionSpec<*> && !optionSpec.requiresArgument() && optionSet.has(optionSpec)) true else optionSpec.value(optionSet) if (optionSpec is ArgumentAcceptingOptionSpec<*> && !optionSpec.requiresArgument() && optionSet.has(optionSpec)) true else optionSpec.value(optionSet)