Removing, deprecating and replacing usage of black/whitelist (#29254)

This commit is contained in:
Johan Andrén 2020-06-18 15:48:28 +02:00 committed by GitHub
parent 25ea7b7f5e
commit 1e9e984727
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 168 additions and 144 deletions

View file

@ -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

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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() {

View file

@ -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 = {

View file

@ -0,0 +1,2 @@
# Internal class rename
ProblemFilters.exclude[MissingClassProblem]("akka.remote.NotWhitelistedClassRemoteDeploymentAttemptException")

View file

@ -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 = []

View file

@ -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(", ")}]")

View file

@ -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))
}
}

View file

@ -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)

View file

@ -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")
"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)
}
}
}

View file

@ -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)

View file

@ -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 =

View file

@ -0,0 +1,2 @@
# Internal class rename
ProblemFilters.exclude[MissingClassProblem]("akka.serialization.jackson.JacksonSerializer$GadgetClassBlacklist")

View file

@ -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 {

View file

@ -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,

View file

@ -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")
}
}

View file

@ -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
"""
}