Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1a8c0d0
feat: add --tls-min-version flag to proxy server
tesshuflower Mar 20, 2026
dfebafc
Upgrade prometheus/client_golang to v1.23.2 & prometheus/common to v0…
cheftako May 8, 2026
c2c0439
Fix e2e test panic by setting prometheus validation scheme
cheftako May 8, 2026
8285bcf
Upgrade klog/v2 to v2.140.0
cheftako May 12, 2026
90f104b
Merge branch 'master' into issue-846-5X64
cheftako May 15, 2026
355674d
Merge pull request #847 from cheftako/issue-846-5X64
cheftako May 18, 2026
24d61ca
Merge pull request #839 from cheftako/issue-838-cFIo
k8s-ci-robot May 18, 2026
b47be68
Upgrade sigs.k8s.io/controller-runtime to v0.24.0
cheftako May 19, 2026
517a065
Fix TestServerLeaseCounter timeout by propagating IsWatchListSemantic…
cheftako May 20, 2026
2b80c6a
Refine TestServerLeaseCounter fix using idiomatic client-go utility
cheftako May 20, 2026
ecd2985
Merge pull request #851 from cheftako/issue-850-1779213007
k8s-ci-robot May 20, 2026
b9bca9c
tests: fix nil pointer dereference in TestProxy_ConcurrencyHTTP
cheftako May 20, 2026
eb28d72
e2e: improve diagnostics by dumping pod logs on failure
cheftako May 20, 2026
969fca6
Sync konnectivity-client dependencies with main module
cheftako May 20, 2026
e471107
Merge pull request #842 from cheftako/issue-841-9xk4
k8s-ci-robot May 21, 2026
a30bcd1
Enforce server-ca-cert in TCP mode validation
cheftako May 21, 2026
8536f7d
Merge pull request #853 from cheftako/fix-511043121-1779385126
k8s-ci-robot May 21, 2026
a8faa87
chore(deps): bump the gomod-dependencies group across 1 directory wit…
dependabot[bot] May 22, 2026
ad9cad2
Merge pull request #852 from kubernetes-sigs/dependabot/go_modules/go…
cheftako May 22, 2026
ecd8d51
Merge pull request #820 from tesshuflower/add-tls-min-version
cheftako May 22, 2026
a62bc76
merge upstream/master into main
May 25, 2026
ca70fbc
UPSTREAM: <carry>: Add OpenShift files
bryan-cox May 18, 2026
30be571
UPSTREAM: <carry>: load balance traffic across redundant backends
bryan-cox May 18, 2026
b010cd3
UPSTREAM: <carry>: CNTRLPLANE-3380: Update OWNERS with HyperShift cor…
bryan-cox May 18, 2026
31c2b7e
UPSTREAM: <drop>: Updating and vendoring go modules after an upstream…
May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 23 additions & 1 deletion cmd/server/app/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package options

