Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions charts/kagenti-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ defaults:
# Cluster DNS is kept direct by proxy-init itself (it reads the pod's
# /etc/resolv.conf nameservers), so there is no in-cluster CIDR knob to set —
# works on Kind / OpenShift / EKS / NodeLocal-DNSCache with no per-cluster config.
# iptables backend override, injected as IPTABLES_CMD. Empty (default) lets
# proxy-init auto-detect from /proc/modules (iptable_nat loaded => legacy, as
# on Kind/kubeadm; absent => nft, as on OpenShift/ROSA). Set to "iptables"
# (nft) or "iptables-legacy" to force a backend where detection is undesired.
iptablesCmd: ""

# Resource defaults (conservative for dev)
# Note: requests must be <= limits
Expand Down
3 changes: 3 additions & 0 deletions kagenti-operator/internal/webhook/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func CompiledDefaults() *PlatformConfig {
// Transparent listener port — must match the authbridge proxy-sidecar
// preset (listener.transparent_proxy_addr default :8082).
TransparentPort: 8082,
// Empty by default: proxy-init auto-detects the iptables backend from
// /proc/modules. Set (e.g. "iptables") to force a backend per-platform.
IptablesCmd: "",
},
Resources: ResourcesConfig{
EnvoyProxy: corev1.ResourceRequirements{
Expand Down
2 changes: 2 additions & 0 deletions kagenti-operator/internal/webhook/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ func logConfig(cfg *PlatformConfig, source string) {
"uid", cfg.Proxy.UID,
"inboundProxyPort", cfg.Proxy.InboundProxyPort,
"adminPort", cfg.Proxy.AdminPort,
"transparentPort", cfg.Proxy.TransparentPort,
"iptablesCmd", cfg.Proxy.IptablesCmd,
)
log.Info("[config] resources.envoyProxy",
"requests", cfg.Resources.EnvoyProxy.Requests,
Expand Down
17 changes: 17 additions & 0 deletions kagenti-operator/internal/webhook/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ type ProxyConfig struct {
// external TCP egress to. It MUST match the authbridge proxy-sidecar
// listener.transparent_proxy_addr (default :8082).
TransparentPort int32 `json:"transparentPort" yaml:"transparentPort"`

// IptablesCmd optionally pins the iptables backend the proxy-init script
// uses, injected as the IPTABLES_CMD env var (omitted when empty). Empty
// (default) lets the script auto-detect from /proc/modules (iptable_nat
// loaded => legacy, as on Kind/kubeadm; absent => nft, as on OpenShift/
// ROSA). Set to "iptables" (nft) or "iptables-legacy" to force a backend
// where auto-detection is wrong or undesired.
IptablesCmd string `json:"iptablesCmd" yaml:"iptablesCmd"`
}

type ResourcesConfig struct {
Expand Down Expand Up @@ -131,6 +139,15 @@ func (c *PlatformConfig) Validate() error {
if c.Proxy.UID < 1 {
return fmt.Errorf("proxy.uid must be >= 1 (got %d): the proxy must not run as root and the egress-enforcement exemption keys on this UID", c.Proxy.UID)
}
// IptablesCmd, when set, pins the proxy-init iptables backend (IPTABLES_CMD).
// Restrict overrides to the binaries shipped in the proxy-init image so a
// chart typo fails fast at operator startup rather than as a per-injected-pod
// init crash. Empty is the default — proxy-init auto-detects from /proc/modules.
switch c.Proxy.IptablesCmd {
case "", "iptables", "iptables-nft", "iptables-legacy":
default:
return fmt.Errorf("proxy.iptablesCmd %q is not a recognized backend (want one of: \"\" (auto-detect), iptables, iptables-nft, iptables-legacy)", c.Proxy.IptablesCmd)
}
if c.Images.EnvoyProxy == "" {
return fmt.Errorf("images.envoyProxy is required")
}
Expand Down
31 changes: 31 additions & 0 deletions kagenti-operator/internal/webhook/config/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,34 @@ func TestValidate_TransparentPort(t *testing.T) {
})
}
}

// IptablesCmd pins the proxy-init backend; only the binaries shipped in the
// image (plus "" = auto-detect) are accepted, so a chart typo fails at operator
// startup rather than as a per-injected-pod init crash.
func TestValidate_IptablesCmd(t *testing.T) {
tests := []struct {
name string
cmd string
wantErr bool
}{
{"empty (auto-detect) ok", "", false},
{"iptables (nft) ok", "iptables", false},
{"iptables-nft ok", "iptables-nft", false},
{"iptables-legacy ok", "iptables-legacy", false},
{"typo rejected", "iptable", true},
{"arbitrary rejected", "nft", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := CompiledDefaults()
c.Proxy.IptablesCmd = tt.cmd
err := c.Validate()
if tt.wantErr && err == nil {
t.Errorf("expected validation error, got nil")
}
if !tt.wantErr && err != nil {
t.Errorf("unexpected validation error: %v", err)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,14 @@ func (b *ContainerBuilder) BuildProxyInitContainer(mode ProxyInitMode, outboundP
return corev1.Container{}
}

// Optional explicit iptables backend override (applies to both modes). Empty
// by default: the init script auto-detects from /proc/modules (iptable_nat
// loaded => legacy, else nft). Set b.cfg.Proxy.IptablesCmd (e.g. "iptables"
// on nft-only platforms) to force a backend.
if b.cfg.Proxy.IptablesCmd != "" {
env = append(env, corev1.EnvVar{Name: "IPTABLES_CMD", Value: b.cfg.Proxy.IptablesCmd})
}

return corev1.Container{
Name: ProxyInitContainerName,
Image: b.cfg.Images.ProxyInit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,45 @@ func TestBuildProxyInitContainer_EnforceRedirect(t *testing.T) {
if _, ok := got["OUTBOUND_PORTS_EXCLUDE"]; ok {
t.Error("enforce-redirect must not set OUTBOUND_PORTS_EXCLUDE")
}
if _, ok := got["IPTABLES_CMD"]; ok {
t.Error("enforce-redirect must not set IPTABLES_CMD by default (proxy-init auto-detects from /proc/modules)")
}
}

// IPTABLES_CMD is injected only when Proxy.IptablesCmd is configured, in both
// modes, so the init script's /proc/modules auto-detection stays the default
// and the backend override is strictly opt-in.
func TestBuildProxyInitContainer_IptablesCmd(t *testing.T) {
modes := []ProxyInitMode{ProxyInitModeRedirect, ProxyInitModeEnforceRedirect}

// Default (empty): env var absent in both modes.
def := NewContainerBuilder(config.CompiledDefaults())
for _, mode := range modes {
for _, e := range def.BuildProxyInitContainer(mode, "", "").Env {
if e.Name == "IPTABLES_CMD" {
t.Errorf("mode %q: IPTABLES_CMD must be absent when unset, got %q", mode, e.Value)
}
}
}

// Configured: env var present with the configured value in both modes.
cfg := config.CompiledDefaults()
cfg.Proxy.IptablesCmd = "iptables"
b := NewContainerBuilder(cfg)
for _, mode := range modes {
found := false
for _, e := range b.BuildProxyInitContainer(mode, "", "").Env {
if e.Name == "IPTABLES_CMD" {
found = true
if e.Value != "iptables" {
t.Errorf("mode %q: IPTABLES_CMD = %q, want %q", mode, e.Value, "iptables")
}
}
}
if !found {
t.Errorf("mode %q: IPTABLES_CMD env not set when configured", mode)
}
}
}

// An unknown mode must fail closed: BuildProxyInitContainer returns a
Expand Down
Loading