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"
+}