import (
"crypto/tls"
"fmt"
"os"
"time"
Expand Down Expand Up @@ -104,7 +105,11 @@ type ProxyRunOptions struct {
// also checks if given comma separated list contains cipher from tls.InsecureCipherSuites().
// NOTE that cipher suites are not configurable for TLS1.3,
// see: https://pkg.go.dev/crypto/tls#Config, so in that case, this option won't have any effect.
CipherSuites []string
CipherSuites []string
// Minimum TLS version for server connections.
// Accepted values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13.
// If empty, defaults to VersionTLS12.
TLSMinVersion string
XfrChannelSize int

// Lease controller configuration
Expand Down Expand Up @@ -153,6 +158,7 @@ func (o *ProxyRunOptions) Flags() *pflag.FlagSet {
flags.StringVar(&o.AuthenticationAudience, "authentication-audience", o.AuthenticationAudience, "Expected agent's token authentication audience (used with agent-namespace, agent-service-account, kubeconfig).")
flags.StringVar(&o.ProxyStrategies, "proxy-strategies", o.ProxyStrategies, "The list of proxy strategies used by the server to pick an agent/tunnel, available strategies are: default, destHost, defaultRoute.")
flags.StringSliceVar(&o.CipherSuites, "cipher-suites", o.CipherSuites, "The comma separated list of allowed cipher suites. Has no effect on TLS1.3. Empty means allow default list.")
flags.StringVar(&o.TLSMinVersion, "tls-min-version", o.TLSMinVersion, "Minimum TLS version for server connections. Accepted values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. Empty defaults to VersionTLS12.")
flags.IntVar(&o.XfrChannelSize, "xfr-channel-size", o.XfrChannelSize, "The size of the two KNP server channels used in server for transferring data. One channel is for data coming from the Kubernetes API Server, and the other one is for data coming from the KNP agent.")
flags.BoolVar(&o.EnableLeaseController, "enable-lease-controller", o.EnableLeaseController, "Enable lease controller to publish and garbage collect proxy server leases.")
flags.StringVar(&o.LeaseNamespace, "lease-namespace", o.LeaseNamespace, "The namespace where lease objects are managed by the controller.")
Expand Down Expand Up @@ -200,6 +206,7 @@ func (o *ProxyRunOptions) Print() {
klog.V(1).Infof("LeaseNamespace set to %s.\n", o.LeaseNamespace)
klog.V(1).Infof("LeaseLabel set to %s.\n", o.LeaseLabel)
klog.V(1).Infof("CipherSuites set to %q.\n", o.CipherSuites)
klog.V(1).Infof("TLSMinVersion set to %q.\n", o.TLSMinVersion)
klog.V(1).Infof("XfrChannelSize set to %d.\n", o.XfrChannelSize)
klog.V(1).Infof("GracefulShutdownTimeout set to %v.\n", o.GracefulShutdownTimeout)
}
Expand Down Expand Up @@ -263,6 +270,10 @@ func (o *ProxyRunOptions) Validate() error {
if o.ServerCaCert != "" {
return fmt.Errorf("server ca cert should not be set for UDS")
}
} else {
if o.ServerCaCert == "" {
return fmt.Errorf("server ca cert must be set when in TCP mode (uds-name is empty)")
}
}
if o.ServerPort > 49151 {
return fmt.Errorf("please do not try to use ephemeral port %d for the server port", o.ServerPort)
Expand Down Expand Up @@ -325,6 +336,16 @@ func (o *ProxyRunOptions) Validate() error {
if o.XfrChannelSize <= 0 {
return fmt.Errorf("channel size %d must be greater than 0", o.XfrChannelSize)
}
// validate the TLS min version
if o.TLSMinVersion != "" {
tlsVer, err := util.GetTLSVersion(o.TLSMinVersion)
if err != nil {
return err
}
if tlsVer < tls.VersionTLS12 {
klog.Warningf("--tls-min-version=%s is below TLS 1.2 and is considered insecure (RFC 8996)", o.TLSMinVersion)
}
}
// validate the cipher suites
if len(o.CipherSuites) != 0 {
acceptedCiphers := util.GetAcceptedCiphers()
Expand Down Expand Up @@ -387,6 +408,7 @@ func NewProxyRunOptions() *ProxyRunOptions {
AuthenticationAudience: "",
ProxyStrategies: "default",
CipherSuites: make([]string, 0),
TLSMinVersion: "",
XfrChannelSize: 10,
EnableLeaseController: false,
LeaseNamespace: "kube-system",
Expand Down
39 changes: 39 additions & 0 deletions cmd/server/app/options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package options

import (
"fmt"
"os"
"reflect"
"testing"
"time"
Expand Down Expand Up @@ -61,6 +62,7 @@ func TestDefaultServerOptions(t *testing.T) {
assertDefaultValue(t, "AuthenticationAudience", defaultServerOptions.AuthenticationAudience, "")
assertDefaultValue(t, "ProxyStrategies", defaultServerOptions.ProxyStrategies, "default")
assertDefaultValue(t, "CipherSuites", defaultServerOptions.CipherSuites, make([]string, 0))
assertDefaultValue(t, "TLSMinVersion", defaultServerOptions.TLSMinVersion, "")
assertDefaultValue(t, "XfrChannelSize", defaultServerOptions.XfrChannelSize, 10)
assertDefaultValue(t, "APIContentType", defaultServerOptions.APIContentType, "application/vnd.kubernetes.protobuf")
assertDefaultValue(t, "GracefulShutdownTimeout", defaultServerOptions.GracefulShutdownTimeout, 0*time.Second)
Expand All @@ -74,11 +76,30 @@ func assertDefaultValue(t *testing.T, fieldName string, actual, expected interfa
}

func TestValidate(t *testing.T) {
tempFile, err := os.CreateTemp("", "server-ca-cert")
if err != nil {
t.Fatalf("failed to create temp file: %v", err)
}
defer os.Remove(tempFile.Name())
tempFile.Close()

_, nonExistentErr := os.Stat("non-existent-file.crt")

for desc, tc := range map[string]struct {
field string
value interface{}
expected error
}{
"ServerCaCert empty in TCP mode": {
field: "ServerCaCert",
value: "",
expected: fmt.Errorf("server ca cert must be set when in TCP mode (uds-name is empty)"),
},
"ServerCaCert non-existent in TCP mode": {
field: "ServerCaCert",
value: "non-existent-file.crt",
expected: fmt.Errorf("error checking server CA cert non-existent-file.crt, got %v", nonExistentErr),
},
"default": {
field: "",
value: nil,
Expand Down Expand Up @@ -149,6 +170,21 @@ func TestValidate(t *testing.T) {
value: "TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
expected: nil,
},
"ValidTLSMinVersion12": {
field: "TLSMinVersion",
value: "VersionTLS12",
expected: nil,
},
"ValidTLSMinVersion13": {
field: "TLSMinVersion",
value: "VersionTLS13",
expected: nil,
},
"InvalidTLSMinVersion": {
field: "TLSMinVersion",
value: "InvalidVersion",
expected: fmt.Errorf("unsupported TLS version \"InvalidVersion\", supported values are: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13"),
},
"Empty proxy strategies": {
field: "ProxyStrategies",
value: "",
Expand Down Expand Up @@ -187,6 +223,9 @@ func TestValidate(t *testing.T) {
} {
t.Run(desc, func(t *testing.T) {
testServerOptions := NewProxyRunOptions()
if tc.field != "ServerCaCert" {
testServerOptions.ServerCaCert = tempFile.Name()
}
if tc.field == "CipherSuites" {
testServerOptions.Flags().Set("cipher-suites", tc.value.(string))
} else if tc.field != "" {
Expand Down
17 changes: 11 additions & 6 deletions cmd/server/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,16 +404,21 @@ func (p *Proxy) runUDSFrontendServer(ctx context.Context, o *options.ProxyRunOpt
return stop, nil
}

func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string, cipherSuites []string) (*tls.Config, error) {
func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string, cipherSuites []string, tlsMinVersion string) (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("failed to load X509 key pair %s and %s: %v", certFile, keyFile, err)
}

cipherSuiteIDs := tlsCipherSuites(cipherSuites)

minVersion, err := util.GetTLSVersion(tlsMinVersion)
if err != nil {
return nil, fmt.Errorf("failed to parse TLS min version: %v", err)
}

if caFile == "" {
return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, CipherSuites: cipherSuiteIDs}, nil
return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: minVersion, CipherSuites: cipherSuiteIDs}, nil // #nosec G402
}

certPool := x509.NewCertPool()
Expand All @@ -426,11 +431,11 @@ func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string, cipherSuites []st
return nil, fmt.Errorf("failed to append cluster CA cert to the cert pool")
}

tlsConfig := &tls.Config{
tlsConfig := &tls.Config{ // #nosec G402
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{cert},
ClientCAs: certPool,
MinVersion: tls.VersionTLS12,
MinVersion: minVersion,
CipherSuites: cipherSuiteIDs,
}

Expand All @@ -442,7 +447,7 @@ func (p *Proxy) runMTLSFrontendServer(_ context.Context, o *options.ProxyRunOpti

var tlsConfig *tls.Config
var err error
if tlsConfig, err = p.getTLSConfig(o.ServerCaCert, o.ServerCert, o.ServerKey, o.CipherSuites); err != nil {
if tlsConfig, err = p.getTLSConfig(o.ServerCaCert, o.ServerCert, o.ServerKey, o.CipherSuites, o.TLSMinVersion); err != nil {
return nil, err
}

Expand Down Expand Up @@ -500,7 +505,7 @@ func (p *Proxy) runMTLSFrontendServer(_ context.Context, o *options.ProxyRunOpti
func (p *Proxy) runAgentServer(o *options.ProxyRunOptions, server *server.ProxyServer) error {
var tlsConfig *tls.Config
var err error
if tlsConfig, err = p.getTLSConfig(o.ClusterCaCert, o.ClusterCert, o.ClusterKey, o.CipherSuites); err != nil {
if tlsConfig, err = p.getTLSConfig(o.ClusterCaCert, o.ClusterCert, o.ClusterKey, o.CipherSuites, o.TLSMinVersion); err != nil {
return err
}

Expand Down
21 changes: 19 additions & 2 deletions e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func waitForDeployment(obj client.Object) func(context.Context, *testing.T, *env
k8sClient := kubernetes.NewForConfigOrDie(cfg.Client().RESTConfig())
err := wait.For(
conditions.New(cfg.Client().Resources(deployment.Namespace)).DeploymentAvailable(deployment.Name, deployment.Namespace),
wait.WithTimeout(120*time.Second),
wait.WithTimeout(60*time.Second),
wait.WithInterval(5*time.Second),
)
if err != nil {
Expand Down Expand Up @@ -316,6 +316,7 @@ func scaleDeployment(obj client.Object, replicas int) func(context.Context, *tes
t.Fatalf("could not update Deployment replicas: %v", err)
}

k8sClient := kubernetes.NewForConfigOrDie(cfg.Client().RESTConfig())
err = wait.For(
conditions.New(cfg.Client().Resources(deployment.Namespace)).ResourceScaled(deployment, func(obj k8s.Object) int32 {
deployment, ok := obj.(*appsv1api.Deployment)
Expand All @@ -329,7 +330,23 @@ func scaleDeployment(obj client.Object, replicas int) func(context.Context, *tes
wait.WithInterval(5*time.Second),
)
if err != nil {
t.Fatalf("waiting for deployment %q to scale failed", deployment.Name)
pods, err := k8sClient.CoreV1().Pods(deployment.Namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.FormatLabels(deployment.Spec.Selector.MatchLabels)})
if err != nil {
t.Fatalf("could not get pods for deployment %q: %v", deployment.Name, err)
}

for _, pod := range pods.Items {
if isPodReady(&pod) {
continue
}

logs, err := dumpPodLogs(ctx, k8sClient, pod.Namespace, pod.Name)
if err != nil {
t.Fatalf("could not dump logs for pod %q: %v", pod.Name, err)
}
t.Errorf("logs for pod %q: %v", pod.Name, logs)
}
t.Fatalf("waiting for deployment %q to scale failed, dumped pod logs", deployment.Name)
}

return ctx
Expand Down
3 changes: 2 additions & 1 deletion e2e/metrics_assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"testing"

"github.com/prometheus/common/expfmt"
"github.com/prometheus/common/model"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/rest"
"sigs.k8s.io/e2e-framework/klient/k8s/resources"
Expand All @@ -48,7 +49,7 @@ func getMetricsGaugeValue(restCfg *rest.Config, namespace string, podName string
return 0, fmt.Errorf("got invalid response from metrics endpoint %q, status code %v: %v", url, resp.StatusCode, string(body))
}

metricsParser := &expfmt.TextParser{}
metricsParser := expfmt.NewTextParser(model.LegacyValidation)
metricsFamilies, err := metricsParser.TextToMetricFamilies(resp.Body)
if err != nil {
return 0, fmt.Errorf("could not parse metrics: %w", err)
Expand Down
72 changes: 34 additions & 38 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
module sigs.k8s.io/apiserver-network-proxy

go 1.25.0
go 1.26.2

require (
github.com/google/uuid v1.6.0
github.com/prometheus/client_golang v1.23.0
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2
github.com/prometheus/common v0.65.0
github.com/spf13/cobra v1.10.1
github.com/prometheus/common v0.67.5
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.5.2
golang.org/x/net v0.53.0
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
k8s.io/api v0.34.0
k8s.io/apimachinery v0.34.0
k8s.io/client-go v0.34.0
k8s.io/component-base v0.34.0
k8s.io/component-helpers v0.34.0
k8s.io/klog/v2 v2.130.1
go.uber.org/mock v0.6.0
golang.org/x/net v0.55.0
google.golang.org/grpc v1.81.1
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
k8s.io/api v0.36.1
k8s.io/apimachinery v0.36.1
k8s.io/client-go v0.36.1
k8s.io/component-base v0.36.1
k8s.io/component-helpers v0.36.1
k8s.io/klog/v2 v2.140.0
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0
sigs.k8s.io/controller-runtime v0.20.3
sigs.k8s.io/e2e-framework v0.6.0
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0
sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/e2e-framework v0.7.0
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand All @@ -52,30 +50,28 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/vladimirvivien/gexe v0.4.1 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/vladimirvivien/gexe v0.5.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/streaming v0.36.1 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

Expand Down
Loading