[CORDA-1993]: Replace reflection-based NodeConfiguration parsing with versioned property-based parsing mechanism. (#4132)

This commit is contained in:
Michele Sollecito
2018-11-08 15:56:00 +00:00
committed by GitHub
parent 9277042db8
commit 6c749889d0
34 changed files with 1161 additions and 513 deletions

View File

@ -1,10 +1,17 @@
package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.core.internal.div
import net.corda.core.utilities.loggerFor
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.Valid
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import picocli.CommandLine.Option
@ -12,6 +19,9 @@ import java.nio.file.Path
import java.nio.file.Paths
open class SharedNodeCmdLineOptions {
private companion object {
private val logger by lazy { loggerFor<SharedNodeCmdLineOptions>() }
}
@Option(
names = ["-b", "--base-directory"],
description = ["The node working directory where all the files are kept."]
@ -37,9 +47,19 @@ open class SharedNodeCmdLineOptions {
)
var devMode: Boolean? = null
open fun parseConfiguration(configuration: Config): NodeConfiguration = configuration.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
open fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
open fun rawConfiguration(): Config = ConfigHelper.loadConfig(baseDirectory, configFile)
val option = Configuration.Validation.Options(strict = unknownConfigKeysPolicy == UnknownConfigKeysPolicy.FAIL)
return configuration.parseAsNodeConfiguration(option)
}
open fun rawConfiguration(): Validated<Config, ConfigException> {
return try {
valid(ConfigHelper.loadConfig(baseDirectory, configFile))
} catch (e: ConfigException) {
return invalid(e)
}
}
fun copyFrom(other: SharedNodeCmdLineOptions) {
baseDirectory = other.baseDirectory
@ -47,11 +67,32 @@ open class SharedNodeCmdLineOptions {
unknownConfigKeysPolicy= other.unknownConfigKeysPolicy
devMode = other.devMode
}
fun logRawConfigurationErrors(errors: Set<ConfigException>) {
if (errors.isNotEmpty()) {
logger.error("There were error(s) while attempting to load the node configuration:")
}
errors.forEach { error ->
when (error) {
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
else -> logger.error(error.message, error)
}
}
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
}
}
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
override fun parseConfiguration(configuration: Config): NodeConfiguration {
return super.parseConfiguration(configuration).also { config ->
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
return super.parseConfiguration(configuration).doIfValid { config ->
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
@ -121,8 +162,8 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
)
var networkRootTrustStorePassword: String? = null
override fun parseConfiguration(configuration: Config): NodeConfiguration {
return super.parseConfiguration(configuration).also { config ->
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
return super.parseConfiguration(configuration).doIfValid { config ->
if (isRegistration) {
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
@ -132,7 +173,7 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
}
}
override fun rawConfiguration(): Config {
override fun rawConfiguration(): Validated<Config, ConfigException> {
val configOverrides = mutableMapOf<String, Any>()
configOverrides += "noLocalShell" to noLocalShell
if (sshdServer) {
@ -141,7 +182,11 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
devMode?.let {
configOverrides += "devMode" to it
}
return ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides))
return try {
valid(ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides)))
} catch (e: ConfigException) {
return invalid(e)
}
}
}

View File

@ -15,6 +15,7 @@ import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
import net.corda.node.internal.subcommands.*
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logConfigurationErrors
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logRawConfig
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon
@ -34,7 +35,6 @@ import java.net.InetAddress
import java.nio.file.Path
import java.time.DayOfWeek
import java.time.ZonedDateTime
import java.util.*
/** An interface that can be implemented to tell the node what to do once it's intitiated. */
interface RunAfterNodeInitialisation {
@ -139,7 +139,8 @@ open class NodeStartup : NodeStartupLogging {
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
// Step 5. Load and validate node configuration.
val configuration = cmdLineOptions.nodeConfiguration().doOnErrors { errors -> logConfigurationErrors(errors, cmdLineOptions.configFile) }.optional ?: return ExitCodes.FAILURE
val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE
val configuration = cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional ?: return ExitCodes.FAILURE
// Step 6. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success

View File

@ -1,41 +1,32 @@
package net.corda.node.internal.subcommands
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions
import net.corda.cliutils.CliWrapperBase
import net.corda.cliutils.ExitCodes
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.core.utilities.loggerFor
import net.corda.node.SharedNodeCmdLineOptions
import net.corda.node.internal.initLogging
import net.corda.node.services.config.NodeConfiguration
import picocli.CommandLine.*
import java.nio.file.Path
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
import net.corda.nodeapi.internal.config.toConfigValue
import picocli.CommandLine.Mixin
internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration", "Validate the configuration without starting the node.") {
internal companion object {
private val logger by lazy { loggerFor<ValidateConfigurationCli>() }
internal fun logConfigurationErrors(errors: Iterable<Exception>, configFile: Path) {
errors.forEach { error ->
when (error) {
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
else -> logger.error("Error while parsing node configuration.", error)
}
}
private val configRenderingOptions = ConfigRenderOptions.defaults().setFormatted(true).setComments(false).setOriginComments(false)
internal fun logConfigurationErrors(errors: Iterable<Configuration.Validation.Error>) {
logger.error(errors.joinToString(System.lineSeparator(), "Error(s) while parsing node configuration:${System.lineSeparator()}") { error -> "\t- ${error.description()}" })
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
private fun Configuration.Validation.Error.description(): String {
return "for path: \"$pathAsString\": $message"
}
internal fun logRawConfig(config: Config) = logger.debug("Actual configuration:\n${V1NodeConfigurationSpec.describe(config, Any::toConfigValue).render(configRenderingOptions)}")
}
@Mixin
@ -44,41 +35,7 @@ internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration
override fun initLogging() = initLogging(cmdLineOptions.baseDirectory)
override fun runProgram(): Int {
val configuration = cmdLineOptions.nodeConfiguration()
if (configuration.isInvalid) {
logConfigurationErrors(configuration.errors, cmdLineOptions.configFile)
return ExitCodes.FAILURE
}
return ExitCodes.SUCCESS
val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE
return cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional?.let { ExitCodes.SUCCESS } ?: ExitCodes.FAILURE
}
}
internal fun SharedNodeCmdLineOptions.nodeConfiguration(): Valid<NodeConfiguration> = NodeConfigurationParser.invoke(this)
private object NodeConfigurationParser : (SharedNodeCmdLineOptions) -> Valid<NodeConfiguration> {
private val logger by lazy { loggerFor<ValidateConfigurationCli>() }
private val configRenderingOptions = ConfigRenderOptions.defaults().setComments(false).setOriginComments(false).setFormatted(true)
override fun invoke(cmds: SharedNodeCmdLineOptions): Valid<NodeConfiguration> {
return attempt(cmds::rawConfiguration).doIfValid(::log).attemptMap(cmds::parseConfiguration).mapValid(::validate)
}
internal fun log(config: Config) = logger.debug("Actual configuration:\n${config.root().render(configRenderingOptions)}")
private fun validate(configuration: NodeConfiguration): Valid<NodeConfiguration> {
return Validated.withResult(configuration, configuration.validate().asSequence().map { error -> IllegalArgumentException(error) }.toSet())
}
private fun <VALUE, MAPPED> Valid<VALUE>.attemptMap(convert: (VALUE) -> MAPPED): Valid<MAPPED> = mapValid { value -> attempt { convert.invoke(value) } }
private fun <VALUE> attempt(action: () -> VALUE): Valid<VALUE> {
return try {
valid(action.invoke())
} catch (exception: Exception) {
return invalid(exception)
}
}
}
private typealias Valid<TARGET> = Validated<TARGET, Exception>
}

View File

@ -1,24 +1,22 @@
package net.corda.node.services.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated
import net.corda.core.context.AuthServiceId
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.TimedFlow
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.nodeapi.internal.config.*
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.tools.shell.SSHDConfiguration
import org.slf4j.Logger
import java.net.URL
import java.nio.file.Path
import java.time.Duration
@ -27,10 +25,6 @@ import javax.security.auth.x500.X500Principal
val Int.MB: Long get() = this * 1024L * 1024L
private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1)
private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1)
private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps"
interface NodeConfiguration {
val myLegalName: CordaX500Name
val emailAddress: String
@ -91,23 +85,24 @@ interface NodeConfiguration {
val cryptoServiceName: SupportedCryptoServices?
val cryptoServiceConf: String? // Location for the cryptoService conf file.
fun validate(): List<String>
companion object {
// default to at least 8MB and a bit extra for larger heap sizes
val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory()
internal val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory()
internal val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1)
internal val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1)
// add 5% of any heapsize over 300MB to the default transaction cache size
private fun getAdditionalCacheMemory(): Long {
return Math.max((Runtime.getRuntime().maxMemory() - 300.MB) / 20, 0)
}
val defaultAttachmentContentCacheSize: Long = 10.MB
const val defaultAttachmentCacheBound = 1024L
internal val defaultAttachmentContentCacheSize: Long = 10.MB
internal const val defaultAttachmentCacheBound = 1024L
const val cordappDirectoriesKey = "cordappDirectories"
val defaultJmxReporterType = JmxReporterType.JOLOKIA
internal val defaultJmxReporterType = JmxReporterType.JOLOKIA
}
fun makeCryptoService(): CryptoService {
@ -128,7 +123,14 @@ enum class JmxReporterType {
JOLOKIA, NEW_RELIC
}
data class DevModeOptions(val disableCheckpointChecker: Boolean = false, val allowCompatibilityZone: Boolean = false)
data class DevModeOptions(val disableCheckpointChecker: Boolean = Defaults.disableCheckpointChecker, val allowCompatibilityZone: Boolean = Defaults.disableCheckpointChecker) {
internal object Defaults {
val disableCheckpointChecker = false
val allowCompatibilityZone = false
}
}
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
@ -182,245 +184,9 @@ data class FlowTimeoutConfiguration(
val backoffBase: Double
)
fun Config.parseAsNodeConfiguration(onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): NodeConfiguration = parseAs<NodeConfigurationImpl>(onUnknownKeys)
internal typealias Valid<TARGET> = Validated<TARGET, Configuration.Validation.Error>
data class NodeConfigurationImpl(
/** This is not retrieved from the config file but rather from a command line argument. */
override val baseDirectory: Path,
override val myLegalName: CordaX500Name,
override val jmxMonitoringHttpPort: Int? = null,
override val emailAddress: String,
private val keyStorePassword: String,
private val trustStorePassword: String,
override val crlCheckSoftFail: Boolean,
override val dataSourceProperties: Properties,
override val compatibilityZoneURL: URL? = null,
override var networkServices: NetworkServicesConfig? = null,
override val tlsCertCrlDistPoint: URL? = null,
override val tlsCertCrlIssuer: X500Principal? = null,
override val rpcUsers: List<User>,
override val security: SecurityConfiguration? = null,
override val verifierType: VerifierType,
override val flowTimeout: FlowTimeoutConfiguration,
override val p2pAddress: NetworkHostAndPort,
override val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList(),
private val rpcAddress: NetworkHostAndPort? = null,
private val rpcSettings: NodeRpcSettings,
override val messagingServerAddress: NetworkHostAndPort?,
override val messagingServerExternal: Boolean = (messagingServerAddress != null),
override val notary: NotaryConfig?,
@Suppress("DEPRECATION")
@Deprecated("Do not configure")
override val certificateChainCheckPolicies: List<CertChainPolicyConfig> = emptyList(),
override val devMode: Boolean = false,
override val noLocalShell: Boolean = false,
override val devModeOptions: DevModeOptions? = null,
override val useTestClock: Boolean = false,
override val lazyBridgeStart: Boolean = true,
override val detectPublicIp: Boolean = true,
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(),
override val sshd: SSHDConfiguration? = null,
override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode),
private val transactionCacheSizeMegaBytes: Int? = null,
private val attachmentContentCacheSizeMegaBytes: Int? = null,
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
override val extraNetworkMapKeys: List<UUID> = emptyList(),
// do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing)
private val h2port: Int? = null,
private val h2Settings: NodeH2Settings? = null,
// do not use or remove (used by Capsule)
private val jarDirs: List<String> = emptyList(),
override val flowMonitorPeriodMillis: Duration = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS,
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS,
override val cordappDirectories: List<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA,
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() },
override val cryptoServiceName: SupportedCryptoServices? = null,
override val cryptoServiceConf: String? = null
) : NodeConfiguration {
companion object {
private val logger = loggerFor<NodeConfigurationImpl>()
// private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT")
}
private val actualRpcSettings: NodeRpcSettings
init {
actualRpcSettings = when {
rpcAddress != null -> {
require(rpcSettings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." }
logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.")
rpcSettings.copy(address = rpcAddress)
}
else -> {
rpcSettings.address ?: throw ConfigException.Missing("rpcSettings.address")
rpcSettings
}
}
}
override val certificatesDirectory = baseDirectory / "certificates"
private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks"
private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks"
// TODO: There are two implications here:
// 1. "signingCertificateStore" and "p2pKeyStore" have the same passwords. In the future we should re-visit this "rule" and see of they can be made different;
// 2. The passwords for store and for keys in this store are the same, this is due to limitations of Artemis.
override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword, keyStorePassword)
private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword, keyStorePassword)
private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks"
private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword, trustStorePassword)
override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
override val rpcOptions: NodeRpcOptions
get() {
return actualRpcSettings.asOptions()
}
private fun validateTlsCertCrlConfig(): List<String> {
val errors = mutableListOf<String>()
if (tlsCertCrlIssuer != null) {
if (tlsCertCrlDistPoint == null) {
errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL"
}
}
if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) {
errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE"
}
return errors
}
private fun validateCryptoService(): List<String> {
val errors = mutableListOf<String>()
if (cryptoServiceName == null && cryptoServiceConf != null) {
errors += "cryptoServiceName is null, but cryptoServiceConf is set to $cryptoServiceConf"
}
return errors
}
override fun validate(): List<String> {
val errors = mutableListOf<String>()
errors += validateDevModeOptions()
val rpcSettingsErrors = validateRpcSettings(rpcSettings)
errors += rpcSettingsErrors
if (rpcSettingsErrors.isEmpty()) {
// Forces lazy property to initialise in order to throw exceptions
rpcOptions
}
errors += validateTlsCertCrlConfig()
errors += validateNetworkServices()
errors += validateH2Settings()
errors += validateCryptoService()
return errors
}
private fun validateH2Settings(): List<String> {
val errors = mutableListOf<String>()
if (h2port != null && h2Settings != null) {
errors += "Cannot specify both 'h2port' and 'h2Settings' in configuration"
}
return errors
}
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
val errors = mutableListOf<String>()
if (options.adminAddress == null) {
errors += "'rpcSettings.adminAddress': missing"
}
if (options.useSsl && options.ssl == null) {
errors += "'rpcSettings.ssl': missing (rpcSettings.useSsl was set to true)."
}
return errors
}
private fun validateDevModeOptions(): List<String> {
if (devMode) {
compatibilityZoneURL?.let {
if (devModeOptions?.allowCompatibilityZone != true) {
return listOf("'compatibilityZoneURL': present. Property cannot be set when 'devMode' is true unless devModeOptions.allowCompatibilityZone is also true")
}
}
// if compatibiliZoneURL is set then it will be copied into the networkServices field and thus skipping
// this check by returning above is fine.
networkServices?.let {
if (devModeOptions?.allowCompatibilityZone != true) {
return listOf("'networkServices': present. Property cannot be set when 'devMode' is true unless devModeOptions.allowCompatibilityZone is also true")
}
}
}
return emptyList()
}
private fun validateNetworkServices(): List<String> {
val errors = mutableListOf<String>()
if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) {
errors += "Cannot configure both compatibilityZoneUrl and networkServices simultaneously"
}
return errors
}
override val transactionCacheSizeBytes: Long
get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes
override val attachmentContentCacheSizeBytes: Long
get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes
override val effectiveH2Settings: NodeH2Settings?
get() = when {
h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port))
else -> h2Settings
}
init {
// This is a sanity feature do not remove.
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
require(security == null || rpcUsers.isEmpty()) {
"Cannot specify both 'rpcUsers' and 'security' in configuration"
}
@Suppress("DEPRECATION")
if (certificateChainCheckPolicies.isNotEmpty()) {
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|Please contact the R3 team on the public slack to discuss your use case.
""".trimMargin())
}
// Support the deprecated method of configuring network services with a single compatibilityZoneURL option
if (compatibilityZoneURL != null && networkServices == null) {
networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true)
}
require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" }
}
}
data class NodeRpcSettings(
val address: NetworkHostAndPort?,
val adminAddress: NetworkHostAndPort?,
val standAloneBroker: Boolean = false,
val useSsl: Boolean = false,
val ssl: BrokerRpcSslOptions?
) {
fun asOptions(): NodeRpcOptions {
return object : NodeRpcOptions {
override val address = this@NodeRpcSettings.address!!
override val adminAddress = this@NodeRpcSettings.adminAddress!!
override val standAloneBroker = this@NodeRpcSettings.standAloneBroker
override val useSsl = this@NodeRpcSettings.useSsl
override val sslConfig = this@NodeRpcSettings.ssl
override fun toString(): String {
return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig"
}
}
}
}
fun Config.parseAsNodeConfiguration(options: Configuration.Validation.Options = Configuration.Validation.Options(strict = true)): Valid<NodeConfiguration> = V1NodeConfigurationSpec.parse(this, options)
data class NodeH2Settings(
val address: NetworkHostAndPort?
@ -494,7 +260,7 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ
// Provider of users credentials and permissions data
data class DataSource(val type: AuthDataSourceType,
val passwordEncryption: PasswordEncryption = PasswordEncryption.NONE,
val passwordEncryption: PasswordEncryption = Defaults.passwordEncryption,
val connection: Properties? = null,
val users: List<User>? = null) {
init {
@ -503,6 +269,10 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ
AuthDataSourceType.DB -> require(users == null && connection != null)
}
}
internal object Defaults {
val passwordEncryption = PasswordEncryption.NONE
}
}
companion object {

View File

@ -0,0 +1,303 @@
package net.corda.node.services.config
import com.typesafe.config.ConfigException
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.SslConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.tools.shell.SSHDConfiguration
import java.net.URL
import java.nio.file.Path
import java.time.Duration
import java.util.*
import javax.security.auth.x500.X500Principal
data class NodeConfigurationImpl(
/** This is not retrieved from the config file but rather from a command line argument. */
override val baseDirectory: Path,
override val myLegalName: CordaX500Name,
override val jmxMonitoringHttpPort: Int? = Defaults.jmxMonitoringHttpPort,
override val emailAddress: String,
private val keyStorePassword: String,
private val trustStorePassword: String,
override val crlCheckSoftFail: Boolean,
override val dataSourceProperties: Properties,
override val compatibilityZoneURL: URL? = Defaults.compatibilityZoneURL,
override var networkServices: NetworkServicesConfig? = Defaults.networkServices,
override val tlsCertCrlDistPoint: URL? = Defaults.tlsCertCrlDistPoint,
override val tlsCertCrlIssuer: X500Principal? = Defaults.tlsCertCrlIssuer,
override val rpcUsers: List<User>,
override val security: SecurityConfiguration? = Defaults.security,
override val verifierType: VerifierType,
override val flowTimeout: FlowTimeoutConfiguration,
override val p2pAddress: NetworkHostAndPort,
override val additionalP2PAddresses: List<NetworkHostAndPort> = Defaults.additionalP2PAddresses,
private val rpcAddress: NetworkHostAndPort? = Defaults.rpcAddress,
private val rpcSettings: NodeRpcSettings,
override val messagingServerAddress: NetworkHostAndPort?,
override val messagingServerExternal: Boolean = Defaults.messagingServerExternal(messagingServerAddress),
override val notary: NotaryConfig?,
@Suppress("DEPRECATION")
@Deprecated("Do not configure")
override val certificateChainCheckPolicies: List<CertChainPolicyConfig> = Defaults.certificateChainCheckPolicies,
override val devMode: Boolean = Defaults.devMode,
override val noLocalShell: Boolean = Defaults.noLocalShell,
override val devModeOptions: DevModeOptions? = Defaults.devModeOptions,
override val useTestClock: Boolean = Defaults.useTestClock,
override val lazyBridgeStart: Boolean = Defaults.lazyBridgeStart,
override val detectPublicIp: Boolean = Defaults.detectPublicIp,
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
override val additionalNodeInfoPollingFrequencyMsec: Long = Defaults.additionalNodeInfoPollingFrequencyMsec,
override val sshd: SSHDConfiguration? = Defaults.sshd,
override val database: DatabaseConfig = Defaults.database(devMode),
private val transactionCacheSizeMegaBytes: Int? = Defaults.transactionCacheSizeMegaBytes,
private val attachmentContentCacheSizeMegaBytes: Int? = Defaults.attachmentContentCacheSizeMegaBytes,
override val attachmentCacheBound: Long = Defaults.attachmentCacheBound,
override val extraNetworkMapKeys: List<UUID> = Defaults.extraNetworkMapKeys,
// do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing)
private val h2port: Int? = Defaults.h2port,
private val h2Settings: NodeH2Settings? = Defaults.h2Settings,
// do not use or remove (used by Capsule)
private val jarDirs: List<String> = Defaults.jarDirs,
override val flowMonitorPeriodMillis: Duration = Defaults.flowMonitorPeriodMillis,
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = Defaults.flowMonitorSuspensionLoggingThresholdMillis,
override val cordappDirectories: List<Path> = Defaults.cordappsDirectories(baseDirectory),
override val jmxReporterType: JmxReporterType? = Defaults.jmxReporterType,
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = Defaults.cordappSignerKeyFingerprintBlacklist,
override val cryptoServiceName: SupportedCryptoServices? = null,
override val cryptoServiceConf: String? = null
) : NodeConfiguration {
internal object Defaults {
val jmxMonitoringHttpPort: Int? = null
val compatibilityZoneURL: URL? = null
val networkServices: NetworkServicesConfig? = null
val tlsCertCrlDistPoint: URL? = null
val tlsCertCrlIssuer: X500Principal? = null
val security: SecurityConfiguration? = null
val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList()
val rpcAddress: NetworkHostAndPort? = null
@Suppress("DEPRECATION")
val certificateChainCheckPolicies: List<CertChainPolicyConfig> = emptyList()
const val devMode: Boolean = false
const val noLocalShell: Boolean = false
val devModeOptions: DevModeOptions? = null
const val useTestClock: Boolean = false
const val lazyBridgeStart: Boolean = true
const val detectPublicIp: Boolean = true
val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis()
val sshd: SSHDConfiguration? = null
val transactionCacheSizeMegaBytes: Int? = null
val attachmentContentCacheSizeMegaBytes: Int? = null
const val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound
val extraNetworkMapKeys: List<UUID> = emptyList()
val h2port: Int? = null
val h2Settings: NodeH2Settings? = null
val jarDirs: List<String> = emptyList()
val flowMonitorPeriodMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_PERIOD_MILLIS
val flowMonitorSuspensionLoggingThresholdMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
val jmxReporterType: JmxReporterType = NodeConfiguration.defaultJmxReporterType
val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() }
fun cordappsDirectories(baseDirectory: Path) = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT)
fun messagingServerExternal(messagingServerAddress: NetworkHostAndPort?) = messagingServerAddress != null
fun database(devMode: Boolean) = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode)
}
companion object {
private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps"
private val logger = loggerFor<NodeConfigurationImpl>()
}
private val actualRpcSettings: NodeRpcSettings
init {
actualRpcSettings = when {
rpcAddress != null -> {
require(rpcSettings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." }
logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.")
rpcSettings.copy(address = rpcAddress)
}
else -> {
rpcSettings.address ?: throw ConfigException.Missing("rpcSettings.address")
rpcSettings
}
}
// This is a sanity feature do not remove.
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
require(security == null || rpcUsers.isEmpty()) {
"Cannot specify both 'rpcUsers' and 'security' in configuration"
}
@Suppress("DEPRECATION")
if (certificateChainCheckPolicies.isNotEmpty()) {
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|Please contact the R3 team on the public Slack to discuss your use case.
""".trimMargin())
}
// Support the deprecated method of configuring network services with a single compatibilityZoneURL option
if (compatibilityZoneURL != null && networkServices == null) {
networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true)
}
require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" }
}
override val certificatesDirectory = baseDirectory / "certificates"
private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks"
private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks"
// TODO: There are two implications here:
// 1. "signingCertificateStore" and "p2pKeyStore" have the same passwords. In the future we should re-visit this "rule" and see of they can be made different;
// 2. The passwords for store and for keys in this store are the same, this is due to limitations of Artemis.
override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword, keyStorePassword)
private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword, keyStorePassword)
private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks"
private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword, trustStorePassword)
override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
override val rpcOptions: NodeRpcOptions
get() {
return actualRpcSettings.asOptions()
}
override val transactionCacheSizeBytes: Long
get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes
override val attachmentContentCacheSizeBytes: Long
get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes
override val effectiveH2Settings: NodeH2Settings?
get() = when {
h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port))
else -> h2Settings
}
fun validate(): List<String> {
val errors = mutableListOf<String>()
errors += validateDevModeOptions()
val rpcSettingsErrors = validateRpcSettings(rpcSettings)
errors += rpcSettingsErrors
if (rpcSettingsErrors.isEmpty()) {
// Forces lazy property to initialise in order to throw exceptions
rpcOptions
}
errors += validateTlsCertCrlConfig()
errors += validateNetworkServices()
errors += validateH2Settings()
errors += validateCryptoService()
return errors
}
private fun validateTlsCertCrlConfig(): List<String> {
val errors = mutableListOf<String>()
if (tlsCertCrlIssuer != null) {
if (tlsCertCrlDistPoint == null) {
errors += "'tlsCertCrlDistPoint' is mandatory when 'tlsCertCrlIssuer' is specified"
}
}
if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) {
errors += "'tlsCertCrlDistPoint' is mandatory when 'crlCheckSoftFail' is false"
}
return errors
}
private fun validateH2Settings(): List<String> {
val errors = mutableListOf<String>()
if (h2port != null && h2Settings != null) {
errors += "cannot specify both 'h2port' and 'h2Settings'"
}
return errors
}
private fun validateCryptoService(): List<String> {
val errors = mutableListOf<String>()
if (cryptoServiceName == null && cryptoServiceConf != null) {
errors += "'cryptoServiceName' is mandatory when 'cryptoServiceConf' is specified"
}
return errors
}
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
val errors = mutableListOf<String>()
if (options.adminAddress == null) {
errors += "'rpcSettings.adminAddress' is mandatory"
}
if (options.useSsl && options.ssl == null) {
errors += "'rpcSettings.ssl' is mandatory when 'rpcSettings.useSsl' is specified"
}
return errors
}
private fun validateDevModeOptions(): List<String> {
if (devMode) {
compatibilityZoneURL?.let {
if (devModeOptions?.allowCompatibilityZone != true) {
return listOf("cannot specify 'compatibilityZoneURL' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true")
}
}
// if compatibilityZoneURL is set then it will be copied into the networkServices field and thus skipping
// this check by returning above is fine.
networkServices?.let {
if (devModeOptions?.allowCompatibilityZone != true) {
return listOf("cannot specify 'networkServices' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true")
}
}
}
return emptyList()
}
private fun validateNetworkServices(): List<String> {
val errors = mutableListOf<String>()
if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) {
errors += "cannot specify both 'compatibilityZoneUrl' and 'networkServices'"
}
return errors
}
}
data class NodeRpcSettings(
val address: NetworkHostAndPort?,
val adminAddress: NetworkHostAndPort?,
val standAloneBroker: Boolean = Defaults.standAloneBroker,
val useSsl: Boolean = Defaults.useSsl,
val ssl: BrokerRpcSslOptions?
) {
internal object Defaults {
val standAloneBroker = false
val useSsl = false
}
fun asOptions(): NodeRpcOptions {
return object : NodeRpcOptions {
override val address = this@NodeRpcSettings.address!!
override val adminAddress = this@NodeRpcSettings.adminAddress!!
override val standAloneBroker = this@NodeRpcSettings.standAloneBroker
override val useSsl = this@NodeRpcSettings.useSsl
override val sslConfig = this@NodeRpcSettings.ssl
override fun toString(): String {
return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig"
}
}
}
}

