implement untrusted mode, see #2573

This commit is contained in:
Roland 2012-10-04 16:50:49 -07:00
parent a684f006b6
commit 3efa0c96e9
8 changed files with 103 additions and 48 deletions

View file

@ -67,7 +67,7 @@ case object Kill extends Kill {
* to another actor you should send the information in your own message.
*/
@SerialVersionUID(1L)
case class Terminated private[akka] (@BeanProperty actor: ActorRef)(@BeanProperty val existenceConfirmed: Boolean) extends AutoReceivedMessage
case class Terminated private[akka] (@BeanProperty actor: ActorRef)(@BeanProperty val existenceConfirmed: Boolean) extends AutoReceivedMessage with PossiblyHarmful
/**
* INTERNAL API
@ -78,7 +78,7 @@ case class Terminated private[akka] (@BeanProperty actor: ActorRef)(@BeanPropert
* and translates this event to [[akka.actor.Terminated]], which is sent itself.
*/
@SerialVersionUID(1L)
private[akka] case class AddressTerminated(address: Address) extends AutoReceivedMessage
private[akka] case class AddressTerminated(address: Address) extends AutoReceivedMessage with PossiblyHarmful
abstract class ReceiveTimeout extends PossiblyHarmful
@ -99,7 +99,7 @@ case object ReceiveTimeout extends ReceiveTimeout {
* nested path descriptions whenever using ! on them, the idea being that the
* message is delivered by active routing of the various actors involved.
*/
sealed trait SelectionPath extends AutoReceivedMessage
sealed trait SelectionPath extends AutoReceivedMessage with PossiblyHarmful
/**
* Internal use only

View file

@ -348,23 +348,44 @@ Akka provides a couple of ways to enhance security between remote nodes (client/
Untrusted Mode
--------------
You can enable untrusted mode for preventing system messages to be send by clients, e.g. messages like.
This will prevent the client to send these messages to the server:
* ``Create``
* ``Recreate``
* ``Suspend``
* ``Resume``
* ``Terminate``
* ``Supervise``
* ``ChildTerminated``
* ``Link``
* ``Unlink``
Here is how to turn it on in the config::
As soon as an actor system can connect to another remotely, it may in principle
send any possible message to any actor contained within that remote system. One
example may be sending a :class:`PoisonPill` to the system guardian, shutting
that system down. This is not always desired, and it can be disabled with the
following setting::
akka.remote.untrusted-mode = on
This disallows sending of system messages (actor life-cycle commands,
DeathWatch, etc.) and any message extending :class:`PossiblyHarmful` to the
system on which this flag is set. Should a client send them nonetheless they
are dropped and logged (at DEBUG level in order to reduce the possibilities for
a denial of service attack). :class:`PossiblyHarmful` covers the predefined
messages like :class:`PoisonPill` and :class:`Kill`, but it can also be added
as a marker trait to user-defined messages.
In summary, the following operations are ignored by a system configured in
untrusted mode when incoming via the remoting layer:
* remote deployment (which also means no remote supervision)
* remote DeathWatch
* ``system.stop()``, :class:`PoisonPill`, :class:`Kill`
* sending any message which extends from the :class:`PossiblyHarmful` marker
interface, which includes :class:`Terminated`
.. note::
Enabling the untrusted mode does not remove the capability of the client to
freely choose the target of its message sends, which means that messages not
prohibited by the above rules can be sent to any actor in the remote system.
It is good practice for a client-facing system to only contain a well-defined
set of entry point actors, which then forward requests (possibly after
performing validation) to another actor system containing the actual worker
actors. If messaging between these two server-side systems is done using
local :class:`ActorRef` (they can be exchanged safely between actor systems
within the same JVM), you can restrict the messages on this interface by
marking them :class:`PossiblyHarmful` so that a client cannot forge them.
Secure Cookie Handshake
-----------------------

View file

@ -350,23 +350,44 @@ Akka provides a couple of ways to enhance security between remote nodes (client/
Untrusted Mode
--------------
You can enable untrusted mode for preventing system messages to be send by clients, e.g. messages like.
This will prevent the client to send these messages to the server:
* ``Create``
* ``Recreate``
* ``Suspend``
* ``Resume``
* ``Terminate``
* ``Supervise``
* ``ChildTerminated``
* ``Link``
* ``Unlink``
Here is how to turn it on in the config::
As soon as an actor system can connect to another remotely, it may in principle
send any possible message to any actor contained within that remote system. One
example may be sending a :class:`PoisonPill` to the system guardian, shutting
that system down. This is not always desired, and it can be disabled with the
following setting::
akka.remote.untrusted-mode = on
This disallows sending of system messages (actor life-cycle commands,
DeathWatch, etc.) and any message extending :class:`PossiblyHarmful` to the
system on which this flag is set. Should a client send them nonetheless they
are dropped and logged (at DEBUG level in order to reduce the possibilities for
a denial of service attack). :class:`PossiblyHarmful` covers the predefined
messages like :class:`PoisonPill` and :class:`Kill`, but it can also be added
as a marker trait to user-defined messages.
In summary, the following operations are ignored by a system configured in
untrusted mode when incoming via the remoting layer:
* remote deployment (which also means no remote supervision)
* remote DeathWatch
* ``system.stop()``, :class:`PoisonPill`, :class:`Kill`
* sending any message which extends from the :class:`PossiblyHarmful` marker
interface, which includes :class:`Terminated`
.. note::
Enabling the untrusted mode does not remove the capability of the client to
freely choose the target of its message sends, which means that messages not
prohibited by the above rules can be sent to any actor in the remote system.
It is good practice for a client-facing system to only contain a well-defined
set of entry point actors, which then forward requests (possibly after
performing validation) to another actor system containing the actual worker
actors. If messaging between these two server-side systems is done using
local :class:`ActorRef` (they can be exchanged safely between actor systems
within the same JVM), you can restrict the messages on this interface by
marking them :class:`PossiblyHarmful` so that a client cannot forge them.
Secure Cookie Handshake
-----------------------

View file

@ -61,7 +61,7 @@ class RemoteActorRefProvider(
def init(system: ActorSystemImpl): Unit = {
local.init(system)
_remoteDaemon = new RemoteSystemDaemon(system, rootPath / "remote", rootGuardian, log)
_remoteDaemon = new RemoteSystemDaemon(system, rootPath / "remote", rootGuardian, log, untrustedMode = remoteSettings.UntrustedMode)
local.registerExtraNames(Map(("remote", remoteDaemon)))
_serialization = SerializationExtension(system)

View file

@ -21,7 +21,12 @@ private[akka] case class DaemonMsgCreate(props: Props, deploy: Deploy, path: Str
*
* INTERNAL USE ONLY!
*/
private[akka] class RemoteSystemDaemon(system: ActorSystemImpl, _path: ActorPath, _parent: InternalActorRef, _log: LoggingAdapter)
private[akka] class RemoteSystemDaemon(
system: ActorSystemImpl,
_path: ActorPath,
_parent: InternalActorRef,
_log: LoggingAdapter,
val untrustedMode: Boolean)
extends VirtualPathContainer(system.provider, _path, _parent, _log) {
/**
@ -53,6 +58,7 @@ private[akka] class RemoteSystemDaemon(system: ActorSystemImpl, _path: ActorPath
case message: DaemonMsg
log.debug("Received command [{}] to RemoteSystemDaemon on [{}]", message, path.address)
message match {
case DaemonMsgCreate(_, _, path, _) if untrustedMode log.debug("does not accept deployments (untrusted) for {}", path)
case DaemonMsgCreate(props, deploy, path, supervisor)
path match {
case ActorPathExtractor(address, elems) if elems.nonEmpty && elems.head == "remote"
@ -65,7 +71,7 @@ private[akka] class RemoteSystemDaemon(system: ActorSystemImpl, _path: ActorPath
addChild(subpath.mkString("/"), actor)
this.sendSystemMessage(Watch(actor, this))
case _
log.error("remote path does not match path from message [{}]", message)
log.debug("remote path does not match path from message [{}]", message)
}
}

View file

@ -268,30 +268,33 @@ abstract class RemoteTransport(val system: ExtendedActorSystem, val provider: Re
remoteMessage.recipient match {
case `remoteDaemon`
if (useUntrustedMode) log.debug("dropping daemon message {} in untrusted mode", remoteMessage.payload.getClass)
else {
if (provider.remoteSettings.LogReceive) log.debug("received daemon message {}", remoteMessage)
remoteMessage.payload match {
case m @ (_: DaemonMsg | _: Terminated)
try remoteDaemon ! m catch {
case e: Exception log.error(e, "exception while processing remote command {} from {}", m, remoteMessage.sender)
}
case x log.warning("remoteDaemon received illegal message {} from {}", x, remoteMessage.sender)
case x log.debug("remoteDaemon received illegal message {} from {}", x, remoteMessage.sender)
}
}
case l @ (_: LocalRef | _: RepointableRef) if l.isLocal
if (provider.remoteSettings.LogReceive) log.debug("received local message {}", remoteMessage)
remoteMessage.payload match {
case msg: PossiblyHarmful if useUntrustedMode log.warning("operating in UntrustedMode, dropping inbound PossiblyHarmful message of type {}", msg.getClass)
case msg: PossiblyHarmful if useUntrustedMode log.debug("operating in UntrustedMode, dropping inbound PossiblyHarmful message of type {}", msg.getClass)
case msg: SystemMessage l.sendSystemMessage(msg)
case msg l.!(msg)(remoteMessage.sender)
}
case r @ (_: RemoteRef | _: RepointableRef) if !r.isLocal
case r @ (_: RemoteRef | _: RepointableRef) if !r.isLocal && !useUntrustedMode
if (provider.remoteSettings.LogReceive) log.debug("received remote-destined message {}", remoteMessage)
remoteMessage.originalReceiver match {
case AddressFromURIString(address) if address == provider.transport.address
// if it was originally addressed to us but is in fact remote from our point of view (i.e. remote-deployed)
r.!(remoteMessage.payload)(remoteMessage.sender)
case r log.error("dropping message {} for non-local recipient {} arriving at {} inbound address is {}", remoteMessage.payload, r, address, provider.transport.address)
case r log.debug("dropping message {} for non-local recipient {} arriving at {} inbound address is {}", remoteMessage.payload, r, address, provider.transport.address)
}
case r log.error("dropping message {} for unknown recipient {} arriving at {} inbound address is {}", remoteMessage.payload, r, address, provider.transport.address)
case r log.debug("dropping message {} for unknown recipient {} arriving at {} inbound address is {}", remoteMessage.payload, r, address, provider.transport.address)
}
}
}
@ -333,5 +336,5 @@ class RemoteMessage(input: RemoteMessageProtocol, system: ExtendedActorSystem) {
/**
* Returns a String representation of this RemoteMessage, intended for debugging purposes.
*/
override def toString: String = "RemoteMessage: " + payload + " to " + recipient + "<+{" + originalReceiver + "} from " + sender
override def toString: String = "RemoteMessage: " + payload.getClass + " to " + recipient + "<+{" + originalReceiver + "} from " + sender
}

View file

@ -10,6 +10,7 @@ import scala.concurrent.Future
import scala.concurrent.Await
import scala.reflect.classTag
import akka.pattern.ask
import akka.event.Logging
object RemoteCommunicationSpec {
class Echo extends Actor {
@ -76,9 +77,12 @@ akka {
}
"send error message for wrong address" in {
EventFilter.error(start = "dropping", occurrences = 1).intercept {
val old = other.eventStream.logLevel
other.eventStream.setLogLevel(Logging.DebugLevel)
EventFilter.debug(start = "dropping", occurrences = 1).intercept {
system.actorFor("akka://remotesys@localhost:12346/user/echo") ! "ping"
}(other)
other.eventStream.setLogLevel(old)
}
"support ask" in {

View file

@ -112,7 +112,7 @@ class TestActorRef[T <: Actor](
object TestActorRef {
private case object InternalGetActor extends AutoReceivedMessage
private case object InternalGetActor extends AutoReceivedMessage with PossiblyHarmful
private val number = new AtomicLong
private[testkit] def randomName: String = {