Configurable authorization/authentication data sources [CORDA-827] (#2145)

* Add support for external data source of access control data (RPC/Shell users credential and permissions), with optional in-memory caching.
* Support password encoded with Apache Shiro fully reversible Modular Crypt Format.
* Introduce 'security' field in Node configuration and related docsite page.
This commit is contained in:
igor nitto
2017-12-11 08:39:09 +00:00
committed by GitHub
parent 991c59e753
commit da38e6f673
33 changed files with 1235 additions and 212 deletions

View File

@ -63,8 +63,8 @@ class RPCStabilityTests {
val executor = Executors.newScheduledThreadPool(1)
fun startAndStop() {
rpcDriver {
val server = startRpcServer<RPCOps>(ops = DummyOps)
startRpcClient<RPCOps>(server.get().broker.hostAndPort!!).get()
val server = startRpcServer<RPCOps>(ops = DummyOps).get()
startRpcClient<RPCOps>(server.broker.hostAndPort!!).get()
}
}
repeat(5) {

View File

@ -22,7 +22,10 @@ import net.corda.core.internal.ThreadBox
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.serialize
import net.corda.core.utilities.*
import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
import net.corda.nodeapi.ArtemisConsumer
import net.corda.nodeapi.ArtemisProducer
import net.corda.nodeapi.RPCApi

View File

@ -1,8 +1,6 @@
package net.corda.client.rpc
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.messaging.rpcContext
import net.corda.nodeapi.internal.config.User
import net.corda.testing.internal.RPCDriverDSL
@ -10,15 +8,12 @@ import net.corda.testing.internal.rpcDriver
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import kotlin.reflect.KVisibility
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.test.assertFailsWith
@RunWith(Parameterized::class)
class RPCPermissionsTests : AbstractRPCTest() {
companion object {
const val DUMMY_FLOW = "StartFlow.net.corda.flows.DummyFlow"
const val OTHER_FLOW = "StartFlow.net.corda.flows.OtherFlow"
const val ALL_ALLOWED = "ALL"
}
@ -26,12 +21,21 @@ class RPCPermissionsTests : AbstractRPCTest() {
* RPC operation.
*/
interface TestOps : RPCOps {
fun validatePermission(str: String)
fun validatePermission(method: String, target: String? = null)
}
class TestOpsImpl : TestOps {
override val protocolVersion = 1
override fun validatePermission(str: String) { rpcContext().requirePermission(str) }
override fun validatePermission(method: String, target: String?) {
val authorized = if (target == null) {
rpcContext().isPermitted(method)
} else {
rpcContext().isPermitted(method, target)
}
if (!authorized) {
throw PermissionException("RPC user not authorized")
}
}
}
/**
@ -46,9 +50,9 @@ class RPCPermissionsTests : AbstractRPCTest() {
rpcDriver {
val emptyUser = userOf("empty", emptySet())
val proxy = testProxyFor(emptyUser)
assertFailsWith(PermissionException::class,
"User ${emptyUser.username} should not be allowed to use $DUMMY_FLOW.",
{ proxy.validatePermission(DUMMY_FLOW) })
assertNotAllowed {
proxy.validatePermission("startFlowDynamic", "net.corda.flows.DummyFlow")
}
}
}
@ -57,7 +61,8 @@ class RPCPermissionsTests : AbstractRPCTest() {
rpcDriver {
val adminUser = userOf("admin", setOf(ALL_ALLOWED))
val proxy = testProxyFor(adminUser)
proxy.validatePermission(DUMMY_FLOW)
proxy.validatePermission("startFlowDynamic", "net.corda.flows.DummyFlow")
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.DummyFlow")
}
}
@ -66,7 +71,8 @@ class RPCPermissionsTests : AbstractRPCTest() {
rpcDriver {
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
val proxy = testProxyFor(joeUser)
proxy.validatePermission(DUMMY_FLOW)
proxy.validatePermission("startFlowDynamic", "net.corda.flows.DummyFlow")
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.DummyFlow")
}
}
@ -75,36 +81,46 @@ class RPCPermissionsTests : AbstractRPCTest() {
rpcDriver {
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
val proxy = testProxyFor(joeUser)
assertFailsWith(PermissionException::class,
"User ${joeUser.username} should not be allowed to use $OTHER_FLOW",
{ proxy.validatePermission(OTHER_FLOW) })
}
}
@Test
fun `check ALL is implemented the correct way round`() {
rpcDriver {
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
val proxy = testProxyFor(joeUser)
assertFailsWith(PermissionException::class,
"Permission $ALL_ALLOWED should not do anything for User ${joeUser.username}",
{ proxy.validatePermission(ALL_ALLOWED) })
}
}
@Test
fun `fine grained permissions are enforced`() {
val allPermissions = CordaRPCOps::class.declaredMemberFunctions.filter { it.visibility == KVisibility.PUBLIC }.map { invokeRpc(it) }
allPermissions.forEach { permission ->
rpcDriver {
val user = userOf("Mark", setOf(permission))
val proxy = testProxyFor(user)
proxy.validatePermission(permission)
(allPermissions - permission).forEach { notOwnedPermission ->
assertFailsWith(PermissionException::class, { proxy.validatePermission(notOwnedPermission) })
}
assertNotAllowed {
proxy.validatePermission("startFlowDynamic", "net.corda.flows.OtherFlow")
}
assertNotAllowed {
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.OtherFlow")
}
}
}
@Test
fun `joe user is not allowed to call other RPC methods`() {
rpcDriver {
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
val proxy = testProxyFor(joeUser)
assertNotAllowed {
proxy.validatePermission("nodeInfo")
}
assertNotAllowed {
proxy.validatePermission("networkMapFeed")
}
}
}
@Test
fun `checking invokeRpc permissions entitlements`() {
rpcDriver {
val joeUser = userOf("joe", setOf("InvokeRpc.networkMapFeed"))
val proxy = testProxyFor(joeUser)
assertNotAllowed {
proxy.validatePermission("nodeInfo")
}
assertNotAllowed {
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.OtherFlow")
}
proxy.validatePermission("networkMapFeed")
}
}
private fun assertNotAllowed(action: () -> Unit) {
assertFailsWith(PermissionException::class, "User should not be allowed to perform this action.", action)
}
}