├── demo ├── demo ├── README.md ├── Dockerfile ├── deployment.yaml └── main.go ├── controller ├── deploy │ ├── namespace.yaml │ ├── serviceaccount.yaml │ ├── service.yaml │ ├── clusterrolebinding.yaml │ ├── kustomization.yaml │ ├── clusterrole.yaml │ └── deployment.yaml ├── .dockerignore ├── .gitignore ├── prestop.sh ├── Dockerfile ├── go.mod ├── cmd │ ├── sidecars.go │ ├── main.go │ ├── webhookconfig.go │ ├── watcher.go │ └── webhook.go ├── Makefile └── go.sum ├── .gitignore ├── sidecar └── main.go ├── Dockerfile-sidecar ├── watcher ├── Dockerfile ├── deployment.yaml ├── go.mod ├── go.sum └── main.go ├── dtunnel ├── README.md └── main.go ├── manifests ├── demo.yaml ├── for_testing │ ├── deployment_sidecar.yaml │ ├── deployment_sidecar_secret.yaml │ └── deployment.yaml └── controller.yaml ├── Makefile ├── go.mod ├── ephemeral └── main.go ├── pkg ├── connection │ ├── utils.go │ └── connection.go └── manager │ └── manager.go ├── ebpf ├── mirrors.h └── mirrors.c ├── README.md └── go.sum /demo/demo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thebsdbox/smesh/HEAD/demo/demo -------------------------------------------------------------------------------- /controller/deploy/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: sidecar-injector 5 | 6 | -------------------------------------------------------------------------------- /controller/deploy/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: sidecar-injector 5 | labels: 6 | app: sidecar-injector 7 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Quick demo of unencrypted TCP 2 | 3 | ## Quick deployment 4 | kubectl delete -f ./deploy.yaml; docker build -t demo/demo:v1 .; kind load docker-image demo/demo:v1; 5 | 6 | kubectl apply -f ./deploy.yaml 7 | -------------------------------------------------------------------------------- /controller/deploy/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: sidecar-injector 5 | labels: 6 | app: sidecar-injector 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 8443 11 | selector: 12 | app: sidecar-injector 13 | -------------------------------------------------------------------------------- /controller/.dockerignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | vendor/* 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | certificates/ca.crt 2 | certificates/ca.key 3 | certificates/pod-01.crt 4 | certificates/pod-01.key 5 | certificates/pod-02.crt 6 | certificates/pod-02.key 7 | mirrors_bpfeb.go 8 | mirrors_bpfeb.o 9 | mirrors_bpfel.go 10 | mirrors_bpfel.o 11 | sidecar/sidecar 12 | dtunnel/dtunnel 13 | -------------------------------------------------------------------------------- /controller/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | vendor/* 14 | 15 | build/_output 16 | 17 | # GOPATH 18 | .go -------------------------------------------------------------------------------- /controller/deploy/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: sidecar-injector 5 | labels: 6 | app: sidecar-injector 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: ClusterRole 10 | name: sidecar-injector 11 | subjects: 12 | - kind: ServiceAccount 13 | name: sidecar-injector 14 | namespace: sidecar-injector 15 | -------------------------------------------------------------------------------- /controller/deploy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: sidecar-injector 2 | 3 | resources: 4 | - namespace.yaml 5 | - clusterrole.yaml 6 | - clusterrolebinding.yaml 7 | - deployment.yaml 8 | - service.yaml 9 | - serviceaccount.yaml 10 | 11 | images: 12 | - name: sidecar-injector 13 | newName: thebsdbox/smesh-sidecar 14 | newTag: v1 15 | apiVersion: kustomize.config.k8s.io/v1beta1 16 | kind: Kustomization 17 | -------------------------------------------------------------------------------- /sidecar/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "smesh/pkg/manager" 5 | 6 | "github.com/gookit/slog" 7 | ) 8 | 9 | func main() { 10 | 11 | slog.Info("starting the SMESH 🐝") 12 | c, err := manager.Setup() 13 | if err != nil { 14 | slog.Fatal(err) 15 | } 16 | err = manager.LoadEPF(c) 17 | if err != nil { 18 | slog.Fatal(err) 19 | } 20 | err = manager.Start(c) 21 | if err != nil { 22 | slog.Fatal(err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | 3 | FROM golang:1.23-alpine as dev 4 | RUN apk add --no-cache git ca-certificates 5 | RUN adduser -D appuser 6 | COPY main.go /src/ 7 | WORKDIR /src 8 | 9 | ENV GO111MODULE=on 10 | RUN --mount=type=cache,sharing=locked,id=gomod,target=/go/pkg/mod/cache \ 11 | --mount=type=cache,sharing=locked,id=goroot,target=/root/.cache/go-build \ 12 | CGO_ENABLED=0 GOOS=linux go build -ldflags '-s -w -extldflags -static' -o demo /src/main.go 13 | 14 | FROM scratch 15 | COPY --from=dev /src/demo / 16 | CMD ["/demo"] -------------------------------------------------------------------------------- /controller/deploy/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRole 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: sidecar-injector 5 | labels: 6 | app: sidecar-injector 7 | rules: 8 | - apiGroups: ["admissionregistration.k8s.io"] 9 | resources: ["mutatingwebhookconfigurations"] 10 | verbs: ["create", "get", "delete", "list", "patch", "update", "watch"] 11 | - apiGroups: [""] # "" indicates the core API group 12 | resources: ["pods"] 13 | verbs: ["get", "watch", "list"] 14 | - apiGroups: [""] # "" indicates the core API group 15 | resources: ["secrets"] 16 | verbs: ["create", "delete"] 17 | -------------------------------------------------------------------------------- /Dockerfile-sidecar: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | 3 | FROM golang:1.23-alpine as dev 4 | RUN apk add --no-cache git ca-certificates upx 5 | RUN adduser -D appuser 6 | COPY . /src/ 7 | WORKDIR /src/sidecar 8 | 9 | ENV GO111MODULE=on 10 | RUN --mount=type=cache,sharing=locked,id=gomod,target=/go/pkg/mod/cache \ 11 | --mount=type=cache,sharing=locked,id=goroot,target=/root/.cache/go-build \ 12 | CGO_ENABLED=0 GOOS=linux go build -ldflags '-s -w -extldflags -static' -o kube-gateway . 13 | RUN upx ./kube-gateway 14 | #FROM debian 15 | FROM scratch 16 | COPY --from=dev /src/sidecar/kube-gateway / 17 | #RUN apt-get update; apt-get install -y net-tools nftables 18 | CMD ["/kube-gateway"] -------------------------------------------------------------------------------- /watcher/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | 3 | FROM golang:1.23-alpine as dev 4 | RUN apk add --no-cache git ca-certificates upx 5 | RUN adduser -D appuser 6 | COPY . /src/ 7 | WORKDIR /src 8 | 9 | ENV GO111MODULE=on 10 | RUN --mount=type=cache,sharing=locked,id=gomod,target=/go/pkg/mod/cache \ 11 | --mount=type=cache,sharing=locked,id=goroot,target=/root/.cache/go-build \ 12 | CGO_ENABLED=0 GOOS=linux go build -ldflags '-s -w -extldflags -static' -o kube-cert-watcher . 13 | RUN upx ./kube-cert-watcher 14 | #FROM debian 15 | FROM scratch 16 | COPY --from=dev /src/kube-cert-watcher / 17 | #RUN apt-get update; apt-get install -y net-tools nftables 18 | CMD ["/kube-cert-watcher"] -------------------------------------------------------------------------------- /controller/prestop.sh: -------------------------------------------------------------------------------- 1 | set -exo pipefail 2 | 3 | # Point to the internal API server hostname 4 | APISERVER=https://kubernetes.default.svc 5 | 6 | # Path to ServiceAccount token 7 | SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount 8 | 9 | # Read this Pod's namespace 10 | NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace) 11 | 12 | # Read the ServiceAccount bearer token 13 | TOKEN=$(cat ${SERVICEACCOUNT}/token) 14 | 15 | # Reference the internal certificate authority (CA) 16 | CACERT=${SERVICEACCOUNT}/ca.crt 17 | 18 | MutatingWebhookConfigurationName=sidecar-injector-webhook 19 | 20 | # Delete the validatingwebhookconfiguration with TOKEN 21 | curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X DELETE ${APISERVER}/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/${MutatingWebhookConfigurationName} 22 | 23 | -------------------------------------------------------------------------------- /controller/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | 3 | FROM golang:1.23-alpine as dev 4 | RUN apk add --no-cache git ca-certificates upx 5 | 6 | COPY . /src/ 7 | WORKDIR /src 8 | 9 | ENV GO111MODULE=on 10 | RUN --mount=type=cache,sharing=locked,id=gomod,target=/go/pkg/mod/cache \ 11 | --mount=type=cache,sharing=locked,id=goroot,target=/root/.cache/go-build \ 12 | CGO_ENABLED=0 GOOS=linux go build -ldflags '-s -w -extldflags -static' -o sidecar-injector ./cmd/ 13 | RUN upx ./sidecar-injector 14 | 15 | 16 | FROM alpine:latest 17 | 18 | # install curl for prestop script 19 | RUN apk --no-cache add curl 20 | 21 | WORKDIR / 22 | 23 | # install binary 24 | COPY --from=dev /src/sidecar-injector . 25 | 26 | # install the prestop script 27 | COPY ./prestop.sh . 28 | 29 | USER 65532:65532 30 | 31 | ENTRYPOINT ["/sidecar-injector"] 32 | -------------------------------------------------------------------------------- /dtunnel/README.md: -------------------------------------------------------------------------------- 1 | # The DTunnel (Dan Tunnel) 2 | 3 | This is an attempt at moving away from Sidecars/iptables/nftables and having everything managed by a single instance per node, an original idea. 4 | 5 | The original design consisted of: 6 | - Proxy and eBPF (sidecar) 7 | - Controller and Watcher (single instance) 8 | 9 | The new design will consiste of: 10 | - Single daemonset, with eBPF 11 | 12 | ## Dtunnel architecture per node 13 | 14 | ### eBPF 15 | 16 | This will work in the same manner, and will watch for all `connect()` operations and if they're in the pod CIDR will redirect them on to the per-node proxy tunnels podIP. 17 | 18 | ### Proxy tunnel 19 | 20 | This will be listening on it's podIP (localhost wont work), where traffic will be directed to. We will then use the `SO_REDIRECT` to find the original destination, where we then will use the Kubernetes API to look up where this pod IP is living. We set the *new* destination to that host and the proxy IP. -------------------------------------------------------------------------------- /demo/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: pod-01 5 | labels: 6 | env: demo1 7 | spec: 8 | containers: 9 | - name: pod-01 10 | image: demo/demo:v1 11 | env: 12 | - name: CLIENT 13 | value: "service2" 14 | --- 15 | apiVersion: v1 16 | kind: Pod 17 | metadata: 18 | name: pod-02 19 | labels: 20 | env: demo2 21 | spec: 22 | containers: 23 | - name: pod-02 24 | image: demo/demo:v1 25 | env: 26 | - name: CLIENT 27 | value: "service1" 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: service1 33 | spec: 34 | clusterIP: None # <= Don't forget!! 35 | selector: 36 | env: demo1 37 | ports: 38 | - protocol: TCP 39 | port: 80 40 | targetPort: 8080 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | name: service2 46 | spec: 47 | clusterIP: None # <= Don't forget!! 48 | selector: 49 | env: demo2 50 | ports: 51 | - protocol: TCP 52 | port: 80 53 | targetPort: 8080 54 | -------------------------------------------------------------------------------- /manifests/demo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: pod-01 5 | labels: 6 | env: demo1 7 | spec: 8 | containers: 9 | - name: pod-01 10 | image: thebsdbox/demo:v1 11 | env: 12 | - name: CLIENT 13 | value: "service2" 14 | --- 15 | apiVersion: v1 16 | kind: Pod 17 | metadata: 18 | name: pod-02 19 | labels: 20 | env: demo2 21 | spec: 22 | containers: 23 | - name: pod-02 24 | image: thebsdbox/demo:v1 25 | env: 26 | - name: CLIENT 27 | value: "service1" 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: service1 33 | spec: 34 | clusterIP: None # <= Don't forget!! 35 | selector: 36 | env: demo1 37 | ports: 38 | - protocol: TCP 39 | port: 80 40 | targetPort: 8080 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | name: service2 46 | spec: 47 | clusterIP: None # <= Don't forget!! 48 | selector: 49 | env: demo2 50 | ports: 51 | - protocol: TCP 52 | port: 80 53 | targetPort: 8080 54 | -------------------------------------------------------------------------------- /controller/deploy/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sidecar-injector 5 | labels: 6 | app: sidecar-injector 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: sidecar-injector 12 | template: 13 | metadata: 14 | labels: 15 | app: sidecar-injector 16 | spec: 17 | serviceAccountName: sidecar-injector 18 | containers: 19 | - name: sidecar-injector 20 | image: thebsdbox/smesh-controller:v1 21 | #imagePullPolicy: Always 22 | args: 23 | - -service-name=sidecar-injector 24 | env: 25 | - name: POD_NAMESPACE 26 | valueFrom: 27 | fieldRef: 28 | fieldPath: metadata.namespace 29 | lifecycle: 30 | preStop: 31 | exec: 32 | command: ["/bin/sh", "-c", "/prestop.sh"] 33 | #volumeMounts: 34 | # - name: webhook-config 35 | # mountPath: /etc/webhook/config 36 | resources: 37 | requests: 38 | memory: 64Mi 39 | cpu: 50m 40 | limits: 41 | memory: 100Mi 42 | cpu: 100m 43 | 44 | #volumes: 45 | # - name: webhook-config 46 | # configMap: 47 | # name: sidecar-injector 48 | -------------------------------------------------------------------------------- /watcher/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: watcher 5 | # namespace: kube-system 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | annotations: 11 | rbac.authorization.kubernetes.io/autoupdate: "true" 12 | name: system:watcher-role 13 | rules: 14 | - apiGroups: [""] # "" indicates the core API group 15 | resources: ["pods"] 16 | verbs: ["get", "watch", "list"] 17 | - apiGroups: [""] # "" indicates the core API group 18 | resources: ["secrets"] 19 | verbs: ["create", "delete"] 20 | --- 21 | kind: ClusterRoleBinding 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | metadata: 24 | name: system:watcher-binding 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: system:watcher-role 29 | subjects: 30 | - kind: ServiceAccount 31 | name: watcher 32 | namespace: default 33 | --- 34 | apiVersion: v1 35 | kind: Pod 36 | metadata: 37 | name: kube-cert-watcher 38 | spec: 39 | serviceAccountName: watcher 40 | containers: 41 | - name: kube-cert-watcher 42 | image: thebsdbox/watcher:v1 43 | imagePullPolicy: Always 44 | command: ["/kube-cert-watcher", "-watch"] 45 | volumeMounts: 46 | # name must match the volume name below 47 | - name: secret-volume 48 | mountPath: /certs 49 | readOnly: true 50 | # The secret data is exposed to Containers in the Pod through a Volume. 51 | env: 52 | - name: SMESH-CA-CERT 53 | valueFrom: 54 | secretKeyRef: 55 | name: watcher 56 | key: ca-cert 57 | - name: SMESH-CA-KEY 58 | valueFrom: 59 | secretKeyRef: 60 | name: watcher 61 | key: ca-key 62 | volumes: 63 | - name: secret-volume 64 | secret: 65 | secretName: watcher 66 | -------------------------------------------------------------------------------- /dtunnel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "smesh/pkg/manager" 7 | 8 | "github.com/gookit/slog" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/clientcmd" 13 | ) 14 | 15 | var kubeclient *kubernetes.Clientset 16 | 17 | func main() { 18 | 19 | slog.Info("starting the SMESH 🐝") 20 | c, err := manager.Setup() 21 | if err != nil { 22 | slog.Fatal(err) 23 | } 24 | err = manager.LoadEPF(c) 25 | if err != nil { 26 | slog.Fatal(err) 27 | } 28 | c.Proxy = true 29 | c.ProxyFunc = getHost 30 | err = manager.Start(c) 31 | if err != nil { 32 | slog.Fatal(err) 33 | } 34 | } 35 | 36 | func getHost(ip string) string { 37 | opts := metav1.ListOptions{} 38 | opts.FieldSelector = "status.podIP=" + ip 39 | l, _ := kubeclient.CoreV1().Pods(metav1.NamespaceAll).List(context.TODO(), opts) 40 | //fmt.Printf("Pods %d %s \n", len(l.Items)) 41 | //fmt.Print(l) 42 | //client.CoreV1().Endpoints(v1.NamespaceAll).Get() 43 | if len(l.Items) == 0 { 44 | return "" 45 | } 46 | if len(l.Items) > 1 { 47 | slog.Warnf("Found [%d] pods with the address [%s]", len(l.Items), ip) 48 | } 49 | // hmmm should really only ever be one 50 | return l.Items[0].Status.HostIP 51 | } 52 | 53 | func client(kubeconfigPath string) error { 54 | var kubeconfig *rest.Config 55 | var err error 56 | if kubeconfigPath != "" { 57 | config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) 58 | if err != nil { 59 | return fmt.Errorf("unable to load kubeconfig from %s: %v", kubeconfigPath, err) 60 | } 61 | kubeconfig = config 62 | } else { 63 | config, err := rest.InClusterConfig() 64 | if err != nil { 65 | return fmt.Errorf("unable to load in-cluster config: %v", err) 66 | } 67 | kubeconfig = config 68 | } 69 | 70 | // build the client set 71 | kubeclient, err = kubernetes.NewForConfig(kubeconfig) 72 | if err != nil { 73 | return fmt.Errorf("creating the kubernetes client set - %s", err) 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #vars 2 | VERSION=v1 3 | SMESHIMAGENAME=smesh-proxy 4 | REPO=thebsdbox 5 | SMESHIMAGEFULLNAME=${REPO}/${SMESHIMAGENAME}:${VERSION} 6 | DEMOIMAGEFULLNAME=${REPO}/demo:${VERSION} 7 | WATCHERIMAGEFULLNAME=${REPO}/watcher:${VERSION} 8 | CONTROLLERIMAGEFULLNAME=${REPO}/smesh-controller:${VERSION} 9 | 10 | .PHONY: help build push all 11 | 12 | help: 13 | @echo "Makefile arguments:" 14 | @echo "" 15 | @echo "alpver - Alpine Version" 16 | @echo "kctlver - kubectl version" 17 | @echo "" 18 | @echo "Makefile commands:" 19 | @echo "build" 20 | @echo "push" 21 | @echo "all" 22 | 23 | .DEFAULT_GOAL := all 24 | 25 | sidecar: build_proxy push_proxy 26 | 27 | build_sidecar: 28 | @go generate ./pkg/manager 29 | @docker build -t ${SMESHIMAGEFULLNAME} -f ./Dockerfile-sidecar . 30 | 31 | push_sidecar: 32 | @docker push ${SMESHIMAGEFULLNAME} 33 | 34 | kind_sidecar: 35 | @kind load docker-image thebsdbox/smesh-proxy:v1 36 | 37 | demo: build_demo push_demo 38 | 39 | build_demo: 40 | @docker build -t ${DEMOIMAGEFULLNAME} ./demo 41 | 42 | push_demo: 43 | @docker push ${DEMOIMAGEFULLNAME} 44 | 45 | kind_demo: 46 | @kind load docker-image thebsdbox/demo:v1 47 | 48 | watcher: build_watcher push_watcher 49 | 50 | build_watcher: 51 | @docker build -t ${WATCHERIMAGEFULLNAME} ./watcher 52 | 53 | push_watcher: 54 | @docker push ${WATCHERIMAGEFULLNAME} 55 | 56 | controller: build_controller push_controller 57 | 58 | build_controller: 59 | @docker build -t ${CONTROLLERIMAGEFULLNAME} ./controller 60 | 61 | push_controller: 62 | @docker push ${CONTROLLERIMAGEFULLNAME} 63 | 64 | kind_controller: 65 | @kind load docker-image thebsdbox/smesh-controller:v1 66 | 67 | controller_manifest: 68 | @kubectl kustomize controller/deploy/ > ./manifests/controller.yaml 69 | 70 | kind: 71 | @kubectl delete -f ./manifests/deployment_sidecar_secret.yaml; make build_proxy; kind load docker-image thebsdbox/smesh-proxy:v1; kubectl apply -f ./manifests/deployment_sidecar_secret.yaml 72 | 73 | all: build push 74 | -------------------------------------------------------------------------------- /controller/go.mod: -------------------------------------------------------------------------------- 1 | module sidecar 2 | 3 | go 1.23.3 4 | 5 | require ( 6 | github.com/gookit/slog v0.5.7 7 | k8s.io/api v0.31.3 8 | k8s.io/apimachinery v0.31.3 9 | k8s.io/client-go v0.31.3 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 14 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 15 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 16 | github.com/go-logr/logr v1.4.2 // indirect 17 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 18 | github.com/go-openapi/jsonreference v0.20.2 // indirect 19 | github.com/go-openapi/swag v0.22.4 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/golang/protobuf v1.5.4 // indirect 22 | github.com/google/gnostic-models v0.6.8 // indirect 23 | github.com/google/go-cmp v0.6.0 // indirect 24 | github.com/google/gofuzz v1.2.0 // indirect 25 | github.com/google/uuid v1.6.0 // indirect 26 | github.com/gookit/color v1.5.4 // indirect 27 | github.com/gookit/goutil v0.6.17 // indirect 28 | github.com/gookit/gsr v0.1.0 // indirect 29 | github.com/imdario/mergo v0.3.6 // indirect 30 | github.com/josharian/intern v1.0.0 // indirect 31 | github.com/json-iterator/go v1.1.12 // indirect 32 | github.com/mailru/easyjson v0.7.7 // indirect 33 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 34 | github.com/modern-go/reflect2 v1.0.2 // indirect 35 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 36 | github.com/spf13/pflag v1.0.5 // indirect 37 | github.com/valyala/bytebufferpool v1.0.0 // indirect 38 | github.com/x448/float16 v0.8.4 // indirect 39 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 40 | golang.org/x/net v0.26.0 // indirect 41 | golang.org/x/oauth2 v0.21.0 // indirect 42 | golang.org/x/sync v0.8.0 // indirect 43 | golang.org/x/sys v0.25.0 // indirect 44 | golang.org/x/term v0.24.0 // indirect 45 | golang.org/x/text v0.18.0 // indirect 46 | golang.org/x/time v0.3.0 // indirect 47 | google.golang.org/protobuf v1.34.2 // indirect 48 | gopkg.in/inf.v0 v0.9.1 // indirect 49 | gopkg.in/yaml.v2 v2.4.0 // indirect 50 | gopkg.in/yaml.v3 v3.0.1 // indirect 51 | k8s.io/klog/v2 v2.130.1 // indirect 52 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 53 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 54 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 55 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 56 | sigs.k8s.io/yaml v1.4.0 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /watcher/go.mod: -------------------------------------------------------------------------------- 1 | module watcher 2 | 3 | go 1.23.3 4 | 5 | require ( 6 | github.com/gookit/slog v0.5.7 7 | k8s.io/api v0.31.3 8 | k8s.io/apimachinery v0.31.3 9 | k8s.io/client-go v0.31.3 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 14 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 15 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 16 | github.com/go-logr/logr v1.4.2 // indirect 17 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 18 | github.com/go-openapi/jsonreference v0.20.2 // indirect 19 | github.com/go-openapi/swag v0.22.4 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/golang/protobuf v1.5.4 // indirect 22 | github.com/google/gnostic-models v0.6.8 // indirect 23 | github.com/google/go-cmp v0.6.0 // indirect 24 | github.com/google/gofuzz v1.2.0 // indirect 25 | github.com/google/uuid v1.6.0 // indirect 26 | github.com/gookit/color v1.5.4 // indirect 27 | github.com/gookit/goutil v0.6.17 // indirect 28 | github.com/gookit/gsr v0.1.0 // indirect 29 | github.com/imdario/mergo v0.3.6 // indirect 30 | github.com/josharian/intern v1.0.0 // indirect 31 | github.com/json-iterator/go v1.1.12 // indirect 32 | github.com/mailru/easyjson v0.7.7 // indirect 33 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 34 | github.com/modern-go/reflect2 v1.0.2 // indirect 35 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 36 | github.com/spf13/pflag v1.0.5 // indirect 37 | github.com/valyala/bytebufferpool v1.0.0 // indirect 38 | github.com/x448/float16 v0.8.4 // indirect 39 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 40 | golang.org/x/net v0.26.0 // indirect 41 | golang.org/x/oauth2 v0.21.0 // indirect 42 | golang.org/x/sync v0.8.0 // indirect 43 | golang.org/x/sys v0.25.0 // indirect 44 | golang.org/x/term v0.24.0 // indirect 45 | golang.org/x/text v0.18.0 // indirect 46 | golang.org/x/time v0.3.0 // indirect 47 | google.golang.org/protobuf v1.34.2 // indirect 48 | gopkg.in/inf.v0 v0.9.1 // indirect 49 | gopkg.in/yaml.v2 v2.4.0 // indirect 50 | gopkg.in/yaml.v3 v3.0.1 // indirect 51 | k8s.io/klog/v2 v2.130.1 // indirect 52 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 53 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 54 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 55 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 56 | sigs.k8s.io/yaml v1.4.0 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /manifests/for_testing/deployment_sidecar.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: pod-01 5 | labels: 6 | env: demo1 7 | spec: 8 | shareProcessNamespace: true 9 | containers: 10 | - name: pod-01 11 | image: demo/demo:v1 12 | env: 13 | - name: CLIENT 14 | value: "service2" 15 | - name: mtls 16 | image: kube-gateway/kube-gateway:v1 17 | securityContext: 18 | privileged: true 19 | #capabilities: 20 | # add: ["IPC_LOCK", "SYS_ADMIN", "SYS_RESOURCE", "BPF"] 21 | volumeMounts: 22 | - mountPath: /sys/kernel/debug 23 | name: sys-kernel-debug 24 | # - name: cgroup 25 | # mountPath: /hostfs/sys/fs/cgroup 26 | volumes: 27 | - name: sys-kernel-debug 28 | hostPath: 29 | path: /sys/kernel/debug 30 | type: DirectoryOrCreate 31 | - name: cgroup 32 | hostPath: 33 | path: /sys/fs/cgroup 34 | --- 35 | apiVersion: v1 36 | kind: Pod 37 | metadata: 38 | name: pod-02 39 | labels: 40 | env: demo2 41 | spec: 42 | shareProcessNamespace: true 43 | containers: 44 | - name: pod-02 45 | image: demo/demo:v1 46 | env: 47 | - name: CLIENT 48 | value: "service1" 49 | - name: mtls 50 | image: kube-gateway/kube-gateway:v1 51 | securityContext: 52 | privileged: 53 | true 54 | #capabilities: 55 | #add: ["IPC_LOCK", "SYS_ADMIN", "SYS_RESOURCE", "BPF"] 56 | env: 57 | - name: MY_NODE_NAME 58 | valueFrom: 59 | fieldRef: 60 | fieldPath: spec.nodeName 61 | volumeMounts: 62 | - mountPath: /sys/kernel/debug 63 | name: sys-kernel-debug 64 | # - name: cgroup 65 | # mountPath: /hostfs/sys/fs/cgroup 66 | volumes: 67 | - name: sys-kernel-debug 68 | hostPath: 69 | path: /sys/kernel/debug 70 | type: DirectoryOrCreate 71 | - name: cgroup 72 | hostPath: 73 | path: /sys/fs/cgroup 74 | --- 75 | apiVersion: v1 76 | kind: Service 77 | metadata: 78 | name: service1 79 | spec: 80 | clusterIP: None # <= Don't forget!! 81 | selector: 82 | env: demo1 83 | ports: 84 | - protocol: TCP 85 | port: 80 86 | targetPort: 8080 87 | --- 88 | apiVersion: v1 89 | kind: Service 90 | metadata: 91 | name: service2 92 | spec: 93 | clusterIP: None # <= Don't forget!! 94 | selector: 95 | env: demo2 96 | ports: 97 | - protocol: TCP 98 | port: 80 99 | targetPort: 8080 100 | -------------------------------------------------------------------------------- /demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func helloHandler(writer http.ResponseWriter, r *http.Request) { 12 | fmt.Printf("GET from %s\n", r.RemoteAddr) 13 | r.Body.Close() 14 | //writer.Write([]byte("OK")) 15 | } 16 | 17 | func main() { 18 | client := os.Getenv("CLIENT") 19 | if client == "" { 20 | panic("No Client address to connect to") 21 | } 22 | hostname, err := os.Hostname() 23 | if err != nil { 24 | panic(err) 25 | } 26 | fmt.Printf("I am %s\n", hostname) 27 | 28 | go connector(client, hostname) 29 | 30 | listen, err := net.Listen("tcp", ":9000") 31 | if err != nil { 32 | panic(err) 33 | } 34 | go curler(client) 35 | 36 | defer listen.Close() 37 | http.HandleFunc("/hello", helloHandler) 38 | 39 | // Set the HTTP listener in a seperate go function as it is blocking 40 | go func() { 41 | err = http.ListenAndServe(":9080", nil) 42 | if err != nil { 43 | panic(err) 44 | } 45 | }() 46 | for { 47 | conn, err := listen.Accept() 48 | if err != nil { 49 | fmt.Println(err) 50 | continue 51 | } 52 | for { 53 | buffer := make([]byte, 256) 54 | _, err = conn.Read(buffer) 55 | if err != nil { 56 | fmt.Printf("[TCP session error] %v", err) 57 | conn.Close() 58 | break 59 | } 60 | fmt.Printf("%s from %s\n", buffer, conn.RemoteAddr().String()) 61 | } 62 | } 63 | } 64 | 65 | func connector(client, hostname string) { 66 | address := fmt.Sprintf("%s:9000", client) 67 | fmt.Printf("[TCP session] connecting to %s\n", client) 68 | for { 69 | conn, err := net.DialTimeout("tcp", address, time.Second*3) 70 | if err != nil { 71 | fmt.Printf("unable to connect to %s, will try in 5 seconds, %v\n", client, err) 72 | time.Sleep(time.Second * 5) 73 | continue 74 | } else { 75 | for { 76 | _, err = conn.Write([]byte(fmt.Sprintf("Hello from %s", hostname))) 77 | if err != nil { 78 | conn.Close() 79 | break 80 | } 81 | time.Sleep(time.Second * 5) 82 | } 83 | } 84 | 85 | } 86 | } 87 | 88 | func curler(client string) { 89 | 90 | address := fmt.Sprintf("http://%s:9080/hello", client) 91 | fmt.Printf("[HTTP] connecting to %s\n", client) 92 | for { 93 | // New client for each connection 94 | c := http.Client{} 95 | c.CloseIdleConnections() 96 | r, err := c.Get(address) 97 | if err != nil { 98 | fmt.Printf("unable to connect to %s, will try in 5 seconds, %v\n", client, err) 99 | time.Sleep(time.Second * 5) 100 | continue 101 | } 102 | r.Body.Close() 103 | time.Sleep(time.Second * 5) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /manifests/controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: sidecar-injector 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | labels: 10 | app: sidecar-injector 11 | name: sidecar-injector 12 | namespace: sidecar-injector 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: ClusterRole 16 | metadata: 17 | labels: 18 | app: sidecar-injector 19 | name: sidecar-injector 20 | rules: 21 | - apiGroups: 22 | - admissionregistration.k8s.io 23 | resources: 24 | - mutatingwebhookconfigurations 25 | verbs: 26 | - create 27 | - get 28 | - delete 29 | - list 30 | - patch 31 | - update 32 | - watch 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - pods 37 | verbs: 38 | - get 39 | - watch 40 | - list 41 | - apiGroups: 42 | - "" 43 | resources: 44 | - secrets 45 | verbs: 46 | - create 47 | - delete 48 | --- 49 | apiVersion: rbac.authorization.k8s.io/v1 50 | kind: ClusterRoleBinding 51 | metadata: 52 | labels: 53 | app: sidecar-injector 54 | name: sidecar-injector 55 | roleRef: 56 | apiGroup: rbac.authorization.k8s.io 57 | kind: ClusterRole 58 | name: sidecar-injector 59 | subjects: 60 | - kind: ServiceAccount 61 | name: sidecar-injector 62 | namespace: sidecar-injector 63 | --- 64 | apiVersion: v1 65 | kind: Service 66 | metadata: 67 | labels: 68 | app: sidecar-injector 69 | name: sidecar-injector 70 | namespace: sidecar-injector 71 | spec: 72 | ports: 73 | - port: 443 74 | targetPort: 8443 75 | selector: 76 | app: sidecar-injector 77 | --- 78 | apiVersion: apps/v1 79 | kind: Deployment 80 | metadata: 81 | labels: 82 | app: sidecar-injector 83 | name: sidecar-injector 84 | namespace: sidecar-injector 85 | spec: 86 | replicas: 1 87 | selector: 88 | matchLabels: 89 | app: sidecar-injector 90 | template: 91 | metadata: 92 | labels: 93 | app: sidecar-injector 94 | spec: 95 | containers: 96 | - args: 97 | - -service-name=sidecar-injector 98 | env: 99 | - name: POD_NAMESPACE 100 | valueFrom: 101 | fieldRef: 102 | fieldPath: metadata.namespace 103 | image: thebsdbox/smesh-controller:v1 104 | lifecycle: 105 | preStop: 106 | exec: 107 | command: 108 | - /bin/sh 109 | - -c 110 | - /prestop.sh 111 | name: sidecar-injector 112 | resources: 113 | limits: 114 | cpu: 100m 115 | memory: 100Mi 116 | requests: 117 | cpu: 50m 118 | memory: 64Mi 119 | serviceAccountName: sidecar-injector 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module smesh 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/cilium/ebpf v0.15.0 7 | github.com/docker/docker v27.3.1+incompatible 8 | github.com/gookit/slog v0.5.7 9 | gopkg.in/yaml.v2 v2.4.0 10 | k8s.io/api v0.31.3 11 | k8s.io/apimachinery v0.31.3 12 | k8s.io/client-go v0.31.3 13 | ) 14 | 15 | require ( 16 | github.com/containerd/log v0.1.0 // indirect 17 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 18 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 19 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 20 | github.com/go-logr/logr v1.4.2 // indirect 21 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 22 | github.com/go-openapi/jsonreference v0.20.2 // indirect 23 | github.com/go-openapi/swag v0.22.4 // indirect 24 | github.com/gogo/protobuf v1.3.2 // indirect 25 | github.com/golang/protobuf v1.5.4 // indirect 26 | github.com/google/gnostic-models v0.6.8 // indirect 27 | github.com/google/go-cmp v0.6.0 // indirect 28 | github.com/google/gofuzz v1.2.0 // indirect 29 | github.com/google/uuid v1.6.0 // indirect 30 | github.com/gookit/color v1.5.4 // indirect 31 | github.com/gookit/goutil v0.6.17 // indirect 32 | github.com/gookit/gsr v0.1.0 // indirect 33 | github.com/imdario/mergo v0.3.6 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/mailru/easyjson v0.7.7 // indirect 37 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 38 | github.com/modern-go/reflect2 v1.0.2 // indirect 39 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 40 | github.com/sirupsen/logrus v1.9.3 // indirect 41 | github.com/spf13/pflag v1.0.5 // indirect 42 | github.com/valyala/bytebufferpool v1.0.0 // indirect 43 | github.com/x448/float16 v0.8.4 // indirect 44 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 45 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect 46 | golang.org/x/net v0.26.0 // indirect 47 | golang.org/x/oauth2 v0.21.0 // indirect 48 | golang.org/x/sync v0.8.0 // indirect 49 | golang.org/x/sys v0.25.0 // indirect 50 | golang.org/x/term v0.24.0 // indirect 51 | golang.org/x/text v0.18.0 // indirect 52 | golang.org/x/time v0.3.0 // indirect 53 | google.golang.org/protobuf v1.34.2 // indirect 54 | gopkg.in/inf.v0 v0.9.1 // indirect 55 | gopkg.in/yaml.v3 v3.0.1 // indirect 56 | gotest.tools/v3 v3.5.1 // indirect 57 | k8s.io/klog/v2 v2.130.1 // indirect 58 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 59 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 60 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 61 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 62 | sigs.k8s.io/yaml v1.4.0 // indirect 63 | ) 64 | -------------------------------------------------------------------------------- /controller/cmd/sidecars.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | func smeshproxy(podname string) *corev1.Container { 8 | privileged := true 9 | secret := podname + "-smesh" 10 | policy := corev1.ContainerRestartPolicyAlways 11 | c := &corev1.Container{ 12 | Name: "smesh-proxy", 13 | Image: "thebsdbox/smesh-proxy:v1", 14 | SecurityContext: &corev1.SecurityContext{ 15 | Privileged: &privileged, // TODO: Fix permissions 16 | }, 17 | RestartPolicy: &policy, 18 | Env: []corev1.EnvVar{ 19 | { 20 | Name: "SMESH-CA", 21 | ValueFrom: &corev1.EnvVarSource{ 22 | SecretKeyRef: &corev1.SecretKeySelector{ 23 | LocalObjectReference: corev1.LocalObjectReference{ 24 | Name: secret, 25 | }, 26 | Key: "ca", 27 | }, 28 | }, 29 | }, 30 | { 31 | Name: "SMESH-CERT", 32 | ValueFrom: &corev1.EnvVarSource{ 33 | SecretKeyRef: &corev1.SecretKeySelector{ 34 | LocalObjectReference: corev1.LocalObjectReference{ 35 | Name: secret, 36 | }, 37 | Key: "cert", 38 | }, 39 | }, 40 | }, 41 | { 42 | Name: "SMESH-KEY", 43 | ValueFrom: &corev1.EnvVarSource{ 44 | SecretKeyRef: &corev1.SecretKeySelector{ 45 | LocalObjectReference: corev1.LocalObjectReference{ 46 | Name: secret, 47 | }, 48 | Key: "key", 49 | }, 50 | }, 51 | }, 52 | }, 53 | } 54 | return c 55 | } 56 | 57 | // func withDebugContainer(pod *corev1.Pod) *corev1.Pod { 58 | // privileged := true 59 | // secret := pod.Name + "-smesh" 60 | // policy := corev1.ContainerRestartPolicyAlways 61 | 62 | // ec := &corev1.EphemeralContainer{ 63 | // EphemeralContainerCommon: corev1.EphemeralContainerCommon{ 64 | // Name: "smesh-proxy", 65 | // Image: "thebsdbox/smesh-proxy:v1", 66 | // SecurityContext: &corev1.SecurityContext{ 67 | // Privileged: &privileged, // TODO: Fix permissions 68 | // }, 69 | // RestartPolicy: &policy, 70 | // Env: []corev1.EnvVar{ 71 | // { 72 | // Name: "SMESH-CA", 73 | // ValueFrom: &corev1.EnvVarSource{ 74 | // SecretKeyRef: &corev1.SecretKeySelector{ 75 | // LocalObjectReference: corev1.LocalObjectReference{ 76 | // Name: secret, 77 | // }, 78 | // Key: "ca", 79 | // }, 80 | // }, 81 | // }, 82 | // { 83 | // Name: "SMESH-CERT", 84 | // ValueFrom: &corev1.EnvVarSource{ 85 | // SecretKeyRef: &corev1.SecretKeySelector{ 86 | // LocalObjectReference: corev1.LocalObjectReference{ 87 | // Name: secret, 88 | // }, 89 | // Key: "cert", 90 | // }, 91 | // }, 92 | // }, 93 | // { 94 | // Name: "SMESH-KEY", 95 | // ValueFrom: &corev1.EnvVarSource{ 96 | // SecretKeyRef: &corev1.SecretKeySelector{ 97 | // LocalObjectReference: corev1.LocalObjectReference{ 98 | // Name: secret, 99 | // }, 100 | // Key: "key", 101 | // }, 102 | // }, 103 | // }, 104 | // }, 105 | // }, 106 | // TargetContainerName: "app", 107 | // } 108 | 109 | // copied := pod.DeepCopy() 110 | // copied.Spec.EphemeralContainers = append(copied.Spec.EphemeralContainers, *ec) 111 | 112 | // return copied 113 | // } 114 | -------------------------------------------------------------------------------- /ephemeral/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "path" 10 | 11 | corev1 "k8s.io/api/core/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/types" 14 | "k8s.io/apimachinery/pkg/util/strategicpatch" 15 | "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/tools/clientcmd" 17 | ) 18 | 19 | var ( 20 | ctx = context.Background() 21 | namespace = "default" 22 | ) 23 | 24 | func main() { 25 | var podName string 26 | flag.StringVar(&podName, "pod", "", "Pod to attach sidecar too") 27 | flag.Parse() 28 | // 0. Initialize the Kubernetes client. 29 | home, err := os.UserHomeDir() 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | config, err := clientcmd.BuildConfigFromFlags("", path.Join(home, ".kube/config")) 35 | if err != nil { 36 | panic(err.Error()) 37 | } 38 | 39 | client, err := kubernetes.NewForConfig(config) 40 | if err != nil { 41 | panic(err.Error()) 42 | } 43 | 44 | fmt.Printf("Looking for pod [%s]\n", podName) 45 | pod, err := client.CoreV1().Pods(corev1.NamespaceDefault).Get(ctx, podName, metav1.GetOptions{}) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | // 2. Add an ephemeral container to the pod spec. 51 | podWithEphemeralContainer := withProxyContainer(pod) 52 | 53 | // 3. Prepare the patch. 54 | podJSON, err := json.Marshal(pod) 55 | if err != nil { 56 | panic(err.Error()) 57 | } 58 | 59 | podWithEphemeralContainerJSON, err := json.Marshal(podWithEphemeralContainer) 60 | if err != nil { 61 | panic(err.Error()) 62 | } 63 | 64 | patch, err := strategicpatch.CreateTwoWayMergePatch(podJSON, podWithEphemeralContainerJSON, pod) 65 | if err != nil { 66 | panic(err.Error()) 67 | } 68 | 69 | // 4. Apply the patch. 70 | pod, err = client.CoreV1(). 71 | Pods(pod.Namespace). 72 | Patch( 73 | ctx, 74 | pod.Name, 75 | types.StrategicMergePatchType, 76 | patch, 77 | metav1.PatchOptions{}, 78 | "ephemeralcontainers", 79 | ) 80 | if err != nil { 81 | panic(err.Error()) 82 | } 83 | 84 | fmt.Printf("Pod has %d ephemeral containers.\n", len(pod.Spec.EphemeralContainers)) 85 | fmt.Printf("Pod has %d volumes.\n", len(pod.Spec.Volumes)) 86 | 87 | } 88 | 89 | func withProxyContainer(pod *corev1.Pod) *corev1.Pod { 90 | //privileged := true 91 | secret := pod.Name + "-smesh" 92 | //policy := corev1.ContainerRestartPolicyAlways 93 | 94 | // ec := &corev1.EphemeralContainer{ 95 | // EphemeralContainerCommon: corev1.EphemeralContainerCommon{ 96 | // Name: "smesh-proxy", 97 | // Image: "thebsdbox/smesh-proxy:v1", 98 | // SecurityContext: &corev1.SecurityContext{ 99 | // Privileged: &privileged, // TODO: Fix permissions 100 | // }, 101 | // //VolumeMounts: []corev1.VolumeMount{ 102 | // // { 103 | // // Name: "certs", 104 | // // ReadOnly: true, 105 | // // MountPath: "/tmp/", 106 | // // }, 107 | // //}, 108 | // }, 109 | // //TargetContainerName: "app", 110 | // } 111 | vol := corev1.SecretVolumeSource{SecretName: secret} 112 | copied := pod.DeepCopy() 113 | //copied.Spec.EphemeralContainers = append(copied.Spec.EphemeralContainers, *ec) 114 | copied.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{Name: "certs", VolumeSource: corev1.VolumeSource{Secret: &vol}}) 115 | 116 | return copied 117 | } 118 | -------------------------------------------------------------------------------- /controller/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "flag" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "os/user" 12 | "syscall" 13 | 14 | "github.com/gookit/slog" 15 | ) 16 | 17 | var ( 18 | port int 19 | webhookServiceName string 20 | ) 21 | var c certs 22 | 23 | func init() { 24 | c.namespace = os.Getenv("POD_NAMESPACE") 25 | } 26 | 27 | func main() { 28 | // init command flags 29 | var kubeconfig string 30 | u, err := user.Current() 31 | if err == nil { 32 | if u != nil { 33 | flag.StringVar(&kubeconfig, "kubeconfig", u.HomeDir+"/.kube/config", "Path to Kubernetes config") 34 | } 35 | } 36 | 37 | flag.IntVar(&port, "port", 8443, "Webhook server port.") 38 | flag.StringVar(&webhookServiceName, "service-name", "sidecar-injector", "Webhook service name.") 39 | flag.Parse() 40 | 41 | client, err := client(kubeconfig) 42 | if err != nil { 43 | slog.Fatalf("unable to get Kubernetes client [%v]", err) 44 | } 45 | 46 | dnsNames := []string{ 47 | webhookServiceName, 48 | webhookServiceName + "." + c.namespace, 49 | webhookServiceName + "." + c.namespace + ".svc", 50 | } 51 | commonName := webhookServiceName + "." + c.namespace + ".svc" 52 | 53 | 54 | 55 | c.org = "thebsdbox.co.uk" 56 | err = c.getEnvCerts() 57 | if err != nil { 58 | slog.Errorf("Error loading existing certificates, generating new ones [%v]", err) 59 | err = c.generateCA() 60 | if err != nil { 61 | slog.Fatalf("generating CA [%v]", err) 62 | } 63 | err = c.loadCA(client) 64 | if err != nil { 65 | slog.Fatalf("creating secrets for CA [%v]", err) 66 | } 67 | } 68 | 69 | // Create our client certificates to authenticate with the API Server 70 | c.createCertificate(commonName, dnsNames, nil) 71 | 72 | if err != nil { 73 | slog.Fatalf("Failed to generate ca and certificate key pair: %v", err) 74 | } 75 | 76 | pair, err := tls.X509KeyPair(c.cert, c.key) 77 | if err != nil { 78 | slog.Fatalf("Failed to load certificate key pair: %v", err) 79 | } 80 | 81 | // create or update the mutatingwebhookconfiguration 82 | err = createOrUpdateMutatingWebhookConfiguration(c.cacert, webhookServiceName, c.namespace, client) 83 | if err != nil { 84 | slog.Fatalf("Failed to create or update the mutating webhook configuration: %v", err) 85 | } 86 | 87 | whsvr := &WebhookServer{ 88 | server: &http.Server{ 89 | Addr: fmt.Sprintf(":%v", port), 90 | TLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}}, 91 | }, 92 | } 93 | 94 | go c.watcher(client) 95 | 96 | // define http server and server handler 97 | mux := http.NewServeMux() 98 | mux.HandleFunc(webhookInjectPath, whsvr.serve) 99 | whsvr.server.Handler = mux 100 | 101 | // start webhook server in new rountine 102 | go func() { 103 | if err := whsvr.server.ListenAndServeTLS("", ""); err != nil { 104 | slog.Fatalf("Failed to listen and serve webhook server: %v", err) 105 | } 106 | }() 107 | 108 | // listening OS shutdown singal 109 | signalChan := make(chan os.Signal, 1) 110 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 111 | <-signalChan 112 | 113 | slog.Printf("Got OS shutdown signal, shutting down webhook server gracefully...") 114 | whsvr.server.Shutdown(context.Background()) 115 | err = tidyWebhook(webhookConfigName, client) 116 | if err != nil { 117 | slog.Errorf("unable to remove webhook configuration [%v]", err) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /manifests/for_testing/deployment_sidecar_secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: pod-01 5 | labels: 6 | env: demo1 7 | spec: 8 | shareProcessNamespace: true 9 | containers: 10 | - name: pod-01 11 | image: demo/demo:v1 12 | env: 13 | - name: CLIENT 14 | value: "service2" 15 | - name: mtls 16 | image: thebsdbox/smesh-proxy:v1 17 | securityContext: 18 | privileged: true 19 | #capabilities: 20 | # add: ["IPC_LOCK", "SYS_ADMIN", "SYS_RESOURCE", "BPF"] 21 | volumeMounts: 22 | - mountPath: /sys/kernel/debug 23 | name: sys-kernel-debug 24 | # - name: cgroup 25 | # mountPath: /hostfs/sys/fs/cgroup 26 | env: 27 | - name: SMESH-CA 28 | valueFrom: 29 | secretKeyRef: 30 | name: pod-01-smesh 31 | key: ca 32 | - name: SMESH-CERT 33 | valueFrom: 34 | secretKeyRef: 35 | name: pod-01-smesh 36 | key: cert 37 | - name: SMESH-KEY 38 | valueFrom: 39 | secretKeyRef: 40 | name: pod-01-smesh 41 | key: key 42 | volumes: 43 | - name: sys-kernel-debug 44 | hostPath: 45 | path: /sys/kernel/debug 46 | type: DirectoryOrCreate 47 | - name: cgroup 48 | hostPath: 49 | path: /sys/fs/cgroup 50 | --- 51 | apiVersion: v1 52 | kind: Pod 53 | metadata: 54 | name: pod-02 55 | labels: 56 | env: demo2 57 | spec: 58 | shareProcessNamespace: true 59 | containers: 60 | - name: pod-02 61 | image: demo/demo:v1 62 | env: 63 | - name: CLIENT 64 | value: "service1" 65 | - name: mtls 66 | image: thebsdbox/smesh-proxy:v1 67 | securityContext: 68 | privileged: 69 | true 70 | #capabilities: 71 | #add: ["IPC_LOCK", "SYS_ADMIN", "SYS_RESOURCE", "BPF"] 72 | env: 73 | - name: MY_NODE_NAME 74 | valueFrom: 75 | fieldRef: 76 | fieldPath: spec.nodeName 77 | - name: SMESH-CA 78 | valueFrom: 79 | secretKeyRef: 80 | name: pod-02-smesh 81 | key: ca 82 | - name: SMESH-CERT 83 | valueFrom: 84 | secretKeyRef: 85 | name: pod-02-smesh 86 | key: cert 87 | - name: SMESH-KEY 88 | valueFrom: 89 | secretKeyRef: 90 | name: pod-02-smesh 91 | key: key 92 | volumeMounts: 93 | - mountPath: /sys/kernel/debug 94 | name: sys-kernel-debug 95 | # - name: cgroup 96 | # mountPath: /hostfs/sys/fs/cgroup 97 | volumes: 98 | - name: sys-kernel-debug 99 | hostPath: 100 | path: /sys/kernel/debug 101 | type: DirectoryOrCreate 102 | - name: cgroup 103 | hostPath: 104 | path: /sys/fs/cgroup 105 | --- 106 | apiVersion: v1 107 | kind: Service 108 | metadata: 109 | name: service1 110 | spec: 111 | clusterIP: None # <= Don't forget!! 112 | selector: 113 | env: demo1 114 | ports: 115 | - protocol: TCP 116 | port: 80 117 | targetPort: 8080 118 | --- 119 | apiVersion: v1 120 | kind: Service 121 | metadata: 122 | name: service2 123 | spec: 124 | clusterIP: None # <= Don't forget!! 125 | selector: 126 | env: demo2 127 | ports: 128 | - protocol: TCP 129 | port: 80 130 | targetPort: 8080 131 | -------------------------------------------------------------------------------- /controller/Makefile: -------------------------------------------------------------------------------- 1 | # Setting SHELL to bash allows bash commands to be executed by recipes. 2 | # This is a requirement for 'setup-envtest.sh' in the test target. 3 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 4 | SHELL = /usr/bin/env bash -o pipefail 5 | .SHELLFLAGS = -ec 6 | 7 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 8 | ifeq (,$(shell go env GOBIN)) 9 | GOBIN=$(shell go env GOPATH)/bin 10 | else 11 | GOBIN=$(shell go env GOBIN) 12 | endif 13 | 14 | # Tools for deploy 15 | KUBECTL?=kubectl 16 | PWD=$(shell pwd) 17 | KUSTOMIZE?=$(PWD)/$(PERMANENT_TMP_GOPATH)/bin/kustomize 18 | KUSTOMIZE_VERSION?=v3.5.4 19 | KUSTOMIZE_ARCHIVE_NAME?=kustomize_$(KUSTOMIZE_VERSION)_$(GOHOSTOS)_$(GOHOSTARCH).tar.gz 20 | kustomize_dir:=$(dir $(KUSTOMIZE)) 21 | 22 | IMAGE = thebsdbox/smesh-sidecar:v1 23 | 24 | all: build 25 | .PHONY: all 26 | 27 | ##@ General 28 | 29 | # The help target prints out all targets with their descriptions organized 30 | # beneath their categories. The categories are represented by '##@' and the 31 | # target descriptions by '##'. The awk commands is responsible for reading the 32 | # entire set of makefiles included in this invocation, looking for lines of the 33 | # file as xyz: ## something, and then pretty-format the target and help. Then, 34 | # if there's a line with ##@ something, that gets pretty-printed as a category. 35 | # More info on the usage of ANSI control characters for terminal formatting: 36 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 37 | # More info on the awk command: 38 | # http://linuxcommand.org/lc3_adv_awk.php 39 | 40 | .PHONY: help 41 | help: ## Display this help. 42 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 43 | 44 | ##@ Development 45 | 46 | .PHONY: fmt 47 | fmt: ## Run go fmt against code. 48 | go fmt ./... 49 | 50 | .PHONY: vet 51 | vet: ## Run go vet against code. 52 | go vet ./... 53 | 54 | .PHONY: test 55 | test: fmt vet ## Run tests. 56 | go test ./... -coverprofile cover.out 57 | 58 | ##@ Build 59 | 60 | .PHONY: build 61 | build: fmt vet ## Build binary. 62 | go build -o bin/sidecar-injector ./cmd/ 63 | 64 | .PHONY: docker-build 65 | docker-build: test ## Build docker image. 66 | docker build -t ${IMAGE} . 67 | 68 | .PHONY: docker-push 69 | docker-push: ## Push docker image. 70 | docker push ${IMAGE} 71 | 72 | kind: 73 | kind load docker-image ${IMAGE} 74 | ##@ Deployment 75 | 76 | deploy: kustomize 77 | cp deploy/kustomization.yaml deploy/kustomization.yaml.tmp 78 | cd deploy && $(KUSTOMIZE) edit set image sidecar-injector=$(IMAGE) 79 | $(KUSTOMIZE) build deploy | $(KUBECTL) apply -f - 80 | mv deploy/kustomization.yaml.tmp deploy/kustomization.yaml 81 | 82 | undeploy: kustomize 83 | $(KUSTOMIZE) build deploy | $(KUBECTL) delete --ignore-not-found -f - 84 | 85 | KUSTOMIZE = $(shell pwd)/bin/kustomize 86 | .PHONY: kustomize 87 | kustomize: ## Download kustomize locally if necessary. 88 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 89 | 90 | # go-get-tool will 'go get' any package $2 and install it to $1. 91 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 92 | define go-get-tool 93 | @[ -f $(1) ] || { \ 94 | set -e ;\ 95 | TMP_DIR=$$(mktemp -d) ;\ 96 | cd $$TMP_DIR ;\ 97 | go mod init tmp ;\ 98 | echo "Downloading $(2)" ;\ 99 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ 100 | rm -rf $$TMP_DIR ;\ 101 | } 102 | endef 103 | 104 | ##@ Cleanup 105 | .PHONY: clean 106 | clean: ## Cleanup resources. 107 | $(KUBECTL) delete ns sidecar-injector 108 | $(KUBECTL) delete mutatingwebhookconfiguration sidecar-injector-webhook 109 | $(KUBECTL) delete -f deploy 110 | rm -f cover.out 111 | 112 | -------------------------------------------------------------------------------- /manifests/for_testing/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: pod-01 5 | labels: 6 | env: demo1 7 | spec: 8 | shareProcessNamespace: true 9 | containers: 10 | - name: pod-01 11 | image: demo/demo:v1 12 | resources: 13 | requests: 14 | memory: "64Mi" 15 | cpu: "250m" 16 | limits: 17 | memory: "128Mi" 18 | cpu: "500m" 19 | env: 20 | - name: CLIENT 21 | value: "service2" 22 | - name: mtls 23 | image: kube-gateway/kube-gateway:v1 24 | command: ["sleep", "infinity"] # For debugging 25 | resources: 26 | requests: 27 | memory: "64Mi" 28 | cpu: "250m" 29 | limits: 30 | memory: "128Mi" 31 | cpu: "500m" 32 | securityContext: 33 | privileged: true 34 | #capabilities: 35 | # add: ["IPC_LOCK", "SYS_ADMIN", "SYS_RESOURCE", "BPF"] 36 | env: 37 | - name: KUBE_NODE_NAME 38 | valueFrom: 39 | fieldRef: 40 | fieldPath: spec.nodeName 41 | volumeMounts: 42 | - mountPath: /sys/kernel/debug 43 | name: sys-kernel-debug 44 | - name: cgroup 45 | mountPath: /hostfs/sys/fs/cgroup 46 | volumes: 47 | - name: sys-kernel-debug 48 | hostPath: 49 | path: /sys/kernel/debug 50 | type: DirectoryOrCreate 51 | - name: cgroup 52 | hostPath: 53 | path: /sys/fs/cgroup 54 | --- 55 | apiVersion: v1 56 | kind: Pod 57 | metadata: 58 | name: pod-02 59 | labels: 60 | env: demo2 61 | spec: 62 | shareProcessNamespace: true 63 | containers: 64 | - name: pod-02 65 | image: demo/demo:v1 66 | resources: 67 | requests: 68 | memory: "64Mi" 69 | cpu: "250m" 70 | limits: 71 | memory: "128Mi" 72 | cpu: "500m" 73 | env: 74 | - name: CLIENT 75 | value: "service1" 76 | volumeMounts: 77 | - mountPath: /sys/kernel/debug 78 | name: sys-kernel-debug 79 | - name: mtls 80 | image: kube-gateway/kube-gateway:v1 81 | command: ["sleep", "infinity"] # For debugging 82 | resources: 83 | requests: 84 | memory: "64Mi" 85 | cpu: "250m" 86 | limits: 87 | memory: "128Mi" 88 | cpu: "500m" 89 | securityContext: 90 | privileged: 91 | true 92 | #add: ["IPC_LOCK", "SYS_ADMIN", "SYS_RESOURCE", "BPF"] 93 | env: 94 | - name: KUBE_NODE_NAME 95 | valueFrom: 96 | fieldRef: 97 | fieldPath: spec.nodeName 98 | volumeMounts: 99 | - mountPath: /sys/kernel/debug 100 | name: sys-kernel-debug 101 | # - name: cgroup 102 | # mountPath: /hostfs/sys/fs/cgroup 103 | volumes: 104 | - name: sys-kernel-debug 105 | hostPath: 106 | path: /sys/kernel/debug 107 | type: DirectoryOrCreate 108 | # - name: cgroup 109 | # hostPath: 110 | # path: /sys/fs/cgroup 111 | --- 112 | apiVersion: v1 113 | kind: Service 114 | metadata: 115 | name: service1 116 | spec: 117 | clusterIP: None # <= Don't forget!! 118 | selector: 119 | env: demo1 120 | ports: 121 | - protocol: TCP 122 | port: 80 123 | targetPort: 8080 124 | --- 125 | apiVersion: v1 126 | kind: Service 127 | metadata: 128 | name: service2 129 | spec: 130 | clusterIP: None # <= Don't forget!! 131 | selector: 132 | env: demo2 133 | ports: 134 | - protocol: TCP 135 | port: 80 136 | targetPort: 8080 137 | --- 138 | apiVersion: apps/v1 139 | kind: DaemonSet 140 | metadata: 141 | name: kube-gateway 142 | spec: 143 | selector: 144 | matchLabels: 145 | app: kube-gateway 146 | template: 147 | metadata: 148 | labels: 149 | app: kube-gateway 150 | spec: 151 | containers: 152 | - name: kube-gateway 153 | image: kube-gateway/kube-gateway:v1 154 | securityContext: 155 | privileged: true 156 | env: 157 | - name: KUBE_GATEWAY 158 | value: "true" 159 | terminationGracePeriodSeconds: 30 160 | hostNetwork: true 161 | -------------------------------------------------------------------------------- /pkg/connection/utils.go: -------------------------------------------------------------------------------- 1 | package connection 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | 10 | "github.com/gookit/slog" 11 | ) 12 | 13 | const ( 14 | SO_ORIGINAL_DST = 80 // Socket option to get the original destination address 15 | ) 16 | 17 | // SockAddrIn is a struct to hold the sockaddr_in structure for IPv4 "retrieved" by the SO_ORIGINAL_DST. 18 | type SockAddrIn struct { 19 | SinFamily uint16 20 | SinPort [2]byte 21 | SinAddr [4]byte 22 | // Pad to match the size of sockaddr_in 23 | Pad [8]byte 24 | } 25 | 26 | type Certs struct { 27 | ca []byte 28 | key []byte 29 | cert []byte 30 | } 31 | 32 | // helper function for getsockopt 33 | func getsockopt(s int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) { 34 | _, _, e := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(optname), uintptr(optval), uintptr(unsafe.Pointer(optlen)), 0) 35 | if e != 0 { 36 | return e 37 | } 38 | return 39 | } 40 | 41 | func ToInt(address string) int { 42 | ip := net.ParseIP(address) 43 | i := int(ip[12]) * 16777216 44 | i += int(ip[13]) * 65536 45 | i += int(ip[14]) * 256 46 | i += int(ip[15]) 47 | return i 48 | } 49 | 50 | func (c *Config) findTargetFromConnection(conn net.Conn) (targetAddr string, targetPort uint16, err error) { 51 | // Using RawConn is necessary to perform low-level operations on the underlying socket file descriptor in Go. 52 | // This allows us to use getsockopt to retrieve the original destination address set by the SO_ORIGINAL_DST option, 53 | // which isn't directly accessible through Go's higher-level networking API. 54 | rawConn, err := conn.(*net.TCPConn).SyscallConn() 55 | if err != nil { 56 | slog.Printf("Failed to get raw connection: %v", err) 57 | return 58 | } 59 | 60 | var originalDst SockAddrIn 61 | // var cookie uint64 62 | // If Control is not nil, it is called after creating the network connection but before binding it to the operating system. 63 | rawConn.Control(func(fd uintptr) { 64 | optlen := uint32(unsafe.Sizeof(originalDst)) 65 | // Retrieve the original destination address by making a syscall with the SO_ORIGINAL_DST option. 66 | err = getsockopt(int(fd), syscall.SOL_IP, SO_ORIGINAL_DST, unsafe.Pointer(&originalDst), &optlen) 67 | if err != nil { 68 | slog.Printf("getsockopt SO_ORIGINAL_DST failed: %v", err) 69 | return 70 | } 71 | // cookie, err = unix.GetsockoptUint64(int(fd), unix.SOL_SOCKET, unix.SO_COOKIE) 72 | // if err != nil { 73 | // slog.Printf("getsockopt SOL_SOCKET failed: %v", err) 74 | // } 75 | }) 76 | // slog.Info("🍪 %d", cookie) 77 | // i := c.Socks.Iterate() 78 | // var key uint32 79 | // var value mirrorsSocket 80 | // for i.Next(&key, &value) { 81 | // // Order of keys is non-deterministic due to randomized map seed 82 | // slog.Infof("%d %v", key, value) 83 | // } 84 | 85 | // // if err != nil || err2 != nil { 86 | // // return 87 | // // } 88 | // // slog.Infof("Cookies %d", cookie) 89 | // var m mirrorsSocket 90 | // err = c.Socks.Lookup(uint32(cookie), &m) 91 | // if err != nil { 92 | // slog.Error(err) 93 | // } else { 94 | // fmt.Printf("%v", m) 95 | // } 96 | targetAddr = net.IPv4(originalDst.SinAddr[0], originalDst.SinAddr[1], originalDst.SinAddr[2], originalDst.SinAddr[3]).String() 97 | targetPort = (uint16(originalDst.SinPort[0]) << 8) | uint16(originalDst.SinPort[1]) 98 | return 99 | } 100 | 101 | func GetEnvCerts() (*Certs, error) { 102 | envca, exists := os.LookupEnv("SMESH-CA") 103 | if !exists { 104 | return nil, fmt.Errorf("unable to find secrets from environment") 105 | } 106 | envcert, exists := os.LookupEnv("SMESH-CERT") 107 | if !exists { 108 | return nil, fmt.Errorf("unable to find secrets from environment") 109 | } 110 | envkey, exists := os.LookupEnv("SMESH-KEY") 111 | if !exists { 112 | return nil, fmt.Errorf("unable to find secrets from environment") 113 | } 114 | return &Certs{ 115 | ca: []byte(envca), 116 | cert: []byte(envcert), 117 | key: []byte(envkey), 118 | }, nil 119 | 120 | } 121 | 122 | func GetFSCerts() (*Certs, error) { 123 | f, err := os.ReadDir("/tmp") 124 | if err != nil { 125 | slog.Errorf("unable to parse /tmp [%v]", err) 126 | } else { 127 | for x := range f { 128 | slog.Infof("%s", f[x].Name()) 129 | } 130 | } 131 | envca, err := os.ReadFile("/tmp/ca.crt") 132 | if err != nil { 133 | return nil, fmt.Errorf("unable to read secrets from filesystem [%v]", err) 134 | } 135 | envcert, err := os.ReadFile("/tmp/cert.crt") 136 | if err != nil { 137 | return nil, fmt.Errorf("unable to read secrets from filesystem [%v]", err) 138 | } 139 | envkey, err := os.ReadFile("/tmp/key.crt") 140 | if err != nil { 141 | return nil, fmt.Errorf("unable to read secrets from filesystem [%v]", err) 142 | } 143 | return &Certs{ 144 | ca: []byte(envca), 145 | cert: []byte(envcert), 146 | key: []byte(envkey), 147 | }, nil 148 | 149 | } 150 | -------------------------------------------------------------------------------- /ebpf/mirrors.h: -------------------------------------------------------------------------------- 1 | // go:build ignore 2 | #include "vmlinux.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define MAX_CONNECTIONS 20000 10 | #define AF_INET 2 /* IP protocol family. */ 11 | #define BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB 4 12 | 13 | struct Config { 14 | __u32 proxy_addr; 15 | __u16 proxy_port; 16 | __u64 proxy_pid; 17 | __u32 network; 18 | __u16 mask; 19 | __u8 debug; 20 | }; 21 | 22 | struct Socket { 23 | __u32 src_addr; 24 | __u16 src_port; 25 | __u32 dst_addr; 26 | __u16 dst_port; 27 | }; 28 | 29 | struct { 30 | __uint(type, BPF_MAP_TYPE_ARRAY); 31 | __uint(max_entries, 1); 32 | __type(key, __u32); 33 | __type(value, struct Config); 34 | } map_config SEC(".maps"); 35 | 36 | struct { 37 | __uint(type, BPF_MAP_TYPE_HASH); 38 | __uint(max_entries, MAX_CONNECTIONS); 39 | __type(key, __u32); 40 | __type(value, struct Socket); 41 | } map_socks SEC(".maps"); 42 | 43 | struct { 44 | __uint(type, BPF_MAP_TYPE_HASH); 45 | __uint(max_entries, MAX_CONNECTIONS); 46 | __type(key, __u16); 47 | __type(value, __u64); 48 | } map_ports SEC(".maps"); 49 | 50 | // struct pid_namespace *get_task_pid_ns(const struct task_struct *task); 51 | // struct pid *get_task_pid_ptr(const struct task_struct *task, 52 | // enum pid_type type); 53 | // pid_t get_task_ns_pid(const struct task_struct *task, enum pid_type type); 54 | 55 | // pid_t get_pid_nr_ns(struct pid *pid, struct pid_namespace *ns); 56 | // pid_t get_ns_pid(void); 57 | 58 | typedef struct pid_key { 59 | __u32 pid; // pid as seen by the userspace (for example, inside its container) 60 | __u32 ns; // pids namespace for the process 61 | } __attribute__((packed)) pid_key_t; 62 | 63 | typedef struct pid_info_t { 64 | __u32 host_pid; // pid as seen by the root cgroup (and by BPF) 65 | __u32 user_pid; // pid as seen by the userspace (for example, inside its 66 | // container) 67 | __u32 ns; // pids namespace for the process 68 | } __attribute__((packed)) pid_info; 69 | 70 | static __always_inline void ns_pid_ppid(struct task_struct *task, int *pid, 71 | int *ppid, __u32 *pid_ns_id) { 72 | struct upid upid; 73 | 74 | unsigned int level = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, level); 75 | struct pid *ns_pid = 76 | (struct pid *)BPF_CORE_READ(task, group_leader, thread_pid); 77 | bpf_probe_read_kernel(&upid, sizeof(upid), &ns_pid->numbers[level]); 78 | 79 | *pid = upid.nr; 80 | unsigned int p_level = 81 | BPF_CORE_READ(task, real_parent, nsproxy, pid_ns_for_children, level); 82 | 83 | struct pid *ns_ppid = 84 | (struct pid *)BPF_CORE_READ(task, real_parent, group_leader, thread_pid); 85 | bpf_probe_read_kernel(&upid, sizeof(upid), &ns_ppid->numbers[p_level]); 86 | *ppid = upid.nr; 87 | 88 | struct ns_common ns = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, ns); 89 | *pid_ns_id = ns.inum; 90 | } 91 | 92 | // __hidden struct pid *get_task_pid_ptr(const struct task_struct *task, 93 | // enum pid_type type) { 94 | // // Returns the pid pointer of the given task. See get_task_pid_ptr for 95 | // // the kernel implementation. 96 | // return (type == PIDTYPE_PID) ? BPF_CORE_READ(task, thread_pid) 97 | // : BPF_CORE_READ(task, signal, pids[type]); 98 | // } 99 | 100 | // __hidden struct pid_namespace *get_task_pid_ns(const struct task_struct 101 | // *task, 102 | // enum pid_type type) { 103 | // struct pid_namespace *ns; 104 | // struct pid *p; 105 | // int level; 106 | 107 | // // See kernel function task_active_pid_ns in pid.c which calls into 108 | // // ns_of_pid. Returns the pid namespace of the given task. 109 | // if (!task) 110 | // task = (struct task_struct *)bpf_get_current_task(); 111 | 112 | // if (!task) 113 | // return NULL; 114 | 115 | // p = get_task_pid_ptr(task, type); 116 | // if (!p) 117 | // return NULL; 118 | 119 | // level = BPF_CORE_READ(p, level); 120 | // ns = BPF_CORE_READ(p, numbers[level].ns); 121 | // return ns; 122 | // } 123 | 124 | // __hidden pid_t get_pid_nr_ns(struct pid *pid, struct pid_namespace *ns) { 125 | // int level, ns_level; 126 | // pid_t nr = 0; 127 | 128 | // /* This function implements the kernel equivalent pid_nr_ns in linux/pid.h 129 | // */ 130 | // if (!pid || !ns) 131 | // return nr; 132 | 133 | // level = BPF_CORE_READ(pid, level); 134 | // ns_level = BPF_CORE_READ(ns, level); 135 | // if (ns_level <= level) { 136 | // struct upid upid; 137 | 138 | // upid = BPF_CORE_READ(pid, numbers[ns_level]); 139 | // if (upid.ns == ns) 140 | // nr = upid.nr; 141 | // } 142 | // return nr; 143 | // } 144 | 145 | // __hidden pid_t get_task_ns_pid(const struct task_struct *task) { 146 | // struct pid_namespace *ns; 147 | // struct pid *p; 148 | 149 | // if (!task) 150 | // task = (struct task_struct *)bpf_get_current_task(); 151 | 152 | // ns = get_task_pid_ns(task, PIDTYPE_TGID); 153 | // p = get_task_pid_ptr(task, PIDTYPE_PID); 154 | // return get_pid_nr_ns(p, ns); 155 | // } 156 | 157 | // __hidden pid_t get_ns_pid(void) { return get_task_ns_pid(NULL); } -------------------------------------------------------------------------------- /controller/cmd/webhookconfig.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/gookit/slog" 10 | admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 11 | apierrors "k8s.io/apimachinery/pkg/api/errors" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/client-go/kubernetes" 14 | "k8s.io/client-go/rest" 15 | "k8s.io/client-go/tools/clientcmd" 16 | ) 17 | 18 | var ( 19 | webhookConfigName = "sidecar-injector-webhook" 20 | webhookInjectPath = "/inject" 21 | ) 22 | 23 | func client(kubeconfigPath string) (*kubernetes.Clientset, error) { 24 | var kubeconfig *rest.Config 25 | 26 | if kubeconfigPath != "" { 27 | config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) 28 | if err != nil { 29 | return nil, fmt.Errorf("unable to load kubeconfig from %s: %v", kubeconfigPath, err) 30 | } 31 | kubeconfig = config 32 | } else { 33 | config, err := rest.InClusterConfig() 34 | if err != nil { 35 | return nil, fmt.Errorf("unable to load in-cluster config: %v", err) 36 | } 37 | kubeconfig = config 38 | } 39 | 40 | // build the client set 41 | clientSet, err := kubernetes.NewForConfig(kubeconfig) 42 | if err != nil { 43 | return nil, fmt.Errorf("creating the kubernetes client set - %s", err) 44 | } 45 | return clientSet, nil 46 | } 47 | 48 | func createOrUpdateMutatingWebhookConfiguration(caPEM []byte, webhookService, webhookNamespace string, clientset *kubernetes.Clientset) error { 49 | slog.Println("Initializing the kube client...") 50 | 51 | mutatingWebhookConfigV1Client := clientset.AdmissionregistrationV1() 52 | slog.Printf("Creating or updating the mutatingwebhookconfiguration: %s", webhookConfigName) 53 | fail := admissionregistrationv1.Fail 54 | sideEffect := admissionregistrationv1.SideEffectClassNone 55 | 56 | mutatingWebhookConfig := &admissionregistrationv1.MutatingWebhookConfiguration{ 57 | ObjectMeta: metav1.ObjectMeta{ 58 | Name: webhookConfigName, 59 | }, 60 | Webhooks: []admissionregistrationv1.MutatingWebhook{{ 61 | Name: "sidecar-injector.thebsdbox.co.uk", 62 | AdmissionReviewVersions: []string{"v1", "v1beta1"}, 63 | SideEffects: &sideEffect, 64 | ClientConfig: admissionregistrationv1.WebhookClientConfig{ 65 | CABundle: caPEM, // self-generated CA for the webhook 66 | Service: &admissionregistrationv1.ServiceReference{ 67 | Name: webhookService, 68 | Namespace: webhookNamespace, 69 | Path: &webhookInjectPath, 70 | }, 71 | }, 72 | Rules: []admissionregistrationv1.RuleWithOperations{ 73 | { 74 | Operations: []admissionregistrationv1.OperationType{ 75 | admissionregistrationv1.Create, 76 | admissionregistrationv1.Update, 77 | }, 78 | Rule: admissionregistrationv1.Rule{ 79 | APIGroups: []string{""}, 80 | APIVersions: []string{"v1"}, 81 | Resources: []string{"pods"}, 82 | }, 83 | }, 84 | }, 85 | NamespaceSelector: &metav1.LabelSelector{ 86 | MatchLabels: map[string]string{ 87 | "sidecar-injection": "enabled", 88 | }, 89 | }, 90 | FailurePolicy: &fail, 91 | }}, 92 | } 93 | 94 | mutatewebhook, _ := json.Marshal(mutatingWebhookConfig) 95 | 96 | fmt.Println("---------------------------------") 97 | fmt.Println("New MutatingWebhookConfig Object") 98 | fmt.Println("---------------------------------") 99 | fmt.Println(string(mutatewebhook)) 100 | 101 | foundWebhookConfig, err := mutatingWebhookConfigV1Client.MutatingWebhookConfigurations().Get(context.TODO(), webhookConfigName, metav1.GetOptions{}) 102 | if err != nil && apierrors.IsNotFound(err) { 103 | if _, err := mutatingWebhookConfigV1Client.MutatingWebhookConfigurations().Create(context.TODO(), mutatingWebhookConfig, metav1.CreateOptions{}); err != nil { 104 | slog.Warnf("Failed to create the mutatingwebhookconfiguration: %s", webhookConfigName) 105 | return err 106 | } 107 | slog.Printf("Created mutatingwebhookconfiguration: %s", webhookConfigName) 108 | } else if err != nil { 109 | slog.Warnf("Failed to check the mutatingwebhookconfiguration: %s", webhookConfigName) 110 | return err 111 | } else { 112 | // there is an existing mutatingWebhookConfiguration 113 | if len(foundWebhookConfig.Webhooks) != len(mutatingWebhookConfig.Webhooks) || 114 | !(foundWebhookConfig.Webhooks[0].Name == mutatingWebhookConfig.Webhooks[0].Name && 115 | reflect.DeepEqual(foundWebhookConfig.Webhooks[0].AdmissionReviewVersions, mutatingWebhookConfig.Webhooks[0].AdmissionReviewVersions) && 116 | reflect.DeepEqual(foundWebhookConfig.Webhooks[0].SideEffects, mutatingWebhookConfig.Webhooks[0].SideEffects) && 117 | reflect.DeepEqual(foundWebhookConfig.Webhooks[0].FailurePolicy, mutatingWebhookConfig.Webhooks[0].FailurePolicy) && 118 | reflect.DeepEqual(foundWebhookConfig.Webhooks[0].Rules, mutatingWebhookConfig.Webhooks[0].Rules) && 119 | reflect.DeepEqual(foundWebhookConfig.Webhooks[0].NamespaceSelector, mutatingWebhookConfig.Webhooks[0].NamespaceSelector) && 120 | reflect.DeepEqual(foundWebhookConfig.Webhooks[0].ClientConfig.CABundle, mutatingWebhookConfig.Webhooks[0].ClientConfig.CABundle) && 121 | reflect.DeepEqual(foundWebhookConfig.Webhooks[0].ClientConfig.Service, mutatingWebhookConfig.Webhooks[0].ClientConfig.Service)) { 122 | mutatingWebhookConfig.ObjectMeta.ResourceVersion = foundWebhookConfig.ObjectMeta.ResourceVersion 123 | if _, err := mutatingWebhookConfigV1Client.MutatingWebhookConfigurations().Update(context.TODO(), mutatingWebhookConfig, metav1.UpdateOptions{}); err != nil { 124 | slog.Warnf("Failed to update the mutatingwebhookconfiguration: %s, %v", webhookConfigName) 125 | return err 126 | } 127 | slog.Printf("Updated the mutatingwebhookconfiguration: %s", webhookConfigName) 128 | } 129 | slog.Warnf("The mutatingwebhookconfiguration: %s already exists and has no change", webhookConfigName) 130 | } 131 | 132 | // fmt.Println(mutatingWebhookConfig) 133 | 134 | return nil 135 | } 136 | 137 | func tidyWebhook(webhookConfigName string, clientset *kubernetes.Clientset) error { 138 | mutatingWebhookConfigV1Client := clientset.AdmissionregistrationV1() 139 | 140 | return mutatingWebhookConfigV1Client.MutatingWebhookConfigurations().Delete(context.TODO(), webhookConfigName, *metav1.NewDeleteOptions(0)) 141 | 142 | } 143 | -------------------------------------------------------------------------------- /pkg/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -type Config mirrors ../../ebpf/mirrors.c 4 | 5 | import ( 6 | "bufio" 7 | "context" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "net" 12 | "os" 13 | "os/signal" 14 | "smesh/pkg/connection" 15 | "strconv" 16 | "strings" 17 | "syscall" 18 | 19 | "github.com/cilium/ebpf" 20 | "github.com/cilium/ebpf/link" 21 | "github.com/cilium/ebpf/rlimit" 22 | "github.com/docker/docker/pkg/parsers/kernel" 23 | "github.com/gookit/slog" 24 | ) 25 | 26 | // This sets upp all of the internal logic, and loads the eBPF 27 | 28 | var tracker struct { 29 | objs mirrorsObjects // out eBPF objects 30 | cg link.Link 31 | connect4Link link.Link 32 | sockopsLink link.Link 33 | sockoptLink link.Link 34 | } 35 | 36 | func LoadEPF(c *connection.Config) error { 37 | v, err := kernel.GetKernelVersion() 38 | if err != nil { 39 | slog.Errorf("unable to parse kernel version %v", err) 40 | } 41 | 42 | slog.Infof("detected Kernel %d.%d.x", v.Kernel, v.Major) 43 | 44 | // Remove resource limits for kernels <5.11. 45 | if v.Kernel >= 5 && v.Major >= 11 { 46 | if err := rlimit.RemoveMemlock(); err != nil { 47 | return fmt.Errorf("removing memlock: %v", err) 48 | } 49 | } 50 | 51 | // Load the compiled eBPF ELF and load it into the kernel 52 | // NOTE: we could also pin the eBPF program 53 | //var objs mirrorsObjects 54 | if err := loadMirrorsObjects(&tracker.objs, nil); err != nil { 55 | return fmt.Errorf("loading eBPF objects: %v", err) 56 | } 57 | //defer objs.Close() 58 | // Attach eBPF programs to the root cgroup 59 | tracker.connect4Link, err = link.AttachCgroup(link.CgroupOptions{ 60 | Path: c.CgroupOverride, 61 | Attach: ebpf.AttachCGroupInet4Connect, 62 | Program: tracker.objs.CgConnect4, 63 | }) 64 | if err != nil { 65 | return fmt.Errorf("attaching CgConnect4 program to Cgroup: %v", err) 66 | } 67 | // defer connect4Link.Close() 68 | 69 | tracker.sockopsLink, err = link.AttachCgroup(link.CgroupOptions{ 70 | Path: c.CgroupOverride, 71 | Attach: ebpf.AttachCGroupSockOps, 72 | Program: tracker.objs.CgSockOps, 73 | }) 74 | if err != nil { 75 | return fmt.Errorf("attaching CgSockOps program to Cgroup: %v", err) 76 | } 77 | // defer sockopsLink.Close() 78 | 79 | tracker.sockoptLink, err = link.AttachCgroup(link.CgroupOptions{ 80 | Path: c.CgroupOverride, 81 | Attach: ebpf.AttachCGroupGetsockopt, 82 | Program: tracker.objs.CgSockOpt, 83 | }) 84 | if err != nil { 85 | return fmt.Errorf("attaching CgSockOpt program to Cgroup: %v", err) 86 | } 87 | // defer sockoptLink.Close() 88 | // Update the proxyMaps map with the proxy server configuration, because we need to know the proxy server PID in order 89 | // to filter out eBPF events generated by the proxy server itself so it would not proxy its own packets in a loop. 90 | 91 | cidr := strings.Split(c.PodCIDR, "/") 92 | if len(cidr) < 1 { 93 | return fmt.Errorf("error parsing cidr %s", c.PodCIDR) 94 | } 95 | 96 | var key uint32 = 0 97 | i, err := strconv.Atoi(cidr[1]) 98 | if err != nil { 99 | // ... handle error 100 | return err 101 | } 102 | config := mirrorsConfig{ 103 | ProxyPort: uint16(c.ProxyPort), 104 | ProxyPid: uint64(os.Getpid()), 105 | ProxyAddr: uint32(connection.ToInt(c.Address)), 106 | Network: uint32(connection.ToInt(cidr[0])), 107 | Mask: uint16(i), 108 | } 109 | 110 | err = tracker.objs.mirrorsMaps.MapConfig.Update(&key, &config, ebpf.UpdateAny) 111 | if err != nil { 112 | slog.Fatalf("Failed to update proxyMaps map: %v", err) 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func cleanup() { 119 | tracker.objs.Close() 120 | tracker.connect4Link.Close() 121 | tracker.sockopsLink.Close() 122 | tracker.sockoptLink.Close() 123 | } 124 | 125 | func Setup() (*connection.Config, error) { 126 | var c connection.Config 127 | flag.StringVar(&c.Address, "address", "127.0.0.1", "Address to bind to, can also be a hostname") 128 | flag.StringVar(&c.ClusterAddress, "overrideAddress", "", "Address to force all traffic to") 129 | flag.StringVar(&c.CgroupOverride, "cgroupPath", "/sys/fs/cgroup", "Path for cgroup") 130 | flag.IntVar(&c.ProxyPort, "proxyPort", 18000, "Port for internal proxy") 131 | flag.IntVar(&c.ClusterPort, "clusterPort", 18001, "External port for cluster connectivity") 132 | flag.IntVar(&c.ClusterTLSPort, "clusterTLSPort", 18443, "External port for cluster connectivity (TLS)") 133 | flag.StringVar(&c.PodCIDR, "podCIDR", "10.244.0.0/16", "The CIDR range used for POD IP addresses") 134 | flag.Parse() 135 | 136 | // Lookup for environment variable 137 | envAddress, exists := os.LookupEnv("KUBE_NODE_NAME") 138 | if exists { 139 | c.ClusterAddress = envAddress 140 | } 141 | //_, gateway := os.LookupEnv("KUBE_GATEWAY") 142 | 143 | i, err := net.ResolveIPAddr("", c.Address) 144 | if err != nil { 145 | return nil, err 146 | } 147 | c.Address = i.String() 148 | 149 | // Overwrite the podcidr 150 | podCIDR, exists := os.LookupEnv("POD_CIDR") 151 | if exists { 152 | c.PodCIDR = podCIDR 153 | } 154 | 155 | return &c, nil 156 | } 157 | 158 | // This is a blocking function 159 | func Start(c *connection.Config) error { 160 | ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 161 | defer stop() 162 | // Start the proxy server on the localhost 163 | // We only demonstrate IPv4 in this example, but the same approach can be used for IPv6 164 | c.Socks = tracker.objs.MapSocks 165 | internalListener := c.StartInternalListener() 166 | defer internalListener.Close() 167 | go c.StartListeners(internalListener, true) 168 | 169 | var err error 170 | externalListener := c.StartExternalListener() 171 | defer externalListener.Close() 172 | 173 | // Attempt to get certificates from API 174 | // c.Certificates, err = getKubeCerts(os.Getenv("KUBECONFIG")) 175 | // if err != nil { 176 | // slog.Error(err) 177 | // Attempt to get from environment secrets 178 | 179 | c.Certificates, err = connection.GetEnvCerts() 180 | if err != nil { 181 | slog.Error(err) 182 | c.Certificates, err = connection.GetFSCerts() 183 | if err != nil { 184 | slog.Error(err) 185 | } 186 | } 187 | 188 | // If we have secrets enable a TLS listener 189 | if c.Certificates != nil { 190 | externalTLSListener := c.StartExternalTLSListener() 191 | defer externalTLSListener.Close() 192 | go c.StartTLSListener(externalTLSListener) 193 | } 194 | 195 | go c.StartListeners(externalListener, false) 196 | _, exists := os.LookupEnv("DEBUG") 197 | if exists { 198 | go cat() 199 | } 200 | <-ctx.Done() // We wait here 201 | 202 | return nil 203 | } 204 | 205 | func readLines(r io.Reader) { 206 | rd := bufio.NewReader(r) 207 | for { 208 | line, err := rd.ReadString('\n') 209 | if err == io.EOF { 210 | break 211 | } 212 | if err != nil { 213 | panic(fmt.Sprintf("Error reading lines %v", err)) 214 | } 215 | 216 | fmt.Printf("%s", line) 217 | 218 | } 219 | } 220 | 221 | func cat() { 222 | file, err := os.Open("/sys/kernel/debug/tracing/trace_pipe") 223 | if err != nil { 224 | fmt.Printf("Error trace pipe %v\n", err) 225 | return 226 | } 227 | defer file.Close() 228 | readLines(file) 229 | } 230 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SMESH 2 | 3 | It's a service mesh (kind of). 4 | 5 | ## How to Build and Run 6 | 7 | 8 | ``` 9 | go generate 10 | go build -o smesh 11 | sudo ./smesh 12 | ``` 13 | 14 | ## Original Architecture 15 | ``` 16 | ┌───────────┐ ┌───────────┐ 17 | │ Pod01 │ │ Pod01 │ 18 | │ 10.0.0.1 ┼────┬─────► 10.0.0.2 │ 19 | └───────────┘ │ └───────────┘ 20 | │ 21 | │ 22 | │ 23 | CNI Magic 🧙🏻‍♂️ 24 | ``` 25 | 26 | ## Version (zero dot zero dot) one (simple tunneling) 27 | 28 | This is the barebones of what I needed to achieve in order to transparently move traffic from one application to another. 29 | 30 | ``` 31 | ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ 32 | │Pod-01 │ │ Pod-02│ 33 | │10.0.0.1 x─x─x─x─► 10.0.2.2:80 │ │ ┌────────────────► 10.0.2.2│ 34 | │ │ eBPF captures the socket │ │ │ :80 │ 35 | │ │ Finds original destination │ │ │ │ 36 | │ │ Changes destination to lo │ │ │ │ 37 | │ │ │ │ │ │ 38 | │ ▼ Our listener sends │ │ │ │ 39 | │127.0.0.1:18000 │ │0.0.0.0:18001 │ 40 | │ │ │ │ ▲ │ 41 | └─────────┼───────────────────────┘ └─────┼───────────────────────────┘ 42 | │ │ 43 | └───────────────────────────────────────────────────┘ 44 | Uses original destination with a modified port 45 | ``` 46 | 47 | The steps: 48 | 49 | - Application on pod-01 does `connect()` to pod-02 (port80) `0.0.0.0:30000 -> 10.0.2.2:80` 50 | - 🐝 modifies the socket`client 0.0.0.0:30000 -> 127.0.0.1:18000` 51 | - Connection arrives `accept()` from `0.0.0.0:30000`, we get original destination address/port from socket 52 | - We do a `connect()` to destination:18001 so (`10.0.2.2:18001`) 53 | - We send the original port (80) as the first bit of data from pod-01 to pod-02 on port 18001 54 | - Pod-02 creates an internal connection to `10.0.2.2:80` 55 | - Send the data over and **YOLO** 56 | 57 | ### Observations 58 | 59 | - It was really easy to break traffic as inside the pod I was seeing the network traffic from the whole KIND instance, so without guarrails in place I was tunnelling kubelet and the api-server etc.. and that was a mess. 60 | - To ensure a situation where I did't try and redirect the traffic that we actually wanted to leave teh pod (so after we've captured it) we need to make sure we ignore any traffic from our pid. Sadly `__u64 pid = (bpf_get_current_pid_tgid() >> 32)` doesn't give us the `pid` inside the pod, but the one in the global namespace. Additionally `btf_bpf_get_ns_current_pid_tgid()` also doesn't work in a `cgroup` eBPF program, but luckily I found another way from spelunking around GitHub. 61 | - The current implementation is messy but it works. 62 | 63 | ## Version (zero dot zero dot) two (TLS) 64 | 65 | Now pods we care about will mount `ca.crt` and their own `.crt/.key` and use those when communicating. 66 | Additionally we have a program that "watches" pods, specifically the `update()` and when a pod gets an IP, then it will create the certs/secret will the required detail. 67 | 68 | ``` 69 | ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ 70 | │Pod-01 │ │ Pod-02│ 71 | │10.0.0.1 x─x─x─x─► 10.0.2.2:80 │ │ ┌────────────────► 10.0.2.2│ 72 | │ │ eBPF captures the socket │ │ │ :80 │ 73 | │ │ Finds original destination │ │ │ │ 74 | │ │ Changes destination to lo │ │ │ │ 75 | │ │ │ │ │ │ 76 | │ ▼ Our TLS listener sends │ │ │ │ 77 | │127.0.0.1:18000 │ │0.0.0.0:18001 │ 78 | │ │ │ │ ▲ │ 79 | └─────────┼───────────────────────┘ └─────┼───────────────────────────┘ 80 | │ │ 81 | └────────────────────────🔐─────────────────────────┘ 82 | Uses original destination with a modified port 83 | ``` 84 | ### Observations 85 | 86 | - There is a delay as the sidecar will error as the secret usually doesn't exist in time. 87 | - The eBPF code is still highly buggy :D 88 | 89 | ### TLS in action 90 | 91 | #### Without the sidecar 92 | 93 | The original port of `9000` is still being send cleartext traffic. 94 | 95 | ``` 96 | 10.0.0.227.35928 > 10.0.1.54.9000: Flags [P.], cksum 0x1650 (incorrect -> 0xd116), seq 153:170, ack 1, win 507, options [nop,nop,TS val 1710156213 ecr 1761501942], length 17 97 | 0x0000: 4500 0045 8b67 4000 4006 9933 0a00 00e3 E..E.g@.@..3.... 98 | 0x0010: 0a00 0136 8c58 2328 ed78 5fcb 31aa 5b9b ...6.X#(.x_.1.[. 99 | 0x0020: 8018 01fb 1650 0000 0101 080a 65ee e9b5 .....P......e... 100 | 0x0030: 68fe 62f6 4865 6c6c 6f20 6672 6f6d 2070 h.b.Hello.from.p 101 | 0x0040: 6f64 2d30 31 od-01 102 | ``` 103 | 104 | #### With the sidecar 105 | 106 | We can see that the destination port has been changed to the TLS port `18443`. 107 | ``` 108 | 10.0.0.196.51740 > 10.0.1.132.18443: Flags [P.], cksum 0x1695 (incorrect -> 0xef2a), seq 1740:1779, ack 1827, win 502, options [nop,nop,TS val 3093655397 ecr 4140148653], length 39 109 | 0x0000: 4500 005b 7b63 4000 4006 a8f2 0a00 00c4 E..[{c@.@....... 110 | 0x0010: 0a00 0184 ca1c 480b 8a63 4d53 f134 4176 ......H..cMS.4Av 111 | 0x0020: 8018 01f6 1695 0000 0101 080a b865 6f65 .............eoe 112 | 0x0030: f6c5 a7ad 1703 0300 2244 536d cf88 3385 ........"DSm..3. 113 | 0x0040: 263d d632 3795 b6b7 76c4 177d efee 9331 &=.27...v..}...1 114 | 0x0050: 2dcb 7c3e 5c16 7af6 9164 eb -.|>\.z..d. 115 | ``` 116 | 117 | ## Version (zero dot zero dot) three (sidecars) 118 | 119 | We've combined the sidecar injector and the pod "watcher"! 120 | 1. A pod is created 121 | 2. The injector rewrites the pod manifest so that it has an `initContainer` containing the proxy 122 | 3. Pod is now scheduled to be created 123 | 4. The pod watcher will see the `update`, where the `pod.status.podIP` is updated with an address and it will create a certificate. 124 | 5. The scheduled pod will be in a waiting/errored state as the `initContainer` will reference the certificate that hadn't been created yet. 125 | 6. Pod should start and traffic will now be encrytped with the certificates. 126 | 127 | ### Video 128 | 129 | 130 | https://github.com/user-attachments/assets/42bbfab4-8bad-4216-8f47-f4f12eb4fcf1 131 | 132 | 133 | ## Troubleshooting 134 | You can then inspect eBPF logs using `sudo cat /sys/kernel/debug/tracing/trace_pipe` to verify transparent proxy indeed intercepts the network traffic. 135 | 136 | ## Quick reload 137 | ``` 138 | kubectl delete -f ./deployment.yaml ;\ 139 | docker build -t kube-gateway/kube-gateway:v1 . ;\ 140 | kind load docker-image kube-gateway/kube-gateway:v1 ;\ 141 | kubectl apply -f ./deployment.yaml 142 | ``` 143 | 144 | ### 145 | -------------------------------------------------------------------------------- /controller/cmd/watcher.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "fmt" 12 | "math/big" 13 | "net" 14 | "os" 15 | "os/signal" 16 | "syscall" 17 | "time" 18 | 19 | "github.com/gookit/slog" 20 | v1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | 23 | "k8s.io/client-go/informers" 24 | "k8s.io/client-go/kubernetes" 25 | ) 26 | 27 | // This contains the watcher for new pods, sadly we can't do this in the webhook 28 | // this is because we need to wait for the pods to have been allocated IP an 29 | // IP address 30 | 31 | type certs struct { 32 | cacert []byte 33 | cakey []byte 34 | key []byte 35 | cert []byte 36 | org string 37 | namespace string 38 | } 39 | 40 | // Actual watcher code 41 | 42 | type informerHandler struct { 43 | clientset *kubernetes.Clientset 44 | c *certs 45 | } 46 | 47 | func (c *certs) watcher(clientSet *kubernetes.Clientset) error { 48 | 49 | factory := informers.NewSharedInformerFactory(clientSet, 0) 50 | 51 | informer := factory.Core().V1().Pods().Informer() 52 | 53 | _, err := informer.AddEventHandler(&informerHandler{clientset: clientSet, c: c}) 54 | if err != nil { 55 | return err 56 | } 57 | stop := make(chan struct{}, 2) 58 | 59 | go informer.Run(stop) 60 | forever := make(chan os.Signal, 1) 61 | signal.Notify(forever, syscall.SIGINT, syscall.SIGTERM) 62 | <-forever 63 | stop <- struct{}{} 64 | close(forever) 65 | close(stop) 66 | return nil 67 | } 68 | 69 | func (i *informerHandler) OnUpdate(oldObj, newObj interface{}) { 70 | newPod := newObj.(*v1.Pod) 71 | oldPod := oldObj.(*v1.Pod) 72 | 73 | // Inspect the changes 74 | if oldPod.Status.PodIP != newPod.Status.PodIP && newPod.Status.PodIP != "" { 75 | i.c.createCertificate(newPod.Name, []string{newPod.Name}, &newPod.Status.PodIP) 76 | 77 | err := i.c.loadSecret(newPod.Name, i.clientset) 78 | if err != nil { 79 | slog.Error(err) 80 | } 81 | //loadSecret(newPod.Name, "", i.clientset) 82 | } 83 | } 84 | 85 | func (i *informerHandler) OnDelete(obj interface{}) { 86 | p := obj.(*v1.Pod) 87 | name := fmt.Sprintf("%s-smesh", p.Name) 88 | err := i.clientset.CoreV1().Secrets(p.Namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) 89 | if err != nil { 90 | slog.Errorf("Error deleting secret %v", err) 91 | } else { 92 | slog.Infof("Deleted secret 🔏 [%s]", name) 93 | } 94 | 95 | } 96 | 97 | func (i *informerHandler) OnAdd(obj interface{}, b bool) { 98 | } 99 | 100 | // -- cert management code -- 101 | 102 | func (c *certs) getEnvCerts() (err error) { 103 | envcert, exists := os.LookupEnv("SMESH-CA-CERT") 104 | if !exists { 105 | return fmt.Errorf("unable to find secrets from environment") 106 | } 107 | envkey, exists := os.LookupEnv("SMESH-CA-KEY") 108 | if !exists { 109 | return fmt.Errorf("unable to find secrets from environment") 110 | } 111 | c.cacert = []byte(envcert) 112 | c.cakey = []byte(envkey) 113 | return nil 114 | } 115 | 116 | func (c *certs) generateCA() error { 117 | // ca := &x509.Certificate{ 118 | // SerialNumber: big.NewInt(1653), 119 | // Subject: pkix.Name{ 120 | // Organization: []string{"ORGANIZATION_NAME"}, 121 | // Country: []string{"COUNTRY_CODE"}, 122 | // Province: []string{"PROVINCE"}, 123 | // Locality: []string{"CITY"}, 124 | // StreetAddress: []string{"ADDRESS"}, 125 | // PostalCode: []string{"POSTAL_CODE"}, 126 | // CommonName: "42CA", 127 | // }, 128 | // NotBefore: time.Now(), 129 | // NotAfter: time.Now().AddDate(10, 0, 0), 130 | // IsCA: true, 131 | // ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 132 | // KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 133 | // BasicConstraintsValid: true, 134 | // } 135 | 136 | ca := &x509.Certificate{ 137 | SerialNumber: big.NewInt(2022), 138 | Subject: pkix.Name{Organization: []string{c.org}}, 139 | NotBefore: time.Now(), 140 | NotAfter: time.Now().AddDate(1, 0, 0), // expired in 1 year 141 | IsCA: true, 142 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 143 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 144 | BasicConstraintsValid: true, 145 | } 146 | 147 | priv, _ := rsa.GenerateKey(rand.Reader, 2048) 148 | pub := &priv.PublicKey 149 | ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv) 150 | if err != nil { 151 | slog.Error("create ca failed") 152 | return err 153 | } 154 | 155 | // Public key 156 | c.cacert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca_b}) 157 | 158 | // Private key 159 | c.cakey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 160 | 161 | return nil 162 | } 163 | 164 | func (c *certs) loadCA(clientSet *kubernetes.Clientset) error { 165 | secretMap := make(map[string][]byte) 166 | 167 | secretMap["ca-cert"] = c.cacert 168 | secretMap["ca-key"] = c.cakey 169 | secret := v1.Secret{ 170 | TypeMeta: metav1.TypeMeta{ 171 | Kind: "Secret", 172 | APIVersion: "v1", 173 | }, 174 | ObjectMeta: metav1.ObjectMeta{ 175 | Name: "watcher", 176 | }, 177 | Data: secretMap, 178 | Type: v1.SecretTypeOpaque, 179 | } 180 | 181 | s, err := clientSet.CoreV1().Secrets(c.namespace).Create(context.TODO(), &secret, metav1.CreateOptions{}) 182 | if err != nil { 183 | return fmt.Errorf("unable to create secrets %v", err) 184 | } 185 | slog.Info(fmt.Sprintf("Created Secret 🔐 [%s]", s.Name)) 186 | 187 | return nil 188 | } 189 | 190 | func (c *certs) createCertificate(commonname string, dnsNames []string, ip *string) { 191 | // Load CA 192 | tls.X509KeyPair(c.cacert, c.cakey) 193 | catls, err := tls.X509KeyPair(c.cacert, c.cakey) 194 | if err != nil { 195 | panic(err) 196 | } 197 | ca, err := x509.ParseCertificate(catls.Certificate[0]) 198 | if err != nil { 199 | panic(err) 200 | } 201 | // Prepare certificate 202 | cert := &x509.Certificate{ 203 | SerialNumber: big.NewInt(1658), 204 | Subject: pkix.Name{ 205 | Organization: []string{c.org}, 206 | CommonName: commonname, 207 | }, 208 | NotBefore: time.Now(), 209 | NotAfter: time.Now().AddDate(10, 0, 0), 210 | SubjectKeyId: []byte{1, 2, 3, 4, 6}, 211 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 212 | KeyUsage: x509.KeyUsageDigitalSignature, 213 | DNSNames: dnsNames, 214 | } 215 | 216 | // if name != nil { 217 | // cert.DNSNames = append(cert.DNSNames, *name) 218 | // } 219 | if ip != nil { 220 | ipAddress := net.ParseIP(*ip) 221 | cert.IPAddresses = append(cert.IPAddresses, ipAddress) 222 | } 223 | 224 | priv, _ := rsa.GenerateKey(rand.Reader, 2048) 225 | pub := &priv.PublicKey 226 | 227 | // Sign the certificate 228 | cert_b, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, catls.PrivateKey) 229 | if err != nil { 230 | panic(err) 231 | } 232 | // Public key 233 | c.cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert_b}) 234 | 235 | // Private key 236 | c.key = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 237 | 238 | } 239 | 240 | func (c *certs) loadSecret(name string, clientSet *kubernetes.Clientset) error { 241 | secretMap := make(map[string][]byte) 242 | 243 | secretMap["ca"] = c.cacert 244 | secretMap["cert"] = c.cert 245 | secretMap["key"] = c.key 246 | 247 | secret := v1.Secret{ 248 | TypeMeta: metav1.TypeMeta{ 249 | Kind: "Secret", 250 | APIVersion: "v1", 251 | }, 252 | ObjectMeta: metav1.ObjectMeta{ 253 | Name: name + "-smesh", 254 | }, 255 | Data: secretMap, 256 | Type: v1.SecretTypeOpaque, 257 | } 258 | 259 | s, err := clientSet.CoreV1().Secrets(v1.NamespaceDefault).Create(context.TODO(), &secret, metav1.CreateOptions{}) 260 | if err != nil { 261 | return fmt.Errorf("unable to create secrets %v", err) 262 | } 263 | slog.Info(fmt.Sprintf("Created Secret 🔐 [%s]", s.Name)) 264 | 265 | return nil 266 | } 267 | -------------------------------------------------------------------------------- /ebpf/mirrors.c: -------------------------------------------------------------------------------- 1 | // #include 2 | // #include 3 | // #include 4 | // #include 5 | // #include 6 | 7 | #include "mirrors.h" 8 | #include "vmlinux.h" 9 | #include 10 | 11 | extern int LINUX_KERNEL_VERSION __kconfig; 12 | 13 | // This hook is triggered when a process (inside the cgroup where this is 14 | // attached) calls the connect() syscall It redirect the connection to the 15 | // transparent proxy but stores the original destination address and port in a 16 | // map_socks 17 | SEC("cgroup/connect4") 18 | int cg_connect4(struct bpf_sock_addr *ctx) { 19 | // Only forward IPv4 TCP connections 20 | if (ctx->user_family != AF_INET) 21 | return 1; 22 | if (ctx->protocol != IPPROTO_TCP) 23 | return 1; 24 | 25 | __u32 key = 0; 26 | struct Config *conf = bpf_map_lookup_elem(&map_config, &key); 27 | if (!conf) 28 | return 1; 29 | 30 | // This field contains the IPv4 address passed to the connect() syscall 31 | // a.k.a. connect to this socket destination address and port 32 | __u32 dst_addr = bpf_ntohl(ctx->user_ip4); 33 | __u32 destination = bpf_ntohl(dst_addr); 34 | 35 | int mask = (-1) << (32 - conf->mask); 36 | // bpf_printk("hex 0x%x 0x%x 0x%x", bpf_htonl(ctx->user_ip4), mask, 37 | // conf->network); 38 | //bpf_printk("IP in bounds %pI4", &destination); 39 | 40 | // If this packet is not part of the podCIDR range then return 41 | if ((bpf_htonl(ctx->user_ip4) & mask) != conf->network) { 42 | return 1; 43 | } 44 | // bpf_printk("IP in bounds %pI4 %pI4 %pI4", &destination, &start, &end); 45 | 46 | // bpf_printk("IP out of bounds %u %u %u", destination, start, end); 47 | // bpf_printk("IP out of bounds %pI4 %pI4 %pI4", &destination, &start, &end); 48 | // if (destination < conf->start_addr || destination > conf->end_addr) { 49 | 50 | // // bpf_printk("IP out of bounds %pI4 %pI4 %pI4", &destination, &start, 51 | // // &end); 52 | // return 1; 53 | // } 54 | 55 | // This field contains the port number passed to the connect() syscall 56 | __u16 dst_port = bpf_ntohl(ctx->user_port) >> 16; 57 | //__u64 pid = (bpf_get_current_pid_tgid() >> 32); 58 | 59 | // This prevents the proxy from proxying itself 60 | // pid_t pid2 = get_ns_pid(); 61 | 62 | struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 63 | 64 | int ns_pid = 0; 65 | int ns_ppid = 0; 66 | __u32 pid_ns_id = 0; 67 | 68 | ns_pid_ppid(task, &ns_pid, &ns_ppid, &pid_ns_id); 69 | 70 | // Wrap this message as it has two many arguments for older kernels (invalid 71 | // func unknown#177) 72 | //if (LINUX_KERNEL_VERSION > KERNEL_VERSION(6, 8, 0)) { 73 | // bpf_printk("[%d vs %d] incoming %pI4:%d", conf->proxy_pid, ns_pid, 74 | // &destination, dst_port); 75 | //} else { 76 | bpf_printk("[%d vs %d] incoming %pI4", conf->proxy_pid, ns_pid, 77 | &destination); 78 | //} 79 | 80 | if (ns_pid == conf->proxy_pid) 81 | return 1; 82 | 83 | if (dst_port == 8080 || dst_port == 8181) { 84 | // kubelet readiness probes (eyeroll) 85 | return 1; 86 | } 87 | 88 | if (dst_port == 18000 || dst_port == 18001) { 89 | bpf_printk("Ignoring cluster to cluster"); 90 | return 1; 91 | } 92 | // Unique identifier for the destination socket 93 | __u64 cookie = bpf_get_socket_cookie(ctx); 94 | 95 | // Store destination socket under cookie key 96 | struct Socket sock; 97 | __builtin_memset(&sock, 0, sizeof(sock)); 98 | sock.dst_addr = dst_addr; 99 | sock.dst_port = dst_port; 100 | bpf_map_update_elem(&map_socks, &cookie, &sock, 0); 101 | 102 | // Redirect the connection to the proxy 103 | ctx->user_ip4 = bpf_htonl(conf->proxy_addr); 104 | // ctx->user_ip4 = bpf_htonl(0x7f000001); // 127.0.0.1 == proxy 105 | // IP 106 | ctx->user_port = bpf_htonl(conf->proxy_port << 16); // Proxy port 107 | 108 | __u32 source = ctx->user_ip4; 109 | // if (LINUX_KERNEL_VERSION > KERNEL_VERSION(6, 8, 0)) { 110 | 111 | // bpf_printk("New Connect() [%d] %pI4:%d to %pI4:%d", cookie, &destination, 112 | // dst_port, &source, bpf_ntohs(ctx->user_port)); 113 | // } else { 114 | bpf_printk("New Connect() %pI4 to %pI4:%d", &destination, &source, 115 | bpf_ntohs(ctx->user_port)); 116 | //} 117 | return 1; 118 | } 119 | 120 | // This program is called whenever there's a socket operation on a particular 121 | // cgroup (retransmit timeout, connection establishment, etc.) This is just to 122 | // record client source address and port after succesful connection 123 | // establishment to the proxy 124 | SEC("sockops") 125 | int cg_sock_ops(struct bpf_sock_ops *ctx) { 126 | // Only forward on IPv4 connections 127 | if (ctx->family != AF_INET) 128 | return 0; 129 | 130 | // Active socket with an established connection 131 | if (ctx->op == BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB) { 132 | __u64 cookie = bpf_get_socket_cookie(ctx); 133 | 134 | // Lookup the socket in the map for the corresponding cookie 135 | // In case the socket is present, store the source port and socket mapping 136 | struct Socket *sock = bpf_map_lookup_elem(&map_socks, &cookie); 137 | if (sock) { 138 | __u16 src_port = ctx->local_port; 139 | bpf_map_update_elem(&map_ports, &src_port, &cookie, 0); 140 | } 141 | __u32 destination = ctx->local_ip4; 142 | 143 | // bpf_printk("sockops hook successful %pI4:%d", &destination, 144 | // ctx->local_port); 145 | } 146 | 147 | return 0; 148 | } 149 | 150 | // This is triggered when the proxy queries the original destination 151 | // information through getsockopt SO_ORIGINAL_DST. This program uses the 152 | // source port of the client to retrieve the socket's cookie from map_ports, 153 | // and then from map_socks to get the original destination information, then 154 | // establishes a connection with the original target and forwards the client's 155 | // request. 156 | SEC("cgroup/getsockopt") 157 | int cg_sock_opt(struct bpf_sockopt *ctx) { 158 | // The SO_ORIGINAL_DST socket option is a specialized option used primarily 159 | // in the context of network address translation (NAT) and transparent 160 | // proxying. In a typical NAT or transparent proxy setup, incoming packets 161 | // are redirected from their original destination to a proxy server. The 162 | // proxy server, upon receiving the packets, often needs to know the 163 | // original destination address in order to handle the traffic 164 | // appropriately. This is where SO_ORIGINAL_DST comes into play. 165 | if (ctx->optname != 80) 166 | return 1; 167 | // Only forward IPv4 TCP connections 168 | if (ctx->sk->family != AF_INET) 169 | return 1; 170 | if (ctx->sk->protocol != IPPROTO_TCP) 171 | return 1; 172 | 173 | // Get the clients source port 174 | // It's actually sk->dst_port because getsockopt() syscall with 175 | // SO_ORIGINAL_DST socket option is retrieving the original dst port of the 176 | // client so it's "querying" the destination port of the client 177 | __u16 src_port = bpf_ntohs(ctx->sk->dst_port); 178 | 179 | // Retrieve the socket cookie using the clients' src_port 180 | __u64 *cookie = bpf_map_lookup_elem(&map_ports, &src_port); 181 | if (!cookie) 182 | return 1; 183 | 184 | // Using the cookie (socket identifier), retrieve the original socket 185 | // (client connect to destination) from map_socks 186 | struct Socket *sock = bpf_map_lookup_elem(&map_socks, cookie); 187 | if (!sock) 188 | return 1; 189 | 190 | struct sockaddr_in *sa = ctx->optval; 191 | if ((void *)(sa + 1) > ctx->optval_end) 192 | return 1; 193 | 194 | // Establish a connection with the original destination target 195 | ctx->optlen = sizeof(*sa); 196 | sa->sin_family = ctx->sk->family; // Address Family 197 | sa->sin_addr.s_addr = bpf_htonl(sock->dst_addr); // Destination Address 198 | sa->sin_port = bpf_htons(sock->dst_port); // Destination Port 199 | ctx->retval = 0; 200 | __u32 address = sa->sin_addr.s_addr; 201 | 202 | if (LINUX_KERNEL_VERSION > KERNEL_VERSION(6, 8, 0)) { 203 | bpf_printk("Redirecting %pI4:%d %d", &address, bpf_ntohs(sa->sin_port), 204 | ctx->sk->src_port); 205 | } 206 | return 1; 207 | } 208 | 209 | char __LICENSE[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /controller/cmd/webhook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/gookit/slog" 11 | admissionv1 "k8s.io/api/admission/v1" 12 | corev1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | "k8s.io/apimachinery/pkg/runtime/serializer" 16 | ) 17 | 18 | var ( 19 | runtimeScheme = runtime.NewScheme() 20 | codecs = serializer.NewCodecFactory(runtimeScheme) 21 | deserializer = codecs.UniversalDeserializer() 22 | ) 23 | 24 | var ignoredNamespaces = []string{ 25 | metav1.NamespaceSystem, 26 | metav1.NamespacePublic, 27 | } 28 | 29 | const ( 30 | admissionWebhookAnnotationInjectKey = "sidecar-injector-webhook.thebsdbox.co.uk/inject" 31 | admissionWebhookAnnotationStatusKey = "sidecar-injector-webhook.thebsdbox.co.uk/status" 32 | ) 33 | 34 | type WebhookServer struct { 35 | server *http.Server 36 | } 37 | 38 | // Webhook Server parameters 39 | // type WhSvrParameters struct { 40 | // port int // webhook server port 41 | // certFile string // path to the x509 certificate for https 42 | // keyFile string // path to the x509 private key matching `CertFile` 43 | // sidecarCfgFile string // path to sidecar injector configuration file 44 | // } 45 | 46 | type patchOperation struct { 47 | Op string `json:"op"` 48 | Path string `json:"path"` 49 | Value interface{} `json:"value,omitempty"` 50 | } 51 | 52 | // Check whether the target resoured need to be mutated 53 | func mutationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool { 54 | // skip special kubernete system namespaces 55 | for _, namespace := range ignoredList { 56 | if metadata.Namespace == namespace { 57 | slog.Printf("Skip mutation for %v for it's in special namespace:%v", metadata.Name, metadata.Namespace) 58 | return false 59 | } 60 | } 61 | 62 | annotations := metadata.GetAnnotations() 63 | if annotations == nil { 64 | annotations = map[string]string{} 65 | } 66 | 67 | status := annotations[admissionWebhookAnnotationStatusKey] 68 | 69 | // determine whether to perform mutation based on annotation for the target resource 70 | var required bool 71 | if strings.ToLower(status) == "injected" { 72 | required = false 73 | } else { 74 | switch strings.ToLower(annotations[admissionWebhookAnnotationInjectKey]) { 75 | default: 76 | required = true 77 | case "n", "not", "false", "off": 78 | required = false 79 | } 80 | } 81 | 82 | slog.Printf("Mutation policy for %v/%v: status: %q required:%v", metadata.Namespace, metadata.Name, status, required) 83 | return required 84 | } 85 | 86 | func addInitContainer(target []corev1.Container, add corev1.Container, basePath string) (patch []patchOperation) { 87 | first := len(target) == 0 88 | var value interface{} 89 | value = add 90 | path := basePath 91 | if first { 92 | first = false 93 | value = []corev1.Container{add} 94 | } else { 95 | path = path + "/-" 96 | } 97 | patch = append(patch, patchOperation{ 98 | Op: "add", 99 | Path: path, 100 | Value: value, 101 | }) 102 | return patch 103 | } 104 | 105 | func updateAnnotation(target map[string]string, added map[string]string) (patch []patchOperation) { 106 | for key, value := range added { 107 | if target == nil || target[key] == "" { 108 | target = map[string]string{} 109 | patch = append(patch, patchOperation{ 110 | Op: "add", 111 | Path: "/metadata/annotations", 112 | Value: map[string]string{ 113 | key: value, 114 | }, 115 | }) 116 | } else { 117 | patch = append(patch, patchOperation{ 118 | Op: "replace", 119 | Path: "/metadata/annotations/" + key, 120 | Value: value, 121 | }) 122 | } 123 | } 124 | return patch 125 | } 126 | 127 | // create mutation patch for resoures 128 | func createPatch(pod *corev1.Pod, annotations map[string]string) ([]byte, error) { 129 | var patch []patchOperation 130 | // Add our init container 131 | patch = append(patch, addInitContainer(pod.Spec.InitContainers, *smeshproxy(pod.Name), "/spec/initContainers")...) 132 | // Stick some annotations on (TODO) 133 | patch = append(patch, updateAnnotation(pod.Annotations, annotations)...) 134 | // Enable shared namespace 135 | patch = append(patch, patchOperation{Op: "replace", 136 | Path: "/spec/shareProcessNamespace", 137 | Value: true}) 138 | return json.Marshal(patch) 139 | } 140 | 141 | // main mutation process 142 | func (whsvr *WebhookServer) mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse { 143 | req := ar.Request 144 | var pod corev1.Pod 145 | if err := json.Unmarshal(req.Object.Raw, &pod); err != nil { 146 | slog.Warnf("Could not unmarshal raw object: %v", err) 147 | return &admissionv1.AdmissionResponse{ 148 | Result: &metav1.Status{ 149 | Message: err.Error(), 150 | }, 151 | } 152 | } 153 | 154 | admRequest, _ := json.Marshal(ar.Request) 155 | 156 | fmt.Println("--------------------------------------") 157 | fmt.Printf("AdmissionReview Request: \n") 158 | fmt.Println("--------------------------------------") 159 | 160 | fmt.Println(string(admRequest)) 161 | 162 | slog.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v", 163 | req.Kind, req.Namespace, req.Name, pod.Name, req.UID, req.Operation, req.UserInfo) 164 | 165 | // determine whether to perform mutation 166 | if !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) { 167 | slog.Printf("Skipping mutation for %s/%s due to policy check", pod.Namespace, pod.Name) 168 | return &admissionv1.AdmissionResponse{ 169 | Allowed: true, 170 | } 171 | } 172 | 173 | annotations := map[string]string{admissionWebhookAnnotationStatusKey: "injected"} 174 | patchBytes, err := createPatch(&pod, annotations) 175 | if err != nil { 176 | return &admissionv1.AdmissionResponse{ 177 | Result: &metav1.Status{ 178 | Message: err.Error(), 179 | }, 180 | } 181 | } 182 | 183 | fmt.Println("--------------------------------------") 184 | fmt.Printf("PatchBytes: \n") 185 | fmt.Println("--------------------------------------") 186 | fmt.Println(string(patchBytes)) 187 | 188 | slog.Printf("AdmissionResponse: patch=%v\n", string(patchBytes)) 189 | return &admissionv1.AdmissionResponse{ 190 | Allowed: true, 191 | Patch: patchBytes, 192 | PatchType: func() *admissionv1.PatchType { 193 | pt := admissionv1.PatchTypeJSONPatch 194 | return &pt 195 | }(), 196 | } 197 | } 198 | 199 | // Serve method for webhook server 200 | func (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { 201 | var body []byte 202 | if r.Body != nil { 203 | if data, err := io.ReadAll(r.Body); err == nil { 204 | body = data 205 | } 206 | } 207 | if len(body) == 0 { 208 | slog.Warnf("empty body") 209 | http.Error(w, "empty body", http.StatusBadRequest) 210 | return 211 | } 212 | 213 | // verify the content type is accurate 214 | contentType := r.Header.Get("Content-Type") 215 | if contentType != "application/json" { 216 | slog.Warnf("Content-Type=%s, expect application/json", contentType) 217 | http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType) 218 | return 219 | } 220 | 221 | fmt.Println("--------------------------------------") 222 | fmt.Printf("Request Body sent from the API Server: \n") 223 | fmt.Println("--------------------------------------") 224 | fmt.Println(string(body)) 225 | 226 | var admissionResponse *admissionv1.AdmissionResponse 227 | ar := admissionv1.AdmissionReview{} 228 | if _, _, err := deserializer.Decode(body, nil, &ar); err != nil { 229 | slog.Warnf("Can't decode body: %v", err) 230 | admissionResponse = &admissionv1.AdmissionResponse{ 231 | Result: &metav1.Status{ 232 | Message: err.Error(), 233 | }, 234 | } 235 | } else { 236 | admissionResponse = whsvr.mutate(&ar) 237 | } 238 | 239 | admResponse, _ := json.Marshal(admissionResponse) 240 | 241 | fmt.Println("--------------------------------------") 242 | fmt.Printf("AdmissionResponse from WebHook Server: \n") 243 | fmt.Println("--------------------------------------") 244 | fmt.Println(string(admResponse)) 245 | 246 | admissionReview := admissionv1.AdmissionReview{ 247 | TypeMeta: metav1.TypeMeta{ 248 | APIVersion: "admission.k8s.io/v1", 249 | Kind: "AdmissionReview", 250 | }, 251 | } 252 | if admissionResponse != nil { 253 | admissionReview.Response = admissionResponse 254 | if ar.Request != nil { 255 | admissionReview.Response.UID = ar.Request.UID 256 | } 257 | } 258 | 259 | resp, err := json.Marshal(admissionReview) 260 | if err != nil { 261 | slog.Warnf("Can't encode response: %v", err) 262 | http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError) 263 | } 264 | slog.Printf("Ready to write reponse ...") 265 | if _, err := w.Write(resp); err != nil { 266 | slog.Warnf("Can't write response: %v", err) 267 | http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /pkg/connection/connection.go: -------------------------------------------------------------------------------- 1 | package connection 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "os" 12 | "time" 13 | 14 | "github.com/cilium/ebpf" 15 | "github.com/gookit/slog" 16 | ) 17 | 18 | type Config struct { 19 | ProxyPort int 20 | ClusterPort int 21 | ClusterTLSPort int 22 | Address string 23 | ClusterAddress string // For Debug purposes 24 | CgroupOverride string // For Debug purposes 25 | 26 | PodCIDR string 27 | Certificates *Certs 28 | 29 | Socks *ebpf.Map 30 | 31 | Proxy bool 32 | ProxyFunc func(string) string 33 | } 34 | 35 | func (c *Config) StartInternalListener() net.Listener { 36 | proxyAddr := fmt.Sprintf("%s:%d", c.Address, c.ProxyPort) 37 | listener, err := net.Listen("tcp", proxyAddr) 38 | if err != nil { 39 | slog.Fatalf("Failed to start proxy server: %v", err) 40 | } 41 | slog.Infof("[pid: %d] %s", os.Getpid(), proxyAddr) 42 | return listener 43 | } 44 | 45 | func (c *Config) StartExternalListener() net.Listener { 46 | proxyAddr := fmt.Sprintf("0.0.0.0:%d", c.ClusterPort) 47 | listener, err := net.Listen("tcp", proxyAddr) 48 | if err != nil { 49 | slog.Fatalf("Failed to start proxy server: %v", err) 50 | } 51 | slog.Infof("[pid: %d] %s", os.Getpid(), proxyAddr) 52 | return listener 53 | } 54 | 55 | func (c *Config) StartExternalTLSListener() net.Listener { 56 | proxyAddr := fmt.Sprintf("0.0.0.0:%d", c.ClusterTLSPort) 57 | 58 | caCertPool := x509.NewCertPool() 59 | if !caCertPool.AppendCertsFromPEM(c.Certificates.ca) { 60 | log.Fatalf("could not append CA") 61 | } 62 | certificate, err := tls.X509KeyPair(c.Certificates.cert, c.Certificates.key) 63 | 64 | if err != nil { 65 | log.Fatalf("could not load certificate: %v", err) 66 | } 67 | 68 | config := &tls.Config{ 69 | ClientCAs: caCertPool, 70 | Certificates: []tls.Certificate{certificate}, 71 | ClientAuth: tls.VerifyClientCertIfGiven, 72 | } //<-- this is the key 73 | 74 | listener, err := tls.Listen("tcp", proxyAddr, config) 75 | 76 | // listener, err := net.Listen("tcp", proxyAddr) 77 | if err != nil { 78 | slog.Fatalf("Failed to start proxy server: %v", err) 79 | } 80 | slog.Infof("[pid: %d] %s", os.Getpid(), proxyAddr) 81 | return listener 82 | } 83 | 84 | // Blocking function 85 | func (c *Config) StartListeners(listener net.Listener, internal bool) { 86 | for { 87 | conn, err := listener.Accept() 88 | if err != nil && !errors.Is(err, net.ErrClosed) { 89 | slog.Printf("Failed to accept connection: %v", err) 90 | continue 91 | } else { 92 | if conn != nil { 93 | if internal { 94 | slog.Printf("internal %s -> %s", conn.RemoteAddr().String(), conn.LocalAddr().String()) 95 | go c.internalProxy(conn) 96 | } else { 97 | slog.Printf("external %s -> %s", conn.RemoteAddr().String(), conn.LocalAddr().String()) 98 | go c.handleExternalConnection(conn) 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | // Blocking function 106 | func (c *Config) StartTLSListener(listener net.Listener) { 107 | for { 108 | conn, err := listener.Accept() 109 | if err != nil { 110 | if !errors.Is(err, net.ErrClosed) { // Don't print closing connection error, just continue to the next 111 | slog.Printf("Failed to accept connection: %v", err) 112 | } 113 | continue 114 | } 115 | 116 | slog.Printf(" %s -> %s", conn.RemoteAddr().String(), conn.LocalAddr().String()) 117 | go c.handleTLSExternalConnection(conn) 118 | 119 | } 120 | } 121 | 122 | // HTTP proxy request handler 123 | func (c *Config) internalProxy(conn net.Conn) { 124 | defer conn.Close() 125 | // Get original destination address 126 | destAddr, destPort, err := c.findTargetFromConnection(conn) 127 | if err != nil { 128 | return 129 | } 130 | targetDestination := fmt.Sprintf("%s:%d", destAddr, destPort) 131 | var targetConn net.Conn 132 | var endpoint string 133 | // Send traffic to endpoint gateway 134 | if c.Certificates != nil { 135 | 136 | caCertPool := x509.NewCertPool() 137 | if !caCertPool.AppendCertsFromPEM(c.Certificates.ca) { 138 | log.Fatalf("could not append CA") 139 | } 140 | certificate, err := tls.X509KeyPair(c.Certificates.cert, c.Certificates.key) 141 | if err != nil { 142 | log.Fatalf("could not load certificate: %v", err) 143 | } 144 | 145 | config := &tls.Config{ 146 | RootCAs: caCertPool, 147 | Certificates: []tls.Certificate{certificate}, 148 | ClientAuth: tls.VerifyClientCertIfGiven, 149 | } //<-- this is the key 150 | 151 | endpoint = fmt.Sprintf("%s:%d", destAddr, c.ClusterTLSPort) 152 | if c.ClusterAddress != "" { 153 | endpoint = fmt.Sprintf("%s:%d", c.ClusterAddress, c.ClusterPort) 154 | } 155 | if c.Proxy { 156 | endpoint = c.ProxyFunc(destAddr) 157 | } 158 | 159 | // Set a timeout, mainly because connections can occur to pods that aren't ready 160 | d := net.Dialer{Timeout: time.Second * 3} 161 | targetConn, err = tls.DialWithDialer(&d, "tcp", endpoint, config) 162 | if err != nil { 163 | slog.Printf("Failed to connect to destination TLS proxy: %v", err) 164 | return 165 | } 166 | } else { 167 | endpoint = fmt.Sprintf("%s:%d", destAddr, c.ClusterPort) 168 | if c.ClusterAddress != "" { 169 | endpoint = fmt.Sprintf("%s:%d", c.ClusterAddress, c.ClusterPort) 170 | } 171 | // Check that the original destination address is reachable from the proxy 172 | targetConn, err = net.DialTimeout("tcp", endpoint, 5*time.Second) 173 | if err != nil { 174 | slog.Printf("Failed to connect to original destination: %v", err) 175 | return 176 | } 177 | } 178 | defer targetConn.Close() 179 | 180 | slog.Printf("connect to proxy %s, original %s", endpoint, targetDestination) 181 | //log.Printf("Internal proxy sending original destination: %s\n", targetDestination) 182 | _, err = targetConn.Write([]byte(targetDestination)) 183 | if err != nil { 184 | slog.Printf("Failed to send original destination: %v", err) 185 | } 186 | 187 | tmp := make([]byte, 256) 188 | 189 | // Ideally we wait here until our remote endpoint has recieved the targetDestination 190 | targetConn.Read(tmp) 191 | 192 | //log.Printf("Internal connection from %s to %s\n", conn.RemoteAddr(), targetConn.RemoteAddr()) 193 | 194 | // The following code creates two data transfer channels: 195 | // - From the client to the target server (handled by a separate goroutine). 196 | // - From the target server to the client (handled by the main goroutine). 197 | go func() { 198 | _, err = io.Copy(targetConn, conn) 199 | if err != nil { 200 | slog.Printf("Failed copying data to target: %v", err) 201 | } 202 | }() 203 | _, err = io.Copy(conn, targetConn) 204 | if err != nil { 205 | slog.Printf("Failed copying data from target: %v", err) 206 | } 207 | } 208 | 209 | // Unencrypted external connection 210 | func (c *Config) handleExternalConnection(conn net.Conn) { 211 | defer conn.Close() 212 | 213 | tmp := make([]byte, 256) 214 | n, err := conn.Read(tmp) 215 | if err != nil { 216 | slog.Print(err) 217 | } 218 | remoteAddress := string(tmp[:n]) 219 | 220 | if remoteAddress == fmt.Sprintf("%s:%d", c.Address, c.ProxyPort) { 221 | slog.Printf("Potential loopback") 222 | return 223 | } 224 | 225 | // Check that the original destination address is reachable from the proxy 226 | targetConn, err := net.DialTimeout("tcp", remoteAddress, 5*time.Second) 227 | if err != nil { 228 | slog.Printf("Failed to connect to original destination[%s]: %v", string(tmp), err) 229 | return 230 | } 231 | defer targetConn.Close() 232 | conn.Write([]byte{'Y'}) // Send a response to kickstart the comms 233 | 234 | slog.Printf("%s -> %s", conn.RemoteAddr(), targetConn.RemoteAddr()) 235 | 236 | // The following code creates two data transfer channels: 237 | // - From the client to the target server (handled by a separate goroutine). 238 | // - From the target server to the client (handled by the main goroutine). 239 | go func() { 240 | _, err = io.Copy(targetConn, conn) 241 | if err != nil { 242 | slog.Printf("Failed copying data to target: %v", err) 243 | } 244 | }() 245 | _, err = io.Copy(conn, targetConn) 246 | if err != nil { 247 | slog.Printf("Failed copying data from target: %v", err) 248 | } 249 | } 250 | 251 | // Unencrypted external connection 252 | func (c *Config) handleTLSExternalConnection(conn net.Conn) { 253 | defer conn.Close() 254 | var tConn *tls.Conn = conn.(*tls.Conn) 255 | 256 | tmp := make([]byte, 256) 257 | n, err := tConn.Read(tmp) 258 | if err != nil { 259 | slog.Print(err) 260 | } 261 | 262 | remoteAddress := string(tmp[:n]) 263 | 264 | if remoteAddress == fmt.Sprintf("%s:%d", c.Address, c.ProxyPort) { 265 | slog.Printf("Potential loopback") 266 | return 267 | } 268 | 269 | // Check that the original destination address is reachable from the proxy 270 | targetConn, err := net.DialTimeout("tcp", remoteAddress, 5*time.Second) 271 | //targetConn, err := tls.Dial("tcp", remoteAddress, config) 272 | if err != nil { 273 | slog.Printf("Failed to connect to original destination[%s]: %v", string(tmp), err) 274 | return 275 | } 276 | defer targetConn.Close() 277 | tConn.Write([]byte{'Y'}) // Send a response to kickstart the comms 278 | 279 | slog.Printf("%s -> %s", conn.RemoteAddr(), targetConn.RemoteAddr()) 280 | 281 | // The following code creates two data transfer channels: 282 | // - From the client to the target server (handled by a separate goroutine). 283 | // - From the target server to the client (handled by the main goroutine). 284 | go func() { 285 | _, err = io.Copy(targetConn, tConn) 286 | if err != nil { 287 | slog.Printf("Failed copying data to target: %v", err) 288 | } 289 | }() 290 | _, err = io.Copy(tConn, targetConn) 291 | if err != nil { 292 | slog.Printf("Failed copying data from target: %v", err) 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /watcher/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 7 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 8 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 9 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 10 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 11 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 12 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 13 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 14 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 15 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 16 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 17 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 18 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 19 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 20 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 21 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 22 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 23 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 24 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 25 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 26 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 27 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 28 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 29 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 32 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 33 | github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= 34 | github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 35 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 36 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 37 | github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= 38 | github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= 39 | github.com/gookit/goutil v0.6.17 h1:SxmbDz2sn2V+O+xJjJhJT/sq1/kQh6rCJ7vLBiRPZjI= 40 | github.com/gookit/goutil v0.6.17/go.mod h1:rSw1LchE1I3TDWITZvefoAC9tS09SFu3lHXLCV7EaEY= 41 | github.com/gookit/gsr v0.1.0 h1:0gadWaYGU4phMs0bma38t+Do5OZowRMEVlHv31p0Zig= 42 | github.com/gookit/gsr v0.1.0/go.mod h1:7wv4Y4WCnil8+DlDYHBjidzrEzfHhXEoFjEA0pPPWpI= 43 | github.com/gookit/slog v0.5.7 h1:n3Dhgmr3NP+KppkNg95+vpFcI4YD1csu9VTQwgcEYTs= 44 | github.com/gookit/slog v0.5.7/go.mod h1:uWCRB4YO+FmgwXEq3s8U7ob1wWP7RStOuY/2a4yC/8o= 45 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 46 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 47 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 48 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 49 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 50 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 51 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 52 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 53 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 54 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 55 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 57 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 58 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 59 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 60 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 61 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 62 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 63 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 64 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 65 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 66 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 67 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 68 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 69 | github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= 70 | github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= 71 | github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= 72 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 74 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 75 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 76 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 77 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 78 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 79 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 80 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 81 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 82 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 83 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 84 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 85 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 86 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 87 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 88 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 89 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 90 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 91 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 92 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 93 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 94 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 95 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 96 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 97 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 98 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 99 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 100 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 101 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 102 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 103 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 104 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 105 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 106 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 107 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 108 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 109 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 110 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 111 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 112 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 113 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 114 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 115 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 116 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 117 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 118 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 119 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 120 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 121 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 122 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 123 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 124 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 125 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 126 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 127 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 128 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 129 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 130 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 131 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 132 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 133 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 134 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 135 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 136 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 137 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 138 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 139 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 140 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 141 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 142 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 144 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 145 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 146 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 147 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 148 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 149 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 150 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 151 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 152 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 153 | k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= 154 | k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= 155 | k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= 156 | k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 157 | k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= 158 | k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= 159 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 160 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 161 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= 162 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 163 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= 164 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 165 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 166 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 167 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 168 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 169 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 170 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 171 | -------------------------------------------------------------------------------- /controller/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 7 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 8 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 9 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 10 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 11 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 12 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 13 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 14 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 15 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 16 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 17 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 18 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 19 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 20 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 21 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 22 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 23 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 24 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 25 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 26 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 27 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 28 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 29 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 32 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 33 | github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= 34 | github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 35 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 36 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 37 | github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= 38 | github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= 39 | github.com/gookit/goutil v0.6.17 h1:SxmbDz2sn2V+O+xJjJhJT/sq1/kQh6rCJ7vLBiRPZjI= 40 | github.com/gookit/goutil v0.6.17/go.mod h1:rSw1LchE1I3TDWITZvefoAC9tS09SFu3lHXLCV7EaEY= 41 | github.com/gookit/gsr v0.1.0 h1:0gadWaYGU4phMs0bma38t+Do5OZowRMEVlHv31p0Zig= 42 | github.com/gookit/gsr v0.1.0/go.mod h1:7wv4Y4WCnil8+DlDYHBjidzrEzfHhXEoFjEA0pPPWpI= 43 | github.com/gookit/slog v0.5.7 h1:n3Dhgmr3NP+KppkNg95+vpFcI4YD1csu9VTQwgcEYTs= 44 | github.com/gookit/slog v0.5.7/go.mod h1:uWCRB4YO+FmgwXEq3s8U7ob1wWP7RStOuY/2a4yC/8o= 45 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 46 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 47 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 48 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 49 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 50 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 51 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 52 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 53 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 54 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 55 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 57 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 58 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 59 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 60 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 61 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 62 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 63 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 64 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 65 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 66 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 67 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 68 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 69 | github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= 70 | github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= 71 | github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= 72 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 74 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 75 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 76 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 77 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 78 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 79 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 80 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 81 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 82 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 83 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 84 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 85 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 86 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 87 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 88 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 89 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 90 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 91 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 92 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 93 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 94 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 95 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 96 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 97 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 98 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 99 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 100 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 101 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 102 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 103 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 104 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 105 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 106 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 107 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 108 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 109 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 110 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 111 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 112 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 113 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 114 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 115 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 116 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 117 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 118 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 119 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 120 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 121 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 122 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 123 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 124 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 125 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 126 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 127 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 128 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 129 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 130 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 131 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 132 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 133 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 134 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 135 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 136 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 137 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 138 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 139 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 140 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 141 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 142 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 144 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 145 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 146 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 147 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 148 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 149 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 150 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 151 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 152 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 153 | k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= 154 | k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= 155 | k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= 156 | k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 157 | k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= 158 | k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= 159 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 160 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 161 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= 162 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 163 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= 164 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 165 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 166 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 167 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 168 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 169 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 170 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 171 | -------------------------------------------------------------------------------- /watcher/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "flag" 12 | "fmt" 13 | "math/big" 14 | "net" 15 | "os" 16 | "os/signal" 17 | "os/user" 18 | "syscall" 19 | "time" 20 | 21 | v1 "k8s.io/api/core/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | 24 | "k8s.io/client-go/informers" 25 | "k8s.io/client-go/kubernetes" 26 | "k8s.io/client-go/rest" 27 | "k8s.io/client-go/tools/clientcmd" 28 | 29 | "github.com/gookit/slog" 30 | ) 31 | 32 | type certs struct { 33 | cacert []byte 34 | cakey []byte 35 | key []byte 36 | cert []byte 37 | folder *string 38 | } 39 | 40 | func main() { 41 | var kubeconfig *string 42 | u, err := user.Current() 43 | if err != nil { 44 | if u != nil { 45 | kubeconfig = flag.String("kubeconfig", u.HomeDir+"/.kube/config", "Path to Kubernetes config") 46 | } 47 | } 48 | var certCollection certs 49 | 50 | slog.Info("Starting Certicate creation 🔏") 51 | ca := flag.Bool("ca", false, "Create a CA") 52 | certName := flag.String("cert", "", "Create a certificate from the CA") 53 | certCollection.folder = flag.String("certFolder", "", "Create a certificate from the CA") 54 | 55 | certIP := flag.String("ip", "192.168.0.1", "Create a certificate from the CA") 56 | certSecret := flag.Bool("load", false, "Create a secret in Kubernetes with the certificate") 57 | loadCA := flag.Bool("loadca", false, "Create a secret in Kubernetes with the certificate") 58 | watch := flag.Bool("watch", false, "Watch Kubernetes for pods being created and create certs") 59 | flag.Parse() 60 | 61 | if *ca { 62 | err := certCollection.generateCA() 63 | if err != nil { 64 | panic(err) 65 | } 66 | err = certCollection.writeCACert() 67 | if err != nil { 68 | panic(err) 69 | } 70 | err = certCollection.writeCAKey() 71 | if err != nil { 72 | panic(err) 73 | } 74 | } 75 | if *loadCA { 76 | err := certCollection.readCACert() 77 | if err != nil { 78 | slog.PanicErr(err) 79 | } 80 | err = certCollection.readCAKey() 81 | if err != nil { 82 | slog.PanicErr(err) 83 | } 84 | 85 | c, err := client(*kubeconfig) 86 | if err != nil { 87 | slog.PanicErr(err) 88 | } 89 | err = certCollection.loadCA(c) 90 | if err != nil { 91 | slog.PanicErr(err) 92 | } 93 | } 94 | if *certName != "" { 95 | certCollection.createCertificate(*certName, *certIP) 96 | err := certCollection.writeCert(*certName) 97 | if err != nil { 98 | panic(err) 99 | } 100 | err = certCollection.writeKey(*certName) 101 | if err != nil { 102 | panic(err) 103 | } 104 | if *certSecret { 105 | c, err := client(*kubeconfig) 106 | if err != nil { 107 | slog.PanicErr(err) 108 | } 109 | err = certCollection.loadSecret(*certName, c) 110 | if err != nil { 111 | slog.Error("secret", "msg", err) 112 | } 113 | } 114 | } 115 | if *watch { 116 | err := certCollection.getEnvCerts() 117 | if err != nil { 118 | slog.Warnf("Error reading certificates from env vars [%v]", err) 119 | 120 | err := certCollection.readCACert() 121 | if err != nil { 122 | slog.PanicErr(err) 123 | } 124 | err = certCollection.readCAKey() 125 | if err != nil { 126 | slog.PanicErr(err) 127 | } 128 | } 129 | var c *kubernetes.Clientset 130 | if kubeconfig == nil { 131 | c, err = client("") 132 | 133 | } else { 134 | c, err = client(*kubeconfig) 135 | } 136 | if err != nil { 137 | slog.PanicErr(err) 138 | } 139 | certCollection.watcher(c) 140 | } 141 | 142 | } 143 | 144 | func (c *certs) getEnvCerts() (err error) { 145 | envcert, exists := os.LookupEnv("SMESH-CA-CERT") 146 | if !exists { 147 | return fmt.Errorf("unable to find secrets from environment") 148 | } 149 | envkey, exists := os.LookupEnv("SMESH-CA-KEY") 150 | if !exists { 151 | return fmt.Errorf("unable to find secrets from environment") 152 | } 153 | c.cacert = []byte(envcert) 154 | c.cakey = []byte(envkey) 155 | return nil 156 | } 157 | 158 | func (c *certs) readCACert() (err error) { 159 | c.cacert, err = os.ReadFile(*c.folder + "ca.crt") 160 | if err != nil { 161 | return err 162 | } 163 | return nil 164 | } 165 | 166 | func (c *certs) readCAKey() (err error) { 167 | c.cakey, err = os.ReadFile(*c.folder + "ca.key") 168 | if err != nil { 169 | return err 170 | } 171 | return nil 172 | } 173 | 174 | func (c *certs) writeCACert() (err error) { 175 | // Public key 176 | certOut, err := os.Create(*c.folder + "ca.crt") 177 | if err != nil { 178 | slog.Error("create ca failed", err) 179 | return err 180 | } 181 | certOut.Write(c.cacert) 182 | certOut.Close() 183 | slog.Info("written ca.crt") 184 | return nil 185 | } 186 | 187 | func (c *certs) writeCAKey() (err error) { 188 | // Public key 189 | certOut, err := os.Create(*c.folder + "ca.key") 190 | if err != nil { 191 | slog.Error("create ca failed", err) 192 | return err 193 | } 194 | certOut.Write(c.cakey) 195 | certOut.Close() 196 | slog.Info("written ca.key") 197 | return nil 198 | } 199 | 200 | func (c *certs) writeCert(name string) (err error) { 201 | // Public key 202 | certOut, err := os.Create(name + ".crt") 203 | if err != nil { 204 | slog.Error("create ca failed", err) 205 | return err 206 | } 207 | certOut.Write(c.cert) 208 | certOut.Close() 209 | slog.Info("written ca.crt") 210 | return nil 211 | } 212 | 213 | func (c *certs) writeKey(name string) (err error) { 214 | // Public key 215 | certOut, err := os.Create(name + ".key") 216 | if err != nil { 217 | slog.Error("create ca failed", err) 218 | return err 219 | } 220 | certOut.Write(c.key) 221 | certOut.Close() 222 | slog.Info("written ca.key") 223 | return nil 224 | } 225 | 226 | func (c *certs) generateCA() error { 227 | ca := &x509.Certificate{ 228 | SerialNumber: big.NewInt(1653), 229 | Subject: pkix.Name{ 230 | Organization: []string{"ORGANIZATION_NAME"}, 231 | Country: []string{"COUNTRY_CODE"}, 232 | Province: []string{"PROVINCE"}, 233 | Locality: []string{"CITY"}, 234 | StreetAddress: []string{"ADDRESS"}, 235 | PostalCode: []string{"POSTAL_CODE"}, 236 | CommonName: "42CA", 237 | }, 238 | NotBefore: time.Now(), 239 | NotAfter: time.Now().AddDate(10, 0, 0), 240 | IsCA: true, 241 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 242 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 243 | BasicConstraintsValid: true, 244 | } 245 | 246 | priv, _ := rsa.GenerateKey(rand.Reader, 2048) 247 | pub := &priv.PublicKey 248 | ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv) 249 | if err != nil { 250 | slog.Error("create ca failed") 251 | return err 252 | } 253 | 254 | // Public key 255 | // certOut, err := os.Create("ca.crt") 256 | // if err != nil { 257 | // slog.Error("create ca failed", err) 258 | // return err 259 | // } 260 | c.cacert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca_b}) 261 | //pem.Encode(certOut, ) 262 | //certOut.Close() 263 | //slog.Info("written ca.crt") 264 | 265 | // Private key 266 | // keyOut, err := os.OpenFile("ca.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 267 | // if err != nil { 268 | // slog.Error("create ca failed", err) 269 | // return err 270 | // } 271 | c.cakey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 272 | //pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 273 | // keyOut.Close() 274 | // slog.Info("written ca.key") 275 | return nil 276 | } 277 | 278 | func (c *certs) createCertificate(name, ip string) { 279 | // Load CA 280 | tls.X509KeyPair(c.cacert, c.cakey) 281 | catls, err := tls.X509KeyPair(c.cacert, c.cakey) 282 | if err != nil { 283 | panic(err) 284 | } 285 | ca, err := x509.ParseCertificate(catls.Certificate[0]) 286 | if err != nil { 287 | panic(err) 288 | } 289 | ipAddress := net.ParseIP(ip) 290 | // Prepare certificate 291 | cert := &x509.Certificate{ 292 | SerialNumber: big.NewInt(1658), 293 | Subject: pkix.Name{ 294 | Organization: []string{"ORGANIZATION_NAME"}, 295 | Country: []string{"COUNTRY_CODE"}, 296 | Province: []string{"PROVINCE"}, 297 | Locality: []string{"CITY"}, 298 | StreetAddress: []string{"ADDRESS"}, 299 | PostalCode: []string{"POSTAL_CODE"}, 300 | CommonName: "TEST", 301 | }, 302 | NotBefore: time.Now(), 303 | NotAfter: time.Now().AddDate(10, 0, 0), 304 | SubjectKeyId: []byte{1, 2, 3, 4, 6}, 305 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 306 | KeyUsage: x509.KeyUsageDigitalSignature, 307 | DNSNames: []string{name}, 308 | IPAddresses: []net.IP{ipAddress}, 309 | } 310 | priv, _ := rsa.GenerateKey(rand.Reader, 2048) 311 | pub := &priv.PublicKey 312 | 313 | // Sign the certificate 314 | cert_b, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, catls.PrivateKey) 315 | if err != nil { 316 | panic(err) 317 | } 318 | // Public key 319 | // certOut, err := os.Create(certificate) 320 | // if err != nil { 321 | // panic(err) 322 | // } 323 | c.cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert_b}) 324 | // pem.Encode(certOut) 325 | // certOut.Close() 326 | // slog.Info(fmt.Sprintf("Written %s", certificate)) 327 | c.key = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 328 | // Private key 329 | // keyOut, err := os.OpenFile(key, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 330 | // if err != nil { 331 | // panic(err) 332 | // } 333 | // pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 334 | // keyOut.Close() 335 | // slog.Info(fmt.Sprintf("Written %s", key)) 336 | 337 | } 338 | 339 | func client(kubeconfigPath string) (*kubernetes.Clientset, error) { 340 | var kubeconfig *rest.Config 341 | 342 | if kubeconfigPath != "" { 343 | config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) 344 | if err != nil { 345 | return nil, fmt.Errorf("unable to load kubeconfig from %s: %v", kubeconfigPath, err) 346 | } 347 | kubeconfig = config 348 | } else { 349 | config, err := rest.InClusterConfig() 350 | if err != nil { 351 | return nil, fmt.Errorf("unable to load in-cluster config: %v", err) 352 | } 353 | kubeconfig = config 354 | } 355 | 356 | // build the client set 357 | clientSet, err := kubernetes.NewForConfig(kubeconfig) 358 | if err != nil { 359 | return nil, fmt.Errorf("creating the kubernetes client set - %s", err) 360 | } 361 | return clientSet, nil 362 | } 363 | 364 | func (c *certs) loadSecret(name string, clientSet *kubernetes.Clientset) error { 365 | 366 | // certificate := fmt.Sprint(name + ".crt") 367 | // key := fmt.Sprint(name + ".key") 368 | // certData, err := os.ReadFile(certificate) 369 | // if err != nil { 370 | // return fmt.Errorf("unable to read certificate %v", err) 371 | // } 372 | // keyData, err := os.ReadFile(key) 373 | // if err != nil { 374 | // return fmt.Errorf("unable to read key %v", err) 375 | // } 376 | // caData, err := os.ReadFile("ca.crt") 377 | // if err != nil { 378 | // return fmt.Errorf("unable to read ca %v", err) 379 | // } 380 | 381 | secretMap := make(map[string][]byte) 382 | 383 | secretMap["ca"] = c.cacert 384 | secretMap["cert"] = c.cert 385 | secretMap["key"] = c.key 386 | 387 | secret := v1.Secret{ 388 | TypeMeta: metav1.TypeMeta{ 389 | Kind: "Secret", 390 | APIVersion: "v1", 391 | }, 392 | ObjectMeta: metav1.ObjectMeta{ 393 | Name: name + "-smesh", 394 | }, 395 | Data: secretMap, 396 | Type: v1.SecretTypeOpaque, 397 | } 398 | 399 | s, err := clientSet.CoreV1().Secrets(v1.NamespaceDefault).Create(context.TODO(), &secret, metav1.CreateOptions{}) 400 | if err != nil { 401 | return fmt.Errorf("unable to create secrets %v", err) 402 | } 403 | slog.Info(fmt.Sprintf("Created Secret 🔐 [%s]", s.Name)) 404 | 405 | return nil 406 | } 407 | func (c *certs) loadCA(clientSet *kubernetes.Clientset) error { 408 | secretMap := make(map[string][]byte) 409 | 410 | secretMap["ca-cert"] = c.cacert 411 | secretMap["ca-key"] = c.cakey 412 | secret := v1.Secret{ 413 | TypeMeta: metav1.TypeMeta{ 414 | Kind: "Secret", 415 | APIVersion: "v1", 416 | }, 417 | ObjectMeta: metav1.ObjectMeta{ 418 | Name: "watcher", 419 | }, 420 | Data: secretMap, 421 | Type: v1.SecretTypeOpaque, 422 | } 423 | 424 | s, err := clientSet.CoreV1().Secrets(v1.NamespaceDefault).Create(context.TODO(), &secret, metav1.CreateOptions{}) 425 | if err != nil { 426 | return fmt.Errorf("unable to create secrets %v", err) 427 | } 428 | slog.Info(fmt.Sprintf("Created Secret 🔐 [%s]", s.Name)) 429 | 430 | return nil 431 | 432 | return nil 433 | } 434 | 435 | // Actual watcher code 436 | 437 | type informerHandler struct { 438 | clientset *kubernetes.Clientset 439 | c *certs 440 | } 441 | 442 | func (c *certs) watcher(clientSet *kubernetes.Clientset) error { 443 | 444 | factory := informers.NewSharedInformerFactory(clientSet, 0) 445 | 446 | informer := factory.Core().V1().Pods().Informer() 447 | 448 | _, err := informer.AddEventHandler(&informerHandler{clientset: clientSet, c: c}) 449 | if err != nil { 450 | return err 451 | } 452 | stop := make(chan struct{}, 2) 453 | 454 | go informer.Run(stop) 455 | forever := make(chan os.Signal, 1) 456 | signal.Notify(forever, syscall.SIGINT, syscall.SIGTERM) 457 | <-forever 458 | stop <- struct{}{} 459 | close(forever) 460 | close(stop) 461 | return nil 462 | } 463 | 464 | func (i *informerHandler) OnUpdate(oldObj, newObj interface{}) { 465 | newPod := newObj.(*v1.Pod) 466 | oldPod := oldObj.(*v1.Pod) 467 | 468 | // Inspect the changes 469 | if oldPod.Status.PodIP != newPod.Status.PodIP && newPod.Status.PodIP != "" { 470 | i.c.createCertificate(newPod.Name, newPod.Status.PodIP) 471 | 472 | err := i.c.loadSecret(newPod.Name, i.clientset) 473 | if err != nil { 474 | slog.Error(err) 475 | } 476 | //loadSecret(newPod.Name, "", i.clientset) 477 | } 478 | } 479 | 480 | func (i *informerHandler) OnDelete(obj interface{}) { 481 | p := obj.(*v1.Pod) 482 | name := fmt.Sprintf("%s-smesh", p.Name) 483 | err := i.clientset.CoreV1().Secrets(p.Namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) 484 | if err != nil { 485 | slog.Errorf("Error deleting secret %v", err) 486 | } else { 487 | slog.Infof("Deleted secret 🔏 [%s]", name) 488 | } 489 | 490 | } 491 | 492 | func (i *informerHandler) OnAdd(obj interface{}, b bool) { 493 | // p := obj.(*corev1.Pod) 494 | 495 | // // dp := obj.(*v1.Deployment) 496 | // // if dp.ObjectMeta.Annotations["needFluentd"] == "yes" { 497 | 498 | // p2, err := i.clientset.CoreV1().Pods(p.Namespace).Get(context.TODO(), p.Name, metav1.GetOptions{}) 499 | // if err != nil { 500 | // klog.Infoln(err) 501 | // } 502 | 503 | // klog.Infof("ADD: the old version %s %s %s", p2.Name, p2.ObjectMeta.ResourceVersion, p2.Status.PodIP) 504 | // fluentContainer := corev1.EphemeralContainer{ 505 | // EphemeralContainerCommon: corev1.EphemeralContainerCommon{ 506 | // Name: "fluentd-sidecar", 507 | // Image: "fluent/fluentd:v1.15-debian-1", 508 | // Env: []corev1.EnvVar{ 509 | // { 510 | // Name: "FLUENTD_CONF", 511 | // Value: "fluentd.conf", 512 | // }, 513 | // }, 514 | // }, 515 | // } 516 | 517 | // p2.Spec.EphemeralContainers = append(p2.Spec.EphemeralContainers, fluentContainer) 518 | // //dp2.Spec.Template.Spec.Volumes = append(dp2.Spec.Template.Spec.Volumes, fluentVolumne) 519 | // p2, err = i.clientset.CoreV1().Pods(p2.Namespace).Update(context.Background(), p2, metav1.UpdateOptions{}) 520 | // if err != nil { 521 | // klog.Infoln(err) 522 | // } 523 | 524 | // p = p2.DeepCopy() 525 | // klog.Infof("ADD: the new version %s %s", p.Name, p.ObjectMeta.ResourceVersion) 526 | 527 | // // resourceVersion should not be set on objects to be created 528 | // // 可能的处理方法 需要先更新这个deploy 529 | 530 | // // _, err := i.clientset.AppsV1().Deployments(dp.Namespace).Create(context.Background(), dp, metav1.CreateOptions{}) 531 | // // if err != nil { 532 | 533 | // // klog.Infoln(err) 534 | // // } 535 | } 536 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= 2 | github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= 3 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 4 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 9 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= 11 | github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 12 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 13 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 14 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 15 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 16 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 17 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 18 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 19 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 20 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 21 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 22 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 23 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 24 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 25 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= 26 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 27 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 28 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 29 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 30 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 31 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 32 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 33 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 34 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 35 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 36 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 37 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 38 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 39 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 40 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 41 | github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= 42 | github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 43 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 44 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 45 | github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= 46 | github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= 47 | github.com/gookit/goutil v0.6.17 h1:SxmbDz2sn2V+O+xJjJhJT/sq1/kQh6rCJ7vLBiRPZjI= 48 | github.com/gookit/goutil v0.6.17/go.mod h1:rSw1LchE1I3TDWITZvefoAC9tS09SFu3lHXLCV7EaEY= 49 | github.com/gookit/gsr v0.1.0 h1:0gadWaYGU4phMs0bma38t+Do5OZowRMEVlHv31p0Zig= 50 | github.com/gookit/gsr v0.1.0/go.mod h1:7wv4Y4WCnil8+DlDYHBjidzrEzfHhXEoFjEA0pPPWpI= 51 | github.com/gookit/slog v0.5.7 h1:n3Dhgmr3NP+KppkNg95+vpFcI4YD1csu9VTQwgcEYTs= 52 | github.com/gookit/slog v0.5.7/go.mod h1:uWCRB4YO+FmgwXEq3s8U7ob1wWP7RStOuY/2a4yC/8o= 53 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 54 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 55 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 56 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 57 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 58 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 59 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 60 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 61 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 62 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 63 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 64 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 65 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 66 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 67 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 68 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 69 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 70 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 72 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 73 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 74 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 75 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 76 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 77 | github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= 78 | github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= 79 | github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= 80 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 81 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 82 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 83 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 84 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 85 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 86 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 87 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 88 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 89 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 90 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 91 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 92 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 93 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 94 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 95 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 96 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 97 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 98 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 99 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 100 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 101 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 102 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 103 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 104 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 105 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 106 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 107 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 108 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 109 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 110 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 111 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= 112 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 113 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 114 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 115 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 116 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 117 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 118 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 119 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 120 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 121 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 122 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 123 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 124 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 125 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 126 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 127 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 128 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 129 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 130 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 131 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 132 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 133 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 134 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 135 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 136 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 137 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 138 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 139 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 140 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 141 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 142 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 143 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 144 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 145 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 146 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 147 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 148 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 149 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 150 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 151 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 152 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 153 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 154 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 155 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 156 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 157 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 158 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 159 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 160 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 161 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 162 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 163 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 164 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 165 | gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= 166 | gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= 167 | k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= 168 | k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= 169 | k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= 170 | k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 171 | k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= 172 | k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= 173 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 174 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 175 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= 176 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 177 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= 178 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 179 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 180 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 181 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 182 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 183 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 184 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 185 | --------------------------------------------------------------------------------