mirror of
https://github.com/corda/corda.git
synced 2025-02-07 11:30:22 +00:00
Dynamic registration of RPC users security roles in ArtemisMessagingS… (#2140)
Dynamic registration of RPC users security roles in ArtemisMessagingServer [ENT-1000]
This commit is contained in:
parent
286ff65e60
commit
0db7dce985
@ -43,7 +43,9 @@ import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
|||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.*
|
import org.apache.activemq.artemis.core.remoting.impl.netty.*
|
||||||
import org.apache.activemq.artemis.core.security.Role
|
import org.apache.activemq.artemis.core.security.Role
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||||
|
import org.apache.activemq.artemis.core.server.SecuritySettingPlugin
|
||||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
||||||
|
import org.apache.activemq.artemis.core.settings.HierarchicalRepository
|
||||||
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
|
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
|
||||||
import org.apache.activemq.artemis.core.settings.impl.AddressSettings
|
import org.apache.activemq.artemis.core.settings.impl.AddressSettings
|
||||||
import org.apache.activemq.artemis.spi.core.remoting.*
|
import org.apache.activemq.artemis.spi.core.remoting.*
|
||||||
@ -139,8 +141,8 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
// Artemis IO errors
|
// Artemis IO errors
|
||||||
@Throws(IOException::class, KeyStoreException::class)
|
@Throws(IOException::class, KeyStoreException::class)
|
||||||
private fun configureAndStartServer() {
|
private fun configureAndStartServer() {
|
||||||
val artemisConfig = createArtemisConfig()
|
val (artemisConfig, securityPlugin) = createArtemisConfig()
|
||||||
val securityManager = createArtemisSecurityManager()
|
val securityManager = createArtemisSecurityManager(securityPlugin)
|
||||||
activeMQServer = ActiveMQServerImpl(artemisConfig, securityManager).apply {
|
activeMQServer = ActiveMQServerImpl(artemisConfig, securityManager).apply {
|
||||||
// Throw any exceptions which are detected during startup
|
// Throw any exceptions which are detected during startup
|
||||||
registerActivationFailureListener { exception -> throw exception }
|
registerActivationFailureListener { exception -> throw exception }
|
||||||
@ -156,7 +158,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createArtemisConfig(): Configuration = ConfigurationImpl().apply {
|
private fun createArtemisConfig() = ConfigurationImpl().apply {
|
||||||
val artemisDir = config.baseDirectory / "artemis"
|
val artemisDir = config.baseDirectory / "artemis"
|
||||||
bindingsDirectory = (artemisDir / "bindings").toString()
|
bindingsDirectory = (artemisDir / "bindings").toString()
|
||||||
journalDirectory = (artemisDir / "journal").toString()
|
journalDirectory = (artemisDir / "journal").toString()
|
||||||
@ -208,8 +210,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
addressFullMessagePolicy = AddressFullMessagePolicy.FAIL
|
addressFullMessagePolicy = AddressFullMessagePolicy.FAIL
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
configureAddressSecurity()
|
}.configureAddressSecurity()
|
||||||
}
|
|
||||||
|
|
||||||
private fun queueConfig(name: String, address: String = name, filter: String? = null, durable: Boolean): CoreQueueConfiguration {
|
private fun queueConfig(name: String, address: String = name, filter: String? = null, durable: Boolean): CoreQueueConfiguration {
|
||||||
return CoreQueueConfiguration().apply {
|
return CoreQueueConfiguration().apply {
|
||||||
@ -227,7 +228,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
* 3. RPC users. These are only given sufficient access to perform RPC with us.
|
* 3. RPC users. These are only given sufficient access to perform RPC with us.
|
||||||
* 4. Verifiers. These are given read access to the verification request queue and write access to the response queue.
|
* 4. Verifiers. These are given read access to the verification request queue and write access to the response queue.
|
||||||
*/
|
*/
|
||||||
private fun ConfigurationImpl.configureAddressSecurity() {
|
private fun ConfigurationImpl.configureAddressSecurity() : Pair<Configuration, LoginListener> {
|
||||||
val nodeInternalRole = Role(NODE_ROLE, true, true, true, true, true, true, true, true)
|
val nodeInternalRole = Role(NODE_ROLE, true, true, true, true, true, true, true, true)
|
||||||
securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node
|
securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node
|
||||||
securityRoles[P2P_QUEUE] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true))
|
securityRoles[P2P_QUEUE] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true))
|
||||||
@ -236,13 +237,22 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$NODE_USER.#"] = setOf(nodeInternalRole)
|
securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$NODE_USER.#"] = setOf(nodeInternalRole)
|
||||||
// Each RPC user must have its own role and its own queue. This prevents users accessing each other's queues
|
// Each RPC user must have its own role and its own queue. This prevents users accessing each other's queues
|
||||||
// and stealing RPC responses.
|
// and stealing RPC responses.
|
||||||
for ((username) in userService.users) {
|
val rolesAdderOnLogin = RolesAdderOnLogin { username ->
|
||||||
securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#"] = setOf(
|
Pair(
|
||||||
|
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#",
|
||||||
|
setOf(
|
||||||
nodeInternalRole,
|
nodeInternalRole,
|
||||||
restrictedRole("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username", consume = true, createNonDurableQueue = true, deleteNonDurableQueue = true))
|
restrictedRole(
|
||||||
|
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username",
|
||||||
|
consume = true,
|
||||||
|
createNonDurableQueue = true,
|
||||||
|
deleteNonDurableQueue = true)))
|
||||||
}
|
}
|
||||||
|
securitySettingPlugins.add(rolesAdderOnLogin)
|
||||||
securityRoles[VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(VERIFIER_ROLE, consume = true))
|
securityRoles[VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(VERIFIER_ROLE, consume = true))
|
||||||
securityRoles["${VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX}.#"] = setOf(nodeInternalRole, restrictedRole(VERIFIER_ROLE, send = true))
|
securityRoles["${VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX}.#"] = setOf(nodeInternalRole, restrictedRole(VERIFIER_ROLE, send = true))
|
||||||
|
val onLoginListener = { username: String -> rolesAdderOnLogin.onLogin(username) }
|
||||||
|
return Pair(this, onLoginListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restrictedRole(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false,
|
private fun restrictedRole(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false,
|
||||||
@ -253,7 +263,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, KeyStoreException::class)
|
@Throws(IOException::class, KeyStoreException::class)
|
||||||
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
|
private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager {
|
||||||
val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||||
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||||
|
|
||||||
@ -270,6 +280,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
// Override to make it work with our login module
|
// Override to make it work with our login module
|
||||||
override fun getAppConfigurationEntry(name: String): Array<AppConfigurationEntry> {
|
override fun getAppConfigurationEntry(name: String): Array<AppConfigurationEntry> {
|
||||||
val options = mapOf(
|
val options = mapOf(
|
||||||
|
LoginListener::javaClass.name to loginListener,
|
||||||
RPCUserService::class.java.name to userService,
|
RPCUserService::class.java.name to userService,
|
||||||
NodeLoginModule.CERT_CHAIN_CHECKS_OPTION_NAME to certChecks)
|
NodeLoginModule.CERT_CHAIN_CHECKS_OPTION_NAME to certChecks)
|
||||||
return arrayOf(AppConfigurationEntry(name, REQUIRED, options))
|
return arrayOf(AppConfigurationEntry(name, REQUIRED, options))
|
||||||
@ -546,6 +557,7 @@ class NodeLoginModule : LoginModule {
|
|||||||
private lateinit var subject: Subject
|
private lateinit var subject: Subject
|
||||||
private lateinit var callbackHandler: CallbackHandler
|
private lateinit var callbackHandler: CallbackHandler
|
||||||
private lateinit var userService: RPCUserService
|
private lateinit var userService: RPCUserService
|
||||||
|
private lateinit var loginListener: LoginListener
|
||||||
private lateinit var peerCertCheck: CertificateChainCheckPolicy.Check
|
private lateinit var peerCertCheck: CertificateChainCheckPolicy.Check
|
||||||
private lateinit var nodeCertCheck: CertificateChainCheckPolicy.Check
|
private lateinit var nodeCertCheck: CertificateChainCheckPolicy.Check
|
||||||
private lateinit var verifierCertCheck: CertificateChainCheckPolicy.Check
|
private lateinit var verifierCertCheck: CertificateChainCheckPolicy.Check
|
||||||
@ -555,6 +567,7 @@ class NodeLoginModule : LoginModule {
|
|||||||
this.subject = subject
|
this.subject = subject
|
||||||
this.callbackHandler = callbackHandler
|
this.callbackHandler = callbackHandler
|
||||||
userService = options[RPCUserService::class.java.name] as RPCUserService
|
userService = options[RPCUserService::class.java.name] as RPCUserService
|
||||||
|
loginListener = options[LoginListener::javaClass.name] as LoginListener
|
||||||
val certChainChecks: Map<String, CertificateChainCheckPolicy.Check> = uncheckedCast(options[CERT_CHAIN_CHECKS_OPTION_NAME])
|
val certChainChecks: Map<String, CertificateChainCheckPolicy.Check> = uncheckedCast(options[CERT_CHAIN_CHECKS_OPTION_NAME])
|
||||||
peerCertCheck = certChainChecks[PEER_ROLE]!!
|
peerCertCheck = certChainChecks[PEER_ROLE]!!
|
||||||
nodeCertCheck = certChainChecks[NODE_ROLE]!!
|
nodeCertCheck = certChainChecks[NODE_ROLE]!!
|
||||||
@ -622,6 +635,7 @@ class NodeLoginModule : LoginModule {
|
|||||||
// TODO Retrieve client IP address to include in exception message
|
// TODO Retrieve client IP address to include in exception message
|
||||||
throw FailedLoginException("Password for user $username does not match")
|
throw FailedLoginException("Password for user $username does not match")
|
||||||
}
|
}
|
||||||
|
loginListener(username)
|
||||||
principals += RolePrincipal(RPC_ROLE) // This enables the RPC client to send requests
|
principals += RolePrincipal(RPC_ROLE) // This enables the RPC client to send requests
|
||||||
principals += RolePrincipal("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username") // This enables the RPC client to receive responses
|
principals += RolePrincipal("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username") // This enables the RPC client to receive responses
|
||||||
return username
|
return username
|
||||||
@ -676,3 +690,40 @@ class NodeLoginModule : LoginModule {
|
|||||||
loginSucceeded = false
|
loginSucceeded = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typealias LoginListener = (String) -> Unit
|
||||||
|
typealias RolesRepository = HierarchicalRepository<MutableSet<Role>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to dynamically assign security roles to RPC users
|
||||||
|
* on their authentication. This object is plugged into the server
|
||||||
|
* as [SecuritySettingPlugin]. It responds to authentication events
|
||||||
|
* from [NodeLoginModule] by adding the address -> roles association
|
||||||
|
* generated by the given [source], unless already done before.
|
||||||
|
*/
|
||||||
|
private class RolesAdderOnLogin(val source: (String) -> Pair<String, Set<Role>>)
|
||||||
|
: SecuritySettingPlugin {
|
||||||
|
|
||||||
|
// Artemis internal container storing roles association
|
||||||
|
private lateinit var repository: RolesRepository
|
||||||
|
|
||||||
|
fun onLogin(username: String) {
|
||||||
|
val (address, roles) = source(username)
|
||||||
|
val entry = repository.getMatch(address)
|
||||||
|
if (entry == null || entry.isEmpty()) {
|
||||||
|
repository.addMatch(address, roles.toMutableSet())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializer called by the Artemis framework
|
||||||
|
override fun setSecurityRepository(repository: RolesRepository) {
|
||||||
|
this.repository = repository
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part of SecuritySettingPlugin interface which is no-op in this case
|
||||||
|
override fun stop() = this
|
||||||
|
|
||||||
|
override fun init(options: MutableMap<String, String>?) = this
|
||||||
|
|
||||||
|
override fun getSecurityRoles() = null
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user