Skip to content

Add --host CLI flag and opt-in TLS to plugin gRPC server (CWE-319) - #162

Open
OffByQuant wants to merge 1 commit into
google:masterfrom
OffByQuant:security/grpc-host-tls-flags
Open

OffByQuant wants to merge 1 commit into
google:masterfrom
OffByQuant:security/grpc-host-tls-flags

Conversation

@OffByQuant

Copy link
Copy Markdown

Summary

The Python plugin server (plugin_server.py) bound the gRPC PluginService surface via add_insecure_port on a hardcoded _HOST = '127.0.0.1', with no TLS plumbing and no CLI flag for the bind address. Two practical issues flow from that combination:

  • Non-loopback bind requires a source edit. Operators running distributed deployments (Java orchestrator on host A drives plugin servers on hosts B/C/D) had to edit plugin_server.py to change _HOST. They will not know to also add TLS in that diff because the project ships no scaffolding for it — and the loopback bind is currently the only thing keeping the unauthenticated PluginService surface off the network.
  • Container / network exposure has no encrypted-transport fallback. docker run -p 34567:34567, a NodePort / LoadBalancer Service, or any iptables rule forwarding inbound traffic to the loopback port erases the loopback mitigation without obviously triggering any security review. There is no opt-in to TLS to fall back on once that happens (CWE-319 — Cleartext Transmission of Sensitive Information).

This PR makes both configuration changes explicit and adds a defensive WARNING for the dangerous combination.

Changes

All in plugin_server/py/plugin_server.py:

  • --host flag (default 127.0.0.1). Replaces the hardcoded constant. Default is unchanged so existing deployments are unaffected. Operators who need a non-loopback bind do so via flag rather than source edit, which lets the help text flag the security tradeoff.
  • --tls_cert_path and --tls_key_path flags (both default empty). Opt-in TLS. When both are set, the new _bind_server_port helper reads the PEM files and binds via add_secure_port with grpc.ssl_server_credentials. When neither is set, falls back to add_insecure_port (unchanged behavior). When exactly one of the two is set, the server refuses to start so an operator who fat-fingered one flag does not silently get plaintext.
  • Startup WARNING when --host is non-loopback and TLS is not configured. The message names both flags and the loopback fallback so the operator has actionable next steps.

Default behavior is unchanged

For any operator running with the flags unset:

  • --host defaults to 127.0.0.1 (matches the prior _HOST constant).
  • --tls_cert_path / --tls_key_path default to empty → add_insecure_port is called exactly as before.
  • No new dependencies. grpc.ssl_server_credentials is part of the gRPC Python package the project already imports.

What this PR deliberately does NOT do

The disclosure that motivated this patch also called for an authentication interceptor (shared-secret token via authorization metadata, constant-time comparison) and a secure-by-default --allow_unauthenticated opt-out. Both were excluded from this PR for two reasons:

  1. Adding auth on the Python side requires a coordinated change to the Java orchestrator's PluginServiceClient so it attaches the token as a Metadata header on every outbound RPC. That is a parallel patch and should land with maintainer guidance on whether the Java side wants a flag-driven or config-driven knob.
  2. Refusing to start without --auth_token is a breaking change for every existing operator on upgrade. That is a project-policy decision, not a unilateral fix to ship in a security PR.

Happy to follow up with a separate PR for the auth piece (Python interceptor + Java client metadata + coordinated CLI flag) once maintainers signal which direction they want to take.

Likewise on the Java client side: operators who flip the new TLS flags will also need to configure the Java PluginServiceClient to dial with .useTransportSecurity() and a matching cert trust. That parallel change is out of scope here; the help text on --tls_cert_path and --tls_key_path could be extended in a follow-up to call this out once the Java-side flag exists.

Test plan

  • Default invocation (no new flags) starts the plugin server identically to today: add_insecure_port on 127.0.0.1:34567, Java orchestrator dials it via plaintext as before.
  • --host 0.0.0.0 (without TLS) starts the server, logs the WARNING, and is reachable from non-loopback callers (matching today's behavior for operators who edited _HOST).
  • --tls_cert_path /path/cert.pem --tls_key_path /path/key.pem (with a self-signed cert generated via openssl req -x509 ...) starts the server with add_secure_port and serves TLS. (Java client coordination is out of scope; verified via grpcurl -insecure -cacert ... against the running server.)
  • --tls_cert_path /path/cert.pem alone (key omitted) refuses to start with the message "must both be set or both empty."
  • No project-wide pytest harness exists for plugin_server.py itself (plugin_service_test.py covers the servicer, not bootstrapping); the existing plugin_service_test suite continues to pass since this PR does not touch the servicer.

References

  • CWE-319: Cleartext Transmission of Sensitive Information
  • gRPC Python — Server-Side TLS docs (grpc.ssl_server_credentials)
  • gRPC Authentication overview

@google-cla

google-cla Bot commented Apr 29, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@OffByQuant

Copy link
Copy Markdown
Author

@googlebot I signed it!

The Python plugin server bound the gRPC PluginService surface via
add_insecure_port on a hardcoded _HOST = '127.0.0.1', with no TLS
plumbing and no flag for the bind address. Two practical issues
flowed from that:

  * Operators who need a non-loopback bind (distributed deployments
    where the Java orchestrator on host A drives plugin servers on
    hosts B/C/D) had to edit plugin_server.py to change _HOST. They
    will not know to add TLS in that diff because the project ships
    no scaffolding for it — and the loopback bind is currently the
    only thing keeping the unauthenticated PluginService surface
    off the network.

  * Container deployments that publish the gRPC port (`docker run -p
    34567:34567`, NodePort/LoadBalancer Services), or any iptables
    rule forwarding inbound traffic to the loopback port, similarly
    erase the loopback mitigation without obviously triggering any
    security review. The existing code offers no encrypted-transport
    knob to fall back on once that happens (CWE-319).

This patch makes both configuration changes explicit:

  * --host: replaces the hardcoded constant. Default '127.0.0.1' so
    existing deployments are unaffected. Operators who need a
    non-loopback bind do so via flag rather than source edit, which
    means the help text can flag the security tradeoff and a
    startup WARNING fires when the combination of non-loopback +
    no-TLS is selected.

  * --tls_cert_path / --tls_key_path: opt-in TLS. When both are
    set, _bind_server_port reads the PEM files and binds via
    add_secure_port with grpc.ssl_server_credentials. When neither
    is set, falls back to add_insecure_port (unchanged behavior).
    When exactly one is set, the server refuses to start so an
    operator who fat-fingered one flag does not silently get
    plaintext.

What this patch does NOT do, deliberately:

  * No --auth_token / authentication interceptor. Adding one
    requires a coordinated change to the Java orchestrator's
    PluginServiceClient (Metadata header attaching the token) and
    is a project-policy decision (secure-by-default refusal-to-start
    is a breaking change for every existing operator). Out of scope
    for this PR; happy to follow up with a separate one if
    maintainers want to take that route.

  * No client-side TLS enablement on the Java side. Operators who
    flip the new TLS flags also need to configure the Java
    PluginServiceClient to dial with .useTransportSecurity() and a
    matching cert trust. That is a parallel change and should land
    with maintainer guidance on whether the Java side wants a
    flag-driven or config-driven knob.

Default behavior is unchanged for any operator running with the
flags unset. The patch is local to plugin_server.py.
@OffByQuant
OffByQuant force-pushed the security/grpc-host-tls-flags branch from bd1525d to 6d96b57 Compare April 29, 2026 07:17
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