Skip to content

feat: support IPv6-only clusters via configurable bind address#333

Merged
matthyx merged 1 commit into
mainfrom
dual-stack
Jun 10, 2026
Merged

feat: support IPv6-only clusters via configurable bind address#333
matthyx merged 1 commit into
mainfrom
dual-stack

Conversation

@matthyx

@matthyx matthyx commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Problem

The storage apiserver does not come up on IPv6-only clusters. Confirmed from a live pod: it listens on 0.0.0.0:8443 (the apiserver default), which cannot accept IPv6 connections.

Root cause: SecureServing.BindAddress is never set. The binary is config-driven, not flag-drivenmain.go runs stdlib flag.Parse() with only -kubeconfig registered, so passing --bind-address to the process is rejected (flag provided but not defined: -bind-address). Configuration is the only available lever.

Change

  • Add a serverBindAddress config field (pkg/config/config.go) and wire it into SecureServing.BindAddress in pkg/cmd/server/start.go, mirroring how serverBindPort is already handled.
  • Default "::" so IPv6-only clusters serve with no Helm changes. With bindv6only=0 (the Linux default) "::" also accepts IPv4/dual-stack traffic, so existing clusters keep working.
  • Escape hatch: set serverBindAddress: "0.0.0.0" to force IPv4-only binding.
  • Add ::1 to the self-signed cert loopback SANs (start.go) for IPv6 loopback TLS on the self-signed path.

Testing

  • go build ./... passes.
  • go test ./pkg/config/... passes (updated TestLoadConfig expectation for the new default).
  • Pending: maintainer/customer confirmation that the pod serves on a real IPv6-only cluster after deploy.

Notes

  • The serverBindAddress value flows through the existing config mount (/etc/config/config.json); the Helm chart can expose it but no chart change is required for the default to take effect.
  • Follow-ups (not in this PR): IPv6-correct NetworkPolicy CIDR (/128) and IPv6-safe HTTPEndpoint URL parsing — data-correctness fixes tracked separately.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added configurable server bind address setting with IPv6 default
    • Enhanced self-signed certificate generation to support both IPv4 and IPv6 loopback addresses for improved compatibility

The storage apiserver never set a bind address, leaving it at the
apiserver default 0.0.0.0, which cannot accept connections on IPv6-only
clusters. The binary takes no apiserver flags (main.go runs flag.Parse
with only -kubeconfig registered), so --bind-address cannot be passed at
runtime; configuration is the only lever.

Add a serverBindAddress config field wired into SecureServing.BindAddress
(mirroring the existing serverBindPort), defaulting to "::" so IPv6-only
clusters serve out of the box. With bindv6only=0 (Linux default) "::"
also accepts IPv4/dual-stack traffic; operators can force IPv4-only with
serverBindAddress: "0.0.0.0".

Also add ::1 to the self-signed cert loopback SANs so IPv6 loopback TLS
works on the self-signed path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds IPv6 dual-stack support to the storage server by introducing a configurable ServerBindAddress setting and expanding the loopback IP addresses used for self-signed certificate generation. The new config field defaults to :: (IPv6 any address) and is applied to secure serving bind configuration during server startup.

Changes

IPv6 Dual-Stack Server Bind Address

Layer / File(s) Summary
Config field, default, and test
pkg/config/config.go, pkg/config/config_test.go
Config struct adds ServerBindAddress field with serverBindAddress mapping. LoadConfig sets default to ::. Test expectations updated to include the new default value.
Server startup integration
pkg/cmd/server/start.go
NewWardleServerOptions conditionally sets SecureServing.BindAddress from parsed ServerBindAddress. MaybeDefaultWithSelfSignedCerts now receives both IPv4 (127.0.0.1) and IPv6 (::1) loopback addresses for certificate SAN/host generation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

A rabbit hops through IPv6 lands so bright,
Binding to ::—dual-stack delight!
With loopback from four and six, the certs align,
Secure servers now dance, in perfect tandem line. 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 describes the main change: enabling IPv6-only cluster support through a configurable server bind address, which matches the core objectives and modifications across all three modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dual-stack

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/cmd/server/start.go`:
- Around line 127-129: The code currently ignores an unparsable
o.StorageConfig.ServerBindAddress in the ParseIPSloppy block; change the
behavior to fail fast by adding validation in the Validate() method: call
netutils.ParseIPSloppy(o.StorageConfig.ServerBindAddress) there and if the
provided ServerBindAddress is non-empty and ParseIPSloppy returns nil, return a
descriptive error; keep the current assignment to
o.RecommendedOptions.SecureServing.BindAddress only when ParseIPSloppy succeeds.
Ensure you reference ParseIPSloppy, o.StorageConfig.ServerBindAddress,
o.RecommendedOptions.SecureServing.BindAddress and the Validate() method when
implementing this check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22e76f6e-8e0f-4a8e-bf2c-f1cde062e46f

📥 Commits

Reviewing files that changed from the base of the PR and between b05fbb8 and 2c2f605.

📒 Files selected for processing (3)
  • pkg/cmd/server/start.go
  • pkg/config/config.go
  • pkg/config/config_test.go

Comment thread pkg/cmd/server/start.go
Comment on lines +127 to +129
if bindIP := netutils.ParseIPSloppy(o.StorageConfig.ServerBindAddress); bindIP != nil {
o.RecommendedOptions.SecureServing.BindAddress = bindIP
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast on invalid serverBindAddress instead of silently falling back.

At Line 127, an unparsable value is ignored and the server continues with the default bind address. A typo in config can unintentionally expose/listen on an unintended interface and break IPv6-only intent. Validate this in Validate() and return an error.

Suggested fix
 func (o WardleServerOptions) Validate(_ []string) error {
 	var errors []error
 	errors = append(errors, o.RecommendedOptions.Validate()...)
 	errors = append(errors, o.ComponentGlobalsRegistry.Validate()...)
+	if ip := netutils.ParseIPSloppy(o.StorageConfig.ServerBindAddress); ip == nil {
+		errors = append(errors, fmt.Errorf("invalid serverBindAddress %q: expected an IP literal", o.StorageConfig.ServerBindAddress))
+	}
 	return utilerrors.NewAggregate(errors)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cmd/server/start.go` around lines 127 - 129, The code currently ignores
an unparsable o.StorageConfig.ServerBindAddress in the ParseIPSloppy block;
change the behavior to fail fast by adding validation in the Validate() method:
call netutils.ParseIPSloppy(o.StorageConfig.ServerBindAddress) there and if the
provided ServerBindAddress is non-empty and ParseIPSloppy returns nil, return a
descriptive error; keep the current assignment to
o.RecommendedOptions.SecureServing.BindAddress only when ParseIPSloppy succeeds.
Ensure you reference ParseIPSloppy, o.StorageConfig.ServerBindAddress,
o.RecommendedOptions.SecureServing.BindAddress and the Validate() method when
implementing this check.

@github-actions

Copy link
Copy Markdown

Summary:

  • License scan: failure
  • Credentials scan: failure
  • Vulnerabilities scan: failure
  • Unit test: success
  • Go linting: failure

@matthyx matthyx merged commit 11472e5 into main Jun 10, 2026
7 checks passed
@matthyx matthyx deleted the dual-stack branch June 10, 2026 07:38
@matthyx matthyx moved this to To Archive in KS PRs tracking Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: To Archive

Development

Successfully merging this pull request may close these issues.

1 participant