diff --git a/docs/source/running-doorman.rst b/docs/source/running-doorman.rst index 9867254c49..40d00e020f 100644 --- a/docs/source/running-doorman.rst +++ b/docs/source/running-doorman.rst @@ -27,11 +27,7 @@ Allowed parameters are: :rootPrivateKeyPassword: Password for the root private key -:host: host on which doorman runs - -:port: port on which doorman runs - -:mode: must be one of: DOORMAN (default), CA_KEYGEN, ROOT_KEYGEN. +:address: The host and port on which doorman runs :database: database properties. The same (including its default value) as for node configuration (see :doc:`corda-configuration-file`). diff --git a/network-management/README.md b/network-management/README.md index 9cf8e3aaad..31861ad6e2 100644 --- a/network-management/README.md +++ b/network-management/README.md @@ -71,8 +71,8 @@ Doorman service can be started with the following options : The doorman service can use JIRA to manage the certificate signing request approval workflow. This can be turned on by providing JIRA connection configuration in the config file. ``` - doormanConfig { - jiraConfig { + doorman { + jira { address = "https://doorman-jira-host.com/" projectCode = "TD" username = "username" @@ -93,7 +93,7 @@ The doorman service can use JIRA to manage the certificate signing request appro ### Network map service Network map service can be enabled by providing the following config: ``` - networkMapConfig { + networkMap { cacheTimeout = 600000 signInterval = 10000 } @@ -104,15 +104,13 @@ The doorman service can use JIRA to manage the certificate signing request appro ##Example config file ``` basedir = "." -host = localhost -port = 0 +address = "localhost:0" rootStorePath = ${basedir}"/certificates/rootstore.jks" keystorePath = ${basedir}"/certificates/caKeystore.jks" #keystorePassword = "password" #Optional if not specified, user will be prompted on the console. #caPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console. #rootPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console. #rootKeystorePassword = "password" #Optional if not specified, user will be prompted on the console. -#trustStorePassword = "password" #Optional if not specified, user will be prompted on the console. Applicable only if, in the ROOT_KEYGEN execution mode. dataSourceProperties { dataSourceClassName = org.h2.jdbcx.JdbcDataSource @@ -153,31 +151,35 @@ networkMap { If local signer is enabled, the server will look for key stores in the certificate folder on start up. The key stores can be created using `--mode` flag. ``` - java -jar doorman-.jar --mode ROOT_KEYGEN + java -jar doorman-.jar --config-file --mode ROOT_KEYGEN ``` and ``` - java -jar doorman-.jar --mode CA_KEYGEN + java -jar doorman-.jar --config-file --mode CA_KEYGEN ``` - A trust store file containing the root trust certificate will be produced in the location `distribute-nodes / truststore.jks` - (relative to `rootStorePath`). `truststore.jks` must be copied to the `certificates` directory of each node before - they attempt to register. The trust store password is `trustpass`. A trust store containing the root certificate will be created in the location `distribute-nodes / network-root-truststore.jks` (relative to `rootStorePath`). The trust store's password can be set using command line argument `--trust-store-password`, or the doorman's keygen utility will ask for password input if trust store password is not provided using this flag. - Path to the network root trust store and password must be provided to corda node via command line arguments before - they attempt to register. + This trust store file is to be distributed to every node that wishes to register with the doorman. The node cannot + register without it. ### 2. Start Doorman service for notary registration - Start the network management server with the doorman service for initial bootstrapping. Network map service (`networkMapConfig`) should be **disabled** at this point. - **Comment out** network map config in the config file and start the server by running : + Start the network management server with the doorman service for initial bootstrapping. Network map service (`networkMap`) + should be **disabled** at this point. **Comment out** network map config in the config file and start the server by running : ``` - java -jar doorman-.jar + java -jar doorman-.jar --config-file ``` ### 3. Create notary node and register with the doorman - After the doorman service is started, start the notary node for the `initial-registration` process. + After the doorman service is started, start the notary node for registration. + ``` + java -jar corda.jar --initial-registration --network-root-truststore-password + ``` + By default it will expect trust store file received from the doorman to be in the location ``certificates/network-root-truststore.jks``. + This can be overridden with the additional `--network-root-truststore` flag. + + NOTE: This step applies to all nodes that wish to register with the doorman. ### 4. Generate node info files for notary nodes Once notary nodes are registered, run the notary nodes with the `just-generate-node-info` flag. @@ -187,16 +189,19 @@ networkMap { The network parameters should contain reference to the notaries node info files. Example network parameters file: - notaries : [{ - notaryNodeInfoFile: "/Path/To/NodeInfo/File1" - validating: true - }, { - notaryNodeInfoFile: "/Path/To/NodeInfo/File2" - validating: false - }] + notaries : [ + { + notaryNodeInfoFile: "/Path/To/NodeInfo/File1" + validating: true + }, + { + notaryNodeInfoFile: "/Path/To/NodeInfo/File2" + validating: false + } + ] minimumPlatformVersion = 1 maxMessageSize = 10485760 - maxTransactionSize = 40000 + maxTransactionSize = 10485760 Save the parameters to `network-parameters.conf` @@ -204,7 +209,7 @@ networkMap { A network parameters file is required to start the network map service for the first time. The initial network parameters file can be loaded using the `--update-network-parameters` flag. We can now restart the network management server with both doorman and network map service. ``` -java -jar doorman-.jar --update-network-parameters network-parameters.conf +java -jar doorman-.jar --config-file --update-network-parameters network-parameters.conf ``` ### 7. Logs @@ -212,5 +217,5 @@ In order to set the desired logging level the system properties need to be used. Appropriate system properties can be set at the execution time. Example: ``` -java -DdefaultLogLevel=TRACE -DconsoleLogLevel=TRACE -jar doorman-.jar +java -DdefaultLogLevel=TRACE -DconsoleLogLevel=TRACE -jar doorman-.jar --config-file ``` \ No newline at end of file diff --git a/network-management/config/network-management-base.conf b/network-management/config/network-management-base.conf index 976b7ff681..1f4bb095ce 100644 --- a/network-management/config/network-management-base.conf +++ b/network-management/config/network-management-base.conf @@ -13,14 +13,14 @@ h2port = 0 # Doorman config # Comment out this section if running without doorman service -doormanConfig{ +doorman { approveInterval = 10000 approveAll = false } # Network map config # Comment out this section if running without network map service -networkMapConfig{ +networkMap { cacheTimeout = 600000 signInterval = 10000 } diff --git a/network-management/config/network-management-jira.conf b/network-management/config/network-management-jira.conf index 6d224eb66a..b3d956463d 100644 --- a/network-management/config/network-management-jira.conf +++ b/network-management/config/network-management-jira.conf @@ -13,10 +13,10 @@ h2port = 0 # Doorman config # Comment out this section if running without doorman service -doormanConfig{ +doorman { approveInterval = 10000 approveAll = false - jiraConfig{ + jira { address = "https://doorman-jira-host.com/" projectCode = "TD" username = "username" @@ -27,7 +27,7 @@ doormanConfig{ # Network map config # Comment out this section if running without network map service -networkMapConfig{ +networkMap { cacheTimeout = 600000 signInterval = 10000 } diff --git a/network-management/config/network-management-local-signing.conf b/network-management/config/network-management-local-signing.conf index c1ec0501e8..39cea6e363 100644 --- a/network-management/config/network-management-local-signing.conf +++ b/network-management/config/network-management-local-signing.conf @@ -19,14 +19,14 @@ h2port = 0 # Doorman config # Comment out this section if running without doorman service -doormanConfig{ +doorman { approveInterval = 10000 approveAll = false } # Network map config # Comment out this section if running without network map service -networkMapConfig{ +networkMap { cacheTimeout = 600000 signInterval = 10000 } diff --git a/network-management/doorman.conf b/network-management/doorman.conf index 8460dad862..ee3dc251c6 100644 --- a/network-management/doorman.conf +++ b/network-management/doorman.conf @@ -1,13 +1,11 @@ basedir = "." -host = localhost -port = 0 +address = "localhost:0" rootStorePath = ${basedir}"/certificates/rootstore.jks" keystorePath = ${basedir}"/certificates/caKeystore.jks" #keystorePassword = "password" #Optional if not specified, user will be prompted on the console. #caPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console. #rootPrivateKeyPassword = "password" #Optional if not specified, user will be prompted on the console. #rootKeystorePassword = "password" #Optional if not specified, user will be prompted on the console. -#trustStorePassword = "password" #Optional if not specified, user will be prompted on the console. Applicable only if, in the ROOT_KEYGEN execution mode. dataSourceProperties { dataSourceClassName = org.h2.jdbcx.JdbcDataSource diff --git a/network-management/hsm.conf b/network-management/hsm-for-doorman.conf similarity index 73% rename from network-management/hsm.conf rename to network-management/hsm-for-doorman.conf index 55f435d4c3..12b3ebefdd 100644 --- a/network-management/hsm.conf +++ b/network-management/hsm-for-doorman.conf @@ -15,17 +15,6 @@ doorman { } } -networkMap { - username = "TEST_USERNAME", - keyGroup = "DEV.CORDACONNECT.OPS.NETMAP" - authParameters { - mode = KEY_FILE - password = "PASSWORD" - keyFilePath = "./Administrator.KEY" - threshold = 2 - } -} - h2port = 0 dataSourceProperties { "dataSourceClassName" = org.h2.jdbcx.JdbcDataSource diff --git a/network-management/hsm-for-networkmap.conf b/network-management/hsm-for-networkmap.conf new file mode 100644 index 0000000000..59556ea46a --- /dev/null +++ b/network-management/hsm-for-networkmap.conf @@ -0,0 +1,22 @@ +basedir = "." +device = "3001@192.168.0.1" +keySpecifier = -1 + +networkMap { + username = "TEST_USERNAME", + keyGroup = "DEV.CORDACONNECT.OPS.NETMAP" + authParameters { + mode = KEY_FILE + password = "PASSWORD" + keyFilePath = "./Administrator.KEY" + threshold = 2 + } +} + +h2port = 0 +dataSourceProperties { + "dataSourceClassName" = org.h2.jdbcx.JdbcDataSource + "dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port} + "dataSource.user" = sa + "dataSource.password" = "" +} diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt index e0d8644f8b..8cf4695ac3 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt @@ -15,10 +15,10 @@ import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever import com.r3.corda.networkmanage.HsmSimulator import com.r3.corda.networkmanage.hsm.authentication.InputReader -import com.r3.corda.networkmanage.hsm.configuration.AuthenticationParameters -import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateParameters -import com.r3.corda.networkmanage.hsm.configuration.NetworkMapCertificateParameters -import com.r3.corda.networkmanage.hsm.configuration.Parameters +import com.r3.corda.networkmanage.hsm.configuration.AuthParametersConfig +import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateConfig +import com.r3.corda.networkmanage.hsm.configuration.NetworkMapCertificateConfig +import com.r3.corda.networkmanage.hsm.configuration.SigningServiceConfig import com.r3.corda.networkmanage.hsm.generator.CertificateConfiguration import com.r3.corda.networkmanage.hsm.generator.GeneratorParameters import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters @@ -127,26 +127,26 @@ abstract class HsmBaseTest { ), hsmUserConfigs) } - protected fun createHsmSigningServiceConfig(): Parameters { - return Parameters( + protected fun createHsmSigningServiceConfig(): SigningServiceConfig { + return SigningServiceConfig( dataSourceProperties = mock(), device = "${hsmSimulator.port}@${hsmSimulator.host}", keySpecifier = 1, - doorman = DoormanCertificateParameters( + doorman = DoormanCertificateConfig( rootKeyStoreFile = rootKeyStoreFile, keyGroup = DOORMAN_CERT_KEY_GROUP, validDays = 3650, rootKeyStorePassword = TRUSTSTORE_PASSWORD, crlDistributionPoint = "http://test.com/revoked.crl", - authParameters = AuthenticationParameters( + authParameters = AuthParametersConfig( mode = SigningServiceAuthMode.PASSWORD, threshold = 2 ) ), - networkMap = NetworkMapCertificateParameters( + networkMap = NetworkMapCertificateConfig( username = "INTEGRATION_TEST", keyGroup = NETWORK_MAP_CERT_KEY_GROUP, - authParameters = AuthenticationParameters( + authParameters = AuthParametersConfig( mode = SigningServiceAuthMode.PASSWORD, password = "INTEGRATION_TEST", threshold = 2 diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt index 18147f0ffc..b0184f52f2 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt @@ -10,10 +10,9 @@ package com.r3.corda.networkmanage.common.utils -import com.google.common.base.CaseFormat -import com.typesafe.config.Config import com.typesafe.config.ConfigFactory -import joptsimple.ArgumentAcceptingOptionSpec +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.ConfigRenderOptions import joptsimple.OptionParser import net.corda.core.CordaOID import net.corda.core.crypto.sha256 @@ -22,6 +21,7 @@ import net.corda.core.internal.SignedDataWithCert import net.corda.core.node.NetworkParameters import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.network.NetworkMap @@ -32,6 +32,9 @@ import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.pkcs.PKCS10CertificationRequest +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.nio.file.Path import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey @@ -40,6 +43,8 @@ import java.security.cert.X509Certificate const val CORDA_NETWORK_MAP = "cordanetworkmap" +val logger: Logger = LoggerFactory.getLogger("com.r3.corda.networkmanage.common.utils") + // TODO These should be defined in node-api typealias SignedNetworkParameters = SignedDataWithCert typealias SignedNetworkMap = SignedDataWithCert @@ -54,20 +59,10 @@ data class CertPathAndKey(val certPath: List, val key: PrivateK */ fun PublicKey.hashString() = encoded.sha256().toString() -fun Array.toConfigWithOptions(registerOptions: OptionParser.() -> Unit): Config { - val parser = OptionParser() - val helpOption = parser.acceptsAll(listOf("h", "?", "help"), "show help").forHelp() - registerOptions(parser) - val optionSet = parser.parse(*this) - // Print help and exit on help option. - if (optionSet.has(helpOption)) { - throw ShowHelpException(parser) - } - // Convert all command line options to Config. - return ConfigFactory.parseMap(parser.recognizedOptions().mapValues { - val optionSpec = it.value - if (optionSpec is ArgumentAcceptingOptionSpec<*> && !optionSpec.requiresArgument() && optionSet.has(optionSpec)) true else optionSpec.value(optionSet) - }.mapKeys { it.key.toCamelcase() }.filterValues { it != null }) +inline fun parseConfig(file: Path): T { + val config = ConfigFactory.parseFile(file.toFile(), ConfigParseOptions.defaults().setAllowMissing(true)).resolve() + logger.info(config.root().render(ConfigRenderOptions.defaults())) + return config.parseAs(strict = false) } class ShowHelpException(val parser: OptionParser, val errorMessage: String? = null) : Exception() @@ -87,12 +82,6 @@ fun initialiseSerialization() { context) } -private fun String.toCamelcase(): String { - return if (contains('_') || contains('-')) { - CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, this.replace("-", "_")) - } else this -} - private fun PKCS10CertificationRequest.firstAttributeValue(identifier: ASN1ObjectIdentifier): ASN1Encodable? { return getAttributes(identifier).firstOrNull()?.attrValues?.firstOrNull() } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanArgsParser.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanArgsParser.kt new file mode 100644 index 0000000000..bd7ca75608 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanArgsParser.kt @@ -0,0 +1,55 @@ +package com.r3.corda.networkmanage.doorman + +import com.r3.corda.networkmanage.common.utils.ShowHelpException +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.ConfigRenderOptions +import joptsimple.OptionParser +import joptsimple.util.EnumConverter +import joptsimple.util.PathConverter +import joptsimple.util.PathProperties +import net.corda.nodeapi.internal.config.parseAs +import java.nio.file.Path + +class DoormanArgsParser { + private val optionParser = OptionParser() + private val helpOption = optionParser.acceptsAll(listOf("h", "help"), "show help").forHelp() + private val configFileArg = optionParser + .accepts("config-file", "The path to the config file") + .withRequiredArg() + .withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING)) + .required() + private val modeArg = optionParser + .accepts("mode", "Set the mode of this application") + .withRequiredArg() + .withValuesConvertedBy(object : EnumConverter(Mode::class.java) {}) + .defaultsTo(Mode.DOORMAN) + private val updateNetworkParametersArg = optionParser + .accepts("update-network-parameters", "Update network parameters file. Currently only network parameters initialisation is supported.") + .withRequiredArg() + .withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING)) + private val trustStorePasswordArg = optionParser + .accepts("trust-store-password", "Password for the generated network root trust store. Only required when operating in ${Mode.ROOT_KEYGEN} mode.") + .withRequiredArg() + + fun parse(vararg args: String): DoormanCmdLineOptions { + val optionSet = optionParser.parse(*args) + if (optionSet.has(helpOption)) { + throw ShowHelpException(optionParser) + } + val configFile = optionSet.valueOf(configFileArg) + val mode = optionSet.valueOf(modeArg) + val networkParametersFile = optionSet.valueOf(updateNetworkParametersArg) + val trustStorePassword = optionSet.valueOf(trustStorePasswordArg) + return DoormanCmdLineOptions(configFile, mode, networkParametersFile, trustStorePassword) + } +} + +data class DoormanCmdLineOptions(val configFile: Path, val mode: Mode, val networkParametersFile: Path?, val trustStorePassword: String?) { + init { + // Make sure trust store password is only specified in root keygen mode. + if (mode != Mode.ROOT_KEYGEN) { + require(trustStorePassword == null) { "Trust store password should not be specified in this mode." } + } + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt index 7126d9ded3..58c70bf71c 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt @@ -10,31 +10,21 @@ package com.r3.corda.networkmanage.doorman -import com.r3.corda.networkmanage.common.utils.toConfigWithOptions -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import net.corda.core.internal.div -import net.corda.core.internal.isRegularFile +import com.google.common.primitives.Booleans +import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.persistence.DatabaseConfig import java.nio.file.Path -import java.nio.file.Paths import java.util.* data class NetworkManagementServerConfig( // TODO: Move local signing to signing server. - val host: String, - val port: Int, + val address: NetworkHostAndPort, val dataSourceProperties: Properties, val database: DatabaseConfig = DatabaseConfig(), - val mode: Mode, val doorman: DoormanConfig?, val networkMap: NetworkMapConfig?, - val updateNetworkParameters: Path?, - val trustStorePassword: String?, - // TODO Should be part of a localSigning sub-config val keystorePath: Path? = null, // TODO Should be part of a localSigning sub-config @@ -52,20 +42,20 @@ data class NetworkManagementServerConfig( // TODO: Move local signing to signing val DEFAULT_APPROVE_INTERVAL = 5.seconds val DEFAULT_SIGN_INTERVAL = 5.seconds } - - init { - if (updateNetworkParameters != null) { - check(updateNetworkParameters.isRegularFile()) { "Update network parameters file $updateNetworkParameters does not exist" } - } - } } data class DoormanConfig(val approveAll: Boolean = false, val jira: JiraConfig? = null, - val approveInterval: Long = NetworkManagementServerConfig.DEFAULT_APPROVE_INTERVAL.toMillis()) + val approveInterval: Long = NetworkManagementServerConfig.DEFAULT_APPROVE_INTERVAL.toMillis()) { + init { + require(Booleans.countTrue(approveAll, jira != null) == 1) { + "Either 'approveAll' or 'jira' config settings need to be specified but not both" + } + } +} data class NetworkMapConfig(val cacheTimeout: Long, - // TODO: Move signing to signing server. + // TODO: Move signing to signing server. val signInterval: Long = NetworkManagementServerConfig.DEFAULT_SIGN_INTERVAL.toMillis()) enum class Mode { @@ -81,43 +71,3 @@ data class JiraConfig( val username: String, val password: String ) - -/** - * Parses the doorman command line options. - */ -fun parseParameters(vararg args: String): NetworkManagementServerConfig { - val argConfig = args.toConfigWithOptions { - accepts("config-file", "The path to the config file") - .withRequiredArg() - .describedAs("filepath") - accepts("update-network-parameters", "Update network parameters filepath. Currently only network parameters initialisation is supported.") - .withRequiredArg() - .describedAs("filepath") - accepts("mode", "Set the mode of this application") - .withRequiredArg() - .defaultsTo(Mode.DOORMAN.name) - accepts("trust-store-password", "Password for generated network root trust store. Only required when operating in root keygen mode.") - .withRequiredArg() - .describedAs("password") - } - - // The config-file option is changed to configFile - val configFile = if (argConfig.hasPath("configFile")) { - Paths.get(argConfig.getString("configFile")) - } else { - // TODO Remove this default - Paths.get(".") / "network-management.conf" - } - require(configFile.isRegularFile()) { "Config file $configFile does not exist" } - - val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))) - .resolve() - .parseAs(false) - - // Make sure trust store password is only specified in root keygen mode. - if (config.mode != Mode.ROOT_KEYGEN) { - require(config.trustStorePassword == null) { "Trust store password should not be specified in this mode." } - } - - return config -} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index 14d45c3b03..d3112b13b8 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -15,32 +15,35 @@ import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.utils.* import com.r3.corda.networkmanage.doorman.signer.LocalSigner import net.corda.core.node.NetworkParameters -import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities +import org.slf4j.LoggerFactory import java.time.Instant import kotlin.concurrent.thread import kotlin.system.exitProcess +private val logger = LoggerFactory.getLogger("com.r3.corda.networkmanage.doorman") + fun main(args: Array) { if (Manifests.exists("Doorman-Version")) { println("Version: ${Manifests.read("Doorman-Version")}") } - val parameters = try { - parseParameters(*args) + val cmdLineOptions = try { + DoormanArgsParser().parse(*args) } catch (e: ShowHelpException) { e.errorMessage?.let(::println) e.parser.printHelpOn(System.out) exitProcess(0) } - // TODO Use the logger for this and elsewhere in this file. - println("Running in ${parameters.mode} mode") - when (parameters.mode) { - Mode.ROOT_KEYGEN -> parameters.rootKeyGenMode() - Mode.CA_KEYGEN -> parameters.caKeyGenMode() - Mode.DOORMAN -> parameters.doormanMode() + val config = parseConfig(cmdLineOptions.configFile) + + logger.info("Running in ${cmdLineOptions.mode} mode") + when (cmdLineOptions.mode) { + Mode.ROOT_KEYGEN -> rootKeyGenMode(cmdLineOptions, config) + Mode.CA_KEYGEN -> caKeyGenMode(config) + Mode.DOORMAN -> doormanMode(cmdLineOptions, config) } } @@ -60,48 +63,48 @@ private fun processKeyStore(config: NetworkManagementServerConfig): Pair) { if (Manifests.exists("Signing-Service-Version")) { println("Signing Service Version: ${Manifests.read("Signing-Service-Version")}") } -} -fun main(args: Array) { - logServiceVersion() - parseParameters(*args).run { - try { - // Validate - // Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available - require(Cipher.getMaxAllowedKeyLength("AES") >= 256) { - "Unlimited Strength Jurisdiction Policy Files must be installed, see http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html" - } - require(Booleans.countTrue(doorman != null, networkMap != null) == 1) { - "Exactly one networkMap or doorman configuration needs to be specified." - } - requireNotNull(dataSourceProperties) + val cmdLineOptions = try { + SigningServiceArgsParser().parse(*args) + } catch (e: ShowHelpException) { + e.errorMessage?.let(::println) + e.parser.printHelpOn(System.out) + exitProcess(0) + } - // Ensure the BouncyCastle provider is installed - if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { - Security.addProvider(BouncyCastleProvider()) - } + val config = parseConfig(cmdLineOptions.configFile) - initialiseSerialization() - // Create DB connection. - val persistence = configureDatabase(dataSourceProperties, database) - if (networkMap != null) { - NetworkMapProcessor(networkMap, device, keySpecifier, persistence).run() - } else { - try { - CsrProcessor(doorman!!, device, keySpecifier, persistence).showMenu() - } catch (e: ShowHelpException) { - e.errorMessage?.let(::println) - e.parser.printHelpOn(System.out) - } - } - } catch (e: Exception) { - logger.error("Error while starting the HSM Signing service.", e) - } + // Validate + // Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available + require(Cipher.getMaxAllowedKeyLength("AES") >= 256) { + "Unlimited Strength Jurisdiction Policy Files must be installed, see http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html" + } + + // Ensure the BouncyCastle provider is installed + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(BouncyCastleProvider()) + } + + initialiseSerialization() + + // Create DB connection. + val persistence = configureDatabase(config.dataSourceProperties, config.database) + if (config.networkMap != null) { + NetworkMapProcessor(config.networkMap, config.device, config.keySpecifier, persistence).run() + } else { + CsrProcessor(config.doorman!!, config.device, config.keySpecifier, persistence).showMenu() } } - - diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt deleted file mode 100644 index c168ab5bcc..0000000000 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package com.r3.corda.networkmanage.hsm.configuration - -import com.r3.corda.networkmanage.common.utils.toConfigWithOptions -import com.r3.corda.networkmanage.hsm.authentication.AuthMode -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import net.corda.core.internal.div -import net.corda.core.internal.isRegularFile -import net.corda.nodeapi.internal.config.parseAs -import net.corda.nodeapi.internal.crypto.X509KeyStore -import net.corda.nodeapi.internal.persistence.DatabaseConfig -import java.nio.file.Path -import java.nio.file.Paths -import java.util.* - -/** - * Configuration parameters. Those are general configuration parameters shared with both - * network map and certificate signing requests processes. - */ -data class Parameters(val dataSourceProperties: Properties, - val database: DatabaseConfig = DatabaseConfig(), - val device: String, - val keySpecifier: Int, - val networkMap: NetworkMapCertificateParameters? = null, - val doorman: DoormanCertificateParameters? = null) - -/** - * Network map signing process specific parameters. - */ -data class NetworkMapCertificateParameters(val username: String, - val keyGroup: String, - val authParameters: AuthenticationParameters) - -/** - * Certificate signing requests process specific parameters. - */ -data class DoormanCertificateParameters(val crlDistributionPoint: String, - val keyGroup:String, - val validDays: Int, - val rootKeyStoreFile: Path, - val rootKeyStorePassword: String, - val authParameters: AuthenticationParameters) { - fun loadRootKeyStore(createNew: Boolean = false): X509KeyStore { - return X509KeyStore.fromFile(rootKeyStoreFile, rootKeyStorePassword, createNew) - } -} - -/** - * Authentication related parameters. - */ -data class AuthenticationParameters(val mode: AuthMode, - val password: String? = null, // This is either HSM password or key file password, depending on the mode. - val keyFilePath: Path? = null, - val threshold: Int) - -/** - * Parses the list of arguments and produces an instance of [Parameters]. - * @param args list of strings corresponding to program arguments - * @return instance of Parameters produced from [args] - */ -fun parseParameters(vararg args: String): Parameters { - val argConfig = args.toConfigWithOptions { - accepts("basedir", "Overriding configuration filepath, default to current directory.").withRequiredArg().defaultsTo(".").describedAs("filepath") - accepts("config-file", "Overriding configuration file.").withRequiredArg().describedAs("filepath") - } - - // The config-file option is changed to configFile - val configFile = if (argConfig.hasPath("configFile")) { - Paths.get(argConfig.getString("configFile")) - } else { - Paths.get(argConfig.getString("basedir")) / "signing_service.conf" - } - require(configFile.isRegularFile()) { "Config file $configFile does not exist" } - - val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))).resolve() - return config.parseAs(false) -} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceConfig.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceConfig.kt new file mode 100644 index 0000000000..0f087b889b --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceConfig.kt @@ -0,0 +1,100 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.hsm.configuration + +import com.google.common.primitives.Booleans +import com.r3.corda.networkmanage.common.utils.ShowHelpException +import com.r3.corda.networkmanage.hsm.authentication.AuthMode +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.ConfigRenderOptions +import joptsimple.OptionParser +import joptsimple.util.PathConverter +import joptsimple.util.PathProperties +import net.corda.core.internal.div +import net.corda.nodeapi.internal.config.parseAs +import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* + +/** + * Configuration parameters. Those are general configuration parameters shared with both + * network map and certificate signing requests processes. + */ +data class SigningServiceConfig(val dataSourceProperties: Properties, + val database: DatabaseConfig = DatabaseConfig(), + val device: String, + val keySpecifier: Int, + val networkMap: NetworkMapCertificateConfig? = null, + val doorman: DoormanCertificateConfig? = null) { + init { + require(Booleans.countTrue(doorman != null, networkMap != null) == 1) { + "Exactly one networkMap or doorman configuration needs to be specified." + } + } +} + +/** + * Network map signing process specific parameters. + */ +data class NetworkMapCertificateConfig(val username: String, + val keyGroup: String, + val authParameters: AuthParametersConfig) + +/** + * Certificate signing requests process specific parameters. + */ +data class DoormanCertificateConfig(val crlDistributionPoint: String, + val keyGroup:String, + val validDays: Int, + val rootKeyStoreFile: Path, + val rootKeyStorePassword: String, + val authParameters: AuthParametersConfig) { + fun loadRootKeyStore(createNew: Boolean = false): X509KeyStore { + return X509KeyStore.fromFile(rootKeyStoreFile, rootKeyStorePassword, createNew) + } +} + +/** + * Authentication related parameters. + */ +data class AuthParametersConfig(val mode: AuthMode, + val password: String? = null, // This is either HSM password or key file password, depending on the mode. + val keyFilePath: Path? = null, + val threshold: Int) + +class SigningServiceArgsParser { + private val optionParser = OptionParser() + private val helpOption = optionParser.acceptsAll(listOf("h", "help"), "show help").forHelp() + private val baseDirArg = optionParser + .accepts("basedir", "Overriding configuration filepath, default to current directory.") + .withRequiredArg() + .withValuesConvertedBy(PathConverter(PathProperties.DIRECTORY_EXISTING)) + .defaultsTo(Paths.get(".")) + private val configFileArg = optionParser + .accepts("config-file", "The path to the config file") + .withRequiredArg() + .withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING)) + + fun parse(vararg args: String): SigningServiceCmdLineOptions { + val optionSet = optionParser.parse(*args) + if (optionSet.has(helpOption)) { + throw ShowHelpException(optionParser) + } + val baseDir = optionSet.valueOf(baseDirArg) + val configFile = optionSet.valueOf(configFileArg) ?: baseDir / "signing_service.conf" + return SigningServiceCmdLineOptions(baseDir, configFile) + } +} + +data class SigningServiceCmdLineOptions(val baseDir: Path, val configFile: Path) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt index 132ff6370c..6b8b91fdf6 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt @@ -12,7 +12,7 @@ package com.r3.corda.networkmanage.hsm.processor import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.authentication.createProvider -import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateParameters +import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateConfig import com.r3.corda.networkmanage.hsm.menu.Menu import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage @@ -21,7 +21,7 @@ import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.persistence.CordaPersistence -class CsrProcessor(private val parameters: DoormanCertificateParameters, +class CsrProcessor(private val config: DoormanCertificateConfig, private val device: String, private val keySpecifier: Int, private val database: CordaPersistence) { @@ -29,12 +29,12 @@ class CsrProcessor(private val parameters: DoormanCertificateParameters, val logger = contextLogger() } - private val auth = parameters.authParameters + private val auth = config.authParameters fun showMenu() { val csrStorage = DBSignedCertificateRequestStorage(database) val sign: (List) -> Unit = { - val signer = parameters.run { + val signer = config.run { HsmCsrSigner( csrStorage, loadRootKeyStore(), @@ -42,7 +42,7 @@ class CsrProcessor(private val parameters: DoormanCertificateParameters, null, validDays, Authenticator( - provider = createProvider(parameters.keyGroup, keySpecifier, device), + provider = createProvider(config.keyGroup, keySpecifier, device), mode = auth.mode, authStrengthThreshold = auth.threshold)) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt index be2a613b32..8f453dbb03 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt @@ -15,12 +15,12 @@ import com.r3.corda.networkmanage.common.signer.NetworkMapSigner import com.r3.corda.networkmanage.hsm.authentication.AuthMode import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.authentication.createProvider -import com.r3.corda.networkmanage.hsm.configuration.NetworkMapCertificateParameters +import com.r3.corda.networkmanage.hsm.configuration.NetworkMapCertificateConfig import com.r3.corda.networkmanage.hsm.signer.HsmSigner import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.persistence.CordaPersistence -class NetworkMapProcessor(private val parameters: NetworkMapCertificateParameters, +class NetworkMapProcessor(private val config: NetworkMapCertificateConfig, private val device: String, private val keySpecifier: Int, private val database: CordaPersistence) { @@ -29,7 +29,7 @@ class NetworkMapProcessor(private val parameters: NetworkMapCertificateParameter } init { - parameters.authParameters.run { + config.authParameters.run { requireNotNull(password) require(mode != AuthMode.CARD_READER) if (mode == AuthMode.KEY_FILE) { @@ -40,7 +40,7 @@ class NetworkMapProcessor(private val parameters: NetworkMapCertificateParameter fun run() { logger.info("Starting network map processor.") - parameters.run { + config.run { val networkMapStorage = PersistentNetworkMapStorage(database) val signer = HsmSigner( Authenticator( diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanArgsParserTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanArgsParserTest.kt new file mode 100644 index 0000000000..a13fde525f --- /dev/null +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanArgsParserTest.kt @@ -0,0 +1,64 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.doorman + +import com.r3.corda.networkmanage.common.utils.ShowHelpException +import joptsimple.OptionException +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull + +class DoormanArgsParserTest { + private val validConfigPath = File("doorman.conf").absolutePath + private val argsParser = DoormanArgsParser() + + @Test + fun `should fail when network parameters file is missing`() { + assertThatThrownBy { + argsParser.parse("--config-file", validConfigPath, "--update-network-parameters", "not-here") + }.hasMessageContaining("not-here") + } + + @Test + fun `should fail when config file is missing`() { + assertThatThrownBy { + argsParser.parse("--config-file", "not-existing-file") + }.hasMessageContaining("not-existing-file") + } + + @Test + fun `should throw ShowHelpException when help option is passed on the command line`() { + assertFailsWith { + argsParser.parse("-h") + } + } + + @Test + fun `should parse trust store password correctly`() { + val parameter = argsParser.parse("--config-file", validConfigPath, "--mode", "ROOT_KEYGEN", "--trust-store-password", "testPassword") + assertEquals("testPassword", parameter.trustStorePassword) + + assertFailsWith { + argsParser.parse("--trust-store-password") + } + + // Should fail if password is provided in mode other then root keygen. + assertFailsWith { + argsParser.parse("--config-file", validConfigPath, "--trust-store-password", "testPassword") + } + + // trust store password is optional. + assertNull(argsParser.parse("--config-file", validConfigPath).trustStorePassword) + } +} diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanParametersTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanParametersTest.kt deleted file mode 100644 index f5227fc461..0000000000 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/DoormanParametersTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package com.r3.corda.networkmanage.doorman - -import com.r3.corda.networkmanage.common.utils.ShowHelpException -import com.typesafe.config.ConfigException -import joptsimple.OptionException -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test -import java.io.File -import java.lang.reflect.InvocationTargetException -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNull -import kotlin.test.assertTrue - -class DoormanParametersTest { - private val validOverrideNetworkConfigPath = File("network-parameters.conf").absolutePath - private val validConfigPath = File("doorman.conf").absolutePath - private val invalidConfigPath = File(javaClass.getResource("/doorman_fail.conf").toURI()).absolutePath - private val validArgs = arrayOf("--config-file", validConfigPath, "--update-network-parameters", validOverrideNetworkConfigPath) - - @Test - fun `should fail when initial network parameters file is missing`() { - val message = assertFailsWith { - parseParameters("--config-file", validConfigPath, "--update-network-parameters", "not-here") - }.targetException.message - assertThat(message).contains("Update network parameters file ") - } - - @Test - fun `should fail when config file is missing`() { - val message = assertFailsWith { - parseParameters("--config-file", "not-existing-file") - }.message - assertThat(message).contains("Config file ") - } - - @Test - fun `should throw ShowHelpException when help option is passed on the command line`() { - assertFailsWith { - parseParameters("-?") - } - } - - @Test - fun `should fail when config missing`() { - assertFailsWith { - parseParameters("--config-file", invalidConfigPath) - } - } - - @Test - fun `should parse database config correctly`() { - val parameter = parseParameters(*validArgs).database - assertTrue(parameter.runMigration) - } - - @Test - fun `should parse trust store password correctly`() { - val parameter = parseParameters("--config-file", validConfigPath, "--mode", "ROOT_KEYGEN", "--trust-store-password", "testPassword") - assertEquals("testPassword", parameter.trustStorePassword) - - assertFailsWith { - parseParameters("--trust-store-password") - } - - // Should fail if password is provided in mode other then root keygen. - assertFailsWith { - parseParameters("--config-file", validConfigPath, "--trust-store-password", "testPassword") - } - - // trust store password is optional. - assertNull(parseParameters("--config-file", validConfigPath).trustStorePassword) - } -} diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/ConfigurationTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/ConfigurationTest.kt deleted file mode 100644 index 4def76076b..0000000000 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/ConfigurationTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package com.r3.corda.networkmanage.hsm.configuration - -import com.r3.corda.networkmanage.TestBase -import com.r3.corda.networkmanage.hsm.authentication.AuthMode -import com.typesafe.config.ConfigException -import org.assertj.core.api.Assertions -import org.junit.Test -import java.io.File -import java.nio.file.Paths -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class ConfigurationTest : TestBase() { - private val validConfigPath = File("./hsm.conf").absolutePath - private val invalidConfigPath = File(javaClass.getResource("/hsm_fail.conf").toURI()).absolutePath - - @Test - fun `config file is parsed correctly`() { - val parameters = parseParameters("--config-file", validConfigPath) - assertEquals("3001@192.168.0.1", parameters.device) - val doormanCertParameters = parameters.doorman!! - assertEquals(AuthMode.PASSWORD, doormanCertParameters.authParameters.mode) - assertEquals(2, doormanCertParameters.authParameters.threshold) - assertEquals(3650, doormanCertParameters.validDays) - val nmParams = parameters.networkMap!! - assertEquals(AuthMode.KEY_FILE, nmParams.authParameters.mode) - assertEquals(Paths.get("./Administrator.KEY"), nmParams.authParameters.keyFilePath) - assertEquals(2, nmParams.authParameters.threshold) - assertEquals("PASSWORD", nmParams.authParameters.password) - assertEquals("TEST_USERNAME", nmParams.username) - } - - @Test - fun `should fail when config missing database source properties`() { - // dataSourceProperties is missing from node_fail.conf and it should fail during parsing, and shouldn't use default from reference.conf. - assertFailsWith { - parseParameters("--config-file", invalidConfigPath) - } - } - - @Test - fun `should fail when config file is missing`() { - val message = assertFailsWith { - com.r3.corda.networkmanage.doorman.parseParameters("--config-file", "not-existing-file") - }.message - Assertions.assertThat(message).contains("Config file ") - } -} \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceArgsParserTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceArgsParserTest.kt new file mode 100644 index 0000000000..630b8d4f4e --- /dev/null +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceArgsParserTest.kt @@ -0,0 +1,57 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.hsm.configuration + +import com.r3.corda.networkmanage.TestBase +import com.r3.corda.networkmanage.common.utils.parseConfig +import com.r3.corda.networkmanage.hsm.authentication.AuthMode +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Test +import java.io.File +import java.nio.file.Paths +import kotlin.test.assertEquals + +class SigningServiceArgsParserTest : TestBase() { + private val doormanConfigPath = File("./hsm-for-doorman.conf").absolutePath + private val networkMapConfigPath = File("./hsm-for-networkmap.conf").absolutePath + private val argsParser = SigningServiceArgsParser() + + @Test + fun `doorman-based config file is parsed correctly`() { + val cmdLineOptions = argsParser.parse("--config-file", doormanConfigPath) + val config = parseConfig(cmdLineOptions.configFile) + assertEquals("3001@192.168.0.1", config.device) + val doormanCertParameters = config.doorman!! + assertEquals(AuthMode.PASSWORD, doormanCertParameters.authParameters.mode) + assertEquals(2, doormanCertParameters.authParameters.threshold) + assertEquals(3650, doormanCertParameters.validDays) + } + + @Test + fun `networkmap-based config file is parsed correctly`() { + val cmdLineOptions = argsParser.parse("--config-file", networkMapConfigPath) + val config = parseConfig(cmdLineOptions.configFile) + assertEquals("3001@192.168.0.1", config.device) + val networkMapConfig = config.networkMap!! + assertEquals(AuthMode.KEY_FILE, networkMapConfig.authParameters.mode) + assertEquals(Paths.get("./Administrator.KEY"), networkMapConfig.authParameters.keyFilePath) + assertEquals(2, networkMapConfig.authParameters.threshold) + assertEquals("PASSWORD", networkMapConfig.authParameters.password) + assertEquals("TEST_USERNAME", networkMapConfig.username) + } + + @Test + fun `should fail when config file is missing`() { + assertThatThrownBy { + argsParser.parse("--config-file", "not-existing-file") + }.hasMessageContaining("not-existing-file") + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index cd81e9f3e3..dbc2c0375c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -19,6 +19,7 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.utilities.NetworkHostAndPort import org.slf4j.LoggerFactory import java.lang.reflect.Field +import java.lang.reflect.InvocationTargetException import java.lang.reflect.Modifier.isStatic import java.lang.reflect.ParameterizedType import java.net.Proxy @@ -74,19 +75,20 @@ fun Config.parseAs(clazz: KClass, strict: Boolean = true): T { val path = defaultToOldPath(property) getValueInternal(path, param.type, strict) } - return constructor.callBy(args) + try { + return constructor.callBy(args) + } catch (e: InvocationTargetException) { + throw e.cause!! + } } class UnknownConfigurationKeysException private constructor(val unknownKeys: Set) : IllegalArgumentException(message(unknownKeys)) { - init { require(unknownKeys.isNotEmpty()) { "Absence of unknown keys should not raise UnknownConfigurationKeysException." } } companion object { - fun of(offendingKeys: Set): UnknownConfigurationKeysException = UnknownConfigurationKeysException(offendingKeys) - private fun message(offendingKeys: Set) = "Unknown configuration keys: ${offendingKeys.joinToString(", ", "[", "]")}." } }