diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 62e9851d88..8d6035431f 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -1693,7 +1693,7 @@ MaxLineLength:ConfigSections.kt$DatabaseConfigSpec$private val initialiseAppSchema by enum(SchemaInitializationType::class).optional().withDefaultValue(DatabaseConfig.Defaults.initialiseAppSchema) MaxLineLength:ConfigSections.kt$DatabaseConfigSpec$private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel) MaxLineLength:ConfigSections.kt$DatabaseConfigSpec$return valid(DatabaseConfig(configuration[initialiseSchema], configuration[initialiseAppSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize])) - MaxLineLength:ConfigSections.kt$NetworkServicesConfigSpec$return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred])) + MaxLineLength:ConfigSections.kt$NetworkServicesConfigSpec$return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred], configuration[csrToken])) MaxLineLength:ConfigSections.kt$NodeRpcSettingsSpec$return valid(NodeRpcSettings(configuration[address], configuration[adminAddress], configuration[standAloneBroker], configuration[useSsl], configuration[ssl])) MaxLineLength:ConfigSections.kt$NotaryConfigSpec$return valid(NotaryConfig(configuration[validating], configuration[serviceLegalName], configuration[className], configuration[etaMessageThresholdSeconds], configuration[extraConfig], configuration[raft], configuration[bftSMaRt])) MaxLineLength:ConfigSections.kt$SSHDConfigurationSpec$override fun parseValid(configuration: Config): Valid<SSHDConfiguration> @@ -3808,6 +3808,7 @@ SpreadOperator:FxTransactionBuildTutorial.kt$ForeignExchangeFlow$(*ourOutputState.map { StateAndContract(it, Cash.PROGRAM_ID) }.toTypedArray()) SpreadOperator:FxTransactionBuildTutorial.kt$ForeignExchangeFlow$(*theirInputStates.toTypedArray()) SpreadOperator:FxTransactionBuildTutorial.kt$ForeignExchangeFlow$(*theirOutputState.map { StateAndContract(it, Cash.PROGRAM_ID) }.toTypedArray()) + SpreadOperator:HTTPNetworkRegistrationService.kt$HTTPNetworkRegistrationService$(OpaqueBytes(request.encoded), "Platform-Version" to "${versionInfo.platformVersion}", "Client-Version" to versionInfo.releaseVersion, "Private-Network-Map" to (config.pnm?.toString() ?: ""), *(config.csrToken?.let { arrayOf(CENM_SUBMISSION_TOKEN to it) } ?: arrayOf())) SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*leftPredicates.toTypedArray()) SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray()) SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*rightPredicates.toTypedArray()) diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 92dfa87e7d..137be849ec 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -513,6 +513,15 @@ networkServices *Default:* not defined + csrToken + Optional token to provide alongside the certificate signing request (CSR) as part of the HTTP header during node registration. + The token can be used by certificate signing authority (or Identity Manager Service) to verify additional identity requirements. + The maximum token length is limited by the maximum HTTP header size, which is normally 8KB, assuming that a few other internal + attributes are also present in the header. Also, the token length itself may never exceed 8192, limited by the database structure. + Only US-ASCII characters are allowed. + + *Default:* not defined + .. _corda_configuration_file_p2pAddress: p2pAddress diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 81440feab0..2855b2935a 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -175,12 +175,15 @@ data class NotaryConfig( * set explicitly ([inferred] == false) or weather they have been inferred via the compatibilityZoneURL parameter * ([inferred] == true) where both the network map and doorman are running on the same endpoint. Only one, * compatibilityZoneURL or networkServices, can be set at any one time. + * @property csrToken Optional token to provide alongside the certificate signing request (CSR) as part of the HTTP header during + * node registration. */ data class NetworkServicesConfig( val doormanURL: URL, val networkMapURL: URL, val pnm: UUID? = null, - val inferred: Boolean = false + val inferred: Boolean = false, + val csrToken: String? = null ) /** diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt index 62c90766da..6e516a6bad 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt @@ -149,9 +149,10 @@ internal object NetworkServicesConfigSpec : Configuration.Specification { - return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred])) + return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred], configuration[csrToken])) } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt index 5de12f20c4..41ea0edd25 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt @@ -22,12 +22,13 @@ import javax.naming.ServiceUnavailableException class HTTPNetworkRegistrationService( val config : NetworkServicesConfig, - val versionInfo: VersionInfo + val versionInfo: VersionInfo, + private val registrationURL: URL = URL("${config.doormanURL}/certificate") ) : NetworkRegistrationService { - private val registrationURL = URL("${config.doormanURL}/certificate") companion object { private val TRANSIENT_ERROR_STATUS_CODES = setOf(HTTP_BAD_GATEWAY, HTTP_UNAVAILABLE, HTTP_GATEWAY_TIMEOUT) + private const val CENM_SUBMISSION_TOKEN = "X-CENM-Submission-Token" } @Throws(CertificateRequestException::class) @@ -59,7 +60,8 @@ class HTTPNetworkRegistrationService( return String(registrationURL.post(OpaqueBytes(request.encoded), "Platform-Version" to "${versionInfo.platformVersion}", "Client-Version" to versionInfo.releaseVersion, - "Private-Network-Map" to (config.pnm?.toString() ?: ""))) + "Private-Network-Map" to (config.pnm?.toString() ?: ""), + *(config.csrToken?.let { arrayOf(CENM_SUBMISSION_TOKEN to it) } ?: arrayOf()))) } } diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index 266726f39b..862c6d6aba 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -263,6 +263,18 @@ class NodeConfigurationImplTest { assertEquals(JmxReporterType.JOLOKIA.toString(), nodeConfig.jmxReporterType.toString()) } + @Test + fun `network services`() { + val rawConfig = getConfig("test-config-with-networkservices.conf") + val nodeConfig = rawConfig.parseAsNodeConfiguration().value() + nodeConfig.networkServices!!.apply { + assertEquals("https://registration.example.com", doormanURL.toString()) + assertEquals("https://cz.example.com", networkMapURL.toString()) + assertEquals("3c23d1a1-aa63-4beb-af9f-c8579dd5f89c", pnm.toString()) + assertEquals("my-TOKEN", csrToken) + } + } + private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfigurationImpl { return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) } diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationServiceTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationServiceTest.kt new file mode 100644 index 0000000000..e1710283d8 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationServiceTest.kt @@ -0,0 +1,73 @@ +package net.corda.node.utilities.registration + +import com.nhaarman.mockito_kotlin.anyOrNull +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever +import net.corda.node.VersionInfo +import net.corda.node.services.config.NetworkServicesConfig +import net.corda.testing.internal.rigorousMock +import org.bouncycastle.pkcs.PKCS10CertificationRequest +import org.junit.Test +import java.io.InputStream +import java.io.OutputStream +import java.net.HttpURLConnection +import java.net.URL +import java.util.* +import kotlin.test.assertEquals + +class HTTPNetworkRegistrationServiceTest { + + @Test + fun `post request properties`() { + val versionInfo = VersionInfo.UNKNOWN + val pnm = UUID.randomUUID(); + val config = rigorousMock().also { + doReturn(pnm).whenever(it).pnm + doReturn(null).whenever(it).csrToken + } + var header = submitDummyRequest(versionInfo, config).requestProperties + assertEquals(4, header.size) + assertEquals(listOf(pnm.toString()), header["Private-Network-Map"]) + assertEquals(listOf(versionInfo.platformVersion.toString()), header["Platform-Version"]) + assertEquals(listOf(versionInfo.releaseVersion), header["Client-Version"]) + assertEquals(listOf("application/octet-stream"), header["Content-Type"]) + } + + @Test + fun `post request properties with CSR token`() { + val versionInfo = VersionInfo.UNKNOWN + val config = rigorousMock().also { + doReturn(null).whenever(it).pnm + doReturn("My-TOKEN").whenever(it).csrToken + } + var header = submitDummyRequest(versionInfo, config).requestProperties + assertEquals(5, header.size) + assertEquals(listOf(""), header["Private-Network-Map"]) + assertEquals(listOf(versionInfo.platformVersion.toString()), header["Platform-Version"]) + assertEquals(listOf(versionInfo.releaseVersion), header["Client-Version"]) + assertEquals(listOf("application/octet-stream"), header["Content-Type"]) + assertEquals(listOf("My-TOKEN"), header["X-CENM-Submission-Token"]) + } + + private fun submitDummyRequest(versionInfo: VersionInfo, config: NetworkServicesConfig) : HttpURLConnection { + val request = rigorousMock().also { + doReturn("dummy".toByteArray()).whenever(it).encoded + } + val inputStream = rigorousMock().also { + doReturn(-1).whenever(it).read() + } + val connection = rigorousMock().also { + doReturn(inputStream).whenever(it).inputStream + doReturn(mock()).whenever(it).outputStream + doReturn(HttpURLConnection.HTTP_OK).whenever(it).responseCode + } + val url = rigorousMock().also { + doReturn(connection).whenever(it).openConnection() + doReturn(connection).whenever(it).openConnection(anyOrNull()) + } + val service = HTTPNetworkRegistrationService(config, versionInfo, url) + service.submitRequest(request) + return connection + } +} diff --git a/node/src/test/resources/test-config-with-networkservices.conf b/node/src/test/resources/test-config-with-networkservices.conf new file mode 100644 index 0000000000..cf146918b9 --- /dev/null +++ b/node/src/test/resources/test-config-with-networkservices.conf @@ -0,0 +1,13 @@ +myLegalName = "O=Bank A,L=London,C=GB" +p2pAddress = "my-corda-node:10002" +rpcSettings { + address = "my-corda-node:10003" + adminAddress = "my-corda-node:10004" +} +devMode = false +networkServices { + doormanURL = "https://registration.example.com" + networkMapURL = "https://cz.example.com" + pnm = "3c23d1a1-aa63-4beb-af9f-c8579dd5f89c" + csrToken = "my-TOKEN" +}