Skip to content

fix(apiserver): keep the unauthenticated plain listener on loopback - #215

Merged
Andrei Kvapil (kvaps) merged 2 commits into
mainfrom
fix/apiserver-plain-localhost
Nov 2, 2026
Merged

Andrei Kvapil (kvaps) merged 2 commits into
mainfrom
fix/apiserver-plain-localhost

Conversation

@kvaps

@kvaps Andrei Kvapil (kvaps) commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

The apiserver's plain-HTTP listener serves the whole LINSTOR API with no client certificate, and it bound every pod interface by default. Only its absence from the Service kept it off the cluster network, and nothing enforced that: any pod that could reach an apiserver pod IP had full read-write access.

The listener now defaults to 127.0.0.1:3370, in the apiserver and in the controller's single-binary REST mode, and the stand manifest passes the same and no longer declares the container port. kubectl port-forward keeps working, since it connects inside the pod's network namespace. Every in-repo client of the plain port already goes through a port-forward; CSI and piraeus use the mutual-TLS port.

The controller's single-binary REST mode (--enable-rest-api=true) has no TLS listener, so with the new default it has no network endpoint at all: in-cluster clients get connection refused. Nothing deployed runs that mode; --rest-bind-address=:3370 brings the old behaviour back, exposure included.

Fixes #201

Testing

  • New tests pin the default of both binaries, the flag registration that has to use it, and the value the stand manifest passes, to a loopback address. Setting any of them back to :3370 reddens a test.
  • go test ./...: green apart from the known server did not stop within 2s after cancel flake in pkg/rest.
  • golangci-lint on the changed lines: clean.
  • Not run on a stand.

Summary by CodeRabbit

  • Security
    • The unauthenticated HTTP debug API now binds to 127.0.0.1:3370 by default, limiting access to the local pod.
    • Invalid bind addresses are rejected at startup. Configuring a non-loopback address triggers a warning.
  • Documentation
    • Updated connection guidance to use kubectl port-forward and connect through localhost:3370.

@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a1b5989c-f394-4b26-ba2f-0bd56bebdbd9
Commits

Reviewing files that changed from the base of the PR and between 1652387 and 972face.

Files selected for processing: 13
  • cmd/apiserver/bind.go
  • cmd/apiserver/bind_flag_test.go
  • cmd/apiserver/bind_test.go
  • cmd/apiserver/main.go
  • cmd/controller/bind.go
  • cmd/controller/bind_flag_test.go
  • cmd/controller/bind_test.go
  • cmd/controller/main.go
  • docs/layer-stack.md
  • docs/usage.md
  • pkg/rest/server.go
  • stand/blockstor-apiserver-deploy.yaml
  • tests/e2e/tiebreaker-r-d-cleanup.sh

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The apiserver and controller now default the unauthenticated plain REST listener to loopback. Startup validates configured addresses and warns when they are off loopback. The apiserver deployment manifest and related documentation now describe loopback binding and port-forward access.

Changes: Plain REST listener binding