View File

@ -0,0 +1,51 @@
package net.corda.node.services.config.schema.parsers
import com.typesafe.config.Config
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigUtil
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.Valid
import java.net.MalformedURLException
import java.net.URL
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import javax.security.auth.x500.X500Principal
internal fun toProperties(rawValue: ConfigObject): Properties = rawValue.toConfig().toProperties()
private fun Config.toProperties() = entrySet().associateByTo(Properties(), { ConfigUtil.splitPath(it.key).joinToString(".") }, { it.value.unwrapped().toString() })
internal fun toCordaX500Name(rawValue: String) = attempt<CordaX500Name, IllegalArgumentException> { CordaX500Name.parse(rawValue) }
internal fun toURL(rawValue: String) = attempt<URL, MalformedURLException> { URL(rawValue) }
internal fun toUUID(rawValue: String) = attempt<UUID, IllegalArgumentException> { UUID.fromString(rawValue) }
internal fun toNetworkHostAndPort(rawValue: String) = attempt<NetworkHostAndPort, IllegalArgumentException> { NetworkHostAndPort.parse(rawValue) }
internal fun toPrincipal(rawValue: String) = attempt<X500Principal, IllegalArgumentException> { X500Principal(rawValue) }
internal fun toPath(rawValue: String) = attempt<Path, InvalidPathException> { Paths.get(rawValue) }
private inline fun <RESULT, reified ERROR : Exception> attempt(action: () -> RESULT, message: (ERROR) -> String): Valid<RESULT> {
return try {
valid(action.invoke())
} catch (e: Exception) {
when (e) {
is ERROR -> badValue(message.invoke(e))
else -> throw e
}
}
}
internal inline fun <reified RESULT, reified ERROR : Exception> attempt(action: () -> RESULT) = attempt(action, { e: ERROR -> "value does not comply with ${RESULT::class.java.simpleName} specification (${e.message})" })
internal fun <RESULT> validValue(result: RESULT) = valid<RESULT, Configuration.Validation.Error>(result)
internal fun <RESULT> badValue(message: String) = invalid<RESULT, Configuration.Validation.Error>(Configuration.Validation.Error.BadValue.of(message))

