├── docs ├── .vuepress │ ├── public │ │ ├── img │ │ │ ├── icon.svg │ │ │ ├── logo.svg │ │ │ └── fairwinds-logo.svg │ │ ├── favicon.png │ │ └── scripts │ │ │ ├── modify.js │ │ │ └── marketing.js │ ├── theme │ │ ├── index.js │ │ └── layouts │ │ │ └── Layout.vue │ ├── styles │ │ ├── palette.styl │ │ └── index.styl │ ├── config-extras.js │ └── config.js ├── package.json ├── gke.md ├── README.md ├── usage.md └── contributing │ ├── guide.md │ └── code-of-conduct.md ├── CODEOWNERS ├── e2e ├── pre.sh ├── tests │ ├── 00_assets │ │ ├── 1_serviceaccounts.yaml │ │ ├── 0_namespaces.yaml │ │ └── 2_bindings.yaml │ └── 00_test.yaml └── test.sh ├── .github ├── ISSUE_TEMPLATE │ ├── other.md │ ├── feature_request.md │ └── bug.yaml ├── pull_request_template.md ├── workflows │ └── stale.yml └── dependabot.yml ├── .gitignore ├── ROADMAP.md ├── plugin.yaml ├── Makefile ├── main.go ├── cmd ├── version.go └── root.go ├── img ├── icon.svg └── logo.svg ├── .goreleaser.yml ├── lookup ├── gke_test.go ├── rbac_subject.go ├── gke_roles.go ├── list.go ├── gke.go ├── lister.go └── lister_test.go ├── README.md ├── go.mod ├── CODE_OF_CONDUCT.md ├── .circleci └── config.yml ├── LICENSE └── go.sum /docs/.vuepress/public/img/icon.svg: -------------------------------------------------------------------------------- 1 | ../../../../img/icon.svg -------------------------------------------------------------------------------- /docs/.vuepress/public/img/logo.svg: -------------------------------------------------------------------------------- 1 | ../../../../img/logo.svg -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ## DO NOT EDIT - Managed by Terraform 2 | * @sudermanjr @ryanisfluffy 3 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extend: '@vuepress/theme-default' 3 | } 4 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FairwindsOps/rbac-lookup/HEAD/docs/.vuepress/public/favicon.png -------------------------------------------------------------------------------- /e2e/pre.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | CGO_ENABLED=0 go build -ldflags "-s -w" -o rbac-lookup 6 | 7 | docker cp ./ e2e-command-runner:/rbac-lookup 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: For misc. tasks like research or continued conversation 4 | title: '' 5 | labels: [triage] 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | dist 3 | 4 | # Output of the go coverage tool 5 | *.out 6 | cover-report.html 7 | coverage.txt 8 | cover.html 9 | 10 | /rbac-lookup 11 | /e2e/tests/rbac-lookup 12 | node_modules 13 | /dist 14 | docs/README.md 15 | -------------------------------------------------------------------------------- /e2e/tests/00_assets/1_serviceaccounts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: e2e-test 6 | namespace: default 7 | --- 8 | apiVersion: v1 9 | kind: ServiceAccount 10 | metadata: 11 | name: circleci 12 | namespace: circleci 13 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## Q32019 4 | Below is a list of work we plan to get done this quarter. Some more details can be found 5 | [in the milestone](https://github.com/FairwindsOps/rbac-lookup/milestone/1) 6 | 7 | * Add contributing instructions 8 | * Add release instructions 9 | -------------------------------------------------------------------------------- /plugin.yaml: -------------------------------------------------------------------------------- 1 | name: "rbac-lookup" 2 | shortDesc: "Reverse lookup for RBAC" 3 | longDesc: "Look up role bindings and cluster role bindings by subject names" 4 | example: "rbac-lookup rob -owide" 5 | command: "./rbac-lookup" 6 | flags: 7 | - name: "output" 8 | shorthand: "o" 9 | desc: "Optional paramater to provide wide output" 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | This PR fixes # 3 | 4 | ## Checklist 5 | * [ ] I have signed the CLA 6 | * [ ] I have updated/added any relevant documentation 7 | 8 | ## Description 9 | ### What's the goal of this PR? 10 | 11 | ### What changes did you make? 12 | 13 | ### What alternative solution should we consider, if any? 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '32 1 * * *' 5 | 6 | permissions: 7 | issues: write 8 | pull-requests: write 9 | 10 | jobs: 11 | stale: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/stale@v4 15 | with: 16 | exempt-issue-labels: pinned 17 | stale-pr-label: stale 18 | stale-issue-label: stale 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | ## DO NOT EDIT - Managed by Terraform 2 | version: 2 3 | updates: 4 | - package-ecosystem: "docker" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/docs" 11 | schedule: 12 | interval: "weekly" 13 | open-pull-requests-limit: 0 14 | ignore: 15 | - dependency-name: "*" 16 | 17 | - package-ecosystem: "gomod" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | -------------------------------------------------------------------------------- /docs/.vuepress/public/scripts/modify.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | document.addEventListener("DOMContentLoaded", function(){ 7 | setTimeout(function() { 8 | var link = document.getElementsByClassName('home-link')[0]; 9 | linkClone = link.cloneNode(true); 10 | linkClone.href = "https://fairwinds.com"; 11 | link.setAttribute('target', '_blank'); 12 | link.parentNode.replaceChild(linkClone, link); 13 | }, 1000); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /e2e/tests/00_assets/0_namespaces.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: one 6 | spec: 7 | finalizers: 8 | - kubernetes 9 | --- 10 | apiVersion: v1 11 | kind: Namespace 12 | metadata: 13 | name: two 14 | spec: 15 | finalizers: 16 | - kubernetes 17 | --- 18 | apiVersion: v1 19 | kind: Namespace 20 | metadata: 21 | name: three 22 | spec: 23 | finalizers: 24 | - kubernetes 25 | --- 26 | apiVersion: v1 27 | kind: Namespace 28 | metadata: 29 | name: circleci 30 | spec: 31 | finalizers: 32 | - kubernetes 33 | -------------------------------------------------------------------------------- /e2e/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | 6 | printf "\n\n" 7 | echo "***************************" 8 | echo "** Install and Run Venom **" 9 | echo "***************************" 10 | printf "\n\n" 11 | 12 | curl -LO https://github.com/ovh/venom/releases/download/v1.1.0/venom.linux-amd64 13 | mv venom.linux-amd64 /usr/local/bin/venom 14 | chmod +x /usr/local/bin/venom 15 | 16 | mv /rbac-lookup/rbac-lookup /rbac-lookup/e2e/tests 17 | 18 | cd /rbac-lookup/e2e 19 | mkdir -p /tmp/test-results 20 | venom run tests/* --output-dir=/tmp/test-results 21 | exit $? 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: [triage, enhancement] 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | 7 | $primaryColor = #23103A 8 | $dangerColor = #A0204C 9 | $warningColor = #FF6C00 10 | $infoColor = #8BD2DC 11 | $successColor = #28a745 12 | 13 | $accentColor = #FF6C00 14 | $textColor = #2c3e50 15 | $borderColor = #eaecef 16 | $codeBgColor = #282c34 17 | $arrowBgColor = #ccc 18 | $badgeTipColor = #42b983 19 | $badgeWarningColor = darken(#ffe564, 35%) 20 | $badgeErrorColor = #DA5961 21 | 22 | // layout 23 | $navbarHeight = 3.6rem 24 | $sidebarWidth = 20rem 25 | $contentWidth = 740px 26 | $homePageWidth = 960px 27 | 28 | // responsive breakpoints 29 | $MQNarrow = 959px 30 | $MQMobile = 719px 31 | $MQMobileNarrow = 419px 32 | 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=GO111MODULE=on go 3 | GOBUILD=$(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | GOTEST=$(GOCMD) test 6 | BINARY_NAME=rbac-lookup 7 | COMMIT := $(shell git rev-parse HEAD) 8 | VERSION := "dev" 9 | 10 | all: test build 11 | build: 12 | $(GOBUILD) -o $(BINARY_NAME) -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT) -s -w" -v 13 | test: 14 | @printf "\n\nTests:\n\n" 15 | $(GOCMD) test -v --bench --benchmem -coverprofile coverage.txt -covermode=atomic ./... 16 | $(GOCMD) vet ./... 2> govet-report.out 17 | $(GOCMD) tool cover -html=coverage.txt -o cover-report.html 18 | @printf "\nCoverage report available at cover-report.html\n\n" 19 | tidy: 20 | $(GOCMD) mod tidy 21 | clean: 22 | $(GOCLEAN) 23 | $(GOCMD) fmt ./... 24 | rm -f $(BINARY_NAME) 25 | # Cross compilation 26 | build-linux: 27 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_NAME) -ldflags "-X main.VERSION=$(VERSION)" -v 28 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/fairwindsops/rbac-lookup/cmd" 19 | ) 20 | 21 | var ( 22 | // version is set during build 23 | version = "" 24 | // COMMIT is set during build 25 | commit = "n/a" 26 | ) 27 | 28 | func main() { 29 | cmd.Execute(version, commit) 30 | } 31 | -------------------------------------------------------------------------------- /docs/.vuepress/config-extras.js: -------------------------------------------------------------------------------- 1 | // To see all options: 2 | // https://vuepress.vuejs.org/config/ 3 | // https://vuepress.vuejs.org/theme/default-theme-config.html 4 | module.exports = { 5 | title: "Rbac Lookup Documentation", 6 | description: "Documentation for Fairwinds' Rbac Lookup", 7 | themeConfig: { 8 | docsRepo: "FairwindsOps/rbac-lookup", 9 | sidebar: [ 10 | { 11 | title: "Rbac Lookup", 12 | path: "/", 13 | sidebarDepth: 0, 14 | }, 15 | { 16 | title: "Usage", 17 | path: "/usage", 18 | }, 19 | { 20 | title: "GKE", 21 | path: "/gke", 22 | }, 23 | { 24 | title: "Contributing", 25 | children: [ 26 | { 27 | title: "Guide", 28 | path: "contributing/guide" 29 | }, 30 | { 31 | title: "Code of Conduct", 32 | path: "contributing/code-of-conduct" 33 | } 34 | ] 35 | } 36 | ] 37 | }, 38 | } 39 | 40 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | func init() { 24 | rootCmd.AddCommand(versionCmd) 25 | } 26 | 27 | var versionCmd = &cobra.Command{ 28 | Use: "version", 29 | Short: "Print the version number of rbac-lookup", 30 | Run: func(cmd *cobra.Command, args []string) { 31 | fmt.Println("Version:" + version + " Commit:" + commit) 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /e2e/tests/00_test.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | name: "Basic Operation" 3 | vars: 4 | timeout: 60s 5 | vpa-wait: 30 6 | testcases: 7 | - name: Apply Manifests 8 | steps: 9 | - script: kubectl apply -f 00_assets/ 10 | - name: Rbac Lookup 11 | steps: 12 | - script: ./rbac-lookup e2e-test 13 | assertions: 14 | - result.code ShouldEqual 0 15 | - result.systemerr ShouldNotEqual "No RBAC Bindings found" 16 | - name: rbac lookup circleci 17 | steps: 18 | - script: ./rbac-lookup circleci -owide 19 | assertions: 20 | - result.code ShouldEqual 0 21 | - result.systemout ShouldContainSubstring "ServiceAccount/circleci:circleci two ClusterRole/cluster-admin RoleBinding/circleci-cluster-admin" 22 | - result.systemout ShouldContainSubstring "ServiceAccount/circleci:circleci cluster-wide ClusterRole/cluster-admin ClusterRoleBinding/circleci-cluster-admin" 23 | - result.systemout ShouldContainSubstring "ServiceAccount/circleci:circleci one ClusterRole/cluster-admin RoleBinding/circleci-cluster-admin" 24 | - name: Cleanup 25 | steps: 26 | - script: kubectl delete -f 00_assets/ 27 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 46 | -------------------------------------------------------------------------------- /docs/.vuepress/public/scripts/marketing.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | var llcookieless = true; 7 | var sf14gv = 32793; 8 | (function() { 9 | var sf14g = document.createElement('script'); 10 | sf14g.src = 'https://lltrck.com/lt-v2.min.js'; 11 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(sf14g, s); 12 | })(); 13 | 14 | (function() { 15 | var gtag = document.createElement('script'); 16 | gtag.src = "https://www.googletagmanager.com/gtag/js?id=G-ZR5M5SRYKY"; 17 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(gtag, s); 18 | window.dataLayer = window.dataLayer || []; 19 | function gtag(){dataLayer.push(arguments);} 20 | gtag('js', new Date()); 21 | gtag('config', 'G-ZR5M5SRYKY'); 22 | })(); 23 | 24 | !function(f,b,e,v,n,t,s) 25 | {if(f.fbq)return;n=f.fbq=function(){n.callMethod? 26 | n.callMethod.apply(n,arguments):n.queue.push(arguments)}; 27 | if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; 28 | n.queue=[];t=b.createElement(e);t.async=!0; 29 | t.src=v;s=b.getElementsByTagName(e)[0]; 30 | s.parentNode.insertBefore(t,s)}(window,document,'script', 31 | 'https://connect.facebook.net/en_US/fbevents.js'); 32 | fbq('init', '521127644762074'); 33 | fbq('track', 'PageView'); 34 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "", 3 | "bugs": { 4 | "url": "https://github.com/FairwindsOps/insights-docs/issues" 5 | }, 6 | "dependencies": { 7 | "vuepress-plugin-check-md": "0.0.2" 8 | }, 9 | "description": "A repository with a Vuepress template for Fairwinds projects", 10 | "devDependencies": { 11 | "vuepress": "^1.9.7", 12 | "vuepress-plugin-clean-urls": "^1.1.1", 13 | "vuepress-plugin-redirect": "^1.2.5" 14 | }, 15 | "directories": { 16 | "doc": "docs" 17 | }, 18 | "homepage": "https://github.com/FairwindsOps/insights-docs#readme", 19 | "license": "MIT", 20 | "main": "index.js", 21 | "name": "fairwinds-docs-template", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/FairwindsOps/insights-docs.git" 25 | }, 26 | "scripts": { 27 | "build": "npm run build:readme && npm run build:docs", 28 | "build:docs": "vuepress build -d ../dist/", 29 | "build:metadata": "cat main-metadata.md > README.md || true", 30 | "build:readme": "npm run build:metadata && cat ../README.md | grep -v 'ocumentation' | sed \"s/https:\\/\\/\\w\\+.docs.fairwinds.com//g\" >> README.md", 31 | "check-links": "vuepress check-md", 32 | "serve": "npm run build:readme && vuepress dev --port 3003", 33 | "vuepress": "vuepress" 34 | }, 35 | "version": "0.0.1" 36 | } 37 | -------------------------------------------------------------------------------- /e2e/tests/00_assets/2_bindings.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: e2e-test-binding 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: cluster-admin 10 | subjects: 11 | - kind: ServiceAccount 12 | name: e2e-test 13 | namespace: default 14 | --- 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: ClusterRoleBinding 17 | metadata: 18 | name: circleci-cluster-admin 19 | roleRef: 20 | apiGroup: rbac.authorization.k8s.io 21 | kind: ClusterRole 22 | name: cluster-admin 23 | subjects: 24 | - kind: ServiceAccount 25 | name: circleci 26 | namespace: circleci 27 | --- 28 | apiVersion: rbac.authorization.k8s.io/v1 29 | kind: RoleBinding 30 | metadata: 31 | name: circleci-cluster-admin 32 | namespace: one 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: cluster-admin 37 | subjects: 38 | - kind: ServiceAccount 39 | name: circleci 40 | namespace: circleci 41 | --- 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | kind: RoleBinding 44 | metadata: 45 | name: circleci-cluster-admin 46 | namespace: two 47 | roleRef: 48 | apiGroup: rbac.authorization.k8s.io 49 | kind: ClusterRole 50 | name: cluster-admin 51 | subjects: 52 | - kind: ServiceAccount 53 | name: circleci 54 | namespace: circleci 55 | -------------------------------------------------------------------------------- /img/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - CGO_ENABLED=0 3 | builds: 4 | - goos: 5 | - linux 6 | - darwin 7 | - windows 8 | goarch: 9 | - amd64 10 | - arm 11 | - arm64 12 | goarm: 13 | - 6 14 | - 7 15 | archives: 16 | - replacements: 17 | darwin: Darwin 18 | linux: Linux 19 | windows: Windows 20 | 386: i386 21 | amd64: x86_64 22 | checksum: 23 | name_template: 'checksums.txt' 24 | snapshot: 25 | name_template: "{{ .Tag }}-next" 26 | changelog: 27 | sort: asc 28 | filters: 29 | exclude: 30 | - '^docs:' 31 | - '^test:' 32 | release: 33 | prerelease: auto 34 | footer: | 35 | You can verify the signature of the checksums.txt file using [cosign](https://github.com/sigstore/cosign). 36 | 37 | ``` 38 | cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub 39 | ``` 40 | signs: 41 | - cmd: cosign 42 | args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}"] 43 | artifacts: checksum 44 | brews: 45 | - name: rbac-lookup 46 | tap: 47 | # The version control system is determined by the token in use 48 | owner: FairwindsOps 49 | name: homebrew-tap 50 | folder: Formula 51 | description: Reverse Lookup for Kubernetes RBAC 52 | homepage: https://github.com/FairWindsOps/rbac-lookup 53 | license: apache-2.0 54 | test: | 55 | system "#{bin}/rbac-lookup version" 56 | -------------------------------------------------------------------------------- /lookup/gke_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookup 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | 22 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 23 | ) 24 | 25 | func TestGetClusterInfo(t *testing.T) { 26 | config1 := clientcmdapi.Config{ 27 | Contexts: map[string]*clientcmdapi.Context{ 28 | "not-gke": { 29 | Cluster: "helloworld", 30 | }, 31 | "actual-gke": { 32 | Cluster: "gke_fairwindsio_us-central1-a_rbac-lookup-testing", 33 | }, 34 | }, 35 | CurrentContext: "bar", 36 | } 37 | 38 | assert.Equal(t, getClusterInfo(&config1, "").ClusterName, "") 39 | assert.Equal(t, getClusterInfo(&config1, "not-gke").ClusterName, "") 40 | assert.Equal(t, getClusterInfo(&config1, "actual-gke").ClusterName, "rbac-lookup-testing") 41 | } 42 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated from FairwindsOps/documentation-template 3 | * DO NOT EDIT MANUALLY 4 | */ 5 | 6 | .github-only { 7 | display: none; 8 | } 9 | 10 | .text-primary { 11 | color: $primaryColor; 12 | } 13 | .text-danger { 14 | color: $dangerColor; 15 | } 16 | .text-warning { 17 | color: $warningColor; 18 | } 19 | .text-info { 20 | color: $infoColor; 21 | } 22 | .text-success { 23 | color: $successColor; 24 | } 25 | 26 | blockquote { 27 | border-left: 0.2rem solid $warningColor; 28 | } 29 | blockquote p { 30 | color: $warningColor; 31 | } 32 | 33 | .theme-default-content:not(.custom), 34 | .page-nav, 35 | .page-edit, 36 | footer { 37 | margin: 0 !important; 38 | } 39 | 40 | .theme-default-content:not(.custom) > h2 { 41 | padding-top: 7rem; 42 | } 43 | 44 | .navbar .site-name { 45 | display: none; 46 | } 47 | 48 | .navbar, .navbar .links { 49 | background-color: $primaryColor !important; 50 | } 51 | 52 | .navbar .links a { 53 | color: #fff; 54 | } 55 | .navbar .links a svg { 56 | display: none; 57 | } 58 | 59 | img { 60 | border: 5px solid #f7f7f7; 61 | } 62 | 63 | .no-border img, 64 | img.no-border, 65 | header img { 66 | border: none; 67 | } 68 | 69 | .mini-img { 70 | text-align: center; 71 | } 72 | 73 | .theme-default-content:not(.custom) .mini-img img { 74 | max-width: 300px; 75 | } 76 | 77 | .page { 78 | padding-bottom: 0 !important; 79 | } 80 | -------------------------------------------------------------------------------- /docs/gke.md: -------------------------------------------------------------------------------- 1 | # GKE IAM Integration 2 | 3 | If you're connected to a GKE cluster, RBAC is only half the story here. Google Cloud IAM roles can grant cluster access. Cluster access is effectively determined by a union of IAM and RBAC roles. To see the relevant IAM roles along with RBAC roles, use the `--gke` flag. 4 | 5 | ``` 6 | rbac-lookup rob --gke 7 | 8 | SUBJECT SCOPE ROLE 9 | rob@example.com cluster-wide ClusterRole/view 10 | rob@example.com nginx-ingress ClusterRole/edit 11 | rob@example.com project-wide IAM/gke-developer 12 | rob@example.com project-wide IAM/viewer 13 | ``` 14 | 15 | Of course this GKE integration also supports wide output, in this case referencing the specific IAM roles that are assigned to a user. 16 | 17 | ``` 18 | rbac-lookup rob --gke --output wide 19 | 20 | SUBJECT SCOPE ROLE SOURCE 21 | User/rob@example.com cluster-wide ClusterRole/view ClusterRoleBinding/rob-cluster-view 22 | User/rob@example.com nginx-ingress ClusterRole/edit RoleBinding/rob-edit 23 | User/rob@example.com project-wide IAM/gke-developer IAMRole/container.developer 24 | User/rob@example.com project-wide IAM/gcp-viewer IAMRole/viewer 25 | ``` 26 | 27 | At this point this integration only supports standard IAM roles, and is not advanced enough to include any custom roles. For a full list of supported roles and how they are mapped, view [lookup/gke_roles.go](https://github.com/FairwindsOps/rbac-lookup/blob/master/lookup/gke_roles.go). 28 | -------------------------------------------------------------------------------- /lookup/rbac_subject.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookup 16 | 17 | import ( 18 | rbacv1 "k8s.io/api/rbac/v1" 19 | ) 20 | 21 | type rbacSubject struct { 22 | Kind string 23 | RolesByScope map[string][]simpleRole 24 | } 25 | 26 | type simpleRole struct { 27 | Kind string 28 | Name string 29 | Source simpleRoleSource 30 | } 31 | 32 | type simpleRoleSource struct { 33 | Kind string 34 | Name string 35 | } 36 | 37 | func (rbacSubj *rbacSubject) addRoleBinding(roleBinding *rbacv1.RoleBinding) { 38 | simpleRole := simpleRole{ 39 | Name: roleBinding.RoleRef.Name, 40 | Source: simpleRoleSource{ 41 | Name: roleBinding.Name, 42 | Kind: "RoleBinding", 43 | }, 44 | } 45 | 46 | simpleRole.Kind = roleBinding.RoleRef.Kind 47 | rbacSubj.RolesByScope[roleBinding.Namespace] = append(rbacSubj.RolesByScope[roleBinding.Namespace], simpleRole) 48 | } 49 | 50 | func (rbacSubj *rbacSubject) addClusterRoleBinding(clusterRoleBinding *rbacv1.ClusterRoleBinding) { 51 | simpleRole := simpleRole{ 52 | Name: clusterRoleBinding.RoleRef.Name, 53 | Source: simpleRoleSource{Name: clusterRoleBinding.Name, Kind: "ClusterRoleBinding"}, 54 | } 55 | 56 | simpleRole.Kind = clusterRoleBinding.RoleRef.Kind 57 | scope := "cluster-wide" 58 | rbacSubj.RolesByScope[scope] = append(rbacSubj.RolesByScope[scope], simpleRole) 59 | } 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: [bug, triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! Please fill the form below. 9 | - type: textarea 10 | id: what-happened 11 | attributes: 12 | label: What happened? 13 | description: What happened? 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: expected 18 | attributes: 19 | label: What did you expect to happen? 20 | description: What is the expected or desired behavior? 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: reproducible 25 | attributes: 26 | label: How can we reproduce this? 27 | description: Please share the steps that we can take to reproduce this. Also include any relevant configuration. 28 | validations: 29 | required: true 30 | - type: input 31 | id: version 32 | attributes: 33 | label: Version 34 | description: The version of the tool that you are using. If a helm chart, please share the name of the chart. 35 | validations: 36 | required: true 37 | - type: checkboxes 38 | id: search 39 | attributes: 40 | label: Search 41 | options: 42 | - label: I did search for other open and closed issues before opening this. 43 | required: true 44 | - type: checkboxes 45 | id: terms 46 | attributes: 47 | label: Code of Conduct 48 | description: By submitting this issue, you agree to follow the CODE_OF_CONDUCT in this repository. 49 | options: 50 | - label: I agree to follow this project's Code of Conduct 51 | required: true 52 | - type: textarea 53 | id: ctx 54 | attributes: 55 | label: Additional context 56 | description: Anything else you would like to add 57 | validations: 58 | required: false 59 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |
2 | RBAC Lookup 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | RBAC Lookup is a CLI that allows you to easily find Kubernetes roles and cluster roles bound to any user, service account, or group name. Binaries are generated with goreleaser for each release for simple installation. 16 | 17 | **Want to learn more?** Reach out on [the Slack channel](https://fairwindscommunity.slack.com/messages/rbac-projects) ([request invite](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-e3c6vj4l-3lIH6dvKqzWII5fSSFDi1g)), send an email to `opensource@fairwinds.com`, or join us for [office hours on Zoom](https://fairwindscommunity.slack.com/messages/office-hours) 18 | 19 | 20 | ## Installation 21 | 22 | ### Homebrew 23 | ``` 24 | brew install FairwindsOps/tap/rbac-lookup 25 | ``` 26 | 27 | ### ASDF 28 | ``` 29 | asdf plugin add rbac-lookup 30 | asdf install rbac-lookup latest 31 | asdf global rbac-lookup latest 32 | ``` 33 | 34 | ## RBAC Manager 35 | While RBAC Lookup helps provide visibility into Kubernetes auth, RBAC Manager helps make auth simpler to manage. This is a Kubernetes operator that enables more concise RBAC configuration that is easier to scale and automate. For more information, see [RBAC Manager on GitHub](https://github.com/FairwindsOps/rbac-manager). 36 | 37 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | In the simplest use case, rbac-lookup will return any matching user, service account, or group along with the roles it has been given. 4 | ``` 5 | rbac-lookup rob 6 | 7 | SUBJECT SCOPE ROLE 8 | rob@example.com cluster-wide ClusterRole/view 9 | rob@example.com nginx-ingress ClusterRole/edit 10 | ``` 11 | 12 | The wide output option includes the kind of subject along with the source role binding. 13 | 14 | ``` 15 | rbac-lookup rob --output wide 16 | 17 | SUBJECT SCOPE ROLE SOURCE 18 | User/rob@example.com cluster-wide ClusterRole/view ClusterRoleBinding/rob-cluster-view 19 | User/rob@example.com nginx-ingress ClusterRole/edit RoleBinding/rob-edit 20 | User/ron@example.com web ClusterRole/edit RoleBinding/ron-edit 21 | ServiceAccount/rops infra ClusterRole/admin RoleBinding/rops-admin 22 | ``` 23 | 24 | It's also possible to filter output by the kind of RBAC Subject. The `--kind` or `-k` parameter accepts `user`, `group`, and `serviceaccount` as values. 25 | 26 | ``` 27 | rbac-lookup ro --output wide --kind user 28 | 29 | SUBJECT SCOPE ROLE SOURCE 30 | User/rob@example.com cluster-wide ClusterRole/view ClusterRoleBinding/rob-cluster-view 31 | User/rob@example.com nginx-ingress ClusterRole/edit RoleBinding/rob-edit 32 | User/ron@example.com web ClusterRole/edit RoleBinding/ron-edit 33 | ``` 34 | 35 | ## Flags Supported 36 | ``` 37 | --context string context to use for Kubernetes config 38 | --gke enable GKE integration 39 | -h, --help help for rbac-lookup 40 | -k, --kind string filter by this RBAC subject kind (user, group, serviceaccount) 41 | --kubeconfig string config file location 42 | -o, --output string output format (normal, wide) 43 | ``` 44 | -------------------------------------------------------------------------------- /lookup/gke_roles.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookup 16 | 17 | var gkeIamScope = "project-wide" 18 | var gkeIamRoles = map[string]simpleRole{ 19 | "roles/container.clusterAdmin": { 20 | Kind: "IAM", 21 | Name: "gke-cluster-admin", 22 | Source: simpleRoleSource{ 23 | Kind: "IAMRole", 24 | Name: "container.clusterAdmin", 25 | }, 26 | }, 27 | "roles/container.admin": { 28 | Kind: "IAM", 29 | Name: "gke-admin", 30 | Source: simpleRoleSource{ 31 | Kind: "IAMRole", 32 | Name: "container.admin", 33 | }, 34 | }, 35 | "roles/container.developer": { 36 | Kind: "IAM", 37 | Name: "gke-developer", 38 | Source: simpleRoleSource{ 39 | Kind: "IAMRole", 40 | Name: "container.developer", 41 | }, 42 | }, 43 | "roles/container.viewer": { 44 | Kind: "IAM", 45 | Name: "gke-viewer", 46 | Source: simpleRoleSource{ 47 | Kind: "IAMRole", 48 | Name: "container.viewer", 49 | }, 50 | }, 51 | "roles/owner": { 52 | Kind: "IAM", 53 | Name: "gcp-owner", 54 | Source: simpleRoleSource{ 55 | Kind: "IAMRole", 56 | Name: "owner", 57 | }, 58 | }, 59 | "roles/admin": { 60 | Kind: "IAM", 61 | Name: "gcp-admin", 62 | Source: simpleRoleSource{ 63 | Kind: "IAMRole", 64 | Name: "admin", 65 | }, 66 | }, 67 | "roles/editor": { 68 | Kind: "IAM", 69 | Name: "gcp-editor", 70 | Source: simpleRoleSource{ 71 | Kind: "IAMRole", 72 | Name: "editor", 73 | }, 74 | }, 75 | "roles/viewer": { 76 | Kind: "IAM", 77 | Name: "gcp-viewer", 78 | Source: simpleRoleSource{ 79 | Kind: "IAMRole", 80 | Name: "viewer", 81 | }, 82 | }, 83 | } 84 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strings" 21 | 22 | "github.com/fairwindsops/rbac-lookup/lookup" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var ( 27 | version string 28 | commit string 29 | outputFormat string 30 | enableGke bool 31 | kubeConfig string 32 | kubeContext string 33 | subjectKind string 34 | ) 35 | 36 | var rootCmd = &cobra.Command{ 37 | Use: "rbac-lookup [subject query]", 38 | Short: "rbac-lookup provides a simple way to view RBAC bindings by user", 39 | Long: "rbac-lookup provides a missing Kubernetes API to view RBAC bindings by user", 40 | Args: cobra.RangeArgs(0, 1), 41 | Run: func(cmd *cobra.Command, args []string) { 42 | if err := cmd.ParseFlags(args); err != nil { 43 | fmt.Printf("Error parsing flags: %v\n", err) 44 | } 45 | 46 | subjectKind = strings.ToLower(subjectKind) 47 | 48 | lookup.List(args, kubeConfig, kubeContext, outputFormat, subjectKind, enableGke) 49 | }, 50 | } 51 | 52 | func init() { 53 | rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "", "output format (normal, wide)") 54 | rootCmd.PersistentFlags().StringVarP(&kubeConfig, "kubeconfig", "", "", "config file location") 55 | rootCmd.PersistentFlags().StringVarP(&kubeContext, "context", "", "", "context to use for Kubernetes config") 56 | rootCmd.PersistentFlags().StringVarP(&subjectKind, "kind", "k", "", "filter by this RBAC subject kind (user, group, serviceaccount)") 57 | rootCmd.PersistentFlags().BoolVar(&enableGke, "gke", false, "enable GKE integration") 58 | } 59 | 60 | // Execute is the primary entrypoint for this CLI 61 | func Execute(VERSION string, COMMIT string) { 62 | version = VERSION 63 | commit = COMMIT 64 | if err := rootCmd.Execute(); err != nil { 65 | fmt.Println(err) 66 | os.Exit(1) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | // This file is generated from FairwindsOps/documentation-template 2 | // DO NOT EDIT MANUALLY 3 | 4 | const fs = require('fs'); 5 | const npath = require('path'); 6 | 7 | const CONFIG_FILE = npath.join(__dirname, 'config-extras.js'); 8 | const BASE_DIR = npath.join(__dirname, '..'); 9 | 10 | const extras = require(CONFIG_FILE); 11 | if (!extras.title || !extras.description || !extras.themeConfig.docsRepo) { 12 | throw new Error("Please specify 'title', 'description', and 'themeConfig.docsRepo' in config-extras.js"); 13 | } 14 | 15 | const docFiles = fs.readdirSync(BASE_DIR) 16 | .filter(f => f !== "README.md") 17 | .filter(f => f !== ".vuepress") 18 | .filter(f => f !== "node_modules") 19 | .filter(f => npath.extname(f) === '.md' || npath.extname(f) === ''); 20 | 21 | const sidebar = [['/', 'Home']].concat(docFiles.map(f => { 22 | const ext = npath.extname(f); 23 | if (ext === '') { 24 | // this is a directory 25 | const title = f; 26 | const children = fs.readdirSync(npath.join(BASE_DIR, f)).map(subf => { 27 | return '/' + f + '/' + npath.basename(subf); 28 | }); 29 | return {title, children}; 30 | } 31 | const path = npath.basename(f); 32 | return path; 33 | })); 34 | 35 | const baseConfig = { 36 | title: "", 37 | description: "", 38 | head: [ 39 | ['link', { rel: 'icon', href: '/favicon.png' }], 40 | ['script', { src: '/scripts/modify.js' }], 41 | ['script', { src: '/scripts/marketing.js' }], 42 | ], 43 | themeConfig: { 44 | docsRepo: "", 45 | docsDir: 'docs', 46 | editLinks: true, 47 | editLinkText: "Help us improve this page", 48 | logo: '/img/fairwinds-logo.svg', 49 | heroText: "", 50 | sidebar, 51 | nav: [ 52 | {text: 'View on GitHub', link: 'https://github.com/' + extras.themeConfig.docsRepo}, 53 | ], 54 | }, 55 | plugins: { 56 | 'vuepress-plugin-clean-urls': { 57 | normalSuffix: '/', 58 | notFoundPath: '/404.html', 59 | }, 60 | 'check-md': {}, 61 | }, 62 | } 63 | 64 | let config = JSON.parse(JSON.stringify(baseConfig)) 65 | if (!fs.existsSync(CONFIG_FILE)) { 66 | throw new Error("Please add config-extras.js to specify your project details"); 67 | } 68 | for (let key in extras) { 69 | if (!config[key]) config[key] = extras[key]; 70 | else if (key === 'head') config[key] = config[key].concat(extras[key]); 71 | else Object.assign(config[key], extras[key]); 72 | } 73 | module.exports = config; 74 | -------------------------------------------------------------------------------- /lookup/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookup 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "k8s.io/client-go/kubernetes" 22 | "k8s.io/client-go/tools/clientcmd" 23 | 24 | // Required for GKE, OIDC, and more 25 | _ "k8s.io/client-go/plugin/pkg/client/auth" 26 | ) 27 | 28 | // List outputs rbac bindings where subject names match given string 29 | func List(args []string, kubeConfig, kubeContext, outputFormat, subjectKind string, enableGke bool) { 30 | 31 | clientConfig := getClientConfig(kubeConfig, kubeContext) 32 | 33 | kubeconfig, err := clientConfig.ClientConfig() 34 | if err != nil { 35 | fmt.Printf("Error getting Kubernetes config: %v\n", err) 36 | os.Exit(1) 37 | } 38 | 39 | clientset, err := kubernetes.NewForConfig(kubeconfig) 40 | if err != nil { 41 | fmt.Printf("Error generating Kubernetes clientset from kubeconfig: %v\n", err) 42 | os.Exit(2) 43 | } 44 | 45 | filter := "" 46 | if len(args) > 0 { 47 | filter = args[0] 48 | } 49 | 50 | l := lister{ 51 | filter: filter, 52 | subjectKind: subjectKind, 53 | clientset: clientset, 54 | rbacSubjectsByScope: make(map[string]rbacSubject), 55 | } 56 | 57 | if enableGke { 58 | rawConfig, err := clientConfig.RawConfig() 59 | if err != nil { 60 | fmt.Printf("Error getting Kubernetes raw config: %v\n", err) 61 | os.Exit(3) 62 | } 63 | 64 | ci := getClusterInfo(&rawConfig, kubeContext) 65 | l.gkeParsedProjectName = ci.ParsedProjectName 66 | } 67 | 68 | loadErr := l.loadAll() 69 | if loadErr != nil { 70 | fmt.Printf("Error loading RBAC Bindings: %v\n", loadErr) 71 | os.Exit(4) 72 | } 73 | 74 | l.printRbacBindings(outputFormat) 75 | } 76 | 77 | func getClientConfig(kubeConfig, kubeContext string) clientcmd.ClientConfig { 78 | configRules := clientcmd.NewDefaultClientConfigLoadingRules() 79 | configRules.ExplicitPath = kubeConfig 80 | return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 81 | configRules, 82 | &clientcmd.ConfigOverrides{CurrentContext: kubeContext}, 83 | ) 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | RBAC Lookup 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | RBAC Lookup is a CLI that allows you to easily find Kubernetes roles and cluster roles bound to any user, service account, or group name. Binaries are generated with goreleaser for each release for simple installation. 16 | 17 | # Documentation 18 | Check out the [documentation at docs.fairwinds.com](https://rbac-lookup.docs.fairwinds.com/) 19 | 20 | ## Installation 21 | 22 | ### Homebrew 23 | ``` 24 | brew install FairwindsOps/tap/rbac-lookup 25 | ``` 26 | 27 | ### ASDF 28 | ``` 29 | asdf plugin add rbac-lookup 30 | asdf install rbac-lookup latest 31 | asdf global rbac-lookup latest 32 | ``` 33 | 34 | ## RBAC Manager 35 | While RBAC Lookup helps provide visibility into Kubernetes auth, RBAC Manager helps make auth simpler to manage. This is a Kubernetes operator that enables more concise RBAC configuration that is easier to scale and automate. For more information, see [RBAC Manager on GitHub](https://github.com/FairwindsOps/rbac-manager). 36 | 37 | 38 | ## Join the Fairwinds Open Source Community 39 | 40 | The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap, 41 | and network with fellow Kubernetes users. 42 | [Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-2na8gtwb4-DGQ4qgmQbczQyB2NlFlYQQ) 43 | 44 | ## Other Projects from Fairwinds 45 | 46 | Enjoying rbac-lookup? Check out some of our other projects: 47 | * [Polaris](https://github.com/FairwindsOps/Polaris) - Audit, enforce, and build policies for Kubernetes resources, including over 20 built-in checks for best practices 48 | * [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage 49 | * [Pluto](https://github.com/FairwindsOps/Pluto) - Detect Kubernetes resources that have been deprecated or removed in future versions 50 | * [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available 51 | * [rbac-manager](https://github.com/FairwindsOps/rbac-manager) - Simplify the management of RBAC in your Kubernetes clusters 52 | 53 | Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=rbac-lookup&utm_medium=rbac-lookup&utm_campaign=rbac-lookup) 54 | -------------------------------------------------------------------------------- /docs/contributing/guide.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Issues, whether bugs, tasks, or feature requests are essential for keeping rbac-lookup great. We believe it should be as easy as possible to contribute changes that get things working in your environment. There are a few guidelines that we need contributors to follow so that we can keep on top of things. 4 | 5 | ## Code of Conduct 6 | 7 | This project adheres to a [code of conduct](/contributing/code-of-conduct.md). Please review this document before contributing to this project. 8 | 9 | ## Sign the CLA 10 | Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/fairwindsops/rbac-lookup). 11 | 12 | ## Project Structure 13 | 14 | rbac-lookup is a relatively simple cobra cli tool that looks up information about rbac in a cluster. The [/cmd](https://github.com/FairwindsOps/rbac-lookup/tree/master/cmd) folder contains the flags and other cobra config, while the [/lookup](https://github.com/FairwindsOps/rbac-lookup/tree/master/lookup) folder has the code for looking up rbac information a cluster. There is additinal code that allows the user to see GKE IAM information as well, since GKE IAM is so closely tied to rbac. 15 | 16 | ## Getting Started 17 | 18 | We label issues with the ["good first issue" tag](https://github.com/FairwindsOps/rbac-lookup/labels/good%20first%20issue) if we believe they'll be a good starting point for new contributors. If you're interested in working on an issue, please start a conversation on that issue, and we can help answer any questions as they come up. 19 | 20 | ## Setting Up Your Development Environment 21 | 22 | ### Prerequisites 23 | 24 | * A properly configured Golang environment with Go 1.17 or higher 25 | * Access to a cluster via a properly configured KUBECONFIG 26 | 27 | ### Installation 28 | 29 | * Install the project with `go get github.com/fairwindsops/rbac-lookup` 30 | * Change into the rbac-lookup directory which is installed at `$GOPATH/src/github.com/fairwindsops/rbac-lookup` 31 | * Use `make build` to build the binary locally. 32 | * Use `make test` to run the tests and generate a coverage report. 33 | * Use `make lint` to run [golangci-lint](https://github.com/golangci/golangci-lint) (requires golangci-lint installed) 34 | 35 | ## Creating a New Issue 36 | 37 | If you've encountered an issue that is not already reported, please create an issue that contains the following: 38 | 39 | - Clear description of the issue 40 | - Steps to reproduce it 41 | - Appropriate labels 42 | 43 | ## Creating a Pull Request 44 | 45 | Each new pull request should: 46 | 47 | - Reference any related issues 48 | - Add tests that show the issues have been solved 49 | - Pass existing tests and linting 50 | - Contain a clear indication of if they're ready for review or a work in progress 51 | - Be up to date and/or rebased on the master branch 52 | 53 | ## Creating a new release 54 | 55 | Push a new annotated tag. This tag should contain a changelog of pertinent changes. Goreleaser will take care of the rest. 56 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fairwindsops/rbac-lookup 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/spf13/cobra v1.7.0 7 | github.com/stretchr/testify v1.8.4 8 | golang.org/x/net v0.15.0 9 | golang.org/x/oauth2 v0.12.0 10 | google.golang.org/api v0.138.0 11 | k8s.io/api v0.28.1 12 | k8s.io/apimachinery v0.28.1 13 | k8s.io/client-go v0.28.1 14 | ) 15 | 16 | require ( 17 | cloud.google.com/go/compute v1.23.0 // indirect 18 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 21 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 22 | github.com/go-logr/logr v1.2.4 // indirect 23 | github.com/go-openapi/jsonpointer v0.20.0 // indirect 24 | github.com/go-openapi/jsonreference v0.20.2 // indirect 25 | github.com/go-openapi/swag v0.22.4 // indirect 26 | github.com/gogo/protobuf v1.3.2 // indirect 27 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 28 | github.com/golang/protobuf v1.5.3 // indirect 29 | github.com/google/gnostic-models v0.6.8 // indirect 30 | github.com/google/go-cmp v0.5.9 // indirect 31 | github.com/google/gofuzz v1.2.0 // indirect 32 | github.com/google/s2a-go v0.1.7 // indirect 33 | github.com/google/uuid v1.3.1 // indirect 34 | github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect 35 | github.com/googleapis/gax-go/v2 v2.12.0 // indirect 36 | github.com/imdario/mergo v0.3.16 // indirect 37 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 38 | github.com/josharian/intern v1.0.0 // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/mailru/easyjson v0.7.7 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 44 | github.com/pkg/errors v0.9.1 // indirect 45 | github.com/pmezard/go-difflib v1.0.0 // indirect 46 | github.com/spf13/pflag v1.0.5 // indirect 47 | go.opencensus.io v0.24.0 // indirect 48 | golang.org/x/crypto v0.13.0 // indirect 49 | golang.org/x/sys v0.12.0 // indirect 50 | golang.org/x/term v0.12.0 // indirect 51 | golang.org/x/text v0.13.0 // indirect 52 | golang.org/x/time v0.3.0 // indirect 53 | google.golang.org/appengine v1.6.7 // indirect 54 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect 55 | google.golang.org/grpc v1.57.0 // indirect 56 | google.golang.org/protobuf v1.31.0 // indirect 57 | gopkg.in/inf.v0 v0.9.1 // indirect 58 | gopkg.in/yaml.v2 v2.4.0 // indirect 59 | gopkg.in/yaml.v3 v3.0.1 // indirect 60 | k8s.io/klog/v2 v2.100.1 // indirect 61 | k8s.io/kube-openapi v0.0.0-20230901164831-6c774f458599 // indirect 62 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect 63 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 64 | sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect 65 | sigs.k8s.io/yaml v1.3.0 // indirect 66 | ) 67 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@fairwinds.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /docs/contributing/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 19 | 23 | 25 | 28 | 29 | 32 | 34 | 36 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/.vuepress/public/img/fairwinds-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | rok8s: fairwinds/rok8s-scripts@12 5 | oss-docs: fairwinds/oss-docs@0 6 | 7 | executors: 8 | golang-exec: 9 | docker: 10 | - image: cimg/go:1.20 11 | 12 | references: 13 | e2e_configuration: &e2e_configuration 14 | command_runner_image: quay.io/reactiveops/ci-images:v12-buster 15 | executor: golang-exec 16 | pre_script: e2e/pre.sh 17 | store-test-results: /tmp/test-results/ 18 | script: e2e/test.sh 19 | requires: 20 | - test 21 | filters: 22 | branches: 23 | only: /.*/ 24 | tags: 25 | ignore: /.*/ 26 | install_vault_alpine: &install_vault_alpine 27 | run: 28 | name: install hashicorp vault 29 | command: | 30 | apk --update add curl yq 31 | cd /tmp 32 | curl -LO https://releases.hashicorp.com/vault/1.13.2/vault_1.13.2_linux_amd64.zip 33 | sha256sum vault_1.13.2_linux_amd64.zip | grep f7930279de8381de7c532164b4a4408895d9606c0d24e2e9d2f9acb5dfe99b3c 34 | unzip vault_1.13.2_linux_amd64.zip 35 | mv vault /usr/bin/vault 36 | jobs: 37 | test: 38 | working_directory: /home/circleci/go/src/github.com/fairwindsops/rbac-lookup 39 | docker: 40 | - image: cimg/go:1.20 41 | environment: 42 | GO111MODULE: "on" 43 | steps: 44 | - checkout 45 | - run: go mod download && go mod verify 46 | - run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic 47 | snapshot: 48 | working_directory: /go/src/github.com/fairwindsops/rbac-lookup 49 | resource_class: large 50 | docker: 51 | - image: goreleaser/goreleaser:v1.18.2 52 | steps: 53 | - checkout 54 | - setup_remote_docker 55 | - run: goreleaser --snapshot --skip-sign 56 | - store_artifacts: 57 | path: dist 58 | destination: snapshot 59 | release: 60 | resource_class: large 61 | working_directory: /go/src/github.com/fairwindsops/rbac-lookup 62 | docker: 63 | - image: goreleaser/goreleaser:v1.18.2 64 | steps: 65 | - checkout 66 | - setup_remote_docker 67 | - *install_vault_alpine 68 | - rok8s/get_vault_env: 69 | vault_path: repo/global/env 70 | - rok8s/get_vault_env: 71 | vault_path: repo/rbac-lookup/env 72 | - run: go mod download && go mod verify 73 | - run: goreleaser 74 | workflows: 75 | version: 2 76 | test: 77 | jobs: 78 | - test: 79 | filters: 80 | branches: 81 | only: /.*/ 82 | tags: 83 | ignore: /.*/ 84 | - snapshot: 85 | requires: 86 | - test 87 | filters: 88 | branches: 89 | only: /.*/ 90 | tags: 91 | ignore: /.*/ 92 | - rok8s/kubernetes_e2e_tests: 93 | name: "End-To-End Kubernetes 1.23" 94 | kind_node_image: "kindest/node:v1.23.13@sha256:ef453bb7c79f0e3caba88d2067d4196f427794086a7d0df8df4f019d5e336b61" 95 | <<: *e2e_configuration 96 | - rok8s/kubernetes_e2e_tests: 97 | name: "End-To-End Kubernetes 1.24" 98 | kind_node_image: "kindest/node:v1.24.7@sha256:577c630ce8e509131eab1aea12c022190978dd2f745aac5eb1fe65c0807eb315" 99 | <<: *e2e_configuration 100 | - rok8s/kubernetes_e2e_tests: 101 | name: "End-To-End Kubernetes 1.25" 102 | kind_node_image: "kindest/node:v1.25.3@sha256:f52781bc0d7a19fb6c405c2af83abfeb311f130707a0e219175677e366cc45d1" 103 | <<: *e2e_configuration 104 | release: 105 | jobs: 106 | - release: 107 | filters: 108 | branches: 109 | ignore: /.*/ 110 | tags: 111 | only: /.*/ 112 | - oss-docs/publish-docs: 113 | repository: rbac-lookup 114 | filters: 115 | branches: 116 | ignore: /.*/ 117 | tags: 118 | only: /^.*/ 119 | -------------------------------------------------------------------------------- /lookup/gke.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookup 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | "strings" 22 | 23 | "golang.org/x/net/context" 24 | "golang.org/x/oauth2/google" 25 | "google.golang.org/api/cloudresourcemanager/v1" 26 | 27 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 28 | 29 | // Required for GKE Auth 30 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 31 | ) 32 | 33 | type gkeClusterInfo struct { 34 | ClusterName string 35 | Region string 36 | ParsedProjectName string 37 | } 38 | 39 | func getClusterInfo(c *clientcmdapi.Config, kubeContext string) *gkeClusterInfo { 40 | context := c.Contexts[c.CurrentContext] 41 | if kubeContext != "" { 42 | context = c.Contexts[kubeContext] 43 | } 44 | 45 | ci := gkeClusterInfo{} 46 | 47 | if context != nil && context.Cluster != "" { 48 | s := strings.Split(context.Cluster, "_") 49 | if s[0] == "gke" { 50 | ci.ClusterName = s[3] 51 | ci.Region = s[2] 52 | ci.ParsedProjectName = s[1] 53 | } 54 | } 55 | 56 | return &ci 57 | } 58 | 59 | func loadGkeIAMPolicy(parsedProjectName string) (*cloudresourcemanager.Policy, error) { 60 | ctx := context.Background() 61 | 62 | c, err := google.DefaultClient(ctx, cloudresourcemanager.CloudPlatformReadOnlyScope) 63 | if err != nil { 64 | fmt.Println("Error initializing Google API client") 65 | return nil, err 66 | } 67 | 68 | crmService, err := cloudresourcemanager.New(c) 69 | if err != nil { 70 | fmt.Println("Error initializing Google Cloud Resource Manager") 71 | return nil, err 72 | } 73 | 74 | ipr := &cloudresourcemanager.GetIamPolicyRequest{} 75 | 76 | var policy *cloudresourcemanager.Policy 77 | var err1, err2, err3 error 78 | 79 | policy, err1 = crmService.Projects.GetIamPolicy(parsedProjectName, ipr).Context(ctx).Do() 80 | if err1 != nil { 81 | fmt.Printf("Could not load IAM policy for %s project from parsed kubeconfig\n", parsedProjectName) 82 | 83 | var credentials *google.Credentials 84 | credentials, err2 = google.FindDefaultCredentials(ctx, cloudresourcemanager.CloudPlatformReadOnlyScope) 85 | 86 | if err2 != nil { 87 | return nil, err2 88 | } 89 | 90 | if credentials.ProjectID == "" { 91 | fmt.Println("No project ID found in default GCP credentials") 92 | return getPolicyFromEnvVar(crmService, ipr) 93 | } 94 | 95 | policy, err3 = crmService.Projects.GetIamPolicy(credentials.ProjectID, ipr).Context(ctx).Do() 96 | 97 | if err3 != nil { 98 | fmt.Printf("Could not load IAM policy for %s project from default GCP credentials\n", credentials.ProjectID) 99 | return getPolicyFromEnvVar(crmService, ipr) 100 | } 101 | 102 | return policy, nil 103 | } 104 | 105 | return policy, nil 106 | } 107 | 108 | func getPolicyFromEnvVar(crmService *cloudresourcemanager.Service, ipr *cloudresourcemanager.GetIamPolicyRequest) (*cloudresourcemanager.Policy, error) { 109 | envVar := os.Getenv("CLOUDSDK_CORE_PROJECT") 110 | if envVar == "" { 111 | return nil, errors.New("Error loading IAM policies for GKE, try setting CLOUDSDK_CORE_PROJECT environment variable") 112 | } 113 | 114 | policy, err := crmService.Projects.GetIamPolicy(envVar, ipr).Context(context.Background()).Do() 115 | 116 | if err != nil { 117 | fmt.Printf("Could not load IAM policy for %s project from CLOUDSDK_CORE_PROJECT environment variable\n", envVar) 118 | return nil, err 119 | } 120 | 121 | fmt.Printf("GCP IAM policy loaded for %s project from CLOUDSDK_CORE_PROJECT environment variable\n\n", envVar) 122 | return policy, nil 123 | } 124 | -------------------------------------------------------------------------------- /lookup/lister.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookup 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "sort" 22 | "strings" 23 | "text/tabwriter" 24 | 25 | "google.golang.org/api/cloudresourcemanager/v1" 26 | 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | 29 | "k8s.io/client-go/kubernetes" 30 | 31 | // Required for different auth providers like GKE, OIDC 32 | _ "k8s.io/client-go/plugin/pkg/client/auth" 33 | ) 34 | 35 | type lister struct { 36 | clientset kubernetes.Interface 37 | filter string 38 | gkeParsedProjectName string 39 | subjectKind string 40 | rbacSubjectsByScope map[string]rbacSubject 41 | } 42 | 43 | func (l *lister) loadAll() error { 44 | rbErr := l.loadRoleBindings() 45 | 46 | if rbErr != nil { 47 | return rbErr 48 | } 49 | 50 | crbErr := l.loadClusterRoleBindings() 51 | 52 | if crbErr != nil { 53 | return crbErr 54 | } 55 | 56 | if l.gkeParsedProjectName != "" { 57 | policy, gkeErr := loadGkeIAMPolicy(l.gkeParsedProjectName) 58 | 59 | if gkeErr != nil { 60 | return gkeErr 61 | } 62 | 63 | l.loadGkeIamPolicy(policy) 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func (l *lister) printRbacBindings(outputFormat string) { 70 | if len(l.rbacSubjectsByScope) < 1 { 71 | fmt.Println("No RBAC Bindings found") 72 | return 73 | } 74 | 75 | names := make([]string, 0, len(l.rbacSubjectsByScope)) 76 | for name := range l.rbacSubjectsByScope { 77 | names = append(names, name) 78 | } 79 | sort.Strings(names) 80 | 81 | w := new(tabwriter.Writer) 82 | w.Init(os.Stdout, 0, 8, 2, ' ', 0) 83 | 84 | if outputFormat == "wide" { 85 | fmt.Fprintln(w, "SUBJECT\t SCOPE\t ROLE\t SOURCE") 86 | } else { 87 | fmt.Fprintln(w, "SUBJECT\t SCOPE\t ROLE") 88 | } 89 | 90 | for _, subjectName := range names { 91 | rbacSubject := l.rbacSubjectsByScope[subjectName] 92 | for scope, simpleRoles := range rbacSubject.RolesByScope { 93 | for _, simpleRole := range simpleRoles { 94 | if outputFormat == "wide" { 95 | fmt.Fprintf(w, "%s/%s \t %s\t %s/%s\t %s/%s\n", rbacSubject.Kind, subjectName, scope, simpleRole.Kind, simpleRole.Name, simpleRole.Source.Kind, simpleRole.Source.Name) 96 | } else { 97 | fmt.Fprintf(w, "%s \t %s\t %s/%s\n", subjectName, scope, simpleRole.Kind, simpleRole.Name) 98 | } 99 | } 100 | } 101 | } 102 | w.Flush() 103 | } 104 | 105 | func (l *lister) loadRoleBindings() error { 106 | roleBindings, err := l.clientset.RbacV1().RoleBindings("").List(context.Background(), metav1.ListOptions{}) 107 | 108 | if err != nil { 109 | fmt.Println("Error loading role bindings") 110 | return err 111 | } 112 | 113 | for _, roleBinding := range roleBindings.Items { 114 | for _, subject := range roleBinding.Subjects { 115 | if l.nameMatches(subject.Name) && l.kindMatches(subject.Kind) { 116 | subjectKey := subject.Name 117 | if subject.Kind == "ServiceAccount" { 118 | subjectKey = fmt.Sprintf("%s:%s", subject.Namespace, subject.Name) 119 | } 120 | if rbacSubj, exist := l.rbacSubjectsByScope[subjectKey]; exist { 121 | rbacSubj.addRoleBinding(&roleBinding) 122 | } else { 123 | rbacSubj := rbacSubject{ 124 | Kind: subject.Kind, 125 | RolesByScope: make(map[string][]simpleRole), 126 | } 127 | rbacSubj.addRoleBinding(&roleBinding) 128 | 129 | l.rbacSubjectsByScope[subjectKey] = rbacSubj 130 | } 131 | } 132 | } 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func (l *lister) loadClusterRoleBindings() error { 139 | clusterRoleBindings, err := l.clientset.RbacV1().ClusterRoleBindings().List(context.Background(), metav1.ListOptions{}) 140 | 141 | if err != nil { 142 | fmt.Println("Error loading cluster role bindings") 143 | return err 144 | } 145 | 146 | for _, clusterRoleBinding := range clusterRoleBindings.Items { 147 | for _, subject := range clusterRoleBinding.Subjects { 148 | if l.nameMatches(subject.Name) && l.kindMatches(subject.Kind) { 149 | subjectKey := subject.Name 150 | if subject.Kind == "ServiceAccount" { 151 | subjectKey = fmt.Sprintf("%s:%s", subject.Namespace, subject.Name) 152 | } 153 | if rbacSubj, exist := l.rbacSubjectsByScope[subjectKey]; exist { 154 | rbacSubj.addClusterRoleBinding(&clusterRoleBinding) 155 | } else { 156 | rbacSubj := rbacSubject{ 157 | Kind: subject.Kind, 158 | RolesByScope: make(map[string][]simpleRole), 159 | } 160 | rbacSubj.addClusterRoleBinding(&clusterRoleBinding) 161 | 162 | l.rbacSubjectsByScope[subjectKey] = rbacSubj 163 | } 164 | } 165 | } 166 | } 167 | 168 | return nil 169 | } 170 | 171 | func (l *lister) loadGkeIamPolicy(policy *cloudresourcemanager.Policy) { 172 | for _, binding := range policy.Bindings { 173 | if sr, ok := gkeIamRoles[binding.Role]; ok { 174 | for _, member := range binding.Members { 175 | s := strings.Split(member, ":") 176 | memberKind := strings.Title(s[0]) 177 | memberName := s[1] 178 | if l.nameMatches(memberName) && l.kindMatches(memberKind) { 179 | rbacSubj, exist := l.rbacSubjectsByScope[memberName] 180 | if !exist { 181 | rbacSubj = rbacSubject{ 182 | Kind: memberKind, 183 | RolesByScope: make(map[string][]simpleRole), 184 | } 185 | } 186 | 187 | rbacSubj.RolesByScope[gkeIamScope] = append(rbacSubj.RolesByScope[gkeIamScope], sr) 188 | l.rbacSubjectsByScope[memberName] = rbacSubj 189 | } 190 | } 191 | } 192 | } 193 | } 194 | 195 | func (l *lister) nameMatches(name string) bool { 196 | return l.filter == "" || strings.Contains(name, l.filter) 197 | } 198 | 199 | func (l *lister) kindMatches(kind string) bool { 200 | if l.subjectKind == "" { 201 | return true 202 | } 203 | 204 | lowerKind := strings.ToLower(kind) 205 | 206 | return lowerKind == l.subjectKind 207 | } 208 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 FairwindsOps Inc 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lookup/lister_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 FairwindsOps Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookup 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | "golang.org/x/net/context" 22 | 23 | "google.golang.org/api/cloudresourcemanager/v1" 24 | 25 | rbacv1 "k8s.io/api/rbac/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | 28 | testclient "k8s.io/client-go/kubernetes/fake" 29 | ) 30 | 31 | func TestLoadRoleBindings(t *testing.T) { 32 | l := genLister() 33 | 34 | loadRoleBindings(t, l) 35 | 36 | assert.Len(t, l.rbacSubjectsByScope, 0, "Expected no rbac subjects initially") 37 | 38 | createRoleBindings(t, l) 39 | 40 | loadRoleBindings(t, l) 41 | 42 | assert.Len(t, l.rbacSubjectsByScope, 3, "Expected 3 rbac subjects") 43 | 44 | expectedRbacSubject := rbacSubject{ 45 | Kind: "User", 46 | RolesByScope: map[string][]simpleRole{ 47 | "foo": {{ 48 | Kind: "Role", 49 | Name: "bar", 50 | Source: simpleRoleSource{ 51 | Kind: "RoleBinding", 52 | Name: "testing", 53 | }, 54 | }}, 55 | }, 56 | } 57 | 58 | expectedRbacSubjectSA := rbacSubject{ 59 | Kind: "ServiceAccount", 60 | RolesByScope: map[string][]simpleRole{ 61 | "two": {{ 62 | Kind: "ClusterRole", 63 | Name: "cluster-admin", 64 | Source: simpleRoleSource{ 65 | Kind: "RoleBinding", 66 | Name: "testing-sa", 67 | }, 68 | }}, 69 | "three": {{ 70 | Kind: "ClusterRole", 71 | Name: "cluster-admin", 72 | Source: simpleRoleSource{ 73 | Kind: "RoleBinding", 74 | Name: "testing-sa", 75 | }, 76 | }}, 77 | }, 78 | } 79 | 80 | assert.EqualValues(t, expectedRbacSubject, l.rbacSubjectsByScope["joe"]) 81 | assert.EqualValues(t, expectedRbacSubject, l.rbacSubjectsByScope["sue"]) 82 | assert.EqualValues(t, expectedRbacSubjectSA, l.rbacSubjectsByScope["circleci:circleci"]) 83 | } 84 | 85 | func TestLoadClusterRoleBindings(t *testing.T) { 86 | l := genLister() 87 | 88 | loadClusterRoleBindings(t, l) 89 | 90 | assert.Len(t, l.rbacSubjectsByScope, 0, "Expected no rbac subjects initially") 91 | 92 | createClusterRoleBindings(t, l) 93 | 94 | loadClusterRoleBindings(t, l) 95 | 96 | assert.Len(t, l.rbacSubjectsByScope, 3, "Expected 3 rbac subjects") 97 | 98 | expectedRbacSubject := rbacSubject{ 99 | Kind: "User", 100 | RolesByScope: map[string][]simpleRole{ 101 | "cluster-wide": {{ 102 | Kind: "ClusterRole", 103 | Name: "bar", 104 | Source: simpleRoleSource{ 105 | Kind: "ClusterRoleBinding", 106 | Name: "testing", 107 | }, 108 | }}, 109 | }, 110 | } 111 | 112 | expectedRbacSubjectSA := rbacSubject{ 113 | Kind: "ServiceAccount", 114 | RolesByScope: map[string][]simpleRole{ 115 | "cluster-wide": {{ 116 | Kind: "ClusterRole", 117 | Name: "cluster-admin", 118 | Source: simpleRoleSource{ 119 | Kind: "ClusterRoleBinding", 120 | Name: "circleci-cluster-admin", 121 | }, 122 | }}, 123 | }, 124 | } 125 | 126 | assert.EqualValues(t, expectedRbacSubject, l.rbacSubjectsByScope["joe"]) 127 | assert.EqualValues(t, expectedRbacSubject, l.rbacSubjectsByScope["sue"]) 128 | assert.EqualValues(t, expectedRbacSubjectSA, l.rbacSubjectsByScope["circleci:circleci"]) 129 | } 130 | 131 | func TestLoadAll(t *testing.T) { 132 | l := genLister() 133 | 134 | loadAll(t, l) 135 | 136 | assert.Len(t, l.rbacSubjectsByScope, 0, "Expected no rbac subjects initially") 137 | 138 | createRoleBindings(t, l) 139 | 140 | createClusterRoleBindings(t, l) 141 | 142 | loadAll(t, l) 143 | 144 | assert.Len(t, l.rbacSubjectsByScope, 3, "Expected 3 rbac subjects") 145 | 146 | expectedRbacSubject := rbacSubject{ 147 | Kind: "User", 148 | RolesByScope: map[string][]simpleRole{ 149 | "cluster-wide": {{ 150 | Kind: "ClusterRole", 151 | Name: "bar", 152 | Source: simpleRoleSource{ 153 | Kind: "ClusterRoleBinding", 154 | Name: "testing", 155 | }, 156 | }}, 157 | "foo": {{ 158 | Kind: "Role", 159 | Name: "bar", 160 | Source: simpleRoleSource{ 161 | Kind: "RoleBinding", 162 | Name: "testing", 163 | }, 164 | }}, 165 | }, 166 | } 167 | 168 | expectedRbacSubjectSA := rbacSubject{ 169 | Kind: "ServiceAccount", 170 | RolesByScope: map[string][]simpleRole{ 171 | "cluster-wide": {{ 172 | Kind: "ClusterRole", 173 | Name: "cluster-admin", 174 | Source: simpleRoleSource{ 175 | Kind: "ClusterRoleBinding", 176 | Name: "circleci-cluster-admin", 177 | }, 178 | }}, 179 | "two": {{ 180 | Kind: "ClusterRole", 181 | Name: "cluster-admin", 182 | Source: simpleRoleSource{ 183 | Kind: "RoleBinding", 184 | Name: "testing-sa", 185 | }, 186 | }}, 187 | "three": {{ 188 | Kind: "ClusterRole", 189 | Name: "cluster-admin", 190 | Source: simpleRoleSource{ 191 | Kind: "RoleBinding", 192 | Name: "testing-sa", 193 | }, 194 | }}, 195 | }, 196 | } 197 | 198 | assert.EqualValues(t, expectedRbacSubject, l.rbacSubjectsByScope["joe"]) 199 | assert.EqualValues(t, expectedRbacSubject, l.rbacSubjectsByScope["sue"]) 200 | assert.EqualValues(t, expectedRbacSubjectSA, l.rbacSubjectsByScope["circleci:circleci"]) 201 | } 202 | 203 | func TestLoadGke(t *testing.T) { 204 | policy := &cloudresourcemanager.Policy{ 205 | Bindings: []*cloudresourcemanager.Binding{{ 206 | Role: "roles/container.admin", 207 | Members: []string{"user:jane@example.com", "user:joe@example.com"}, 208 | }, { 209 | Role: "roles/container.developer", 210 | Members: []string{"serviceAccount:ci@example.iam.gserviceaccount.com"}, 211 | }, { 212 | Role: "roles/viewer", 213 | Members: []string{"group:devs@example.com"}, 214 | }, { 215 | Role: "roles/owner", 216 | Members: []string{"user:jane@example.com"}, 217 | }}, 218 | } 219 | 220 | l := genLister() 221 | 222 | assert.Len(t, l.rbacSubjectsByScope, 0, "Expected no rbac subjects initially") 223 | 224 | l.loadGkeIamPolicy(policy) 225 | 226 | assert.Len(t, l.rbacSubjectsByScope, 4, "Expected 4 rbac subjects") 227 | 228 | assert.EqualValues(t, l.rbacSubjectsByScope["jane@example.com"], rbacSubject{ 229 | Kind: "User", 230 | RolesByScope: map[string][]simpleRole{ 231 | "project-wide": {{ 232 | Kind: "IAM", 233 | Name: "gke-admin", 234 | Source: simpleRoleSource{ 235 | Kind: "IAMRole", 236 | Name: "container.admin", 237 | }, 238 | }, { 239 | Kind: "IAM", 240 | Name: "gcp-owner", 241 | Source: simpleRoleSource{ 242 | Kind: "IAMRole", 243 | Name: "owner", 244 | }, 245 | }}, 246 | }, 247 | }) 248 | 249 | assert.EqualValues(t, l.rbacSubjectsByScope["joe@example.com"], rbacSubject{ 250 | Kind: "User", 251 | RolesByScope: map[string][]simpleRole{ 252 | "project-wide": {{ 253 | Kind: "IAM", 254 | Name: "gke-admin", 255 | Source: simpleRoleSource{ 256 | Kind: "IAMRole", 257 | Name: "container.admin", 258 | }, 259 | }}, 260 | }, 261 | }) 262 | 263 | assert.EqualValues(t, l.rbacSubjectsByScope["devs@example.com"], rbacSubject{ 264 | Kind: "Group", 265 | RolesByScope: map[string][]simpleRole{ 266 | "project-wide": {{ 267 | Kind: "IAM", 268 | Name: "gcp-viewer", 269 | Source: simpleRoleSource{ 270 | Kind: "IAMRole", 271 | Name: "viewer", 272 | }, 273 | }}, 274 | }, 275 | }) 276 | 277 | assert.EqualValues(t, l.rbacSubjectsByScope["ci@example.iam.gserviceaccount.com"], rbacSubject{ 278 | Kind: "ServiceAccount", 279 | RolesByScope: map[string][]simpleRole{ 280 | "project-wide": {{ 281 | Kind: "IAM", 282 | Name: "gke-developer", 283 | Source: simpleRoleSource{ 284 | Kind: "IAMRole", 285 | Name: "container.developer", 286 | }, 287 | }}, 288 | }, 289 | }) 290 | } 291 | 292 | func TestLoadGkeFilters(t *testing.T) { 293 | policy := &cloudresourcemanager.Policy{ 294 | Bindings: []*cloudresourcemanager.Binding{{ 295 | Role: "roles/container.admin", 296 | Members: []string{"user:jane@example.com", "user:joe@example.com"}, 297 | }, { 298 | Role: "roles/container.developer", 299 | Members: []string{"serviceAccount:ci@example.iam.gserviceaccount.com"}, 300 | }, { 301 | Role: "roles/viewer", 302 | Members: []string{"group:devs@example.com"}, 303 | }, { 304 | Role: "roles/owner", 305 | Members: []string{"user:jane@example.com"}, 306 | }}, 307 | } 308 | 309 | l := genLister() 310 | l.filter = "example" 311 | l.subjectKind = "user" 312 | 313 | assert.Len(t, l.rbacSubjectsByScope, 0, "Expected no rbac subjects initially") 314 | 315 | l.loadGkeIamPolicy(policy) 316 | 317 | assert.Len(t, l.rbacSubjectsByScope, 2, "Expected 2 rbac subjects") 318 | 319 | assert.EqualValues(t, rbacSubject{ 320 | Kind: "User", 321 | RolesByScope: map[string][]simpleRole{ 322 | "project-wide": {{ 323 | Kind: "IAM", 324 | Name: "gke-admin", 325 | Source: simpleRoleSource{ 326 | Kind: "IAMRole", 327 | Name: "container.admin", 328 | }, 329 | }, { 330 | Kind: "IAM", 331 | Name: "gcp-owner", 332 | Source: simpleRoleSource{ 333 | Kind: "IAMRole", 334 | Name: "owner", 335 | }, 336 | }}, 337 | }, 338 | }, l.rbacSubjectsByScope["jane@example.com"]) 339 | 340 | assert.EqualValues(t, rbacSubject{ 341 | Kind: "User", 342 | RolesByScope: map[string][]simpleRole{ 343 | "project-wide": {{ 344 | Kind: "IAM", 345 | Name: "gke-admin", 346 | Source: simpleRoleSource{ 347 | Kind: "IAMRole", 348 | Name: "container.admin", 349 | }, 350 | }}, 351 | }, 352 | }, l.rbacSubjectsByScope["joe@example.com"]) 353 | } 354 | 355 | func genLister() lister { 356 | return lister{ 357 | clientset: testclient.NewSimpleClientset(), 358 | rbacSubjectsByScope: make(map[string]rbacSubject), 359 | } 360 | } 361 | 362 | func loadAll(t *testing.T, l lister) { 363 | err := l.loadAll() 364 | 365 | assert.Nil(t, err, "Expected no error loading all rbac Bindings") 366 | } 367 | 368 | func loadRoleBindings(t *testing.T, l lister) { 369 | err := l.loadRoleBindings() 370 | 371 | assert.Nil(t, err, "Expected no error loading role bindings") 372 | } 373 | 374 | func createRoleBindings(t *testing.T, l lister) { 375 | roleBindings := []rbacv1.RoleBinding{ 376 | { 377 | ObjectMeta: metav1.ObjectMeta{ 378 | Name: "testing", 379 | Namespace: "foo", 380 | }, 381 | Subjects: []rbacv1.Subject{{ 382 | Name: "joe", 383 | Kind: "User", 384 | }, { 385 | Name: "sue", 386 | Kind: "User", 387 | }}, 388 | RoleRef: rbacv1.RoleRef{ 389 | Kind: "Role", 390 | Name: "bar", 391 | }, 392 | }, 393 | { 394 | ObjectMeta: metav1.ObjectMeta{ 395 | Name: "testing-sa", 396 | Namespace: "two", 397 | }, 398 | Subjects: []rbacv1.Subject{{ 399 | Name: "circleci", 400 | Kind: "ServiceAccount", 401 | Namespace: "circleci", 402 | }}, 403 | RoleRef: rbacv1.RoleRef{ 404 | Kind: "ClusterRole", 405 | Name: "cluster-admin", 406 | }, 407 | }, 408 | { 409 | ObjectMeta: metav1.ObjectMeta{ 410 | Name: "testing-sa", 411 | Namespace: "three", 412 | }, 413 | Subjects: []rbacv1.Subject{{ 414 | Name: "circleci", 415 | Kind: "ServiceAccount", 416 | Namespace: "circleci", 417 | }}, 418 | RoleRef: rbacv1.RoleRef{ 419 | Kind: "ClusterRole", 420 | Name: "cluster-admin", 421 | }, 422 | }, 423 | } 424 | 425 | for _, roleBinding := range roleBindings { 426 | _, err := l.clientset.RbacV1().RoleBindings(roleBinding.Namespace).Create(context.Background(), &roleBinding, metav1.CreateOptions{}) 427 | assert.Nil(t, err, "Expected no error creating role bindings") 428 | } 429 | } 430 | 431 | func loadClusterRoleBindings(t *testing.T, l lister) { 432 | err := l.loadClusterRoleBindings() 433 | 434 | assert.Nil(t, err, "Expected no error loading cluster role bindings") 435 | } 436 | 437 | func createClusterRoleBindings(t *testing.T, l lister) { 438 | clusterRoleBindings := []rbacv1.ClusterRoleBinding{ 439 | { 440 | ObjectMeta: metav1.ObjectMeta{ 441 | Name: "testing", 442 | }, 443 | Subjects: []rbacv1.Subject{{ 444 | Name: "joe", 445 | Kind: "User", 446 | }, { 447 | Name: "sue", 448 | Kind: "User", 449 | }}, 450 | RoleRef: rbacv1.RoleRef{ 451 | Kind: "ClusterRole", 452 | Name: "bar", 453 | }, 454 | }, 455 | { 456 | ObjectMeta: metav1.ObjectMeta{ 457 | Name: "circleci-cluster-admin", 458 | }, 459 | Subjects: []rbacv1.Subject{{ 460 | Name: "circleci", 461 | Kind: "ServiceAccount", 462 | Namespace: "circleci", 463 | }}, 464 | RoleRef: rbacv1.RoleRef{ 465 | Kind: "ClusterRole", 466 | Name: "cluster-admin", 467 | }, 468 | }, 469 | } 470 | 471 | for _, clusterRoleBinding := range clusterRoleBindings { 472 | _, err := l.clientset.RbacV1().ClusterRoleBindings().Create(context.Background(), &clusterRoleBinding, metav1.CreateOptions{}) 473 | assert.Nil(t, err, "Expected no error creating cluster role bindings") 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= 3 | cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= 4 | cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 5 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 8 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 9 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 10 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 11 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 16 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 17 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 18 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 19 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 20 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 21 | github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= 22 | github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 23 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 24 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 25 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 26 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 27 | github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= 28 | github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= 29 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 30 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 31 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 32 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 33 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 34 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 35 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 36 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 37 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 38 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 39 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 40 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 41 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 42 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 46 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 47 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 48 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 49 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 50 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 51 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 52 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 53 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 54 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 55 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 56 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 57 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 58 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 59 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 60 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 61 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 62 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 63 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 64 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 65 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 66 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 67 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 68 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 69 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 70 | github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= 71 | github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= 72 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 73 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= 74 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 75 | github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= 76 | github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= 77 | github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= 78 | github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= 79 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 80 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 81 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 82 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 83 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 84 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 85 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 86 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 87 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 88 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 89 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 90 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 91 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 92 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 93 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 94 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 95 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 96 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 97 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 98 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 99 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 100 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 101 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 102 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 103 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 104 | github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= 105 | github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= 106 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 107 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 108 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 109 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 110 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 111 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 112 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 113 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 114 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 115 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 116 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 117 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 118 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 119 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 120 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 121 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 122 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 123 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 124 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 125 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 126 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 127 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 128 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 129 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 130 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 131 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 132 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 133 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 134 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 135 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 136 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 137 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 138 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 139 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 140 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 141 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 142 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 143 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 144 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 145 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 146 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 147 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 148 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 149 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 150 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 151 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 152 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 153 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 154 | golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= 155 | golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= 156 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 157 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 158 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 160 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 162 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 163 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 164 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 167 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= 169 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 170 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 171 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 172 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 173 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 174 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 175 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 176 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 177 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 178 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 179 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 180 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 181 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 182 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 183 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 184 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 185 | golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= 186 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 187 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 188 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 189 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 190 | google.golang.org/api v0.138.0 h1:K/tVp05MxNVbHShRw9m7e9VJGdagNeTdMzqPH7AUqr0= 191 | google.golang.org/api v0.138.0/go.mod h1:4xyob8CxC+0GChNBvEUAk8VBKNvYOTWM9T3v3UfRxuY= 192 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 193 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 194 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 195 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 196 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 197 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 198 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 199 | google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= 200 | google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= 201 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= 202 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= 203 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 204 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 205 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 206 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 207 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 208 | google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= 209 | google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= 210 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 211 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 212 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 213 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 214 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 215 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 216 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 217 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 218 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 219 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 220 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 221 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 222 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 223 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 224 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 225 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 226 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 227 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 228 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 229 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 230 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 231 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 232 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 233 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 234 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 235 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 236 | k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= 237 | k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= 238 | k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= 239 | k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= 240 | k8s.io/client-go v0.28.1 h1:pRhMzB8HyLfVwpngWKE8hDcXRqifh1ga2Z/PU9SXVK8= 241 | k8s.io/client-go v0.28.1/go.mod h1:pEZA3FqOsVkCc07pFVzK076R+P/eXqsgx5zuuRWukNE= 242 | k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= 243 | k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 244 | k8s.io/kube-openapi v0.0.0-20230901164831-6c774f458599 h1:nVKRi5eItf3x9kkIMfdT4D1/LqPzj0bLjxLYWbdUtV0= 245 | k8s.io/kube-openapi v0.0.0-20230901164831-6c774f458599/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= 246 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= 247 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 248 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 249 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 250 | sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= 251 | sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 252 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 253 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 254 | --------------------------------------------------------------------------------