From bd575a6fe99b62fea369d919e05e58054f1016ed Mon Sep 17 00:00:00 2001 From: Max Sokolovsky Date: Fri, 8 Nov 2024 12:12:03 -0500 Subject: [PATCH 01/10] [WIP] Imperative API: Proxy and port-forward (#85) * Add a port-forward type * Add support for automatic reconnects when pods are terminated * List clients * Add a Dockerfile for the proxy * Add proxy * Update go.mod * Update package name * Add config parsing * Immediately close connection when there are no clients --- Dockerfile.proxy | 13 +++ cmd/proxy/main.go | 18 ++++ forward/port-forward.go | 157 +++++++++++++++++++++++++++++++++++ go.mod | 59 +++++++++++-- go.sum | 180 ++++++++++++++++++++++++++++++++++++---- proxy/config.go | 26 ++++++ proxy/proxy.go | 118 ++++++++++++++++++++++++++ server.go | 4 + session_manager.go | 11 +++ 9 files changed, 560 insertions(+), 26 deletions(-) create mode 100644 Dockerfile.proxy create mode 100644 cmd/proxy/main.go create mode 100644 forward/port-forward.go create mode 100644 proxy/config.go create mode 100644 proxy/proxy.go diff --git a/Dockerfile.proxy b/Dockerfile.proxy new file mode 100644 index 0000000..679e9c5 --- /dev/null +++ b/Dockerfile.proxy @@ -0,0 +1,13 @@ +FROM golang:1.23 AS builder +WORKDIR /app + +COPY go.mod . +COPY go.sum . +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 go build -o proxy ./proxy + +FROM scratch +COPY --from=builder /app/proxy . +CMD ["./proxy"] diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go new file mode 100644 index 0000000..f088077 --- /dev/null +++ b/cmd/proxy/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/sirupsen/logrus" + + "github.com/rancher/remotedialer/proxy" +) + +func main() { + cfg, err := proxy.ConfigFromEnvironment() + if err != nil { + logrus.Fatalf("fatal configuration error: %v", err) + } + err = proxy.Start(cfg) + if err != nil { + logrus.Fatal(err) + } +} diff --git a/forward/port-forward.go b/forward/port-forward.go new file mode 100644 index 0000000..7492e86 --- /dev/null +++ b/forward/port-forward.go @@ -0,0 +1,157 @@ +package portforward + +import ( + "bytes" + "context" + "errors" + "fmt" + "math/rand" + "net/http" + "net/url" + "strings" + "time" + + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" +) + +type PortForward struct { + restConfig *rest.Config + podClient corev1.PodInterface + Namespace string + LabelSelector string + Ports []string + readyCh chan struct{} + stopCh chan struct{} + cancel context.CancelFunc +} + +// New initializes and returns a PortForward value after validating the incoming parameters. +func New(restConfig *rest.Config, podClient corev1.PodInterface, namespace string, labelSelector string, ports []string) (*PortForward, error) { + if restConfig == nil { + return nil, fmt.Errorf("restConfig must not be nil") + } + if podClient == nil { + return nil, fmt.Errorf("podClient must not be nil") + } + if labelSelector == "" { + return nil, fmt.Errorf("labelSelector must not be empty") + } + if len(ports) == 0 { + return nil, fmt.Errorf("ports must not be empty") + } + if namespace == "" { + return nil, fmt.Errorf("namespace must not be empty") + } + return &PortForward{ + restConfig: restConfig, + podClient: podClient, + Namespace: namespace, + LabelSelector: labelSelector, + Ports: ports, + readyCh: make(chan struct{}, 1), + stopCh: make(chan struct{}, 1), + }, nil +} + +// Stop releases the resources of the running port-forwarder. +func (r *PortForward) Stop() { + r.cancel() + r.stopCh <- struct{}{} +} + +// Start launches a port forwarder in the background. +func (r *PortForward) Start() { + ctx, cancel := context.WithCancel(context.Background()) + r.cancel = cancel + + go func() { + for { + select { + case <-ctx.Done(): + logrus.Infoln("Goroutine stopped.") + return + default: + r.readyCh = make(chan struct{}, 1) + err := r.runForwarder(r.readyCh, r.stopCh, r.Ports) + if err != nil { + if errors.Is(err, portforward.ErrLostConnectionToPod) { + logrus.Error("Lost connection to pod; restarting.") + time.Sleep(time.Second) + continue + } else { + logrus.Errorf("Non-restartable error: %v", err) + return + } + } + } + } + }() + // TODO: maybe block until port forwarding is ready. +} + +// runForwarder starts a port forwarder and blocks until it is stopped when it receives a value on the stopCh. +func (r *PortForward) runForwarder(readyCh, stopCh chan struct{}, ports []string) error { + podName, err := r.podName() + if err != nil { + return err + } + path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", r.Namespace, podName) + hostIP := strings.TrimPrefix(r.restConfig.Host, "https://") + serverURL := url.URL{ + Scheme: "https", + Path: path, + Host: hostIP, + } + roundTripper, upgrader, err := spdy.RoundTripperFor(r.restConfig) + if err != nil { + return err + } + dialer := spdy.NewDialer(upgrader, &http.Client{ + Transport: roundTripper, + }, http.MethodPost, &serverURL) + + stdout, stderr := new(bytes.Buffer), new(bytes.Buffer) + forwarder, err := portforward.New(dialer, ports, stopCh, readyCh, stdout, stderr) + if err != nil { + return err + } + + go func() { + for range readyCh { + } // Wait until port forwarding is ready. + + if s := stderr.String(); s != "" { + logrus.Error(s) + } else if s = stdout.String(); s != "" { + logrus.Info(s) + } + }() + + return forwarder.ForwardPorts() +} + +// podName tries to select a random pod and return its name from the pods that the +// underlying client finds by label selector. +// The method continuously retries if there are no pods yet that match the selector. +func (r *PortForward) podName() (string, error) { + for { + pods, err := r.podClient.List(context.Background(), metav1.ListOptions{ + LabelSelector: r.LabelSelector, + }) + if err != nil { + return "", err + } + if len(pods.Items) < 1 { + logrus.Debugf("no pod found with label selector %q, retrying", r.LabelSelector) + time.Sleep(time.Second) + continue + } + i := rand.Intn(len(pods.Items)) + return pods.Items[i].Name, nil + } +} diff --git a/go.mod b/go.mod index 6e885c5..1a9b1ed 100644 --- a/go.mod +++ b/go.mod @@ -1,30 +1,73 @@ module github.com/rancher/remotedialer -go 1.20 +go 1.23 require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 + github.com/kelseyhightower/envconfig v1.4.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.16.0 + github.com/rancher/dynamiclistener v0.6.0 + github.com/rancher/norman v0.0.0-20240827141653-344b77749da7 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 + k8s.io/apimachinery v0.30.1 + k8s.io/client-go v0.30.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect + github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 // indirect + github.com/rancher/wrangler/v3 v3.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.30.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index b2853c7..4eaa9f9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -6,55 +8,197 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= +github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuviMQAHL1Q= +github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s= +github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 h1:vv1jDlYbd4KhGbPNxmjs8CYgEHUrQm2bMtmULfXJ6iw= +github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1/go.mod h1:A/y3BLQkxZXYD60MNDRwAG9WGxXfvd6Z6gWR/a8wPw8= +github.com/rancher/norman v0.0.0-20240827141653-344b77749da7 h1:2nrarKPgyv09CsNDk63f/qnHPh4LaBPmlU17XKN+xmc= +github.com/rancher/norman v0.0.0-20240827141653-344b77749da7/go.mod h1:Lv4Ju3T80Nndnpj0fGt0FV73RA3UhEUeRo+h1mbmmx8= +github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA= +github.com/rancher/wrangler/v3 v3.0.0/go.mod h1:Dfckuuq7MJk2JWVBDywRlZXMxEyPxHy4XqGrPEzu5Eg= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/proxy/config.go b/proxy/config.go new file mode 100644 index 0000000..7a2f386 --- /dev/null +++ b/proxy/config.go @@ -0,0 +1,26 @@ +package proxy + +import ( + "github.com/kelseyhightower/envconfig" +) + +type Config struct { + Namespace string `default:"cattle-system"` + TLSName string `split_words:"true"` + CAName string `required:"true" split_words:"true"` + CertCANamespace string `required:"true" split_words:"true"` + CertCAName string `required:"true" split_words:"true"` + Secret string `required:"true" split_words:"true"` + ProxyPort int `required:"true" split_words:"true"` + PeerPort int `required:"true" split_words:"true"` + HTTPSPort int `required:"true" split_words:"true"` +} + +func ConfigFromEnvironment() (*Config, error) { + var c Config + err := envconfig.Process("", &c) + if err != nil { + return nil, err + } + return &c, nil +} diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 0000000..4eb538b --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,118 @@ +package proxy + +import ( + "context" + "fmt" + "io" + "math/rand" + "net" + "net/http" + + "github.com/gorilla/mux" + "github.com/rancher/dynamiclistener" + "github.com/rancher/dynamiclistener/server" + "github.com/sirupsen/logrus" + + "github.com/rancher/remotedialer" +) + +func runProxyListener(ctx context.Context, cfg *Config, server *remotedialer.Server) error { + l, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", cfg.ProxyPort)) //this RDP app starts only once and always running + if err != nil { + return err + } + defer l.Close() + + for { + conn, err := l.Accept() // the client of 6666 is kube-apiserver, according to the APIService object spec, just to this TCP 6666 + if err != nil { + logrus.Errorf("proxy TCP connection accept failed: %v", err) + continue + } + + go func() { + clients := server.ListClients() + if len(clients) == 0 { + logrus.Info("proxy TCP connection failed: no clients") + conn.Close() + return + } + client := clients[rand.Intn(len(clients))] + peerAddr := fmt.Sprintf(":%d", cfg.PeerPort) // rancher's special https server for imperative API + clientConn, err := server.Dialer(client)(ctx, "tcp", peerAddr) + if err != nil { + logrus.Errorf("proxy dialing %s failed: %v", peerAddr, err) + conn.Close() + return + } + + go pipe(conn, clientConn) + go pipe(clientConn, conn) + }() + } +} + +func pipe(a, b net.Conn) { + defer func(a net.Conn) { + if err := a.Close(); err != nil { + logrus.Errorf("proxy TCP connection close failed: %v", err) + } + }(a) + defer func(b net.Conn) { + if err := b.Close(); err != nil { + logrus.Errorf("proxy TCP connection close failed: %v", err) + } + }(b) + n, err := io.Copy(a, b) + if err != nil { + logrus.Errorf("proxy copy failed: %v", err) + return + } + logrus.Debugf("proxy copied %d bytes to %v from %v", n, a.LocalAddr(), b.LocalAddr()) +} + +func Start(cfg *Config) error { + logrus.SetLevel(logrus.DebugLevel) + ctx := context.Background() + router := mux.NewRouter() + authorizer := func(req *http.Request) (string, bool, error) { + // TODO: Actually do authorization here with a shared Secret, compare + id := req.Header.Get("X-Tunnel-ID") + if id == "" { + return "", false, fmt.Errorf("X-Tunnel-ID not specified in request header") + } + return id, true, nil + } + remoteDialerServer := remotedialer.New(authorizer, remotedialer.DefaultErrorWriter) + + // rancher will connect via its remotedialer port-forwarder + router.Handle("/connect", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + remoteDialerServer.ServeHTTP(w, req) + })) + + go func() { + if err := runProxyListener(ctx, cfg, remoteDialerServer); err != nil { + logrus.Errorf("proxy listener failed to start in the background: %v", err) + } + }() + + // the secret will be created in the cluster. + // only started once, always running, RDP pod, this app + if err := server.ListenAndServe(ctx, cfg.HTTPSPort, 0, router, &server.ListenOpts{ + //Secrets: wContext.Core.Secret(), + CAName: cfg.CAName, + CANamespace: cfg.Namespace, + CertName: cfg.CertCAName, + CertNamespace: cfg.CertCANamespace, + TLSListenerConfig: dynamiclistener.Config{ + SANs: []string{cfg.TLSName}, + FilterCN: func(cns ...string) []string { + return []string{cfg.TLSName} + }, + }, + }); err != nil { + return fmt.Errorf("extension server exited with an error: %w", err) + } + <-ctx.Done() + return nil +} diff --git a/server.go b/server.go index 17cf77f..e752a21 100644 --- a/server.go +++ b/server.go @@ -79,6 +79,10 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } +func (s *Server) ListClients() []string { + return s.sessions.listClients() +} + func (s *Server) auth(req *http.Request) (clientKey string, authed, peer bool, err error) { id := req.Header.Get(ID) token := req.Header.Get(Token) diff --git a/session_manager.go b/session_manager.go index 6fa0a05..36eef21 100644 --- a/session_manager.go +++ b/session_manager.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/gorilla/websocket" + "github.com/rancher/remotedialer/metrics" ) @@ -66,6 +67,16 @@ func (sm *sessionManager) addListener(listener sessionListener) { } } +func (sm *sessionManager) listClients() []string { + sm.Lock() + defer sm.Unlock() + clients := make([]string, 0, len(sm.clients)) + for c := range sm.clients { + clients = append(clients, c) + } + return clients +} + func (sm *sessionManager) getDialer(clientKey string) (Dialer, error) { sm.Lock() defer sm.Unlock() From 50cd851de74515330647d8da32c224f0f89bcd03 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Tue, 28 Jan 2025 15:34:05 -0300 Subject: [PATCH 02/10] added proxy client and server working impl, also some examples --- Dockerfile.proxy | 2 +- cmd/proxy/deployment.yaml | 93 ++++++++++++ cmd/proxy/main.go | 14 +- examples/fakek8s/Dockerfile | 13 ++ examples/fakek8s/deployment.yaml | 28 ++++ examples/fakek8s/go.mod | 3 + examples/fakek8s/main.go | 113 ++++++++++++++ examples/proxyclient/main.go | 192 ++++++++++++++++++++++++ forward/{port-forward.go => forward.go} | 114 +++++++------- go.mod | 5 +- go.sum | 2 - proxy/{proxy.go => server.go} | 28 ++-- proxyclient/client.go | 173 +++++++++++++++++++++ secret.go | 16 ++ 14 files changed, 727 insertions(+), 69 deletions(-) create mode 100644 cmd/proxy/deployment.yaml create mode 100644 examples/fakek8s/Dockerfile create mode 100644 examples/fakek8s/deployment.yaml create mode 100644 examples/fakek8s/go.mod create mode 100644 examples/fakek8s/main.go create mode 100644 examples/proxyclient/main.go rename forward/{port-forward.go => forward.go} (50%) rename proxy/{proxy.go => server.go} (83%) create mode 100644 proxyclient/client.go create mode 100644 secret.go diff --git a/Dockerfile.proxy b/Dockerfile.proxy index 679e9c5..f350d73 100644 --- a/Dockerfile.proxy +++ b/Dockerfile.proxy @@ -6,7 +6,7 @@ COPY go.sum . RUN go mod download COPY . . -RUN CGO_ENABLED=0 go build -o proxy ./proxy +RUN CGO_ENABLED=0 go build -o proxy ./cmd/proxy FROM scratch COPY --from=builder /app/proxy . diff --git a/cmd/proxy/deployment.yaml b/cmd/proxy/deployment.yaml new file mode 100644 index 0000000..495a367 --- /dev/null +++ b/cmd/proxy/deployment.yaml @@ -0,0 +1,93 @@ +#FIXME This is temporary file. This should be converted into Helm Charts in the charts repo. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: remotedialer-proxy + namespace: cattle-system + labels: + app: remotedialer-proxy +spec: + replicas: 1 + selector: + matchLabels: + app: remotedialer-proxy + template: + metadata: + labels: + app: remotedialer-proxy + spec: + containers: + - name: remotedialer-proxy + image: rancher/remotedialer-proxy:latest + imagePullPolicy: IfNotPresent + env: + - name: TLS_NAME + value: "remotedialer-proxy" + - name: CA_NAME + value: "remotedialer-proxy-ca" + - name: CERT_CA_NAMESPACE + value: "cattle-system" + - name: CERT_CA_NAME + value: "remotedialer-proxy-cert" + - name: SECRET + value: "secret" # X-Tunnel-ID header secret + - name: PROXY_PORT + value: "6666" # The proxy TCP port for kube-apiserver traffic + - name: PEER_PORT + value: "8888" # The port used to connect to the special "imperative API" server behind the remotedialer + - name: HTTPS_PORT + value: "8443" # The dynamiclistener HTTPS port for /connect + ports: + - containerPort: 6666 + name: proxy + - containerPort: 8443 + name: https + - containerPort: 8888 + name: peer + +--- +apiVersion: v1 +kind: Service +metadata: + name: remotedialer-proxy + namespace: cattle-system + labels: + app: remotedialer-proxy +spec: + type: ClusterIP + selector: + app: remotedialer-proxy + ports: + - name: proxy + port: 6666 + targetPort: proxy + - name: https + port: 8443 + targetPort: https + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: remotedialer-proxy-secret-access + namespace: cattle-system +rules: +- apiGroups: [""] # "" indicates the core API group + resources: ["secrets"] # We need to read secrets + verbs: ["get", "list", "watch", "create", "delete"] # Allowed verbs + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: remotedialer-proxy-secret-access-binding + namespace: cattle-system +subjects: +- kind: ServiceAccount + name: default # The service account name + namespace: cattle-system # The same namespace as the SA +roleRef: + kind: Role + name: remotedialer-proxy-secret-access + apiGroup: rbac.authorization.k8s.io diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index f088077..dcba298 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -2,16 +2,28 @@ package main import ( "github.com/sirupsen/logrus" + "k8s.io/client-go/rest" + "github.com/rancher/lasso/pkg/log" "github.com/rancher/remotedialer/proxy" ) func main() { + logrus.Info("Starting Remote Dialer Proxy") + cfg, err := proxy.ConfigFromEnvironment() if err != nil { logrus.Fatalf("fatal configuration error: %v", err) } - err = proxy.Start(cfg) + + // Initializing Wrangler + restConfig, err := rest.InClusterConfig() + if err != nil { + log.Errorf("failed to get in-cluster config: %w", err) + return + } + + err = proxy.Start(cfg, restConfig) if err != nil { logrus.Fatal(err) } diff --git a/examples/fakek8s/Dockerfile b/examples/fakek8s/Dockerfile new file mode 100644 index 0000000..7ec1ace --- /dev/null +++ b/examples/fakek8s/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.23 as builder +WORKDIR /app +COPY go.mod . +RUN go mod download +COPY . . +RUN go build -o /app/tcp-client main.go + +FROM debian:bookworm-slim +COPY --from=builder /app/tcp-client . + +RUN ls . + +CMD ["./tcp-client"] \ No newline at end of file diff --git a/examples/fakek8s/deployment.yaml b/examples/fakek8s/deployment.yaml new file mode 100644 index 0000000..c2cd35a --- /dev/null +++ b/examples/fakek8s/deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fakek8s-deployment + namespace: cattle-system + labels: + app: fakek8s +spec: + replicas: 1 + selector: + matchLabels: + app: fakek8s + template: + metadata: + labels: + app: fakek8s + spec: + containers: + - name: fakek8s + image: rancher/fakek8s:latest + imagePullPolicy: IfNotPresent + env: + - name: TARGET_HOST + value: "remotedialer-proxy.cattle-system.svc.cluster.local" + - name: TARGET_PORT + value: "6666" + - name: SEND_INTERVAL + value: "1" diff --git a/examples/fakek8s/go.mod b/examples/fakek8s/go.mod new file mode 100644 index 0000000..b0dda9e --- /dev/null +++ b/examples/fakek8s/go.mod @@ -0,0 +1,3 @@ +module dummy/fakek8s + +go 1.23 diff --git a/examples/fakek8s/main.go b/examples/fakek8s/main.go new file mode 100644 index 0000000..d6741ef --- /dev/null +++ b/examples/fakek8s/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "context" + "fmt" + "net" + "os" + "os/signal" + "strconv" + "syscall" + "time" +) + +var ( + targetHost = "remotedialer-proxy.cattle-system.svc.cluster.local" + targetPort = 6666 + retryDelay = 5 * time.Second +) + +func init() { + if host, ok := os.LookupEnv("TARGET_HOST"); ok { + targetHost = host + } + + if portStr, ok := os.LookupEnv("TARGET_PORT"); ok { + if p, err := strconv.Atoi(portStr); err != nil { + fmt.Printf("Could not parse TARGET_PORT=%q: %v. Using default %d.\n", + portStr, err, targetPort) + } else { + targetPort = p + } + } + + if intervalStr, ok := os.LookupEnv("SEND_INTERVAL"); ok { + if i, err := strconv.Atoi(intervalStr); err != nil { + fmt.Printf("Could not parse SEND_INTERVAL=%q: %v. Using default %v.\n", + intervalStr, err, retryDelay) + } else { + retryDelay = time.Duration(i) * time.Second + } + } +} + +func echoHandler(ctx context.Context, conn net.Conn) { + defer conn.Close() + go func() { + <-ctx.Done() + fmt.Println("echoHandler: context canceled; closing connection.") + _ = conn.Close() + }() + + buffer := make([]byte, 1024) + for { + n, err := conn.Read(buffer) + if err != nil { + fmt.Printf("Connection closed or error occurred: %v\n", err) + return + } + + fmt.Println("Received from Server:", string(buffer[:n])) + + // Echo back the received data + if _, err := conn.Write(buffer[:n]); err != nil { + fmt.Printf("Error sending data back: %v\n", err) + return + } + + fmt.Println("Sent back to Server:", string(buffer[:n])) + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigChan + fmt.Println("main: received shutdown signal; canceling context...") + cancel() + }() + + for { + select { + case <-ctx.Done(): + fmt.Println("main: context canceled; exiting dial loop.") + return + default: + } + + fmt.Printf("Attempting to connect to %s:%d...\n", targetHost, targetPort) + + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", targetHost, targetPort)) + if err != nil { + fmt.Printf("Failed to connect: %v. Retrying in %v...\n", err, retryDelay) + time.Sleep(retryDelay) + continue + } + + fmt.Println("Connected to the server.") + + // Send a welcome message + welcomeMessage := "Hello, server! Client has connected.\nPlease type any word and hit enter:" + if _, err = conn.Write([]byte(welcomeMessage)); err != nil { + fmt.Printf("Error sending welcome message: %v\n", err) + conn.Close() + continue + } + + echoHandler(ctx, conn) + } +} diff --git a/examples/proxyclient/main.go b/examples/proxyclient/main.go new file mode 100644 index 0000000..02641c7 --- /dev/null +++ b/examples/proxyclient/main.go @@ -0,0 +1,192 @@ +package main + +import ( + "bufio" + "context" + "fmt" + "net" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + + "github.com/rancher/remotedialer/forward" + proxyclient "github.com/rancher/remotedialer/proxyclient" + "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" + "github.com/sirupsen/logrus" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" +) + +var ( + namespace = "cattle-system" + label = "app=remotedialer-proxy" + certSecretName = "remotedialer-proxy-cert" + certServerName = "remotedialer-proxy" + connectSecret = "secret" + ports = []string{"5555:8443"} + fakeImperativeAPIAddr = "0.0.0.0:8888" +) + +func init() { + if val, ok := os.LookupEnv("NAMESPACE"); ok { + namespace = val + } + if val, ok := os.LookupEnv("LABEL"); ok { + label = val + } + if val, ok := os.LookupEnv("CERT_SECRET_NAME"); ok { + certSecretName = val + } + if val, ok := os.LookupEnv("CERT_SERVER_NAME"); ok { + certServerName = val + } + if val, ok := os.LookupEnv("CONNECT_SECRET"); ok { + connectSecret = val + } + if val, ok := os.LookupEnv("PORTS"); ok { + ports = strings.Split(val, ",") + } + if val, ok := os.LookupEnv("FAKE_IMPERATIVE_API_ADDR"); ok { + fakeImperativeAPIAddr = val + } +} + +func handleConnection(ctx context.Context, conn net.Conn) { + go func() { + <-ctx.Done() + fmt.Println("handleConnection: context canceled; closing connection.") + _ = conn.Close() + }() + + defer fmt.Println("handleConnection: exiting for", conn.RemoteAddr()) + defer conn.Close() + + buffer := make([]byte, 1024) + for { + n, err := conn.Read(buffer) + if err != nil { + fmt.Println("Connection closed or error occurred:", err) + return + } + fmt.Println("Received from Client", string(buffer[:n])) + } +} + +func handleKeyboardInput(ctx context.Context, conn net.Conn) { + go func() { + <-ctx.Done() + fmt.Println("handleKeyboardInput: context canceled; closing connection.") + _ = conn.Close() + }() + + defer fmt.Println("handleKeyboardInput: exiting for", conn.RemoteAddr()) + defer conn.Close() + + reader := bufio.NewReader(os.Stdin) + for { + input, err := reader.ReadByte() + if err != nil { + fmt.Println("Error reading keyboard input:", err) + return + } + + _, err = conn.Write([]byte{input}) + if err != nil { + fmt.Println("Error sending data to client:", err) + return + } + } +} + +func fakeImperativeAPI(ctx context.Context) error { + ln, err := net.Listen("tcp", fakeImperativeAPIAddr) + if err != nil { + return fmt.Errorf("Error starting server on %s: %w", fakeImperativeAPIAddr, err) + } + fmt.Printf("Server listening on %s...\n", fakeImperativeAPIAddr) + + go func() { + <-ctx.Done() + fmt.Println("fakeImperativeAPI: context canceled; closing listener.") + _ = ln.Close() + }() + + for { + conn, acceptErr := ln.Accept() + if acceptErr != nil { + select { + case <-ctx.Done(): + fmt.Println("fakeImperativeAPI: accept loop stopping; context is done.") + return nil + default: + return fmt.Errorf("fakeImperativeAPI: error accepting connection: %w", acceptErr) + } + } + + fmt.Println("Connection established with client:", conn.RemoteAddr()) + + go handleConnection(ctx, conn) + go handleKeyboardInput(ctx, conn) + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + if err := fakeImperativeAPI(ctx); err != nil { + logrus.Errorf("fakeImperativeAPI error: %v", err) + cancel() + } + }() + + home := homedir.HomeDir() + kubeConfigPath := filepath.Join(home, ".kube", "config") + cfg, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) + if err != nil { + panic(err.Error()) + } + + coreFactory, err := core.NewFactoryFromConfigWithOptions(cfg, nil) + if err != nil { + logrus.Fatal(err) + } + + podClient := coreFactory.Core().V1().Pod() + + portForwarder, err := forward.New(cfg, podClient, namespace, label, ports) + if err != nil { + logrus.Fatal(err) + } + + proxyClient, err := proxyclient.New( + connectSecret, + namespace, + certSecretName, + certServerName, + cfg, + portForwarder, + ) + if err != nil { + logrus.Fatal(err) + } + + go func() { + logrus.Info("RDP Client Started... Waiting for CTRL+C") + <-sigChan + logrus.Info("Stopping...") + + cancel() + proxyClient.Stop() + }() + + if err := proxyClient.Start(ctx); err != nil { + logrus.Fatal(err) + } +} diff --git a/forward/port-forward.go b/forward/forward.go similarity index 50% rename from forward/port-forward.go rename to forward/forward.go index 7492e86..80d53ed 100644 --- a/forward/port-forward.go +++ b/forward/forward.go @@ -1,4 +1,4 @@ -package portforward +package forward import ( "bytes" @@ -11,9 +11,9 @@ import ( "strings" "time" + v1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" @@ -21,17 +21,17 @@ import ( type PortForward struct { restConfig *rest.Config - podClient corev1.PodInterface - Namespace string - LabelSelector string + podClient v1.PodController + namespace string + labelSelector string Ports []string - readyCh chan struct{} - stopCh chan struct{} - cancel context.CancelFunc + + readyCh chan struct{} + stopCh chan struct{} + cancel context.CancelFunc } -// New initializes and returns a PortForward value after validating the incoming parameters. -func New(restConfig *rest.Config, podClient corev1.PodInterface, namespace string, labelSelector string, ports []string) (*PortForward, error) { +func New(restConfig *rest.Config, podClient v1.PodController, namespace string, labelSelector string, ports []string) (*PortForward, error) { if restConfig == nil { return nil, fmt.Errorf("restConfig must not be nil") } @@ -47,25 +47,35 @@ func New(restConfig *rest.Config, podClient corev1.PodInterface, namespace strin if namespace == "" { return nil, fmt.Errorf("namespace must not be empty") } + + for _, p := range ports { + if strings.HasPrefix(p, "0:") { + return nil, fmt.Errorf("cannot bind port zero", p) + } + } + return &PortForward{ restConfig: restConfig, podClient: podClient, - Namespace: namespace, - LabelSelector: labelSelector, + namespace: namespace, + labelSelector: labelSelector, Ports: ports, readyCh: make(chan struct{}, 1), stopCh: make(chan struct{}, 1), }, nil } -// Stop releases the resources of the running port-forwarder. func (r *PortForward) Stop() { r.cancel() r.stopCh <- struct{}{} } -// Start launches a port forwarder in the background. -func (r *PortForward) Start() { +func (r *PortForward) Start() error { + var failed bool + var err error + + r.readyCh = make(chan struct{}, 1) + ctx, cancel := context.WithCancel(context.Background()) r.cancel = cancel @@ -76,37 +86,46 @@ func (r *PortForward) Start() { logrus.Infoln("Goroutine stopped.") return default: - r.readyCh = make(chan struct{}, 1) - err := r.runForwarder(r.readyCh, r.stopCh, r.Ports) + err = r.runForwarder(ctx, r.readyCh, r.stopCh, r.Ports) if err != nil { if errors.Is(err, portforward.ErrLostConnectionToPod) { - logrus.Error("Lost connection to pod; restarting.") - time.Sleep(time.Second) - continue + logrus.Errorf("Lost connection to pod (no automatic retry in this refactor): %v", err) } else { logrus.Errorf("Non-restartable error: %v", err) + failed = true + r.readyCh <- struct{}{} return } } } } }() - // TODO: maybe block until port forwarding is ready. + + // wait for the port forward to be ready if not failed + <-r.readyCh + + if failed { + return err + } + + return nil } -// runForwarder starts a port forwarder and blocks until it is stopped when it receives a value on the stopCh. -func (r *PortForward) runForwarder(readyCh, stopCh chan struct{}, ports []string) error { - podName, err := r.podName() +func (r *PortForward) runForwarder(ctx context.Context, readyCh, stopCh chan struct{}, ports []string) error { + podName, err := findPodName(ctx, r.namespace, r.labelSelector, r.podClient) if err != nil { return err } - path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", r.Namespace, podName) + logrus.Infof("Selected pod %q for label %q", podName, r.labelSelector) + + path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", r.namespace, podName) hostIP := strings.TrimPrefix(r.restConfig.Host, "https://") serverURL := url.URL{ Scheme: "https", Path: path, Host: hostIP, } + roundTripper, upgrader, err := spdy.RoundTripperFor(r.restConfig) if err != nil { return err @@ -121,37 +140,28 @@ func (r *PortForward) runForwarder(readyCh, stopCh chan struct{}, ports []string return err } - go func() { - for range readyCh { - } // Wait until port forwarding is ready. - - if s := stderr.String(); s != "" { - logrus.Error(s) - } else if s = stdout.String(); s != "" { - logrus.Info(s) - } - }() - return forwarder.ForwardPorts() } -// podName tries to select a random pod and return its name from the pods that the -// underlying client finds by label selector. -// The method continuously retries if there are no pods yet that match the selector. -func (r *PortForward) podName() (string, error) { +func findPodName(ctx context.Context, namespace, labelSelector string, podClient v1.PodClient) (string, error) { for { - pods, err := r.podClient.List(context.Background(), metav1.ListOptions{ - LabelSelector: r.LabelSelector, - }) - if err != nil { - return "", err - } - if len(pods.Items) < 1 { - logrus.Debugf("no pod found with label selector %q, retrying", r.LabelSelector) - time.Sleep(time.Second) - continue + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + pods, err := podClient.List(namespace, metav1.ListOptions{ + LabelSelector: labelSelector, + }) + if err != nil { + return "", err + } + if len(pods.Items) < 1 { + logrus.Debugf("no pod found with label selector %q, retrying in 1s", labelSelector) + time.Sleep(time.Second) + continue + } + i := rand.Intn(len(pods.Items)) + return pods.Items[i].Name, nil } - i := rand.Intn(len(pods.Items)) - return pods.Items[i].Name, nil } } diff --git a/go.mod b/go.mod index 1a9b1ed..85da3f6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,8 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.16.0 github.com/rancher/dynamiclistener v0.6.0 - github.com/rancher/norman v0.0.0-20240827141653-344b77749da7 + github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 + github.com/rancher/wrangler/v3 v3.0.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 k8s.io/apimachinery v0.30.1 @@ -46,8 +47,6 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect - github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 // indirect - github.com/rancher/wrangler/v3 v3.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.22.0 // indirect diff --git a/go.sum b/go.sum index 4eaa9f9..a8c57ee 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,6 @@ github.com/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuv github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s= github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 h1:vv1jDlYbd4KhGbPNxmjs8CYgEHUrQm2bMtmULfXJ6iw= github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1/go.mod h1:A/y3BLQkxZXYD60MNDRwAG9WGxXfvd6Z6gWR/a8wPw8= -github.com/rancher/norman v0.0.0-20240827141653-344b77749da7 h1:2nrarKPgyv09CsNDk63f/qnHPh4LaBPmlU17XKN+xmc= -github.com/rancher/norman v0.0.0-20240827141653-344b77749da7/go.mod h1:Lv4Ju3T80Nndnpj0fGt0FV73RA3UhEUeRo+h1mbmmx8= github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA= github.com/rancher/wrangler/v3 v3.0.0/go.mod h1:Dfckuuq7MJk2JWVBDywRlZXMxEyPxHy4XqGrPEzu5Eg= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= diff --git a/proxy/proxy.go b/proxy/server.go similarity index 83% rename from proxy/proxy.go rename to proxy/server.go index 4eb538b..7c7c125 100644 --- a/proxy/proxy.go +++ b/proxy/server.go @@ -12,6 +12,7 @@ import ( "github.com/rancher/dynamiclistener" "github.com/rancher/dynamiclistener/server" "github.com/sirupsen/logrus" + "k8s.io/client-go/rest" "github.com/rancher/remotedialer" ) @@ -71,22 +72,25 @@ func pipe(a, b net.Conn) { logrus.Debugf("proxy copied %d bytes to %v from %v", n, a.LocalAddr(), b.LocalAddr()) } -func Start(cfg *Config) error { +func Start(cfg *Config, restConfig *rest.Config) error { logrus.SetLevel(logrus.DebugLevel) ctx := context.Background() - router := mux.NewRouter() + + // Setting Up Default Authorizer authorizer := func(req *http.Request) (string, bool, error) { - // TODO: Actually do authorization here with a shared Secret, compare - id := req.Header.Get("X-Tunnel-ID") - if id == "" { - return "", false, fmt.Errorf("X-Tunnel-ID not specified in request header") + id := req.Header.Get("X-API-Tunnel-Secret") + if id != cfg.Secret { + return "", false, fmt.Errorf("X-API-Tunnel-Secret not specified in request header") } return id, true, nil } + + // Initializing Remote Dialer Server remoteDialerServer := remotedialer.New(authorizer, remotedialer.DefaultErrorWriter) - // rancher will connect via its remotedialer port-forwarder + router := mux.NewRouter() router.Handle("/connect", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + logrus.Info("got a connection") remoteDialerServer.ServeHTTP(w, req) })) @@ -96,10 +100,14 @@ func Start(cfg *Config) error { } }() - // the secret will be created in the cluster. - // only started once, always running, RDP pod, this app + // Setting Up Remote Dialer HTTPS Server + secretController, err := remotedialer.BuildSecretController(restConfig) + if err != nil { + return fmt.Errorf("build secret controller failed w/ err: %w", err) + } + if err := server.ListenAndServe(ctx, cfg.HTTPSPort, 0, router, &server.ListenOpts{ - //Secrets: wContext.Core.Secret(), + Secrets: secretController, CAName: cfg.CAName, CANamespace: cfg.Namespace, CertName: cfg.CertCAName, diff --git a/proxyclient/client.go b/proxyclient/client.go new file mode 100644 index 0000000..3f53756 --- /dev/null +++ b/proxyclient/client.go @@ -0,0 +1,173 @@ +package proxyclient + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + + "github.com/gorilla/websocket" + "github.com/rancher/remotedialer" + v1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" +) + +const ( + defaultServerAddr = "wss://127.0.0.1" + defaultServerPort = 5555 + defaultServerPath = "/connect" +) + +var ( + nonTLSDialer = &websocket.Dialer{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } +) + +type PortForwarder interface { + Start() error + Stop() +} + +type ProxyClientOpt func(*ProxyClient) + +type ProxyClient struct { + forwarder PortForwarder + serverUrl string + serverConnectSecret string + dialer *websocket.Dialer + secretController v1.SecretController + + onConnect func(ctx context.Context, session *remotedialer.Session) error +} + +func New(serverSharedSecret, namespace, certSecretName, certServerName string, restConfig *rest.Config, forwarder PortForwarder, opts ...ProxyClientOpt) (*ProxyClient, error) { + if restConfig == nil { + return nil, fmt.Errorf("restConfig required") + } + + if forwarder == nil { + return nil, fmt.Errorf("a PortForwarder must be provided") + } + + if namespace == "" { + return nil, fmt.Errorf("namespace required") + } + + if certSecretName == "" { + return nil, fmt.Errorf("certSecretName required") + } + + serverUrl := fmt.Sprintf("%s:%d%s", defaultServerAddr, defaultServerPort, defaultServerPath) + dialer, err := buildDialer(namespace, certSecretName, certServerName, restConfig) + if err != nil { + return nil, fmt.Errorf("couldn't build dialer: %w", err) + } + + client := &ProxyClient{ + serverUrl: serverUrl, + forwarder: forwarder, + dialer: dialer, + serverConnectSecret: serverSharedSecret, + } + + for _, opt := range opts { + opt(client) + } + + return client, nil +} + +func buildDialer(namespace, certSecretName, certServerName string, restConfig *rest.Config) (*websocket.Dialer, error) { + secretController, err := remotedialer.BuildSecretController(restConfig) + if err != nil { + logrus.Error("build secret controller failed: %w, defaulting to non TLS connection", err) + return nonTLSDialer, nil + } + + secret, err := secretController.Get(namespace, certSecretName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + crtData, exists := secret.Data["tls.crt"] + if !exists { + return nil, fmt.Errorf("secret %s/%s missing tls.crt field", namespace, certSecretName) + } + + rootCAs := x509.NewCertPool() + if ok := rootCAs.AppendCertsFromPEM(crtData); !ok { + return nil, fmt.Errorf("failed to parse tls.crt from secret into a CA pool") + } + + return &websocket.Dialer{ + TLSClientConfig: &tls.Config{ + RootCAs: rootCAs, + ServerName: certServerName, + }, + }, nil +} + +func (c *ProxyClient) Start(ctx context.Context) error { + if err := c.forwarder.Start(); err != nil { + return err + } + + defer c.forwarder.Stop() + + logrus.Infof("ProxyClient connecting to %s", c.serverUrl) + + headers := http.Header{} + if c.serverConnectSecret == "" { + return fmt.Errorf("server shared secret must be provided") + } + + headers.Set("X-API-Tunnel-Secret", c.serverConnectSecret) + + authFn := func(proto, address string) bool { + return true + } + + onConnect := func(sessionCtx context.Context, session *remotedialer.Session) error { + logrus.Infoln("ProxyClient: remotedialer session connected!") + if c.onConnect != nil { + return c.onConnect(sessionCtx, session) + } + return nil + } + + if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, c.dialer, authFn, onConnect); err != nil { + return fmt.Errorf("remotedialer.ClientConnect error: %w", err) + } + + logrus.Infof("ProxyClient: ClientConnect finished. If no error, the session closed cleanly.") + return nil +} + +func (c *ProxyClient) Stop() { + if c.forwarder != nil { + c.forwarder.Stop() + logrus.Infoln("ProxyClient: port-forward stopped.") + } +} + +func WithServerURL(serverUrl string) ProxyClientOpt { + return func(pc *ProxyClient) { + pc.serverUrl = serverUrl + } +} + +func WithOnConnectCallback(onConnect func(ctx context.Context, session *remotedialer.Session) error) ProxyClientOpt { + return func(pc *ProxyClient) { + pc.onConnect = onConnect + } +} + +func WithCustomDialer(dialer *websocket.Dialer) ProxyClientOpt { + return func(pc *ProxyClient) { + pc.dialer = dialer + } +} diff --git a/secret.go b/secret.go new file mode 100644 index 0000000..7b8f7d1 --- /dev/null +++ b/secret.go @@ -0,0 +1,16 @@ +package remotedialer + +import ( + "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" + v1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" + "k8s.io/client-go/rest" +) + +func BuildSecretController(restConfig *rest.Config) (v1.SecretController, error) { + core, err := core.NewFactoryFromConfigWithOptions(restConfig, nil) + if err != nil { + return nil, err + } + + return core.Core().V1().Secret(), nil +} From d8601ed51680611fe341345dcb9f73634e6bfa31 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Tue, 28 Jan 2025 15:36:32 -0300 Subject: [PATCH 03/10] fixed formatting issue --- forward/forward.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forward/forward.go b/forward/forward.go index 80d53ed..a82c6e7 100644 --- a/forward/forward.go +++ b/forward/forward.go @@ -50,7 +50,7 @@ func New(restConfig *rest.Config, podClient v1.PodController, namespace string, for _, p := range ports { if strings.HasPrefix(p, "0:") { - return nil, fmt.Errorf("cannot bind port zero", p) + return nil, fmt.Errorf("cannot bind port zero") } } From 154d42262e1fda3788e060b76004ed5ad26573f2 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Tue, 28 Jan 2025 16:57:34 -0300 Subject: [PATCH 04/10] added proxyclient retry --- examples/proxyclient/main.go | 4 +-- proxyclient/client.go | 69 +++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/examples/proxyclient/main.go b/examples/proxyclient/main.go index 02641c7..17a61e9 100644 --- a/examples/proxyclient/main.go +++ b/examples/proxyclient/main.go @@ -186,7 +186,5 @@ func main() { proxyClient.Stop() }() - if err := proxyClient.Start(ctx); err != nil { - logrus.Fatal(err) - } + proxyClient.Run(ctx) } diff --git a/proxyclient/client.go b/proxyclient/client.go index 3f53756..ad22f14 100644 --- a/proxyclient/client.go +++ b/proxyclient/client.go @@ -61,6 +61,10 @@ func New(serverSharedSecret, namespace, certSecretName, certServerName string, r return nil, fmt.Errorf("certSecretName required") } + if serverSharedSecret == "" { + return nil, fmt.Errorf("server shared secret must be provided") + } + serverUrl := fmt.Sprintf("%s:%d%s", defaultServerAddr, defaultServerPort, defaultServerPath) dialer, err := buildDialer(namespace, certSecretName, certServerName, restConfig) if err != nil { @@ -111,40 +115,41 @@ func buildDialer(namespace, certSecretName, certServerName string, restConfig *r }, nil } -func (c *ProxyClient) Start(ctx context.Context) error { - if err := c.forwarder.Start(); err != nil { - return err - } - - defer c.forwarder.Stop() - - logrus.Infof("ProxyClient connecting to %s", c.serverUrl) - - headers := http.Header{} - if c.serverConnectSecret == "" { - return fmt.Errorf("server shared secret must be provided") - } - - headers.Set("X-API-Tunnel-Secret", c.serverConnectSecret) - - authFn := func(proto, address string) bool { - return true - } - - onConnect := func(sessionCtx context.Context, session *remotedialer.Session) error { - logrus.Infoln("ProxyClient: remotedialer session connected!") - if c.onConnect != nil { - return c.onConnect(sessionCtx, session) +func (c *ProxyClient) Run(ctx context.Context) { + go func() { + for { + select { + case <-ctx.Done(): + logrus.Infof("ProxyClient: ClientConnect finished. If no error, the session closed cleanly.") + return + + default: + if err := c.forwarder.Start(); err != nil { + logrus.Errorf("remotedialer.ProxyClient error: %s ", err) + } + + logrus.Infof("ProxyClient connecting to %s", c.serverUrl) + + headers := http.Header{} + headers.Set("X-API-Tunnel-Secret", c.serverConnectSecret) + + onConnectAuth := func(proto, address string) bool { return true } + onConnect := func(sessionCtx context.Context, session *remotedialer.Session) error { + logrus.Infoln("ProxyClient: remotedialer session connected!") + if c.onConnect != nil { + return c.onConnect(sessionCtx, session) + } + return nil + } + + if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, c.dialer, onConnectAuth, onConnect); err != nil { + logrus.Errorf("remotedialer.ClientConnect error: %w", err) + } + } } - return nil - } - - if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, c.dialer, authFn, onConnect); err != nil { - return fmt.Errorf("remotedialer.ClientConnect error: %w", err) - } + }() - logrus.Infof("ProxyClient: ClientConnect finished. If no error, the session closed cleanly.") - return nil + <-ctx.Done() } func (c *ProxyClient) Stop() { From 51b4974d5f1dbe95a245a9b87fb4d58d3a590171 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Tue, 28 Jan 2025 16:59:04 -0300 Subject: [PATCH 05/10] fix format err --- proxyclient/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyclient/client.go b/proxyclient/client.go index ad22f14..42a7161 100644 --- a/proxyclient/client.go +++ b/proxyclient/client.go @@ -143,7 +143,7 @@ func (c *ProxyClient) Run(ctx context.Context) { } if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, c.dialer, onConnectAuth, onConnect); err != nil { - logrus.Errorf("remotedialer.ClientConnect error: %w", err) + logrus.Errorf("remotedialer.ClientConnect error: %s", err.Error()) } } } From 7550820cf2e859686133b7ec82594ce52f14d9e9 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Tue, 28 Jan 2025 17:01:40 -0300 Subject: [PATCH 06/10] added retry timeout --- proxyclient/client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proxyclient/client.go b/proxyclient/client.go index 42a7161..5d306b7 100644 --- a/proxyclient/client.go +++ b/proxyclient/client.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "fmt" "net/http" + "time" "github.com/gorilla/websocket" "github.com/rancher/remotedialer" @@ -25,6 +26,7 @@ var ( nonTLSDialer = &websocket.Dialer{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } + retryTimeout = 1 * time.Second ) type PortForwarder interface { @@ -126,6 +128,8 @@ func (c *ProxyClient) Run(ctx context.Context) { default: if err := c.forwarder.Start(); err != nil { logrus.Errorf("remotedialer.ProxyClient error: %s ", err) + time.Sleep(retryTimeout) + continue } logrus.Infof("ProxyClient connecting to %s", c.serverUrl) @@ -144,6 +148,7 @@ func (c *ProxyClient) Run(ctx context.Context) { if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, c.dialer, onConnectAuth, onConnect); err != nil { logrus.Errorf("remotedialer.ClientConnect error: %s", err.Error()) + time.Sleep(retryTimeout) } } } From 57577bad98babe9c9b68c5662ef3bb3753169301 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Tue, 28 Jan 2025 17:08:52 -0300 Subject: [PATCH 07/10] stopping forwarder when remote dialer is closed --- proxyclient/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proxyclient/client.go b/proxyclient/client.go index 5d306b7..a1806e9 100644 --- a/proxyclient/client.go +++ b/proxyclient/client.go @@ -148,6 +148,7 @@ func (c *ProxyClient) Run(ctx context.Context) { if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, c.dialer, onConnectAuth, onConnect); err != nil { logrus.Errorf("remotedialer.ClientConnect error: %s", err.Error()) + c.forwarder.Stop() time.Sleep(retryTimeout) } } From 015c10151ab5e79edaa1a87e614ab87e43ff83e6 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Wed, 29 Jan 2025 12:47:14 -0300 Subject: [PATCH 08/10] addressing comments from josh --- cmd/proxy/main.go | 4 +--- cmd/proxy/{deployment.yaml => proxy.yaml} | 10 +++++----- .../fakek8s/{deployment.yaml => fakek8s.yaml} | 0 forward/forward.go | 19 +++++++++---------- proxy/server.go | 8 ++++++-- proxyclient/client.go | 6 ++++-- secret.go | 16 ---------------- 7 files changed, 25 insertions(+), 38 deletions(-) rename cmd/proxy/{deployment.yaml => proxy.yaml} (87%) rename examples/fakek8s/{deployment.yaml => fakek8s.yaml} (100%) delete mode 100644 secret.go diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index dcba298..8b2c0b2 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -4,7 +4,6 @@ import ( "github.com/sirupsen/logrus" "k8s.io/client-go/rest" - "github.com/rancher/lasso/pkg/log" "github.com/rancher/remotedialer/proxy" ) @@ -16,10 +15,9 @@ func main() { logrus.Fatalf("fatal configuration error: %v", err) } - // Initializing Wrangler restConfig, err := rest.InClusterConfig() if err != nil { - log.Errorf("failed to get in-cluster config: %w", err) + logrus.Errorf("failed to get in-cluster config: %w", err) return } diff --git a/cmd/proxy/deployment.yaml b/cmd/proxy/proxy.yaml similarity index 87% rename from cmd/proxy/deployment.yaml rename to cmd/proxy/proxy.yaml index 495a367..925c9e5 100644 --- a/cmd/proxy/deployment.yaml +++ b/cmd/proxy/proxy.yaml @@ -73,9 +73,9 @@ metadata: name: remotedialer-proxy-secret-access namespace: cattle-system rules: -- apiGroups: [""] # "" indicates the core API group - resources: ["secrets"] # We need to read secrets - verbs: ["get", "list", "watch", "create", "delete"] # Allowed verbs +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create"] --- apiVersion: rbac.authorization.k8s.io/v1 @@ -85,8 +85,8 @@ metadata: namespace: cattle-system subjects: - kind: ServiceAccount - name: default # The service account name - namespace: cattle-system # The same namespace as the SA + name: default + namespace: cattle-system roleRef: kind: Role name: remotedialer-proxy-secret-access diff --git a/examples/fakek8s/deployment.yaml b/examples/fakek8s/fakek8s.yaml similarity index 100% rename from examples/fakek8s/deployment.yaml rename to examples/fakek8s/fakek8s.yaml diff --git a/forward/forward.go b/forward/forward.go index a82c6e7..152c3cd 100644 --- a/forward/forward.go +++ b/forward/forward.go @@ -71,13 +71,12 @@ func (r *PortForward) Stop() { } func (r *PortForward) Start() error { - var failed bool - var err error - - r.readyCh = make(chan struct{}, 1) + var readyErr error ctx, cancel := context.WithCancel(context.Background()) + r.cancel = cancel + r.readyCh = make(chan struct{}, 1) go func() { for { @@ -86,14 +85,14 @@ func (r *PortForward) Start() error { logrus.Infoln("Goroutine stopped.") return default: - err = r.runForwarder(ctx, r.readyCh, r.stopCh, r.Ports) + err := r.runForwarder(ctx, r.readyCh, r.stopCh, r.Ports) if err != nil { if errors.Is(err, portforward.ErrLostConnectionToPod) { logrus.Errorf("Lost connection to pod (no automatic retry in this refactor): %v", err) } else { logrus.Errorf("Non-restartable error: %v", err) - failed = true r.readyCh <- struct{}{} + readyErr = err return } } @@ -104,15 +103,15 @@ func (r *PortForward) Start() error { // wait for the port forward to be ready if not failed <-r.readyCh - if failed { - return err + if readyErr != nil { + return readyErr } return nil } func (r *PortForward) runForwarder(ctx context.Context, readyCh, stopCh chan struct{}, ports []string) error { - podName, err := findPodName(ctx, r.namespace, r.labelSelector, r.podClient) + podName, err := lookForPodName(ctx, r.namespace, r.labelSelector, r.podClient) if err != nil { return err } @@ -143,7 +142,7 @@ func (r *PortForward) runForwarder(ctx context.Context, readyCh, stopCh chan str return forwarder.ForwardPorts() } -func findPodName(ctx context.Context, namespace, labelSelector string, podClient v1.PodClient) (string, error) { +func lookForPodName(ctx context.Context, namespace, labelSelector string, podClient v1.PodClient) (string, error) { for { select { case <-ctx.Done(): diff --git a/proxy/server.go b/proxy/server.go index 7c7c125..226e39d 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -11,6 +11,8 @@ import ( "github.com/gorilla/mux" "github.com/rancher/dynamiclistener" "github.com/rancher/dynamiclistener/server" + + "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" "github.com/sirupsen/logrus" "k8s.io/client-go/rest" @@ -100,12 +102,14 @@ func Start(cfg *Config, restConfig *rest.Config) error { } }() - // Setting Up Remote Dialer HTTPS Server - secretController, err := remotedialer.BuildSecretController(restConfig) + // Setting Up Secret Controller + core, err := core.NewFactoryFromConfigWithOptions(restConfig, nil) if err != nil { return fmt.Errorf("build secret controller failed w/ err: %w", err) } + secretController := core.Core().V1().Secret() + // Setting Up Remote Dialer HTTPS Server if err := server.ListenAndServe(ctx, cfg.HTTPSPort, 0, router, &server.ListenOpts{ Secrets: secretController, CAName: cfg.CAName, diff --git a/proxyclient/client.go b/proxyclient/client.go index a1806e9..eeb843f 100644 --- a/proxyclient/client.go +++ b/proxyclient/client.go @@ -10,6 +10,7 @@ import ( "github.com/gorilla/websocket" "github.com/rancher/remotedialer" + "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" v1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -88,12 +89,13 @@ func New(serverSharedSecret, namespace, certSecretName, certServerName string, r } func buildDialer(namespace, certSecretName, certServerName string, restConfig *rest.Config) (*websocket.Dialer, error) { - secretController, err := remotedialer.BuildSecretController(restConfig) + core, err := core.NewFactoryFromConfigWithOptions(restConfig, nil) if err != nil { logrus.Error("build secret controller failed: %w, defaulting to non TLS connection", err) - return nonTLSDialer, nil + return nonTLSDialer, err } + secretController := core.Core().V1().Secret() secret, err := secretController.Get(namespace, certSecretName, metav1.GetOptions{}) if err != nil { return nil, err diff --git a/secret.go b/secret.go deleted file mode 100644 index 7b8f7d1..0000000 --- a/secret.go +++ /dev/null @@ -1,16 +0,0 @@ -package remotedialer - -import ( - "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" - v1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" - "k8s.io/client-go/rest" -) - -func BuildSecretController(restConfig *rest.Config) (v1.SecretController, error) { - core, err := core.NewFactoryFromConfigWithOptions(restConfig, nil) - if err != nil { - return nil, err - } - - return core.Core().V1().Secret(), nil -} From f84202ba9565ff234cebf355cb67f4ddb8982e4f Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Wed, 29 Jan 2025 12:53:49 -0300 Subject: [PATCH 09/10] fixing ci --- cmd/proxy/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 8b2c0b2..dc16b2f 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -17,7 +17,7 @@ func main() { restConfig, err := rest.InClusterConfig() if err != nil { - logrus.Errorf("failed to get in-cluster config: %w", err) + logrus.Errorf("failed to get in-cluster config: %s", err.Error()) return } From 0afa8978a8d316af9991177f5fe866a84f882447 Mon Sep 17 00:00:00 2001 From: "Felipe C. Gehrke" Date: Fri, 31 Jan 2025 14:33:15 -0300 Subject: [PATCH 10/10] addressed comments from @tomleb + other fixes --- Dockerfile.proxy | 5 +- cmd/proxy/proxy.yaml | 2 +- examples/proxyclient/main.go | 1 + forward/forward.go | 30 +++---- go.mod | 61 +++++++------- go.sum | 149 +++++++++++++++++------------------ proxy/config.go | 72 +++++++++++++---- proxy/server.go | 58 ++++++++++---- proxyclient/client.go | 113 +++++++++++++++++++------- 9 files changed, 305 insertions(+), 186 deletions(-) diff --git a/Dockerfile.proxy b/Dockerfile.proxy index f350d73..3166ebb 100644 --- a/Dockerfile.proxy +++ b/Dockerfile.proxy @@ -1,11 +1,10 @@ FROM golang:1.23 AS builder WORKDIR /app -COPY go.mod . -COPY go.sum . +COPY . . RUN go mod download -COPY . . + RUN CGO_ENABLED=0 go build -o proxy ./cmd/proxy FROM scratch diff --git a/cmd/proxy/proxy.yaml b/cmd/proxy/proxy.yaml index 925c9e5..d8330eb 100644 --- a/cmd/proxy/proxy.yaml +++ b/cmd/proxy/proxy.yaml @@ -75,7 +75,7 @@ metadata: rules: - apiGroups: [""] resources: ["secrets"] - verbs: ["get", "create"] + verbs: ["get", "create", "update"] --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/examples/proxyclient/main.go b/examples/proxyclient/main.go index 17a61e9..04ecd0a 100644 --- a/examples/proxyclient/main.go +++ b/examples/proxyclient/main.go @@ -166,6 +166,7 @@ func main() { } proxyClient, err := proxyclient.New( + ctx, connectSecret, namespace, certSecretName, diff --git a/forward/forward.go b/forward/forward.go index 152c3cd..ac6f68d 100644 --- a/forward/forward.go +++ b/forward/forward.go @@ -24,10 +24,9 @@ type PortForward struct { podClient v1.PodController namespace string labelSelector string - Ports []string + ports []string readyCh chan struct{} - stopCh chan struct{} cancel context.CancelFunc } @@ -59,15 +58,13 @@ func New(restConfig *rest.Config, podClient v1.PodController, namespace string, podClient: podClient, namespace: namespace, labelSelector: labelSelector, - Ports: ports, + ports: ports, readyCh: make(chan struct{}, 1), - stopCh: make(chan struct{}, 1), }, nil } func (r *PortForward) Stop() { r.cancel() - r.stopCh <- struct{}{} } func (r *PortForward) Start() error { @@ -85,7 +82,7 @@ func (r *PortForward) Start() error { logrus.Infoln("Goroutine stopped.") return default: - err := r.runForwarder(ctx, r.readyCh, r.stopCh, r.Ports) + err := r.runForwarder(ctx, r.readyCh, r.ports) if err != nil { if errors.Is(err, portforward.ErrLostConnectionToPod) { logrus.Errorf("Lost connection to pod (no automatic retry in this refactor): %v", err) @@ -100,17 +97,22 @@ func (r *PortForward) Start() error { } }() - // wait for the port forward to be ready if not failed - <-r.readyCh + // wait for the port forward to be ready if not failed or cancelled + for { + select { + case <-ctx.Done(): + return nil - if readyErr != nil { - return readyErr + case <-r.readyCh: + if readyErr != nil { + return readyErr + } + return nil + } } - - return nil } -func (r *PortForward) runForwarder(ctx context.Context, readyCh, stopCh chan struct{}, ports []string) error { +func (r *PortForward) runForwarder(ctx context.Context, readyCh chan struct{}, ports []string) error { podName, err := lookForPodName(ctx, r.namespace, r.labelSelector, r.podClient) if err != nil { return err @@ -134,7 +136,7 @@ func (r *PortForward) runForwarder(ctx context.Context, readyCh, stopCh chan str }, http.MethodPost, &serverURL) stdout, stderr := new(bytes.Buffer), new(bytes.Buffer) - forwarder, err := portforward.New(dialer, ports, stopCh, readyCh, stdout, stderr) + forwarder, err := portforward.New(dialer, ports, ctx.Done(), readyCh, stdout, stderr) if err != nil { return err } diff --git a/go.mod b/go.mod index 85da3f6..8376b97 100644 --- a/go.mod +++ b/go.mod @@ -5,68 +5,67 @@ go 1.23 require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 - github.com/kelseyhightower/envconfig v1.4.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_golang v1.19.1 github.com/rancher/dynamiclistener v0.6.0 - github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 - github.com/rancher/wrangler/v3 v3.0.0 + github.com/rancher/wrangler/v3 v3.0.1-rc.2 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 - k8s.io/apimachinery v0.30.1 - k8s.io/client-go v0.30.1 + k8s.io/api v0.31.1 + k8s.io/apimachinery v0.31.1 + k8s.io/client-go v0.31.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/swag v0.22.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/spdystream v0.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rancher/lasso v0.0.0-20240924233157-8f384efc8813 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.30.1 // indirect - k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index a8c57ee..2b8df16 100644 --- a/go.sum +++ b/go.sum @@ -2,32 +2,32 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -38,23 +38,20 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN 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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= -github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -66,10 +63,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -79,28 +74,29 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= -github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= -github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +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/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuviMQAHL1Q= github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s= -github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 h1:vv1jDlYbd4KhGbPNxmjs8CYgEHUrQm2bMtmULfXJ6iw= -github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1/go.mod h1:A/y3BLQkxZXYD60MNDRwAG9WGxXfvd6Z6gWR/a8wPw8= -github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA= -github.com/rancher/wrangler/v3 v3.0.0/go.mod h1:Dfckuuq7MJk2JWVBDywRlZXMxEyPxHy4XqGrPEzu5Eg= +github.com/rancher/lasso v0.0.0-20240924233157-8f384efc8813 h1:V/LY8pUHZG9Kc+xEDWDOryOnCU6/Q+Lsr9QQEQnshpU= +github.com/rancher/lasso v0.0.0-20240924233157-8f384efc8813/go.mod h1:IxgTBO55lziYhTEETyVKiT8/B5Rg92qYiRmcIIYoPgI= +github.com/rancher/wrangler/v3 v3.0.1-rc.2 h1:sHrZTPNco7SCNw372sv51DMK9a53ra/YboL4sQJjEQM= +github.com/rancher/wrangler/v3 v3.0.1-rc.2/go.mod h1:eXqcPIuGWblud9Wd1Auh7AWRHd6gs2H24asMMPuUR/s= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -117,25 +113,27 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -145,58 +143,57 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= -k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= -k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= -k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/proxy/config.go b/proxy/config.go index 7a2f386..03ec854 100644 --- a/proxy/config.go +++ b/proxy/config.go @@ -1,26 +1,70 @@ package proxy import ( - "github.com/kelseyhightower/envconfig" + "fmt" + "os" + "strconv" ) type Config struct { - Namespace string `default:"cattle-system"` - TLSName string `split_words:"true"` - CAName string `required:"true" split_words:"true"` - CertCANamespace string `required:"true" split_words:"true"` - CertCAName string `required:"true" split_words:"true"` - Secret string `required:"true" split_words:"true"` - ProxyPort int `required:"true" split_words:"true"` - PeerPort int `required:"true" split_words:"true"` - HTTPSPort int `required:"true" split_words:"true"` + TLSName string // certificate client name (SAN) + CAName string // certificate authority secret name + CertCANamespace string // certificate secret namespace + CertCAName string // certificate secret name + Secret string // remotedialer secret + ProxyPort int // tcp remotedialer-proxy port + PeerPort int // cluster-external service port + HTTPSPort int // https remotedialer-proxy port } -func ConfigFromEnvironment() (*Config, error) { - var c Config - err := envconfig.Process("", &c) +func requiredString(key string) (string, error) { + value := os.Getenv(key) + if value == "" { + return "", fmt.Errorf("%s cannot be empty", key) + } + return value, nil +} + +func requiredPort(key string) (int, error) { + valueStr := os.Getenv(key) + port, err := strconv.Atoi(valueStr) if err != nil { + return 0, fmt.Errorf("failed to read %s: %w", key, err) + } + if port <= 0 { + return 0, fmt.Errorf("%s should be greater than 0", key) + } + return port, nil +} + +func ConfigFromEnvironment() (*Config, error) { + var err error + var config Config + + if config.TLSName, err = requiredString("TLS_NAME"); err != nil { return nil, err } - return &c, nil + if config.CAName, err = requiredString("CA_NAME"); err != nil { + return nil, err + } + if config.CertCANamespace, err = requiredString("CERT_CA_NAMESPACE"); err != nil { + return nil, err + } + if config.CertCAName, err = requiredString("CERT_CA_NAME"); err != nil { + return nil, err + } + if config.Secret, err = requiredString("SECRET"); err != nil { + return nil, err + } + if config.ProxyPort, err = requiredPort("PROXY_PORT"); err != nil { + return nil, err + } + if config.PeerPort, err = requiredPort("PEER_PORT"); err != nil { + return nil, err + } + if config.HTTPSPort, err = requiredPort("HTTPS_PORT"); err != nil { + return nil, err + } + + return &config, nil } diff --git a/proxy/server.go b/proxy/server.go index 226e39d..6e3074c 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -7,6 +7,7 @@ import ( "math/rand" "net" "net/http" + "time" "github.com/gorilla/mux" "github.com/rancher/dynamiclistener" @@ -19,6 +20,11 @@ import ( "github.com/rancher/remotedialer" ) +const ( + listClientsRetryCount = 10 + listClientSleepTime = 1 * time.Second +) + func runProxyListener(ctx context.Context, cfg *Config, server *remotedialer.Server) error { l, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", cfg.ProxyPort)) //this RDP app starts only once and always running if err != nil { @@ -34,23 +40,33 @@ func runProxyListener(ctx context.Context, cfg *Config, server *remotedialer.Ser } go func() { - clients := server.ListClients() - if len(clients) == 0 { - logrus.Info("proxy TCP connection failed: no clients") - conn.Close() - return + var retryTimes = 0 + for { + clients := server.ListClients() + if len(clients) == 0 { + retryTimes++ + if retryTimes > listClientsRetryCount { + conn.Close() + return + } + + logrus.Info("proxy TCP connection failed: no clients, retrying in a sec") + time.Sleep(listClientSleepTime) + } else { + client := clients[rand.Intn(len(clients))] + peerAddr := fmt.Sprintf(":%d", cfg.PeerPort) // rancher's special https server for imperative API + clientConn, err := server.Dialer(client)(ctx, "tcp", peerAddr) + if err != nil { + logrus.Errorf("proxy dialing %s failed: %v", peerAddr, err) + conn.Close() + return + } + + go pipe(conn, clientConn) + go pipe(clientConn, conn) + break + } } - client := clients[rand.Intn(len(clients))] - peerAddr := fmt.Sprintf(":%d", cfg.PeerPort) // rancher's special https server for imperative API - clientConn, err := server.Dialer(client)(ctx, "tcp", peerAddr) - if err != nil { - logrus.Errorf("proxy dialing %s failed: %v", peerAddr, err) - conn.Close() - return - } - - go pipe(conn, clientConn) - go pipe(clientConn, conn) }() } } @@ -107,13 +123,17 @@ func Start(cfg *Config, restConfig *rest.Config) error { if err != nil { return fmt.Errorf("build secret controller failed w/ err: %w", err) } + + if err := core.Start(ctx, 1); err != nil { + return fmt.Errorf("secretController factory start failed: %w", err) + } + secretController := core.Core().V1().Secret() // Setting Up Remote Dialer HTTPS Server if err := server.ListenAndServe(ctx, cfg.HTTPSPort, 0, router, &server.ListenOpts{ Secrets: secretController, CAName: cfg.CAName, - CANamespace: cfg.Namespace, CertName: cfg.CertCAName, CertNamespace: cfg.CertCANamespace, TLSListenerConfig: dynamiclistener.Config{ @@ -121,6 +141,10 @@ func Start(cfg *Config, restConfig *rest.Config) error { FilterCN: func(cns ...string) []string { return []string{cfg.TLSName} }, + RegenerateCerts: func() bool { + return true + }, + ExpirationDaysCheck: 10, }, }); err != nil { return fmt.Errorf("extension server exited with an error: %w", err) diff --git a/proxyclient/client.go b/proxyclient/client.go index eeb843f..6b4e8fe 100644 --- a/proxyclient/client.go +++ b/proxyclient/client.go @@ -6,28 +6,27 @@ import ( "crypto/x509" "fmt" "net/http" + "sync" "time" "github.com/gorilla/websocket" "github.com/rancher/remotedialer" "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" v1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" ) const ( - defaultServerAddr = "wss://127.0.0.1" - defaultServerPort = 5555 - defaultServerPath = "/connect" -) - -var ( - nonTLSDialer = &websocket.Dialer{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - retryTimeout = 1 * time.Second + defaultServerAddr = "wss://127.0.0.1" + defaultServerPort = 5555 + defaultServerPath = "/connect" + retryTimeout = 1 * time.Second + certificateWatchInterval = 10 * time.Second ) type PortForwarder interface { @@ -41,13 +40,19 @@ type ProxyClient struct { forwarder PortForwarder serverUrl string serverConnectSecret string - dialer *websocket.Dialer - secretController v1.SecretController + + dialer *websocket.Dialer + dialerMtx sync.Mutex + + secretController v1.SecretController + namespace string + certSecretName string + certServerName string onConnect func(ctx context.Context, session *remotedialer.Session) error } -func New(serverSharedSecret, namespace, certSecretName, certServerName string, restConfig *rest.Config, forwarder PortForwarder, opts ...ProxyClientOpt) (*ProxyClient, error) { +func New(ctx context.Context, serverSharedSecret, namespace, certSecretName, certServerName string, restConfig *rest.Config, forwarder PortForwarder, opts ...ProxyClientOpt) (*ProxyClient, error) { if restConfig == nil { return nil, fmt.Errorf("restConfig required") } @@ -69,16 +74,18 @@ func New(serverSharedSecret, namespace, certSecretName, certServerName string, r } serverUrl := fmt.Sprintf("%s:%d%s", defaultServerAddr, defaultServerPort, defaultServerPath) - dialer, err := buildDialer(namespace, certSecretName, certServerName, restConfig) - if err != nil { - return nil, fmt.Errorf("couldn't build dialer: %w", err) - } client := &ProxyClient{ serverUrl: serverUrl, forwarder: forwarder, - dialer: dialer, serverConnectSecret: serverSharedSecret, + certSecretName: certSecretName, + certServerName: certServerName, + namespace: namespace, + } + + if err := client.buildDialer(ctx, restConfig); err != nil { + return nil, fmt.Errorf("dialer build failed %w: ", err) } for _, opt := range opts { @@ -88,19 +95,66 @@ func New(serverSharedSecret, namespace, certSecretName, certServerName string, r return client, nil } -func buildDialer(namespace, certSecretName, certServerName string, restConfig *rest.Config) (*websocket.Dialer, error) { +func (c *ProxyClient) buildDialer(ctx context.Context, restConfig *rest.Config) error { core, err := core.NewFactoryFromConfigWithOptions(restConfig, nil) if err != nil { - logrus.Error("build secret controller failed: %w, defaulting to non TLS connection", err) - return nonTLSDialer, err + return fmt.Errorf("build secret controller failed: %w", err) } secretController := core.Core().V1().Secret() - secret, err := secretController.Get(namespace, certSecretName, metav1.GetOptions{}) + secretController.Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(oldSecret, newSecret interface{}) { + updatedSecretCert, ok := newSecret.(*corev1.Secret) + if ok { + if updatedSecretCert.Name == c.certSecretName { + rootCAs, err := buildCertFromSecret(c.namespace, c.certSecretName, updatedSecretCert) + if err != nil { + logrus.Errorf("build certificate failed: %s", err.Error()) + return + } + + c.dialerMtx.Lock() + c.dialer = &websocket.Dialer{ + TLSClientConfig: &tls.Config{ + RootCAs: rootCAs, + ServerName: c.certServerName, + }, + } + c.dialerMtx.Unlock() + logrus.Infof("certificate updated successfully") + } + } + }, + }) + + if err := core.Start(ctx, 1); err != nil { + return fmt.Errorf("secret controller factory start failed: %w", err) + } + + secret, err := secretController.Get(c.namespace, c.certSecretName, metav1.GetOptions{}) if err != nil { - return nil, err + return err } + rootCAs, err := buildCertFromSecret(c.namespace, c.certSecretName, secret) + if err != nil { + return fmt.Errorf("build certificate failed: %w", err) + } + + c.dialerMtx.Lock() + c.dialer = &websocket.Dialer{ + TLSClientConfig: &tls.Config{ + RootCAs: rootCAs, + ServerName: c.certServerName, + }, + } + c.dialerMtx.Unlock() + + return nil +} + +func buildCertFromSecret(namespace, certSecretName string, secret *corev1.Secret) (*x509.CertPool, error) { crtData, exists := secret.Data["tls.crt"] if !exists { return nil, fmt.Errorf("secret %s/%s missing tls.crt field", namespace, certSecretName) @@ -111,12 +165,7 @@ func buildDialer(namespace, certSecretName, certServerName string, restConfig *r return nil, fmt.Errorf("failed to parse tls.crt from secret into a CA pool") } - return &websocket.Dialer{ - TLSClientConfig: &tls.Config{ - RootCAs: rootCAs, - ServerName: certServerName, - }, - }, nil + return rootCAs, nil } func (c *ProxyClient) Run(ctx context.Context) { @@ -148,7 +197,11 @@ func (c *ProxyClient) Run(ctx context.Context) { return nil } - if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, c.dialer, onConnectAuth, onConnect); err != nil { + c.dialerMtx.Lock() + dialer := c.dialer + c.dialerMtx.Unlock() + + if err := remotedialer.ClientConnect(ctx, c.serverUrl, headers, dialer, onConnectAuth, onConnect); err != nil { logrus.Errorf("remotedialer.ClientConnect error: %s", err.Error()) c.forwarder.Stop() time.Sleep(retryTimeout)