View File

@ -0,0 +1,236 @@
@file:Suppress("DEPRECATION")
package net.corda.node.services.config.schema.v1
import com.typesafe.config.Config
import com.typesafe.config.ConfigObject
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.configuration.parsing.internal.get
import net.corda.common.configuration.parsing.internal.listOrEmpty
import net.corda.common.configuration.parsing.internal.map
import net.corda.common.configuration.parsing.internal.mapValid
import net.corda.common.configuration.parsing.internal.nested
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.core.context.AuthServiceId
import net.corda.node.services.config.AuthDataSourceType
import net.corda.node.services.config.CertChainPolicyConfig
import net.corda.node.services.config.CertChainPolicyType
import net.corda.node.services.config.DevModeOptions
import net.corda.node.services.config.FlowOverride
import net.corda.node.services.config.FlowOverrideConfig
import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.NetworkServicesConfig
import net.corda.node.services.config.NodeH2Settings
import net.corda.node.services.config.NodeRpcSettings
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.PasswordEncryption
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.SecurityConfiguration.AuthService.Companion.defaultAuthServiceId
import net.corda.node.services.config.Valid
import net.corda.node.services.config.schema.parsers.attempt
import net.corda.node.services.config.schema.parsers.badValue
import net.corda.node.services.config.schema.parsers.toCordaX500Name
import net.corda.node.services.config.schema.parsers.toNetworkHostAndPort
import net.corda.node.services.config.schema.parsers.toPath
import net.corda.node.services.config.schema.parsers.toProperties
import net.corda.node.services.config.schema.parsers.toURL
import net.corda.node.services.config.schema.parsers.toUUID
import net.corda.node.services.config.schema.parsers.validValue
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
import net.corda.tools.shell.SSHDConfiguration
internal object UserSpec : Configuration.Specification<User>("User") {
private val username by string().optional()
private val user by string().optional()
private val password by string(sensitive = true)
private val permissions by string().listOrEmpty()
override fun parseValid(configuration: Config): Valid<User> {
val username = configuration[username] ?: configuration[user]
return when (username) {
null -> invalid(Configuration.Validation.Error.MissingValue.forKey("username"))
else -> valid(User(username, configuration[password], configuration[permissions].toSet()))
}
}
}
internal object SecurityConfigurationSpec : Configuration.Specification<SecurityConfiguration>("SecurityConfiguration") {
private object AuthServiceSpec : Configuration.Specification<SecurityConfiguration.AuthService>("AuthService") {
private object DataSourceSpec : Configuration.Specification<SecurityConfiguration.AuthService.DataSource>("DataSource") {
private val type by enum(AuthDataSourceType::class)
private val passwordEncryption by enum(PasswordEncryption::class).optional().withDefaultValue(SecurityConfiguration.AuthService.DataSource.Defaults.passwordEncryption)
private val connection by nestedObject(sensitive = true).map(::toProperties).optional()
private val users by nested(UserSpec).list().optional()
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService.DataSource> {
val type = configuration[type]
val passwordEncryption = configuration[passwordEncryption]
val connection = configuration[connection]
val users = configuration[users]
return when {
type == AuthDataSourceType.INMEMORY && (users == null || connection != null) -> badValue("\"INMEMORY\" data source type requires \"users\" and cannot specify \"connection\"")
type == AuthDataSourceType.DB && (users != null || connection == null) -> badValue("\"DB\" data source type requires \"connection\" and cannot specify \"users\"")
else -> valid(SecurityConfiguration.AuthService.DataSource(type, passwordEncryption, connection, users))
}
}
}
private object OptionsSpec : Configuration.Specification<SecurityConfiguration.AuthService.Options>("Options") {
private object CacheSpec : Configuration.Specification<SecurityConfiguration.AuthService.Options.Cache>("Cache") {
private val expireAfterSecs by long().mapValid { value -> if (value >= 0) validValue(value) else badValue("cannot be less than 0'") }
private val maxEntries by long().mapValid { value -> if (value >= 0) validValue(value) else badValue("cannot be less than 0'") }
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService.Options.Cache> {
return valid(SecurityConfiguration.AuthService.Options.Cache(configuration[expireAfterSecs], configuration[maxEntries]))
}
}
private val cache by nested(CacheSpec).optional()
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService.Options> {
return valid(SecurityConfiguration.AuthService.Options(configuration[cache]))
}
}
private val dataSource by nested(DataSourceSpec)
private val id by string().map(::AuthServiceId).optional()
val options by nested(OptionsSpec).optional()
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService> {
val dataSource = configuration[dataSource]
val id = configuration[id] ?: defaultAuthServiceId(dataSource.type)
val options = configuration[options]
return when {
dataSource.type == AuthDataSourceType.INMEMORY && options?.cache != null -> badValue("no cache supported for \"INMEMORY\" data provider")
else -> valid(SecurityConfiguration.AuthService(dataSource, id, options))
}
}
}
private val authService by nested(AuthServiceSpec)
override fun parseValid(configuration: Config): Valid<SecurityConfiguration> {
return valid(SecurityConfiguration(configuration[authService]))
}
}
internal object DevModeOptionsSpec : Configuration.Specification<DevModeOptions>("DevModeOptions") {
private val disableCheckpointChecker by boolean().optional().withDefaultValue(DevModeOptions.Defaults.disableCheckpointChecker)
private val allowCompatibilityZone by boolean().optional().withDefaultValue(DevModeOptions.Defaults.allowCompatibilityZone)
override fun parseValid(configuration: Config): Valid<DevModeOptions> {
return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone]))
}
}
internal object NetworkServicesConfigSpec : Configuration.Specification<NetworkServicesConfig>("NetworkServicesConfig") {
private val doormanURL by string().mapValid(::toURL)
private val networkMapURL by string().mapValid(::toURL)
private val pnm by string().mapValid(::toUUID).optional()
private val inferred by boolean().optional().withDefaultValue(false)
override fun parseValid(configuration: Config): Valid<NetworkServicesConfig> {
return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred]))
}
}
@Suppress("DEPRECATION")
internal object CertChainPolicyConfigSpec : Configuration.Specification<CertChainPolicyConfig>("CertChainPolicyConfig") {
private val role by string()
private val policy by enum(CertChainPolicyType::class)
private val trustedAliases by string().listOrEmpty()
override fun parseValid(configuration: Config): Valid<CertChainPolicyConfig> {
return valid(CertChainPolicyConfig(configuration[role], configuration[policy], configuration[trustedAliases].toSet()))
}
}
internal object FlowTimeoutConfigurationSpec : Configuration.Specification<FlowTimeoutConfiguration>("FlowTimeoutConfiguration") {
private val timeout by duration()
private val maxRestartCount by int()
private val backoffBase by double()
override fun parseValid(configuration: Config): Valid<FlowTimeoutConfiguration> {
return valid(FlowTimeoutConfiguration(configuration[timeout], configuration[maxRestartCount], configuration[backoffBase]))
}
}
internal object NotaryConfigSpec : Configuration.Specification<NotaryConfig>("NotaryConfig") {
private val validating by boolean()
private val serviceLegalName by string().mapValid(::toCordaX500Name).optional()
private val className by string().optional().withDefaultValue("net.corda.node.services.transactions.SimpleNotaryService")
private val extraConfig by nestedObject().map(ConfigObject::toConfig).optional()
override fun parseValid(configuration: Config): Valid<NotaryConfig> {
return valid(NotaryConfig(configuration[validating], configuration[serviceLegalName], configuration[className], configuration[extraConfig]))
}
}
internal object NodeRpcSettingsSpec : Configuration.Specification<NodeRpcSettings>("NodeRpcSettings") {
internal object BrokerRpcSslOptionsSpec : Configuration.Specification<BrokerRpcSslOptions>("BrokerRpcSslOptions") {
private val keyStorePath by string().mapValid(::toPath)
private val keyStorePassword by string(sensitive = true)
override fun parseValid(configuration: Config): Valid<BrokerRpcSslOptions> {
return valid(BrokerRpcSslOptions(configuration[keyStorePath], configuration[keyStorePassword]))
}
}
private val address by string().mapValid(::toNetworkHostAndPort).optional()
private val adminAddress by string().mapValid(::toNetworkHostAndPort).optional()
private val standAloneBroker by boolean().optional().withDefaultValue(NodeRpcSettings.Defaults.standAloneBroker)
private val useSsl by boolean().optional().withDefaultValue(NodeRpcSettings.Defaults.useSsl)
private val ssl by nested(BrokerRpcSslOptionsSpec).optional()
override fun parseValid(configuration: Config): Valid<NodeRpcSettings> {
return valid(NodeRpcSettings(configuration[address], configuration[adminAddress], configuration[standAloneBroker], configuration[useSsl], configuration[ssl]))
}
}
internal object SSHDConfigurationSpec : Configuration.Specification<SSHDConfiguration>("SSHDConfiguration") {
private val port by int()
override fun parseValid(configuration: Config): Valid<SSHDConfiguration> = attempt<SSHDConfiguration, IllegalArgumentException> { SSHDConfiguration(configuration[port]) }
}
internal object DatabaseConfigSpec : Configuration.Specification<DatabaseConfig>("DatabaseConfig") {
private val initialiseSchema by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.initialiseSchema)
private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel)
private val exportHibernateJMXStatistics by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.exportHibernateJMXStatistics)
private val mappedSchemaCacheSize by long().optional().withDefaultValue(DatabaseConfig.Defaults.mappedSchemaCacheSize)
override fun parseValid(configuration: Config): Valid<DatabaseConfig> {
return valid(DatabaseConfig(configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize]))
}
}
internal object NodeH2SettingsSpec : Configuration.Specification<NodeH2Settings>("NodeH2Settings") {
private val address by string().mapValid(::toNetworkHostAndPort).optional()
override fun parseValid(configuration: Config): Valid<NodeH2Settings> {
return valid(NodeH2Settings(configuration[address]))
}
}
internal object FlowOverridesConfigSpec : Configuration.Specification<FlowOverrideConfig>("FlowOverrideConfig") {
internal object SingleSpec : Configuration.Specification<FlowOverride>("FlowOverride") {
private val initiator by string()
private val responder by string()
override fun parseValid(configuration: Config): Valid<FlowOverride> {
return valid(FlowOverride(configuration[initiator], configuration[responder]))
}
}
private val overrides by nested(FlowOverridesConfigSpec.SingleSpec).listOrEmpty()
override fun parseValid(configuration: Config): Valid<FlowOverrideConfig> {
return valid(FlowOverrideConfig(configuration[overrides]))
}
}

