ENT-1226 Check minimum platform version when submitting node info (#300)

* Adding min platform version check when submitting node info

* Return error message to developer

* Fixing integration test

* Added todo to move checks out of data layer

* Added extra logging in nodeinfowebservice and use nodeinfo.verified instead of deserialize

* Tidy up tests

* Cache network parameters

* Add NodeInfoWithSigned class to stop calling to only verify node data once

* Fixing review comments

* Return correct response code if doorman not initialised properly

* Fix merge conflict
This commit is contained in:
Anthony Keenan
2018-01-09 13:31:26 +00:00
committed by GitHub
parent 2832eb489b
commit a65db712c7
8 changed files with 118 additions and 44 deletions

View File

@ -148,7 +148,7 @@ class PersistentNetworkMapStorageTest : TestBase() {
assertThat(validNodeInfoHash).containsOnly(nodeInfoHashA, nodeInfoHashB)
}
private fun createValidSignedNodeInfo(organisation: String): SignedNodeInfo {
private fun createValidSignedNodeInfo(organisation: String): NodeInfoWithSigned {
val nodeInfoBuilder = TestNodeInfoBuilder()
val requestId = requestStorage.saveRequest(createRequest(organisation).first)
requestStorage.markRequestTicketCreated(requestId)
@ -156,6 +156,6 @@ class PersistentNetworkMapStorageTest : TestBase() {
val (identity) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB"))
val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1))
requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList())
return nodeInfoBuilder.buildWithSigned().second
return NodeInfoWithSigned(nodeInfoBuilder.buildWithSigned().second)
}
}

View File

@ -28,7 +28,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
class PersitenceNodeInfoStorageTest : TestBase() {
class PersistentNodeInfoStorageTest : TestBase() {
private lateinit var requestStorage: CertificationRequestStorage
private lateinit var nodeInfoStorage: PersistentNodeInfoStorage
private lateinit var persistence: CordaPersistence
@ -85,34 +85,34 @@ class PersitenceNodeInfoStorageTest : TestBase() {
@Test
fun `getNodeInfo returns persisted SignedNodeInfo using the hash of just the NodeInfo`() {
// given
val (nodeInfoA, signedNodeInfoA) = createValidSignedNodeInfo("TestA")
val (nodeInfoB, signedNodeInfoB) = createValidSignedNodeInfo("TestB")
val (nodeA) = createValidSignedNodeInfo("TestA")
val (nodeB) = createValidSignedNodeInfo("TestB")
// Put signed node info data
nodeInfoStorage.putNodeInfo(signedNodeInfoA)
nodeInfoStorage.putNodeInfo(signedNodeInfoB)
nodeInfoStorage.putNodeInfo(nodeA)
nodeInfoStorage.putNodeInfo(nodeB)
// when
val persistedSignedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeInfoA.serialize().hash)
val persistedSignedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeInfoB.serialize().hash)
val persistedSignedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeA.nodeInfo.serialize().hash)
val persistedSignedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeB.nodeInfo.serialize().hash)
// then
assertEquals(persistedSignedNodeInfoA?.verified(), nodeInfoA)
assertEquals(persistedSignedNodeInfoB?.verified(), nodeInfoB)
assertEquals(persistedSignedNodeInfoA?.verified(), nodeA.nodeInfo)
assertEquals(persistedSignedNodeInfoB?.verified(), nodeB.nodeInfo)
}
@Test
fun `same public key with different node info`() {
// Create node info.
val (nodeInfo1, signedNodeInfo1, key) = createValidSignedNodeInfo("Test", serial = 1)
val nodeInfo2 = nodeInfo1.copy(serial = 2)
val signedNodeInfo2 = nodeInfo2.signWith(listOf(key))
val (node1, key) = createValidSignedNodeInfo("Test", serial = 1)
val nodeInfo2 = node1.nodeInfo.copy(serial = 2)
val node2 = NodeInfoWithSigned(nodeInfo2.signWith(listOf(key)))
val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(signedNodeInfo1)
assertEquals(nodeInfo1, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified())
val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(node1)
assertEquals(node1.nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified())
// This should replace the node info.
nodeInfoStorage.putNodeInfo(signedNodeInfo2)
nodeInfoStorage.putNodeInfo(node2)
// Old node info should be removed.
assertNull(nodeInfoStorage.getNodeInfo(nodeInfo1Hash))
@ -122,17 +122,17 @@ class PersitenceNodeInfoStorageTest : TestBase() {
@Test
fun `putNodeInfo persists SignedNodeInfo with its signature`() {
// given
val (_, signedNodeInfo) = createValidSignedNodeInfo("Test")
val (nodeInfoWithSigned) = createValidSignedNodeInfo("Test")
// when
val nodeInfoHash = nodeInfoStorage.putNodeInfo(signedNodeInfo)
val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfoWithSigned)
// then
val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash)
assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(signedNodeInfo.signatures)
assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signedNodeInfo.signatures)
}
private fun createValidSignedNodeInfo(organisation: String, serial: Long = 1): Triple<NodeInfo, SignedNodeInfo, PrivateKey> {
private fun createValidSignedNodeInfo(organisation: String, serial: Long = 1): Pair<NodeInfoWithSigned, PrivateKey> {
val nodeInfoBuilder = TestNodeInfoBuilder()
val requestId = requestStorage.saveRequest(createRequest(organisation).first)
requestStorage.markRequestTicketCreated(requestId)
@ -140,7 +140,7 @@ class PersitenceNodeInfoStorageTest : TestBase() {
val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB"))
val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1))
requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList())
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(serial)
return Triple(nodeInfo, signedNodeInfo, key)
val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(serial)
return Pair(NodeInfoWithSigned(signedNodeInfo), key)
}
}

View File

@ -25,13 +25,15 @@ import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.createNodeInfoAndSigned
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.FileNotFoundException
import java.io.IOException
import java.net.URL
import java.nio.charset.Charset
import java.security.cert.X509Certificate
import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals
@ -44,7 +46,7 @@ class NodeInfoWebServiceTest {
private lateinit var rootCaCert: X509Certificate
private lateinit var intermediateCa: CertificateAndKeyPair
private val testNetworkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis())
private val testNetworkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis())
@Before
fun init() {
@ -55,10 +57,13 @@ class NodeInfoWebServiceTest {
@Test
fun `submit nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkParameters() }.thenReturn(testNetworkParameters(emptyList()))
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), mock(), testNetworkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
// Post node info and signature to doorman, this should pass without any exception.
@ -66,6 +71,38 @@ class NodeInfoWebServiceTest {
}
}
@Test
fun `submit old nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkParameters() }.thenReturn(testNetworkParameters(emptyList(), minimumPlatformVersion = 2))
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
.hasMessageStartingWith("Response Code 400: Minimum platform version is 2")
}
}
@Test
fun `submit nodeInfo when no network parameters`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkParameters() }.thenReturn(null)
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
.hasMessageStartingWith("Response Code 503: Network parameters have not been initialised")
}
}
@Test
fun `get network map`() {
val networkMap = NetworkMap(listOf(randomSHA256(), randomSHA256()), randomSHA256())
@ -136,7 +173,10 @@ class NodeInfoWebServiceTest {
requestMethod = "POST"
setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM)
outputStream.write(payload)
inputStream.close() // This will give us a nice IOException if the response isn't HTTP 200
if (responseCode != 200) {
throw IOException("Response Code $responseCode: ${IOUtils.toString(errorStream, Charset.defaultCharset())}")
}
inputStream.close()
}
}