Layer / File(s) Summary
Address validation and startup wiring
cmd/apiserver/bind.go, cmd/apiserver/main.go, cmd/apiserver/*_test.go, cmd/controller/bind.go, cmd/controller/main.go, cmd/controller/*_test.go
Both binaries default the plain REST listener to 127.0.0.1:3370 and validate configured addresses. Empty or malformed addresses produce errors. Loopback addresses and localhost are not reported as exposed; other valid hosts are. Startup warns for off-loopback addresses. Tests cover the defaults, validation, and flag registration.
Loopback deployment and access guidance
stand/blockstor-apiserver-deploy.yaml, cmd/apiserver/bind_test.go, docs/layer-stack.md, docs/usage.md, pkg/rest/server.go, tests/e2e/tiebreaker-r-d-cleanup.sh
The apiserver manifest binds the debug listener to loopback and removes its rest-debug container port declaration. The manifest test checks configured addresses. Documentation and comments describe loopback binding and port-forward access.

Priority: High

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: High

Merge Risk: Minimal · up to 972fa

The plain HTTP listener defaults to loopback. No merge-blocking regression was identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surv… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: keeping the unauthenticated plain listener on loopback.
Linked Issues check Passed Issue #201 requires preventing unauthenticated plain-HTTP access from other pods. The PR changes the apiserver default to 127.0.0.1:3370, applies the same default to the controller single-binary RES…
Out of Scope Changes check Passed The changed code, manifest, tests, documentation, and comments all support the loopback-only plain-HTTP listener objective. Startup validation and warnings support safe handling of explicit non-loopba…
Full details: Linked Issues check

Explanation

Issue #201 requires preventing unauthenticated plain-HTTP access from other pods. The PR changes the apiserver default to 127.0.0.1:3370, applies the same default to the controller single-binary REST mode, and updates the stand manifest to pass the loopback address. The PR adds tests for both defaults, flag registration, and the manifest value. These changes implement the loopback option in #201. The supplied PR evidence is used because the repository diff was unavailable.

Full details: Out of Scope Changes check

Explanation

The changed code, manifest, tests, documentation, and comments all support the loopback-only plain-HTTP listener objective. Startup validation and warnings support safe handling of explicit non-loopback addresses. No unrelated change is demonstrated.

Full details: Docstring Coverage

Explanation

Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 13 functions across 10 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The plain-HTTP listener serves the whole API without a client
certificate. It bound every pod interface by default, so any pod that
could reach the apiserver pod IP had full read-write access; only its
absence from the Service kept it off the cluster network, and nothing
enforced that.

The default is now 127.0.0.1:3370, in the apiserver and in the
controller's legacy REST mode, and the stand manifest passes the same.
kubectl port-forward still reaches it, since it connects inside the
pod's network namespace. Tests pin the default and the manifest value
to a loopback address.

Fixes #201

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/apiserver-plain-localhost branch from 388cb02 to 59dace5 Compare October 26, 2026 13:52
@kvaps
Andrei Kvapil (kvaps) changed the base branch from fix/csi-clone-and-restore-idempotency to main October 26, 2026 13:52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Andrei Kvapil (@kvaps) NOT LGTM, but only for a missing license header. The fix itself works.

I tested it on 59dace5, which sits directly on current main. The unit tests in both binaries pass and golangci-lint finds nothing on the new lines. Then I broke the fix piece by piece: the default as :3370, 0.0.0.0:3370 and [::]:3370, the flag registered with a literal or through flag.String, and the manifest argument back to :3370. Every one turned a test red. In-repo clients of the plain port all go through kubectl port-forward. CSI, piraeus and the observability scenarios dial 3371, so nothing in the cluster loses access, and the green e2e lanes on this head agree.

The blocker: four new files have no // SPDX-License-Identifier: Apache-2.0 line. These are bind.go and bind_test.go in both cmd/apiserver and cmd/controller. AGENTS.md:29-30 asks for it on every blockstor Go file, and these are the only Go files under cmd/ and pkg/ without it.

Non-blocking:

  • The binaries still accept any --rest-bind-address without a word. A deployment applied from the old stand manifest passes :3370 explicitly, so it stays exposed until the manifest is re-applied. An empty value is worse. The TLS flag treats empty as disabled, but net.Listen("tcp", "") binds every interface on a random port. So an operator who tries to switch the plain listener off gets it on all addresses. A startup warning for a non-loopback address, plus rejecting an empty one, would cover both.
  • This conflicts with #216 in cmd/controller/main.go, on the line above the rest-bind-address flag. If ":3370" comes back while resolving it, TestPlainListenerFlagUsesTheLoopbackDefault catches that. Also, the networking page in #205 says the manifests bind 3370 on every pod address. After this merges it should say loopback.

…rn off loopback

An empty --rest-bind-address reached net.Listen and bound every
interface on a random port, so turning the listener off the way the
TLS flag allows opened it wider. It is refused now, and an address off
loopback, such as the :3370 an older manifest still passes, logs a
warning at start. The new files carry the license header.

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
@kvaps

Copy link
Copy Markdown
Member Author

Thanks, head 972face.

All four files carry the license header now. Both binaries also refuse an empty --rest-bind-address at start, and log a warning when the address is off loopback, so a deployment still on the old manifest's :3370 says so. TestCheckPlainBindAddress covers empty, bare port, loopback in all three spellings, and the exposed shapes.

I'll resolve the #216 conflict in cmd/controller/main.go on whichever lands second, and the networking page goes to loopback with the release that ships this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Andrei Kvapil (@kvaps) LGTM. The license header is in on every Go file under cmd/ and pkg/, and the new check for empty and off-loopback addresses works.

I tested 972face on a trial merge with main at 7bb6606. go vet is clean and the unit tests in both binaries pass. I broke the new code to see the tests catch it: marking localhost as exposed or inverting the loopback check turns TestCheckPlainBindAddress red in both binaries. Putting :3370 back in the controller's flag registration or in the stand manifest still reddens the older tests.

The red integration job on this head is not caused by this PR. The harness builds rest.Server with its own address and never goes through cmd/, and the only pkg/rest change is a comment. The same job is red on main at 7bb6606, with other GroupG and GroupKWF timeouts.

Non-blocking:

  • No test covers the call itself. If I replace checkPlainBindAddress with false, nil in both main.go files, every test stays green, and an empty --rest-bind-address binds every interface again. bind_flag_test.go makes the same argument for the flag default, so its AST walk could require the call too.
  • The two bind.go files are byte-identical. One copy in pkg/rest would keep them from drifting.
  • The conflict with #216 in cmd/controller/main.go is still there. It's only the --enable-rest-api help text above the flag.
  • The site in #205 still says the manifests bind port 3370 on every pod address and that any pod reaching the pod IP can use it. That's in the networking page (port table, API server section, the list at the end) and in the linstor client section of the compatibility page. After this merges it's no longer true.

@kvaps
Andrei Kvapil (kvaps) merged commit 27a8520 into main Nov 2, 2026
15 of 16 checks passed
Andrei Kvapil (kvaps) added a commit that referenced this pull request Nov 2, 2026
Main now binds the unauthenticated port 3370 to the pod's loopback
(#215) and runs the snapshot stream on 3372 over mutual TLS with a CA
of its own (#219). Say so on the networking, compatibility and
installation pages, add the stream certificate and the new bind
address to the upgrade note, and list the stream TLS failures that
keep a satellite from starting.

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
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.

apiserver: unauthenticated plain-HTTP listener on :3370 reachable from any pod in the cluster

2 participants