View File

@ -0,0 +1,149 @@
package net.corda.node.services.config.schema.v1
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.configuration.parsing.internal.get
import net.corda.common.configuration.parsing.internal.listOrEmpty
import net.corda.common.configuration.parsing.internal.map
import net.corda.common.configuration.parsing.internal.mapValid
import net.corda.common.configuration.parsing.internal.nested
import net.corda.common.configuration.parsing.internal.toValidationError
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.node.services.config.JmxReporterType
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.NodeConfigurationImpl.Defaults
import net.corda.node.services.config.Valid
import net.corda.node.services.config.VerifierType
import net.corda.node.services.config.schema.parsers.badValue
import net.corda.node.services.config.schema.parsers.toCordaX500Name
import net.corda.node.services.config.schema.parsers.toNetworkHostAndPort
import net.corda.node.services.config.schema.parsers.toPath
import net.corda.node.services.config.schema.parsers.toPrincipal
import net.corda.node.services.config.schema.parsers.toProperties
import net.corda.node.services.config.schema.parsers.toURL
import net.corda.node.services.config.schema.parsers.toUUID
import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices
internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfiguration>("NodeConfiguration") {
private val myLegalName by string().mapValid(::toCordaX500Name)
private val emailAddress by string()
private val jmxMonitoringHttpPort by int().optional()
private val dataSourceProperties by nestedObject(sensitive = true).map(::toProperties)
private val rpcUsers by nested(UserSpec).listOrEmpty()
private val security by nested(SecurityConfigurationSpec).optional()
private val devMode by boolean().optional().withDefaultValue(Defaults.devMode)
private val devModeOptions by nested(DevModeOptionsSpec).optional()
private val compatibilityZoneURL by string().mapValid(::toURL).optional()
private val networkServices by nested(NetworkServicesConfigSpec).optional()
private val certificateChainCheckPolicies by nested(CertChainPolicyConfigSpec).list().optional().withDefaultValue(Defaults.certificateChainCheckPolicies)
private val verifierType by enum(VerifierType::class)
private val flowTimeout by nested(FlowTimeoutConfigurationSpec)
private val notary by nested(NotaryConfigSpec).optional()
private val additionalNodeInfoPollingFrequencyMsec by long().optional().withDefaultValue(Defaults.additionalNodeInfoPollingFrequencyMsec)
private val p2pAddress by string().mapValid(::toNetworkHostAndPort)
private val additionalP2PAddresses by string().mapValid(::toNetworkHostAndPort).list().optional().withDefaultValue(Defaults.additionalP2PAddresses)
private val rpcSettings by nested(NodeRpcSettingsSpec)
private val messagingServerAddress by string().mapValid(::toNetworkHostAndPort).optional()
private val messagingServerExternal by boolean().optional()
private val useTestClock by boolean().optional().withDefaultValue(Defaults.useTestClock)
private val lazyBridgeStart by boolean().optional().withDefaultValue(Defaults.lazyBridgeStart)
private val detectPublicIp by boolean().optional().withDefaultValue(Defaults.detectPublicIp)
private val sshd by nested(SSHDConfigurationSpec).optional()
private val database by nested(DatabaseConfigSpec).optional()
private val noLocalShell by boolean().optional().withDefaultValue(Defaults.noLocalShell)
private val attachmentCacheBound by long().optional().withDefaultValue(Defaults.attachmentCacheBound)
private val extraNetworkMapKeys by string().mapValid(::toUUID).list().optional().withDefaultValue(Defaults.extraNetworkMapKeys)
private val tlsCertCrlDistPoint by string().mapValid(::toURL).optional()
private val tlsCertCrlIssuer by string().mapValid(::toPrincipal).optional()
private val h2Settings by nested(NodeH2SettingsSpec).optional()
private val flowMonitorPeriodMillis by duration().optional().withDefaultValue(Defaults.flowMonitorPeriodMillis)
private val flowMonitorSuspensionLoggingThresholdMillis by duration().optional().withDefaultValue(Defaults.flowMonitorSuspensionLoggingThresholdMillis)
private val crlCheckSoftFail by boolean()
private val jmxReporterType by enum(JmxReporterType::class).optional().withDefaultValue(Defaults.jmxReporterType)
private val baseDirectory by string().mapValid(::toPath)
private val flowOverrides by nested(FlowOverridesConfigSpec).optional()
private val keyStorePassword by string(sensitive = true)
private val trustStorePassword by string(sensitive = true)
private val rpcAddress by string().mapValid(::toNetworkHostAndPort).optional()
private val transactionCacheSizeMegaBytes by int().optional()
private val attachmentContentCacheSizeMegaBytes by int().optional()
private val h2port by int().optional()
private val jarDirs by string().list().optional().withDefaultValue(Defaults.jarDirs)
private val cordappDirectories by string().mapValid(::toPath).list().optional()
private val cordappSignerKeyFingerprintBlacklist by string().list().optional().withDefaultValue(Defaults.cordappSignerKeyFingerprintBlacklist)
private val cryptoServiceName by enum(SupportedCryptoServices::class).optional()
private val cryptoServiceConf by string().optional()
@Suppress("unused")
private val custom by nestedObject().optional()
override fun parseValid(configuration: Config): Valid<NodeConfiguration> {
val messagingServerExternal = configuration[messagingServerExternal] ?: Defaults.messagingServerExternal(configuration[messagingServerAddress])
val database = configuration[database] ?: Defaults.database(configuration[devMode])
val cordappDirectories = configuration[cordappDirectories] ?: Defaults.cordappsDirectories(configuration[baseDirectory])
val result = try {
valid<NodeConfigurationImpl, Configuration.Validation.Error>(NodeConfigurationImpl(
baseDirectory = configuration[baseDirectory],
myLegalName = configuration[myLegalName],
emailAddress = configuration[emailAddress],
p2pAddress = configuration[p2pAddress],
keyStorePassword = configuration[keyStorePassword],
trustStorePassword = configuration[trustStorePassword],
crlCheckSoftFail = configuration[crlCheckSoftFail],
dataSourceProperties = configuration[dataSourceProperties],
rpcUsers = configuration[rpcUsers],
verifierType = configuration[verifierType],
flowTimeout = configuration[flowTimeout],
rpcSettings = configuration[rpcSettings],
messagingServerAddress = configuration[messagingServerAddress],
notary = configuration[notary],
flowOverrides = configuration[flowOverrides],
additionalP2PAddresses = configuration[additionalP2PAddresses],
additionalNodeInfoPollingFrequencyMsec = configuration[additionalNodeInfoPollingFrequencyMsec],
jmxMonitoringHttpPort = configuration[jmxMonitoringHttpPort],
security = configuration[security],
devMode = configuration[devMode],
devModeOptions = configuration[devModeOptions],
compatibilityZoneURL = configuration[compatibilityZoneURL],
networkServices = configuration[networkServices],
certificateChainCheckPolicies = configuration[certificateChainCheckPolicies],
messagingServerExternal = messagingServerExternal,
useTestClock = configuration[useTestClock],
lazyBridgeStart = configuration[lazyBridgeStart],
detectPublicIp = configuration[detectPublicIp],
sshd = configuration[sshd],
database = database,
noLocalShell = configuration[noLocalShell],
attachmentCacheBound = configuration[attachmentCacheBound],
extraNetworkMapKeys = configuration[extraNetworkMapKeys],
tlsCertCrlDistPoint = configuration[tlsCertCrlDistPoint],
tlsCertCrlIssuer = configuration[tlsCertCrlIssuer],
h2Settings = configuration[h2Settings],
flowMonitorPeriodMillis = configuration[flowMonitorPeriodMillis],
flowMonitorSuspensionLoggingThresholdMillis = configuration[flowMonitorSuspensionLoggingThresholdMillis],
jmxReporterType = configuration[jmxReporterType],
rpcAddress = configuration[rpcAddress],
transactionCacheSizeMegaBytes = configuration[transactionCacheSizeMegaBytes],
attachmentContentCacheSizeMegaBytes = configuration[attachmentContentCacheSizeMegaBytes],
h2port = configuration[h2port],
jarDirs = configuration[jarDirs],
cordappDirectories = cordappDirectories,
cordappSignerKeyFingerprintBlacklist = configuration[cordappSignerKeyFingerprintBlacklist],
cryptoServiceName = configuration[cryptoServiceName],
cryptoServiceConf = configuration[cryptoServiceConf]
))
} catch (e: Exception) {
return when (e) {
is ConfigException -> invalid(e.toValidationError(typeName = "NodeConfiguration"))
is IllegalArgumentException -> badValue(e.message!!)
else -> throw e
}
}
return result.mapValid { conf -> Valid.withResult(conf as NodeConfiguration, conf.validate().map(::toError).toSet()) }
}
}
private fun toError(validationErrorMessage: String): Configuration.Validation.Error = Configuration.Validation.Error.BadValue.of(validationErrorMessage)

