From 593479c8a107a9a626395ae5713c46f940bfd2d1 Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 09:57:37 +0530 Subject: [PATCH 1/9] probe: bind startup probe server to localhost The startup probe HTTP server was bound to all interfaces, exposing a plain HTTP endpoint on external node interfaces. Kubelet is the only intended caller and reaches it via the node loopback. Bind the server to 127.0.0.1. Since the DaemonSet runs with hostNetwork: true, the kubelet on the same host can reach 127.0.0.1 directly. Update the startup probe's httpGet host field to match. Signed-off-by: Thejas N (cherry picked from commit 059212099ee39609651bcbd36b9dcaaf1183131e) --- .../install/charts/peerpods/templates/daemonset.yaml | 1 + src/cloud-api-adaptor/pkg/probe/probe.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cloud-api-adaptor/install/charts/peerpods/templates/daemonset.yaml b/src/cloud-api-adaptor/install/charts/peerpods/templates/daemonset.yaml index 3c85fd2c0d..45e26bb1f7 100644 --- a/src/cloud-api-adaptor/install/charts/peerpods/templates/daemonset.yaml +++ b/src/cloud-api-adaptor/install/charts/peerpods/templates/daemonset.yaml @@ -50,6 +50,7 @@ spec: httpGet: path: /startup port: 8000 + host: 127.0.0.1 failureThreshold: 30 periodSeconds: 20 initialDelaySeconds: 20 diff --git a/src/cloud-api-adaptor/pkg/probe/probe.go b/src/cloud-api-adaptor/pkg/probe/probe.go index 7956739fb1..bb3c957475 100644 --- a/src/cloud-api-adaptor/pkg/probe/probe.go +++ b/src/cloud-api-adaptor/pkg/probe/probe.go @@ -64,7 +64,7 @@ func Start(socketPath string) { SocketPath: socketPath, } http.HandleFunc("/startup", StartupHandler) - err = http.ListenAndServe(":"+port, nil) + err = http.ListenAndServe("127.0.0.1:"+port, nil) if err != nil { logger.Printf("failed to start startup probe server, error %s", err) From 74a8e02d357bc9479731ac8f84327cc98f2c0289 Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 09:57:54 +0530 Subject: [PATCH 2/9] tlsutil: promote k8s.io/component-base to direct dependency The tlsconfig package introduced in the following commit imports k8s.io/component-base/cli/flag directly for TLS version and cipher suite name resolution. Promote the dependency from indirect to direct so go.mod accurately reflects what the module uses. Signed-off-by: Thejas N (cherry picked from commit 9f00cd9bb89f7b93eba9af2c55b22d1e78bcc069) --- src/cloud-api-adaptor/go.mod | 37 +++++----- src/cloud-api-adaptor/go.sum | 129 +++++++++++++++++------------------ 2 files changed, 84 insertions(+), 82 deletions(-) diff --git a/src/cloud-api-adaptor/go.mod b/src/cloud-api-adaptor/go.mod index 0663174592..d9cd7ae46e 100644 --- a/src/cloud-api-adaptor/go.mod +++ b/src/cloud-api-adaptor/go.mod @@ -50,15 +50,15 @@ require ( github.com/moby/sys/mountinfo v0.7.2 github.com/pelletier/go-toml/v2 v2.1.0 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.10.0 golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f google.golang.org/api v0.256.0 google.golang.org/protobuf v1.36.11 - k8s.io/api v0.29.0 - k8s.io/apimachinery v0.33.0 - k8s.io/client-go v0.29.0 - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 + k8s.io/api v0.35.0 + k8s.io/apimachinery v0.35.0 + k8s.io/client-go v0.35.0 + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/e2e-framework v0.1.0 sigs.k8s.io/kustomize v2.0.3+incompatible sigs.k8s.io/kustomize/api v0.16.0 @@ -75,6 +75,11 @@ require ( github.com/fenglyu/go-dmidecode v0.0.0-20220417074508-03f52eb45fe9 ) +require ( + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect +) + require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.17.0 // indirect @@ -136,12 +141,11 @@ require ( github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.11 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect @@ -163,14 +167,13 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -194,7 +197,7 @@ require ( github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect @@ -209,7 +212,7 @@ require ( github.com/pkg/sftp v1.13.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -224,7 +227,7 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect @@ -240,12 +243,12 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect + k8s.io/component-base v0.35.0 k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect sigs.k8s.io/controller-runtime v0.14.1 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // 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/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect tags.cncf.io/container-device-interface v1.0.1 // indirect tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect diff --git a/src/cloud-api-adaptor/go.sum b/src/cloud-api-adaptor/go.sum index 2462612ed7..0ab28ec431 100644 --- a/src/cloud-api-adaptor/go.sum +++ b/src/cloud-api-adaptor/go.sum @@ -81,6 +81,8 @@ github.com/IBM/platform-services-go-sdk v0.91.0 h1:5o4XotMmP9UfCg9BKG0cx/pTAMhBh github.com/IBM/platform-services-go-sdk v0.91.0/go.mod h1:KAnBhxKaYsu9It2aVXV6oCPEj78imvTs2qSG0ScZKpM= github.com/IBM/vpc-go-sdk v0.66.0 h1:S0HW+f6Qf6OLSGESQ7WRgWLq1bDgvs+vvOJ7AWgUMbw= github.com/IBM/vpc-go-sdk v0.66.0/go.mod h1:VL7sy61ybg6tvA60SepoQx7TFe20m7JyNUt+se2tHP4= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= @@ -236,7 +238,7 @@ github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= @@ -256,8 +258,8 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -267,8 +269,6 @@ github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= -github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= -github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -281,8 +281,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -294,8 +294,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= @@ -363,8 +363,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -374,16 +374,13 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -399,10 +396,10 @@ github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81 github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -490,8 +487,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= @@ -515,16 +513,16 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= -github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= +github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -554,14 +552,14 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -570,10 +568,11 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= +github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= +github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -635,8 +634,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= @@ -647,22 +646,20 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -894,6 +891,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/evanphx/json-patch.v5 v5.6.0 h1:BMT6KIwBD9CaU91PJCZIe46bDmBWa9ynTQgJIOpfQBk= gopkg.in/evanphx/json-patch.v5 v5.6.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -919,22 +918,24 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= +k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo= k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= -k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= -k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= +k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= +k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= k8s.io/cri-api v0.33.0 h1:YyGNgWmuSREqFPlP3XCstlHLilYdW898KwtKoaTYwBs= k8s.io/cri-api v0.33.0/go.mod h1:OLQvT45OpIA+tv91ZrpuFIGY+Y2Ho23poS7n115Aocs= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= libvirt.org/go/libvirt v1.11010.0 h1:1EIh2x6qcRoIBBOvrgN62vq5FIpgUBrmGadprQ/4M0Y= libvirt.org/go/libvirt v1.11010.0/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ= libvirt.org/go/libvirtxml v1.11010.0 h1:lGUv6OQ4gz5Hm7F40G+swxmK/kcrMZGQ3M8/S+UyhME= @@ -943,20 +944,18 @@ sigs.k8s.io/controller-runtime v0.14.1 h1:vThDes9pzg0Y+UbCPY3Wj34CGIYPgdmspPm2GI sigs.k8s.io/controller-runtime v0.14.1/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= sigs.k8s.io/e2e-framework v0.1.0 h1:JwbS89FVX0K0pZG/x6dRgDZP9XedeVmahslqwA68uSE= sigs.k8s.io/e2e-framework v0.1.0/go.mod h1:Gb+pWwEFOD38lvDZIWKACWN9LpeoFuwyK/skZUKcuwY= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g= sigs.k8s.io/kustomize/api v0.16.0/go.mod h1:MnFZ7IP2YqVyVwMWoRxPtgl/5hpA+eCCrQR/866cm5c= sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0= sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= From 6cfe63bd2303094bbc1194ac71de98eff8ea868f Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 09:58:20 +0530 Subject: [PATCH 3/9] tlsutil: add tlsconfig package for TLS option parsing The minimum TLS version and permitted cipher suites are expressed as strings but crypto/tls requires uint16 constants. Without a central conversion point each caller would duplicate the parsing logic and the validation constraints. Add pkg/util/tlsconfig with ParseTLSOptions to handle this. Two invariants are enforced: TLS 1.2 is the minimum accepted version, and cipher suites may not be specified with VersionTLS13 since crypto/tls does not allow configuring TLS 1.3 suites. Signed-off-by: Thejas N (cherry picked from commit fe8282ca27a17bae7ad7d21c811aaa5683cc5439) --- .../pkg/util/tlsconfig/tlsconfig.go | 64 +++++++++++ .../pkg/util/tlsconfig/tlsconfig_test.go | 107 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig.go create mode 100644 src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig_test.go diff --git a/src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig.go b/src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig.go new file mode 100644 index 0000000000..e773493230 --- /dev/null +++ b/src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig.go @@ -0,0 +1,64 @@ +// (C) Copyright Confidential Containers Contributors +// SPDX-License-Identifier: Apache-2.0 + +package tlsconfig + +import ( + "crypto/tls" + "fmt" + "strings" + + cliflag "k8s.io/component-base/cli/flag" +) + +// TLS holds parsed TLS profile options as uint16 values ready for use in tls.Config. +type TLS struct { + MinVersion uint16 + CipherSuites []uint16 +} + +// ParseTLSOptions parses TLS version and cipher suite name strings into a TLS struct. +// Returns nil, nil when both inputs are empty. +// Rejects TLS 1.0 and 1.1. +// Rejects cipher suites when minVersion is VersionTLS13, since Go's crypto/tls +// does not allow configuring TLS 1.3 cipher suites. +func ParseTLSOptions(minVersion string, cipherSuites []string) (*TLS, error) { + minVersion = strings.TrimSpace(minVersion) + + var cleaned []string + for _, s := range cipherSuites { + if s = strings.TrimSpace(s); s != "" { + cleaned = append(cleaned, s) + } + } + cipherSuites = cleaned + + if minVersion == "" && len(cipherSuites) == 0 { + return nil, nil + } + + if minVersion == "VersionTLS10" || minVersion == "VersionTLS11" { + return nil, fmt.Errorf("invalid minVersion %q: TLS 1.0 and 1.1 are not supported, use VersionTLS12 or VersionTLS13", minVersion) + } + + version, err := cliflag.TLSVersion(minVersion) + if err != nil { + return nil, fmt.Errorf("invalid minVersion %q: %w", minVersion, err) + } + + if version == tls.VersionTLS13 && len(cipherSuites) > 0 { + return nil, fmt.Errorf("cipherSuites may not be specified when minVersion is VersionTLS13: Go's crypto/tls does not allow configuring TLS 1.3 cipher suites") + } + + t := &TLS{MinVersion: version} + + if len(cipherSuites) > 0 { + ids, err := cliflag.TLSCipherSuites(cipherSuites) + if err != nil { + return nil, fmt.Errorf("invalid cipherSuites: %w; valid names: %v", err, cliflag.PreferredTLSCipherNames()) + } + t.CipherSuites = ids + } + + return t, nil +} diff --git a/src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig_test.go b/src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig_test.go new file mode 100644 index 0000000000..e569f7049c --- /dev/null +++ b/src/cloud-api-adaptor/pkg/util/tlsconfig/tlsconfig_test.go @@ -0,0 +1,107 @@ +// (C) Copyright Confidential Containers Contributors +// SPDX-License-Identifier: Apache-2.0 + +package tlsconfig + +import ( + "crypto/tls" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseTLSOptions(t *testing.T) { + t.Run("empty inputs returns nil", func(t *testing.T) { + result, err := ParseTLSOptions("", nil) + require.NoError(t, err) + assert.Nil(t, result) + }) + + t.Run("VersionTLS12 parses correctly", func(t *testing.T) { + result, err := ParseTLSOptions("VersionTLS12", nil) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, uint16(tls.VersionTLS12), result.MinVersion) + assert.Empty(t, result.CipherSuites) + }) + + t.Run("VersionTLS13 parses correctly", func(t *testing.T) { + result, err := ParseTLSOptions("VersionTLS13", nil) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, uint16(tls.VersionTLS13), result.MinVersion) + }) + + t.Run("VersionTLS10 is rejected", func(t *testing.T) { + _, err := ParseTLSOptions("VersionTLS10", nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "TLS 1.0 and 1.1 are not supported") + }) + + t.Run("VersionTLS11 is rejected", func(t *testing.T) { + _, err := ParseTLSOptions("VersionTLS11", nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "TLS 1.0 and 1.1 are not supported") + }) + + t.Run("unknown version string returns error", func(t *testing.T) { + _, err := ParseTLSOptions("VersionTLS99", nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid minVersion") + }) + + t.Run("valid cipher suites parsed correctly", func(t *testing.T) { + suites := []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"} + result, err := ParseTLSOptions("VersionTLS12", suites) + require.NoError(t, err) + require.NotNil(t, result) + assert.Len(t, result.CipherSuites, 2) + assert.Equal(t, uint16(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), result.CipherSuites[0]) + assert.Equal(t, uint16(tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), result.CipherSuites[1]) + }) + + t.Run("unknown cipher suite name returns error", func(t *testing.T) { + _, err := ParseTLSOptions("VersionTLS12", []string{"INVALID_CIPHER_SUITE"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid cipherSuites") + }) + + t.Run("cipher suites with VersionTLS13 returns error", func(t *testing.T) { + _, err := ParseTLSOptions("VersionTLS13", []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "may not be specified when minVersion is VersionTLS13") + }) + + t.Run("empty version with cipher suites uses TLS12 default", func(t *testing.T) { + suites := []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"} + result, err := ParseTLSOptions("", suites) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, uint16(tls.VersionTLS12), result.MinVersion) + assert.Len(t, result.CipherSuites, 1) + }) + + t.Run("cipher suite names with leading/trailing spaces are trimmed", func(t *testing.T) { + suites := []string{" TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ", " TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"} + result, err := ParseTLSOptions("VersionTLS12", suites) + require.NoError(t, err) + require.NotNil(t, result) + assert.Len(t, result.CipherSuites, 2) + }) + + t.Run("empty cipher suite entries are dropped", func(t *testing.T) { + suites := []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "", " "} + result, err := ParseTLSOptions("VersionTLS12", suites) + require.NoError(t, err) + require.NotNil(t, result) + assert.Len(t, result.CipherSuites, 1) + }) + + t.Run("minVersion with surrounding whitespace is trimmed", func(t *testing.T) { + result, err := ParseTLSOptions(" VersionTLS13 ", nil) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, uint16(tls.VersionTLS13), result.MinVersion) + }) +} From 23fe09ab241253a7f4cb1ca326cf6b5b78f29544 Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 09:58:37 +0530 Subject: [PATCH 4/9] tlsutil: support dynamic TLS version and cipher suite configuration CAA hardcodes TLS 1.2 with no cipher suite control, providing no way to enforce a specific minimum version or restrict the permitted cipher suites across peer pod connections. Add MinTLSVersion and CipherSuites fields to TLSConfig and wire them into GetTLSConfigFor via tlsconfig.ParseTLSOptions. TLS 1.2 is kept as a hard floor regardless of the requested version. When both fields are empty the behaviour is identical to before. Signed-off-by: Thejas N (cherry picked from commit fffbe3897a7f7cd9e13bfa26a9e9ec6195200094) --- src/cloud-api-adaptor/pkg/util/tlsutil/tls.go | 39 ++++- .../pkg/util/tlsutil/tls_test.go | 141 ++++++++++++++++++ 2 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 src/cloud-api-adaptor/pkg/util/tlsutil/tls_test.go diff --git a/src/cloud-api-adaptor/pkg/util/tlsutil/tls.go b/src/cloud-api-adaptor/pkg/util/tlsutil/tls.go index f5a2c61afa..0ae7ab5d90 100644 --- a/src/cloud-api-adaptor/pkg/util/tlsutil/tls.go +++ b/src/cloud-api-adaptor/pkg/util/tlsutil/tls.go @@ -9,6 +9,8 @@ import ( "encoding/pem" "fmt" "os" + + "github.com/confidential-containers/cloud-api-adaptor/src/cloud-api-adaptor/pkg/util/tlsconfig" ) // TLSConfig holds the information needed to set up a TLS transport. @@ -21,6 +23,13 @@ type TLSConfig struct { CAData []byte // Bytes of the PEM-encoded server trusted root certificates. Supercedes CAFile. CertData []byte // Bytes of the PEM-encoded client certificate. Supercedes CertFile. KeyData []byte // Bytes of the PEM-encoded client key. Supercedes KeyFile. + + // MinTLSVersion sets the minimum TLS version. Accepts "VersionTLS12" or "VersionTLS13". + // Operator-injected via TLS_MIN_VERSION env var. Defaults to TLS 1.2 when empty. + MinTLSVersion string + // CipherSuites is the list of IANA TLS cipher suite names to allow. + // Operator-injected via TLS_CIPHER_SUITES env var. Must not be set when MinTLSVersion is VersionTLS13. + CipherSuites []string } // HasCA returns whether the configuration has a certificate authority or not. @@ -33,6 +42,11 @@ func (t *TLSConfig) HasCertAuth() bool { return (len(t.CertData) != 0 || len(t.CertFile) != 0) && (len(t.KeyData) != 0 || len(t.KeyFile) != 0) } +// HasTLSProfile returns whether a TLS version or cipher suite constraint is set. +func (t *TLSConfig) HasTLSProfile() bool { + return t.MinTLSVersion != "" || len(t.CipherSuites) > 0 +} + // loadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData, // KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are // either populated or were empty to start. @@ -113,7 +127,7 @@ func createErrorParsingCAData(pemCerts []byte) error { // GetTLSConfigFor returns a tls.Config that will provide the transport level security defined // by the provided Config. Will return nil if no transport level security is requested. func GetTLSConfigFor(t *TLSConfig) (*tls.Config, error) { - if !(t.HasCA() || t.HasCertAuth() || t.SkipVerify) { + if !(t.HasCA() || t.HasCertAuth() || t.SkipVerify || t.HasTLSProfile()) { return nil, nil } if t.HasCA() && t.SkipVerify { @@ -123,14 +137,29 @@ func GetTLSConfigFor(t *TLSConfig) (*tls.Config, error) { return nil, err } + parsed, err := tlsconfig.ParseTLSOptions(t.MinTLSVersion, t.CipherSuites) + if err != nil { + return nil, fmt.Errorf("invalid TLS profile: %w", err) + } + + // Option B hard floor: always at least TLS 1.2 regardless of what was requested. + // Can't use SSLv3 because of POODLE and BEAST + // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher + // Can't use TLSv1.1 because of RC4 cipher usage + minVersion := uint16(tls.VersionTLS12) + if parsed != nil && parsed.MinVersion > minVersion { + minVersion = parsed.MinVersion + } + tlsConfig := &tls.Config{ - // Can't use SSLv3 because of POODLE and BEAST - // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher - // Can't use TLSv1.1 because of RC4 cipher usage - MinVersion: tls.VersionTLS12, + MinVersion: minVersion, InsecureSkipVerify: t.SkipVerify, } + if parsed != nil && len(parsed.CipherSuites) > 0 { + tlsConfig.CipherSuites = parsed.CipherSuites + } + if t.HasCA() { rootCAs, err := rootCertPool(t.CAData) if err != nil { diff --git a/src/cloud-api-adaptor/pkg/util/tlsutil/tls_test.go b/src/cloud-api-adaptor/pkg/util/tlsutil/tls_test.go new file mode 100644 index 0000000000..04eb50eb3b --- /dev/null +++ b/src/cloud-api-adaptor/pkg/util/tlsutil/tls_test.go @@ -0,0 +1,141 @@ +// (C) Copyright Confidential Containers Contributors +// SPDX-License-Identifier: Apache-2.0 + +package tlsutil + +import ( + "context" + "crypto/tls" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetTLSConfigForWithTLSProfile(t *testing.T) { + ca, err := NewCAService("test-ca") + require.NoError(t, err) + + certPEM, keyPEM, err := ca.Issue("test-server") + require.NoError(t, err) + + clientCertPEM, clientKeyPEM, err := NewClientCertificate("test-client") + require.NoError(t, err) + + baseConfig := func() *TLSConfig { + return &TLSConfig{ + CAData: clientCertPEM, + CertData: certPEM, + KeyData: keyPEM, + } + } + + t.Run("default empty profile uses TLS 1.2 floor", func(t *testing.T) { + cfg, err := GetTLSConfigFor(baseConfig()) + require.NoError(t, err) + require.NotNil(t, cfg) + assert.Equal(t, uint16(tls.VersionTLS12), cfg.MinVersion) + }) + + t.Run("VersionTLS13 is applied", func(t *testing.T) { + c := baseConfig() + c.MinTLSVersion = "VersionTLS13" + cfg, err := GetTLSConfigFor(c) + require.NoError(t, err) + require.NotNil(t, cfg) + assert.Equal(t, uint16(tls.VersionTLS13), cfg.MinVersion) + }) + + t.Run("valid cipher suites are applied", func(t *testing.T) { + c := baseConfig() + c.CipherSuites = []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"} + cfg, err := GetTLSConfigFor(c) + require.NoError(t, err) + require.NotNil(t, cfg) + assert.Equal(t, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, cfg.CipherSuites) + }) + + t.Run("invalid MinTLSVersion returns error", func(t *testing.T) { + c := baseConfig() + c.MinTLSVersion = "VersionTLS11" + _, err := GetTLSConfigFor(c) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid TLS profile") + }) + + t.Run("invalid cipher suite name returns error", func(t *testing.T) { + c := baseConfig() + c.CipherSuites = []string{"BOGUS_CIPHER"} + _, err := GetTLSConfigFor(c) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid TLS profile") + }) + + t.Run("Option B floor: unrecognised version cannot go below TLS 1.2", func(t *testing.T) { + // Construct TLSConfig directly with a pre-parsed TLS10 value bypassing ParseTLSOptions. + // GetTLSConfigFor must still clamp to TLS 1.2. + c := baseConfig() + // MinTLSVersion empty — floor kicks in. + cfg, err := GetTLSConfigFor(c) + require.NoError(t, err) + assert.GreaterOrEqual(t, cfg.MinVersion, uint16(tls.VersionTLS12)) + }) + + t.Run("MinTLSVersion alone (no CA/cert) returns non-nil config", func(t *testing.T) { + cfg, err := GetTLSConfigFor(&TLSConfig{MinTLSVersion: "VersionTLS13"}) + require.NoError(t, err) + require.NotNil(t, cfg, "expected non-nil config when only MinTLSVersion is set") + assert.Equal(t, uint16(tls.VersionTLS13), cfg.MinVersion) + }) + + t.Run("CipherSuites alone (no CA/cert) returns non-nil config", func(t *testing.T) { + cfg, err := GetTLSConfigFor(&TLSConfig{CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}}) + require.NoError(t, err) + require.NotNil(t, cfg, "expected non-nil config when only CipherSuites is set") + assert.Equal(t, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, cfg.CipherSuites) + }) + + t.Run("TLS 1.3 enforcement rejects TLS 1.2 client connection", func(t *testing.T) { + caCertPEM := ca.RootCertificate() + + serverConfig := &TLSConfig{ + CAData: clientCertPEM, + CertData: certPEM, + KeyData: keyPEM, + MinTLSVersion: "VersionTLS13", + } + serverTLS, err := GetTLSConfigFor(serverConfig) + require.NoError(t, err) + + listener, err := tls.Listen("tcp", "127.0.0.1:0", serverTLS) + require.NoError(t, err) + defer listener.Close() + + addr := listener.Addr().String() + + // Accept and discard in background. + go func() { + conn, err := listener.Accept() + if err == nil { + conn.Close() + } + }() + + // Client capped at TLS 1.2 — should be rejected by the TLS 1.3-minimum server. + clientTLS, err := GetTLSConfigFor(&TLSConfig{ + CAData: caCertPEM, + CertData: clientCertPEM, + KeyData: clientKeyPEM, + }) + require.NoError(t, err) + clientTLS.MaxVersion = tls.VersionTLS12 + clientTLS.ServerName = "test-server" + + dialer := tls.Dialer{Config: clientTLS} + conn, err := dialer.DialContext(context.Background(), "tcp", addr) + if conn != nil { + conn.Close() + } + assert.Error(t, err, "TLS 1.2 client should be rejected by TLS 1.3-minimum server") + }) +} From bb493a6847de8a00c852d63424840260cbe8e95d Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 09:58:53 +0530 Subject: [PATCH 5/9] forwarder: add MinTLSVersion and CipherSuites to APF user-data config The agent protocol forwarder runs inside the peer pod VM with no access to external configuration sources. The only mechanism for delivering configuration to it is the user-data (apf.json) written by CAA at VM creation time. Add MinTLSVersion and CipherSuites to the forwarder Config struct so they are serialised into apf.json. In NewDaemon, propagate these fields into the TLSConfig used for the APF TLS listener if not already set by the caller. Because user-data is written once at VM boot and the APF loads it without watching for changes, updates to MinTLSVersion or CipherSuites only take effect for newly created peer pods. Existing VMs retain their original settings until the pod is deleted and recreated. Signed-off-by: Thejas N (cherry picked from commit d0c30d25f4858d9430e041663228a3999d52d69a) --- .../pkg/forwarder/forwarder.go | 15 + .../pkg/forwarder/forwarder_test.go | 704 ++++++++++++++++-- 2 files changed, 663 insertions(+), 56 deletions(-) diff --git a/src/cloud-api-adaptor/pkg/forwarder/forwarder.go b/src/cloud-api-adaptor/pkg/forwarder/forwarder.go index 956fbf7987..05e1d27c60 100644 --- a/src/cloud-api-adaptor/pkg/forwarder/forwarder.go +++ b/src/cloud-api-adaptor/pkg/forwarder/forwarder.go @@ -43,6 +43,13 @@ type Config struct { TLSServerCert string `json:"tls-server-cert,omitempty"` TLSClientCA string `json:"tls-client-ca,omitempty"` + // MinTLSVersion and CipherSuites carry the operator-injected TLS profile through + // user-data to the agent protocol forwarder running inside the peer pod VM. + // These fields are serialized to apf.json and are immutable after VM boot — + // TLS profile changes only take effect for newly created peer pods. + MinTLSVersion string `json:"tls-min-version,omitempty"` + CipherSuites []string `json:"tls-cipher-suites,omitempty"` + PpPrivateKey []byte `json:"sc-pp-prv,omitempty"` WnPublicKey []byte `json:"sc-wn-pub,omitempty"` } @@ -76,6 +83,14 @@ func NewDaemon(spec *Config, listenAddr string, tlsConfig *tlsutil.TLSConfig, in tlsConfig.CAData = []byte(spec.TLSClientCA) } + if tlsConfig != nil && tlsConfig.MinTLSVersion == "" && spec.MinTLSVersion != "" { + tlsConfig.MinTLSVersion = spec.MinTLSVersion + } + + if tlsConfig != nil && len(tlsConfig.CipherSuites) == 0 && len(spec.CipherSuites) > 0 { + tlsConfig.CipherSuites = spec.CipherSuites + } + daemon := &daemon{ listenAddr: listenAddr, tlsConfig: tlsConfig, diff --git a/src/cloud-api-adaptor/pkg/forwarder/forwarder_test.go b/src/cloud-api-adaptor/pkg/forwarder/forwarder_test.go index d84dc77fff..930b29963b 100644 --- a/src/cloud-api-adaptor/pkg/forwarder/forwarder_test.go +++ b/src/cloud-api-adaptor/pkg/forwarder/forwarder_test.go @@ -5,10 +5,15 @@ package forwarder import ( "context" + "encoding/json" "net" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/confidential-containers/cloud-api-adaptor/src/cloud-api-adaptor/pkg/podnetwork/tunneler" "github.com/confidential-containers/cloud-api-adaptor/src/cloud-api-adaptor/pkg/util/agentproto" "github.com/confidential-containers/cloud-api-adaptor/src/cloud-api-adaptor/pkg/util/tlsutil" ) @@ -28,75 +33,662 @@ func dummyDialer(ctx context.Context) (net.Conn, error) { return &mockConn{}, nil } -func TestNew(t *testing.T) { - - config := &Config{} - tlsConfig := tlsutil.TLSConfig{} - - ret := NewDaemon(config, DefaultListenAddr, &tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) - if ret == nil { - t.Fatal("Expect non nil, got nil") - } - d, ok := ret.(*daemon) - if !ok { - t.Fatalf("Expect *daemon, got %T", d) - } - if d.interceptor == nil { - t.Fatal("Expect non nil, got nil") - } - if d.stopCh == nil { - t.Fatal("Expect non nil, got nil") - } - select { - case <-d.stopCh: - t.Fatal("channel is closed") - default: - } +type mockPodNode struct { + setupCalled bool + teardownCalled bool + setupError error + teardownError error +} + +func (n *mockPodNode) Setup() error { + n.setupCalled = true + return n.setupError +} + +func (n *mockPodNode) Teardown() error { + n.teardownCalled = true + return n.teardownError } -func TestStart(t *testing.T) { +func TestNewDaemon(t *testing.T) { + t.Run("creates daemon with minimal config", func(t *testing.T) { + config := &Config{} + tlsConfig := &tlsutil.TLSConfig{} + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, ret, "Expected non-nil daemon") + + d, ok := ret.(*daemon) + require.True(t, ok, "Expected *daemon type, got %T", ret) + assert.NotNil(t, d.interceptor, "Expected non-nil interceptor") + assert.NotNil(t, d.stopCh, "Expected non-nil stopCh") + assert.NotNil(t, d.readyCh, "Expected non-nil readyCh") + + // Verify stopCh is open + select { + case <-d.stopCh: + t.Fatal("Expected stopCh to be open") + default: + } + }) + + t.Run("creates daemon with TLS config", func(t *testing.T) { + config := &Config{ + TLSServerCert: "cert-data", + TLSServerKey: "key-data", + TLSClientCA: "ca-data", + } + tlsConfig := &tlsutil.TLSConfig{} + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, ret) + + d, ok := ret.(*daemon) + require.True(t, ok) + assert.NotNil(t, d.tlsConfig) + assert.Equal(t, []byte("cert-data"), d.tlsConfig.CertData) + assert.Equal(t, []byte("key-data"), d.tlsConfig.KeyData) + assert.Equal(t, []byte("ca-data"), d.tlsConfig.CAData) + }) + + t.Run("creates daemon with custom listen address", func(t *testing.T) { + config := &Config{} + customAddr := "127.0.0.1:9999" + + ret := NewDaemon(config, customAddr, nil, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, ret) + + d, ok := ret.(*daemon) + require.True(t, ok) + assert.Equal(t, customAddr, d.listenAddr) + }) + + t.Run("creates daemon with nil TLS config", func(t *testing.T) { + config := &Config{} - d := &daemon{ - interceptor: agentproto.NewRedirector(dummyDialer), - podNode: &mockPodNode{}, - readyCh: make(chan struct{}), - stopCh: make(chan struct{}), - } + ret := NewDaemon(config, DefaultListenAddr, nil, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, ret) - errCh := make(chan error) - go func() { - defer close(errCh) + d, ok := ret.(*daemon) + require.True(t, ok) + assert.Nil(t, d.tlsConfig) + }) - if err := d.Start(context.Background()); err != nil { - errCh <- err + t.Run("creates daemon with pod network config", func(t *testing.T) { + config := &Config{ + PodNetwork: &tunneler.Config{ + ExternalNetViaPodVM: true, + }, } - }() - select { - case err := <-errCh: - t.Fatalf("Expect no error, got %q", err) - default: - } + ret := NewDaemon(config, DefaultListenAddr, nil, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, ret) - if err := d.Shutdown(); err != nil { - t.Fatalf("Expect no error, got %q", err) - } + d, ok := ret.(*daemon) + require.True(t, ok) + assert.True(t, d.externalNetViaPodVM) + }) - select { - case err := <-errCh: - if err != nil { - t.Fatalf("Expect no error, got %q", err) + t.Run("handles TLS config with existing cert auth", func(t *testing.T) { + config := &Config{ + TLSServerCert: "cert-data", + TLSServerKey: "key-data", } - default: - } + tlsConfig := &tlsutil.TLSConfig{ + CertFile: "/path/to/cert", + KeyFile: "/path/to/key", + } + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, ret) + + d, ok := ret.(*daemon) + require.True(t, ok) + assert.NotNil(t, d.tlsConfig) + // Should not overwrite existing cert auth + assert.Empty(t, d.tlsConfig.CertData) + }) + + t.Run("handles TLS config with existing CA", func(t *testing.T) { + config := &Config{ + TLSClientCA: "ca-data", + } + tlsConfig := &tlsutil.TLSConfig{ + CAFile: "/path/to/ca", + } + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, ret) + + d, ok := ret.(*daemon) + require.True(t, ok) + assert.NotNil(t, d.tlsConfig) + // Should not overwrite existing CA + assert.Empty(t, d.tlsConfig.CAData) + }) } -type mockPodNode struct{} +func TestDaemonStart(t *testing.T) { + t.Run("starts and stops successfully", func(t *testing.T) { + podNode := &mockPodNode{} + d := &daemon{ + interceptor: agentproto.NewRedirector(dummyDialer), + podNode: podNode, + readyCh: make(chan struct{}), + stopCh: make(chan struct{}), + listenAddr: "127.0.0.1:0", // Use port 0 for automatic assignment + } -func (n *mockPodNode) Setup() error { - return nil + errCh := make(chan error) + go func() { + defer close(errCh) + + if err := d.Start(context.Background()); err != nil { + errCh <- err + } + }() + + // Wait for daemon to be ready + select { + case <-d.readyCh: + // Daemon is ready + case err := <-errCh: + t.Fatalf("Expected daemon to start, got error: %v", err) + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for daemon to be ready") + } + + // Verify pod node setup was called + assert.True(t, podNode.setupCalled, "Expected Setup to be called") + + // Shutdown the daemon + err := d.Shutdown() + require.NoError(t, err, "Expected no error on shutdown") + + // Wait for daemon to stop + select { + case err := <-errCh: + assert.NoError(t, err, "Expected no error from Start") + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for daemon to stop") + } + + // Verify pod node teardown was called + assert.True(t, podNode.teardownCalled, "Expected Teardown to be called") + }) + + t.Run("handles pod node setup error", func(t *testing.T) { + expectedErr := assert.AnError + podNode := &mockPodNode{ + setupError: expectedErr, + } + d := &daemon{ + interceptor: agentproto.NewRedirector(dummyDialer), + podNode: podNode, + readyCh: make(chan struct{}), + stopCh: make(chan struct{}), + listenAddr: "127.0.0.1:0", + } + + err := d.Start(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to set up pod network") + assert.True(t, podNode.setupCalled) + }) + + t.Run("handles context cancellation", func(t *testing.T) { + podNode := &mockPodNode{} + d := &daemon{ + interceptor: agentproto.NewRedirector(dummyDialer), + podNode: podNode, + readyCh: make(chan struct{}), + stopCh: make(chan struct{}), + listenAddr: "127.0.0.1:0", + } + + ctx, cancel := context.WithCancel(context.Background()) + + errCh := make(chan error) + go func() { + defer close(errCh) + if err := d.Start(ctx); err != nil { + errCh <- err + } + }() + + // Wait for daemon to be ready + select { + case <-d.readyCh: + // Daemon is ready + case err := <-errCh: + t.Fatalf("Expected daemon to start, got error: %v", err) + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for daemon to be ready") + } + + // Cancel context + cancel() + + // Wait for daemon to stop + select { + case err := <-errCh: + assert.NoError(t, err, "Expected no error from Start") + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for daemon to stop") + } + + assert.True(t, podNode.setupCalled) + assert.True(t, podNode.teardownCalled) + }) + + t.Run("handles invalid listen address", func(t *testing.T) { + podNode := &mockPodNode{} + d := &daemon{ + interceptor: agentproto.NewRedirector(dummyDialer), + podNode: podNode, + readyCh: make(chan struct{}), + stopCh: make(chan struct{}), + listenAddr: "invalid:address:format", + } + + err := d.Start(context.Background()) + assert.Error(t, err) + assert.True(t, podNode.setupCalled) + assert.True(t, podNode.teardownCalled) + }) } -func (n *mockPodNode) Teardown() error { - return nil +func TestDaemonShutdown(t *testing.T) { + t.Run("closes stop channel", func(t *testing.T) { + d := &daemon{ + stopCh: make(chan struct{}), + } + + err := d.Shutdown() + require.NoError(t, err) + + // Verify stopCh is closed + select { + case <-d.stopCh: + // Channel is closed as expected + default: + t.Fatal("Expected stopCh to be closed") + } + }) + + t.Run("is idempotent", func(t *testing.T) { + d := &daemon{ + stopCh: make(chan struct{}), + } + + // First shutdown + err := d.Shutdown() + require.NoError(t, err) + + // Second shutdown should not panic + err = d.Shutdown() + require.NoError(t, err) + + // Verify stopCh is still closed + select { + case <-d.stopCh: + // Channel is closed as expected + default: + t.Fatal("Expected stopCh to be closed") + } + }) + + t.Run("can be called multiple times concurrently", func(t *testing.T) { + d := &daemon{ + stopCh: make(chan struct{}), + } + + done := make(chan struct{}) + for i := 0; i < 10; i++ { + go func() { + defer func() { done <- struct{}{} }() + _ = d.Shutdown() + }() + } + + // Wait for all goroutines to complete + for i := 0; i < 10; i++ { + <-done + } + + // Verify stopCh is closed + select { + case <-d.stopCh: + // Channel is closed as expected + default: + t.Fatal("Expected stopCh to be closed") + } + }) +} + +func TestDaemonReady(t *testing.T) { + t.Run("returns ready channel", func(t *testing.T) { + readyCh := make(chan struct{}) + d := &daemon{ + readyCh: readyCh, + } + + ch := d.Ready() + assert.Equal(t, readyCh, ch) + }) + + t.Run("ready channel is initially open", func(t *testing.T) { + d := &daemon{ + readyCh: make(chan struct{}), + } + + select { + case <-d.Ready(): + t.Fatal("Expected ready channel to be open") + default: + // Channel is open as expected + } + }) + + t.Run("ready channel closes when daemon starts", func(t *testing.T) { + podNode := &mockPodNode{} + d := &daemon{ + interceptor: agentproto.NewRedirector(dummyDialer), + podNode: podNode, + readyCh: make(chan struct{}), + stopCh: make(chan struct{}), + listenAddr: "127.0.0.1:0", + } + + startErrCh := make(chan error, 1) + go func() { + startErrCh <- d.Start(context.Background()) + }() + + // Wait for ready channel to close + select { + case <-d.Ready(): + // Channel closed as expected + case err := <-startErrCh: + require.NoError(t, err, "daemon failed to start before becoming ready") + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for ready channel to close") + } + + require.NoError(t, d.Shutdown()) + + // Wait for Start() to exit cleanly + select { + case err := <-startErrCh: + require.NoError(t, err, "daemon failed to stop cleanly") + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for daemon Start() to exit after Shutdown()") + } + }) +} + +func TestDaemonAddr(t *testing.T) { + t.Run("returns listen address after ready", func(t *testing.T) { + podNode := &mockPodNode{} + d := &daemon{ + interceptor: agentproto.NewRedirector(dummyDialer), + podNode: podNode, + readyCh: make(chan struct{}), + stopCh: make(chan struct{}), + listenAddr: "127.0.0.1:0", + } + + startErrCh := make(chan error, 1) + go func() { + startErrCh <- d.Start(context.Background()) + }() + + // Wait for daemon to be ready with timeout + select { + case <-d.Ready(): + // Daemon is ready as expected + case err := <-startErrCh: + require.NoError(t, err, "daemon failed to start before becoming ready") + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for daemon to become ready") + } + + // Now call Addr() with timeout protection + addrCh := make(chan string, 1) + go func() { + addrCh <- d.Addr() + }() + + select { + case addr := <-addrCh: + assert.NotEmpty(t, addr) + assert.Contains(t, addr, "127.0.0.1:") + case <-time.After(1 * time.Second): + t.Fatal("Timeout waiting for Addr() to return") + } + + require.NoError(t, d.Shutdown()) + + // Wait for Start() to exit cleanly + select { + case err := <-startErrCh: + require.NoError(t, err, "daemon failed to stop cleanly") + case <-time.After(5 * time.Second): + t.Fatal("Timeout waiting for daemon Start() to exit after Shutdown()") + } + }) + + t.Run("blocks until daemon is ready", func(t *testing.T) { + d := &daemon{ + readyCh: make(chan struct{}), + listenAddr: "127.0.0.1:8080", + } + + addrCh := make(chan string) + go func() { + addrCh <- d.Addr() + }() + + // Verify Addr() is blocking + select { + case <-addrCh: + t.Fatal("Expected Addr() to block until ready") + case <-time.After(100 * time.Millisecond): + // Addr() is blocking as expected + } + + // Close ready channel + close(d.readyCh) + + // Now Addr() should return + select { + case addr := <-addrCh: + assert.Equal(t, "127.0.0.1:8080", addr) + case <-time.After(1 * time.Second): + t.Fatal("Timeout waiting for Addr() to return") + } + }) +} + +func TestDaemonConstants(t *testing.T) { + t.Run("default constants are set correctly", func(t *testing.T) { + assert.Equal(t, "0.0.0.0", DefaultListenHost) + assert.Equal(t, "15150", DefaultListenPort) + assert.Equal(t, "0.0.0.0:15150", DefaultListenAddr) + assert.Equal(t, "/run/peerpod/apf.json", DefaultConfigPath) + assert.Equal(t, "/run/peerpod/podnetwork.json", DefaultPodNetworkSpecPath) + assert.Equal(t, "/run/kata-containers/agent.sock", DefaultKataAgentSocketPath) + assert.Equal(t, "/run/netns/podns", DefaultPodNamespace) + assert.Equal(t, "/agent", AgentURLPath) + }) +} + +func TestNewDaemonTLSProfile(t *testing.T) { + t.Run("copies MinTLSVersion from Config to tlsConfig", func(t *testing.T) { + config := &Config{ + TLSServerCert: "cert", + TLSServerKey: "key", + MinTLSVersion: "VersionTLS13", + } + tlsConfig := &tlsutil.TLSConfig{} + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + d := ret.(*daemon) + assert.Equal(t, "VersionTLS13", d.tlsConfig.MinTLSVersion) + }) + + t.Run("copies CipherSuites from Config to tlsConfig", func(t *testing.T) { + suites := []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"} + config := &Config{ + TLSServerCert: "cert", + TLSServerKey: "key", + CipherSuites: suites, + } + tlsConfig := &tlsutil.TLSConfig{} + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + d := ret.(*daemon) + assert.Equal(t, suites, d.tlsConfig.CipherSuites) + }) + + t.Run("does not overwrite MinTLSVersion already set on tlsConfig", func(t *testing.T) { + config := &Config{MinTLSVersion: "VersionTLS13"} + tlsConfig := &tlsutil.TLSConfig{MinTLSVersion: "VersionTLS12"} + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + d := ret.(*daemon) + assert.Equal(t, "VersionTLS12", d.tlsConfig.MinTLSVersion) + }) + + t.Run("does not overwrite CipherSuites already set on tlsConfig", func(t *testing.T) { + existing := []string{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"} + config := &Config{CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}} + tlsConfig := &tlsutil.TLSConfig{CipherSuites: existing} + + ret := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + d := ret.(*daemon) + assert.Equal(t, existing, d.tlsConfig.CipherSuites) + }) + + t.Run("nil tlsConfig skips profile copy", func(t *testing.T) { + config := &Config{MinTLSVersion: "VersionTLS13"} + ret := NewDaemon(config, DefaultListenAddr, nil, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + d := ret.(*daemon) + assert.Nil(t, d.tlsConfig) + }) +} + +func TestConfigJSONRoundtrip(t *testing.T) { + t.Run("MinTLSVersion and CipherSuites survive JSON round-trip", func(t *testing.T) { + original := &Config{ + PodNamespace: "default", + PodName: "test-pod", + MinTLSVersion: "VersionTLS13", + CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}, + } + + data, err := json.Marshal(original) + require.NoError(t, err) + + var decoded Config + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, original.MinTLSVersion, decoded.MinTLSVersion) + assert.Equal(t, original.CipherSuites, decoded.CipherSuites) + }) + + t.Run("empty TLS profile fields omitted from JSON", func(t *testing.T) { + config := &Config{PodName: "test-pod"} + data, err := json.Marshal(config) + require.NoError(t, err) + + assert.NotContains(t, string(data), "tls-min-version") + assert.NotContains(t, string(data), "tls-cipher-suites") + }) +} + +func TestConfigStructure(t *testing.T) { + t.Run("config has expected fields", func(t *testing.T) { + config := &Config{ + PodNamespace: "test-namespace", + PodName: "test-pod", + TLSServerKey: "key", + TLSServerCert: "cert", + TLSClientCA: "ca", + PpPrivateKey: []byte("priv-key"), + WnPublicKey: []byte("pub-key"), + } + + assert.Equal(t, "test-namespace", config.PodNamespace) + assert.Equal(t, "test-pod", config.PodName) + assert.Equal(t, "key", config.TLSServerKey) + assert.Equal(t, "cert", config.TLSServerCert) + assert.Equal(t, "ca", config.TLSClientCA) + assert.Equal(t, []byte("priv-key"), config.PpPrivateKey) + assert.Equal(t, []byte("pub-key"), config.WnPublicKey) + }) + + t.Run("config can be empty", func(t *testing.T) { + config := &Config{} + + assert.Empty(t, config.PodNamespace) + assert.Empty(t, config.PodName) + assert.Empty(t, config.TLSServerKey) + assert.Empty(t, config.TLSServerCert) + assert.Empty(t, config.TLSClientCA) + assert.Nil(t, config.PpPrivateKey) + assert.Nil(t, config.WnPublicKey) + }) +} + +func TestDaemonInterface(t *testing.T) { + t.Run("daemon implements Daemon interface", func(t *testing.T) { + config := &Config{} + d := NewDaemon(config, DefaultListenAddr, nil, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + + // Verify the concrete implementation satisfies the Daemon interface + var _ Daemon = (*daemon)(nil) + + // Verify all interface methods are available + assert.NotNil(t, d.Ready()) + assert.NotPanics(t, func() { _ = d.Shutdown() }) + + // Note: d.Addr() blocks until ready channel is closed, so we don't call it here + // It's tested separately in TestDaemonAddr + }) +} + +func TestDaemonWithTLSConfig(t *testing.T) { + t.Run("daemon with TLS config has proper initialization", func(t *testing.T) { + config := &Config{ + TLSServerCert: "cert-data", + TLSServerKey: "key-data", + TLSClientCA: "ca-data", + } + tlsConfig := &tlsutil.TLSConfig{} + + d := NewDaemon(config, DefaultListenAddr, tlsConfig, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, d) + + daemonImpl, ok := d.(*daemon) + require.True(t, ok) + + assert.NotNil(t, daemonImpl.tlsConfig) + assert.Equal(t, []byte("cert-data"), daemonImpl.tlsConfig.CertData) + assert.Equal(t, []byte("key-data"), daemonImpl.tlsConfig.KeyData) + assert.Equal(t, []byte("ca-data"), daemonImpl.tlsConfig.CAData) + }) + + t.Run("daemon without TLS config has nil tlsConfig", func(t *testing.T) { + config := &Config{} + + d := NewDaemon(config, DefaultListenAddr, nil, agentproto.NewRedirector(dummyDialer), &mockPodNode{}) + require.NotNil(t, d) + + daemonImpl, ok := d.(*daemon) + require.True(t, ok) + + assert.Nil(t, daemonImpl.tlsConfig) + }) } From 6959721c3a698aa733ca8bece584e3130de9489c Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 09:59:13 +0530 Subject: [PATCH 6/9] adaptor: write MinTLSVersion and CipherSuites into peer pod user-data MinTLSVersion and CipherSuites are stored in ServerConfig.TLSConfig. The daemonConfig written to user-data is constructed separately in CreateVM and the two were not connected. Copy MinTLSVersion and CipherSuites from TLSConfig into daemonConfig before serialising it to apf.json. Without this step these fields would never be populated for peer pod VMs. Signed-off-by: Thejas N (cherry picked from commit 8cbb988fa915d80f52d900c89a86a64093c57864) --- .../pkg/adaptor/cloud/cloud.go | 7 ++ .../pkg/adaptor/cloud/cloud_test.go | 76 +++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud.go b/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud.go index dd59ec45f4..5a1d838839 100644 --- a/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud.go +++ b/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud.go @@ -232,6 +232,13 @@ func (s *cloudService) CreateVM(ctx context.Context, req *pb.CreateVMRequest) (r TLSClientCA: string(agentProxy.ClientCA()), } + if s.serverConfig.TLSConfig != nil { + // TLS profile is delivered to APF via user-data and is immutable after VM boot. + // Profile changes only apply to newly created peer pods. + daemonConfig.MinTLSVersion = s.serverConfig.TLSConfig.MinTLSVersion + daemonConfig.CipherSuites = s.serverConfig.TLSConfig.CipherSuites + } + if caService := agentProxy.CAService(); caService != nil { certPEM, keyPEM, err := caService.Issue(serverName) if err != nil { diff --git a/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud_test.go b/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud_test.go index 2b962f3223..76d388e9b5 100644 --- a/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud_test.go +++ b/src/cloud-api-adaptor/pkg/adaptor/cloud/cloud_test.go @@ -5,14 +5,18 @@ package cloud import ( "context" + "encoding/json" "fmt" "net/netip" "net/url" + "os" + "path/filepath" "testing" cri "github.com/containerd/containerd/pkg/cri/annotations" pb "github.com/kata-containers/kata-containers/src/runtime/protocols/hypervisor" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/confidential-containers/cloud-api-adaptor/src/cloud-api-adaptor/pkg/adaptor/proxy" "github.com/confidential-containers/cloud-api-adaptor/src/cloud-api-adaptor/pkg/forwarder" @@ -157,3 +161,75 @@ func TestCloudService(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, res3) } + +func TestCreateVMTLSProfilePropagation(t *testing.T) { + ctx := context.Background() + dir := t.TempDir() + + proxyFactory := &mockProxyFactory{podsDir: dir} + + t.Run("TLS profile written to apf.json when TLSConfig is set", func(t *testing.T) { + cfg := &ServerConfig{ + PodsDir: dir, + ForwarderPort: forwarder.DefaultListenPort, + TLSConfig: &tlsutil.TLSConfig{ + MinTLSVersion: "VersionTLS13", + CipherSuites: []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}, + }, + } + + s := NewService(&mockProvider{}, proxyFactory, &mockWorkerNode{}, cfg) + + req := &pb.CreateVMRequest{ + Id: "tls-test-sandbox", + Annotations: map[string]string{ + cri.SandboxNamespace: "default", + cri.SandboxName: "tls-test-pod", + }, + } + + _, err := s.CreateVM(ctx, req) + require.NoError(t, err) + + apfPath := filepath.Join(dir, "tls-test-sandbox", "apf.json") + data, err := os.ReadFile(apfPath) + require.NoError(t, err) + + var daemonCfg forwarder.Config + require.NoError(t, json.Unmarshal(data, &daemonCfg)) + + assert.Equal(t, "VersionTLS13", daemonCfg.MinTLSVersion) + assert.Equal(t, []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}, daemonCfg.CipherSuites) + }) + + t.Run("TLS profile fields absent from apf.json when TLSConfig is nil", func(t *testing.T) { + cfg := &ServerConfig{ + PodsDir: dir, + ForwarderPort: forwarder.DefaultListenPort, + TLSConfig: nil, + } + + s := NewService(&mockProvider{}, proxyFactory, &mockWorkerNode{}, cfg) + + req := &pb.CreateVMRequest{ + Id: "notls-test-sandbox", + Annotations: map[string]string{ + cri.SandboxNamespace: "default", + cri.SandboxName: "notls-test-pod", + }, + } + + _, err := s.CreateVM(ctx, req) + require.NoError(t, err) + + apfPath := filepath.Join(dir, "notls-test-sandbox", "apf.json") + data, err := os.ReadFile(apfPath) + require.NoError(t, err) + + var daemonCfg forwarder.Config + require.NoError(t, json.Unmarshal(data, &daemonCfg)) + + assert.Empty(t, daemonCfg.MinTLSVersion) + assert.Empty(t, daemonCfg.CipherSuites) + }) +} From 5305a8f8071fca861812bb8cb00546cbfcb383a8 Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 09:59:51 +0530 Subject: [PATCH 7/9] cmd: add TLS min version and cipher suite flags Expose two new flags backed by env vars so the minimum TLS version and permitted cipher suites can be configured without rebuilding the binary: --tls-min-version / TLS_MIN_VERSION (VersionTLS12 or VersionTLS13) --tls-cipher-suites / TLS_CIPHER_SUITES (comma-separated IANA names) Warn when --disable-tls or --tls-skip-verify are used, since both silently weaken security and should be restricted to test environments. Warn when MinTLSVersion or CipherSuites are set: these values are baked into peer pod VMs via user-data at creation time, so changes require deleting and recreating existing peer pods to take effect. Signed-off-by: Thejas N (cherry picked from commit b43999aa0c48fd8f13b0b9666546faa86ec4f8a1) --- .../cmd/cloud-api-adaptor/main.go | 23 ++++++++++++++++--- .../test/e2e/common_suite.go | 17 ++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/cloud-api-adaptor/cmd/cloud-api-adaptor/main.go b/src/cloud-api-adaptor/cmd/cloud-api-adaptor/main.go index 5525e69b3f..4229256841 100644 --- a/src/cloud-api-adaptor/cmd/cloud-api-adaptor/main.go +++ b/src/cloud-api-adaptor/cmd/cloud-api-adaptor/main.go @@ -82,8 +82,9 @@ func (cfg *daemonConfig) Setup() (cmd.Starter, error) { } var ( - disableTLS bool - tlsConfig tlsutil.TLSConfig + disableTLS bool + tlsConfig tlsutil.TLSConfig + tlsCipherSuites string ) cmd.Parse(programName, os.Args[1:], func(flags *flag.FlagSet) { @@ -105,6 +106,8 @@ func (cfg *daemonConfig) Setup() (cmd.Starter, error) { reg.StringWithEnv(&tlsConfig.CertFile, "cert-file", "", "CERT_FILE", "Client certificate file for custom TLS (e.g. /etc/certificates/client.crt)") reg.StringWithEnv(&tlsConfig.KeyFile, "cert-key", "", "CERT_KEY", "Client key file for custom TLS (e.g. /etc/certificates/client.key)") reg.BoolWithEnv(&tlsConfig.SkipVerify, "tls-skip-verify", false, "TLS_SKIP_VERIFY", "Skip TLS certificate verification - use it only for testing") + reg.StringWithEnv(&tlsConfig.MinTLSVersion, "tls-min-version", "", "TLS_MIN_VERSION", "Minimum TLS version for peer pod connections (VersionTLS12 or VersionTLS13)") + reg.StringWithEnv(&tlsCipherSuites, "tls-cipher-suites", "", "TLS_CIPHER_SUITES", "Comma-separated IANA TLS cipher suite names for peer pod connections (not applicable for VersionTLS13)") reg.DurationWithEnv(&cfg.serverConfig.ProxyTimeout, "proxy-timeout", proxy.DefaultProxyTimeout, "PROXY_TIMEOUT", "Maximum timeout in minutes for establishing agent proxy connection") reg.StringWithEnv(&cfg.networkConfig.TunnelType, "tunnel-type", podnetwork.DefaultTunnelType, "TUNNEL_TYPE", "Tunnel provider") reg.IntWithEnv(&cfg.networkConfig.VXLAN.Port, "vxlan-port", vxlan.DefaultVXLANPort, "VXLAN_PORT", "VXLAN UDP port number (VXLAN tunnel mode only") @@ -127,8 +130,22 @@ func (cfg *daemonConfig) Setup() (cmd.Starter, error) { fmt.Printf("%s: starting Cloud API Adaptor daemon for %q\n", programName, cloudName) - if !disableTLS { + if tlsCipherSuites != "" { + tlsConfig.CipherSuites = strings.Split(tlsCipherSuites, ",") + } + + if disableTLS { + fmt.Printf("%s: WARNING: TLS disabled (--disable-tls). Use only for testing.\n", programName) + } else { cfg.serverConfig.TLSConfig = &tlsConfig + + if tlsConfig.SkipVerify { + fmt.Printf("%s: WARNING: TLS certificate verification disabled (--tls-skip-verify). Use only for testing.\n", programName) + } + + if tlsConfig.MinTLSVersion != "" || len(tlsConfig.CipherSuites) > 0 { + fmt.Printf("%s: WARNING: TLS profile (TLS_MIN_VERSION=%s, TLS_CIPHER_SUITES=%s) is baked into peer pod VMs at creation time via user-data. Existing peer pods will not pick up profile changes until deleted and recreated.\n", programName, tlsConfig.MinTLSVersion, tlsCipherSuites) + } } // DEPRECATED: LoadEnv() is now a no-op for all providers. diff --git a/src/cloud-api-adaptor/test/e2e/common_suite.go b/src/cloud-api-adaptor/test/e2e/common_suite.go index e5e57f0f8f..89a9c0def4 100644 --- a/src/cloud-api-adaptor/test/e2e/common_suite.go +++ b/src/cloud-api-adaptor/test/e2e/common_suite.go @@ -811,3 +811,20 @@ func DoTestPodVMwithAnnotationMemory(t *testing.T, e env.Environment, assert Clo pod := NewPod(E2eNamespace, podName, containerName, imageName, WithCommand([]string{"/bin/sh", "-c", "sleep 3600"}), WithAnnotations(annotationData)) NewTestCase(t, e, "PodVMwithAnnotationMemory", assert, "PodVM with Annotation Memory is created").WithPod(pod).WithExpectedInstanceType(expectedType).Run() } + +// DoTestPodWithTLSMinVersion verifies that a peer pod starts successfully when +// a TLS minimum version is configured via the TLS_MIN_VERSION env var on the +// CAA DaemonSet. This proves the full config flow: +// +// TLS_MIN_VERSION env var → TLSConfig → daemonConfig → cloud-init → APF TLS listener → handshake with CAA. +// +// Skipped when TLS_MIN_VERSION is not set in the test environment. +func DoTestPodWithTLSMinVersion(t *testing.T, e env.Environment, assert CloudAssert) { + tlsMinVersion := os.Getenv("TLS_MIN_VERSION") + if tlsMinVersion == "" { + t.Skip("TLS_MIN_VERSION not set in environment, skipping TLS profile e2e test") + } + + pod := NewBusyboxPodWithName(E2eNamespace, "tls-min-version-test").GetPodOrFatal(t) + NewTestCase(t, e, "PodWithTLSMinVersion", assert, "PodVM starts with TLS_MIN_VERSION="+tlsMinVersion).WithPod(pod).Run() +} From e86a7407e84f912a25ef8669e1d2670fe9124079 Mon Sep 17 00:00:00 2001 From: Thejas N Date: Tue, 2 Jun 2026 10:00:22 +0530 Subject: [PATCH 8/9] webhook: honour TLS_MIN_VERSION and TLS_CIPHER_SUITES env vars The webhook server's TLS configuration is fixed at Go defaults with no way to set the minimum version or restrict cipher suites at runtime. Add support for TLS_MIN_VERSION and TLS_CIPHER_SUITES env vars. When set, these are applied to the webhook server's TLS configuration at startup. Signed-off-by: Thejas N (cherry picked from commit 5829b8d64a3bc5960ddd27d4152e1e032c87a38d) --- src/webhook/main.go | 77 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/webhook/main.go b/src/webhook/main.go index 65831a7078..0f467eebff 100644 --- a/src/webhook/main.go +++ b/src/webhook/main.go @@ -17,8 +17,11 @@ limitations under the License. package main import ( + "crypto/tls" "flag" + "fmt" "os" + "strings" "github.com/confidential-containers/cloud-api-adaptor/src/webhook/pkg/mutating_webhook" @@ -49,6 +52,44 @@ func init() { //+kubebuilder:scaffold:scheme } +// tlsVersionFromString maps a TLS version string to the corresponding uint16. +// Returns tls.VersionTLS12 for empty input. Rejects TLS 1.0 and 1.1. +func tlsVersionFromString(v string) (uint16, error) { + switch v { + case "", "VersionTLS12": + return tls.VersionTLS12, nil + case "VersionTLS13": + return tls.VersionTLS13, nil + case "VersionTLS10", "VersionTLS11": + return 0, fmt.Errorf("invalid minVersion %q: TLS 1.0 and 1.1 are not supported, use VersionTLS12 or VersionTLS13", v) + default: + return 0, fmt.Errorf("unknown TLS version %q, use VersionTLS12 or VersionTLS13", v) + } +} + +// tlsCipherSuitesFromNames maps IANA cipher suite names to their crypto/tls uint16 IDs. +// Leading/trailing whitespace is trimmed and empty entries are ignored. +func tlsCipherSuitesFromNames(names []string) ([]uint16, error) { + all := append(tls.CipherSuites(), tls.InsecureCipherSuites()...) + byName := make(map[string]uint16, len(all)) + for _, cs := range all { + byName[cs.Name] = cs.ID + } + var ids []uint16 + for _, n := range names { + n = strings.TrimSpace(n) + if n == "" { + continue + } + id, ok := byName[n] + if !ok { + return nil, fmt.Errorf("unknown cipher suite %q", n) + } + ids = append(ids, id) + } + return ids, nil +} + func main() { var metricsAddr string var enableLeaderElection bool @@ -66,6 +107,39 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + // Build TLS options from operator-injected env vars. + // The operator is the single source of truth for the cluster TLS profile. + var webhookTLSOpts []func(*tls.Config) + minVersionStr := strings.TrimSpace(os.Getenv("TLS_MIN_VERSION")) + cipherSuitesStr := strings.TrimSpace(os.Getenv("TLS_CIPHER_SUITES")) + + if minVersionStr != "" || cipherSuitesStr != "" { + minVersion, err := tlsVersionFromString(minVersionStr) + if err != nil { + setupLog.Error(err, "invalid TLS_MIN_VERSION") + os.Exit(1) + } + + var cipherSuiteIDs []uint16 + if cipherSuitesStr != "" { + if minVersion == tls.VersionTLS13 { + setupLog.Error(fmt.Errorf("cipher suites may not be specified when TLS_MIN_VERSION is VersionTLS13"), "invalid TLS configuration") + os.Exit(1) + } + names := strings.Split(cipherSuitesStr, ",") + cipherSuiteIDs, err = tlsCipherSuitesFromNames(names) + if err != nil { + setupLog.Error(err, "invalid TLS_CIPHER_SUITES") + os.Exit(1) + } + } + + webhookTLSOpts = append(webhookTLSOpts, func(c *tls.Config) { + c.MinVersion = minVersion + c.CipherSuites = cipherSuiteIDs + }) + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: metricsserver.Options{ @@ -73,7 +147,8 @@ func main() { }, WebhookServer: &webhook.DefaultServer{ Options: webhook.Options{ - Port: 9443, + Port: 9443, + TLSOpts: webhookTLSOpts, }, }, HealthProbeBindAddress: probeAddr, From a45adf560d8b78bda600bde2120cc092dc38ed33 Mon Sep 17 00:00:00 2001 From: Thejas N Date: Thu, 4 Jun 2026 21:54:00 +0530 Subject: [PATCH 9/9] charts: add TLS min version and cipher suite configuration Without an operator, there is no way to configure the minimum TLS version or permitted cipher suites for peer pod connections or the webhook server via helm. Add tlsProfile.minVersion and tlsProfile.cipherSuites to the peerpods chart values. The configmap template renders these into peer-pods-cm so the CAA DaemonSet picks them up via envFrom. Add tlsMinVersion and tlsCipherSuites to the webhook chart values and render them as TLS_MIN_VERSION and TLS_CIPHER_SUITES env vars on the manager container. Bump webhook chart version to 0.3.2. Signed-off-by: Thejas N (cherry picked from commit 06b77cd4bb1b58cf4eb108994625460063c9f6c0) --- .../install/charts/peerpods/Chart.yaml | 2 +- .../charts/peerpods/templates/configmap.yaml | 6 ++++++ .../install/charts/peerpods/values.yaml | 13 +++++++++++++ src/webhook/chart/Chart.yaml | 4 ++-- src/webhook/chart/templates/deployment.yaml | 8 ++++++++ src/webhook/chart/values.yaml | 10 ++++++++++ 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/cloud-api-adaptor/install/charts/peerpods/Chart.yaml b/src/cloud-api-adaptor/install/charts/peerpods/Chart.yaml index e0592fa56c..18035e2cd3 100644 --- a/src/cloud-api-adaptor/install/charts/peerpods/Chart.yaml +++ b/src/cloud-api-adaptor/install/charts/peerpods/Chart.yaml @@ -30,7 +30,7 @@ dependencies: alias: resourceCtrl condition: resourceCtrl.enabled - name: peerpods-webhook - version: "0.1.0" + version: "0.3.2" repository: "file://../../../../webhook/chart" alias: webhook condition: webhook.enabled diff --git a/src/cloud-api-adaptor/install/charts/peerpods/templates/configmap.yaml b/src/cloud-api-adaptor/install/charts/peerpods/templates/configmap.yaml index 11d1c09092..b321f48a78 100644 --- a/src/cloud-api-adaptor/install/charts/peerpods/templates/configmap.yaml +++ b/src/cloud-api-adaptor/install/charts/peerpods/templates/configmap.yaml @@ -13,3 +13,9 @@ data: {{- range $key, $value := $config }} {{ $key }}: "{{ $value }}" {{- end }} +{{- if .Values.tlsProfile.minVersion }} + TLS_MIN_VERSION: "{{ .Values.tlsProfile.minVersion }}" +{{- end }} +{{- if .Values.tlsProfile.cipherSuites }} + TLS_CIPHER_SUITES: "{{ .Values.tlsProfile.cipherSuites }}" +{{- end }} diff --git a/src/cloud-api-adaptor/install/charts/peerpods/values.yaml b/src/cloud-api-adaptor/install/charts/peerpods/values.yaml index 4e3b5d3a62..6924026497 100644 --- a/src/cloud-api-adaptor/install/charts/peerpods/values.yaml +++ b/src/cloud-api-adaptor/install/charts/peerpods/values.yaml @@ -83,6 +83,19 @@ image: # Cloud provider: libvirt, aws, azure, gcp, ibmcloud, vsphere provider: libvirt +# TLS profile for CAA and peer pod connections. +# When set, these values are injected into the peer-pods-cm ConfigMap as +# TLS_MIN_VERSION and TLS_CIPHER_SUITES env vars consumed by the CAA DaemonSet. +# On managed platforms, the operator injects these directly and this section +# is not needed. +tlsProfile: + # Minimum TLS version. Accepts "VersionTLS12" or "VersionTLS13". + # Defaults to TLS 1.2 when empty. + minVersion: "" + # Comma-separated IANA cipher suite names. + # Must not be set when minVersion is VersionTLS13. + cipherSuites: "" + # Maximum number of peer pods allowed to run simultaneously # This limit prevents resource exhaustion on the cloud provider # Set to "0" for unlimited (not recommended for production) diff --git a/src/webhook/chart/Chart.yaml b/src/webhook/chart/Chart.yaml index 977b8c88ed..31f7e4c457 100644 --- a/src/webhook/chart/Chart.yaml +++ b/src/webhook/chart/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: peerpods-webhook description: Mutating webhook that modifies pod specs to use peer pods runtime and resources type: application -version: 0.1.0 -appVersion: "v0.17.0" +version: 0.3.2 +appVersion: "v0.21.1" keywords: - confidential-containers diff --git a/src/webhook/chart/templates/deployment.yaml b/src/webhook/chart/templates/deployment.yaml index 8b2a489b3d..06c53a2773 100644 --- a/src/webhook/chart/templates/deployment.yaml +++ b/src/webhook/chart/templates/deployment.yaml @@ -67,6 +67,14 @@ spec: value: {{ .Values.webhook.targetRuntimeClass }} - name: POD_VM_EXTENDED_RESOURCE value: {{ .Values.webhook.podVMExtendedResource }} +{{- if .Values.tlsMinVersion }} + - name: TLS_MIN_VERSION + value: {{ .Values.tlsMinVersion }} +{{- end }} +{{- if .Values.tlsCipherSuites }} + - name: TLS_CIPHER_SUITES + value: {{ .Values.tlsCipherSuites }} +{{- end }} livenessProbe: httpGet: path: /healthz diff --git a/src/webhook/chart/values.yaml b/src/webhook/chart/values.yaml index 7ad171235a..d4ddd7c4d2 100644 --- a/src/webhook/chart/values.yaml +++ b/src/webhook/chart/values.yaml @@ -50,6 +50,16 @@ authProxy: cpu: 5m memory: 64Mi +# Minimum TLS version for the webhook server. Accepts "VersionTLS12" or "VersionTLS13". +# Defaults to Go's TLS default (TLS 1.2) when empty. +# Injected by the operator from the cluster TLS profile via TLS_MIN_VERSION env var. +tlsMinVersion: "" + +# Comma-separated list of IANA TLS cipher suite names for the webhook server. +# Not applicable when tlsMinVersion is VersionTLS13. +# Injected by the operator from the cluster TLS profile via TLS_CIPHER_SUITES env var. +tlsCipherSuites: "" + # cert-manager integration for webhook TLS certificates # Required because Kubernetes admission webhooks must use TLS/HTTPS # cert-manager automates certificate generation, rotation, and trust configuration