├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yaml ├── LICENSE ├── Makefile ├── README.md ├── cmd └── kubectl-view_serviceaccount_kubeconfig.go ├── go.mod ├── go.sum └── pkg └── cmd ├── util.go ├── util_test.go ├── view-serviceaccount-kubeconfig.go └── view-serviceaccount-kubeconfig_test.go /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths-ignore: ['**.md'] 7 | pull_request: 8 | types: [opened, synchronize] 9 | paths-ignore: ['**.md'] 10 | 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v4 17 | with: 18 | go-version-file: go.mod 19 | - name: Ensure go.mod is already tidied 20 | run: go mod tidy && git diff -s --exit-code go.sum 21 | - run: make lint test 22 | - run: make dist 23 | - run: make validate-krew-manifest 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["v*"] 6 | 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-go@v4 13 | with: 14 | go-version-file: go.mod 15 | - run: make release 16 | - uses: softprops/action-gh-release@v1 17 | with: 18 | files: dist/!(config.yaml) 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | - uses: rajatjindal/krew-release-bot@v0.0.46 22 | with: 23 | krew_template_file: dist/krew/view-serviceaccount-kubeconfig.yaml 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /hack/tools/bin 3 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | linters: 4 | disable-all: true 5 | enable: 6 | - errcheck 7 | - gofmt 8 | - gosimple 9 | - govet 10 | - ineffassign 11 | - staticcheck 12 | - typecheck 13 | - unused 14 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | project_name: kubectl-view_serviceaccount_kubeconfig 2 | builds: 3 | - main: ./cmd/kubectl-view_serviceaccount_kubeconfig.go 4 | binary: kubectl-view_serviceaccount_kubeconfig 5 | env: 6 | - CGO_ENABLED=0 7 | goos: 8 | - linux 9 | - windows 10 | - darwin 11 | goarch: 12 | - amd64 13 | - arm 14 | - arm64 15 | archives: 16 | - name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}" 17 | format: zip 18 | files: 19 | - LICENSE 20 | - README.md 21 | wrap_in_directory: false 22 | checksum: 23 | name_template: 'checksums.txt' 24 | changelog: 25 | skip: true 26 | krews: 27 | - name: view-serviceaccount-kubeconfig 28 | homepage: https://github.com/superbrothers/kubectl-view-serviceaccount-kubeconfig-plugin 29 | short_description: Show a kubeconfig setting to access the apiserver with a specified serviceaccount. 30 | description: Show a kubeconfig setting to access the apiserver with a specified serviceaccount. 31 | skip_upload: true 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kazuki Suda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= go 2 | DIST_DIR := dist 3 | 4 | .PHONY: build 5 | build: 6 | $(GO) build -o $(DIST_DIR)/kubectl-view_serviceaccount_kubeconfig cmd/kubectl-view_serviceaccount_kubeconfig.go 7 | 8 | TOOLS_BIN_DIR := $(CURDIR)/hack/tools/bin 9 | GORELEASER_VERSION ?= v1.25.1 10 | GORELEASER := $(TOOLS_BIN_DIR)/goreleaser 11 | GOLANGCI_LINT_VERSION ?= v1.57.1 12 | GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint 13 | VALIDATE_KREW_MAIFEST_VERSION ?= v0.4.4 14 | VALIDATE_KREW_MAIFEST := $(TOOLS_BIN_DIR)/validate-krew-manifest 15 | GORELEASER_FILTER_VERSION ?= v0.3.0 16 | GORELEASER_FILTER := $(TOOLS_BIN_DIR)/goreleaser-filter 17 | 18 | $(GORELEASER): 19 | GOBIN=$(TOOLS_BIN_DIR) go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION) 20 | 21 | $(GOLANGCI_LINT): 22 | GOBIN=$(TOOLS_BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) 23 | 24 | $(VALIDATE_KREW_MAIFEST): 25 | GOBIN=$(TOOLS_BIN_DIR) go install sigs.k8s.io/krew/cmd/validate-krew-manifest@$(VALIDATE_KREW_MAIFEST_VERSION) 26 | 27 | $(GORELEASER_FILTER): 28 | GOBIN=$(TOOLS_BIN_DIR) go install github.com/t0yv0/goreleaser-filter@$(GORELEASER_FILTER_VERSION) 29 | 30 | .PHONY: build-cross 31 | build-cross: $(GORELEASER) 32 | $(GORELEASER) build --snapshot --clean 33 | 34 | .PHONY: lint 35 | lint: $(GOLANGCI_LINT) 36 | $(GOLANGCI_LINT) run 37 | 38 | .PHONY: lint-fix 39 | lint-fix: $(GOLANGCI_LINT) 40 | $(GOLANGCI_LINT) run --fix 41 | 42 | .PHONY: test 43 | test: 44 | $(GO) test -v ./... 45 | 46 | .PHONY: validate-krew-manifest 47 | validate-krew-manifest: $(VALIDATE_KREW_MAIFEST) 48 | $(VALIDATE_KREW_MAIFEST) -manifest dist/krew/view-serviceaccount-kubeconfig.yaml -skip-install 49 | 50 | .PHONY: dist 51 | dist: $(GORELEASER) $(GORELEASER_FILTER) 52 | cat .goreleaser.yaml | $(GORELEASER_FILTER) -goos $(shell go env GOOS) -goarch $(shell go env GOARCH) | $(GORELEASER) release -f- --clean --skip=publish --snapshot 53 | 54 | .PHONY: dist-all 55 | dist-all: $(GORELEASER) 56 | $(GORELEASER) release --clean --skip-publish --snapshot 57 | 58 | .PHONY: release 59 | release: $(GORELEASER) 60 | $(GORELEASER) release --clean --skip-publish 61 | 62 | .PHONY: clean 63 | clean: 64 | rm -rf $(DIST_DIR) $(TOOLS_BIN_DIR) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kubectl plugin view-serviceaccount-kubeconfig SERVICEACCOUNT 2 | 3 | Show a kubeconfig setting for serviceaccount from bound token or secret-based token. 4 | 5 | Note that in Kubernetes 1.24+, secret-based tokens are no longer auto-created 6 | by default for new service accounts. Using bound tokens created by "kubectl 7 | create token" command to access the Kubernetes API is recommended instead. 8 | 9 | See [Configure Service Accounts for Pods \| Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) for more information. 10 | 11 | ``` 12 | Usage: 13 | kubectl view-serviceaccount-kubeconfig [SERVICEACCOUNT] [options] [flags] 14 | 15 | Examples: 16 | # Show a kubeconfig setting using bound token for serviceaccount/myapp in the current namespace 17 | kubectl create token myapp | kubectl view-serviceaccount-kubeconfig 18 | 19 | # Show a kubeconfig setting using bound token for a serviceaccount in a custom namspcae 20 | kubectl create token myapp --namespace myns | kubectl view-serviceaccount-kubeconfig 21 | 22 | # Show a kubeconfig setting using bound token with a custom expiration 23 | kubectl create token myapp --duration 10m | kubectl view-serviceaccount-kubeconfig 24 | 25 | # Show a kubeconfig setting using bound token in JSON format 26 | kubectl create token myapp | kubectl view-serviceaccount-kubeconfig --output json 27 | 28 | # Show a kubeconfig setting using secret-based token for serviceaccount/myapp in the current namespace 29 | kubectl view-serviceaccount-kubeconfig default 30 | 31 | # Show a kubeconfig setting using secret-based token for serviceaccount/bot in namespace/kube-system 32 | kubectl view-serviceaccount-kubeconfig bot -n kube-system 33 | ``` 34 | 35 | ## Try the plugin 36 | 37 | ``` 38 | # create a serviceaccount "myapp" in the current namespace 39 | $ kubectl create serviceaccount myapp 40 | 41 | # save a kubeconfig setting for serviceaccount "myapp" 42 | $ kubectl create token myapp | kubectl view-serviceaccount-kubeconfig >./kubeconfig 43 | 44 | # list pods as serviceaccount "myapp" from outside of kubernetes cluster 45 | $ kubectl get pods --kubeconfig=./kubeconfig 46 | ``` 47 | 48 | ## Install the plugin 49 | 50 | 1. Install [krew](https://github.com/GoogleContainerTools/krew) that is a plugin manager for kubectl. 51 | 2. Run: 52 | 53 | kubectl krew install view-serviceaccount-kubeconfig 54 | 55 | 3. Try it out: 56 | 57 | kubectl view-serviceaccount-kubeconfig default 58 | 59 | ## License 60 | 61 | This software is released under the MIT License. 62 | -------------------------------------------------------------------------------- /cmd/kubectl-view_serviceaccount_kubeconfig.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | "github.com/spf13/pflag" 8 | "github.com/superbrothers/kubectl-view-serviceaccount-kubeconfig-plugin/pkg/cmd" 9 | 10 | "k8s.io/cli-runtime/pkg/genericclioptions" 11 | "k8s.io/klog/v2" 12 | ) 13 | 14 | func init() { 15 | // Initialize glog flags 16 | klog.InitFlags(flag.CommandLine) 17 | _ = flag.CommandLine.Set("logtostderr", "true") 18 | } 19 | 20 | func main() { 21 | flags := pflag.NewFlagSet("kubectl-view-serviceaccount-kubeconfig", pflag.ExitOnError) 22 | pflag.CommandLine = flags 23 | 24 | root := cmd.NewCmdViewServiceaccountKubeconfig(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}) 25 | if err := root.Execute(); err != nil { 26 | os.Exit(1) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/superbrothers/kubectl-view-serviceaccount-kubeconfig-plugin 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.2 6 | 7 | require ( 8 | github.com/go-jose/go-jose/v3 v3.0.3 9 | github.com/spf13/cobra v1.8.0 10 | github.com/spf13/pflag v1.0.5 11 | k8s.io/api v0.30.0 12 | k8s.io/apimachinery v0.30.0 13 | k8s.io/cli-runtime v0.30.0 14 | k8s.io/client-go v0.30.0 15 | k8s.io/klog/v2 v2.120.1 16 | k8s.io/kubectl v0.30.0 17 | ) 18 | 19 | require ( 20 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 21 | github.com/MakeNowJust/heredoc v1.0.0 // indirect 22 | github.com/blang/semver/v4 v4.0.0 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/emicklei/go-restful/v3 v3.12.0 // indirect 25 | github.com/evanphx/json-patch v5.9.0+incompatible // indirect 26 | github.com/go-errors/errors v1.5.1 // indirect 27 | github.com/go-logr/logr v1.4.1 // indirect 28 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 29 | github.com/go-openapi/jsonreference v0.21.0 // indirect 30 | github.com/go-openapi/swag v0.23.0 // indirect 31 | github.com/gogo/protobuf v1.3.2 // indirect 32 | github.com/golang/protobuf v1.5.4 // indirect 33 | github.com/google/btree v1.1.2 // indirect 34 | github.com/google/gnostic-models v0.6.8 // indirect 35 | github.com/google/gofuzz v1.2.0 // indirect 36 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 37 | github.com/google/uuid v1.6.0 // indirect 38 | github.com/gorilla/websocket v1.5.1 // indirect 39 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 40 | github.com/imdario/mergo v0.3.16 // indirect 41 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 42 | github.com/josharian/intern v1.0.0 // indirect 43 | github.com/json-iterator/go v1.1.12 // indirect 44 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 45 | github.com/mailru/easyjson v0.7.7 // indirect 46 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 47 | github.com/moby/spdystream v0.2.0 // indirect 48 | github.com/moby/term v0.5.0 // indirect 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 50 | github.com/modern-go/reflect2 v1.0.2 // indirect 51 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 52 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 53 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 54 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 55 | github.com/pkg/errors v0.9.1 // indirect 56 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 57 | github.com/xlab/treeprint v1.2.0 // indirect 58 | go.starlark.net v0.0.0-20240411212711-9b43f0afd521 // indirect 59 | golang.org/x/crypto v0.22.0 // indirect 60 | golang.org/x/net v0.24.0 // indirect 61 | golang.org/x/oauth2 v0.19.0 // indirect 62 | golang.org/x/sync v0.7.0 // indirect 63 | golang.org/x/sys v0.19.0 // indirect 64 | golang.org/x/term v0.19.0 // indirect 65 | golang.org/x/text v0.14.0 // indirect 66 | golang.org/x/time v0.5.0 // indirect 67 | google.golang.org/protobuf v1.33.0 // indirect 68 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 69 | gopkg.in/inf.v0 v0.9.1 // indirect 70 | gopkg.in/yaml.v2 v2.4.0 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect 73 | k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect 74 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 75 | sigs.k8s.io/kustomize/api v0.17.1 // indirect 76 | sigs.k8s.io/kustomize/kyaml v0.17.0 // indirect 77 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 78 | sigs.k8s.io/yaml v1.4.0 // indirect 79 | ) 80 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 2 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 3 | github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= 4 | github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 5 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 7 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 8 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 10 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 11 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= 17 | github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 18 | github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= 19 | github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 20 | github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= 21 | github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 22 | github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= 23 | github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= 24 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 25 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 26 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 27 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 28 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 29 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 30 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 31 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 32 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 33 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 34 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 35 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 36 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 37 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 38 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 39 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 40 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 41 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 42 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 43 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 44 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 45 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 46 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 47 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 48 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 49 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 50 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 51 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 52 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 53 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 54 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 55 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 56 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 57 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 58 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 59 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 60 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 61 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 62 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 63 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 64 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 65 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 66 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 67 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 68 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 69 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 70 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 71 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 72 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 73 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 74 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 75 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 76 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 77 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 78 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 79 | github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= 80 | github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 81 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 82 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 83 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 84 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 85 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 86 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 87 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 88 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 89 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 90 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 91 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 92 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 93 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 94 | github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= 95 | github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= 96 | github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= 97 | github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= 98 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 99 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 100 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 101 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 102 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 103 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 104 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 105 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 106 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 107 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 108 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 109 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 110 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 111 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 112 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 113 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 114 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 115 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 116 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 117 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 118 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 119 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 120 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 121 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 122 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 123 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 124 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 125 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 126 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 127 | go.starlark.net v0.0.0-20240411212711-9b43f0afd521 h1:1Ufp2S2fPpj0RHIQ4rbzpCdPLCPkzdK7BaVFH3nkYBQ= 128 | go.starlark.net v0.0.0-20240411212711-9b43f0afd521/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= 129 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 130 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 131 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 132 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 133 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 134 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 135 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 136 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 137 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 138 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 139 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 140 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 141 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 142 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 143 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 144 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 145 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 146 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 147 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 148 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 149 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 150 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 151 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 152 | golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= 153 | golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= 154 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 155 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 156 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 157 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 158 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 160 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 161 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 162 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 163 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 166 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 167 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 169 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 170 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 171 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 172 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 173 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 174 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 175 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 176 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 177 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 178 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 179 | golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= 180 | golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= 181 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 182 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 183 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 184 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 185 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 186 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 187 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 188 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 189 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 190 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 191 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 192 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 193 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 194 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 195 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 196 | golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= 197 | golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= 198 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 199 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 200 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 201 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 202 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 203 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 204 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 205 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 206 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 207 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 208 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 209 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 210 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 211 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 212 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 213 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 214 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 215 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 216 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 217 | k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= 218 | k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= 219 | k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= 220 | k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= 221 | k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= 222 | k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= 223 | k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= 224 | k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= 225 | k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= 226 | k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 227 | k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA= 228 | k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 229 | k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= 230 | k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= 231 | k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= 232 | k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 233 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 234 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 235 | sigs.k8s.io/kustomize/api v0.17.1 h1:MYJBOP/yQ3/5tp4/sf6HiiMfNNyO97LmtnirH9SLNr4= 236 | sigs.k8s.io/kustomize/api v0.17.1/go.mod h1:ffn5491s2EiNrJSmgqcWGzQUVhc/pB0OKNI0HsT/0tA= 237 | sigs.k8s.io/kustomize/kyaml v0.17.0 h1:G2bWs03V9Ur2PinHLzTUJ8Ded+30SzXZKiO92SRDs3c= 238 | sigs.k8s.io/kustomize/kyaml v0.17.0/go.mod h1:6lxkYF1Cv9Ic8g/N7I86cvxNc5iinUo/P2vKsHNmpyE= 239 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 240 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 241 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 242 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 243 | -------------------------------------------------------------------------------- /pkg/cmd/util.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/go-jose/go-jose/v3/jwt" 8 | v1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/types" 11 | "k8s.io/client-go/kubernetes" 12 | ) 13 | 14 | func getTokenForServiceAccount(ctx context.Context, client *kubernetes.Clientset, namespace, serviceaccountName string) (string, []byte, error) { 15 | serviceaccount, err := client.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceaccountName, metav1.GetOptions{}) 16 | if err != nil { 17 | return "", nil, fmt.Errorf("Failed to get serviceaccount %s/%s: %v", namespace, serviceaccountName, err) 18 | } 19 | 20 | var serviceAccountSecrets []v1.ObjectReference 21 | 22 | if len(serviceaccount.Secrets) > 0 { 23 | serviceAccountSecrets = serviceaccount.Secrets 24 | } else { 25 | secrets, err := client.CoreV1().Secrets(namespace).List(ctx, metav1.ListOptions{}) 26 | if err != nil { 27 | return "", nil, fmt.Errorf("failed to list secrets: %v", err) 28 | } 29 | 30 | for _, secret := range secrets.Items { 31 | if secret.Annotations["kubernetes.io/service-account.uid"] == string(serviceaccount.UID) { 32 | serviceAccountSecrets = append(serviceAccountSecrets, v1.ObjectReference{Name: secret.Name}) 33 | break 34 | } 35 | } 36 | } 37 | 38 | if len(serviceAccountSecrets) < 1 { 39 | return "", nil, fmt.Errorf(`"serviceaccount %s/%s has no secrets. 40 | 41 | In Kubernetes 1.24+, secret-based tokens are no longer auto-created 42 | by default for new service accounts. Using bound tokens created by "kubectl 43 | create token" command to access the Kubernetes API is recommended instead. 44 | 45 | Alternatively, you can attach a long-lived token to the service account; 46 | see https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#create-token 47 | for more information. 48 | 49 | Check the help message of this command to see how to show the kubeconfig 50 | setting with a bound token.`, namespace, serviceaccountName) 51 | } 52 | 53 | var secret *v1.Secret 54 | for _, secretRef := range serviceAccountSecrets { 55 | secret, err = client.CoreV1().Secrets(namespace).Get(ctx, secretRef.Name, metav1.GetOptions{}) 56 | if err != nil { 57 | return "", nil, fmt.Errorf("Failed to get a secret: %v", err) 58 | } 59 | 60 | if secret.Type == v1.SecretTypeServiceAccountToken { 61 | break 62 | } 63 | } 64 | 65 | if secret == nil { 66 | return "", nil, fmt.Errorf("serviceAccount %s/%s has no secret type %q", namespace, serviceaccountName, v1.SecretTypeServiceAccountToken) 67 | } 68 | 69 | token, ok := secret.Data["token"] 70 | if !ok { 71 | return "", nil, fmt.Errorf("key 'token' not found in %s", secret.GetName()) 72 | } 73 | 74 | caCrt, ok := secret.Data["ca.crt"] 75 | if !ok { 76 | return "", nil, fmt.Errorf("key 'ca.crt' not found in %s", secret.GetName()) 77 | } 78 | 79 | return string(token), caCrt, nil 80 | } 81 | 82 | type kubeName struct { 83 | Name string `json:"name"` 84 | } 85 | 86 | type kubeClaims struct { 87 | Namespace string `json:"namespace"` 88 | ServiceAccount kubeName `json:"serviceaccount"` 89 | } 90 | 91 | type claims struct { 92 | Kubernetes kubeClaims `json:"kubernetes.io"` 93 | } 94 | 95 | func getServiceAccountNamespacedNameFromBoundToken(tokenData string) (*types.NamespacedName, error) { 96 | tok, err := jwt.ParseSigned(tokenData) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | var unsafeClaims claims 102 | if err := tok.UnsafeClaimsWithoutVerification(&unsafeClaims); err != nil { 103 | return nil, err 104 | } 105 | 106 | namespacedName := &types.NamespacedName{ 107 | Namespace: unsafeClaims.Kubernetes.Namespace, 108 | Name: unsafeClaims.Kubernetes.ServiceAccount.Name, 109 | } 110 | 111 | return namespacedName, nil 112 | } 113 | -------------------------------------------------------------------------------- /pkg/cmd/util_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "k8s.io/apimachinery/pkg/types" 8 | ) 9 | 10 | func TestGetServiceAccountNamespacedNameFromBoundToken(t *testing.T) { 11 | tests := []struct { 12 | token string 13 | wantError bool 14 | expected *types.NamespacedName 15 | }{ 16 | { 17 | token: "this is invalid token", 18 | wantError: true, 19 | expected: nil, 20 | }, 21 | { 22 | token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjRMX0dHcnlxX3BxdzdIVmhabjBBWlJnMm1qZ1VNbUJadmM4aTF4SEZTVlUifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjc4NjM0MTczLCJpYXQiOjE2Nzg2MzA1NzMsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkZWZhdWx0IiwidWlkIjoiNWY0NDE0MjMtZmRlMS00N2JiLTlkNmQtMDM3NDNjZTU1MWZmIn19LCJuYmYiOjE2Nzg2MzA1NzMsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTpkZWZhdWx0In0.mXBIw5Z_QXRfNR2Ic1-Uwq8tSMaUM7I1xZ4W1LBB8chFTyHNCP0o_G-7JtQH1AOOUPRWUw_sg_sypHtij05OsCx7C61NoBC8NoL4jq6V28duvqj2AhOXTW08Je_wXLe_FD4E1i5n1TU6Wg3tXFzROjB3pt8koM-MBxGo8LiTsJrC41ipZEMZZBF44pmOXG1LKxks3q1JzLcthdVt4yFyy9tW33MPg_i50If1JeD0Cqbb-UQ-PIPNDsGDFaNd0R-KBQy3tnMxP86Hoj1oRymCgQIz5HRkgBsjoYkDul7Cc6uomy4hcM5amV8aonuxLy0mO0xhcMSkEGgEREd3Xv_bFw", 23 | wantError: false, 24 | expected: &types.NamespacedName{ 25 | Namespace: "kube-system", 26 | Name: "default", 27 | }, 28 | }, 29 | } 30 | 31 | for i, tt := range tests { 32 | actual, err := getServiceAccountNamespacedNameFromBoundToken(tt.token) 33 | gotError := err != nil 34 | 35 | if tt.wantError != gotError { 36 | t.Errorf("%d: expected %t, got %t, err: %q", i, tt.wantError, gotError, err) 37 | } 38 | 39 | if !reflect.DeepEqual(tt.expected, actual) { 40 | t.Errorf("%d: expected %#v, got %#v", i, tt.expected, actual) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/cmd/view-serviceaccount-kubeconfig.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "strings" 9 | 10 | "github.com/spf13/cobra" 11 | "k8s.io/cli-runtime/pkg/genericclioptions" 12 | "k8s.io/cli-runtime/pkg/printers" 13 | "k8s.io/client-go/kubernetes" 14 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 15 | "k8s.io/client-go/tools/clientcmd/api/latest" 16 | "k8s.io/kubectl/pkg/util/templates" 17 | 18 | // Initialize all known client auth plugins. 19 | _ "k8s.io/client-go/plugin/pkg/client/auth" 20 | ) 21 | 22 | var ( 23 | viewServiceaccountKubeconfigLong = templates.LongDesc(` 24 | Show a kubeconfig setting for serviceaccount from bound token or secret-based token. 25 | 26 | Note that in Kubernetes 1.24+, secret-based tokens are no longer auto-created 27 | by default for new service accounts. Using bound tokens created by "kubectl 28 | create token" command to access the Kubernetes API is recommended instead. 29 | `) 30 | 31 | viewServiceaccountKubeconfigExample = templates.Examples(` 32 | # Show a kubeconfig setting using bound token for serviceaccount/myapp in the current namespace 33 | kubectl create token myapp | kubectl view-serviceaccount-kubeconfig 34 | 35 | # Show a kubeconfig setting using bound token for a serviceaccount in a custom namspcae 36 | kubectl create token myapp --namespace myns | kubectl view-serviceaccount-kubeconfig 37 | 38 | # Show a kubeconfig setting using bound token with a custom expiration 39 | kubectl create token myapp --duration 10m | kubectl view-serviceaccount-kubeconfig 40 | 41 | # Show a kubeconfig setting using bound token in JSON format 42 | kubectl create token myapp | kubectl view-serviceaccount-kubeconfig --output json 43 | 44 | # Show a kubeconfig setting using secret-based token for serviceaccount/myapp in the current namespace 45 | kubectl view-serviceaccount-kubeconfig default 46 | 47 | # Show a kubeconfig setting using secret-based token for serviceaccount/bot in namespace/kube-system 48 | kubectl view-serviceaccount-kubeconfig bot -n kube-system 49 | `) 50 | ) 51 | 52 | // ViewServiceaccountKubeconfig provides information required to show the 53 | // KUBECONFIG setting of serviceaccount 54 | type ViewServiceaccountKubeconfigOptions struct { 55 | configFlags *genericclioptions.ConfigFlags 56 | printFlags *genericclioptions.PrintFlags 57 | printObj printers.ResourcePrinterFunc 58 | 59 | serviceaccountName string 60 | 61 | genericclioptions.IOStreams 62 | } 63 | 64 | // NewViewServiceaccountKubeconfig provides an instance of 65 | // ViewServiceaccountKubeconfig with default value 66 | func NewViewServiceaccountKubeconfigOptions(streams genericclioptions.IOStreams) *ViewServiceaccountKubeconfigOptions { 67 | return &ViewServiceaccountKubeconfigOptions{ 68 | configFlags: genericclioptions.NewConfigFlags(true), 69 | // disabled all output flags except yaml/json. 70 | printFlags: (&genericclioptions.PrintFlags{ 71 | JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(), 72 | }).WithDefaultOutput("yaml"), 73 | 74 | IOStreams: streams, 75 | } 76 | } 77 | 78 | // NewCmdViewServiceaccountKubeconfig provides a cobra command wrapping 79 | // ViewServiceaccountKubeconfigOptions 80 | func NewCmdViewServiceaccountKubeconfig(streams genericclioptions.IOStreams) *cobra.Command { 81 | o := NewViewServiceaccountKubeconfigOptions(streams) 82 | 83 | cmd := &cobra.Command{ 84 | Use: "kubectl view-serviceaccount-kubeconfig [SERVICEACCOUNT] [options]", 85 | Short: "Show a kubeconfig setting for serviceaccount from bound token or secret-based token.", 86 | Long: viewServiceaccountKubeconfigLong, 87 | Example: viewServiceaccountKubeconfigExample, 88 | RunE: func(c *cobra.Command, args []string) error { 89 | if err := o.Complete(args); err != nil { 90 | return err 91 | } 92 | if err := o.Validate(); err != nil { 93 | return err 94 | } 95 | c.SilenceUsage = true 96 | if err := o.Run(); err != nil { 97 | return err 98 | } 99 | 100 | return nil 101 | }, 102 | } 103 | 104 | cmd.Flags().StringVarP(o.printFlags.OutputFormat, "output", "o", *o.printFlags.OutputFormat, fmt.Sprintf("Output format. One of : %s.", strings.Join(o.printFlags.AllowedFormats(), "|"))) 105 | 106 | o.configFlags.AddFlags(cmd.Flags()) 107 | 108 | // add the klog flags 109 | cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine) 110 | // Workaround for this issue: 111 | // https://github.com/kubernetes/kubernetes/issues/17162 112 | _ = flag.CommandLine.Parse([]string{}) 113 | 114 | return cmd 115 | } 116 | 117 | // Complete sets all information required for showing the KUBECONFIG setting 118 | // of serviceaccount 119 | func (o *ViewServiceaccountKubeconfigOptions) Complete(args []string) error { 120 | if len(args) > 1 { 121 | return fmt.Errorf("exactly one SERVICEACCOUT is required, got %d", len(args)) 122 | } 123 | 124 | if len(args) > 0 { 125 | o.serviceaccountName = args[0] 126 | } 127 | 128 | printer, err := o.printFlags.ToPrinter() 129 | if err != nil { 130 | return err 131 | } 132 | o.printObj = printer.PrintObj 133 | 134 | return nil 135 | } 136 | 137 | // Validate ensures that all required arguments and flag values are provided 138 | func (o *ViewServiceaccountKubeconfigOptions) Validate() error { 139 | return nil 140 | } 141 | 142 | // Run shows a kubeconfig to access the apiserver with a specified 143 | // serviceaccount 144 | func (o *ViewServiceaccountKubeconfigOptions) Run() error { 145 | kubeConfig := o.configFlags.ToRawKubeConfigLoader() 146 | 147 | rawConfig, err := kubeConfig.RawConfig() 148 | if err != nil { 149 | return fmt.Errorf("Failed to get current kubeconfig data") 150 | } 151 | 152 | var currentContext string 153 | if *o.configFlags.Context != "" { 154 | currentContext = *o.configFlags.Context 155 | } else { 156 | currentContext = rawConfig.CurrentContext 157 | } 158 | 159 | cluster := rawConfig.Contexts[currentContext].Cluster 160 | server := rawConfig.Clusters[cluster].Server 161 | 162 | var ( 163 | serviceaccountName string 164 | namespace string 165 | token string 166 | caCrt []byte 167 | ) 168 | 169 | // We expect the serviceaccount bound token can be read from the stdin 170 | // if no arguments is specified. 171 | if o.serviceaccountName == "" { 172 | tokenData, err := io.ReadAll(o.IOStreams.In) 173 | if err != nil { 174 | return err 175 | } 176 | token = string(tokenData) 177 | 178 | namespacedName, err := getServiceAccountNamespacedNameFromBoundToken(token) 179 | if err != nil { 180 | return err 181 | } 182 | 183 | namespace = namespacedName.Namespace 184 | serviceaccountName = namespacedName.Name 185 | 186 | // We get CA certificate data from the kubeconfig file 187 | caCrt = rawConfig.Clusters[cluster].CertificateAuthorityData 188 | } else { 189 | restConfig, err := o.configFlags.ToRESTConfig() 190 | if err != nil { 191 | return err 192 | } 193 | 194 | client := kubernetes.NewForConfigOrDie(restConfig) 195 | 196 | serviceaccountName = o.serviceaccountName 197 | namespace, _, err = kubeConfig.Namespace() 198 | if err != nil { 199 | return err 200 | } 201 | 202 | token, caCrt, err = getTokenForServiceAccount(context.Background(), client, namespace, serviceaccountName) 203 | if err != nil { 204 | return err 205 | } 206 | } 207 | 208 | config := &clientcmdapi.Config{ 209 | CurrentContext: currentContext, 210 | Clusters: map[string]*clientcmdapi.Cluster{ 211 | cluster: { 212 | Server: server, 213 | CertificateAuthorityData: caCrt, 214 | }, 215 | }, 216 | AuthInfos: map[string]*clientcmdapi.AuthInfo{ 217 | serviceaccountName: { 218 | Token: token, 219 | }, 220 | }, 221 | Contexts: map[string]*clientcmdapi.Context{ 222 | currentContext: { 223 | Cluster: cluster, 224 | AuthInfo: serviceaccountName, 225 | Namespace: namespace, 226 | }, 227 | }, 228 | } 229 | 230 | convertedObj, err := latest.Scheme.ConvertToVersion(config, latest.ExternalVersion) 231 | if err != nil { 232 | return err 233 | } 234 | 235 | return o.printObj(convertedObj, o.Out) 236 | } 237 | -------------------------------------------------------------------------------- /pkg/cmd/view-serviceaccount-kubeconfig_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "k8s.io/cli-runtime/pkg/genericclioptions" 7 | ) 8 | 9 | func TestViewServiceaccountKubeconfigOptionComplete(t *testing.T) { 10 | tests := []struct { 11 | args []string 12 | wantError bool 13 | }{ 14 | { 15 | args: []string{}, 16 | wantError: false, 17 | }, 18 | { 19 | args: []string{"default"}, 20 | wantError: false, 21 | }, 22 | { 23 | args: []string{"default", "mysa"}, 24 | wantError: true, 25 | }, 26 | } 27 | 28 | for i, tt := range tests { 29 | o := NewViewServiceaccountKubeconfigOptions(genericclioptions.NewTestIOStreamsDiscard()) 30 | err := o.Complete(tt.args) 31 | if tt.wantError { 32 | if err == nil { 33 | t.Errorf("%d: expected err, but got nil", i) 34 | } 35 | continue 36 | } 37 | 38 | if err != nil { 39 | t.Errorf("%d: unexpected err: %v", i, err) 40 | } 41 | } 42 | } 43 | --------------------------------------------------------------------------------