View File

@ -72,7 +72,6 @@
multiParam: false
acceptableValues:
- "FAIL"
- "WARN"
- "IGNORE"
- parameterName: "--sshd"
parameterType: "boolean"

View File

@ -1,6 +1,7 @@
package net.corda.node.services.config
import com.typesafe.config.*
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.core.internal.toPath
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
@ -33,14 +34,16 @@ class NodeConfigurationImplTest {
fun `can't have tlsCertCrlDistPoint null when tlsCertCrlIssuer is given`() {
val configValidationResult = configTlsCertCrlOptions(null, "C=US, L=New York, OU=Corda, O=R3 HoldCo LLC, CN=Corda Root CA").validate()
assertTrue { configValidationResult.isNotEmpty() }
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL")
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint")
assertThat(configValidationResult.first()).contains("tlsCertCrlIssuer")
}
@Test
fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() {
val configValidationResult = configTlsCertCrlOptions(null, null, false).validate()
assertTrue { configValidationResult.isNotEmpty() }
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE")
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint")
assertThat(configValidationResult.first()).contains("crlCheckSoftFail")
}
@Test
@ -143,9 +146,9 @@ class NodeConfigurationImplTest {
val missingPropertyPath = "rpcSettings.address"
rawConfig = rawConfig.withoutPath(missingPropertyPath)
assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.isInstanceOfSatisfying(ConfigException.Missing::class.java) { exception ->
assertThat(exception.message).isNotNull()
assertThat(exception.message).contains(missingPropertyPath)
assertThat(rawConfig.parseAsNodeConfiguration().errors.single()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
assertThat(error.message).contains(missingPropertyPath)
assertThat(error.typeName).isEqualTo(NodeConfiguration::class.java.simpleName)
}
}
@ -191,7 +194,10 @@ class NodeConfigurationImplTest {
fun `fail on wrong cryptoServiceName`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
rawConfig = rawConfig.withValue("cryptoServiceName", ConfigValueFactory.fromAnyRef("UNSUPPORTED"))
assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.hasMessageStartingWith("UNSUPPORTED is not one of")
val config = rawConfig.parseAsNodeConfiguration()
assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("has no constant of the name 'UNSUPPORTED'") }.toList()).isNotEmpty
}
@Test
@ -200,7 +206,7 @@ class NodeConfigurationImplTest {
rawConfig = rawConfig.withoutPath("rpcSettings.address")
rawConfig = rawConfig.withValue("rpcAddress", ConfigValueFactory.fromAnyRef("localhost:4444"))
assertThatCode { rawConfig.parseAsNodeConfiguration() }.doesNotThrowAnyException()
assertThat(rawConfig.parseAsNodeConfiguration().isValid).isTrue()
}
@Test
@ -210,11 +216,11 @@ class NodeConfigurationImplTest {
val config = rawConfig.parseAsNodeConfiguration()
assertThat(config.validate().filter { it.contains("rpcSettings.adminAddress") }).isNotEmpty
assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("rpcSettings.adminAddress") }.toList()).isNotEmpty
}
@Test
fun `compatiilityZoneURL populates NetworkServices`() {
fun `compatibilityZoneURL populates NetworkServices`() {
val compatibilityZoneURL = URI.create("https://r3.com").toURL()
val configuration = testConfiguration.copy(
devMode = false,
@ -228,7 +234,7 @@ class NodeConfigurationImplTest {
@Test
fun `jmxReporterType is null and defaults to Jokolia`() {
val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
val nodeConfig = rawConfig.parseAsNodeConfiguration()
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
}
@ -236,7 +242,7 @@ class NodeConfigurationImplTest {
fun `jmxReporterType is not null and is set to New Relic`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC"))
val nodeConfig = rawConfig.parseAsNodeConfiguration()
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow()
assertTrue(JmxReporterType.NEW_RELIC.toString() == nodeConfig.jmxReporterType.toString())
}
@ -244,15 +250,15 @@ class NodeConfigurationImplTest {
fun `jmxReporterType is not null and set to Jokolia`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA"))
val nodeConfig = rawConfig.parseAsNodeConfiguration()
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
}
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration {
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfigurationImpl {
return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
}
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration {
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfigurationImpl {
return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer?.let { X500Principal(it) }, crlCheckSoftFail = crlCheckSoftFail)
}