├── pkg ├── inspector │ ├── sensitive_keyword.txt │ ├── patterns.go │ └── inspector.go ├── utils │ └── utils.go └── kubeclient │ └── kubeclient.go ├── demo.gif ├── workshop ├── examples │ ├── namespace.yaml │ ├── workshop-apiserver-sa.yaml │ ├── tenant │ │ ├── tenant-1-serviceaccount.yaml │ │ ├── tenant-2-serviceaccount.yaml │ │ ├── cluster-2.yaml │ │ ├── tenant-clusterrole.yaml │ │ ├── tenant-clusterrolebinding.yaml │ │ └── cluster-1.yaml │ ├── etcd │ │ ├── etcd-service.yaml │ │ ├── etcd-deployment.yaml │ │ ├── deploy.sh │ │ └── generate-certs.sh │ ├── workshop-apiserver-clusterrolebinding.yaml │ ├── apiserviceservice.yaml │ └── workshop-apiserver-deployment.yaml ├── pkg │ ├── apis │ │ └── workshop │ │ │ ├── v1alpha1 │ │ │ ├── doc.go │ │ │ ├── types.go │ │ │ ├── registry.go │ │ │ └── zz_generated.deepcopy.go │ │ │ └── registry.go │ └── server │ │ ├── config.go │ │ ├── server.go │ │ └── registry │ │ └── cluster │ │ └── rest.go ├── cmd │ ├── main.go │ └── app │ │ ├── app.go │ │ └── options │ │ └── options.go ├── Dockerfile ├── go.mod └── go.sum ├── slides └── discover the secrets hidden in apis.pdf ├── cmd └── inspector │ ├── main.go │ └── app │ └── app.go ├── Dockerfile ├── .gitignore ├── .github └── workflows │ ├── release_build.yaml │ └── build_workshop.yaml ├── .goreleaser.yaml ├── README_zh.md ├── go.mod ├── README.md └── go.sum /pkg/inspector/sensitive_keyword.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeahx/KubeAPI-Inspector/HEAD/demo.gif -------------------------------------------------------------------------------- /workshop/examples/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: kubeapi-inspector-workshop -------------------------------------------------------------------------------- /slides/discover the secrets hidden in apis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeahx/KubeAPI-Inspector/HEAD/slides/discover the secrets hidden in apis.pdf -------------------------------------------------------------------------------- /workshop/pkg/apis/workshop/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:openapi-gen=true 2 | // +k8s:deepcopy-gen=package 3 | // +groupName=workshop.io 4 | 5 | package v1alpha1 6 | -------------------------------------------------------------------------------- /cmd/inspector/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/yeahx/kubeapi-inspector/cmd/inspector/app" 5 | ) 6 | 7 | func main() { 8 | app.Run() 9 | } 10 | -------------------------------------------------------------------------------- /workshop/examples/workshop-apiserver-sa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: workshop-apiserver-sa 5 | namespace: kubeapi-inspector-workshop 6 | labels: 7 | app: workshop-apiserver -------------------------------------------------------------------------------- /workshop/examples/tenant/tenant-1-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: workshop-tenant-1 5 | namespace: kubeapi-inspector-workshop 6 | labels: 7 | app: workshop-apiserver -------------------------------------------------------------------------------- /workshop/examples/tenant/tenant-2-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: workshop-tenant-2 5 | namespace: kubeapi-inspector-workshop 6 | labels: 7 | app: workshop-apiserver -------------------------------------------------------------------------------- /workshop/examples/tenant/cluster-2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: workshop.io/v1alpha1 2 | kind: Cluster 3 | metadata: 4 | name: cluster-2 5 | spec: 6 | tenant: workshop-tenant-2 7 | name: cluster-2 8 | apiserver: http://10.12.233.1:6443 9 | kubeconfig: Aa== -------------------------------------------------------------------------------- /workshop/examples/tenant/tenant-clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: tenant-clusterrole 5 | rules: 6 | - apiGroups: ["workshop.io"] 7 | resources: ["clusters"] 8 | verbs: ["get", "watch", "list", "create", "delete"] -------------------------------------------------------------------------------- /workshop/examples/etcd/etcd-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: etcd 5 | namespace: kubeapi-inspector-workshop 6 | labels: 7 | app: etcd 8 | spec: 9 | ports: 10 | - port: 2379 11 | name: client 12 | targetPort: 2379 13 | selector: 14 | app: etcd -------------------------------------------------------------------------------- /workshop/examples/tenant/tenant-clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: tenant-clusterrolebinding 5 | subjects: 6 | - kind: Group 7 | name: system:serviceaccounts:kubeapi-inspector-workshop # Name is case sensitive 8 | apiGroup: rbac.authorization.k8s.io 9 | roleRef: 10 | kind: ClusterRole 11 | name: tenant-clusterrole 12 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /workshop/examples/workshop-apiserver-clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: workshop-apiserver-crb 5 | labels: 6 | app: workshop-apiserver 7 | subjects: 8 | - kind: ServiceAccount 9 | name: workshop-apiserver-sa 10 | namespace: kubeapi-inspector-workshop 11 | roleRef: 12 | kind: ClusterRole 13 | name: cluster-admin 14 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /workshop/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | 7 | genericapiserver "k8s.io/apiserver/pkg/server" 8 | "k8s.io/component-base/logs" 9 | 10 | "workshop/cmd/app" 11 | ) 12 | 13 | func main() { 14 | logs.InitLogs() 15 | defer logs.FlushLogs() 16 | 17 | if len(os.Getenv("GOMAXPROCS")) == 0 { 18 | runtime.GOMAXPROCS(runtime.NumCPU()) 19 | } 20 | 21 | cmd := app.NewServerCommand(genericapiserver.SetupSignalHandler()) 22 | if err := cmd.Execute(); err != nil { 23 | panic(err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /workshop/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.23 as builder 3 | 4 | WORKDIR /workspace 5 | ARG TARGETOS=linux 6 | ARG TARGETARCH=amd64 7 | 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | 12 | # Copy the go source 13 | COPY cmd/ cmd/ 14 | COPY pkg/ pkg/ 15 | 16 | ENV GO111MODULE on 17 | ENV DEBUG true 18 | 19 | # Build workshop-apiserver 20 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GO111MODULE=on go build -o workshop-apiserver cmd/main.go 21 | 22 | FROM gcr.io/distroless/static-debian12:debug 23 | WORKDIR / 24 | COPY --from=builder /workspace/workshop-apiserver . 25 | 26 | ENTRYPOINT ["/workshop-apiserver"] -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.23 as builder 3 | 4 | WORKDIR /workspace 5 | ARG TARGETOS=linux 6 | ARG TARGETARCH=amd64 7 | 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | 12 | # Copy the go source 13 | COPY cmd/ cmd/ 14 | COPY pkg/ pkg/ 15 | 16 | ENV GO111MODULE on 17 | ENV DEBUG true 18 | ENV GOPROXY http://goproxy.cn,direct 19 | 20 | # Build workshop-apiserver 21 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GO111MODULE=on go build -o inspector cmd/inspector/main.go 22 | 23 | FROM gcr.io/distroless/static-debian12:debug 24 | WORKDIR / 25 | COPY --from=builder /workspace/inspector . 26 | 27 | ENTRYPOINT ["/inspector"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | # custom 28 | .idea/ 29 | local/ 30 | bin/ 31 | KubeAPI-Inspector-CFT.txt 32 | -------------------------------------------------------------------------------- /workshop/examples/apiserviceservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiregistration.k8s.io/v1 2 | kind: APIService 3 | metadata: 4 | name: v1alpha1.workshop.io 5 | spec: 6 | version: v1alpha1 7 | versionPriority: 1000 8 | group: workshop.io 9 | groupPriorityMinimum: 10000 10 | insecureSkipTLSVerify: true 11 | service: 12 | name: workshop-apiservice 13 | namespace: kubeapi-inspector-workshop 14 | --- 15 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: workshop-apiservice 19 | namespace: kubeapi-inspector-workshop 20 | labels: 21 | app: workshop-apiservice 22 | spec: 23 | ports: 24 | - name: apiservice 25 | port: 443 26 | protocol: TCP 27 | targetPort: 443 28 | selector: 29 | app: workshop-apiserver 30 | -------------------------------------------------------------------------------- /workshop/pkg/apis/workshop/registry.go: -------------------------------------------------------------------------------- 1 | package workshop 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | "workshop/pkg/apis/workshop/v1alpha1" 7 | ) 8 | 9 | const GroupName = "workshop.io" 10 | 11 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} 12 | 13 | var ( 14 | SchemeBuilder runtime.SchemeBuilder 15 | localSchemeBuilder = &SchemeBuilder 16 | Install = localSchemeBuilder.AddToScheme 17 | ) 18 | 19 | func init() { 20 | localSchemeBuilder.Register(addKnownTypes) 21 | } 22 | 23 | func addKnownTypes(scheme *runtime.Scheme) error { 24 | scheme.AddKnownTypes(SchemeGroupVersion, 25 | &v1alpha1.Cluster{}, 26 | &v1alpha1.ClusterList{}, 27 | ) 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/release_build.yaml: -------------------------------------------------------------------------------- 1 | name: Release Inspector 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" # triggers only if push new tag version, like `0.8.4` or else 7 | 8 | jobs: 9 | build: 10 | name: GoReleaser build 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Check out code into the Go module directory 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 # See: https://goreleaser.com/ci/actions/ 18 | 19 | - name: Set up Go 1.23 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.23 23 | id: go 24 | 25 | - name: Run GoReleaser 26 | uses: goreleaser/goreleaser-action@v3 27 | with: 28 | version: latest 29 | args: release 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: KubeAPI-Inspector 4 | 5 | before: 6 | hooks: 7 | - go mod tidy 8 | builds: 9 | - env: 10 | - CGO_ENABLED=0 11 | - GO111MODULE=on 12 | goos: 13 | - linux 14 | - darwin 15 | main: ./cmd/inspector/ 16 | binary: inspector 17 | goarch: 18 | - amd64 19 | - arm64 20 | # ensures mod timestamp to be the commit timestamp 21 | mod_timestamp: "{{ .CommitTimestamp }}" 22 | flags: 23 | # trims path 24 | - -trimpath 25 | ldflags: 26 | # use commit date instead of current date as main.date 27 | # only needed if you actually use those things in your main package, otherwise can be ignored. 28 | - -s -w 29 | 30 | # proxies from the go mod proxy before building 31 | # https://goreleaser.com/customization/gomod 32 | 33 | # config the checksum filename 34 | # https://goreleaser.com/customization/checksum 35 | checksum: 36 | name_template: "checksums.txt" 37 | -------------------------------------------------------------------------------- /pkg/inspector/patterns.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | // 定义敏感字段的正则表达式模式 4 | var sensitivePatterns = []string{ 5 | `(?i)password`, // match "password",not case-sensitive 6 | `(?i)passwd`, 7 | `(?i)pwd`, 8 | `(?i)secret`, 9 | `(?i)token`, 10 | `(?i)api[_-]?key`, 11 | `(?i)auth[_-]?token`, 12 | `(?i)kubeconfig`, 13 | `(?i)private[_-]?key`, 14 | `(?i)certificate`, 15 | `(?i)access[_-]?key`, 16 | `(?i)secret[_-]?key`, 17 | `(?i)aws[_-]?access[_-]?key[_-]?id`, 18 | `(?i)aws[_-]?secret[_-]?access[_-]?key`, 19 | `(?i)rsa[_-]?private[_-]?key`, 20 | `(?i)dsa[_-]?private[_-]?key`, 21 | `(?i)ecdsa[_-]?private[_-]?key`, 22 | `(?i)config$`, 23 | `(?i)env`, 24 | `(?i)credentials?`, 25 | `(?i)dockerconfigjson`, 26 | `(?i)^.*[_-]?config$`, 27 | `(?i)^.*[_-]?secret$`, 28 | `(?i)^.*[_-]?file$`, 29 | `(?i)^.*[_-]?key$`, 30 | `(?i)^.*[_-]?cert$`, 31 | `(?i)^.*[_-]?credential$`, 32 | `(?i)host`, 33 | `(?i)port`, 34 | `(?i)url`, 35 | `(?i)connection[_-]?string`, 36 | `(?i)database[_-]?url`, 37 | `(?i)command`, 38 | } 39 | -------------------------------------------------------------------------------- /workshop/pkg/server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "k8s.io/apiserver/pkg/registry/rest" 5 | genericapiserver "k8s.io/apiserver/pkg/server" 6 | restclient "k8s.io/client-go/rest" 7 | "workshop/pkg/apis/workshop/v1alpha1" 8 | clusterRegistry "workshop/pkg/server/registry/cluster" 9 | ) 10 | 11 | type Config struct { 12 | Apiserver *genericapiserver.Config 13 | Rest *restclient.Config 14 | } 15 | 16 | func (c Config) Complete() (*Server, error) { 17 | 18 | genericServer, err := c.Apiserver.Complete(nil).New("mutli-cluster server", genericapiserver.NewEmptyDelegate()) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | s := NewServer(genericServer) 24 | 25 | clusterStorage, err := clusterRegistry.NewClusterRest(Scheme, c.Apiserver.RESTOptionsGetter) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | workshopApiGroupIfo := genericapiserver.NewDefaultAPIGroupInfo(v1alpha1.GroupName, Scheme, ParameterCodec, Codecs) 31 | workshopServerResources := map[string]rest.Storage{ 32 | "clusters": clusterStorage, 33 | } 34 | 35 | workshopApiGroupIfo.VersionedResourcesStorageMap[v1alpha1.SchemeGroupVersion.Version] = workshopServerResources 36 | 37 | err = s.GenericAPIServer.InstallAPIGroup(&workshopApiGroupIfo) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return s, nil 43 | } 44 | -------------------------------------------------------------------------------- /workshop/examples/etcd/etcd-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: etcd 5 | namespace: kubeapi-inspector-workshop 6 | labels: 7 | app: etcd 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: etcd 12 | replicas: 1 13 | template: 14 | metadata: 15 | labels: 16 | app: etcd 17 | spec: 18 | containers: 19 | - name: etcd 20 | image: quay.io/coreos/etcd:v3.4.13 21 | command: 22 | - etcd 23 | - --advertise-client-urls=https://0.0.0.0:2379 24 | - --listen-client-urls=https://0.0.0.0:2379 25 | - --cert-file=/etc/etcd/certs/server.crt 26 | - --key-file=/etc/etcd/certs/server.key 27 | - --client-cert-auth 28 | - --trusted-ca-file=/etc/etcd/certs/ca.crt 29 | - --data-dir=/var/lib/etcd 30 | ports: 31 | - containerPort: 2379 32 | name: client 33 | volumeMounts: 34 | - name: etcd-certs 35 | mountPath: /etc/etcd/certs 36 | readOnly: true 37 | - name: etcd-data 38 | mountPath: /var/lib/etcd 39 | resources: 40 | requests: 41 | cpu: 100m 42 | memory: 100Mi 43 | limits: 44 | cpu: 200m 45 | memory: 200Mi 46 | volumes: 47 | - name: etcd-certs 48 | secret: 49 | secretName: etcd-certs 50 | defaultMode: 0400 51 | - name: etcd-data 52 | emptyDir: {} -------------------------------------------------------------------------------- /workshop/examples/etcd/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # 检查证书是否已经生成 5 | if [ ! -d "./certs" ]; then 6 | echo "error: certs directory not found" 7 | echo "plz run ./generate-certs.sh to generate certificates first" 8 | exit 1 9 | fi 10 | 11 | # 确保命名空间存在 12 | kubectl create namespace kubeapi-inspector-workshop --dry-run=client -o yaml | kubectl apply -f - 13 | 14 | # 创建服务账号 15 | kubectl create serviceaccount workshop-apiserver-sa -n kubeapi-inspector-workshop --dry-run=client -o yaml | kubectl apply -f - 16 | 17 | # 创建证书的Secret 18 | kubectl create secret generic etcd-certs \ 19 | --from-file=ca.crt=certs/ca.crt \ 20 | --from-file=server.crt=certs/server.pem \ 21 | --from-file=server.key=certs/server-key.pem \ 22 | --from-file=etcd-client.crt=certs/etcd-client.pem \ 23 | --from-file=etcd-client.key=certs/etcd-client-key.pem \ 24 | -n kubeapi-inspector-workshop \ 25 | --dry-run=client -o yaml | kubectl apply -f - 26 | 27 | # 创建etcd服务器地址的ConfigMap 28 | kubectl create configmap etcd-config \ 29 | --from-literal=etcd-servers=https://etcd.kubeapi-inspector-workshop.svc.cluster.local:2379 \ 30 | -n kubeapi-inspector-workshop \ 31 | --dry-run=client -o yaml | kubectl apply -f - 32 | 33 | # 部署etcd 34 | kubectl apply -f etcd-deployment.yaml 35 | kubectl apply -f etcd-service.yaml 36 | 37 | # 部署workshop-apiserver 38 | kubectl apply -f ../workshop-apiserver-deployment.yaml 39 | 40 | echo "deployment completed!" 41 | echo "you can use the following command to check the deployment status:" 42 | echo "kubectl get pods,svc -n kubeapi-inspector-workshop" -------------------------------------------------------------------------------- /workshop/pkg/apis/workshop/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 4 | 5 | // +k8s:openapi-gen=true 6 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 7 | 8 | // Cluster is the Schema for the clusters API 9 | type Cluster struct { 10 | metav1.TypeMeta `json:",inline"` 11 | metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 12 | 13 | Spec ClusterSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` 14 | Status ClusterStatus `json:"status,omitempty"` 15 | } 16 | 17 | // +k8s:openapi-gen=true 18 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 19 | 20 | // ClusterList contains a list of Cluster 21 | type ClusterList struct { 22 | metav1.TypeMeta `json:",inline"` 23 | metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 24 | Items []Cluster `json:"items" protobuf:"bytes,2,rep,name=items"` 25 | } 26 | 27 | // ClusterSpec defines the desired state of Cluster 28 | type ClusterSpec struct { 29 | Tenant string `json:"tenant,omitempty" protobuf:"bytes,1,opt,name=tenant"` 30 | Name string `json:"name,omitempty" protobuf:"bytes,2,opt,name=name"` 31 | APIServer string `json:"apiserver,omitempty" protobuf:"bytes,3,opt,name=apiserver"` 32 | Kubeconfig []byte `json:"kubeconfig,omitempty" protobuf:"bytes,4,opt,name=kubeconfig"` 33 | } 34 | 35 | // +kubebuilder:subresource:status 36 | 37 | // ClusterStatus defines the observed state of Cluster 38 | type ClusterStatus struct { 39 | } 40 | -------------------------------------------------------------------------------- /workshop/examples/workshop-apiserver-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: workshop-deployment 5 | namespace: kubeapi-inspector-workshop 6 | labels: 7 | app: workshop-apiserver 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: workshop-apiserver 12 | replicas: 1 13 | template: 14 | metadata: 15 | labels: 16 | app: workshop-apiserver 17 | spec: 18 | terminationGracePeriodSeconds: 10 19 | serviceAccountName: workshop-apiserver-sa 20 | containers: 21 | - ports: 22 | - name: apiservice 23 | containerPort: 443 24 | protocol: TCP 25 | command: 26 | - /workshop-apiserver 27 | args: 28 | - --etcd-cafile=/certs/ca.crt 29 | - --etcd-certfile=/certs/etcd-client.crt 30 | - --etcd-keyfile=/certs/etcd-client.key 31 | - --etcd-servers=$(ETCD_SERVERS) 32 | env: 33 | - name: ETCD_SERVERS 34 | valueFrom: 35 | configMapKeyRef: 36 | name: etcd-config 37 | key: etcd-servers 38 | image: workshop-apiserver:latest 39 | imagePullPolicy: IfNotPresent 40 | name: apiserver 41 | volumeMounts: 42 | - name: etcd-certs 43 | mountPath: /certs 44 | readOnly: true 45 | resources: 46 | requests: 47 | cpu: 100m 48 | memory: 128M 49 | limits: 50 | cpu: 200m 51 | memory: 256M 52 | 53 | volumes: 54 | - name: etcd-certs 55 | secret: 56 | secretName: etcd-certs 57 | defaultMode: 0400 -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # KubeAPI-Inspector 2 | [English](https://github.com/yeahx/KubeAPI-Inspector/blob/main/README.md) | 简体中文 3 | ## 概述 4 | 5 | 一个专为 Kubernetes 环境设计的工具,旨在高效且自动地发现集群中隐藏的漏洞 API。 6 | 附赠一个靶场可以学习到自定义 apiserver 的一个经典漏洞,这一设计会导致 API 端点鉴权失效,并可能危及整个集群。 7 | 8 | ![demo](https://github.com/yeahx/KubeAPI-Inspector/blob/main/demo.gif) 9 | ## 功能 10 | ### inspector 已实现 11 | * 【✅】自动解析 openapi 发现敏感字段 12 | * 【✅】自动探测潜在的认证绕过的 api 13 | * 【✅】自动加载环境内的凭证 14 | ### inspector 待实现 15 | * 【 】自动服务发现并探测潜在的缺陷 extension apiserver 16 | * 【 】集成已知控制面组件利用? 17 | ### workshop 已实现 18 | * 【✅】REST层的缺陷实现 19 | ### workshop 待实现 20 | * 【 】带有 operator 控制器的典型漏洞 21 | ## 使用方法 22 | ### 集群内 23 | 1. 在 pod 中下载二进制文件 24 | 2. 运行二进制文件 `./inspector` 25 | ### 集群外 26 | 1. ./inspector -kubeconfig path/to/kubeconfig 27 | 2. 测试其他命名空间 ./inspector -kubeconfig path/to/kubeconfig -namespace kube-system 28 | 3. 跳过敏感字段测试 ./inspector -kubeconfig path/to/kubeconfig -skipCheckSensitiveField=true 29 | ## 安装 30 | ### 要求 31 | 1. golang>1.22 32 | 2. kubernetes 和 docker 33 | 3. linux-amd64, linux-arm 34 | ### 构建 kubeapi-inspector 35 | * 当前工作目录:/repo/ 36 | 1. 使用 `go build CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o inspector cmd/inspector/main.go` 37 | 2. 或使用 docker 构建 `docker build . -t inspector:latest` 38 | ### 构建和部署 workshop 步骤 39 | * 当前工作目录:/repo/workshop/ 40 | 41 | 1. 设置一个 Kubernetes 集群,可以使用 minikube,如 `minikube start --kubernetes-version='v1.23.17'` 42 | 2. 使用 docker 构建 workshop 镜像 `docker build . -t workshop-apiserver:latest` 43 | 3. 部署 workshop-apiserver 使用的etcd `cd workshop/examples/etcd && ./generate-certs.sh && deploy.sh` 44 | 4. 创建 workshop k8s 资源 `cat examples/{namespace,apiserviceservice,workshop-apiserver-sa,workshop-apiserver-clusterrolebinding,workshop-apiserver-deployment}.yaml | kubectl apply -f -` 45 | 5. 创建 demo 的集群 resource 及租户的服务账号 `kubectl apply -f examples/tenant` -------------------------------------------------------------------------------- /workshop/cmd/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/spf13/cobra" 7 | cliflag "k8s.io/component-base/cli/flag" 8 | "k8s.io/component-base/logs" 9 | "k8s.io/component-base/term" 10 | "os" 11 | "workshop/cmd/app/options" 12 | ) 13 | 14 | func NewServerCommand(stopCh <-chan struct{}) *cobra.Command { 15 | opts := options.NewOptions() 16 | cmd := &cobra.Command{ 17 | Short: "Launch mutlicluster-server", 18 | Long: "Launch mutlicluster-server", 19 | RunE: func(c *cobra.Command, args []string) error { 20 | if err := runCommand(opts, stopCh); err != nil { 21 | return err 22 | } 23 | return nil 24 | }, 25 | } 26 | fs := cmd.Flags() 27 | nfs := opts.Flags() 28 | for _, f := range nfs.FlagSets { 29 | fs.AddFlagSet(f) 30 | } 31 | local := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 32 | logs.AddGoFlags(local) 33 | nfs.FlagSet("logging").AddGoFlagSet(local) 34 | 35 | usageFmt := "Usage:\n %s\n" 36 | cols, _, _ := term.TerminalSize(cmd.OutOrStdout()) 37 | cmd.SetUsageFunc(func(cmd *cobra.Command) error { 38 | fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine()) 39 | cliflag.PrintSections(cmd.OutOrStderr(), nfs, cols) 40 | return nil 41 | }) 42 | cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { 43 | fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine()) 44 | cliflag.PrintSections(cmd.OutOrStdout(), nfs, cols) 45 | }) 46 | fs.AddGoFlagSet(local) 47 | return cmd 48 | } 49 | 50 | func runCommand(o *options.Options, stopCh <-chan struct{}) error { 51 | 52 | errors := o.Validate() 53 | if len(errors) > 0 { 54 | return errors[0] 55 | } 56 | 57 | config, err := o.ServerConfig() 58 | 59 | if err != nil { 60 | return err 61 | } 62 | 63 | s, err := config.Complete() 64 | 65 | if err != nil { 66 | return err 67 | } 68 | 69 | return s.RunUntil(stopCh) 70 | } 71 | -------------------------------------------------------------------------------- /workshop/pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | "k8s.io/apimachinery/pkg/runtime" 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | "k8s.io/apimachinery/pkg/runtime/serializer" 9 | "k8s.io/apimachinery/pkg/util/wait" 10 | genericapiserver "k8s.io/apiserver/pkg/server" 11 | workshopinternal "workshop/pkg/apis/workshop" 12 | "workshop/pkg/apis/workshop/v1alpha1" 13 | ) 14 | 15 | var ( 16 | Scheme = runtime.NewScheme() 17 | Codecs = serializer.NewCodecFactory(Scheme) 18 | ParameterCodec = runtime.NewParameterCodec(Scheme) 19 | ) 20 | 21 | func init() { 22 | _ = v1alpha1.Install(Scheme) 23 | _ = workshopinternal.Install(Scheme) 24 | _ = metainternal.AddToScheme(Scheme) 25 | metav1.AddToGroupVersion(Scheme, metav1.SchemeGroupVersion) 26 | internalGroupVersion := schema.GroupVersion{Group: "", Version: "v1"} 27 | metav1.AddToGroupVersion(Scheme, internalGroupVersion) 28 | 29 | } 30 | 31 | // Server contains state for a Kubernetes cluster master/api server. 32 | type Server struct { 33 | GenericAPIServer *genericapiserver.GenericAPIServer 34 | } 35 | 36 | // NewServer returns a new instance of Server from the given config. 37 | func NewServer(apiserver *genericapiserver.GenericAPIServer) *Server { 38 | return &Server{ 39 | GenericAPIServer: apiserver, 40 | } 41 | } 42 | 43 | func (s *Server) RunUntil(stopCh <-chan struct{}) error { 44 | //ctx, cancel := context.WithCancel(context.Background()) 45 | //defer cancel() 46 | 47 | // Start informers 48 | //go s.nodes.Run(stopCh) 49 | //go s.pods.Run(stopCh) 50 | 51 | // Ensure cache is up to date 52 | //ok := cache.WaitForCacheSync(stopCh, s.nodes.HasSynced) 53 | //if !ok { 54 | // return nil 55 | //} 56 | 57 | return s.GenericAPIServer.PrepareRun().RunWithContext(wait.ContextForChannel(stopCh)) 58 | } 59 | -------------------------------------------------------------------------------- /workshop/pkg/apis/workshop/v1alpha1/registry.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | ) 8 | 9 | // GroupName specifies the group name used to register the objects. 10 | const GroupName = "workshop.io" 11 | 12 | // GroupVersion specifies the group and the version used to register the objects. 13 | var GroupVersion = v1.GroupVersion{Group: GroupName, Version: "v1alpha1"} 14 | 15 | // SchemeGroupVersion is group version used to register these objects 16 | // Deprecated: use GroupVersion instead. 17 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} 18 | 19 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 20 | func Resource(resource string) schema.GroupResource { 21 | return SchemeGroupVersion.WithResource(resource).GroupResource() 22 | } 23 | 24 | var ( 25 | // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. 26 | SchemeBuilder runtime.SchemeBuilder 27 | localSchemeBuilder = &SchemeBuilder 28 | // Depreciated: use Install instead 29 | AddToScheme = localSchemeBuilder.AddToScheme 30 | Install = localSchemeBuilder.AddToScheme 31 | ) 32 | 33 | func init() { 34 | // We only register manually written functions here. The registration of the 35 | // generated functions takes place in the generated files. The separation 36 | // makes the code compile even when the generated files are missing. 37 | localSchemeBuilder.Register(addKnownTypes) 38 | } 39 | 40 | // Adds the list of known types to Scheme. 41 | func addKnownTypes(scheme *runtime.Scheme) error { 42 | scheme.AddKnownTypes(SchemeGroupVersion, 43 | &Cluster{}, 44 | &ClusterList{}, 45 | ) 46 | // AddToGroupVersion allows the serialization of client types like ListOptions. 47 | v1.AddToGroupVersion(scheme, SchemeGroupVersion) 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yeahx/kubeapi-inspector 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.7 6 | 7 | require ( 8 | github.com/google/gnostic-models v0.6.9 9 | k8s.io/api v0.32.3 10 | k8s.io/apimachinery v0.32.3 11 | k8s.io/client-go v0.32.3 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 16 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 17 | github.com/fxamacker/cbor/v2 v2.8.0 // indirect 18 | github.com/go-logr/logr v1.4.2 // indirect 19 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 20 | github.com/go-openapi/jsonreference v0.21.0 // indirect 21 | github.com/go-openapi/swag v0.23.1 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/protobuf v1.5.4 // indirect 24 | github.com/google/go-cmp v0.7.0 // indirect 25 | github.com/google/gofuzz v1.2.0 // indirect 26 | github.com/google/uuid v1.6.0 // indirect 27 | github.com/josharian/intern v1.0.0 // indirect 28 | github.com/json-iterator/go v1.1.12 // indirect 29 | github.com/mailru/easyjson v0.9.0 // indirect 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 31 | github.com/modern-go/reflect2 v1.0.2 // indirect 32 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 33 | github.com/pkg/errors v0.9.1 // indirect 34 | github.com/spf13/pflag v1.0.6 // indirect 35 | github.com/x448/float16 v0.8.4 // indirect 36 | golang.org/x/net v0.38.0 // indirect 37 | golang.org/x/oauth2 v0.28.0 // indirect 38 | golang.org/x/sys v0.31.0 // indirect 39 | golang.org/x/term v0.30.0 // indirect 40 | golang.org/x/text v0.23.0 // indirect 41 | golang.org/x/time v0.11.0 // indirect 42 | google.golang.org/protobuf v1.36.6 // indirect 43 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 44 | gopkg.in/inf.v0 v0.9.1 // indirect 45 | gopkg.in/yaml.v3 v3.0.1 // indirect 46 | k8s.io/klog/v2 v2.130.1 // indirect 47 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 48 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect 49 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 50 | sigs.k8s.io/randfill v1.0.0 // indirect 51 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 52 | sigs.k8s.io/yaml v1.4.0 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KubeAPI-Inspector:discover the secrets hidden in apis 2 | English | [简体中文](https://github.com/yeahx/KubeAPI-Inspector/blob/main/README_zh.md) 3 | ## Description 4 | 5 | A tool specifically designed for Kubernetes environments aims to efficiently and automatically discover hidden vulnerable APIs within clusters. It reveals and demonstrates a common error through a workshop format, which could lead to API endpoint authentication failures and potentially compromise the entire cluster. The workshop can be deployed using Kubernetes resource YAML files. 6 | 7 | ![demo](https://github.com/yeahx/KubeAPI-Inspector/blob/main/demo.gif) 8 | ## Features 9 | ### Implemented in Inspector 10 | * 【✅】Automatically parse OpenAPI to identify sensitive fields 11 | * 【✅】Automatically detect potential authentication bypass APIs 12 | * 【✅】Automatically load credentials from the environment 13 | ### To be Implemented in Inspector 14 | * 【 】Automatically discover services and detect potential vulnerabilities in extension API servers 15 | * 【 】exploitation of known control plane components? 16 | ### Implemented in Workshop 17 | * 【✅】Flawed implementation of the REST layer 18 | ### To be Implemented in Workshop 19 | * 【 】Typical vulnerabilities involving operator controllers 20 | 21 | ## Usage 22 | ### in-cluster 23 | 1. download binary in pod 24 | 2. run binary `./inspector` 25 | ### out-of-cluster 26 | 1. `./inspector -kubeconfig path/to/kubeconfig` 27 | 2. test other namespace `./inspector -kubeconfig path/to/kubeconfig -namespace kube-system` 28 | 3. skip sensitive field test `./inspector -kubeconfig path/to/kubeconfig -skipCheckSensitiveField=true` 29 | 30 | ## Installation 31 | ### Requirements 32 | 1. golang>1.22 33 | 2. kubernetes and docker 34 | 3. linux-amd64, linux-arm 35 | ### build kubeapi-inspector 36 | CWD: /repo/ 37 | 1. use go build `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o inspector cmd/inspector/main.go` 38 | 2. or use docker to build `docker build . -t inspector:latest` 39 | ### build & deploy workshop steps 40 | CWD: /repo/workshop/ 41 | 1. setup a kubernetes cluster, maybe you should use minikube, e.g. `minikube start --kubernetes-version='v1.23.17'` 42 | 2. build workshop image with docker `docker build . -t workshop-apiserver:latest` 43 | 3. deploy etcd for workshop-apiserver `cd workshop/examples/etcd && ./generate-certs.sh && deploy.sh` 44 | 4. create workshop k8s resource `cat examples/{namespace,apiserviceservice,workshop-apiserver-sa,workshop-apiserver-clusterrolebinding,workshop-apiserver-deployment}.yaml | kubectl apply -f -` 45 | 5. create demo cluster resource and tenant accounts `kubectl apply -f examples/tenant` 46 | 47 | ## License 48 | MIT License -------------------------------------------------------------------------------- /cmd/inspector/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/yeahx/kubeapi-inspector/pkg/inspector" 8 | "github.com/yeahx/kubeapi-inspector/pkg/kubeclient" 9 | "github.com/yeahx/kubeapi-inspector/pkg/utils" 10 | ) 11 | 12 | func Run() { 13 | var kubeconfig, namespace, token, server string 14 | var skipCheckSensitiveField, insecureSkipTLS bool 15 | 16 | flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") 17 | //flag.BoolVar(&skipNativeAPI, "skip-native-api", true, "") 18 | flag.StringVar(&namespace, "namespace", "", "") 19 | flag.StringVar(&token, "token", "", "token for access apiserver. Only required if out-of-cluster.") 20 | flag.StringVar(&server, "server", "", "target apiserver address.") 21 | flag.BoolVar(&skipCheckSensitiveField, "skipCheckSensitiveField", false, "if true skip check resource sensitive field") 22 | flag.BoolVar(&insecureSkipTLS, "insecure-skip-tls-verify", false, "if true, skip TLS verification for Kubernetes API server") 23 | flag.Parse() 24 | 25 | kubeClient, err := kubeclient.NewKubeClient(kubeconfig, namespace, insecureSkipTLS) 26 | if err != nil { 27 | fmt.Printf("[-] Failed to create kubeclient: %s\nmake sure kubeconfig is valided.", err) 28 | return 29 | } 30 | 31 | doc, err := kubeClient.DownloadOpenApiSchema() 32 | if err != nil { 33 | fmt.Printf("[-] Failed to download openapi schema: %s\n, will be skip sensitive field test.", err) 34 | skipCheckSensitiveField = true 35 | } 36 | 37 | err = kubeClient.FetchCRDApis() 38 | if err != nil { 39 | fmt.Printf("[-] Failed to fetch CRD apis: %s\n", err) 40 | } 41 | 42 | scan := inspector.NewInspector(kubeClient, nil) 43 | err = scan.ParseDocument(doc) 44 | if err != nil { 45 | fmt.Printf("[-] Failed to parse document: %s\n, will be skip sensitive field test.", err) 46 | skipCheckSensitiveField = true 47 | } 48 | 49 | _, err = scan.DiscoveryAPIServiceBySRV() 50 | if err != nil { 51 | fmt.Printf("[-] Failed to discovery apiservice by srv: %s\n", err) 52 | } 53 | 54 | for k, v := range kubeClient.Resources { 55 | if utils.IsStatusSubresource(v.Name) { 56 | continue 57 | } 58 | 59 | fmt.Printf("[*] Starting validation for %s, group: %s, version: %s, resource: %s,\n", k, v.GroupName, v.Version, v.Name) 60 | if !skipCheckSensitiveField { 61 | err := scan.DetectSensitiveField(v.GroupName, v.Version, v.Name) 62 | if err != nil { 63 | fmt.Printf("[-] Failed to detect sensitive field: %s\n", err) 64 | } 65 | } 66 | err = scan.DetectObjectLeak(v.GroupName, v.Version, v.Name) 67 | if err != nil { 68 | fmt.Printf("[-] Detect err: %v", err) 69 | return 70 | } 71 | } 72 | 73 | fmt.Println("[*] Done") 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/build_workshop.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Push Workshop Docker Image 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | # Environment variables available to all jobs and steps in the workflow 12 | env: 13 | REGISTRY: ghcr.io 14 | IMAGE_NAME: yeahx/kubeapi-inspector:workshop-apiserver 15 | 16 | jobs: 17 | build-and-push-image: 18 | runs-on: ubuntu-latest # Use the latest Ubuntu runner 19 | 20 | # Grant permissions for actions to push to ghcr.io 21 | permissions: 22 | contents: read # Needed to check out the repository 23 | packages: write # Needed to push Docker images to ghcr.io 24 | 25 | steps: 26 | - name: Get version 27 | id: get_version 28 | run: | 29 | echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 30 | 31 | - name: Checkout repository 32 | uses: actions/checkout@v3 # Checks out your repository code 33 | 34 | - name: Log in to the Container registry 35 | uses: docker/login-action@v3 36 | with: 37 | registry: ${{ env.REGISTRY }} 38 | username: ${{ github.actor }} # Use the GitHub Actions token user 39 | password: ${{ secrets.REGISTRY }} # Use the automatically generated token 40 | 41 | - name: Set up Docker Buildx 42 | uses: docker/setup-buildx-action@v3 # Sets up the buildx builder instance 43 | 44 | # - name: Extract metadata (tags, labels) for Docker 45 | # id: meta # Assign an ID to refer to the outputs of this step 46 | # uses: docker/metadata-action@v5 47 | # with: 48 | # images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # Full image name 49 | # # Generate tags based on the event 50 | # tags: | 51 | # type=ref,event=pr # Example: Add PR number tag (e.g., pr-123) 52 | # type=sha,format=short # Add short git commit SHA as tag (e.g., a1b2c3d) 53 | # type=raw,value=latest,enable={{is_default_branch}} 54 | 55 | - name: Build and push Docker image 56 | uses: docker/build-push-action@v6 57 | with: 58 | context: ./workshop # IMPORTANT: Set build context to the workshop directory 59 | file: ./workshop/Dockerfile # IMPORTANT: Specify the Dockerfile path relative to repo root 60 | push: true # Push the image after building 61 | tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ steps.get_version.outputs.VERSION }} # Use tags generated by the metadata step 62 | # labels: ${{ steps.meta.outputs.labels }} # Add labels generated by the metadata step 63 | cache-from: type=gha # Enable build cache from GitHub Actions cache 64 | cache-to: type=gha,mode=max # Write build cache to GitHub Actions cache (mode=max for potentially better performance) -------------------------------------------------------------------------------- /workshop/examples/etcd/generate-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | #检查cfssl是否已安装 5 | if ! command -v cfssl &> /dev/null || ! command -v cfssljson &> /dev/null; then 6 | echo "plz install cfssl and cfssljson first" 7 | echo "Linux x86_64 example:" 8 | echo "curl -L https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_linux_amd64 -o cfssl" 9 | echo "curl -L https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_linux_amd64 -o cfssljson" 10 | echo "chmod +x cfssl cfssljson" 11 | echo "sudo mv cfssl cfssljson /usr/local/bin/" 12 | exit 1 13 | fi 14 | 15 | # 创建证书输出目录 16 | CERT_DIR="./certs" 17 | mkdir -p ${CERT_DIR} 18 | cd ${CERT_DIR} 19 | 20 | # 生成CA证书 21 | echo "generating CA certificate..." 22 | echo '{"CN":"CA","key":{"algo":"rsa","size":2048}}' | cfssl gencert -initca - | cfssljson -bare ca 23 | mv ca.pem ca.crt 24 | mv ca-key.pem ca.key 25 | 26 | # 创建证书配置文件 27 | cat > config.json < server-csr.json < client-csr.json < 0 { 150 | i := l.Items[0] 151 | gvk := i.GetObjectKind().GroupVersionKind() 152 | gvk.Kind = fmt.Sprintf("%sList", gvk.Kind) 153 | l.SetGroupVersionKind(gvk) 154 | } 155 | 156 | return l, len(l.Items), nil 157 | } 158 | 159 | // removeOjbectFields remove redundant fields 160 | func RemoveObjectFields(list *unstructured.UnstructuredList, uri string) { 161 | _ = list.EachListItem(func(object runtime.Object) error { 162 | unstructuredObj, ok := object.(*unstructured.Unstructured) 163 | if !ok { 164 | return nil 165 | } 166 | unstructuredObj.SetManagedFields(nil) 167 | //unstructuredObj.SetAnnotations() 168 | unstructured.RemoveNestedField(unstructuredObj.UnstructuredContent(), "metadata", 169 | "annotations", "kubectl.kubernetes.io/last-applied-configuration") 170 | //fmt.Printf("%s, leak data: %v", uri, unstructuredObj) 171 | return nil 172 | }) 173 | } 174 | 175 | func PrintResult(uri, verb string, object any) { 176 | fmt.Printf("[+] Path: %s found broken access control by %s verb.\nCommand example: %s\n", uri, verb, generateCurlExample(verb, uri)) 177 | indent, _ := json.MarshalIndent(object, "", " ") 178 | fmt.Printf("[+] leak objects: %v \n", string(indent)) 179 | } 180 | 181 | func generateCurlExample(verb, uri string) string { 182 | output := "" 183 | switch verb { 184 | case "Watch": 185 | output = fmt.Sprintf("curl -H\"Authorization: Bearer $token\" -k https://kubernetes.default%s?watch=true&timeoutSeconds=2", uri) 186 | case "DeleteCollection": 187 | output = fmt.Sprintf("curl -H\"Authorization: Bearer $token\" -k https://kubernetes.default%s?dryRun=All", uri) 188 | } 189 | 190 | return output 191 | } 192 | -------------------------------------------------------------------------------- /workshop/cmd/app/options/options.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | import ( 4 | "fmt" 5 | apiextensionsserver "k8s.io/apiextensions-apiserver/pkg/apiserver" 6 | openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" 7 | genericapiserver "k8s.io/apiserver/pkg/server" 8 | genericoptions "k8s.io/apiserver/pkg/server/options" 9 | serverstorage "k8s.io/apiserver/pkg/server/storage" 10 | "k8s.io/apiserver/pkg/storage/storagebackend" 11 | utilversion "k8s.io/apiserver/pkg/util/version" 12 | "k8s.io/client-go/rest" 13 | "k8s.io/client-go/tools/clientcmd" 14 | "k8s.io/component-base/cli/flag" 15 | "k8s.io/component-base/logs" 16 | logsapi "k8s.io/component-base/logs/api/v1" 17 | _ "k8s.io/component-base/logs/json/register" 18 | "net" 19 | generatedopenapi "workshop/pkg/apis/workshop/v1alpha1" 20 | "workshop/pkg/server" 21 | //"sigs.k8s.io/metrics-server/pkg/api" 22 | //generatedopenapi "sigs.k8s.io/metrics-server/pkg/api/generated/openapi" 23 | //"sigs.k8s.io/metrics-server/pkg/server" 24 | ) 25 | 26 | type Options struct { 27 | // genericoptions.RecomendedOptions - EtcdOptions 28 | GenericServerRunOptions *genericoptions.ServerRunOptions 29 | SecureServing *genericoptions.SecureServingOptionsWithLoopback 30 | Authentication *genericoptions.DelegatingAuthenticationOptions 31 | Authorization *genericoptions.DelegatingAuthorizationOptions 32 | Etcd *genericoptions.EtcdOptions 33 | Logging *logs.Options 34 | 35 | Kubeconfig string 36 | 37 | // Only to be used to for testing 38 | DisableAuthForTesting bool 39 | } 40 | 41 | func (o *Options) Validate() []error { 42 | var errors []error 43 | err := logsapi.ValidateAndApply(o.Logging, nil) 44 | if err != nil { 45 | errors = append(errors, err) 46 | } 47 | if errs := o.GenericServerRunOptions.Validate(); len(errs) > 0 { 48 | errors = append(errors, errs...) 49 | } 50 | return errors 51 | } 52 | 53 | func (o *Options) Flags() (fs flag.NamedFlagSets) { 54 | msfs := fs.FlagSet("mutlicluster-server") 55 | msfs.StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, "The path to the kubeconfig used to connect to the Kubernetes API server and the Kubelets (defaults to in-cluster config)") 56 | 57 | o.GenericServerRunOptions.AddUniversalFlags(fs.FlagSet("generic")) 58 | o.SecureServing.AddFlags(fs.FlagSet("apiserver secure serving")) 59 | o.Authentication.AddFlags(fs.FlagSet("apiserver authentication")) 60 | o.Authorization.AddFlags(fs.FlagSet("apiserver authorization")) 61 | o.Etcd.AddFlags(fs.FlagSet("etcd")) 62 | logsapi.AddFlags(o.Logging, fs.FlagSet("logging")) 63 | 64 | return fs 65 | } 66 | 67 | // NewOptions constructs a new set of default options for metrics-server. 68 | func NewOptions() *Options { 69 | return &Options{ 70 | GenericServerRunOptions: genericoptions.NewServerRunOptions(), 71 | SecureServing: genericoptions.NewSecureServingOptions().WithLoopback(), 72 | Authentication: genericoptions.NewDelegatingAuthenticationOptions(), 73 | Authorization: genericoptions.NewDelegatingAuthorizationOptions(), 74 | Etcd: genericoptions.NewEtcdOptions(storagebackend.NewDefaultConfig("/workshop/test", nil)), 75 | Logging: logs.NewOptions(), 76 | } 77 | } 78 | 79 | func (o Options) ServerConfig() (*server.Config, error) { 80 | apiserver, err := o.ApiserverConfig() 81 | if err != nil { 82 | return nil, err 83 | } 84 | restConfig, err := o.restConfig() 85 | if err != nil { 86 | return nil, err 87 | } 88 | return &server.Config{ 89 | Apiserver: apiserver, 90 | Rest: restConfig, 91 | }, nil 92 | } 93 | 94 | func (o Options) ApiserverConfig() (*genericapiserver.Config, error) { 95 | if err := o.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil { 96 | return nil, fmt.Errorf("error creating self-signed certificates: %v", err) 97 | } 98 | 99 | serverConfig := genericapiserver.NewConfig(server.Codecs) 100 | 101 | if err := o.GenericServerRunOptions.ApplyTo(serverConfig); err != nil { 102 | return nil, err 103 | } 104 | 105 | if err := o.SecureServing.ApplyTo(&serverConfig.SecureServing, &serverConfig.LoopbackClientConfig); err != nil { 106 | return nil, err 107 | } 108 | 109 | if !o.DisableAuthForTesting { 110 | if err := o.Authentication.ApplyTo(&serverConfig.Authentication, serverConfig.SecureServing, nil); err != nil { 111 | return nil, err 112 | } 113 | if err := o.Authorization.ApplyTo(&serverConfig.Authorization); err != nil { 114 | return nil, err 115 | } 116 | } 117 | 118 | if err := o.Etcd.ApplyWithStorageFactoryTo(serverstorage.NewDefaultStorageFactory( 119 | o.Etcd.StorageConfig, 120 | o.Etcd.DefaultStorageMediaType, 121 | server.Codecs, 122 | serverstorage.NewDefaultResourceEncodingConfig(server.Scheme), 123 | apiextensionsserver.DefaultAPIResourceConfigSource(), 124 | nil, 125 | ), serverConfig); err != nil { 126 | return nil, err 127 | } 128 | 129 | // versionGet := version.Get() 130 | // enable OpenAPI schemas 131 | serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(server.Scheme)) 132 | serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(generatedopenapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(server.Scheme)) 133 | serverConfig.OpenAPIConfig.Info.Title = "mutlicluster-server" 134 | serverConfig.OpenAPIV3Config.Info.Title = "mutlicluster-server" 135 | serverConfig.OpenAPIConfig.Info.Version = "1" 136 | serverConfig.OpenAPIV3Config.Info.Version = "1" 137 | serverConfig.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion() 138 | 139 | return serverConfig, nil 140 | } 141 | 142 | func (o Options) restConfig() (*rest.Config, error) { 143 | var config *rest.Config 144 | var err error 145 | if len(o.Kubeconfig) > 0 { 146 | loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.Kubeconfig} 147 | loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) 148 | 149 | config, err = loader.ClientConfig() 150 | } else { 151 | config, err = rest.InClusterConfig() 152 | } 153 | if err != nil { 154 | return nil, fmt.Errorf("unable to construct lister client config: %v", err) 155 | } 156 | // Use protobufs for communication with apiserver 157 | config.ContentType = "application/vnd.kubernetes.protobuf" 158 | err = rest.SetKubernetesDefaults(config) 159 | if err != nil { 160 | return nil, err 161 | } 162 | return config, nil 163 | } 164 | -------------------------------------------------------------------------------- /workshop/pkg/server/registry/cluster/rest.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/fields" 9 | "k8s.io/apimachinery/pkg/labels" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | "k8s.io/apimachinery/pkg/runtime/schema" 12 | "k8s.io/apimachinery/pkg/util/validation/field" 13 | "k8s.io/apiserver/pkg/authentication/user" 14 | genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 15 | "k8s.io/apiserver/pkg/registry/generic" 16 | genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" 17 | "k8s.io/apiserver/pkg/registry/rest" 18 | "k8s.io/apiserver/pkg/storage/names" 19 | "strings" 20 | "workshop/pkg/apis/workshop/v1alpha1" 21 | ) 22 | 23 | type ClusterRest struct { 24 | *genericregistry.Store 25 | } 26 | 27 | var _ rest.StandardStorage = &ClusterRest{} 28 | 29 | //var _ rest.StorageWithReadiness = &clusterRest{} 30 | //var _ rest.TableConvertor = &clusterRest{} 31 | 32 | func (s *ClusterRest) ShortNames() []string { 33 | return []string{"cls"} 34 | } 35 | 36 | func (s *ClusterRest) NamespaceScoped() bool { 37 | return false 38 | } 39 | 40 | func getServiceAccountName(fullName string) string { 41 | parts := strings.Split(fullName, ":") 42 | if len(parts) == 4 && parts[1] == "serviceaccount" { 43 | return parts[len(parts)-1] 44 | } 45 | 46 | return fullName 47 | } 48 | 49 | func predicateListOptions(userInfo user.Info, options *metainternalversion.ListOptions) *metainternalversion.ListOptions { 50 | for _, g := range userInfo.GetGroups() { 51 | if g == user.SystemPrivilegedGroup { 52 | return options 53 | } 54 | } 55 | 56 | tenant := getServiceAccountName(userInfo.GetName()) 57 | if options == nil { 58 | options = &metainternalversion.ListOptions{ 59 | FieldSelector: fields.OneTermEqualSelector("spec.tenant", tenant), 60 | } 61 | } 62 | 63 | if options.FieldSelector == nil { 64 | options.FieldSelector = fields.OneTermEqualSelector("spec.tenant", tenant) 65 | return options 66 | } 67 | options.FieldSelector = fields.AndSelectors(options.FieldSelector, fields.OneTermEqualSelector("spec.tenant", tenant)) 68 | return options 69 | } 70 | 71 | func (s *ClusterRest) validateObjectTenant(ctx context.Context, userInfo user.Info, name string, options *metav1.GetOptions) (runtime.Object, error) { 72 | tenant := getServiceAccountName(userInfo.GetName()) 73 | obj, err := s.Store.Get(ctx, name, options) 74 | if err != nil { 75 | return nil, err 76 | } 77 | cls, ok := obj.(*v1alpha1.Cluster) 78 | if !ok { 79 | return nil, fmt.Errorf("object is not a Cluster") 80 | } 81 | 82 | for _, g := range userInfo.GetGroups() { 83 | if g == user.SystemPrivilegedGroup { 84 | return cls, nil 85 | } 86 | } 87 | 88 | if !(cls.Spec.Tenant == tenant) { 89 | return nil, fmt.Errorf("tenant not match") 90 | } 91 | 92 | return cls, nil 93 | } 94 | 95 | func (s *ClusterRest) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { 96 | userInfo, ok := genericapirequest.UserFrom(ctx) 97 | 98 | if !ok { 99 | return &v1alpha1.ClusterList{}, nil 100 | } 101 | 102 | newListOptions := predicateListOptions(userInfo, options) 103 | return s.Store.List(ctx, newListOptions) 104 | } 105 | 106 | func (s *ClusterRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { 107 | userInfo, ok := genericapirequest.UserFrom(ctx) 108 | if !ok { 109 | return &v1alpha1.Cluster{}, nil 110 | } 111 | 112 | return s.validateObjectTenant(ctx, userInfo, name, options) 113 | } 114 | func (s *ClusterRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { 115 | userInfo, ok := genericapirequest.UserFrom(ctx) 116 | if !ok { 117 | return nil, false, nil 118 | } 119 | 120 | _, err := s.validateObjectTenant(ctx, userInfo, name, &metav1.GetOptions{}) 121 | if err != nil { 122 | return nil, false, err 123 | } 124 | 125 | return s.Store.Delete(ctx, name, deleteValidation, options) 126 | } 127 | 128 | func NewClusterRest(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*ClusterRest, error) { 129 | 130 | strategy := NewStrategy(scheme) 131 | 132 | store := &genericregistry.Store{ 133 | NewFunc: func() runtime.Object { return &v1alpha1.Cluster{} }, 134 | NewListFunc: func() runtime.Object { return &v1alpha1.ClusterList{} }, 135 | PredicateFunc: nil, 136 | DefaultQualifiedResource: v1alpha1.SchemeGroupVersion.WithResource("clusters").GroupResource(), 137 | SingularQualifiedResource: v1alpha1.SchemeGroupVersion.WithResource("cluster").GroupResource(), 138 | CreateStrategy: strategy, 139 | UpdateStrategy: strategy, 140 | DeleteStrategy: strategy, 141 | TableConvertor: rest.NewDefaultTableConvertor(schema.GroupResource{Resource: "cluster"}), 142 | } 143 | 144 | options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs} 145 | if err := store.CompleteWithOptions(options); err != nil { 146 | return nil, err 147 | } 148 | 149 | r := &ClusterRest{Store: store} 150 | 151 | return r, nil 152 | } 153 | 154 | func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { 155 | apiserver, ok := obj.(*v1alpha1.Cluster) 156 | if !ok { 157 | return nil, nil, fmt.Errorf("given object is not a Cluster") 158 | } 159 | 160 | fieldsSet := fields.Set{ 161 | "metadata.name": apiserver.ObjectMeta.Name, 162 | "spec.tenant": apiserver.Spec.Tenant, 163 | } 164 | 165 | return labels.Set(apiserver.ObjectMeta.Labels), fieldsSet, nil 166 | } 167 | 168 | func NewStrategy(typer runtime.ObjectTyper) ClusterStrategy { 169 | return ClusterStrategy{typer, names.SimpleNameGenerator} 170 | } 171 | 172 | type ClusterStrategy struct { 173 | runtime.ObjectTyper 174 | names.NameGenerator 175 | } 176 | 177 | func (ClusterStrategy) NamespaceScoped() bool { 178 | return false 179 | } 180 | 181 | func (ClusterStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {} 182 | 183 | func (ClusterStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {} 184 | 185 | func (ClusterStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { return nil } 186 | 187 | // WarningsOnCreate returns warnings for the creation of the given object. 188 | func (ClusterStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } 189 | 190 | func (ClusterStrategy) AllowCreateOnUpdate() bool { return false } 191 | 192 | func (ClusterStrategy) AllowUnconditionalUpdate() bool { return false } 193 | 194 | func (ClusterStrategy) Canonicalize(obj runtime.Object) {} 195 | 196 | func (ClusterStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { 197 | return field.ErrorList{} 198 | } 199 | 200 | // WarningsOnUpdate returns warnings for the given update. 201 | func (ClusterStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { 202 | return nil 203 | } 204 | -------------------------------------------------------------------------------- /workshop/examples/tenant/cluster-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: workshop.io/v1alpha1 2 | kind: Cluster 3 | metadata: 4 | name: cluster-1 5 | spec: 6 | tenant: workshop-tenant-1 7 | name: cluster-1 8 | apiserver: https://kubernetes.default 9 | kubeconfig: YXBpVmVyc2lvbjogdjEKY2x1c3RlcnM6Ci0gY2x1c3RlcjoKICAgIGNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhOiBMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VNMWVrTkRRV01yWjBGM1NVSkJaMGxDUVVSQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFRldUVkpOZDBWUldVUldVVkZFUlhkd2NtUlhTbXdLWTIwMWJHUkhWbnBOUWpSWVJGUkpNRTFVUVhsTmVrRXpUWHBWTUU1V2IxaEVWRTB3VFZSQmVVMVVRVE5OZWxVd1RsWnZkMFpVUlZSTlFrVkhRVEZWUlFwQmVFMUxZVE5XYVZwWVNuVmFXRkpzWTNwRFEwRlRTWGRFVVZsS1MyOWFTV2gyWTA1QlVVVkNRbEZCUkdkblJWQkJSRU5EUVZGdlEyZG5SVUpCU20xd0NtaHNZa2h3YUc1dGFXMTRWWFozVFhKTk55OXJjalIyUVd3MWRWZFdXRzltZUVkTk5IWnNRMjlLTUVkdU1YQk5hbGg0Ums4ME5GRjNiRU40U2xWVVNuQUtkbVJuTm01cmRXazNkMk0yVjA5eVVrWkJOaTlVTTFNeE4yWk5PRU5KYjJ3eVEwVjBRVVZVVDNKSVJqaFlVRUpWVW1OT1dqaEJXVlZHZUVwSWIzZzFXZ3BQUVhaT1dIVXdVblE1YW5STWQwaHpTbmhEY0d3NVRHRmFObE5OYXpsV1IwUnpNVVpZY0dOMmFYVTJPRXhTY0RGaWVVRllNR2t2VG1aMWVrcDVSRlJXQ201VGFUUnBaMGRqV2xwS2RWTlZSVlpJY1ZsUFlVZFdXbGxUU1RORGJWaDVXVEphZVdkdGMwVTFRMUV6ZVRWVFRXd3haMGxFZVhrM1oyeHdNbXc1ZVdNS2FYRkVZMlIzVFV0M1JqZGhVR3BYWkhsV1dYUkVZek5NZG5CaU5VOUhiVWhHUzFkSVYycHphbGRUUlVwa2VIUnVNMHBzTDJwdWQzRnBaVTVzTW5CYVN3bzVOV3BpYTNka2FXVnFhbTF0TDFvMlZGcHpRMEYzUlVGQllVNURUVVZCZDBSbldVUldVakJRUVZGSUwwSkJVVVJCWjB0clRVRTRSMEV4VldSRmQwVkNDaTkzVVVaTlFVMUNRV1k0ZDBoUldVUldVakJQUWtKWlJVWktWWGN5ZFU1SmFHUkplbVpIYVVseU1qbGpiMGxuZWxKVVZUSk5RVEJIUTFOeFIxTkpZak1LUkZGRlFrTjNWVUZCTkVsQ1FWRkJOekJuTjA5a1IydGxabkozUXpaM2FXNVRUMDF3VWs1RlF6UnNiVmRZVXpZMmRIZGFPRzVOVXpGTlJHUXpZMkpHTWdwMGMwc3dOWE5qZG1oNWJ6ZE5NVFp5WWtGelptVndjMU5TZWtzeFkwaEdjbFZoVHl0cWNFaGpZVGc0UXpkNVQzWXhXVEpaY3pScmNtaFpUM1pwTjNScENreGpPRFEzVEVreFVHUlJSRFZoV1ZwQ1ZuWTRZM2RqTldSNFFqRnljbXNyVGtGWGREbE1ObGRyTjBSelNtaHNUQ3RTVG1KQlVsTXlXbEl5Vm5KRFFYUUtLMmRqVkcxSGRsSnZhR1I1TVV0Q1duRkJia0ZYTVhWS1FXRjFjV1I2VG5oT0t6bEpZa0ZDZEUxWlRuZEtkMjE2YTBKWlNuWnpXREoxYTBFNVNtaGtlQXBtYWxobFRrVk1RMVk1WVdkTmJVUkVOM3BTT1dFdmRXUkZhelJxWm1nelUwbHhNbWwyTkV0M2RYUkVMMlZWVFRCbWFVTjRiM1JDZWtZeFZHRlZjMjFpQ21rME9VOWFMelIxVjA1MWNXMDNaVUZCUjBwemJEaE5jMDQzWWpscVpuSlZXV3R1UndvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PQogICAgc2VydmVyOiBodHRwczovLzEwLjEyLjE4My4yNjo2NDQzCiAgbmFtZToga3ViZXJuZXRlcwpjb250ZXh0czoKLSBjb250ZXh0OgogICAgY2x1c3Rlcjoga3ViZXJuZXRlcwogICAgdXNlcjoga3ViZXJuZXRlcy1hZG1pbgogIG5hbWU6IGt1YmVybmV0ZXMtYWRtaW5Aa3ViZXJuZXRlcwpjdXJyZW50LWNvbnRleHQ6IGt1YmVybmV0ZXMtYWRtaW5Aa3ViZXJuZXRlcwpraW5kOiBDb25maWcKcHJlZmVyZW5jZXM6IHt9CnVzZXJzOgotIG5hbWU6IGt1YmVybmV0ZXMtYWRtaW4KICB1c2VyOgogICAgY2xpZW50LWNlcnRpZmljYXRlLWRhdGE6IExTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJGZWtORFFXWjFaMEYzU1VKQlowbEpZM1EzWkhGRlNXMVVlRWwzUkZGWlNrdHZXa2xvZG1OT1FWRkZURUpSUVhkR1ZFVlVUVUpGUjBFeFZVVUtRWGhOUzJFelZtbGFXRXAxV2xoU2JHTjZRV1ZHZHpCNVRrUkZkMDFxVFhkT2VrMHhUa1JXWVVaM01IbE9WRVYzVFdwTmQwNTZUVEZPUkdSaFRVUlJlQXBHZWtGV1FtZE9Wa0pCYjFSRWJrNDFZek5TYkdKVWNIUlpXRTR3V2xoS2VrMVNhM2RHZDFsRVZsRlJSRVY0UW5Ka1YwcHNZMjAxYkdSSFZucE1WMFpyQ21KWGJIVk5TVWxDU1dwQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSUlVaQlFVOURRVkU0UVUxSlNVSkRaMHREUVZGRlFYZEhObVZQUkd0dmFVOWFUakpCWTFnS09VOVFhVWhRYkZkaWQzaFNPRTQzZFN0RWNtTkZiM1UxYkVSeGMxcG5SbUpEUjNodFVuWmhTMGhTVGxBd1JtY3lVMkpSZHpOWmRFSklObTUwVVVsc1NBcG1XVXBxU1d4dFdHVnViRFpFT1RsWFJ6Z3ZXRlYwVVhoWllteEhUMUJHWVdkcFFsTmFWRmMwWm1ocVJVWXliMk5HZWl0c1dDdG9jbnBFVm5aeE9TOTFDbFpuWm5ZME5GRlBha1JMVnpKMEt5OTRNRXA0U1VaQlFsRXZZVXBDTTJOUU5sZHBWRUZrUlZCRk9FSlFWalJVSzNaRU1HVXhXR2RVVEhkT2FUTk5ZbWNLVW1sMWNsTnJRMWhGUkU1NUszcElVVzFoZDNKak9EWlJORzQ1ZVVOeGJtOHZhMk5QYUZZd2RVdE9UWEZEVkVjMVoyVnpZa3BRU1dsRVQzbzRPVmxLY1FwNWNXUTJTRWRLY0RKNWFYY3hNako2YW05dk0wUlFWV1pZVERKV2FuUllZblpyV2tnMlNVSktXRUoxVFhkSFVrbEdWbEpyY0ZkRk5WQmpNVGw0TDNOTENtYzJOU3N4VVVsRVFWRkJRbTh3WjNkU2FrRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwSmhRWGRGZDFsRVZsSXdiRUpCZDNkRFoxbEpTM2RaUWtKUlZVZ0tRWGRKZDBoM1dVUldVakJxUWtKbmQwWnZRVlZzVkVSaE5EQnBSakJxVGpoaFNXbDJZakY1WjJsRVRrWk9WRmwzUkZGWlNrdHZXa2xvZG1OT1FWRkZUQXBDVVVGRVoyZEZRa0ZGV0VNeU1URXZZVTE1YWpkdU9TdE1kbTFzYW5rNU4xZEZUbU5rVERkNVlsSlFTbmczU1dOS1owUm1iVU5PZFRkdE9HMDVkRkl4Q2pWbFUycGlOREE0ZG5GVVltVnJSV0V5TTFoNVNtd3hZa2czTkZoclJTOUpUMFIyTWtKVFJHUjFhek5OVFZwVVFsWmFXRFZEY1dFMWFEVm5ORE5yYkVjS2VESlFVbHAwV0ZSSWFuUlJZMlF3YTFJMVdIZEZZa2hUUWxkTU5VRnhWRkp6TDJadFlrWXJWa3N6VUM5WlJrRlZURzVFVGtGdldraE9NV3hDUjNKTWRRcG5lakpKVVhsemJreEtWbXRNYVVGRk9HcE9WVlpaUzNseVlXRk9ialZrUTBSVkwxUmlXVFZNYVhNclVsaEdSWE55TTNaMVZEbHFOMHRPTUZWS1MzcFNDbWR4VVVOWUsyaGxiMHhXYnpSamJWQTBVamxITml0MWNpdEpRbEZpVG1adGFGbGhiVTVPWXpKV2FIcHFVMkZ2UXk5TGRHdFJhVkp4UzNNdk5VMUdWVGtLU1ZsRVlrMW9Xa2Q0ZGpObVZ5dHlUMDgzVlRsNVprdHBiMk52S3psSk5EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0KICAgIGNsaWVudC1rZXktZGF0YTogTFMwdExTMUNSVWRKVGlCU1UwRWdVRkpKVmtGVVJTQkxSVmt0TFMwdExRcE5TVWxGYjNkSlFrRkJTME5CVVVWQmQwYzJaVTlFYTI5cFQxcE9Na0ZqV0RsUFVHbElVR3hYWW5kNFVqaE9OM1VyUkhKalJXOTFOV3hFY1hOYVowWmlDa05IZUcxU2RtRkxTRkpPVURCR1p6SlRZbEYzTTFsMFFrZzJiblJSU1d4SVpsbEtha2xzYlZobGJtdzJSRGs1VjBjNEwxaFZkRkY0V1dKc1IwOVFSbUVLWjJsQ1UxcFVWelJtYUdwRlJqSnZZMFo2SzJ4WUsyaHlla1JXZG5FNUwzVldaMloyTkRSUlQycEVTMWN5ZENzdmVEQktlRWxHUVVKUkwyRktRak5qVUFvMlYybFVRV1JGVUVVNFFsQldORlFyZGtRd1pURllaMVJNZDA1cE0wMWlaMUpwZFhKVGEwTllSVVJPZVN0NlNGRnRZWGR5WXpnMlVUUnVPWGxEY1c1dkNpOXJZMDlvVmpCMVMwNU5jVU5VUnpWblpYTmlTbEJKYVVSUGVqZzVXVXB4ZVhGa05raEhTbkF5ZVdsM01USXllbXB2YnpORVVGVm1XRXd5Vm1wMFdHSUtkbXRhU0RaSlFrcFlRblZOZDBkU1NVWldVbXR3VjBVMVVHTXhPWGd2YzB0bk5qVXJNVkZKUkVGUlFVSkJiMGxDUVVSdVlWSTVhMUpZYURGcFZpczNiQXB0ZHpneE16aHVhMDFHWTNOa1oyOWlhU3MyTTBNeWFrdzVlSGRaVlRkS1RFbEJiekZTVERVMk5tMVVOVnBHY0RNeWMwcHVNbEJWVTBrMU9GbFpSWEY0Q25WMmRVSXdjbTlIUVRKT0x6RXZSMnB5UjJseFNVMXpNMGhwTWt0SVVrZE1hR3BHZFRsRVlWaFpkMDFQUVZFMGJFTmFURE4zU1d4SUx5OXBhRGRDVUZvS1R5OWtSRnBhZVdGSVEwZE5TMEY0Wm1wdVIybHBVVFF3TXpGNVJqUlBaR2hyTTNCaFoyZGpMM0JFTlRWM09GUndhVGhXV1VKUlJtUjRLMlpCZVZOcE1BcFlTRU14TUVReWJsZzBXakp1V1VNdk9FdHJNMHhOVEhaNVZWWlhVREJyZVdZdmNXMXhTalEyVDI1WGVrWXlVWFF5Tkdvek4xTnBOa1FyVTB4VVNHaHVDblpoZVZKTlVIaERiM1p2TkhaaFJtODVZa3cyVFZObFEzVlZkSFE0Y0hSUFdFWlRWVEZrV1RsbGJrMXNWV1ZTZFZOTmFGUkJXa3hXZW5wWFUzSjFiekVLU2tSQlkyWmtWVU5uV1VWQmVVOUJPR1kwZHpaQlExWXhNV2h2UmxscmExbFpUMEpQVWt4TVFVTkxXSFY2Ykd4Rk1WcG9hVFYxTm5ReGFYTTNXVGxSWkFwMGRESjBUbTl3UVhKb1ZraHBOR1pCWjJod09XNWlZM3BOZVhRMVdWZHNTRzFuU1c1emFUWmFVelpLTlRBd1JYaEJhSGRvYlRORVRIUmpkRVZtY0ZZekNqSTBVRGR0TW1kaVVIWXdhMUpFTm14TE1VVXdhMDkzTmxscFVFbG9NelJRYW5SbVFuUldOVEpoZEM5R05rbG5UVlJVTW5WdmNrMURaMWxGUVRsVU1IZ0thamhsT0haTk5HVjZPWGgxVmtaUlYyNTBXRVJ0TW1GVFNYZ3ZTMGxpWWpCRGFtdG9WMjlNV0hrM1IxVm1jVkIyVjJWUk5WcHFWMWxTZUVWTlVFY3lUZ295WmtNM2FVOUlOamNyUjNoNk1HeGhUVTQ1TW01eWRFdHFUalJ6TnpWUWNFeGlRMVF5ZVZOM1JuRmxSakJWWVM5UWQweGhhVmgyVTB0VlRVcHBXV1pLQ214UVYzZEdLMWxoV0dsNmFHRlBVbkZhTDB4b1IxUkxWVU5sVm1jMWVsaDBXV2RwWmk5R1kwTm5XVVZCYVRNNWRHaEJXVlp2VmxKcFlVeFVXbGRGTHpZS2NHZDNabFJ2V0hvMVp6SldjMkZqT0hKcWFVNU9iVWsyVW5GcWRreFllRFI0YVRGaWVFdDRkVVJ1WVc5dlRGVTVZM2g1UTJJMmVIZ3laVGgxT0M4M2R3cDNibk5OY0dscVIxQkZaM2MwWjNjM2VFbE9WWE15VnpWcmFETXZRM2RyZUcxNloyc3lkQ3RLVkVWNmRtbHdWVmRxZFZwME5IUmhjVVpOTVZWNlVVMHlDazl5VTB0NVZFVTBaWGtyTjJWTGFFTkpjMGhpVEZKRlEyZFpRVWROTVV4cFpVMUdZalJNV2tWMVUxVk5LM05rVEc5emJqaFVkRFphUW5Jck9HeEllV1FLUjFrMmRsWkRiVk50T1ROUFUyVlVUMWRCWm1abWNrWnVPSEJXYkc1aWREbGlRMjVDY25vd1FpOWhibUZRVG1WTU4zTndNeTkyU2tkc05FNDFWbnB6U2dwRVlreG1WWEJGYkUxWU1HWnhiazVDWVRWblRUWTVWMGhrTkhSNFREYzJORUp1U1RaWFNGbEpUbGxvU21RMlZEbEdaRmc1U21oek5rZE9UMWhhVUZOdkNtbDFSa2x3ZDB0Q1owaHFhWEYwZEhOT2FXUXJWRGcxZVM4dllXdHZkV3hIY2pnNFJrOXFiMU12Vms1clYxRndhblpqWWtVMlRFeERXV3RoVEdGVWN6UUtUM2xUWW5aSVRERlJPRGhtWWpKUGQyTkVkVGxOU1RsTFptTjFhbkoxSzBSSVVsRkJSM2htVG5CVGEzVkdha3QyUkRoNGJ6Uk9OSEFyVlZSbmRsRjNhd3BCZEVGbFdtOVhhbnBRZDBsbU1uZHlRV1J1TmpGaWIzUjZObmxhYlhkUWJXTXdVemxKVlZvcmJHMHdTa1Y0WjBkSmVXUlJDaTB0TFMwdFJVNUVJRkpUUVNCUVVrbFdRVlJGSUV0RldTMHRMUzB0Q2c9PQo= -------------------------------------------------------------------------------- /pkg/kubeclient/kubeclient.go: -------------------------------------------------------------------------------- 1 | package kubeclient 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "github.com/yeahx/kubeapi-inspector/pkg/utils" 13 | 14 | openapi_v2 "github.com/google/gnostic-models/openapiv2" 15 | "k8s.io/apimachinery/pkg/runtime" 16 | "k8s.io/client-go/discovery" 17 | 18 | authorizationv1 "k8s.io/api/authorization/v1" 19 | rbacv1 "k8s.io/api/rbac/v1" 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/client-go/kubernetes" 22 | authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1" 23 | "k8s.io/client-go/rest" 24 | "k8s.io/client-go/tools/clientcmd" 25 | ) 26 | 27 | type KubeClient struct { 28 | clientset *kubernetes.Clientset 29 | doc *openapi_v2.Document 30 | namespace string 31 | DiscoveryClient discovery.DiscoveryInterface 32 | AuthClient authorizationv1client.AuthorizationV1Interface 33 | Rules []rbacv1.PolicyRule 34 | // [accounts.space.test.io] = Resource{}, [accounts.space.test.io/status] = Resource{} 35 | Resources map[string]Resource 36 | } 37 | 38 | type APIGroup string 39 | 40 | type Resource struct { 41 | GroupName string 42 | GroupVersion string 43 | Remote bool 44 | *metav1.APIResource 45 | } 46 | 47 | // NewKubeClient creates a Kubernetes client. 48 | func NewKubeClient(kubeconfig, namespace string, insecureSkipTLS bool) (*KubeClient, error) { 49 | var config *rest.Config 50 | var err error 51 | var inCluster bool 52 | 53 | const namespaceFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" 54 | 55 | if namespace == "" { 56 | ns, err := os.ReadFile(namespaceFile) 57 | if err != nil { 58 | namespace = "default" 59 | } else { 60 | namespace = string(ns) 61 | } 62 | } 63 | 64 | if kubeconfig == "" { 65 | //home := os.Getenv("HOME") 66 | //kubeconfig = fmt.Sprintf("%s/.kube/config", home) 67 | 68 | config, err = rest.InClusterConfig() 69 | if err != nil { 70 | return nil, err 71 | } 72 | inCluster = true 73 | } else { 74 | config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) 75 | if err != nil { 76 | return nil, fmt.Errorf("failed to build config from path %s: %w", kubeconfig, err) 77 | } 78 | 79 | // Set Insecure to true (skip TLS verification) only if the flag is set 80 | // If the flag is not set, set Insecure to false if certificate data or files are provided 81 | if insecureSkipTLS { 82 | config.Insecure = true 83 | } else if config.CAData != nil || config.CertData != nil || config.CAFile != "" || config.CertFile != "" || config.KeyFile != "" { 84 | config.Insecure = false 85 | } else { 86 | config.Insecure = true 87 | } 88 | } 89 | 90 | clientset, err := kubernetes.NewForConfig(config) 91 | if err != nil { 92 | return nil, fmt.Errorf("failed to create kubeclient: %w", err) 93 | } 94 | 95 | kc := &KubeClient{clientset: clientset, namespace: namespace} 96 | 97 | kc.DiscoveryClient = kc.clientset.Discovery() 98 | kc.AuthClient = kc.clientset.AuthorizationV1() 99 | 100 | //ssr := &authenticationv1.SelfSubjectReview{} 101 | //res, err := kc.clientset.AuthenticationV1().SelfSubjectReviews().Create(context.TODO(), ssr, metav1.CreateOptions{}) 102 | //if err != nil { 103 | // return nil, err 104 | //} 105 | 106 | kc.Resources = make(map[string]Resource) 107 | 108 | // check rule review 109 | if err = kc.loadRBACPolicy(); err != nil { 110 | return nil, err 111 | } 112 | 113 | if inCluster { 114 | fmt.Printf("[*] Use in-cluster mode, Host: %s, Token %s", config.Host, config.BearerToken) 115 | } else { 116 | fmt.Printf("[*] Load %s config, Host: %s, Token: %s\n", kubeconfig, config.Host, config.BearerToken) 117 | } 118 | 119 | return kc, nil 120 | } 121 | 122 | func (kc *KubeClient) Get(ctx context.Context, uri string) ([]byte, error) { 123 | return kc.clientset.RESTClient().Get().RequestURI(uri).DoRaw(ctx) 124 | } 125 | 126 | func (kc *KubeClient) Watch(uri string) ([]byte, error) { 127 | params := "?watch=true&timeoutSeconds=2" 128 | 129 | uri = fmt.Sprintf("%s%s", uri, params) 130 | 131 | b, err := kc.Get(context.TODO(), uri) 132 | //fmt.Printf("%s", string(b)) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | return b, nil 138 | } 139 | 140 | func (kc *KubeClient) List(uri string) ([]byte, error) { 141 | b, err := kc.Get(context.TODO(), uri) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | return b, nil 147 | } 148 | 149 | // DeleteCollection use deletecollection verb to test, make sure target apiserver support dryRun mode. 150 | func (kc *KubeClient) DeleteCollection(uri string) ([]byte, error) { 151 | params := "?dryRun=All" 152 | uri = fmt.Sprintf("%s%s", uri, params) 153 | 154 | b, err := kc.clientset.RESTClient().Delete().RequestURI(uri).DoRaw(context.TODO()) 155 | if err != nil { 156 | return nil, err 157 | } 158 | 159 | return b, nil 160 | } 161 | 162 | func (kc *KubeClient) GetClientSet() *kubernetes.Clientset { 163 | if kc.clientset == nil { 164 | return nil 165 | } 166 | 167 | return kc.clientset 168 | } 169 | 170 | // loadRBACPolicy load current accounts rbac rules. 171 | func (kc *KubeClient) loadRBACPolicy() error { 172 | sar := &authorizationv1.SelfSubjectRulesReview{ 173 | Spec: authorizationv1.SelfSubjectRulesReviewSpec{ 174 | Namespace: kc.namespace, 175 | }, 176 | } 177 | 178 | res, err := kc.AuthClient.SelfSubjectRulesReviews().Create(context.TODO(), sar, metav1.CreateOptions{}) 179 | if err != nil { 180 | return err 181 | } 182 | 183 | // Verbs: [Get,List]; APIGroup: space.test.io; Resources: [accounts, accounts/status] 184 | kc.Rules = utils.ConvertToPolicyRule(res.Status) 185 | return nil 186 | } 187 | 188 | func (kc *KubeClient) FetchCRDApis() error { 189 | fmt.Printf("[*] Starting to discovery apis\n") 190 | body, err := kc.clientset.RESTClient().Get(). 191 | AbsPath("/apis"). 192 | SetHeader("Accept", runtime.ContentTypeJSON). 193 | Do(context.TODO()). 194 | Raw() 195 | 196 | if err != nil { 197 | return err 198 | } 199 | 200 | apiGroupList := &metav1.APIGroupList{} 201 | 202 | err = json.Unmarshal(body, apiGroupList) 203 | if err != nil { 204 | return err 205 | } 206 | 207 | for _, group := range apiGroupList.Groups { 208 | if utils.IsNativeAPI(group.Name) { 209 | continue 210 | } 211 | for _, version := range group.Versions { 212 | resourceList, err := kc.DiscoveryClient.ServerResourcesForGroupVersion(version.GroupVersion) 213 | if err != nil { 214 | log.Printf("could not retrieve resource list for group version %s: %v", version.GroupVersion, err) 215 | continue 216 | } 217 | for _, resource := range resourceList.APIResources { 218 | r := Resource{ 219 | GroupName: group.Name, 220 | GroupVersion: version.GroupVersion, 221 | Remote: isRemoteApi(resource), 222 | APIResource: resource.DeepCopy(), 223 | } 224 | 225 | r.Version = version.Version 226 | 227 | combine := utils.CombineResourceGroup(r.APIResource.Name, r.GroupName) 228 | 229 | if _, ok := kc.Resources[combine]; !ok { 230 | kc.Resources[combine] = r 231 | } 232 | } 233 | } 234 | } 235 | 236 | apisCount := len(kc.Resources) 237 | 238 | fmt.Printf("[*] Discovered %d custom apis\n", apisCount) 239 | 240 | return nil 241 | } 242 | 243 | func (kc *KubeClient) DownloadOpenApiSchema() (*openapi_v2.Document, error) { 244 | fmt.Printf("[*] Starting to download openapi definition\n") 245 | doc, err := kc.clientset.OpenAPISchema() 246 | if err != nil { 247 | return nil, errors.New(fmt.Sprintf("failed to get openapi schema: %v", err)) 248 | } 249 | kc.doc = doc 250 | return kc.doc, nil 251 | } 252 | 253 | // isRemoteApi determines whether a given API resource is served by a remote API server. 254 | func isRemoteApi(resource metav1.APIResource) bool { 255 | part := strings.Split(resource.Name, "/") 256 | 257 | return resource.StorageVersionHash == "" && len(part) == 1 258 | } 259 | -------------------------------------------------------------------------------- /pkg/inspector/inspector.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/yeahx/kubeapi-inspector/pkg/kubeclient" 10 | "github.com/yeahx/kubeapi-inspector/pkg/utils" 11 | 12 | openapi_v2 "github.com/google/gnostic-models/openapiv2" 13 | apierrors "k8s.io/apimachinery/pkg/api/errors" 14 | 15 | "net" 16 | ) 17 | 18 | type Inspector struct { 19 | client *kubeclient.KubeClient 20 | schemaMap map[string]*openapi_v2.Schema 21 | sensitiveInfoRegexps []*regexp.Regexp 22 | sensitiveCheckFunc func(p string) bool 23 | pathBodyParameterMap map[string]*openapi_v2.BodyParameter // map[pathItem.Name]*BodyParameter 24 | pathPathParameterMap map[string]*openapi_v2.NonBodyParameter // map[pathItem.Name]*NonBodyParameter 25 | } 26 | 27 | // DNSServiceInfo 28 | type DNSServiceInfo struct { 29 | Target string 30 | Port uint16 31 | Priority uint16 32 | Weight uint16 33 | } 34 | 35 | func NewInspector(client *kubeclient.KubeClient, sensitiveCheckFunc func(string) bool) *Inspector { 36 | regs := compileRegexps(sensitivePatterns) 37 | fmt.Printf("[*] Load %d sensitive pattern\n", len(regs)) 38 | 39 | i := &Inspector{client: client, sensitiveInfoRegexps: regs, sensitiveCheckFunc: sensitiveCheckFunc} 40 | 41 | if i.sensitiveCheckFunc == nil { 42 | i.sensitiveCheckFunc = func(p string) bool { 43 | for _, re := range i.sensitiveInfoRegexps { 44 | if r := re.MatchString(p); r { 45 | return true 46 | } 47 | } 48 | return false 49 | } 50 | } 51 | 52 | return i 53 | } 54 | 55 | // DiscoveryAPIServiceBySRV 56 | func (i *Inspector) DiscoveryAPIServiceBySRV() ([]DNSServiceInfo, error) { 57 | fmt.Printf("[*] Starting discovery api service by coredns\n") 58 | 59 | dnsPatterns := []string{ 60 | "any.any.svc.cluster.local.", 61 | "any.any.any.svc.cluster.local.", 62 | } 63 | 64 | var results []DNSServiceInfo 65 | for _, dns := range dnsPatterns { 66 | _, srvs, err := net.LookupSRV("", "", dns) 67 | if err != nil { 68 | fmt.Printf("[*] DNS Query Eror %s: %s\n", dns, err.Error()) 69 | continue 70 | } 71 | 72 | for _, srv := range srvs { 73 | 74 | results = append(results, DNSServiceInfo{ 75 | Target: srv.Target, 76 | Port: srv.Port, 77 | Priority: srv.Priority, 78 | Weight: srv.Weight, 79 | }) 80 | } 81 | } 82 | 83 | // print result 84 | for _, r := range results { 85 | fmt.Printf("[+] Service: %s:%d\n", 86 | r.Target, r.Port) 87 | } 88 | 89 | return results, nil 90 | } 91 | 92 | func (i *Inspector) DetectObjectLeak(group, version, resource string) error { 93 | var errors []error 94 | 95 | // is subresource should be skip. 96 | if len(strings.Split(resource, "/")) > 1 { 97 | return nil 98 | } 99 | 100 | uri := utils.MakeUri(group, version, resource) 101 | 102 | baseRes, err := i.client.List(uri) 103 | if err != nil { 104 | // 403 105 | if !apierrors.IsForbidden(err) { 106 | errors = append(errors, err) 107 | fmt.Printf("[-] verb List access apiserver failed: %v", err) 108 | } 109 | } 110 | 111 | // diff object? 112 | // baseObj, baseLen, err := bytesToUnstructuredList(baseRes) 113 | _, baseLen, err := utils.BytesToUnstructuredList(baseRes) 114 | //if err != nil { 115 | // errors = append(errors, err) 116 | //} 117 | 118 | watchRes, err := i.client.Watch(uri) 119 | if err != nil { 120 | if !apierrors.IsForbidden(err) { 121 | errors = append(errors, err) 122 | fmt.Printf("[-] verb Watch access apiserver failed: %v", err) 123 | } 124 | } 125 | 126 | watchObj, watchLen, err := utils.WatchResToUnstructuredList(watchRes) 127 | // diff 128 | 129 | if watchLen > baseLen { 130 | utils.RemoveObjectFields(watchObj, uri) 131 | utils.PrintResult(utils.MakeUri(group, version, resource), "Watch", watchObj) 132 | return nil 133 | } 134 | 135 | dcRes, err := i.client.DeleteCollection(uri) 136 | if err != nil { 137 | if !apierrors.IsForbidden(err) { 138 | errors = append(errors, err) 139 | fmt.Printf("[-] verb DeleteCollection access apiserver failed: %v", err) 140 | } 141 | // 403 skip 142 | } 143 | 144 | dcObj, dcLen, err := utils.BytesToUnstructuredList(dcRes) 145 | 146 | if dcLen > baseLen { 147 | utils.RemoveObjectFields(dcObj, uri) 148 | utils.PrintResult(utils.MakeUri(group, version, resource), "DeleteCollection", watchObj) 149 | return nil 150 | } 151 | 152 | // lres wres 153 | 154 | return nil 155 | } 156 | 157 | func (i *Inspector) DetectSensitiveField(group, version, resource string) error { 158 | uri := utils.MakeUri(group, version, resource) 159 | sensitiveFields := make(map[string]bool) 160 | path := []string{"$"} 161 | 162 | bodyParameter, ok := i.pathBodyParameterMap[uri] 163 | if !ok { 164 | return errors.New(fmt.Sprintf("%s not found body parameter", uri)) 165 | } 166 | refName := strings.TrimPrefix(bodyParameter.GetSchema().XRef, "#/definitions/") 167 | refSchema, exists := i.schemaMap[refName] 168 | if !exists { 169 | return errors.New(fmt.Sprintf("%s not found ref %s schema", uri, refName)) 170 | } 171 | 172 | resolveSchema(refSchema, i.schemaMap, path, sensitiveFields, i.sensitiveCheckFunc) 173 | //schemaMap := make(map[string]*openapi_v2.Schema) 174 | 175 | return nil 176 | } 177 | 178 | func (i *Inspector) ParseDocument(doc *openapi_v2.Document) error { 179 | i.schemaMap = make(map[string]*openapi_v2.Schema) 180 | i.pathBodyParameterMap = make(map[string]*openapi_v2.BodyParameter) 181 | i.pathPathParameterMap = make(map[string]*openapi_v2.NonBodyParameter) 182 | 183 | if doc.GetDefinitions() == nil { 184 | return errors.New("openapi document definitions is nil") 185 | } 186 | 187 | if properties := doc.GetDefinitions().GetAdditionalProperties(); properties != nil { 188 | for _, p := range doc.GetDefinitions().GetAdditionalProperties() { 189 | i.schemaMap[p.GetName()] = p.GetValue() 190 | } 191 | } 192 | 193 | if doc.GetPaths() == nil { 194 | return errors.New("openapi document paths is nil") 195 | } 196 | 197 | for _, pathItem := range doc.GetPaths().GetPath() { 198 | if pathItem.GetValue() != nil && pathItem.GetValue().GetPost() != nil { 199 | bodyParam, err := getPostBodyParameter(pathItem.GetValue().GetPost().GetParameters()) 200 | if err != nil { 201 | continue 202 | } 203 | i.pathBodyParameterMap[pathItem.Name] = bodyParam 204 | } 205 | 206 | } 207 | 208 | fmt.Printf("[*] Parse openapi schema success.\n") 209 | 210 | return nil 211 | } 212 | 213 | // resolveSchema 解析schema,处理可能的$xref引用 214 | func resolveSchema(schema *openapi_v2.Schema, definitions map[string]*openapi_v2.Schema, 215 | path []string, sensitiveFields map[string]bool, checkFunc func(ppName string) bool) { 216 | if schema == nil { 217 | return 218 | } 219 | 220 | // 解析properties 221 | if schema.Properties != nil { 222 | for _, pair := range schema.Properties.AdditionalProperties { 223 | propertyName := pair.Name 224 | propertySchema := pair.Value 225 | 226 | // 更新路径 227 | currentPath := append(path, propertyName) 228 | 229 | // 检查是否为敏感字段 230 | if checkFunc(propertyName) { 231 | fullPath := strings.Join(currentPath, ".") 232 | fmt.Printf("[+] sensitive field found: %s\n", fullPath) 233 | sensitiveFields[fullPath] = true 234 | } 235 | 236 | // check schema has xref 237 | if propertySchema.XRef != "" { 238 | refName := strings.TrimPrefix(propertySchema.XRef, "#/definitions/") 239 | refSchema, exists := definitions[refName] 240 | if exists { 241 | resolveSchema(refSchema, definitions, currentPath, sensitiveFields, checkFunc) 242 | } 243 | } else { 244 | // 递归解析非引用的schema 245 | resolveSchema(propertySchema, definitions, currentPath, sensitiveFields, checkFunc) 246 | } 247 | } 248 | } 249 | } 250 | 251 | func getPostBodyParameter(items []*openapi_v2.ParametersItem) (*openapi_v2.BodyParameter, error) { 252 | if items == nil { 253 | return nil, errors.New("ParametersItem is nil") 254 | } 255 | 256 | for _, item := range items { 257 | if item.GetParameter() != nil && item.GetParameter().GetBodyParameter() != nil && 258 | item.GetParameter().GetBodyParameter().GetName() == "body" { 259 | return item.GetParameter().GetBodyParameter(), nil 260 | } else { 261 | print(item.GetParameter()) 262 | } 263 | } 264 | 265 | return nil, nil 266 | } 267 | 268 | func compileRegexps(parttens []string) []*regexp.Regexp { 269 | var regexps []*regexp.Regexp 270 | for _, partten := range parttens { 271 | re, err := regexp.Compile(partten) 272 | if err != nil { 273 | 274 | } 275 | regexps = append(regexps, re) 276 | } 277 | 278 | return regexps 279 | } 280 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= 6 | github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 7 | github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= 8 | github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 9 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 10 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 11 | github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= 12 | github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= 13 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 14 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 15 | github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= 16 | github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= 17 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 18 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 19 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 20 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 21 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 22 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 23 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 24 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 25 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 26 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 27 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 28 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 29 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 30 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 32 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 33 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 34 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 35 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 36 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 37 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 38 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 39 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 40 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 41 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 42 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 43 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 44 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 45 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= 46 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 47 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 50 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 51 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 52 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 53 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 54 | github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= 55 | github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 56 | github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 57 | github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 58 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 59 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 62 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 63 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 64 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 65 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 66 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 67 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 68 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 69 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 70 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 71 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 72 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 73 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 74 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 75 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 76 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 77 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 78 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 79 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 80 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 81 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 82 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 83 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 84 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 85 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 86 | golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= 87 | golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 88 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 89 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 90 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 91 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 92 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 93 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 95 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 96 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 97 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 98 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 99 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 100 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 101 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 102 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 103 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 104 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 105 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 106 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 107 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 108 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 109 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 110 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 111 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 113 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 114 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 115 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 116 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 117 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 118 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 119 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 120 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 121 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 122 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 123 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 124 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 125 | k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= 126 | k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= 127 | k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= 128 | k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= 129 | k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= 130 | k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= 131 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 132 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 133 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= 134 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= 135 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= 136 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 137 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 138 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 139 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 140 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 141 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 142 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= 143 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 144 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 145 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 146 | -------------------------------------------------------------------------------- /workshop/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 2 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 3 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= 4 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 5 | github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 6 | github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 7 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= 8 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 9 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 10 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 11 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 12 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 13 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 14 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 15 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 16 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 17 | github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= 18 | github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= 19 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 20 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 21 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 22 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 23 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 24 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 28 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 30 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 31 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 32 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 33 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 34 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 35 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 36 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 37 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 38 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 39 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 40 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 41 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 42 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 43 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 44 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 45 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 46 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 47 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 48 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 49 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 50 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 51 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 52 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 53 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 54 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 55 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 56 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 57 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 58 | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= 59 | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 60 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 61 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 62 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 63 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 64 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 65 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 66 | github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= 67 | github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= 68 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 69 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 70 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 71 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 72 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 73 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 74 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 75 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 76 | github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= 77 | github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 78 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 79 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 80 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 81 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 82 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= 83 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 84 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 85 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 86 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 87 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 88 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= 89 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= 90 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 91 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 92 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 93 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 94 | github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= 95 | github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 96 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 97 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 98 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 99 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 100 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 101 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 102 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 103 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 104 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 105 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 106 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 107 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 108 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 109 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 110 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 111 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 112 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 113 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 114 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 115 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 116 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 117 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 118 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 119 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 120 | github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= 121 | github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= 122 | github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= 123 | github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= 124 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 125 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 126 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 127 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 128 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 129 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 130 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 131 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 132 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 133 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 134 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 135 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 136 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 137 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 138 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 139 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 140 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 141 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 142 | github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 143 | github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= 144 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 145 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 146 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 147 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 148 | github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= 149 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 150 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 151 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 152 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 153 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 154 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 155 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 156 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 157 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 158 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 159 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 160 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= 161 | github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= 162 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 163 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 164 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= 165 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 166 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 167 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 168 | go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= 169 | go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= 170 | go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= 171 | go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= 172 | go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= 173 | go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= 174 | go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= 175 | go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= 176 | go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= 177 | go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= 178 | go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= 179 | go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= 180 | go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= 181 | go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= 182 | go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= 183 | go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= 184 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= 185 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= 186 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= 187 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= 188 | go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= 189 | go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= 190 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= 191 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= 192 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= 193 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= 194 | go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= 195 | go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= 196 | go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= 197 | go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= 198 | go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= 199 | go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= 200 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 201 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 202 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 203 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 204 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 205 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 206 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 207 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 208 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 209 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 210 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 211 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 212 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 213 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 214 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 215 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 216 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 217 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 218 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 219 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 220 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 221 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= 222 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 223 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 224 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 225 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 226 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 227 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 228 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 229 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 230 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 231 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 232 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 233 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 234 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 235 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 236 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 237 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 238 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 239 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 240 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 241 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 242 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 243 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 244 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 245 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 246 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 247 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 248 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 249 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 250 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 251 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 252 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 253 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 254 | google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= 255 | google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= 256 | google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= 257 | google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= 258 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= 259 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 260 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 261 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 262 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 263 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 264 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 265 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 266 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 267 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 268 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 269 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 270 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 271 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 272 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 273 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 274 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 275 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 276 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 277 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 278 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 279 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 280 | k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= 281 | k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= 282 | k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= 283 | k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= 284 | k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= 285 | k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= 286 | k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= 287 | k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= 288 | k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= 289 | k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= 290 | k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= 291 | k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= 292 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 293 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 294 | k8s.io/kms v0.31.2 h1:pyx7l2qVOkClzFMIWMVF/FxsSkgd+OIGH7DecpbscJI= 295 | k8s.io/kms v0.31.2/go.mod h1:OZKwl1fan3n3N5FFxnW5C4V3ygrah/3YXeJWS3O6+94= 296 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= 297 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 298 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= 299 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 300 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= 301 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 302 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 303 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 304 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 305 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 306 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 307 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 308 | --------------------------------------------------------------------------------