Removing, deprecating and replacing usage of black/whitelist (#29254)
This commit is contained in:
parent
25ea7b7f5e
commit
1e9e984727
23 changed files with 168 additions and 144 deletions
|
|
@ -411,7 +411,7 @@ In order to force the `validatePullRequest` task to build the entire project, re
|
|||
changes one can use the special `PLS BUILD ALL` command (typed in a comment on GitHub, on the pull request), which will cause
|
||||
the validator to test all projects.
|
||||
|
||||
Note, that `OK TO TEST` will only be picked up when the user asking for it is considered an admin. Public (!) members of the [akka organization](https://github.com/orgs/akka/people) are automatically considered admins and users manually declared admin in the Jenkins job (currently no one is explicitly listed). `PLS BUILD` and `PLS BUILD ALL` can be issued by everyone that is an admin or everyone who was whitelisted in the Jenkins Job (whitelisting != declaring someone an admin).
|
||||
Note, that `OK TO TEST` will only be picked up when the user asking for it is considered an admin. Public (!) members of the [akka organization](https://github.com/orgs/akka/people) are automatically considered admins and users manually declared admin in the Jenkins job (currently no one is explicitly listed). `PLS BUILD` and `PLS BUILD ALL` can be issued by everyone that is an admin or everyone who was given permission in the Jenkins Job.
|
||||
|
||||
## Source style
|
||||
|
||||
|
|
|
|||
|
|
@ -793,11 +793,7 @@ akka {
|
|||
}
|
||||
|
||||
serialization.protobuf {
|
||||
|
||||
# Additional classes that are allowed even if they are not defined in `serialization-bindings`.
|
||||
# It can be exact class name or name of super class or interfaces (one level).
|
||||
# This is useful when a class is not used for serialization any more and therefore removed
|
||||
# from `serialization-bindings`, but should still be possible to deserialize.
|
||||
# deprecated, use `allowed-classes` instead
|
||||
whitelist-class = [
|
||||
"com.google.protobuf.GeneratedMessage",
|
||||
"com.google.protobuf.GeneratedMessageV3",
|
||||
|
|
@ -805,6 +801,13 @@ akka {
|
|||
"akka.protobuf.GeneratedMessage",
|
||||
"akka.protobufv3.internal.GeneratedMessageV3"
|
||||
]
|
||||
|
||||
# Additional classes that are allowed even if they are not defined in `serialization-bindings`.
|
||||
# It can be exact class name or name of super class or interfaces (one level).
|
||||
# This is useful when a class is not used for serialization any more and therefore removed
|
||||
# from `serialization-bindings`, but should still be possible to deserialize.
|
||||
allowed-classes = ${akka.serialization.protobuf.whitelist-class}
|
||||
|
||||
}
|
||||
|
||||
# Used to set the behavior of the scheduler.
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ private[cluster] class ClusterRemoteWatcher(
|
|||
override val log = Logging(context.system, ActorWithLogClass(this, ClusterLogClass.ClusterCore))
|
||||
|
||||
// allowed to watch even though address not in cluster membership, i.e. remote watch
|
||||
private val watchPathWhitelist = Set("/system/sharding/")
|
||||
private val watchPathAllowList = Set("/system/sharding/")
|
||||
|
||||
private var pendingDelayedQuarantine: Set[UniqueAddress] = Set.empty
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ private[cluster] class ClusterRemoteWatcher(
|
|||
private def isWatchOutsideClusterAllowed(watchee: InternalActorRef): Boolean = {
|
||||
context.system.name == watchee.path.address.system && {
|
||||
val pathPrefix = watchee.path.elements.take(2).mkString("/", "/", "/")
|
||||
watchPathWhitelist.contains(pathPrefix)
|
||||
watchPathAllowList.contains(pathPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,14 +105,14 @@ The procedure for changing from Java serialization to Jackson would look like:
|
|||
described in @ref:[Serialization with Jackson](../serialization-jackson.md).
|
||||
* Test the system with the new serialization in a new test cluster (no rolling update).
|
||||
* Remove the binding for the marker interface in `akka.actor.serialization-bindings`, so that Jackson is not used for serialization (toBinary) yet.
|
||||
* Configure `akka.serialization.jackson.whitelist-class-prefix=["com.myapp"]`
|
||||
* Configure `akka.serialization.jackson.allowed-class-prefix=["com.myapp"]`
|
||||
* This is needed for Jackson deserialization when the `serialization-bindings` isn't defined.
|
||||
* Replace `com.myapp` with the name of the root package of your application to trust all classes.
|
||||
* Roll out the change.
|
||||
* Java serialization is still used, but this version is prepared for next roll out.
|
||||
1. Rolling update to enable serialization with Jackson.
|
||||
* Add the binding to the marker interface in `akka.actor.serialization-bindings` to the Jackson serializer.
|
||||
* Remove `akka.serialization.jackson.whitelist-class-prefix`.
|
||||
* Remove `akka.serialization.jackson.allowed-class-prefix`.
|
||||
* Roll out the change.
|
||||
* Old nodes will still send messages with Java serialization, and that can still be deserialized by new nodes.
|
||||
* New nodes will send messages with Jackson serialization, and old node can deserialize those because they were
|
||||
|
|
|
|||
|
|
@ -271,22 +271,22 @@ Scala
|
|||
Java
|
||||
: @@snip [RemoteDeploymentDocTest.java](/akka-docs/src/test/java/jdocs/remoting/RemoteDeploymentDocTest.java) { #deploy }
|
||||
|
||||
### Remote deployment whitelist
|
||||
### Remote deployment allow list
|
||||
|
||||
As remote deployment can potentially be abused by both users and even attackers a whitelist feature
|
||||
As remote deployment can potentially be abused by both users and even attackers an allow list feature
|
||||
is available to guard the ActorSystem from deploying unexpected actors. Please note that remote deployment
|
||||
is *not* remote code loading, the Actors class to be deployed onto a remote system needs to be present on that
|
||||
remote system. This still however may pose a security risk, and one may want to restrict remote deployment to
|
||||
only a specific set of known actors by enabling the whitelist feature.
|
||||
only a specific set of known actors by enabling the allow list feature.
|
||||
|
||||
To enable remote deployment whitelisting set the `akka.remote.deployment.enable-whitelist` value to `on`.
|
||||
To enable remote deployment allow list set the `akka.remote.deployment.enable-allow-list` value to `on`.
|
||||
The list of allowed classes has to be configured on the "remote" system, in other words on the system onto which
|
||||
others will be attempting to remote deploy Actors. That system, locally, knows best which Actors it should or
|
||||
should not allow others to remote deploy onto it. The full settings section may for example look like this:
|
||||
|
||||
@@snip [RemoteDeploymentWhitelistSpec.scala](/akka-remote/src/test/scala/akka/remote/classic/RemoteDeploymentWhitelistSpec.scala) { #whitelist-config }
|
||||
@@snip [RemoteDeploymentAllowListSpec.scala](/akka-remote/src/test/scala/akka/remote/classic/RemoteDeploymentAllowListSpec.scala) { #allow-list-config }
|
||||
|
||||
Actor classes not included in the whitelist will not be allowed to be remote deployed onto this system.
|
||||
Actor classes not included in the allow list will not be allowed to be remote deployed onto this system.
|
||||
|
||||
## Lifecycle and Failure Recovery Model
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ to ensure that a fix can be provided without delay.
|
|||
## Security Related Documentation
|
||||
|
||||
* @ref:[Java Serialization](../serialization.md#java-serialization)
|
||||
* @ref:[Remote deployment whitelist](../remoting.md#remote-deployment-whitelist)
|
||||
* @ref:[Remote deployment allow list](../remoting.md#remote-deployment-allow-list)
|
||||
* @ref:[Remote Security](../remoting-artery.md#remote-security)
|
||||
|
||||
## Fixed Security Vulnerabilities
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ such as:
|
|||
* `java.io.Serializable`
|
||||
* `java.util.Comparable`.
|
||||
|
||||
The blacklist of possible serialization gadget classes defined by Jackson databind are checked
|
||||
The deny list of possible serialization gadget classes defined by Jackson databind are checked
|
||||
and disallowed for deserialization.
|
||||
|
||||
@@@ warning
|
||||
|
|
@ -350,12 +350,12 @@ That type of migration must be configured with the old class name as key. The ac
|
|||
### Remove from serialization-bindings
|
||||
|
||||
When a class is not used for serialization any more it can be removed from `serialization-bindings` but to still
|
||||
allow deserialization it must then be listed in the `whitelist-class-prefix` configuration. This is useful for example
|
||||
allow deserialization it must then be listed in the `allowed-class-prefix` configuration. This is useful for example
|
||||
during rolling update with serialization changes, or when reading old stored data. It can also be used
|
||||
when changing from Jackson serializer to another serializer (e.g. Protobuf) and thereby changing the serialization
|
||||
binding, but it should still be possible to deserialize old data with Jackson.
|
||||
|
||||
@@snip [config](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala) { #whitelist-class-prefix }
|
||||
@@snip [config](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala) { #allowed-class-prefix }
|
||||
|
||||
It's a list of class names or prefixes of class names.
|
||||
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ Java
|
|||
: @@snip [StatefulMapConcat.java](/akka-docs/src/test/java/jdocs/stream/operators/flow/StatefulMapConcat.java) { #zip-with-index }
|
||||
|
||||
In this sample we let the value of the elements have an effect on the following elements, if an element starts
|
||||
with `blacklist:word` we add it to a black list and filter out any subsequent entries of `word`:
|
||||
with `deny:word` we add it to a deny list and filter out any subsequent entries of `word`:
|
||||
|
||||
Scala
|
||||
: @@snip [StatefulMapConcat.scala](/akka-docs/src/test/scala/docs/stream/operators/flow/StatefulMapConcat.scala) { #blacklist }
|
||||
: @@snip [StatefulMapConcat.scala](/akka-docs/src/test/scala/docs/stream/operators/flow/StatefulMapConcat.scala) { #denylist }
|
||||
|
||||
Java
|
||||
: @@snip [StatefulMapConcat.java](/akka-docs/src/test/java/jdocs/stream/operators/flow/StatefulMapConcat.java) { #blacklist }
|
||||
: @@snip [StatefulMapConcat.java](/akka-docs/src/test/java/jdocs/stream/operators/flow/StatefulMapConcat.java) { #denylist }
|
||||
|
||||
For cases where there is a need to emit elements based on the state when the stream ends, it is possible to add an extra
|
||||
element signalling the end of the stream before the `statefulMapConcat` operator.
|
||||
|
|
|
|||
|
|
@ -43,40 +43,39 @@ public class StatefulMapConcat {
|
|||
// #zip-with-index
|
||||
}
|
||||
|
||||
static void blacklist() {
|
||||
// #blacklist
|
||||
Source<String, NotUsed> fruitsAndBlacklistCommands =
|
||||
static void denylist() {
|
||||
// #denylist
|
||||
Source<String, NotUsed> fruitsAndDenyCommands =
|
||||
Source.from(
|
||||
Arrays.asList(
|
||||
"banana", "pear", "orange", "blacklist:banana", "banana", "pear", "banana"));
|
||||
Arrays.asList("banana", "pear", "orange", "deny:banana", "banana", "pear", "banana"));
|
||||
|
||||
Flow<String, String, NotUsed> blacklistingFlow =
|
||||
Flow<String, String, NotUsed> denyFilterFlow =
|
||||
Flow.of(String.class)
|
||||
.statefulMapConcat(
|
||||
() -> {
|
||||
Set<String> blacklist = new HashSet<>();
|
||||
Set<String> denyList = new HashSet<>();
|
||||
|
||||
return (element) -> {
|
||||
if (element.startsWith("blacklist:")) {
|
||||
blacklist.add(element.substring(10));
|
||||
if (element.startsWith("deny:")) {
|
||||
denyList.add(element.substring(10));
|
||||
return Collections
|
||||
.emptyList(); // no element downstream when adding a blacklisted keyword
|
||||
} else if (blacklist.contains(element)) {
|
||||
.emptyList(); // no element downstream when adding a deny listed keyword
|
||||
} else if (denyList.contains(element)) {
|
||||
return Collections
|
||||
.emptyList(); // no element downstream if element is blacklisted
|
||||
.emptyList(); // no element downstream if element is deny listed
|
||||
} else {
|
||||
return Collections.singletonList(element);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
fruitsAndBlacklistCommands.via(blacklistingFlow).runForeach(System.out::println, system);
|
||||
fruitsAndDenyCommands.via(denyFilterFlow).runForeach(System.out::println, system);
|
||||
// prints
|
||||
// banana
|
||||
// pear
|
||||
// orange
|
||||
// pear
|
||||
// #blacklist
|
||||
// #denylist
|
||||
}
|
||||
|
||||
static void reactOnEnd() {
|
||||
|
|
|
|||
|
|
@ -34,33 +34,33 @@ class StatefulMapConcat {
|
|||
// #zip-with-index
|
||||
}
|
||||
|
||||
def blacklist(): Unit = {
|
||||
// #blacklist
|
||||
val fruitsAndBlacklistCommands = Source(
|
||||
"banana" :: "pear" :: "orange" :: "blacklist:banana" :: "banana" :: "pear" :: "banana" :: Nil)
|
||||
def denylist(): Unit = {
|
||||
// #denylist
|
||||
val fruitsAndDeniedCommands = Source(
|
||||
"banana" :: "pear" :: "orange" :: "deny:banana" :: "banana" :: "pear" :: "banana" :: Nil)
|
||||
|
||||
val blacklistingFlow = Flow[String].statefulMapConcat { () =>
|
||||
var blacklist = Set.empty[String]
|
||||
val denyFilterFlow = Flow[String].statefulMapConcat { () =>
|
||||
var denyList = Set.empty[String]
|
||||
|
||||
{ element =>
|
||||
if (element.startsWith("blacklist:")) {
|
||||
blacklist += element.drop(10)
|
||||
Nil // no element downstream when adding a blacklisted keyword
|
||||
} else if (blacklist(element)) {
|
||||
Nil // no element downstream if element is blacklisted
|
||||
if (element.startsWith("deny:")) {
|
||||
denyList += element.drop(10)
|
||||
Nil // no element downstream when adding a deny listed keyword
|
||||
} else if (denyList(element)) {
|
||||
Nil // no element downstream if element is deny listed
|
||||
} else {
|
||||
element :: Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fruitsAndBlacklistCommands.via(blacklistingFlow).runForeach(println)
|
||||
fruitsAndDeniedCommands.via(denyFilterFlow).runForeach(println)
|
||||
// prints
|
||||
// banana
|
||||
// pear
|
||||
// orange
|
||||
// pear
|
||||
// #blacklist
|
||||
// #denylist
|
||||
}
|
||||
|
||||
def reactOnEnd(): Unit = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
# Internal class rename
|
||||
ProblemFilters.exclude[MissingClassProblem]("akka.remote.NotWhitelistedClassRemoteDeploymentAttemptException")
|
||||
|
|
@ -234,10 +234,18 @@ akka {
|
|||
|
||||
# remote deployment configuration section
|
||||
deployment {
|
||||
# If true, will only allow specific classes to be instanciated on this system via remote deployment
|
||||
# deprecated, use `enable-allow-list`
|
||||
enable-whitelist = off
|
||||
|
||||
# If true, will only allow specific classes listed in `allowed-actor-classes` to be instanciated on this
|
||||
# system via remote deployment
|
||||
enable-allow-list = ${akka.remote.deployment.enable-whitelist}
|
||||
|
||||
|
||||
# deprecated, use `allowed-actor-classes`
|
||||
whitelist = []
|
||||
|
||||
allowed-actor-classes = ${akka.remote.deployment.whitelist}
|
||||
}
|
||||
|
||||
### Default dispatcher for the remoting subsystem
|
||||
|
|
@ -336,7 +344,7 @@ akka {
|
|||
untrusted-mode = off
|
||||
|
||||
# When 'untrusted-mode=on' inbound actor selections are by default discarded.
|
||||
# Actors with paths defined in this white list are granted permission to receive actor
|
||||
# Actors with paths defined in this list are granted permission to receive actor
|
||||
# selections messages.
|
||||
# E.g. trusted-selection-paths = ["/user/receptionist", "/user/namingService"]
|
||||
trusted-selection-paths = []
|
||||
|
|
@ -807,7 +815,7 @@ akka {
|
|||
untrusted-mode = off
|
||||
|
||||
# When 'untrusted-mode=on' inbound actor selections are by default discarded.
|
||||
# Actors with paths defined in this white list are granted permission to receive actor
|
||||
# Actors with paths defined in this list are granted permission to receive actor
|
||||
# selections messages.
|
||||
# E.g. trusted-selection-paths = ["/user/receptionist", "/user/namingService"]
|
||||
trusted-selection-paths = []
|
||||
|
|
|
|||
|
|
@ -73,10 +73,11 @@ private[akka] class RemoteSystemDaemon(
|
|||
|
||||
private val parent2children = new ConcurrentHashMap[ActorRef, Set[ActorRef]]
|
||||
|
||||
private val whitelistEnabled = system.settings.config.getBoolean("akka.remote.deployment.enable-whitelist")
|
||||
private val remoteDeploymentWhitelist: immutable.Set[String] = {
|
||||
private val allowListEnabled = system.settings.config.getBoolean("akka.remote.deployment.enable-allow-list")
|
||||
private val remoteDeploymentAllowList: immutable.Set[String] = {
|
||||
import akka.util.ccompat.JavaConverters._
|
||||
if (whitelistEnabled) system.settings.config.getStringList("akka.remote.deployment.whitelist").asScala.toSet
|
||||
if (allowListEnabled)
|
||||
system.settings.config.getStringList("akka.remote.deployment.allowed-actor-classes").asScala.toSet
|
||||
else Set.empty
|
||||
}
|
||||
|
||||
|
|
@ -164,13 +165,13 @@ private[akka] class RemoteSystemDaemon(
|
|||
case DaemonMsgCreate(_, _, path, _) if untrustedMode =>
|
||||
log.debug("does not accept deployments (untrusted) for [{}]", path) // TODO add security marker?
|
||||
|
||||
case DaemonMsgCreate(props, deploy, path, supervisor) if whitelistEnabled =>
|
||||
case DaemonMsgCreate(props, deploy, path, supervisor) if allowListEnabled =>
|
||||
val name = props.clazz.getCanonicalName
|
||||
if (remoteDeploymentWhitelist.contains(name))
|
||||
if (remoteDeploymentAllowList.contains(name))
|
||||
doCreateActor(message, props, deploy, path, supervisor)
|
||||
else {
|
||||
val ex =
|
||||
new NotWhitelistedClassRemoteDeploymentAttemptException(props.actorClass(), remoteDeploymentWhitelist)
|
||||
new NotAllowedClassRemoteDeploymentAttemptException(props.actorClass(), remoteDeploymentAllowList)
|
||||
log.error(
|
||||
LogMarker.Security,
|
||||
ex,
|
||||
|
|
@ -273,8 +274,8 @@ private[akka] class RemoteSystemDaemon(
|
|||
}
|
||||
|
||||
/** INTERNAL API */
|
||||
final class NotWhitelistedClassRemoteDeploymentAttemptException(illegal: Class[_], whitelist: immutable.Set[String])
|
||||
final class NotAllowedClassRemoteDeploymentAttemptException(illegal: Class[_], allowedClassNames: immutable.Set[String])
|
||||
extends RuntimeException(
|
||||
s"Attempted to deploy not whitelisted Actor class: " +
|
||||
s"Attempted to deploy Actor class: " +
|
||||
s"[$illegal], " +
|
||||
s"whitelisted classes: [${whitelist.mkString(", ")}]")
|
||||
s"which is not allowed, allowed classes: [${allowedClassNames.mkString(", ")}]")
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ class ProtobufSerializer(val system: ExtendedActorSystem) extends BaseSerializer
|
|||
private val parsingMethodBindingRef = new AtomicReference[Map[Class[_], Method]](Map.empty)
|
||||
private val toByteArrayMethodBindingRef = new AtomicReference[Map[Class[_], Method]](Map.empty)
|
||||
|
||||
private val whitelistClassNames: Set[String] = {
|
||||
private val allowedClassNames: Set[String] = {
|
||||
import akka.util.ccompat.JavaConverters._
|
||||
system.settings.config.getStringList("akka.serialization.protobuf.whitelist-class").asScala.toSet
|
||||
system.settings.config.getStringList("akka.serialization.protobuf.allowed-classes").asScala.toSet
|
||||
}
|
||||
|
||||
// This must lazy otherwise it will deadlock the ActorSystem creation
|
||||
|
|
@ -110,18 +110,18 @@ class ProtobufSerializer(val system: ExtendedActorSystem) extends BaseSerializer
|
|||
}
|
||||
|
||||
private def checkAllowedClass(clazz: Class[_]): Unit = {
|
||||
if (!isInWhitelist(clazz)) {
|
||||
if (!isInAllowList(clazz)) {
|
||||
val warnMsg = s"Can't deserialize object of type [${clazz.getName}] in [${getClass.getName}]. " +
|
||||
"Only classes that are whitelisted are allowed for security reasons. " +
|
||||
"Configure whitelist with akka.actor.serialization-bindings or " +
|
||||
"akka.serialization.protobuf.whitelist-class"
|
||||
"Only classes that are on the allow list are allowed for security reasons. " +
|
||||
"Configure allowed classes with akka.actor.serialization-bindings or " +
|
||||
"akka.serialization.protobuf.allowed-classes"
|
||||
log.warning(LogMarker.Security, warnMsg)
|
||||
throw new IllegalArgumentException(warnMsg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the `serialization-bindings` as source for the whitelist.
|
||||
* Using the `serialization-bindings` as source for the allowed classes.
|
||||
* Note that the intended usage of serialization-bindings is for lookup of
|
||||
* serializer when serializing (`toBinary`). For deserialization (`fromBinary`) the serializer-id is
|
||||
* used for selecting serializer.
|
||||
|
|
@ -130,13 +130,13 @@ class ProtobufSerializer(val system: ExtendedActorSystem) extends BaseSerializer
|
|||
*
|
||||
* If an old class is removed from `serialization-bindings` when it's not used for serialization
|
||||
* but still used for deserialization (e.g. rolling update with serialization changes) it can
|
||||
* be allowed by specifying in `akka.protobuf.whitelist-class`.
|
||||
* be allowed by specifying in `akka.protobuf.allowed-classes`.
|
||||
*
|
||||
* That is also possible when changing a binding from a ProtobufSerializer to another serializer (e.g. Jackson)
|
||||
* and still bind with the same class (interface).
|
||||
*/
|
||||
private def isInWhitelist(clazz: Class[_]): Boolean = {
|
||||
isBoundToProtobufSerializer(clazz) || isInWhitelistClassName(clazz)
|
||||
private def isInAllowList(clazz: Class[_]): Boolean = {
|
||||
isBoundToProtobufSerializer(clazz) || isInAllowListClassName(clazz)
|
||||
}
|
||||
|
||||
private def isBoundToProtobufSerializer(clazz: Class[_]): Boolean = {
|
||||
|
|
@ -148,9 +148,9 @@ class ProtobufSerializer(val system: ExtendedActorSystem) extends BaseSerializer
|
|||
}
|
||||
}
|
||||
|
||||
private def isInWhitelistClassName(clazz: Class[_]): Boolean = {
|
||||
whitelistClassNames(clazz.getName) ||
|
||||
whitelistClassNames(clazz.getSuperclass.getName) ||
|
||||
clazz.getInterfaces.exists(c => whitelistClassNames(c.getName))
|
||||
private def isInAllowListClassName(clazz: Class[_]): Boolean = {
|
||||
allowedClassNames(clazz.getName) ||
|
||||
allowedClassNames(clazz.getSuperclass.getName) ||
|
||||
clazz.getInterfaces.exists(c => allowedClassNames(c.getName))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class UntrustedSpec extends ArteryMultiNodeSpec(UntrustedSpec.config) with Impli
|
|||
|
||||
"UntrustedMode" must {
|
||||
|
||||
"allow actor selection to configured white list" in {
|
||||
"allow actor selection to configured allow list" in {
|
||||
val sel = client.actorSelection(RootActorPath(address) / receptionist.path.elements)
|
||||
sel ! "hello"
|
||||
expectMsg("hello")
|
||||
|
|
@ -152,7 +152,7 @@ class UntrustedSpec extends ArteryMultiNodeSpec(UntrustedSpec.config) with Impli
|
|||
expectNoMessage(1.second)
|
||||
}
|
||||
|
||||
"discard actor selection to child of matching white list" in {
|
||||
"discard actor selection to child of matching allow list" in {
|
||||
val sel = client.actorSelection(RootActorPath(address) / receptionist.path.elements / "child1")
|
||||
sel ! "hello"
|
||||
expectNoMessage(1.second)
|
||||
|
|
|
|||
|
|
@ -5,19 +5,18 @@
|
|||
package akka.remote.classic
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import com.github.ghik.silencer.silent
|
||||
import com.typesafe.config._
|
||||
|
||||
import akka.actor._
|
||||
import akka.remote.EndpointException
|
||||
import akka.remote.NotAllowedClassRemoteDeploymentAttemptException
|
||||
import akka.remote.transport._
|
||||
import akka.testkit._
|
||||
|
||||
// relies on test transport
|
||||
object RemoteDeploymentWhitelistSpec {
|
||||
object RemoteDeploymentAllowListSpec {
|
||||
|
||||
class EchoWhitelisted extends Actor {
|
||||
class EchoAllowed extends Actor {
|
||||
var target: ActorRef = context.system.deadLetters
|
||||
|
||||
def receive = {
|
||||
|
|
@ -35,7 +34,7 @@ object RemoteDeploymentWhitelistSpec {
|
|||
}
|
||||
}
|
||||
|
||||
class EchoNotWhitelisted extends Actor {
|
||||
class EchoNotAllowed extends Actor {
|
||||
var target: ActorRef = context.system.deadLetters
|
||||
|
||||
def receive = {
|
||||
|
|
@ -57,6 +56,7 @@ object RemoteDeploymentWhitelistSpec {
|
|||
akka {
|
||||
actor.provider = remote
|
||||
|
||||
|
||||
remote {
|
||||
use-unsafe-remote-features-outside-cluster = on
|
||||
classic.enabled-transports = [
|
||||
|
|
@ -79,7 +79,7 @@ object RemoteDeploymentWhitelistSpec {
|
|||
transport-class = "akka.remote.transport.TestTransport"
|
||||
applied-adapters = []
|
||||
registry-key = aX33k0jWKg
|
||||
local-address = "test://RemoteDeploymentWhitelistSpec@localhost:12345"
|
||||
local-address = "test://RemoteDeploymentAllowListSpec@localhost:12345"
|
||||
maximum-payload-bytes = 32000 bytes
|
||||
scheme-identifier = test
|
||||
}
|
||||
|
|
@ -105,31 +105,30 @@ object RemoteDeploymentWhitelistSpec {
|
|||
}
|
||||
|
||||
@silent("deprecated")
|
||||
class RemoteDeploymentWhitelistSpec
|
||||
extends AkkaSpec(RemoteDeploymentWhitelistSpec.cfg)
|
||||
class RemoteDeploymentAllowListSpec
|
||||
extends AkkaSpec(RemoteDeploymentAllowListSpec.cfg)
|
||||
with ImplicitSender
|
||||
with DefaultTimeout {
|
||||
|
||||
import RemoteDeploymentWhitelistSpec._
|
||||
import RemoteDeploymentAllowListSpec._
|
||||
|
||||
val conf =
|
||||
ConfigFactory.parseString("""
|
||||
akka.loglevel = DEBUG
|
||||
akka.remote.test {
|
||||
local-address = "test://remote-sys@localhost:12346"
|
||||
maximum-payload-bytes = 48000 bytes
|
||||
}
|
||||
|
||||
//#whitelist-config
|
||||
//#allow-list-config
|
||||
akka.remote.deployment {
|
||||
enable-whitelist = on
|
||||
enable-allow-list = on
|
||||
|
||||
whitelist = [
|
||||
allowed-actor-classes = [
|
||||
"NOT_ON_CLASSPATH", # verify we don't throw if a class not on classpath is listed here
|
||||
"akka.remote.classic.RemoteDeploymentWhitelistSpec.EchoWhitelisted"
|
||||
"akka.remote.classic.RemoteDeploymentAllowListSpec.EchoAllowed"
|
||||
]
|
||||
}
|
||||
//#whitelist-config
|
||||
//#allow-list-config
|
||||
""").withFallback(system.settings.config).resolve()
|
||||
val remoteSystem = ActorSystem("remote-sys", conf)
|
||||
|
||||
|
|
@ -147,10 +146,10 @@ class RemoteDeploymentWhitelistSpec
|
|||
AssociationRegistry.clear()
|
||||
}
|
||||
|
||||
"RemoteDeployment Whitelist" must {
|
||||
"RemoteDeployment Allow List" must {
|
||||
|
||||
"allow deploying Echo actor (included in whitelist)" in {
|
||||
val r = system.actorOf(Props[EchoWhitelisted](), "blub")
|
||||
"allow deploying Echo actor (included in allow list)" in {
|
||||
val r = system.actorOf(Props[EchoAllowed](), "blub")
|
||||
r.path.toString should ===(
|
||||
s"akka.test://remote-sys@localhost:12346/remote/akka.test/${getClass.getSimpleName}@localhost:12345/user/blub")
|
||||
r ! 42
|
||||
|
|
@ -165,13 +164,19 @@ class RemoteDeploymentWhitelistSpec
|
|||
expectMsg("postStop")
|
||||
}
|
||||
|
||||
"not deploy actor not listed in whitelist" in {
|
||||
val r = system.actorOf(Props[EchoNotWhitelisted](), "danger-mouse")
|
||||
r.path.toString should ===(
|
||||
s"akka.test://remote-sys@localhost:12346/remote/akka.test/${getClass.getSimpleName}@localhost:12345/user/danger-mouse")
|
||||
r ! 42
|
||||
expectNoMessage(1.second)
|
||||
system.stop(r)
|
||||
"not deploy actor not listed in allow list" in {
|
||||
EventFilter
|
||||
.warning(start = "received dead letter", occurrences = 1)
|
||||
.intercept {
|
||||
EventFilter[NotAllowedClassRemoteDeploymentAttemptException](occurrences = 1).intercept {
|
||||
val r = system.actorOf(Props[EchoNotAllowed](), "danger-mouse")
|
||||
r.path.toString should ===(
|
||||
s"akka.test://remote-sys@localhost:12346/remote/akka.test/${getClass.getSimpleName}@localhost:12345/user/danger-mouse")
|
||||
r ! 42
|
||||
expectNoMessage(1.second)
|
||||
system.stop(r)
|
||||
}(remoteSystem)
|
||||
}(remoteSystem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +123,7 @@ akka.actor.serialization-bindings {
|
|||
|
||||
"UntrustedMode" must {
|
||||
|
||||
"allow actor selection to configured white list" in {
|
||||
"allow actor selection to configured allow list" in {
|
||||
val sel = client.actorSelection(RootActorPath(address) / receptionist.path.elements)
|
||||
sel ! "hello"
|
||||
expectMsg("hello")
|
||||
|
|
@ -181,7 +181,7 @@ akka.actor.serialization-bindings {
|
|||
expectNoMessage(1.second)
|
||||
}
|
||||
|
||||
"discard actor selection to child of matching white list" in {
|
||||
"discard actor selection to child of matching allow list" in {
|
||||
val sel = client.actorSelection(RootActorPath(address) / receptionist.path.elements / "child1")
|
||||
sel ! "hello"
|
||||
expectNoMessage(1.second)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ object MaliciousMessage {
|
|||
}
|
||||
|
||||
class ProtobufSerializerSpec extends AkkaSpec(s"""
|
||||
akka.serialization.protobuf.whitelist-class = [
|
||||
akka.serialization.protobuf.allowed-classes = [
|
||||
"com.google.protobuf.GeneratedMessage",
|
||||
"com.google.protobuf.GeneratedMessageV3",
|
||||
"scalapb.GeneratedMessageCompanion",
|
||||
|
|
@ -87,7 +87,7 @@ class ProtobufSerializerSpec extends AkkaSpec(s"""
|
|||
protobufV3Message should ===(deserialized)
|
||||
}
|
||||
|
||||
"disallow deserialization of classes that are not in bindings and not in configured whitelist-class" in {
|
||||
"disallow deserialization of classes that are not in bindings and not in configured allowed classes" in {
|
||||
val originalSerializer = ser.serializerFor(classOf[MyMessage])
|
||||
|
||||
intercept[IllegalArgumentException] {
|
||||
|
|
@ -95,7 +95,7 @@ class ProtobufSerializerSpec extends AkkaSpec(s"""
|
|||
}
|
||||
}
|
||||
|
||||
"allow deserialization of classes in configured whitelist-class" in {
|
||||
"allow deserialization of classes in configured allowed classes" in {
|
||||
val originalSerializer = ser.serializerFor(classOf[MyMessage])
|
||||
|
||||
val deserialized =
|
||||
|
|
@ -103,7 +103,7 @@ class ProtobufSerializerSpec extends AkkaSpec(s"""
|
|||
deserialized.getClass should ===(classOf[AnotherMessage])
|
||||
}
|
||||
|
||||
"allow deserialization of interfaces in configured whitelist-class" in {
|
||||
"allow deserialization of interfaces in configured allowed classes" in {
|
||||
val originalSerializer = ser.serializerFor(classOf[MyMessage])
|
||||
|
||||
val deserialized =
|
||||
|
|
@ -111,7 +111,7 @@ class ProtobufSerializerSpec extends AkkaSpec(s"""
|
|||
deserialized.getClass should ===(classOf[AnotherMessage2])
|
||||
}
|
||||
|
||||
"allow deserialization of super classes in configured whitelist-class" in {
|
||||
"allow deserialization of super classes in configured allowed classes" in {
|
||||
val originalSerializer = ser.serializerFor(classOf[MyMessage])
|
||||
|
||||
val deserialized =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
# Internal class rename
|
||||
ProblemFilters.exclude[MissingClassProblem]("akka.serialization.jackson.JacksonSerializer$GadgetClassBlacklist")
|
||||
|
|
@ -127,10 +127,14 @@ akka.serialization.jackson {
|
|||
# }
|
||||
json-write-features {}
|
||||
|
||||
# Deprecated, use `allowed-class-prefix` instead
|
||||
whitelist-class-prefix = []
|
||||
|
||||
# Additional classes that are allowed even if they are not defined in `serialization-bindings`.
|
||||
# This is useful when a class is not used for serialization any more and therefore removed
|
||||
# from `serialization-bindings`, but should still be possible to deserialize.
|
||||
whitelist-class-prefix = []
|
||||
allowed-class-prefix = ${akka.serialization.jackson.whitelist-class-prefix}
|
||||
|
||||
|
||||
# settings for compression of the payload
|
||||
compression {
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ import akka.util.OptionVal
|
|||
@InternalApi private[akka] object JacksonSerializer {
|
||||
|
||||
/**
|
||||
* Using the blacklist from Jackson databind of class names that shouldn't be allowed.
|
||||
* Using the deny list from Jackson databind of class names that shouldn't be allowed.
|
||||
* Not nice to depend on implementation details of Jackson, but good to use the same
|
||||
* list to automatically have the list updated when new classes are added in Jackson.
|
||||
*/
|
||||
class GadgetClassBlacklist extends SubTypeValidator {
|
||||
class GadgetClassDenyList extends SubTypeValidator {
|
||||
|
||||
private def defaultNoDeserClassNames: java.util.Set[String] =
|
||||
SubTypeValidator.DEFAULT_NO_DESER_CLASS_NAMES // it's has protected visibility
|
||||
|
|
@ -204,10 +204,10 @@ import akka.util.OptionVal
|
|||
k -> transformer
|
||||
}
|
||||
}
|
||||
private val blacklist: GadgetClassBlacklist = new GadgetClassBlacklist
|
||||
private val whitelistClassPrefix = {
|
||||
private val denyList: GadgetClassDenyList = new GadgetClassDenyList
|
||||
private val allowedClassPrefix = {
|
||||
import akka.util.ccompat.JavaConverters._
|
||||
conf.getStringList("whitelist-class-prefix").asScala.toVector
|
||||
conf.getStringList("allowed-class-prefix").asScala.toVector
|
||||
}
|
||||
private val typeInManifest: Boolean = conf.getBoolean("type-in-manifest")
|
||||
// Calculated eagerly so as to fail fast
|
||||
|
|
@ -397,32 +397,32 @@ import akka.util.OptionVal
|
|||
className.length > 0 && className.charAt(className.length - 1) == '$'
|
||||
|
||||
private def checkAllowedClassName(className: String): Unit = {
|
||||
if (!blacklist.isAllowedClassName(className)) {
|
||||
if (!denyList.isAllowedClassName(className)) {
|
||||
val warnMsg = s"Can't serialize/deserialize object of type [$className] in [${getClass.getName}]. " +
|
||||
s"Blacklisted for security reasons."
|
||||
s"Disallowed (on deny list) for security reasons."
|
||||
log.warning(LogMarker.Security, warnMsg)
|
||||
throw new IllegalArgumentException(warnMsg)
|
||||
}
|
||||
}
|
||||
|
||||
private def checkAllowedClass(clazz: Class[_]): Unit = {
|
||||
if (!blacklist.isAllowedClass(clazz)) {
|
||||
if (!denyList.isAllowedClass(clazz)) {
|
||||
val warnMsg = s"Can't serialize/deserialize object of type [${clazz.getName}] in [${getClass.getName}]. " +
|
||||
s"Blacklisted for security reasons."
|
||||
s"Not allowed for security reasons."
|
||||
log.warning(LogMarker.Security, warnMsg)
|
||||
throw new IllegalArgumentException(warnMsg)
|
||||
} else if (!isInWhitelist(clazz)) {
|
||||
} else if (!isInAllowList(clazz)) {
|
||||
val warnMsg = s"Can't serialize/deserialize object of type [${clazz.getName}] in [${getClass.getName}]. " +
|
||||
"Only classes that are whitelisted are allowed for security reasons. " +
|
||||
"Configure whitelist with akka.actor.serialization-bindings or " +
|
||||
"akka.serialization.jackson.whitelist-class-prefix."
|
||||
"Only classes that are listed as allowed are allowed for security reasons. " +
|
||||
"Configure allowed classes with akka.actor.serialization-bindings or " +
|
||||
"akka.serialization.jackson.allowed-class-prefix."
|
||||
log.warning(LogMarker.Security, warnMsg)
|
||||
throw new IllegalArgumentException(warnMsg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the `serialization-bindings` as source for the whitelist.
|
||||
* Using the `serialization-bindings` as source for the allowed classes.
|
||||
* Note that the intended usage of serialization-bindings is for lookup of
|
||||
* serializer when serializing (`toBinary`). For deserialization (`fromBinary`) the serializer-id is
|
||||
* used for selecting serializer.
|
||||
|
|
@ -431,13 +431,13 @@ import akka.util.OptionVal
|
|||
*
|
||||
* If an old class is removed from `serialization-bindings` when it's not used for serialization
|
||||
* but still used for deserialization (e.g. rolling update with serialization changes) it can
|
||||
* be allowed by specifying in `whitelist-class-prefix`.
|
||||
* be allowed by specifying in `allowed-class-prefix`.
|
||||
*
|
||||
* That is also possible when changing a binding from a JacksonSerializer to another serializer (e.g. protobuf)
|
||||
* and still bind with the same class (interface).
|
||||
*/
|
||||
private def isInWhitelist(clazz: Class[_]): Boolean = {
|
||||
isBoundToJacksonSerializer(clazz) || isInWhitelistClassPrefix(clazz.getName)
|
||||
private def isInAllowList(clazz: Class[_]): Boolean = {
|
||||
isBoundToJacksonSerializer(clazz) || hasAllowedClassPrefix(clazz.getName)
|
||||
}
|
||||
|
||||
private def isBoundToJacksonSerializer(clazz: Class[_]): Boolean = {
|
||||
|
|
@ -452,8 +452,8 @@ import akka.util.OptionVal
|
|||
}
|
||||
}
|
||||
|
||||
private def isInWhitelistClassPrefix(className: String): Boolean =
|
||||
whitelistClassPrefix.exists(className.startsWith)
|
||||
private def hasAllowedClassPrefix(className: String): Boolean =
|
||||
allowedClassPrefix.exists(className.startsWith)
|
||||
|
||||
/**
|
||||
* Check that serialization-bindings are not configured with open-ended interfaces,
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") {
|
|||
}
|
||||
}
|
||||
|
||||
"allow deserialization of classes in configured whitelist-class-prefix" in {
|
||||
"allow deserialization of classes in configured allowed-class-prefix" in {
|
||||
val json = """{"name":"abc"}"""
|
||||
|
||||
val old = SimpleCommand("abc")
|
||||
|
|
@ -638,7 +638,7 @@ abstract class JacksonSerializerSpec(serializerName: String)
|
|||
"akka.serialization.jackson.JavaTestMessages$$TestMessage" = $serializerName
|
||||
}
|
||||
}
|
||||
akka.serialization.jackson.whitelist-class-prefix = ["akka.serialization.jackson.ScalaTestMessages$$OldCommand"]
|
||||
akka.serialization.jackson.allowed-class-prefix = ["akka.serialization.jackson.ScalaTestMessages$$OldCommand"]
|
||||
""")))
|
||||
with AnyWordSpecLike
|
||||
with Matchers
|
||||
|
|
@ -900,17 +900,17 @@ abstract class JacksonSerializerSpec(serializerName: String)
|
|||
event2.field2 should ===(17)
|
||||
}
|
||||
|
||||
"not allow serialization of blacklisted class" in {
|
||||
"not allow serialization of deny listed class" in {
|
||||
val serializer = serializerFor(SimpleCommand("ok"))
|
||||
val fileHandler = new FileHandler(s"target/tmp-${this.getClass.getName}")
|
||||
try {
|
||||
intercept[IllegalArgumentException] {
|
||||
serializer.manifest(fileHandler)
|
||||
}.getMessage.toLowerCase should include("blacklist")
|
||||
}.getMessage.toLowerCase should include("deny list")
|
||||
} finally fileHandler.close()
|
||||
}
|
||||
|
||||
"not allow deserialization of blacklisted class" in {
|
||||
"not allow deserialization of deny list class" in {
|
||||
withTransportInformation() { () =>
|
||||
val msg = SimpleCommand("ok")
|
||||
val serializer = serializerFor(msg)
|
||||
|
|
@ -918,18 +918,18 @@ abstract class JacksonSerializerSpec(serializerName: String)
|
|||
intercept[IllegalArgumentException] {
|
||||
// maliciously changing manifest
|
||||
serializer.fromBinary(blob, classOf[FileHandler].getName)
|
||||
}.getMessage.toLowerCase should include("blacklist")
|
||||
}.getMessage.toLowerCase should include("deny list")
|
||||
}
|
||||
}
|
||||
|
||||
"not allow serialization of class that is not in serialization-bindings (whitelist)" in {
|
||||
"not allow serialization of class that is not in serialization-bindings (allowed-class-prefix)" in {
|
||||
val serializer = serializerFor(SimpleCommand("ok"))
|
||||
intercept[IllegalArgumentException] {
|
||||
serializer.manifest(Status.Success("bad"))
|
||||
}.getMessage.toLowerCase should include("whitelist")
|
||||
}.getMessage.toLowerCase should include("allowed-class-prefix")
|
||||
}
|
||||
|
||||
"not allow deserialization of class that is not in serialization-bindings (whitelist)" in {
|
||||
"not allow deserialization of class that is not in serialization-bindings (allowed-class-prefix)" in {
|
||||
withTransportInformation() { () =>
|
||||
val msg = SimpleCommand("ok")
|
||||
val serializer = serializerFor(msg)
|
||||
|
|
@ -937,7 +937,7 @@ abstract class JacksonSerializerSpec(serializerName: String)
|
|||
intercept[IllegalArgumentException] {
|
||||
// maliciously changing manifest
|
||||
serializer.fromBinary(blob, classOf[Status.Success].getName)
|
||||
}.getMessage.toLowerCase should include("whitelist")
|
||||
}.getMessage.toLowerCase should include("allowed-class-prefix")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,11 +150,11 @@ object SerializationDocSpec {
|
|||
#//#date-time
|
||||
"""
|
||||
|
||||
val configWhitelist = """
|
||||
#//#whitelist-class-prefix
|
||||
akka.serialization.jackson.whitelist-class-prefix =
|
||||
val configAllowList = """
|
||||
#//#allowed-class-prefix
|
||||
akka.serialization.jackson.allowed-class-prefix =
|
||||
["com.myservice.event.OrderAdded", "com.myservice.command"]
|
||||
#//#whitelist-class-prefix
|
||||
#//#allowed-class-prefix
|
||||
"""
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue