CORDA-3452: Node: Configure the input of custom string in CSR (#5844)

* CORDA-3452: Node: Configure the input of custom string in CSR to be used by Identity Service

* CORDA-3452: Remove unused import

* CORDA-3452: Add test for networkServices configuration
This commit is contained in:
Denis Rekalov 2020-01-13 09:52:51 +00:00 committed by Matthew Nesbit
parent ce774e459a
commit 73a0782f5d
8 changed files with 120 additions and 6 deletions

View File

@ -1693,7 +1693,7 @@
<ID>MaxLineLength:ConfigSections.kt$DatabaseConfigSpec$private val initialiseAppSchema by enum(SchemaInitializationType::class).optional().withDefaultValue(DatabaseConfig.Defaults.initialiseAppSchema)</ID>
<ID>MaxLineLength:ConfigSections.kt$DatabaseConfigSpec$private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel)</ID>
<ID>MaxLineLength:ConfigSections.kt$DatabaseConfigSpec$return valid(DatabaseConfig(configuration[initialiseSchema], configuration[initialiseAppSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize]))</ID>
<ID>MaxLineLength:ConfigSections.kt$NetworkServicesConfigSpec$return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred]))</ID>
<ID>MaxLineLength:ConfigSections.kt$NetworkServicesConfigSpec$return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred], configuration[csrToken]))</ID>
<ID>MaxLineLength:ConfigSections.kt$NodeRpcSettingsSpec$return valid(NodeRpcSettings(configuration[address], configuration[adminAddress], configuration[standAloneBroker], configuration[useSsl], configuration[ssl]))</ID>
<ID>MaxLineLength:ConfigSections.kt$NotaryConfigSpec$return valid(NotaryConfig(configuration[validating], configuration[serviceLegalName], configuration[className], configuration[etaMessageThresholdSeconds], configuration[extraConfig], configuration[raft], configuration[bftSMaRt]))</ID>
<ID>MaxLineLength:ConfigSections.kt$SSHDConfigurationSpec$override fun parseValid(configuration: Config): Valid&lt;SSHDConfiguration&gt;</ID>
@ -3808,6 +3808,7 @@
<ID>SpreadOperator:FxTransactionBuildTutorial.kt$ForeignExchangeFlow$(*ourOutputState.map { StateAndContract(it, Cash.PROGRAM_ID) }.toTypedArray())</ID>
<ID>SpreadOperator:FxTransactionBuildTutorial.kt$ForeignExchangeFlow$(*theirInputStates.toTypedArray())</ID>
<ID>SpreadOperator:FxTransactionBuildTutorial.kt$ForeignExchangeFlow$(*theirOutputState.map { StateAndContract(it, Cash.PROGRAM_ID) }.toTypedArray())</ID>
<ID>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()))</ID>
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*leftPredicates.toTypedArray())</ID>
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())</ID>
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*rightPredicates.toTypedArray())</ID>

View File

@ -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

View File

@ -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
)
/**

View File

@ -149,9 +149,10 @@ internal object NetworkServicesConfigSpec : Configuration.Specification<NetworkS
private val networkMapURL by string().mapValid(::toURL)
private val pnm by string().mapValid(::toUUID).optional()
private val inferred by boolean().optional().withDefaultValue(false)
private val csrToken by string(sensitive = true).optional()
override fun parseValid(configuration: Config): Valid<NetworkServicesConfig> {
return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred]))
return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred], configuration[csrToken]))
}
}

View File

@ -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())))
}
}

View File

@ -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)
}

View File

@ -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<NetworkServicesConfig>().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<NetworkServicesConfig>().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<PKCS10CertificationRequest>().also {
doReturn("dummy".toByteArray()).whenever(it).encoded
}
val inputStream = rigorousMock<InputStream>().also {
doReturn(-1).whenever(it).read()
}
val connection = rigorousMock<HttpURLConnection>().also {
doReturn(inputStream).whenever(it).inputStream
doReturn(mock<OutputStream>()).whenever(it).outputStream
doReturn(HttpURLConnection.HTTP_OK).whenever(it).responseCode
}
val url = rigorousMock<URL>().also {
doReturn(connection).whenever(it).openConnection()
doReturn(connection).whenever(it).openConnection(anyOrNull())
}
val service = HTTPNetworkRegistrationService(config, versionInfo, url)
service.submitRequest(request)
return connection
}
}

View File

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