Skip to content
Permalink
Browse files

Integratin the blended login module into the authenticating broker.

  • Loading branch information...
atooni committed Sep 10, 2019
1 parent 08db9ce commit 559bef328dcb74ff3cbe20a2d86ee99ffc088493
@@ -5,7 +5,7 @@ import blended.jms.utils.{BlendedJMSConnectionConfig, ConnectionConfig}
import blended.util.config.Implicits._
import com.typesafe.config.Config
import org.apache.activemq.ActiveMQConnectionFactory

import scala.collection.JavaConverters._
import scala.concurrent.duration.FiniteDuration
import scala.util.Try

@@ -23,14 +23,16 @@ case class BrokerConfig(
override val minReconnect : FiniteDuration,
override val maxReconnectTimeout : Option[FiniteDuration],
override val properties : Map[String, String],
override val defaultUser : Option[String],
override val defaultPassword : Option[String],
brokerName : String,
file : String,
withSsl : Boolean,
withAuthentication : Boolean
withAuthentication : Boolean,
anonymousUser : Option[String],
anonymousGroups : List[String]
) extends ConnectionConfig {
override val enabled : Boolean = true
override val defaultUser : Option[String] = None
override val defaultPassword : Option[String] = None
override val useJndi : Boolean = false
override val cfEnabled : Option[ConnectionConfig => Boolean] = None
override val cfClassName : Option[String] = Some(classOf[ActiveMQConnectionFactory].getName())
@@ -59,6 +61,10 @@ object BrokerConfig {

val authenticate : Config => Boolean = cfg => cfg.getBoolean("withAuthentication", false)

val anonymous : Config => Option[String] = cfg => cfg.getStringOption("anonymousUser")

val anonymousGroups : Config => List[String] = cfg => cfg.getStringList("anonymousGroups", List.empty)

def create(brokerName : String, idSvc : ContainerIdentifierService, cfg : Config) : Try[BrokerConfig] = Try {

val resolve : String => Try[Any] = value => idSvc.resolvePropertyString(value)
@@ -71,6 +77,8 @@ object BrokerConfig {
BrokerConfig(
vendor = jmsConfig.vendor,
provider = jmsConfig.provider,
defaultUser = jmsConfig.defaultUser,
defaultPassword = jmsConfig.defaultPassword,
clientId = jmsConfig.clientId,
jmxEnabled = jmsConfig.jmxEnabled,
keepAliveEnabled = jmsConfig.keepAliveEnabled,
@@ -85,7 +93,9 @@ object BrokerConfig {
brokerName = name(resolve)(cfg).getOrElse(brokerName),
file = file(resolve)(cfg).getOrElse(s"$brokerName.amq"),
withSsl = ssl(cfg),
withAuthentication = authenticate(cfg)
withAuthentication = authenticate(cfg),
anonymousUser = anonymous(cfg),
anonymousGroups = anonymousGroups(cfg)
)
}
}
@@ -88,7 +88,7 @@ class BrokerControlActor(brokerCfg : BrokerConfig, cfg : OSGIActorConfig, sslCtx
private[this] var svcReg : Option[ServiceRegistration[_]] = None
private[this] val uuid = UUID.randomUUID().toString()

override def toString : String = s"BrokerControlActor(${brokerCfg})"
override def toString : String = s"BrokerControlActor($brokerCfg)"

private[this] def startBroker() : Unit = {

@@ -109,7 +109,7 @@ class BrokerControlActor(brokerCfg : BrokerConfig, cfg : OSGIActorConfig, sslCtx
broker = Some(b)

if (brokerCfg.withAuthentication) {
val plugins : List[BrokerPlugin] = new JaasAuthenticationPlugin() :: Option(b.getPlugins()).map(_.toList).getOrElse(List.empty)
val plugins : List[BrokerPlugin] = new JaasAuthenticationPlugin(brokerCfg) :: Option(b.getPlugins()).map(_.toList).getOrElse(List.empty)
b.setPlugins(plugins.toArray)
} else {
log.info(s"The broker [${brokerCfg.brokerName}] will start without authentication")
@@ -1,18 +1,27 @@
package blended.activemq.brokerstarter.internal

import java.security.Principal
import java.security.cert.X509Certificate
import java.util

import blended.security.PasswordCallbackHandler

import scala.collection.JavaConverters._
import blended.security.boot.GroupPrincipal
import blended.util.logging.Logger
import javax.security.auth.Subject
import javax.security.auth.login.LoginContext
import org.apache.activemq.broker.{Broker, ConnectionContext}
import org.apache.activemq.command.ConnectionInfo
import org.apache.activemq.security.{AbstractAuthenticationBroker, SecurityContext}

import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}

/**
* A broker that performs authentication against an arbitrary blended login module
*/
class JaasAuthenticatingBroker(parent : Broker) extends AbstractAuthenticationBroker(parent) {
class JaasAuthenticatingBroker(parent : Broker, brokerCfg : BrokerConfig) extends AbstractAuthenticationBroker(parent) {

private val log : Logger = Logger[JaasAuthenticatingBroker]

@@ -40,24 +49,38 @@ class JaasAuthenticatingBroker(parent : Broker) extends AbstractAuthenticationBr
context.setSecurityContext(null)
throw e
}

var securityContext = context.getSecurityContext
if (securityContext == null) {
securityContext = authenticate(info.getUserName, info.getPassword, null)
context.setSecurityContext(securityContext)
securityContexts.add(securityContext)
}
try super.addConnection(context, info)
catch {
case e: Exception =>
securityContexts.remove(securityContext)
context.setSecurityContext(null)
throw e
}
}

@throws[SecurityException]
override def authenticate(username: String, password: String, peerCertificates: Array[X509Certificate]): SecurityContext = {
throw new SecurityException("Jaas Authentication is not yet enabled")

(Option(username), Option(password)) match {
case (Some(u), _) =>
log.info(s"Trying to authenticate [$username] for broker [${getBrokerName()}]")
try {
val lc = new LoginContext("Test", new PasswordCallbackHandler(u, Option(password).map(_.toCharArray()).getOrElse(Array.empty)))
lc.login()
val subj : Subject = lc.getSubject()
val g : Set[Principal] = subj.getPrincipals().asScala.filter(_.isInstanceOf[GroupPrincipal]).toSet
new SecurityContext(u) {
override def getPrincipals: util.Set[Principal] = g.asJava
}
} catch {
case NonFatal(t) =>
log.warn(s"Error logging in [$u] to [${brokerCfg.brokerName}] : [${t.getMessage()}]")
throw new SecurityException(t)
}
case (None, _) =>
brokerCfg.anonymousUser match {
case Some(u) =>
log.info(s"Authenticating anonymous user name [$u] to broker [${getBrokerName()}] with groups [${brokerCfg.anonymousGroups}]")
val groups : Set[Principal] = brokerCfg.anonymousGroups.map(g => new GroupPrincipal(g)).toSet
new SecurityContext(u) {
override def getPrincipals: util.Set[Principal] = groups.asJava
}
case None =>
throw new SecurityException(s"Anonymous access to broker [${getBrokerName()}] is disabled")
}
}
}
}
@@ -6,12 +6,12 @@ import org.apache.activemq.broker.{Broker, BrokerPlugin}
/**
* An ActiveMQ plugin that allows authentication against an arbitrary blended login module.
*/
class JaasAuthenticationPlugin extends BrokerPlugin {
class JaasAuthenticationPlugin(brokerCfg : BrokerConfig) extends BrokerPlugin {

private val log : Logger = Logger[JaasAuthenticationPlugin]

override def installPlugin(broker: Broker): Broker = {
log.info(s"Activating blended JAAS authentication for Active MQ broker [${broker.getBrokerName()}]")
new JaasAuthenticatingBroker(broker)
new JaasAuthenticatingBroker(broker, brokerCfg)
}
}
@@ -13,6 +13,14 @@ akka {
}

blended {

security {
simple {
andreas { pwd: "mysecret", groups: ["admins", "blended"] }
tobias { pwd: "secret", groups : ["de_admins", "blended"] }
}
}

activemq {
brokerstarter {

@@ -24,13 +32,18 @@ blended {
withSsl : false
minReconnect : 2 seconds
withAuthentication : true
anonymousUser : "blended"
anonymousGroups : [ "users" ]
}

broker2 {
file: "broker2.amq"
provider: "broker2"
clientId: "broker2"
withSsl : false
withAuthentication : true
defaultUser : "andreas"
defaultPassword : "mysecret"
minReconnect : 2 seconds
}
}
@@ -6,10 +6,10 @@ import akka.actor.ActorSystem
import akka.testkit.TestProbe
import blended.akka.internal.BlendedAkkaActivator
import blended.jms.utils.{Connected, ConnectionStateChanged, IdAwareConnectionFactory}
import blended.security.internal.SecurityActivator
import blended.testsupport.pojosr.{PojoSrTestHelper, SimplePojoContainerSpec}
import blended.testsupport.scalatest.LoggingFreeSpecLike
import blended.testsupport.{BlendedTestSupport, RequiresForkedJVM}
import javax.jms.Connection
import org.osgi.framework.BundleActivator
import org.scalatest.Matchers

@@ -25,10 +25,11 @@ class BrokerActivatorSpec extends SimplePojoContainerSpec

override def bundles : Seq[(String, BundleActivator)] = Seq(
"blended.akka" -> new BlendedAkkaActivator(),
"blended.security" -> new SecurityActivator(),
"blended.activemq.brokerstarter" -> new BrokerActivator()
)

private implicit val timeout : FiniteDuration = 10.seconds
private implicit val timeout : FiniteDuration = 5.seconds
private implicit val system : ActorSystem = mandatoryService[ActorSystem](registry)(None)

"The BrokerActivator should" - {
@@ -38,7 +38,7 @@ abstract class ConnectionHolder(config : ConnectionConfig)(implicit system : Act

if (!connecting.getAndSet(true)) {
try {
log.info(s"Creating underlying connection for provider [$vendor:$provider] with client id [${config.clientId}]")
log.info(s"Creating underlying connection for provider [$vendor:$provider] as user [${config.defaultUser}] with client id [${config.clientId}]")

val cf : ConnectionFactory = getConnectionFactory()

@@ -116,7 +116,7 @@ class JndiConnectionHolder(
val envMap = new util.Hashtable[String, Object]()

val cfgMap : Map[String, String] =
config.properties ++ config.ctxtClassName.map(c => (Context.INITIAL_CONTEXT_FACTORY -> c)).toMap
config.properties ++ config.ctxtClassName.map(c => Context.INITIAL_CONTEXT_FACTORY -> c).toMap

cfgMap.foreach {
case (k, v) =>
@@ -8,8 +8,8 @@ import scala.concurrent.duration._

class ConfigLoginSpec extends AbstractLoginSpec {

private implicit val timeout = 3.seconds
override val baseDir = new File(BlendedTestSupport.projectTestOutput, "simple").getAbsolutePath()
private implicit val timeout : FiniteDuration = 3.seconds
override val baseDir : String = new File(BlendedTestSupport.projectTestOutput, "simple").getAbsolutePath()

"The Simple Login Module should" - {

@@ -21,8 +21,8 @@ class ConfigLoginSpec extends AbstractLoginSpec {
val groups = mgr.permissions(sub.get)

groups.granted.size should be(2)
assert(groups.granted.exists(_.permissionClass == Some("admins")))
assert(groups.granted.exists(_.permissionClass == Some("blended")))
assert(groups.granted.exists(_.permissionClass.contains("admins")))
assert(groups.granted.exists(_.permissionClass.contains("blended")))
}

"deny a login for an unknown user" in {
@@ -91,6 +91,7 @@ abstract class AbstractLoginModule extends LoginModule {
loggedInUser match {
case None => false
case Some(u) =>
log.debug(s"User [$u] logged in successfully")
subject.foreach { s =>
s.getPrincipals().add(new UserPrincipal(u))
val groups = getGroups(u)
@@ -2,6 +2,7 @@ import blended.sbt.Dependencies
import blended.sbt.phoenix.osgi.OsgiBundle
import phoenix.ProjectFactory
import sbt._
import de.wayofquality.sbt.testlogconfig.TestLogConfig.autoImport._

object BlendedActivemqBrokerstarter extends ProjectFactory {

@@ -30,9 +31,20 @@ object BlendedActivemqBrokerstarter extends ProjectFactory {
bundleActivator = s"$projectName.internal.BrokerActivator"
)

override def settings : Seq[sbt.Setting[_]] = super.settings ++ Seq(
Test / testlogDefaultLevel := "INFO",
Test / testlogLogPackages ++= Map(
"App" -> "Debug",
"spec" -> "Debug",
"blended" -> "Debug"
)
)

override def dependsOn : Seq[ClasspathDep[ProjectReference]] = Seq(
BlendedAkka.project,
BlendedJmsUtils.project,
BlendedSecurityBoot.project,
BlendedSecurityJvm.project,
BlendedTestsupport.project % Test,
BlendedTestsupportPojosr.project % Test
)

0 comments on commit 559bef3

Please sign in to comment.
You can’t perform that action at this time.