From 9da30b431f62b15702ae6c2c8b37d14867b2e64f Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Mon, 4 Mar 2019 11:01:08 +0000 Subject: [PATCH] CORDA-2554 - Bootstrapper - option to include contracts to whitelist from signed JARs (#4712) * NetworkBoostrapper can optionally whitelist contracts from signed jars based on include_whitelist.txt file. * refactoring, docs * logs * add ne parameters to the generateWhitelist method at the end * Addressing review comments. * CORDA-2577 disable non-downgrade rule - test fix and docs --- docs/source/network-bootstrapper.rst | 3 +- .../internal/network/NetworkBootstrapper.kt | 6 ++-- .../internal/network/WhitelistGenerator.kt | 33 ++++++++++++------- .../network/WhitelistGeneratorTest.kt | 4 ++- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/docs/source/network-bootstrapper.rst b/docs/source/network-bootstrapper.rst index 1fe3ea13e8..451d1af768 100644 --- a/docs/source/network-bootstrapper.rst +++ b/docs/source/network-bootstrapper.rst @@ -91,7 +91,8 @@ By default the Bootstrapper will whitelist all the contracts found in the unsign Whitelisted contracts are checked by `Zone constraints`, while contract classes from signed JARs will be checked by `Signature constraints`. To prevent certain contracts from unsigned JARs from being whitelisted, add their fully qualified class name in the ``exclude_whitelist.txt``. These will instead use the more restrictive ``HashAttachmentConstraint``. -Refer to :doc:`api-contract-constraints` to understand the implication of different constraint types before adding ``exclude_whitelist.txt`` files. +To add certain contracts from signed JARs to whitelist, add their fully qualified class name in the ``include_whitelist.txt``. +Refer to :doc:`api-contract-constraints` to understand the implication of different constraint types before adding ``exclude_whitelist.txt`` or ``include_whitelist.txt`` files. For example: diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index de95fcea8b..309ad7cae0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -247,8 +247,10 @@ internal constructor(private val initSerEnv: Boolean, println("Gathering notary identities") val notaryInfos = gatherNotaryInfos(nodeInfoFiles, configs) println("Generating contract implementations whitelist") - // Only add contracts to the whitelist from unsigned jars - val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.filter { !isSigned(it) }.map(contractsJarConverter)) + val signedJars = cordappJars.filter { isSigned(it) } // signed JARs are excluded by default, optionally include them in order to transition states from CZ whitelist to signature constraint + val unsignedJars = cordappJars - signedJars + val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), unsignedJars.map(contractsJarConverter), + readIncludeWhitelist(directory), signedJars.map(contractsJarConverter)) val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs, networkParametersOverrides) if (newNetParams != existingNetParams) { println("${if (existingNetParams == null) "New" else "Updated"} $newNetParams") diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/WhitelistGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/WhitelistGenerator.kt index 9979337386..ef76b11d52 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/WhitelistGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/WhitelistGenerator.kt @@ -1,10 +1,7 @@ package net.corda.nodeapi.internal.network import net.corda.core.contracts.ContractClassName -import net.corda.core.internal.div -import net.corda.core.internal.exists -import net.corda.core.internal.readAllLines -import net.corda.core.internal.toMultiMap +import net.corda.core.internal.* import net.corda.core.node.NetworkParameters import net.corda.core.node.services.AttachmentId import net.corda.nodeapi.internal.ContractsJar @@ -12,15 +9,18 @@ import org.slf4j.LoggerFactory import java.nio.file.Path private const val EXCLUDE_WHITELIST_FILE_NAME = "exclude_whitelist.txt" +private const val INCLUDE_WHITELIST_FILE_NAME = "include_whitelist.txt" private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.network.WhitelistGenerator") fun generateWhitelist(networkParameters: NetworkParameters?, excludeContracts: List, - cordappJars: List): Map> { + cordappJars: List, + includeContracts: List, + optionalCordappJars: List): Map> { val existingWhitelist = networkParameters?.whitelistedContractImplementations ?: emptyMap() if (excludeContracts.isNotEmpty()) { - logger.info("Exclude contracts from whitelist: ${excludeContracts.joinToString()}") + logger.info("Exclude contracts from $EXCLUDE_WHITELIST_FILE_NAME: ${excludeContracts.joinToString()}") existingWhitelist.keys.forEach { require(it !in excludeContracts) { "$it is already part of the existing whitelist and cannot be excluded." } } @@ -30,14 +30,23 @@ fun generateWhitelist(networkParameters: NetworkParameters?, .flatMap { jar -> (jar.scan() - excludeContracts).map { it to jar.hash } } .toMultiMap() - return (newWhiteList.keys + existingWhitelist.keys).associateBy({ it }) { + if (includeContracts.isNotEmpty()) + logger.info("Include contracts from $INCLUDE_WHITELIST_FILE_NAME: ${includeContracts.joinToString()} present in JARs: $optionalCordappJars.") + + val newSignedJarsWhiteList = optionalCordappJars + .flatMap { jar -> (jar.scan()).filter { includeContracts.contains(it) }.map { it to jar.hash } } + .toMultiMap() + + return (newWhiteList.keys + existingWhitelist.keys + newSignedJarsWhiteList.keys).associateBy({ it }) { val existingHashes = existingWhitelist[it] ?: emptyList() val newHashes = newWhiteList[it] ?: emptyList() - (existingHashes + newHashes).distinct() + val newHashesFormSignedJar = newSignedJarsWhiteList[it] ?: emptyList() + (existingHashes + newHashes + newHashesFormSignedJar).distinct() } } -fun readExcludeWhitelist(directory: Path): List { - val file = directory / EXCLUDE_WHITELIST_FILE_NAME - return if (file.exists()) file.readAllLines().map(String::trim) else emptyList() -} \ No newline at end of file +fun readExcludeWhitelist(directory: Path): List = readAllLines(directory / EXCLUDE_WHITELIST_FILE_NAME) + +fun readIncludeWhitelist(directory: Path): List = readAllLines(directory / INCLUDE_WHITELIST_FILE_NAME) + +private fun readAllLines(path: Path) : List = if (path.exists()) path.readAllLines().map(String::trim) else emptyList() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt index 4bf6960c85..2bfb9c68b4 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt @@ -129,7 +129,9 @@ class WhitelistGeneratorTest { return generateWhitelist( testNetworkParameters(whitelistedContractImplementations = existingWhitelist), excludeContracts, - contractJars + contractJars, + emptyList(), + emptyList() ) } }