Skip to content

Reject inbound remote deployment when it is not enabled - #1

Draft
claude-code-staging[bot] wants to merge 1 commit into
mainfrom
claude/claude-security-fix-find_staging_01a5wye3kh24yqd3wdxk7nff-koopq1
Draft

claude-code-staging[bot] wants to merge 1 commit into
mainfrom
claude/claude-security-fix-find_staging_01a5wye3kh24yqd3wdxk7nff-koopq1

Conversation

@claude-code-staging

Copy link
Copy Markdown

Motivation

pekko.remote.use-unsafe-remote-features-outside-cluster is documented in
remote/src/main/resources/reference.conf as disabling remote watch and deployment when Pekko Cluster is
not used. For deployment, that was only enforced on the sending side:
RemoteActorRefProvider.hasClusterOrUseUnsafe is consulted when this node deploys actors on other
nodes, but RemoteSystemDaemon was registered under /remote unconditionally and accepted an inbound
DaemonMsgCreate whenever untrusted-mode was off.

A node running pekko.actor.provider = remote with default settings (use-unsafe-remote-features-outside-cluster = off,
untrusted-mode = off, enable-allow-list = off) would therefore instantiate and start any Actor /
IndirectActorProducer subclass on its classpath for any peer that completed the remoting handshake, with
peer-chosen constructor arguments, supervisor, router configuration (including nrOfInstances) and
dispatcher — while the documentation said remote deployment was disabled in that configuration. Accepted
deployments were only logged at debug level, so an operator had no audit trail of peer-initiated actor
creation.

Modification

  • RemoteSystemDaemon evaluates the same condition as RemoteActorRefProvider.hasClusterOrUseUnsafe and
    drops an inbound DaemonMsgCreate with a LogMarker.Security warning when remote deployment is not
    enabled, before doCreateActor is reached. The check is placed ahead of the allow list, which narrows an
    already enabled remote deployment rather than enabling it.
  • The rejected path is the raw string from the peer, so control, format, line and paragraph separator
    characters are replaced and the value is truncated before it is logged.
  • Accepted deployments are logged once the actor has been created, with a LogMarker.Security: at info
    level when deployment was enabled without Cluster (the opt-in unsafe configuration worth auditing), and
    at debug level with Cluster, where one deployment per remote deployed routee would otherwise flood the
    default logs.
  • reference.conf, remoting.md, remoting-artery.md and remote-security.md state that the setting
    applies to inbound deployment as well, that only deployment is symmetric (remote watch is gated on the
    watching side only), and that neither gate authenticates the requesting peer — network controls, TLS
    with mutual authentication and the allow list still matter. A migration note covers upgrades where only
    the deploying node has the setting enabled.

Both logs name the class the peer asked for (props.clazz, the value that goes on the wire) rather than
props.actorClass(), so that producing a security log never runs peer-selected code.

Deliberately out of scope, and unchanged by this PR: DaemonMsgCreateSerializer.fromBinary still resolves
the peer-named class and deserializes the peer-supplied constructor arguments before the daemon sees the
message. That affects Cluster nodes equally and is not covered by the deployment gate, so it is a separate
change. Inbound deployment is also still accepted from any peer that can associate, including a non-member
of the cluster — the gate decides whether deployment is enabled, not who may request it. The added warning
in the docs says so.

Result

A node that neither uses Cluster nor sets use-unsafe-remote-features-outside-cluster = on no longer
creates actors that a peer asks it to create, which restores the documented contract, and peer-initiated
actor creation is auditable where it is allowed.

This is a behaviour change for non-Cluster setups that enabled the setting only on the deploying node:
the target now drops the request. The migration guide says so and names the symptom.

Tests

  • Added remote/src/test/scala/org/apache/pekko/remote/InboundRemoteDeploymentSpec.scala. It drives real
    end-to-end remote deployment (config-driven actorOf -> useActorOnNode -> wire -> /remote daemon)
    from a node that has the setting enabled onto a node under test:

    • setting off, no Cluster: no actor is created (Identify answers ActorIdentity(_, None)) and a
      LogMarker.Security warning is logged;
    • setting off plus enable-allow-list = on with the deployed class allow listed: still rejected, which
      pins down that the allow list does not enable remote deployment;
    • setting on: the actor is created, replies over remoting, and the LogMarker.Security info log is
      emitted.

    The assertions match on the log event rather than only its text, so the security marker and the level are
    both checked. The first two cases fail without the production change.

  • Not run - no build tooling available in the environment this was prepared in: sbt is not installed and
    the dependency repositories are unreachable, so sbt "remote/testOnly org.apache.pekko.remote.InboundRemoteDeployment*",
    sbt +mimaReportBinaryIssues, scalafmt --mode diff-ref=origin/main, sbt headerCreateAll and
    sbt docs/paradox could not be executed and need to run in CI. No public API, binary shape or
    serialization format was changed: RemoteSystemDaemon's constructor signature is untouched and the new
    members are private.

References

None - enforce the documented use-unsafe-remote-features-outside-cluster deployment gate on the
receiving side.


Generated by Claude Code

Motivation:
`pekko.remote.use-unsafe-remote-features-outside-cluster` documents that
remote watch and deployment are disabled when Pekko Cluster is not used,
but for deployment that was only enforced when a node deploys actors on
other nodes. `RemoteSystemDaemon` accepted a `DaemonMsgCreate` from any
peer that completed the remoting handshake whenever `untrusted-mode` was
off, so a node running `pekko.actor.provider = remote` with default
settings would instantiate and start any Actor subclass on its classpath
with peer-chosen constructor arguments, supervisor, router configuration
and dispatcher. Accepted deployments were only logged at debug level, so
there was no audit trail either.

Modification:
`RemoteSystemDaemon` now evaluates the same condition as
`RemoteActorRefProvider.hasClusterOrUseUnsafe` and drops a
`DaemonMsgCreate` with a `LogMarker.Security` warning when remote
deployment is not enabled, before any actor is created. The requested
path comes straight off the wire, so control, format and separator
characters are replaced and it is truncated before being logged.

Accepted deployments are logged once the actor has been created, also
with a `LogMarker.Security`: at info level when deployment was enabled
without Cluster, and at debug level with Cluster, where one deployment
per remote deployed routee would otherwise flood the default logs.

reference.conf and the remoting and remote-security docs now state that
the setting applies to inbound deployment as well, that the allow list
narrows an already enabled remote deployment rather than enabling it,
and that neither gate authenticates the requesting peer. A migration
note covers the case where only the deploying node has it enabled.

Result:
A node that neither uses Cluster nor opts in no longer creates actors
that a peer asks it to create, and peer-initiated actor creation is
auditable where it is allowed.

Tests:
- Added remote/src/test/scala/org/apache/pekko/remote/InboundRemoteDeploymentSpec.scala:
  an end-to-end remote deployment from a node that has the setting
  enabled onto a node that has it off, plus a variant where the target
  also has the allow list enabled, asserts that no actor is created and
  that a security marked warning is logged. With the setting on, the
  actor is created, replies over remoting, and the security marked info
  log is emitted.
- Not run - no build tooling available in this environment: sbt is not
  installed and the dependency repositories are unreachable, so
  `sbt remote/testOnly`, `sbt +mimaReportBinaryIssues`, `scalafmt`,
  `sbt headerCreateAll` and `sbt docs/paradox` could not be executed.
  No public API, binary shape or serialization format was changed.

References:
None - enforce the documented `use-unsafe-remote-features-outside-cluster`
deployment gate on the receiving side
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant