├── .circleci └── config.yml ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yaml │ ├── feature_request.md │ └── other.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ └── stale.yml ├── .gitignore ├── .goreleaser.yml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── DESIGN.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── ROADMAP.md ├── cmd └── manager │ └── main.go ├── deploy ├── 0_namespace.yaml ├── 1_rbac.yaml ├── 2_crd.yaml └── 3_deployment.yaml ├── docs ├── .vuepress │ ├── config-extras.js │ ├── config.js │ ├── public │ │ ├── favicon.png │ │ ├── img │ │ │ ├── fairwinds-logo.svg │ │ │ └── rbac-manager-logo.svg │ │ └── scripts │ │ │ ├── marketing.js │ │ │ └── modify.js │ ├── styles │ │ ├── index.styl │ │ └── palette.styl │ └── theme │ │ ├── index.js │ │ └── layouts │ │ └── Layout.vue ├── _config.yml ├── aks.md ├── aws.md ├── contributing │ ├── code-of-conduct.md │ └── guide.md ├── gke.md ├── introduction.md ├── package-lock.json ├── package.json ├── rbacdefinitions.md └── upgrades.md ├── e2e ├── chainsaw │ ├── .chainsaw.yaml │ ├── cluster-role-bindings │ │ ├── chainsaw-test.yaml │ │ ├── expected.yaml │ │ └── resources.yaml │ ├── deleted │ │ ├── chainsaw-test.yaml │ │ ├── expected.yaml │ │ └── resources.yaml │ └── service-accounts │ │ ├── chainsaw-test.yaml │ │ ├── expected.yaml │ │ └── resources.yaml ├── pre.sh └── test.sh ├── examples ├── rbacdefinition-everything.yaml ├── rbacdefinition-sa-imagepull.yaml └── rbacdefinition-users.yaml ├── fairwinds-insights.yaml ├── go.mod ├── go.sum ├── img └── rbac-manager-logo.svg ├── pkg ├── apis │ ├── addtoscheme_rbacmanager_v1beta1.go │ ├── apis.go │ └── rbacmanager │ │ ├── group.go │ │ └── v1beta1 │ │ ├── doc.go │ │ ├── rbacdefinition_types.go │ │ ├── register.go │ │ └── zz_generated.deepcopy.go ├── controller │ ├── namespace.go │ ├── rbacdefinition.go │ └── shared.go ├── kube │ ├── kube.go │ └── rbacdefinitions.go ├── metrics │ ├── metrics.go │ └── metrics_test.go ├── reconciler │ ├── cases_test.go │ ├── matcher.go │ ├── matcher_test.go │ ├── parser.go │ ├── parser_test.go │ ├── reconciler.go │ └── reconciler_test.go └── watcher │ ├── clusterrolebinding.go │ ├── rolebinding.go │ ├── serviceaccount.go │ └── shared.go └── version └── version.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | rok8s: fairwinds/rok8s-scripts@14 5 | oss-docs: fairwinds/oss-docs@0 6 | 7 | references: 8 | install_vault: &install_vault 9 | run: 10 | name: install hashicorp vault 11 | command: | 12 | apk --update add curl yq 13 | cd /tmp 14 | curl -LO https://releases.hashicorp.com/vault/1.13.2/vault_1.13.2_linux_amd64.zip 15 | sha256sum vault_1.13.2_linux_amd64.zip | grep f7930279de8381de7c532164b4a4408895d9606c0d24e2e9d2f9acb5dfe99b3c 16 | unzip vault_1.13.2_linux_amd64.zip 17 | mv vault /usr/bin/vault 18 | 19 | chainsaw_configuration: &chainsaw_configuration 20 | pre_script: e2e/pre.sh 21 | script: e2e/test.sh 22 | command_runner_image: quay.io/reactiveops/ci-images:v14-bullseye 23 | enable_docker_layer_caching: true 24 | attach-workspace: true 25 | requires: 26 | - test 27 | - snapshot 28 | filters: 29 | branches: 30 | only: /.*/ 31 | tags: 32 | ignore: /v.*/ 33 | 34 | jobs: 35 | test: 36 | docker: 37 | - image: cimg/go:1.22 38 | working_directory: /home/circleci/go/src/github.com/fairwindsops/rbac-manager 39 | steps: 40 | - checkout 41 | - run: go test ./... -coverprofile=coverage.txt -covermode=atomic 42 | 43 | lint: 44 | docker: 45 | - image: golangci/golangci-lint:v1.59.1 46 | steps: 47 | - checkout 48 | - run: golangci-lint run --timeout=10m -v 49 | 50 | build_and_release: 51 | working_directory: /home/circleci/go/src/github.com/fairwindsops/rbac-manager 52 | resource_class: large 53 | shell: /bin/bash 54 | docker: 55 | - image: goreleaser/goreleaser:v2.0.1 56 | environment: 57 | GO111MODULE: "on" 58 | steps: 59 | - checkout 60 | - setup_remote_docker 61 | - *install_vault 62 | - rok8s/get_vault_env: 63 | vault_path: repo/global/env 64 | - rok8s/docker_login: 65 | registry: "quay.io" 66 | username: $REACTIVEOPS_QUAY_USER 67 | password-variable: REACTIVEOPS_QUAY_TOKEN 68 | - run: goreleaser 69 | snapshot: 70 | working_directory: /home/circleci/go/src/github.com/fairwindsops/rbac-manager 71 | resource_class: large 72 | docker: 73 | - image: goreleaser/goreleaser:v2.0.1 74 | steps: 75 | - checkout 76 | - setup_remote_docker 77 | - run: goreleaser --snapshot --skip sign 78 | - run: mkdir -p /tmp/workspace/docker_save/ 79 | - run: docker save quay.io/reactiveops/rbac-manager:${CIRCLE_SHA1}-amd64 > /tmp/workspace/docker_save/rbac-manager_${CIRCLE_SHA1}-amd64.tar 80 | - run: pwd; ls -la * 81 | - persist_to_workspace: 82 | root: /tmp/workspace/ 83 | paths: 84 | - docker_save 85 | - store_artifacts: 86 | path: dist 87 | destination: snapshot 88 | 89 | workflows: 90 | version: 2 91 | tests: 92 | jobs: 93 | - test 94 | - lint 95 | - snapshot: 96 | context: org-global 97 | requires: 98 | - test 99 | - lint 100 | - rok8s/kubernetes_e2e_tests: 101 | name: "End-To-End Kubernetes 1.28" 102 | kind_node_image: "kindest/node:v1.28.9@sha256:dca54bc6a6079dd34699d53d7d4ffa2e853e46a20cd12d619a09207e35300bd0" 103 | <<: *chainsaw_configuration 104 | - rok8s/kubernetes_e2e_tests: 105 | name: "End-To-End Kubernetes 1.29" 106 | kind_node_image: "kindest/node:v1.29.4@sha256:3abb816a5b1061fb15c6e9e60856ec40d56b7b52bcea5f5f1350bc6e2320b6f8" 107 | <<: *chainsaw_configuration 108 | - rok8s/kubernetes_e2e_tests: 109 | name: "End-To-End Kubernetes 1.30" 110 | kind_node_image: "kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e" 111 | <<: *chainsaw_configuration 112 | release: 113 | jobs: 114 | - build_and_release: 115 | filters: 116 | branches: 117 | ignore: /.*/ 118 | tags: 119 | only: /v.*/ 120 | - oss-docs/publish-docs: 121 | repository: rbac-manager 122 | filters: 123 | branches: 124 | ignore: /.*/ 125 | tags: 126 | only: /^.*/ 127 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | examples 2 | deploy 3 | .vscode 4 | .dockerignore 5 | Dockerfile 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pre-commit-config.yaml 2 | /rbac-manager 3 | docs/README.md 4 | 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | bin 12 | 13 | # Test binary, build with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Kubernetes Generated files - skip generated files, except for vendored files 20 | 21 | !vendor/**/zz_generated.* 22 | 23 | # editor and IDE paraphernalia 24 | .idea 25 | *.swp 26 | *.swo 27 | *~ 28 | 29 | # custom additions 30 | tmp 31 | .vscode 32 | 33 | coverage.txt 34 | cover-report.html 35 | node_modules 36 | /dist 37 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | release: 2 | prerelease: auto 3 | footer: | 4 | You can verify the signatures of both the checksums.txt file and the published docker images using [cosign](https://github.com/sigstore/cosign). 5 | 6 | ``` 7 | cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub 8 | ``` 9 | builds: 10 | - main: ./cmd/manager 11 | ldflags: 12 | - -X github.com/fairwindsops/rbac-manager/version.Version={{.Version}} -s -w 13 | goarch: 14 | - amd64 15 | - arm 16 | - arm64 17 | env: 18 | - CGO_ENABLED=0 19 | goos: 20 | - linux 21 | - darwin 22 | goarm: 23 | - 6 24 | - 7 25 | signs: 26 | - cmd: cosign 27 | args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}", "--yes"] 28 | artifacts: checksum 29 | dockers: 30 | - image_templates: 31 | - "quay.io/reactiveops/rbac-manager:{{ .FullCommit }}-amd64" 32 | - "quay.io/reactiveops/rbac-manager:{{ .Tag }}-amd64" 33 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}-amd64" 34 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}.{{ .Minor }}-amd64" 35 | use: buildx 36 | dockerfile: Dockerfile 37 | build_flag_templates: 38 | - "--platform=linux/amd64" 39 | - image_templates: 40 | - "quay.io/reactiveops/rbac-manager:{{ .Tag }}-arm64v8" 41 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}-arm64v8" 42 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}.{{ .Minor }}-arm64v8" 43 | use: buildx 44 | goarch: arm64 45 | dockerfile: Dockerfile 46 | build_flag_templates: 47 | - "--platform=linux/arm64/v8" 48 | - image_templates: 49 | - "quay.io/reactiveops/rbac-manager:{{ .Tag }}-armv7" 50 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}-armv7" 51 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}.{{ .Minor }}-armv7" 52 | use: buildx 53 | goarch: arm64 54 | dockerfile: Dockerfile 55 | build_flag_templates: 56 | - "--platform=linux/arm/v7" 57 | docker_manifests: 58 | - name_template: quay.io/reactiveops/rbac-manager:{{ .Tag }} 59 | image_templates: 60 | - "quay.io/reactiveops/rbac-manager:{{ .Tag }}-amd64" 61 | - "quay.io/reactiveops/rbac-manager:{{ .Tag }}-arm64v8" 62 | - "quay.io/reactiveops/rbac-manager:{{ .Tag }}-armv7" 63 | - name_template: quay.io/reactiveops/rbac-manager:v{{ .Major }} 64 | image_templates: 65 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}-amd64" 66 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}-arm64v8" 67 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}-armv7" 68 | - name_template: quay.io/reactiveops/rbac-manager:v{{ .Major }}.{{ .Minor }} 69 | image_templates: 70 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}.{{ .Minor }}-amd64" 71 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}.{{ .Minor }}-arm64v8" 72 | - "quay.io/reactiveops/rbac-manager:v{{ .Major }}.{{ .Minor }}-armv7" 73 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ## DO NOT EDIT - Managed by Terraform 2 | * @sudermanjr @ryanisfluffy 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # Design and Architecture Notes 2 | 3 | ## cmd/manager/main.go 4 | 5 | This is the primary entrypoint 6 | 7 | ## pkg/watcher 8 | 9 | This package watches all resources that rbac-manager "owns" in order to trigger reconciliation if an outside actor modifies or deletes one of them 10 | 11 | ## pkg/reconciler/parser.go 12 | 13 | Here the rbacDefinition is parsed into ServiceAccounts, ClusterRoleBindings, and RoleBindings 14 | 15 | ## pkg/controller 16 | 17 | This package contains the watchers of Namesapces and RbacDefinitions, which are the primary things that can be used to trigger rbac-manager actions. 18 | 19 | ## pkg/reconciler/reconciler.go 20 | 21 | This contains the functions that reconcile Namespaces, ServiceAccounts, ClusterRoleBindings, RoleBindings, and OnwerReferences 22 | 23 | ## pkg/apis 24 | 25 | This contains the types necessary to define the RbacDefinition. 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | 3 | # 'nobody' user in alpine 4 | USER 65534 5 | COPY rbac-manager / 6 | 7 | ENTRYPOINT ["/rbac-manager"] 8 | CMD ["--log-level=info"] 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | GOTEST=$(GOCMD) test 6 | BINARY_NAME=rbac-manager 7 | COMMIT := $(shell git rev-parse HEAD) 8 | VERSION := "dev" 9 | 10 | all: test 11 | test: 12 | @printf "Linter:\n" 13 | golangci-lint run 14 | @printf "\n\nTests:\n\n" 15 | $(GOCMD) test -v -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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | RBAC Manager 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | RBAC Manager is designed to simplify authorization in Kubernetes. This is an operator that supports declarative configuration for RBAC with new custom resources. Instead of managing role bindings or service accounts directly, you can specify a desired state and RBAC Manager will make the necessary changes to achieve that state. 19 | 20 | # Documentation 21 | Check out the [documentation at docs.fairwinds.com](https://rbac-manager.docs.fairwinds.com/) 22 | 23 | 24 | ## Join the Fairwinds Open Source Community 25 | 26 | The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap, 27 | and network with fellow Kubernetes users. 28 | [Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-2na8gtwb4-DGQ4qgmQbczQyB2NlFlYQQ) 29 | or 30 | [join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved! 31 | 32 | 33 | Love Fairwinds Open Source? Automate Fairwinds Open Source for free with Fairwinds Insights. Click to learn more 35 | 36 | 37 | ## Other Projects from Fairwinds 38 | 39 | Enjoying rbac-manager? Check out some of our other projects: 40 | * [Polaris](https://github.com/FairwindsOps/Polaris) - Audit, enforce, and build policies for Kubernetes resources, including over 20 built-in checks for best practices 41 | * [Goldilocks](https://github.com/FairwindsOps/Goldilocks) - Right-size your Kubernetes Deployments by compare your memory and CPU settings against actual usage 42 | * [Pluto](https://github.com/FairwindsOps/Pluto) - Detect Kubernetes resources that have been deprecated or removed in future versions 43 | * [Nova](https://github.com/FairwindsOps/Nova) - Check to see if any of your Helm charts have updates available 44 | 45 | Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_source=rbac-manager&utm_medium=rbac-manager&utm_campaign=rbac-manager) 46 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## Q42020 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-manager/milestone/3) 6 | 7 | * Update CRD to apiextensions.k8s.io/v1 8 | 9 | -------------------------------------------------------------------------------- /cmd/manager/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "net/http" 22 | "os" 23 | 24 | "github.com/prometheus/client_golang/prometheus/promhttp" 25 | "github.com/sirupsen/logrus" 26 | _ "k8s.io/client-go/plugin/pkg/client/auth" 27 | "k8s.io/klog" 28 | "sigs.k8s.io/controller-runtime/pkg/client/config" 29 | "sigs.k8s.io/controller-runtime/pkg/manager" 30 | "sigs.k8s.io/controller-runtime/pkg/manager/signals" 31 | 32 | "github.com/fairwindsops/rbac-manager/pkg/apis" 33 | "github.com/fairwindsops/rbac-manager/pkg/controller" 34 | "github.com/fairwindsops/rbac-manager/pkg/metrics" 35 | "github.com/fairwindsops/rbac-manager/pkg/watcher" 36 | "github.com/fairwindsops/rbac-manager/version" 37 | ) 38 | 39 | var logLevel = flag.String("log-level", logrus.InfoLevel.String(), "Logrus log level") 40 | var addr = flag.String("metrics-address", ":8042", "The address to serve prometheus metrics.") 41 | 42 | func init() { 43 | klog.InitFlags(nil) 44 | } 45 | 46 | func main() { 47 | flag.Parse() 48 | 49 | parsedLevel, err := logrus.ParseLevel(*logLevel) 50 | if err != nil { 51 | // This should theoretically never happen 52 | logrus.Errorf("log-level flag has invalid value %s", *logLevel) 53 | } else { 54 | logrus.SetLevel(parsedLevel) 55 | } 56 | 57 | logrus.Info("----------------------------------") 58 | logrus.Infof("rbac-manager %v running", version.Version) 59 | logrus.Info("----------------------------------") 60 | 61 | // Get a config to talk to the apiserver 62 | logrus.Debug("Setting up client for manager") 63 | cfg, err := config.GetConfig() 64 | if err != nil { 65 | logrus.Error(err, ": unable to set up client config") 66 | os.Exit(1) 67 | } 68 | 69 | // Create a new Cmd to provide shared dependencies and start components 70 | logrus.Debug("Setting up manager") 71 | mgr, err := manager.New(cfg, manager.Options{}) 72 | if err != nil { 73 | logrus.Error(err, ": unable to set up overall controller manager") 74 | os.Exit(1) 75 | } 76 | 77 | logrus.Info("Registering components") 78 | 79 | // Setup Scheme for all resources 80 | logrus.Debug("Setting up scheme") 81 | if err := apis.AddToScheme(mgr.GetScheme()); err != nil { 82 | logrus.Error(err, ": unable add APIs to scheme") 83 | os.Exit(1) 84 | } 85 | 86 | // Setup all Controllers 87 | logrus.Debug("Setting up controller") 88 | if err := controller.Add(mgr); err != nil { 89 | logrus.Error(err, ": unable to register controller to the manager") 90 | os.Exit(1) 91 | } 92 | 93 | // Watch Related Resources 94 | logrus.Info("Watching resources related to RBAC Definitions") 95 | watcher.WatchRelatedResources() 96 | 97 | // Start metrics endpoint 98 | go func() { 99 | metrics.RegisterMetrics() 100 | http.Handle("/metrics", promhttp.Handler()) 101 | if err := http.ListenAndServe(*addr, nil); err != nil { 102 | logrus.Error(err, ": unable to serve the metrics endpoint") 103 | os.Exit(1) 104 | } 105 | }() 106 | 107 | // Start the Cmd 108 | logrus.Info("Watching RBAC Definitions") 109 | if err := mgr.Start(signals.SetupSignalHandler()); err != nil { 110 | logrus.Error(err, ": unable to run the manager") 111 | os.Exit(1) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /deploy/0_namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: rbac-manager 6 | labels: 7 | app: rbac-manager 8 | -------------------------------------------------------------------------------- /deploy/1_rbac.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: rbac-manager 6 | namespace: rbac-manager 7 | labels: 8 | app: rbac-manager 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | name: rbac-manager 14 | labels: 15 | app: rbac-manager 16 | rules: 17 | - apiGroups: 18 | - rbacmanager.reactiveops.io 19 | resources: 20 | - rbacdefinitions 21 | verbs: 22 | - get 23 | - list 24 | - watch 25 | - apiGroups: 26 | - rbac.authorization.k8s.io 27 | - authorization.k8s.io 28 | resources: 29 | - '*' 30 | verbs: 31 | - '*' 32 | - apiGroups: 33 | - "" # core 34 | resources: 35 | - serviceaccounts 36 | verbs: 37 | - '*' 38 | - apiGroups: 39 | - "" # core 40 | resources: 41 | - namespaces 42 | verbs: 43 | - get 44 | - list 45 | - watch 46 | --- 47 | apiVersion: rbac.authorization.k8s.io/v1 48 | kind: ClusterRoleBinding 49 | metadata: 50 | name: rbac-manager 51 | labels: 52 | app: rbac-manager 53 | roleRef: 54 | apiGroup: rbac.authorization.k8s.io 55 | kind: ClusterRole 56 | name: rbac-manager 57 | subjects: 58 | - kind: ServiceAccount 59 | name: rbac-manager 60 | namespace: "rbac-manager" 61 | -------------------------------------------------------------------------------- /deploy/2_crd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | labels: 6 | app: rbac-manager 7 | name: rbacdefinitions.rbacmanager.reactiveops.io 8 | spec: 9 | group: rbacmanager.reactiveops.io 10 | names: 11 | kind: RBACDefinition 12 | plural: rbacdefinitions 13 | singular: rbacdefinition 14 | shortNames: 15 | - rbd 16 | - rbacdef 17 | scope: Cluster 18 | versions: 19 | - name: v1beta1 20 | served: true 21 | storage: true 22 | schema: 23 | openAPIV3Schema: 24 | required: 25 | - rbacBindings 26 | type: object 27 | properties: 28 | rbacBindings: 29 | items: 30 | properties: 31 | clusterRoleBindings: 32 | items: 33 | properties: 34 | clusterRole: 35 | type: string 36 | required: 37 | - clusterRole 38 | type: object 39 | type: array 40 | name: 41 | type: string 42 | roleBindings: 43 | items: 44 | properties: 45 | clusterRole: 46 | type: string 47 | namespace: 48 | type: string 49 | namespaceSelector: 50 | type: object 51 | properties: 52 | matchLabels: 53 | type: object 54 | additionalProperties: 55 | type: string 56 | matchExpressions: 57 | type: array 58 | items: 59 | type: object 60 | properties: 61 | key: 62 | type: string 63 | operator: 64 | type: 65 | string 66 | enum: 67 | - Exists 68 | - DoesNotExist 69 | - In 70 | - NotIn 71 | values: 72 | type: array 73 | items: 74 | type: string 75 | required: 76 | - key 77 | - operator 78 | role: 79 | type: string 80 | type: object 81 | type: array 82 | subjects: 83 | items: 84 | type: object 85 | properties: 86 | automountServiceAccountToken: 87 | type: boolean 88 | imagePullSecrets: 89 | type: array 90 | items: 91 | type: string 92 | kind: 93 | type: string 94 | enum: 95 | - Group 96 | - ServiceAccount 97 | - User 98 | name: 99 | type: string 100 | namespace: 101 | type: string 102 | required: 103 | - name 104 | - kind 105 | type: array 106 | required: 107 | - name 108 | - subjects 109 | type: object 110 | type: array 111 | status: 112 | type: object 113 | -------------------------------------------------------------------------------- /deploy/3_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: rbac-manager 5 | namespace: rbac-manager 6 | labels: 7 | app: rbac-manager 8 | annotations: 9 | polaris.fairwinds.com/deploymentMissingReplicas-exempt: 'true' 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: rbac-manager 15 | release: rbac-manager 16 | template: 17 | metadata: 18 | labels: 19 | app: rbac-manager 20 | release: rbac-manager 21 | spec: 22 | serviceAccountName: rbac-manager 23 | containers: 24 | - name: rbac-manager 25 | image: "quay.io/reactiveops/rbac-manager:v1" 26 | imagePullPolicy: Always 27 | # these liveness probes use the metrics endpoint 28 | readinessProbe: 29 | httpGet: 30 | scheme: HTTP 31 | path: /metrics 32 | port: 8042 33 | initialDelaySeconds: 5 34 | timeoutSeconds: 3 35 | periodSeconds: 3 36 | failureThreshold: 3 37 | livenessProbe: 38 | httpGet: 39 | scheme: HTTP 40 | path: /metrics 41 | port: 8042 42 | initialDelaySeconds: 5 43 | timeoutSeconds: 3 44 | periodSeconds: 10 45 | failureThreshold: 3 46 | securityContext: 47 | allowPrivilegeEscalation: false 48 | privileged: false 49 | readOnlyRootFilesystem: true 50 | runAsNonRoot: true 51 | capabilities: 52 | drop: 53 | - ALL 54 | resources: 55 | limits: 56 | cpu: 100m 57 | memory: 128Mi 58 | requests: 59 | cpu: 100m 60 | memory: 128Mi 61 | ports: 62 | # metrics port 63 | - name: http-metrics 64 | containerPort: 8042 65 | protocol: TCP 66 | -------------------------------------------------------------------------------- /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 Manager Documentation", 6 | description: "Documentation for Fairwinds' Rbac Manager", 7 | themeConfig: { 8 | docsRepo: "FairwindsOps/rbac-manager", 9 | sidebar: [ 10 | { 11 | title: "Rbac Manager", 12 | path: "/", 13 | sidebarDepth: 0, 14 | }, 15 | { 16 | title: "Introduction", 17 | path: "/introduction", 18 | }, 19 | { 20 | title: "RBACDefinitions", 21 | path: "/rbacdefinitions" 22 | }, 23 | { 24 | title: "Upgrades", 25 | path: "/upgrades", 26 | }, 27 | { 28 | title: "Cloud Provider Auth", 29 | children: [ 30 | { 31 | title: "AWS", 32 | path: "/aws" 33 | }, 34 | { 35 | title: "AKS", 36 | path: "/aks" 37 | }, 38 | { 39 | title: "GKE", 40 | path: "/gke", 41 | } 42 | ] 43 | }, 44 | { 45 | title: "Contributing", 46 | children: [ 47 | { 48 | title: "Guide", 49 | path: "contributing/guide" 50 | }, 51 | { 52 | title: "Code of Conduct", 53 | path: "contributing/code-of-conduct" 54 | } 55 | ] 56 | } 57 | ] 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FairwindsOps/rbac-manager/a075baed876f991787e7b966c2faf7d51d2596d6/docs/.vuepress/public/favicon.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/.vuepress/public/img/rbac-manager-logo.svg: -------------------------------------------------------------------------------- 1 | ../../../../img/rbac-manager-logo.svg -------------------------------------------------------------------------------- /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/.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 | -------------------------------------------------------------------------------- /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/.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 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extend: '@vuepress/theme-default' 3 | } 4 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 46 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/aks.md: -------------------------------------------------------------------------------- 1 | # Authentication With Azure Kubernetes Service 2 | Azure Kubernetes Service (AKS) enables mapping Azure Active Directory (AAD) users and groups to RBAC users and groups. Although not enabled by default, this post shows [how to enable AAD mapping to RBAC](https://docs.microsoft.com/en-us/azure/aks/aad-integration). 3 | 4 | ## A Simple RBAC Definition for AKS 5 | With AAD integration enabled, groups and users are mapped directly to RBAC. Users are mapped using their user name (generally email) while groups are mapped using the group object ID. 6 | 7 | ```yaml 8 | apiVersion: rbacmanager.reactiveops.io/v1beta1 9 | kind: RBACDefinition 10 | metadata: 11 | name: sample-config 12 | rbacBindings: 13 | - name: web-developers 14 | subjects: 15 | - kind: User 16 | name: jane@example.com 17 | - kind: User 18 | name: joe@example.com 19 | roleBindings: 20 | - clusterRole: edit 21 | namespace: web 22 | - name: sample-group 23 | subjects: 24 | - kind: Group 25 | name: 894656e1-39f8-4bfe-b16a-510f61af6f41 26 | roleBindings: 27 | - clusterRole: edit 28 | namespace: api 29 | ``` 30 | 31 | Because group IDs are not easy to understand in a Kubernetes context, we generally recommend sticking with AAD users and using RBAC Definitions to group them. This ensures that all authorization config for Kubernetes stays within Kubernetes. -------------------------------------------------------------------------------- /docs/aws.md: -------------------------------------------------------------------------------- 1 | # Authentication With AWS 2 | On AWS, we recommend using [aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator) for Kubernetes authentication. With EKS, this is included by default, and it's fairly straightforward to [setup with Kops](https://github.com/kubernetes-sigs/aws-iam-authenticator#kops-usage) or other methods of cluster provisioning. This library provides a variety of ways of mapping IAM roles and users to Kubernetes groups and users. 3 | 4 | In the examples below, we'll show how aws-iam-authenticator configuration can work with rbac-manager. In all cases, the aws-iam-authenticator configuration snippets will represent part of the Kubernetes ConfigMap it reads config from. With EKS, the ConfigMap is named `aws-auth`, though other deployment patterns may use different naming. 5 | 6 | ``` 7 | kubectl get configmap -n kube-system aws-auth -oyaml 8 | ``` 9 | 10 | More [information about configuring aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator#full-configuration-format) is available in the official readme. 11 | 12 | ## Mapping Roles to Groups 13 | One of the most common uses of aws-iam-authenticator involves mapping the AWS IAM Roles to Kubernetes Groups. The example aws-iam-authenticator configuration below uses a bit of a shortcut to use a group that already is bound to a cluster-admin role (`system:masters`). 14 | 15 | ```yaml 16 | mapRoles: 17 | - roleARN: arn:aws:iam::000000000000:role/KubernetesAdmin 18 | username: kubernetes-admin 19 | groups: 20 | - system:masters 21 | ``` 22 | 23 | Although this works, it's rather inelegant and doesn't allow you to modify the access this group has with RBAC without also affecting the system:masters group. A better alternative would be to use aws-iam-authenticator to map this to a new Kubernetes group that you can attach specific RBAC bindings to: 24 | 25 | ```yaml 26 | mapRoles: 27 | - roleARN: arn:aws:iam::000000000000:role/KubernetesAdmin 28 | username: kubernetes-admin 29 | groups: 30 | - kubernetes-admins 31 | ``` 32 | 33 | With that group, you could create RBAC Bindings with RBAC Manager that would allow you to specify access specifically for that group. 34 | 35 | ```yaml 36 | apiVersion: rbacmanager.reactiveops.io/v1beta1 37 | kind: RBACDefinition 38 | metadata: 39 | name: kubernetes-admins 40 | rbacBindings: 41 | - name: kubernetes-admins 42 | subjects: 43 | - kind: Group 44 | name: kubernetes-admins 45 | clusterRoleBindings: 46 | - clusterRole: cluster-admin 47 | ``` 48 | 49 | A downside to this approach is that authorization configuration ends up getting split between AWS IAM Roles assigned to users and RBAC bindings. To understand what a user has access to in Kubernetes, you first have to determine what IAM roles they can assume, then what Kubernetes groups those roles map to, then what roles are bound to those Kubernetes groups. It can get quite complex to understand the level of access granted to your cluster. 50 | 51 | ## Mapping Specific Users 52 | An alternative approach involves configuring aws-iam-authenticator to map specific IAM users to RBAC users. The configuration looks like this. 53 | 54 | ```yaml 55 | mapUsers: 56 | - userARN: arn:aws:iam::012345678901:user/Jane 57 | username: jane 58 | - userARN: arn:aws:iam::012345678901:user/Joe 59 | username: joe 60 | ``` 61 | 62 | With the above config you could specify RBAC Bindings with the following RBAC Definition: 63 | 64 | ```yaml 65 | apiVersion: rbacmanager.reactiveops.io/v1beta1 66 | kind: RBACDefinition 67 | metadata: 68 | name: sample-config 69 | rbacBindings: 70 | - name: web-developers 71 | subjects: 72 | - kind: User 73 | name: jane 74 | - kind: User 75 | name: joe 76 | roleBindings: 77 | - clusterRole: edit 78 | namespace: web 79 | ``` 80 | 81 | Although we no longer need to understand which AWS IAM users can assume specific IAM roles. To understand what a user has access to in Kubernetes, you still need to understand what IAM users have been mapped to which usernames, then what roles are bound to those Kubernetes users. Although this is simpler, it still requires understanding 2 different sets of config. 82 | 83 | ## Mapping All Users in an AWS Account Automatically 84 | If you like the above approach, but would prefer to just map all IAM users automatically to RBAC users, there's an [aws-iam-authenticator configuration](https://github.com/kubernetes-sigs/aws-iam-authenticator#full-configuration-format) option for that as well: 85 | 86 | ```yaml 87 | mapAccounts: 88 | - "012345678901" 89 | ``` 90 | 91 | With this approach, all IAM users are mapped to Kubernetes users with the full ARN as the username. By default, these users will be part of the `system:authenticated` group. That group is generally granted minimal permissions. With [rbac-lookup](http://github.com/FairwindsOps/rbac-lookup), you can view exactly what has been granted to that group with the following command: 92 | 93 | ``` 94 | rbac-lookup system:authenticated 95 | ``` 96 | 97 | To grant specific permissions to users with RBAC Manager we can use an RBAC Definition: 98 | 99 | ```yaml 100 | apiVersion: rbacmanager.reactiveops.io/v1beta1 101 | kind: RBACDefinition 102 | metadata: 103 | name: sample-config 104 | rbacBindings: 105 | - name: web-developers 106 | subjects: 107 | - kind: User 108 | name: arn:aws:iam::012345678901:user/Jane 109 | - kind: User 110 | name: arn:aws:iam::012345678901:user/Joe 111 | roleBindings: 112 | - clusterRole: edit 113 | namespace: web 114 | ``` 115 | 116 | By using `mapAccounts`, all authorization config lives within RBAC itself, allowing you to rely entirely on RBAC configuration for a clear picture of auth. -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /docs/contributing/guide.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Issues, whether bugs, tasks, or feature requests are essential for keeping rbac-manager 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). 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-manager). 11 | 12 | ## Project Structure 13 | 14 | rbac-manager is a relatively simple cobra cli tool that looks up information about rbac in a cluster. The [/cmd](https://github.com/FairwindsOps/rbac-manager/tree/master/cmd/manager) folder contains the flags and other cobra config. 15 | 16 | ## Getting Started 17 | 18 | We label issues with the ["good first issue" tag](https://github.com/FairwindsOps/rbac-manager/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 | ### Prerequisites 22 | * A properly configured Golang environment with Go 1.11 or higher 23 | * Access to a cluster via a properly configured KUBECONFIG 24 | 25 | ### Installation 26 | * Install the project with `go get github.com/fairwindsops/rbac-manager` 27 | * Change into the rbac-manager directory which is installed at `$GOPATH/src/github.com/fairwindsops/rbac-manager` 28 | * Run tests with `make test` 29 | 30 | ## Creating a New Issue 31 | 32 | If you've encountered an issue that is not already reported, please create an issue that contains the following: 33 | 34 | - Clear description of the issue 35 | - Steps to reproduce it 36 | - Appropriate labels 37 | 38 | ## Creating a Pull Request 39 | 40 | Each new pull request should: 41 | 42 | - Reference any related issues 43 | - Add tests that show the issues have been solved 44 | - Pass existing tests and linting 45 | - Contain a clear indication of if they're ready for review or a work in progress 46 | - Be up to date and/or rebased on the master branch 47 | 48 | ## Creating a new release 49 | 50 | Push a new annotated tag. This tag should contain a changelog of pertinent changes. Currently github releases are manual; go into the UI and create a release with the changelog as the text. In the future, it would be nice to implement Goreleaser. 51 | -------------------------------------------------------------------------------- /docs/gke.md: -------------------------------------------------------------------------------- 1 | # Authentication With Google Kubernetes Engine 2 | Google Kubernetes Engine (GKE) takes a unique approach to auth. Google IAM users are automatically mapped to Kubernetes RBAC users. Unfortunately there is no mapping for IAM groups to RBAC groups with GKE at this point. 3 | 4 | ## Initial RBAC Setup on GKE 5 | The first time you configure RBAC on a GKE cluster, you may need to first grant yourself RBAC access. That can be accomplished with a command like this: 6 | 7 | ``` 8 | kubectl create clusterrolebinding initial-cluster-admin \ 9 | --clusterrole=cluster-admin \ 10 | --user=$(gcloud config get-value account) 11 | ``` 12 | 13 | ## A Simple RBAC Definition for GKE 14 | Google IAM users are mapped to Kubernetes RBAC users with their email as the username. This is also the case for Google IAM Service accounts. That makes RBAC Bindings very straightforward: 15 | 16 | ```yaml 17 | apiVersion: rbacmanager.reactiveops.io/v1beta1 18 | kind: RBACDefinition 19 | metadata: 20 | name: sample-config 21 | rbacBindings: 22 | - name: web-developers 23 | subjects: 24 | - kind: User 25 | name: jane@example.com 26 | - kind: User 27 | name: joe@example.com 28 | roleBindings: 29 | - clusterRole: edit 30 | namespace: web 31 | ``` 32 | 33 | ## Understanding the Overlap Between IAM and RBAC 34 | Google Cloud IAM roles can provide fairly specific Kubernetes authorization configuration that overlaps with RBAC roles. This means that a user's access to a GKE cluster ends up being a union of both IAM and RBAC roles. This blog post provides more information on [how IAM and RBAC work together in GKE](https://medium.com/uptime-99/making-sense-of-kubernetes-rbac-and-iam-roles-on-gke-914131b01922). If you're simply trying to see relevant GKE IAM and RBAC roles in one place, [rbac-lookup can help](https://github.com/FairwindsOps/rbac-lookup) with that. 35 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | RBAC Manager was designed to simplify authorization in Kubernetes. This is an operator that supports declarative configuration for RBAC with new custom resources. Instead of managing role bindings or service accounts directly, you can specify a desired state and RBAC Manager will make the necessary changes to achieve that state. 4 | 5 | This project has three main goals: 6 | 7 | 1. Provide a declarative approach to RBAC that is more approachable and scalable. 8 | 2. Reduce the amount of configuration required for great auth. 9 | 3. Enable automation of RBAC configuration updates with CI/CD. 10 | 11 | ## An Example 12 | 13 | To fully understand how RBAC Manager works, it's helpful to walk through a simple example. In this example we'll have a single user, Joe, that needs `edit` access to the `web` namespace and `view` access to `api` namespace. 14 | 15 | With RBAC, that requires creating 2 role bindings, the first grants `edit` access to the `web` namespace. 16 | ```yaml 17 | kind: RoleBinding 18 | apiVersion: rbac.authorization.k8s.io/v1 19 | metadata: 20 | name: joe-web 21 | namespace: web 22 | subjects: 23 | - kind: User 24 | name: joe@example.com 25 | roleRef: 26 | kind: ClusterRole 27 | name: edit 28 | apiGroup: rbac.authorization.k8s.io 29 | ``` 30 | 31 | The second grants `view` access to the `api` namespace. 32 | ```yaml 33 | kind: RoleBinding 34 | apiVersion: rbac.authorization.k8s.io/v1 35 | metadata: 36 | name: joe-api 37 | namespace: api 38 | subjects: 39 | - kind: User 40 | name: joe@example.com 41 | roleRef: 42 | kind: ClusterRole 43 | name: view 44 | apiGroup: rbac.authorization.k8s.io 45 | ``` 46 | 47 | It's easy to see just how repetitive this becomes. With RBAC Manager, we can use a custom resource to achieve the same result. 48 | ```yaml 49 | apiVersion: rbacmanager.reactiveops.io/v1beta1 50 | kind: RBACDefinition 51 | metadata: 52 | name: joe-access 53 | rbacBindings: 54 | - name: joe 55 | subjects: 56 | - kind: User 57 | name: joe@example.com 58 | roleBindings: 59 | - namespace: api 60 | clusterRole: view 61 | - namespace: web 62 | clusterRole: edit 63 | ``` 64 | 65 | ## The Benefits 66 | 67 | With an RBAC Definition custom resource, we can cut the amount of configuration in half (or often significantly more). RBAC Manager is deployed as an operator and listens for new and updated RBAC Definitions, making the necessary changes to achieve the desired state. 68 | 69 | This approach is incredibly helpful for 2 specific cases: 70 | 71 | #### 1. Updating a Role Binding 72 | 73 | Unfortunately there's no way to change the role an existing Kubernetes Role Binding refers to. That means that changing a role granted to a user involves deleting and recreating a Kubernetes Role Binding. With RBAC Manager, that process happens automatically when an RBAC Definition is updated. 74 | 75 | #### 2. Detecting Role Binding Removal 76 | 77 | When it comes to potential CI automation of changes to RBAC configuration, tracking the removal of a role binding can get quite tricky. If you were using a traditional workflow where desired Kubernetes objects are represent in a repo as yaml files, the creates and updates are reasonably straightforward, but revoking access on the basis of a Role Binding being removed is quite tricky. 78 | 79 | With RBAC Manager, each RBAC Definition "owns" any resources it creates, and will always compare the desired state in the current RBAC Definition with the list of resources currently owned by it. If a Role Binding is no longer included in a RBAC Definition, RBAC Manager will automatically remove it. 80 | 81 | ## Getting Started 82 | 83 | RBAC Manager is simple to install with either the [Helm chart](https://github.com/FairwindsOps/charts/tree/master/stable/rbac-manager) or Kubernetes deployment YAML included in this repo: 84 | 85 | ``` 86 | helm repo add fairwinds-stable https://charts.fairwinds.com/stable 87 | helm install rbac-manager fairwinds-stable/rbac-manager --namespace rbac-manager --create-namespace 88 | ``` 89 | 90 | ``` 91 | kubectl apply -f deploy/ 92 | ``` 93 | 94 | Once RBAC Manager is installed in your cluster, you'll be able to deploy RBAC Definitions to your cluster. There are examples of these custom resources above as well as in the examples directory of this repository. 95 | 96 | ## Dynamic Namespaces and Labels 97 | 98 | RBAC Definitions can now include `namespaceSelectors` in place of `namespace` attributes when specifying Role Binding configuration. This can be incredibly helpful when working with dynamically provisioned namespaces. 99 | 100 | ```yaml 101 | apiVersion: rbacmanager.reactiveops.io/v1beta1 102 | kind: RBACDefinition 103 | metadata: 104 | name: dev-access 105 | rbacBindings: 106 | - name: dev-team 107 | subjects: 108 | - kind: Group 109 | name: dev-team 110 | roleBindings: 111 | - clusterRole: edit 112 | namespaceSelector: 113 | matchLabels: 114 | team: dev 115 | ``` 116 | 117 | In the example above, Role Bindings would automatically get created for each Namespace with a `team=dev` label. This supports the same functionality as other Kubernetes label selectors, read the [official docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) for more information. 118 | 119 | ## Further Reading 120 | 121 | ### RBAC Definitions 122 | 123 | RBAC Definitions can manage Cluster Role Bindings, Role Bindings, and Service Accounts. To better understand how these work, read our [RBAC Definition documentation](/rbacdefinitions). 124 | 125 | ### Cloud Specific Authentication Tips 126 | 127 | To properly configure authorization with RBAC in Kubernetes, you first need to have good authentication. We've provided some helpful documentation for working with authentication on [AWS](aws.md), [GKE](gke.md), and [Azure](aks.md). 128 | 129 | ### Better Visibility With RBAC Lookup 130 | 131 | We have a related open source tool that allows you to easily find roles and cluster roles attached to any user, service account, or group name in your Kubernetes cluster. If that sounds interesting, take a look at [rbac-lookup](https://github.com/FairwindsOps/rbac-lookup) on GitHub. 132 | 133 | ## License 134 | 135 | Apache License 2.0 136 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/rbacdefinitions.md: -------------------------------------------------------------------------------- 1 | # RBAC Definitions 2 | Just as Kubernetes Deployments make Pods much simpler to manage at scale, RBAC Definitions are designed to simplify the management of Role Bindings and Service Accounts at scale. RBAC Manager will create, update, or delete Cluster Role Bindings, Role Bindings, or Service Accounts that are referenced in an RBAC Definition. Here's a more complete example of what that could look like: 3 | 4 | ```yaml 5 | apiVersion: rbacmanager.reactiveops.io/v1beta1 6 | kind: RBACDefinition 7 | metadata: 8 | name: rbac-manager-users-example 9 | rbacBindings: 10 | - name: cluster-admins 11 | subjects: 12 | - kind: User 13 | name: jane@example.com 14 | clusterRoleBindings: 15 | - clusterRole: cluster-admin 16 | - name: web-developers 17 | subjects: 18 | - kind: User 19 | name: dave@example.com 20 | - kind: User 21 | name: joe@example.com 22 | roleBindings: 23 | - clusterRole: edit 24 | namespace: web 25 | - clusterRole: view 26 | namespace: api 27 | - name: ci-bot 28 | subjects: 29 | - kind: ServiceAccount 30 | name: ci-bot 31 | namespace: rbac-manager 32 | roleBindings: 33 | - clusterRole: edit 34 | namespaceSelector: 35 | matchLabels: 36 | ci: edit 37 | - clusterRole: admin 38 | namespaceSelector: 39 | matchExpressions: 40 | - key: app 41 | operator: In 42 | values: 43 | - web 44 | - queue 45 | ``` 46 | 47 | In the above example, RBAC Manager will create the following resources: 48 | - A Cluster Role Binding that gives Jane cluster-admin access 49 | - A Role Binding that gives Dave and Joe edit access in the web namespace 50 | - A Role Binding that gives Dave and Joe view access in the api namespace 51 | - A Service Account named ci-bot in the rbac-manager namespace 52 | - Role Binding(s) that grant the ci-bot Service Account edit access in all namespaces with `ci=edit` labels 53 | - Role Binding(s) that grant the ci-bot Service Account admin access in all namespaces with `app=web` or `app=queue` labels 54 | 55 | There are more examples of RBAC Definitions in the examples directory of this repo. 56 | -------------------------------------------------------------------------------- /docs/upgrades.md: -------------------------------------------------------------------------------- 1 | # Upgrades 2 | 3 | ## Upgrading to RBAC Manager 0.4.0 4 | RBAC Manager 0.4.0 was a huge release that unfortunately included some significant breaking changes. To our knowledge, we're the only ones that have been using this project up to this point, but we certainly still do not take breaking changes lightly. We hope that the advantages it provides are worth the upgrade effort. This version involves a transition from Python to Go + Kubebuilder. There are a number of advantages included in this move: 5 | 6 | - Improved testing capabilities with client-go 7 | - Decreases Docker image size by 2/3 8 | - Encourages usage of best practices through framework usage 9 | - Strong typing should help avoid introducing stupid bugs 10 | - Potential for generating docs with Kubebuilder 11 | 12 | Beyond those benefits, here are the key new changes involved with this release: 13 | 14 | - New RBAC Definition syntax 15 | - Support for multiple RBAC Definitions 16 | - Using OwnerReferences to associate a specific RBAC Definition with a Cluster Role Binding, Role Binding, or Service Account 17 | - Using CRD validation for all RBAC Definition fields 18 | - Moving from rbac-manager.reactiveops.io to rbacmanager.reactiveops.io for CRD 19 | - Support for groups (helpful when working with something like Heptio Authenticator) 20 | - Some basic testing in place 21 | 22 | ### The Upgrade Process 23 | Unfortunately with the changes to the custom resource definition, this process will essentially involve deleting the previous configuration and creating new replacement configuration with the updated syntax. Before beginning this process, ensure that you have cluster-admin access that is not managed by RBAC Manager. Part of this process will involve temporarily deleting those roles, so it's important to ensure you have cluster-admin access through a separate Cluster Role Binding. 24 | 25 | 1. Delete any existing RBAC Definitions - this allows the original RBAC Manager version to delete any associated role bindings. 26 | 2. Delete the RBAC Manager deployment configuration. 27 | 3. Deploy the updated version of RBAC Manager. 28 | 4. Create an updated RBAC Definition with the new syntax (see the `examples` directory of this repository). -------------------------------------------------------------------------------- /e2e/chainsaw/.chainsaw.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/configuration-chainsaw-v1alpha1.json 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Configuration 4 | metadata: 5 | name: congiguration 6 | spec: 7 | parallel: 1 8 | fullName: true 9 | failFast: false 10 | delayBeforeCleanup: 3s 11 | -------------------------------------------------------------------------------- /e2e/chainsaw/cluster-role-bindings/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: clusterrolebindings 6 | spec: 7 | steps: 8 | - try: 9 | - apply: 10 | file: resources.yaml 11 | - assert: 12 | file: expected.yaml 13 | -------------------------------------------------------------------------------- /e2e/chainsaw/cluster-role-bindings/expected.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | rbac-manager: reactiveops 6 | ownerReferences: 7 | - apiVersion: rbacmanager.reactiveops.io/v1beta1 8 | kind: RBACDefinition 9 | name: rbac-manager-definition 10 | roleRef: 11 | apiGroup: rbac.authorization.k8s.io 12 | kind: ClusterRole 13 | name: test-rbac-manager 14 | subjects: 15 | - kind: ServiceAccount 16 | name: test-rbac-manager 17 | namespace: rbac-manager 18 | -------------------------------------------------------------------------------- /e2e/chainsaw/cluster-role-bindings/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbacmanager.reactiveops.io/v1beta1 2 | kind: RBACDefinition 3 | metadata: 4 | name: rbac-manager-definition 5 | rbacBindings: 6 | - name: admins 7 | subjects: 8 | - kind: ServiceAccount 9 | name: test-rbac-manager 10 | namespace: rbac-manager 11 | clusterRoleBindings: 12 | - clusterRole: test-rbac-manager 13 | -------------------------------------------------------------------------------- /e2e/chainsaw/deleted/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: clusterrolebindings 6 | spec: 7 | steps: 8 | - description: setup rbac definition, make sure expected resources are created 9 | try: 10 | - apply: 11 | file: resources.yaml 12 | - assert: 13 | file: expected.yaml 14 | - description: delete rbac definition, make sure previously created resources are deleted 15 | try: 16 | - delete: 17 | ref: 18 | apiVersion: rbacmanager.reactiveops.io/v1beta1 19 | kind: RBACDefinition 20 | name: rbac-manager-definition 21 | - error: 22 | file: expected.yaml 23 | -------------------------------------------------------------------------------- /e2e/chainsaw/deleted/expected.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | rbac-manager: reactiveops 6 | ownerReferences: 7 | - apiVersion: rbacmanager.reactiveops.io/v1beta1 8 | kind: RBACDefinition 9 | name: rbac-manager-definition 10 | roleRef: 11 | apiGroup: rbac.authorization.k8s.io 12 | kind: ClusterRole 13 | name: test-rbac-manager 14 | subjects: 15 | - kind: ServiceAccount 16 | name: test-rbac-manager 17 | namespace: rbac-manager 18 | -------------------------------------------------------------------------------- /e2e/chainsaw/deleted/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbacmanager.reactiveops.io/v1beta1 2 | kind: RBACDefinition 3 | metadata: 4 | name: rbac-manager-definition 5 | rbacBindings: 6 | - name: admins 7 | subjects: 8 | - kind: ServiceAccount 9 | name: test-rbac-manager 10 | namespace: rbac-manager 11 | clusterRoleBindings: 12 | - clusterRole: test-rbac-manager 13 | -------------------------------------------------------------------------------- /e2e/chainsaw/service-accounts/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: clusterrolebindings 6 | spec: 7 | steps: 8 | - try: 9 | - apply: 10 | file: resources.yaml 11 | - assert: 12 | file: expected.yaml 13 | -------------------------------------------------------------------------------- /e2e/chainsaw/service-accounts/expected.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | rbac-manager: reactiveops 6 | ownerReferences: 7 | - apiVersion: rbacmanager.reactiveops.io/v1beta1 8 | kind: RBACDefinition 9 | name: rbac-manager-definition-1 10 | roleRef: 11 | apiGroup: rbac.authorization.k8s.io 12 | kind: ClusterRole 13 | name: test-rbac-manager 14 | subjects: 15 | - kind: ServiceAccount 16 | name: test-rbac-manager 17 | namespace: rbac-manager 18 | --- 19 | apiVersion: v1 20 | kind: ServiceAccount 21 | imagePullSecrets: 22 | - name: robot-secret 23 | metadata: 24 | annotations: 25 | rbacmanager.reactiveops.io/managed-pull-secrets: robot-secret 26 | labels: 27 | rbac-manager: reactiveops 28 | name: test-rbac-manager 29 | namespace: rbac-manager 30 | ownerReferences: 31 | - apiVersion: rbacmanager.reactiveops.io/v1beta1 32 | kind: RBACDefinition 33 | name: rbac-manager-definition-1 34 | -------------------------------------------------------------------------------- /e2e/chainsaw/service-accounts/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbacmanager.reactiveops.io/v1beta1 2 | kind: RBACDefinition 3 | metadata: 4 | name: rbac-manager-definition-1 5 | rbacBindings: 6 | - name: admins 7 | subjects: 8 | - kind: ServiceAccount 9 | name: test-rbac-manager 10 | namespace: rbac-manager 11 | imagePullSecrets: 12 | - robot-secret 13 | clusterRoleBindings: 14 | - clusterRole: test-rbac-manager 15 | -------------------------------------------------------------------------------- /e2e/pre.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | wget -O /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/v4.35.1/yq_linux_amd64" 6 | chmod +x /usr/local/bin/yq 7 | 8 | if [ -z "$CI_SHA1" ]; then 9 | echo "CI_SHA1 not set. Something is wrong" 10 | exit 1 11 | else 12 | echo "CI_SHA1: $CI_SHA1" 13 | fi 14 | 15 | printf "\n\n" 16 | echo "********************************************************************" 17 | echo "** LOADING IMAGES TO DOCKER AND KIND **" 18 | echo "********************************************************************" 19 | printf "\n\n" 20 | docker load --input /tmp/workspace/docker_save/rbac-manager_${CI_SHA1}-amd64.tar 21 | export PATH=$(pwd)/bin-kind:$PATH 22 | kind load docker-image --name e2e quay.io/reactiveops/rbac-manager:${CI_SHA1}-amd64 23 | printf "\n\n" 24 | echo "********************************************************************" 25 | echo "** END LOADING IMAGE **" 26 | echo "********************************************************************" 27 | printf "\n\n" 28 | 29 | export newImage=quay.io/reactiveops/rbac-manager:${CI_SHA1}-amd64 30 | yq -i '.spec.template.spec.containers[0].image = env(newImage)' deploy/3_deployment.yaml 31 | yq -i '.spec.template.spec.containers[0].imagePullPolicy = "IfNotPresent"' deploy/3_deployment.yaml 32 | cat deploy/3_deployment.yaml 33 | 34 | docker cp deploy e2e-command-runner:/ 35 | docker cp e2e/chainsaw e2e-command-runner:/ 36 | -------------------------------------------------------------------------------- /e2e/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=$(dirname $BASH_SOURCE) 4 | 5 | printf "\n\n" 6 | echo "**************************" 7 | echo "** Begin E2E Test Setup **" 8 | echo "**************************" 9 | printf "\n\n" 10 | 11 | set -e 12 | 13 | 14 | printf "\n\n" 15 | echo "********************************************************************" 16 | echo "** Install rbac-manager at $CI_SHA1 **" 17 | echo "********************************************************************" 18 | printf "\n\n" 19 | 20 | kubectl apply -f deploy/ 21 | kubectl -n rbac-manager wait deployment/rbac-manager --timeout=120s --for condition=available 22 | 23 | printf "\n\n" 24 | echo "********************************************************************" 25 | echo "** Install and run Chainsaw **" 26 | echo "********************************************************************" 27 | printf "\n\n" 28 | 29 | cd "$BASE_DIR/chainsaw" 30 | 31 | curl -sL https://github.com/kyverno/chainsaw/releases/download/v0.1.0/chainsaw_linux_amd64.tar.gz -o linux_amd64.tar.gz 32 | tar -xvf linux_amd64.tar.gz chainsaw 33 | rm linux_amd64.tar.gz 34 | chmod +x chainsaw 35 | 36 | ./chainsaw test 37 | 38 | if [ $? -ne 0 ]; then 39 | exit 1 40 | fi 41 | -------------------------------------------------------------------------------- /examples/rbacdefinition-everything.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbacmanager.reactiveops.io/v1beta1 2 | kind: RBACDefinition 3 | metadata: 4 | name: rbac-manager-everything-example 5 | rbacBindings: 6 | - name: admins 7 | subjects: 8 | - kind: Group 9 | name: example 10 | clusterRoleBindings: 11 | - clusterRole: edit 12 | roleBindings: 13 | - clusterRole: admin 14 | namespace: default 15 | - name: example-users 16 | subjects: 17 | - kind: User 18 | name: sue@example.com 19 | - kind: User 20 | name: joe@example.com 21 | clusterRoleBindings: 22 | - clusterRole: edit 23 | roleBindings: 24 | - clusterRole: admin 25 | namespaceSelector: 26 | matchLabels: 27 | team: dev 28 | - name: example-service-account 29 | subjects: 30 | - kind: ServiceAccount 31 | name: example 32 | namespace: default 33 | imagePullSecrets: 34 | - robot-secret 35 | automountServiceAccountToken: false 36 | clusterRoleBindings: 37 | - clusterRole: view 38 | roleBindings: 39 | - clusterRole: admin 40 | namespaceSelector: 41 | matchExpressions: 42 | - key: app 43 | operator: In 44 | values: 45 | - web 46 | - queue 47 | -------------------------------------------------------------------------------- /examples/rbacdefinition-sa-imagepull.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbacmanager.reactiveops.io/v1beta1 2 | kind: RBACDefinition 3 | metadata: 4 | name: robot-rbac 5 | rbacBindings: 6 | - name: robot-ci 7 | subjects: 8 | - kind: ServiceAccount 9 | namespace: app 10 | name: robot 11 | imagePullSecrets: 12 | - robot-secret 13 | roleBindings: 14 | - clusterRole: edit 15 | namespace: app -------------------------------------------------------------------------------- /examples/rbacdefinition-users.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbacmanager.reactiveops.io/v1beta1 2 | kind: RBACDefinition 3 | metadata: 4 | name: rbac-manager-users-example 5 | rbacBindings: 6 | - name: cluster-admins 7 | subjects: 8 | - kind: User 9 | name: sue@example.com 10 | - kind: User 11 | name: joe@example.com 12 | clusterRoleBindings: 13 | - clusterRole: cluster-admin 14 | - name: web-developers 15 | subjects: 16 | - kind: User 17 | name: sarah@example.com 18 | - kind: User 19 | name: john@example.com 20 | - kind: User 21 | name: rob@example.com 22 | roleBindings: 23 | - clusterRole: edit 24 | namespace: web 25 | - clusterRole: view 26 | namespace: api 27 | - name: api-developers 28 | subjects: 29 | - kind: User 30 | name: daniel@example.com 31 | - kind: User 32 | name: jess@example.com 33 | - kind: User 34 | name: lance@example.com 35 | roleBindings: 36 | - clusterRole: edit 37 | namespace: api 38 | - clusterRole: view 39 | namespace: web 40 | - name: ci-bot 41 | subjects: 42 | - kind: ServiceAccount 43 | name: ci-bot 44 | namespace: default 45 | roleBindings: 46 | - clusterRole: edit 47 | namespace: api 48 | - clusterRole: edit 49 | namespace: web 50 | -------------------------------------------------------------------------------- /fairwinds-insights.yaml: -------------------------------------------------------------------------------- 1 | options: 2 | organization: fairwinds-opensource 3 | baseBranch: master 4 | 5 | manifests: 6 | yaml: 7 | - ./deploy/ 8 | - ./examples 9 | 10 | exemptions: 11 | - image: quay.io/reactiveops/rbac-manager 12 | report: trivy 13 | reason: rbac-manager's image is built from the scratch base image, and is unscannable 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fairwindsops/rbac-manager 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.4 6 | 7 | require ( 8 | github.com/prometheus/client_golang v1.19.1 9 | github.com/sirupsen/logrus v1.9.3 10 | github.com/stretchr/testify v1.9.0 11 | k8s.io/api v0.30.2 12 | k8s.io/apimachinery v0.30.2 13 | k8s.io/client-go v0.30.2 14 | k8s.io/klog v1.0.0 15 | sigs.k8s.io/controller-runtime v0.18.4 16 | ) 17 | 18 | require ( 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 23 | github.com/evanphx/json-patch v5.7.0+incompatible // indirect 24 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 25 | github.com/fsnotify/fsnotify v1.7.0 // indirect 26 | github.com/go-logr/logr v1.4.2 // indirect 27 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 28 | github.com/go-openapi/jsonreference v0.21.0 // indirect 29 | github.com/go-openapi/swag v0.23.0 // indirect 30 | github.com/gogo/protobuf v1.3.2 // indirect 31 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 32 | github.com/golang/protobuf v1.5.4 // indirect 33 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect 34 | github.com/google/go-cmp v0.6.0 // indirect 35 | github.com/google/gofuzz v1.2.0 // indirect 36 | github.com/google/uuid v1.6.0 // indirect 37 | github.com/imdario/mergo v0.3.16 // 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/prometheus/client_model v0.6.1 // indirect 47 | github.com/prometheus/common v0.55.0 // indirect 48 | github.com/prometheus/procfs v0.15.1 // indirect 49 | github.com/spf13/pflag v1.0.5 // indirect 50 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect 51 | golang.org/x/net v0.26.0 // indirect 52 | golang.org/x/oauth2 v0.21.0 // indirect 53 | golang.org/x/sys v0.21.0 // indirect 54 | golang.org/x/term v0.21.0 // indirect 55 | golang.org/x/text v0.16.0 // indirect 56 | golang.org/x/time v0.5.0 // indirect 57 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 58 | google.golang.org/protobuf v1.34.2 // indirect 59 | gopkg.in/inf.v0 v0.9.1 // indirect 60 | gopkg.in/yaml.v2 v2.4.0 // indirect 61 | gopkg.in/yaml.v3 v3.0.1 // indirect 62 | k8s.io/klog/v2 v2.130.1 // indirect 63 | k8s.io/kube-openapi v0.0.0-20240620174524-b456828f718b // indirect 64 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect 65 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 66 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 67 | sigs.k8s.io/yaml v1.4.0 // indirect 68 | ) 69 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= 9 | github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 10 | github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= 11 | github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 12 | github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= 13 | github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= 14 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 15 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 16 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 17 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 18 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 19 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 20 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 21 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 22 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 23 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 24 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 25 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 26 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 27 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 28 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 29 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 30 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 31 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 32 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 33 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 34 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 35 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 36 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= 37 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= 38 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 39 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 40 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 41 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 42 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 43 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 44 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= 45 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 46 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 47 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 48 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 49 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 50 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 51 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 52 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 53 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 54 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 55 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 56 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 57 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 58 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 59 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 60 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 61 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 62 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 63 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 64 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 65 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 66 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 67 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 68 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 69 | github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= 70 | github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= 71 | github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= 72 | github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= 73 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 74 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 75 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 76 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 77 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 78 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 79 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 80 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 81 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 82 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 83 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 84 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 85 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 86 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 87 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 88 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 89 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 90 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 91 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 92 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 93 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 94 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 95 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 96 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 97 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 98 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 99 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 100 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 101 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 102 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 103 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 104 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 105 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 106 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 107 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= 108 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 109 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 110 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 111 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 112 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 113 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 114 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 115 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 116 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 117 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 118 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 119 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 120 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 122 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 123 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 124 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 125 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 126 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 127 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 128 | golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= 129 | golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= 130 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 131 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 132 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 133 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 134 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 135 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 136 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 137 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 138 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 139 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 140 | golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= 141 | golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 142 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 143 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 144 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 145 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 146 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 147 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 148 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 149 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 150 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 151 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 152 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 153 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 154 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 155 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 156 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 157 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 158 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 159 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 160 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 161 | k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= 162 | k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= 163 | k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= 164 | k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= 165 | k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= 166 | k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= 167 | k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= 168 | k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= 169 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 170 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 171 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 172 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 173 | k8s.io/kube-openapi v0.0.0-20240620174524-b456828f718b h1:Q9xmGWBvOGd8UJyccgpYlLosk/JlfP3xQLNkQlHJeXw= 174 | k8s.io/kube-openapi v0.0.0-20240620174524-b456828f718b/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= 175 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= 176 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 177 | sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw= 178 | sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= 179 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 180 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 181 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 182 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 183 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 184 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 185 | -------------------------------------------------------------------------------- /img/rbac-manager-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/apis/addtoscheme_rbacmanager_v1beta1.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package apis 18 | 19 | import ( 20 | "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 21 | ) 22 | 23 | func init() { 24 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 25 | AddToSchemes = append(AddToSchemes, v1beta1.SchemeBuilder.AddToScheme) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/apis/apis.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Generate deepcopy for apis 18 | //go:generate go run ../../vendor/k8s.io/code-generator/cmd/deepcopy-gen/main.go -O zz_generated.deepcopy -i ./... -h ../../hack/boilerplate.go.txt 19 | 20 | // Package apis contains Kubernetes API groups. 21 | package apis 22 | 23 | import ( 24 | "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 28 | var AddToSchemes runtime.SchemeBuilder 29 | 30 | // AddToScheme adds all Resources to the Scheme 31 | func AddToScheme(s *runtime.Scheme) error { 32 | return AddToSchemes.AddToScheme(s) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/apis/rbacmanager/group.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package rbacmanager contains rbacmanager API versions 18 | package rbacmanager 19 | -------------------------------------------------------------------------------- /pkg/apis/rbacmanager/v1beta1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the rbacmanager v1beta1 API group 18 | // +k8s:openapi-gen=true 19 | // +k8s:deepcopy-gen=package,register 20 | // +k8s:conversion-gen=github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager 21 | // +k8s:defaulter-gen=TypeMeta 22 | // +groupName=rbacmanager.reactiveops.io 23 | package v1beta1 24 | -------------------------------------------------------------------------------- /pkg/apis/rbacmanager/v1beta1/rbacdefinition_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | rbacv1 "k8s.io/api/rbac/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // Subject is an expansion on the rbacv1.Subject to allow definition of ImagePullSecrets for a Service Account 25 | type Subject struct { 26 | rbacv1.Subject `json:",inline"` 27 | ImagePullSecrets []string `json:"imagePullSecrets"` 28 | AutomountServiceAccountToken *bool `json:"automountServiceAccountToken,omitempty"` 29 | } 30 | 31 | // RBACBinding is a specification for a RBACBinding resource 32 | type RBACBinding struct { 33 | Name string `json:"name"` 34 | Subjects []Subject `json:"subjects"` 35 | ClusterRoleBindings []ClusterRoleBinding `json:"clusterRoleBindings"` 36 | RoleBindings []RoleBinding `json:"roleBindings"` 37 | } 38 | 39 | // ClusterRoleBinding is a specification for a ClusterRoleBinding resource 40 | type ClusterRoleBinding struct { 41 | ClusterRole string `json:"clusterRole"` 42 | } 43 | 44 | // RoleBinding is a specification for a RoleBinding resource 45 | type RoleBinding struct { 46 | ClusterRole string `json:"clusterRole,omitempty"` 47 | Role string `json:"role,omitempty"` 48 | Namespace string `json:"namespace,omitempty"` 49 | NamespaceSelector metav1.LabelSelector `json:"namespaceSelector,omitempty"` 50 | } 51 | 52 | // +genclient 53 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 54 | 55 | // RBACDefinition is the Schema for the rbacdefinitions API 56 | // +k8s:openapi-gen=true 57 | type RBACDefinition struct { 58 | metav1.TypeMeta `json:",inline"` 59 | metav1.ObjectMeta `json:"metadata"` 60 | RBACBindings []RBACBinding `json:"rbacBindings"` 61 | Status RBACDefinitionStatus `json:"status,omitempty"` 62 | } 63 | 64 | // RBACDefinitionStatus defines the observed state of RBACDefinition 65 | type RBACDefinitionStatus struct { 66 | } 67 | 68 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 69 | 70 | // RBACDefinitionList contains a list of RBACDefinition 71 | type RBACDefinitionList struct { 72 | metav1.TypeMeta `json:",inline"` 73 | metav1.ListMeta `json:"metadata,omitempty"` 74 | Items []RBACDefinition `json:"items"` 75 | } 76 | 77 | func init() { 78 | SchemeBuilder.Register(&RBACDefinition{}, &RBACDefinitionList{}) 79 | } 80 | -------------------------------------------------------------------------------- /pkg/apis/rbacmanager/v1beta1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // NOTE: Boilerplate only. Ignore this file. 18 | 19 | // Package v1beta1 contains API Schema definitions for the rbacmanager v1beta1 API group 20 | // +k8s:openapi-gen=true 21 | // +k8s:deepcopy-gen=package,register 22 | // +k8s:conversion-gen=github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager 23 | // +k8s:defaulter-gen=TypeMeta 24 | // +groupName=rbacmanager.reactiveops.io 25 | package v1beta1 26 | 27 | import ( 28 | "k8s.io/apimachinery/pkg/runtime/schema" 29 | "sigs.k8s.io/controller-runtime/pkg/scheme" 30 | ) 31 | 32 | var ( 33 | // SchemeGroupVersion is group version used to register these objects 34 | SchemeGroupVersion = schema.GroupVersion{Group: "rbacmanager.reactiveops.io", Version: "v1beta1"} 35 | 36 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 37 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 38 | 39 | // AddToScheme is required by pkg/client/... 40 | AddToScheme = SchemeBuilder.AddToScheme 41 | ) 42 | 43 | // Resource is required by pkg/client/listers/... 44 | func Resource(resource string) schema.GroupResource { 45 | return SchemeGroupVersion.WithResource(resource).GroupResource() 46 | } 47 | -------------------------------------------------------------------------------- /pkg/apis/rbacmanager/v1beta1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Code generated by deepcopy-gen. DO NOT EDIT. 5 | 6 | package v1beta1 7 | 8 | import ( 9 | runtime "k8s.io/apimachinery/pkg/runtime" 10 | ) 11 | 12 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 13 | func (in *ClusterRoleBinding) DeepCopyInto(out *ClusterRoleBinding) { 14 | *out = *in 15 | return 16 | } 17 | 18 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRoleBinding. 19 | func (in *ClusterRoleBinding) DeepCopy() *ClusterRoleBinding { 20 | if in == nil { 21 | return nil 22 | } 23 | out := new(ClusterRoleBinding) 24 | in.DeepCopyInto(out) 25 | return out 26 | } 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *RBACBinding) DeepCopyInto(out *RBACBinding) { 30 | *out = *in 31 | if in.Subjects != nil { 32 | in, out := &in.Subjects, &out.Subjects 33 | *out = make([]Subject, len(*in)) 34 | copy(*out, *in) 35 | } 36 | if in.ClusterRoleBindings != nil { 37 | in, out := &in.ClusterRoleBindings, &out.ClusterRoleBindings 38 | *out = make([]ClusterRoleBinding, len(*in)) 39 | copy(*out, *in) 40 | } 41 | if in.RoleBindings != nil { 42 | in, out := &in.RoleBindings, &out.RoleBindings 43 | *out = make([]RoleBinding, len(*in)) 44 | copy(*out, *in) 45 | } 46 | return 47 | } 48 | 49 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBACBinding. 50 | func (in *RBACBinding) DeepCopy() *RBACBinding { 51 | if in == nil { 52 | return nil 53 | } 54 | out := new(RBACBinding) 55 | in.DeepCopyInto(out) 56 | return out 57 | } 58 | 59 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 60 | func (in *RBACDefinition) DeepCopyInto(out *RBACDefinition) { 61 | *out = *in 62 | out.TypeMeta = in.TypeMeta 63 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 64 | if in.RBACBindings != nil { 65 | in, out := &in.RBACBindings, &out.RBACBindings 66 | *out = make([]RBACBinding, len(*in)) 67 | for i := range *in { 68 | (*in)[i].DeepCopyInto(&(*out)[i]) 69 | } 70 | } 71 | out.Status = in.Status 72 | return 73 | } 74 | 75 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBACDefinition. 76 | func (in *RBACDefinition) DeepCopy() *RBACDefinition { 77 | if in == nil { 78 | return nil 79 | } 80 | out := new(RBACDefinition) 81 | in.DeepCopyInto(out) 82 | return out 83 | } 84 | 85 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 86 | func (in *RBACDefinition) DeepCopyObject() runtime.Object { 87 | if c := in.DeepCopy(); c != nil { 88 | return c 89 | } 90 | return nil 91 | } 92 | 93 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 94 | func (in *RBACDefinitionList) DeepCopyInto(out *RBACDefinitionList) { 95 | *out = *in 96 | out.TypeMeta = in.TypeMeta 97 | out.ListMeta = in.ListMeta 98 | if in.Items != nil { 99 | in, out := &in.Items, &out.Items 100 | *out = make([]RBACDefinition, len(*in)) 101 | for i := range *in { 102 | (*in)[i].DeepCopyInto(&(*out)[i]) 103 | } 104 | } 105 | return 106 | } 107 | 108 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBACDefinitionList. 109 | func (in *RBACDefinitionList) DeepCopy() *RBACDefinitionList { 110 | if in == nil { 111 | return nil 112 | } 113 | out := new(RBACDefinitionList) 114 | in.DeepCopyInto(out) 115 | return out 116 | } 117 | 118 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 119 | func (in *RBACDefinitionList) DeepCopyObject() runtime.Object { 120 | if c := in.DeepCopy(); c != nil { 121 | return c 122 | } 123 | return nil 124 | } 125 | 126 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 127 | func (in *RBACDefinitionStatus) DeepCopyInto(out *RBACDefinitionStatus) { 128 | *out = *in 129 | return 130 | } 131 | 132 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RBACDefinitionStatus. 133 | func (in *RBACDefinitionStatus) DeepCopy() *RBACDefinitionStatus { 134 | if in == nil { 135 | return nil 136 | } 137 | out := new(RBACDefinitionStatus) 138 | in.DeepCopyInto(out) 139 | return out 140 | } 141 | 142 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 143 | func (in *RoleBinding) DeepCopyInto(out *RoleBinding) { 144 | *out = *in 145 | return 146 | } 147 | 148 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBinding. 149 | func (in *RoleBinding) DeepCopy() *RoleBinding { 150 | if in == nil { 151 | return nil 152 | } 153 | out := new(RoleBinding) 154 | in.DeepCopyInto(out) 155 | return out 156 | } 157 | -------------------------------------------------------------------------------- /pkg/controller/namespace.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 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 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package controller 15 | 16 | import ( 17 | "context" 18 | 19 | v1 "k8s.io/api/core/v1" 20 | "k8s.io/apimachinery/pkg/api/errors" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "k8s.io/client-go/kubernetes" 23 | "k8s.io/client-go/rest" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | "sigs.k8s.io/controller-runtime/pkg/manager" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | 28 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 29 | "github.com/fairwindsops/rbac-manager/pkg/kube" 30 | "github.com/fairwindsops/rbac-manager/pkg/metrics" 31 | "github.com/fairwindsops/rbac-manager/pkg/reconciler" 32 | ) 33 | 34 | // newNamespaceReconciler returns a new reconcile.Reconciler 35 | func newNamespaceReconciler(mgr manager.Manager) reconcile.Reconciler { 36 | return &ReconcileNamespace{Client: mgr.GetClient(), config: mgr.GetConfig(), scheme: mgr.GetScheme()} 37 | } 38 | 39 | // ReconcileNamespace reconciles a Namespace object 40 | type ReconcileNamespace struct { 41 | client.Client 42 | scheme *runtime.Scheme 43 | config *rest.Config 44 | } 45 | 46 | // Reconcile makes changes in response to Namespace changes 47 | func (r *ReconcileNamespace) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 48 | var err error 49 | 50 | // Fetch the Namespace 51 | namespace := &v1.Namespace{} 52 | err = r.Get(ctx, request.NamespacedName, namespace) 53 | 54 | if err != nil { 55 | if errors.IsNotFound(err) { 56 | err = reconcileNamespace(r.config, namespace) 57 | if err != nil { 58 | metrics.ErrorCounter.Inc() 59 | return reconcile.Result{}, err 60 | } 61 | return reconcile.Result{}, nil 62 | } 63 | // Error reading the object - requeue the request. 64 | metrics.ErrorCounter.Inc() 65 | return reconcile.Result{}, err 66 | } 67 | 68 | err = reconcileNamespace(r.config, namespace) 69 | if err != nil { 70 | return reconcile.Result{}, err 71 | } 72 | 73 | return reconcile.Result{}, nil 74 | } 75 | 76 | func reconcileNamespace(config *rest.Config, namespace *v1.Namespace) error { 77 | metrics.ReconcileCounter.WithLabelValues("namespace").Inc() 78 | var err error 79 | var rbacDefList rbacmanagerv1beta1.RBACDefinitionList 80 | rdr := reconciler.Reconciler{} 81 | 82 | // Full Kubernetes ClientSet is required because RBAC types don't 83 | // implement methods required for controller-runtime methods to work 84 | rdr.Clientset, err = kubernetes.NewForConfig(config) 85 | 86 | if err != nil { 87 | return err 88 | } 89 | 90 | rbacDefList, err = kube.GetRbacDefinitions() 91 | if err != nil { 92 | return err 93 | } 94 | 95 | for _, rbacDef := range rbacDefList.Items { 96 | err = rdr.ReconcileNamespaceChange(&rbacDef, namespace) 97 | if err != nil { 98 | return err 99 | } 100 | } 101 | 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /pkg/controller/rbacdefinition.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "context" 21 | 22 | "k8s.io/apimachinery/pkg/api/errors" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/client-go/kubernetes" 25 | "sigs.k8s.io/controller-runtime/pkg/client" 26 | "sigs.k8s.io/controller-runtime/pkg/manager" 27 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 | 29 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 30 | "github.com/fairwindsops/rbac-manager/pkg/metrics" 31 | "github.com/fairwindsops/rbac-manager/pkg/reconciler" 32 | ) 33 | 34 | // newRbacDefReconciler returns a new reconcile.Reconciler 35 | func newRbacDefReconciler(mgr manager.Manager) reconcile.Reconciler { 36 | clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) 37 | 38 | if err != nil { 39 | // If we can't get a clientset we can't do anything else 40 | panic(err) 41 | } 42 | 43 | return &ReconcileRBACDefinition{ 44 | Client: mgr.GetClient(), 45 | clientset: clientset, 46 | scheme: mgr.GetScheme(), 47 | } 48 | } 49 | 50 | // ReconcileRBACDefinition reconciles a RBACDefinition object 51 | type ReconcileRBACDefinition struct { 52 | client.Client 53 | scheme *runtime.Scheme 54 | clientset kubernetes.Interface 55 | } 56 | 57 | // Reconcile makes changes in response to RBACDefinition changes 58 | func (r *ReconcileRBACDefinition) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 59 | metrics.ReconcileCounter.WithLabelValues("rbacdefinition").Inc() 60 | var err error 61 | rdr := reconciler.Reconciler{Clientset: r.clientset} 62 | 63 | // Fetch the RBACDefinition instance 64 | rbacDef := &rbacmanagerv1beta1.RBACDefinition{} 65 | err = r.Get(ctx, request.NamespacedName, rbacDef) 66 | if err != nil { 67 | metrics.ErrorCounter.Inc() 68 | if errors.IsNotFound(err) { 69 | // Object not found, return. Created objects are automatically garbage collected. 70 | // For additional cleanup logic use finalizers. 71 | return reconcile.Result{}, nil 72 | } 73 | // Error reading the object - requeue the request. 74 | return reconcile.Result{}, err 75 | } 76 | 77 | err = rdr.Reconcile(rbacDef) 78 | if err != nil { 79 | metrics.ErrorCounter.Inc() 80 | return reconcile.Result{}, err 81 | } 82 | 83 | return reconcile.Result{}, nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/controller/shared.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "github.com/sirupsen/logrus" 21 | corev1 "k8s.io/api/core/v1" 22 | "sigs.k8s.io/controller-runtime/pkg/client" 23 | "sigs.k8s.io/controller-runtime/pkg/controller" 24 | "sigs.k8s.io/controller-runtime/pkg/handler" 25 | "sigs.k8s.io/controller-runtime/pkg/manager" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | "sigs.k8s.io/controller-runtime/pkg/source" 28 | 29 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 30 | ) 31 | 32 | // Add creates a new RBACDefinition Controller and adds it to the Manager. 33 | // The Manager will set fields on the Controller and Start it. 34 | func Add(mgr manager.Manager) error { 35 | var err error 36 | 37 | rbacDef := &rbacmanagerv1beta1.RBACDefinition{} 38 | err = addController(mgr, newRbacDefReconciler(mgr), "rbacdefinition", rbacDef) 39 | 40 | if err != nil { 41 | logrus.Errorf("Error adding RBAC Definition reconciler") 42 | return err 43 | } 44 | 45 | namespace := &corev1.Namespace{} 46 | err = addController(mgr, newNamespaceReconciler(mgr), "namespace", namespace) 47 | 48 | if err != nil { 49 | logrus.Errorf("Error adding Namespace reconciler") 50 | return err 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // add adds a new Controller to mgr with r as the reconcile.Reconciler 57 | func addController(mgr manager.Manager, r reconcile.Reconciler, name string, cType client.Object) error { 58 | // Create a new controller 59 | c, err := controller.New(name, mgr, controller.Options{Reconciler: r}) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | // Watch for changes to Resource 65 | err = c.Watch(source.Kind(mgr.GetCache(), cType, &handler.EnqueueRequestForObject{})) 66 | 67 | if err != nil { 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/kube/kube.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kube 18 | 19 | import ( 20 | "os" 21 | 22 | "github.com/sirupsen/logrus" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/client-go/kubernetes" 25 | "sigs.k8s.io/controller-runtime/pkg/client/config" 26 | ) 27 | 28 | // LabelKey is the key of the key/value pair given to all resources managed by RBAC Manager 29 | const LabelKey = "rbac-manager" 30 | 31 | // LabelValue is the value of the key/value pair given to all resources managed by RBAC Manager 32 | const LabelValue = "reactiveops" 33 | 34 | // Labels is the key/value pair given to all resources managed by RBAC Manager 35 | var Labels = map[string]string{LabelKey: LabelValue} 36 | 37 | // ListOptions is the default set of options to find resources managed by RBAC Manager 38 | var ListOptions = metav1.ListOptions{LabelSelector: LabelKey + "=" + LabelValue} 39 | 40 | // GetClientsetOrDie returns a new Kubernetes Clientset or dies 41 | func GetClientsetOrDie() *kubernetes.Clientset { 42 | kubeConf, err := config.GetConfig() 43 | 44 | if err != nil { 45 | logrus.Error(err, "unable to get Kubernetes client config") 46 | os.Exit(1) 47 | } 48 | 49 | clientset, err := kubernetes.NewForConfig(kubeConf) 50 | 51 | if err != nil { 52 | logrus.Error(err, "unable to get Kubernetes clientset") 53 | os.Exit(1) 54 | } 55 | 56 | return clientset 57 | } 58 | -------------------------------------------------------------------------------- /pkg/kube/rbacdefinitions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kube 18 | 19 | import ( 20 | "context" 21 | 22 | "k8s.io/apimachinery/pkg/runtime/serializer" 23 | "k8s.io/client-go/kubernetes/scheme" 24 | "k8s.io/client-go/rest" 25 | "sigs.k8s.io/controller-runtime/pkg/client/config" 26 | 27 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 28 | ) 29 | 30 | // GetRbacDefinition returns an RbacDefinition for a specified name or an error 31 | func GetRbacDefinition(name string) (rbacmanagerv1beta1.RBACDefinition, error) { 32 | rbacDef := rbacmanagerv1beta1.RBACDefinition{} 33 | 34 | client, err := getRbacDefClient() 35 | if err != nil { 36 | return rbacDef, err 37 | } 38 | 39 | err = client.Get().Resource("rbacdefinitions").Name(name).Do(context.TODO()).Into(&rbacDef) 40 | 41 | return rbacDef, err 42 | } 43 | 44 | // GetRbacDefinitions returns an RbacDefinitionList or an error 45 | func GetRbacDefinitions() (rbacmanagerv1beta1.RBACDefinitionList, error) { 46 | list := rbacmanagerv1beta1.RBACDefinitionList{} 47 | 48 | client, err := getRbacDefClient() 49 | if err != nil { 50 | return list, err 51 | } 52 | 53 | err = client.Get().Resource("rbacdefinitions").Do(context.TODO()).Into(&list) 54 | 55 | return list, err 56 | } 57 | 58 | func getRbacDefClient() (*rest.RESTClient, error) { 59 | _ = rbacmanagerv1beta1.AddToScheme(scheme.Scheme) 60 | clientConfig := config.GetConfigOrDie() 61 | clientConfig.ContentConfig.GroupVersion = &rbacmanagerv1beta1.SchemeGroupVersion 62 | clientConfig.APIPath = "/apis" 63 | clientConfig.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs} 64 | clientConfig.UserAgent = rest.DefaultKubernetesUserAgent() 65 | 66 | return rest.UnversionedRESTClientFor(clientConfig) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package metrics 18 | 19 | import ( 20 | "github.com/prometheus/client_golang/prometheus" 21 | ) 22 | 23 | const namespace = "rbacmanager" 24 | 25 | var ( 26 | // ErrorCounter is a global counter for errors 27 | ErrorCounter = prometheus.NewCounter( 28 | prometheus.CounterOpts{ 29 | Namespace: namespace, 30 | Name: "errors_total", 31 | Help: "Number of errors while reconciling", 32 | }) 33 | 34 | // ChangeCounter counts kubernetes events (e.g. create, delete) on objects (e.g. ClusterRoleBinding) 35 | ChangeCounter = prometheus.NewCounterVec( 36 | prometheus.CounterOpts{ 37 | Namespace: namespace, 38 | Name: "changed_total", 39 | Help: "Number of times a Kubernetes object is created or deleted by the rbac-manager", 40 | }, 41 | []string{"object", "action"}, 42 | ) 43 | 44 | // ReconcileCounter counts controllers invocations 45 | ReconcileCounter = prometheus.NewCounterVec( 46 | prometheus.CounterOpts{ 47 | Namespace: namespace, 48 | Name: "reconcile_total", 49 | Help: "Number of times a reconciling is triggered", 50 | }, 51 | []string{"controller"}, 52 | ) 53 | ) 54 | 55 | // RegisterMetrics must be called exactly once and registers the prometheus counters as metrics 56 | func RegisterMetrics() { 57 | prometheus.MustRegister(ErrorCounter) 58 | prometheus.MustRegister(ChangeCounter) 59 | prometheus.MustRegister(ReconcileCounter) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/metrics/metrics_test.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRegisterMetrics(t *testing.T) { 10 | assert.NotPanics(t, RegisterMetrics) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/reconciler/cases_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 reconciler 16 | 17 | import ( 18 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 19 | v1 "k8s.io/api/core/v1" 20 | rbacv1 "k8s.io/api/rbac/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | var trueVal bool = true 25 | var falseVal bool = false 26 | 27 | // Parse Subject into ServiceAccount 28 | var saTestCases = []struct { 29 | name string 30 | given []rbacmanagerv1beta1.Subject 31 | expected []v1.ServiceAccount 32 | }{ 33 | { 34 | "AutomountServiceAccountToken is true", 35 | []rbacmanagerv1beta1.Subject{{ 36 | Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot", Namespace: "default"}, 37 | AutomountServiceAccountToken: &trueVal, 38 | }}, 39 | []v1.ServiceAccount{{ 40 | ObjectMeta: metav1.ObjectMeta{Name: "robot", Namespace: "default"}, 41 | AutomountServiceAccountToken: &trueVal, 42 | }}, 43 | }, 44 | { 45 | "AutomountServiceAccountToken is false", 46 | []rbacmanagerv1beta1.Subject{{ 47 | Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot", Namespace: "default"}, 48 | AutomountServiceAccountToken: &falseVal, 49 | }}, 50 | []v1.ServiceAccount{{ 51 | ObjectMeta: metav1.ObjectMeta{Name: "robot", Namespace: "default"}, 52 | AutomountServiceAccountToken: &falseVal, 53 | }}, 54 | }, 55 | { 56 | "AutomountServiceAccountToken is empty", 57 | []rbacmanagerv1beta1.Subject{{ 58 | Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot", Namespace: "default"}, 59 | }}, 60 | []v1.ServiceAccount{{ 61 | ObjectMeta: metav1.ObjectMeta{Name: "robot", Namespace: "default"}, 62 | AutomountServiceAccountToken: nil, 63 | }}, 64 | }, 65 | { 66 | "ImagePullSecrets is empty", 67 | []rbacmanagerv1beta1.Subject{{ 68 | Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot", Namespace: "default"}, 69 | ImagePullSecrets: []string{}, 70 | }}, 71 | []v1.ServiceAccount{{ 72 | ObjectMeta: metav1.ObjectMeta{Name: "robot", Namespace: "default"}, 73 | ImagePullSecrets: nil, 74 | }}, 75 | }, 76 | { 77 | "ImagePullSecrets is not empty", 78 | []rbacmanagerv1beta1.Subject{{ 79 | Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot", Namespace: "default"}, 80 | ImagePullSecrets: []string{"secret-z", "secret-a"}, 81 | }}, 82 | []v1.ServiceAccount{{ 83 | ObjectMeta: metav1.ObjectMeta{Name: "robot", Namespace: "default"}, 84 | ImagePullSecrets: []v1.LocalObjectReference{{Name: "secret-a"}, {Name: "secret-z"}}, 85 | }}, 86 | }, 87 | { 88 | "Parsing multiple Service Accounts", 89 | []rbacmanagerv1beta1.Subject{ 90 | {Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot-a", Namespace: "default"}}, 91 | {Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot-a", Namespace: "non-default"}}, 92 | {Subject: rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: "robot-z", Namespace: "default"}}, 93 | }, 94 | []v1.ServiceAccount{ 95 | {ObjectMeta: metav1.ObjectMeta{Name: "robot-z", Namespace: "default"}}, 96 | {ObjectMeta: metav1.ObjectMeta{Name: "robot-a", Namespace: "non-default"}}, 97 | {ObjectMeta: metav1.ObjectMeta{Name: "robot-a", Namespace: "default"}}, 98 | }, 99 | }, 100 | } 101 | -------------------------------------------------------------------------------- /pkg/reconciler/matcher.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 reconciler 16 | 17 | import ( 18 | v1 "k8s.io/api/core/v1" 19 | rbacv1 "k8s.io/api/rbac/v1" 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | func crbMatches(existingCRB *rbacv1.ClusterRoleBinding, requestedCRB *rbacv1.ClusterRoleBinding) bool { 24 | if !metaMatches(&existingCRB.ObjectMeta, &requestedCRB.ObjectMeta) { 25 | return false 26 | } 27 | if !subjectsMatch(&existingCRB.Subjects, &requestedCRB.Subjects) { 28 | return false 29 | } 30 | 31 | if !roleRefMatches(&existingCRB.RoleRef, &requestedCRB.RoleRef) { 32 | return false 33 | } 34 | 35 | return true 36 | } 37 | 38 | func rbMatches(existingRB *rbacv1.RoleBinding, requestedRB *rbacv1.RoleBinding) bool { 39 | if !metaMatches(&existingRB.ObjectMeta, &requestedRB.ObjectMeta) { 40 | return false 41 | } 42 | if !subjectsMatch(&existingRB.Subjects, &requestedRB.Subjects) { 43 | return false 44 | } 45 | 46 | if !roleRefMatches(&existingRB.RoleRef, &requestedRB.RoleRef) { 47 | return false 48 | } 49 | 50 | return true 51 | } 52 | 53 | func saMatches(existingSA *v1.ServiceAccount, requestedSA *v1.ServiceAccount) bool { 54 | if !metaMatches(&existingSA.ObjectMeta, &requestedSA.ObjectMeta) { 55 | return false 56 | } 57 | requestedSAManagedPullSecretsAnnotation, exists := requestedSA.Annotations[ManagedPullSecretsAnnotationKey] 58 | if !exists { 59 | return false 60 | } 61 | 62 | existingSAManagedPullSecretsAnnotation, exists := existingSA.Annotations[ManagedPullSecretsAnnotationKey] 63 | if !exists { 64 | return false 65 | } 66 | 67 | if requestedSAManagedPullSecretsAnnotation != existingSAManagedPullSecretsAnnotation { 68 | return false 69 | } 70 | 71 | for _, requestedSAImagePullSecrets := range requestedSA.ImagePullSecrets { 72 | matches := false 73 | for _, existingSAPullSecret := range existingSA.ImagePullSecrets { 74 | if requestedSAImagePullSecrets.Name == existingSAPullSecret.Name { 75 | matches = true 76 | } 77 | } 78 | if !matches { 79 | return false 80 | } 81 | } 82 | 83 | return true 84 | } 85 | 86 | func metaMatches(existingMeta *metav1.ObjectMeta, requestedMeta *metav1.ObjectMeta) bool { 87 | if existingMeta.Name != requestedMeta.Name { 88 | return false 89 | } 90 | 91 | if existingMeta.Namespace != requestedMeta.Namespace { 92 | return false 93 | } 94 | 95 | if !ownerRefsMatch(&existingMeta.OwnerReferences, &requestedMeta.OwnerReferences) { 96 | return false 97 | } 98 | 99 | return true 100 | } 101 | 102 | func ownerRefsMatch(existingOwnerRefs *[]metav1.OwnerReference, requestedOwnerRefs *[]metav1.OwnerReference) bool { 103 | requested := *requestedOwnerRefs 104 | existing := *existingOwnerRefs 105 | 106 | if len(requested) != len(existing) { 107 | return false 108 | } 109 | 110 | for index, existingOwnerRef := range existing { 111 | if !ownerRefMatches(&existingOwnerRef, &requested[index]) { 112 | return false 113 | } 114 | } 115 | 116 | return true 117 | } 118 | 119 | func ownerRefMatches(existingOwnerRef *metav1.OwnerReference, requestedOwnerRef *metav1.OwnerReference) bool { 120 | if existingOwnerRef.Kind != requestedOwnerRef.Kind { 121 | return false 122 | } 123 | 124 | if existingOwnerRef.Name != requestedOwnerRef.Name { 125 | return false 126 | } 127 | 128 | if existingOwnerRef.APIVersion != requestedOwnerRef.APIVersion { 129 | return false 130 | } 131 | 132 | return true 133 | } 134 | 135 | func subjectsMatch(existingSubjects *[]rbacv1.Subject, requestedSubjects *[]rbacv1.Subject) bool { 136 | rSubjects := *requestedSubjects 137 | eSubjects := *existingSubjects 138 | 139 | if len(eSubjects) != len(rSubjects) { 140 | return false 141 | } 142 | 143 | for index, existingSubject := range eSubjects { 144 | if !subjectMatches(&existingSubject, &rSubjects[index]) { 145 | return false 146 | } 147 | } 148 | 149 | return true 150 | } 151 | 152 | func subjectMatches(existingSubject *rbacv1.Subject, requestedSubject *rbacv1.Subject) bool { 153 | if existingSubject.Kind != requestedSubject.Kind { 154 | return false 155 | } 156 | 157 | if existingSubject.Name != requestedSubject.Name { 158 | return false 159 | } 160 | 161 | if existingSubject.Namespace != requestedSubject.Namespace { 162 | return false 163 | } 164 | 165 | return true 166 | } 167 | 168 | func roleRefMatches(existingRoleRef *rbacv1.RoleRef, requestedRoleRef *rbacv1.RoleRef) bool { 169 | if existingRoleRef.Kind != requestedRoleRef.Kind { 170 | return false 171 | } 172 | 173 | if existingRoleRef.Name != requestedRoleRef.Name { 174 | return false 175 | } 176 | 177 | return true 178 | } 179 | -------------------------------------------------------------------------------- /pkg/reconciler/matcher_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 reconciler 16 | 17 | import ( 18 | "testing" 19 | 20 | v1 "k8s.io/api/core/v1" 21 | rbacv1 "k8s.io/api/rbac/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | 25 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 26 | ) 27 | 28 | func generateOwnerReferences(name string) []metav1.OwnerReference { 29 | rbacDef := rbacmanagerv1beta1.RBACDefinition{} 30 | rbacDef.Name = name 31 | 32 | return []metav1.OwnerReference{ 33 | *metav1.NewControllerRef(&rbacDef, schema.GroupVersionKind{ 34 | Group: rbacmanagerv1beta1.SchemeGroupVersion.Group, 35 | Version: rbacmanagerv1beta1.SchemeGroupVersion.Version, 36 | Kind: "RBACDefinition", 37 | }), 38 | } 39 | } 40 | func TestCrbMatches(t *testing.T) { 41 | subject1 := rbacv1.Subject{Kind: "User", Name: "joe"} 42 | subject2 := rbacv1.Subject{Kind: "User", Name: "sue"} 43 | subject3 := rbacv1.Subject{Kind: "ServiceAccount", Name: "ci"} 44 | 45 | labels1 := map[string]string{"rbac-manager": "fairwinds"} 46 | labels2 := map[string]string{"something": "else"} 47 | 48 | crb1 := rbacv1.ClusterRoleBinding{ 49 | ObjectMeta: metav1.ObjectMeta{ 50 | Name: "hello-world", 51 | OwnerReferences: []metav1.OwnerReference{}, 52 | Labels: labels1, 53 | }, 54 | RoleRef: rbacv1.RoleRef{ 55 | Kind: "ClusterRole", 56 | Name: "Admin", 57 | }, 58 | Subjects: []rbacv1.Subject{subject1, subject2, subject3}, 59 | } 60 | 61 | crb2 := rbacv1.ClusterRoleBinding{ 62 | ObjectMeta: metav1.ObjectMeta{ 63 | Name: "hello-other-world", 64 | OwnerReferences: generateOwnerReferences("foo"), 65 | Labels: labels2, 66 | }, 67 | RoleRef: rbacv1.RoleRef{ 68 | Kind: "ClusterRole", 69 | Name: "Admin", 70 | }, 71 | Subjects: []rbacv1.Subject{subject1, subject2, subject3}, 72 | } 73 | 74 | crb3 := rbacv1.ClusterRoleBinding{ 75 | ObjectMeta: metav1.ObjectMeta{ 76 | Name: "hello-other-world", 77 | OwnerReferences: generateOwnerReferences("bar"), 78 | Labels: labels1, 79 | }, 80 | RoleRef: rbacv1.RoleRef{ 81 | Kind: "ClusterRole", 82 | Name: "Admin", 83 | }, 84 | Subjects: []rbacv1.Subject{subject1, subject2, subject3}, 85 | } 86 | 87 | if crbMatches(&crb1, &crb2) { 88 | t.Fatal("CRB 1 should not match CRB 2") 89 | } 90 | 91 | if crbMatches(&crb1, &crb3) { 92 | t.Fatal("CRB 1 should not match CRB 3") 93 | } 94 | 95 | if crbMatches(&crb2, &crb3) { 96 | t.Fatal("CRB 2 should not match CRB 3") 97 | } 98 | 99 | if !crbMatches(&crb1, &crb1) { 100 | t.Fatal("CRB 1 should match CRB 1") 101 | } 102 | 103 | if !crbMatches(&crb2, &crb2) { 104 | t.Fatal("CRB 2 should match CRB 2") 105 | } 106 | 107 | if !crbMatches(&crb3, &crb3) { 108 | t.Fatal("CRB 3 should match CRB 3") 109 | } 110 | } 111 | 112 | func TestRbMatches(t *testing.T) { 113 | subject1 := rbacv1.Subject{Kind: "User", Name: "joe"} 114 | subject2 := rbacv1.Subject{Kind: "User", Name: "sue"} 115 | subject3 := rbacv1.Subject{Kind: "ServiceAccount", Name: "ci"} 116 | 117 | labels1 := map[string]string{"rbac-manager": "fairwinds"} 118 | labels2 := map[string]string{"something": "else"} 119 | 120 | rb1 := rbacv1.RoleBinding{ 121 | ObjectMeta: metav1.ObjectMeta{ 122 | Name: "hello-world", 123 | Namespace: "default", 124 | OwnerReferences: []metav1.OwnerReference{}, 125 | Labels: labels1, 126 | }, 127 | RoleRef: rbacv1.RoleRef{ 128 | Kind: "ClusterRole", 129 | Name: "Admin", 130 | }, 131 | Subjects: []rbacv1.Subject{subject1, subject2, subject3}, 132 | } 133 | 134 | rb2 := rbacv1.RoleBinding{ 135 | ObjectMeta: metav1.ObjectMeta{ 136 | Name: "hello-other-world", 137 | Namespace: "default", 138 | OwnerReferences: generateOwnerReferences("foo"), 139 | Labels: labels2, 140 | }, 141 | RoleRef: rbacv1.RoleRef{ 142 | Kind: "ClusterRole", 143 | Name: "Admin", 144 | }, 145 | Subjects: []rbacv1.Subject{subject1, subject2, subject3}, 146 | } 147 | 148 | rb3 := rbacv1.RoleBinding{ 149 | ObjectMeta: metav1.ObjectMeta{ 150 | Name: "hello-other-world", 151 | Namespace: "sample", 152 | OwnerReferences: generateOwnerReferences("bar"), 153 | Labels: labels1, 154 | }, 155 | RoleRef: rbacv1.RoleRef{ 156 | Kind: "ClusterRole", 157 | Name: "Admin", 158 | }, 159 | Subjects: []rbacv1.Subject{subject1, subject2, subject3}, 160 | } 161 | 162 | if rbMatches(&rb1, &rb2) { 163 | t.Fatal("RB 1 should not match RB 2") 164 | } 165 | 166 | if rbMatches(&rb1, &rb3) { 167 | t.Fatal("RB 1 should not match RB 3") 168 | } 169 | 170 | if rbMatches(&rb2, &rb3) { 171 | t.Fatal("RB 2 should not match RB 3") 172 | } 173 | 174 | if !rbMatches(&rb1, &rb1) { 175 | t.Fatal("RB 1 should match RB 1") 176 | } 177 | 178 | if !rbMatches(&rb2, &rb2) { 179 | t.Fatal("RB 2 should match RB 2") 180 | } 181 | 182 | if !rbMatches(&rb3, &rb3) { 183 | t.Fatal("RB 3 should match RB 3") 184 | } 185 | } 186 | 187 | func TestSAMatches(t *testing.T) { 188 | sa1 := v1.ServiceAccount{ 189 | ObjectMeta: metav1.ObjectMeta{ 190 | Name: "sample-name", 191 | Namespace: "sample", 192 | OwnerReferences: generateOwnerReferences("foo"), 193 | Annotations: map[string]string{ManagedPullSecretsAnnotationKey: "fairwinds"}, 194 | }, 195 | ImagePullSecrets: []v1.LocalObjectReference{{Name: "fairwinds"}}, 196 | } 197 | sa2 := v1.ServiceAccount{ 198 | ObjectMeta: metav1.ObjectMeta{ 199 | Name: "sample-name", 200 | Namespace: "sample", 201 | OwnerReferences: generateOwnerReferences("foo"), 202 | Annotations: map[string]string{ManagedPullSecretsAnnotationKey: "fairwinds"}, 203 | }, 204 | ImagePullSecrets: []v1.LocalObjectReference{{Name: "fairwinds"}}, 205 | } 206 | sa3 := v1.ServiceAccount{ 207 | ObjectMeta: metav1.ObjectMeta{ 208 | Name: "sample-name", 209 | Namespace: "sample", 210 | OwnerReferences: generateOwnerReferences("foo"), 211 | Annotations: map[string]string{ManagedPullSecretsAnnotationKey: "fairwinds,another"}, 212 | }, 213 | ImagePullSecrets: []v1.LocalObjectReference{{Name: "fairwinds"}, {Name: "another"}}, 214 | } 215 | sa4 := v1.ServiceAccount{ 216 | ObjectMeta: metav1.ObjectMeta{ 217 | Name: "sample-name", 218 | Namespace: "sample", 219 | OwnerReferences: generateOwnerReferences("foo"), 220 | Annotations: map[string]string{ManagedPullSecretsAnnotationKey: "fairwinds"}, 221 | }, 222 | ImagePullSecrets: []v1.LocalObjectReference{{Name: "fairwinds"}, {Name: "extra"}}, 223 | } 224 | sa5 := v1.ServiceAccount{ 225 | ObjectMeta: metav1.ObjectMeta{ 226 | Name: "sample-name", 227 | Namespace: "sample", 228 | OwnerReferences: generateOwnerReferences("foo"), 229 | Annotations: map[string]string{ManagedPullSecretsAnnotationKey: "fairwinds,another"}, 230 | }, 231 | ImagePullSecrets: []v1.LocalObjectReference{{Name: "fairwinds"}}, 232 | } 233 | 234 | if !saMatches(&sa1, &sa2) { 235 | t.Fatal("SA 1 should match SA 2") 236 | } 237 | 238 | if saMatches(&sa1, &sa3) { 239 | t.Fatal("SA 1 should not match SA 3") 240 | } 241 | 242 | if !saMatches(&sa4, &sa1) { 243 | t.Fatal("SA 4 should match SA 1") 244 | } 245 | 246 | if saMatches(&sa1, &sa5) { 247 | t.Fatal("SA 1 should not match SA 3") 248 | } 249 | 250 | if saMatches(&sa4, &sa3) { 251 | t.Fatal("SA 4 should not match SA 3") 252 | } 253 | 254 | if saMatches(&sa5, &sa3) { 255 | t.Fatal("SA 5 should not match SA 3") 256 | } 257 | 258 | if saMatches(&sa5, &sa4) { 259 | t.Fatal("SA 5 should not match SA 4") 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /pkg/reconciler/parser.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 reconciler 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/sirupsen/logrus" 24 | v1 "k8s.io/api/core/v1" 25 | rbacv1 "k8s.io/api/rbac/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/labels" 28 | "k8s.io/client-go/kubernetes" 29 | 30 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 31 | "github.com/fairwindsops/rbac-manager/pkg/kube" 32 | ) 33 | 34 | // Parser parses RBAC Definitions and determines the Kubernetes resources that it specifies 35 | type Parser struct { 36 | Clientset kubernetes.Interface 37 | ownerRefs []metav1.OwnerReference 38 | parsedClusterRoleBindings []rbacv1.ClusterRoleBinding 39 | parsedRoleBindings []rbacv1.RoleBinding 40 | parsedServiceAccounts []v1.ServiceAccount 41 | } 42 | 43 | const ManagedPullSecretsAnnotationKey string = "rbacmanager.reactiveops.io/managed-pull-secrets" 44 | 45 | // Parse determines the desired Kubernetes resources an RBAC Definition refers to 46 | func (p *Parser) Parse(rbacDef rbacmanagerv1beta1.RBACDefinition) error { 47 | if rbacDef.RBACBindings == nil { 48 | logrus.Warn("No RBACBindings defined") 49 | return nil 50 | } 51 | 52 | namespaces, err := p.Clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 53 | if err != nil { 54 | logrus.Debug("Error listing namespaces") 55 | return err 56 | } 57 | 58 | for _, rbacBinding := range rbacDef.RBACBindings { 59 | namePrefix := rdNamePrefix(&rbacDef, &rbacBinding) 60 | err := p.parseRBACBinding(rbacBinding, namePrefix, namespaces) 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func (p *Parser) parseRBACBinding(rbacBinding rbacmanagerv1beta1.RBACBinding, namePrefix string, namespaces *v1.NamespaceList) error { 70 | for _, requestedSubject := range rbacBinding.Subjects { 71 | if requestedSubject.Kind == "ServiceAccount" { 72 | pullsecrets := []v1.LocalObjectReference{} 73 | for _, secret := range requestedSubject.ImagePullSecrets { 74 | pullsecrets = append(pullsecrets, v1.LocalObjectReference{Name: secret}) 75 | } 76 | annotations := make(map[string]string) 77 | managedPullSecrets := strings.Join(requestedSubject.ImagePullSecrets, ",") 78 | annotations[ManagedPullSecretsAnnotationKey] = managedPullSecrets 79 | p.parsedServiceAccounts = append(p.parsedServiceAccounts, v1.ServiceAccount{ 80 | ObjectMeta: metav1.ObjectMeta{ 81 | Name: requestedSubject.Name, 82 | Namespace: requestedSubject.Namespace, 83 | OwnerReferences: p.ownerRefs, 84 | Labels: kube.Labels, 85 | Annotations: annotations, 86 | }, 87 | ImagePullSecrets: pullsecrets, 88 | AutomountServiceAccountToken: requestedSubject.AutomountServiceAccountToken, 89 | }) 90 | } 91 | } 92 | 93 | if rbacBinding.ClusterRoleBindings != nil { 94 | for _, requestedCRB := range rbacBinding.ClusterRoleBindings { 95 | err := p.parseClusterRoleBinding(requestedCRB, rbacBinding.Subjects, namePrefix) 96 | if err != nil { 97 | return err 98 | } 99 | } 100 | } 101 | 102 | if rbacBinding.RoleBindings != nil { 103 | for _, requestedRB := range rbacBinding.RoleBindings { 104 | err := p.parseRoleBinding(requestedRB, rbacBinding.Subjects, namePrefix, namespaces) 105 | if err != nil { 106 | return err 107 | } 108 | } 109 | } 110 | return nil 111 | } 112 | 113 | func (p *Parser) parseClusterRoleBinding( 114 | crb rbacmanagerv1beta1.ClusterRoleBinding, subjects []rbacmanagerv1beta1.Subject, prefix string) error { 115 | crbName := fmt.Sprintf("%v-%v", prefix, crb.ClusterRole) 116 | subs := managerSubjectsToRbacSubjects(subjects) 117 | 118 | p.parsedClusterRoleBindings = append(p.parsedClusterRoleBindings, rbacv1.ClusterRoleBinding{ 119 | ObjectMeta: metav1.ObjectMeta{ 120 | Name: crbName, 121 | OwnerReferences: p.ownerRefs, 122 | Labels: kube.Labels, 123 | }, 124 | RoleRef: rbacv1.RoleRef{ 125 | Kind: "ClusterRole", 126 | Name: crb.ClusterRole, 127 | }, 128 | Subjects: subs, 129 | }) 130 | 131 | return nil 132 | } 133 | 134 | func (p *Parser) parseRoleBinding( 135 | rb rbacmanagerv1beta1.RoleBinding, subjects []rbacmanagerv1beta1.Subject, prefix string, namespaces *v1.NamespaceList) error { 136 | 137 | objectMeta := metav1.ObjectMeta{ 138 | OwnerReferences: p.ownerRefs, 139 | Labels: kube.Labels, 140 | } 141 | 142 | var requestedRoleName string 143 | var roleRef rbacv1.RoleRef 144 | 145 | if rb.ClusterRole != "" { 146 | logrus.Debugf("Processing Requested ClusterRole %v <> %v <> %v", rb.ClusterRole, rb.Namespace, rb) 147 | requestedRoleName = rb.ClusterRole 148 | roleRef = rbacv1.RoleRef{ 149 | Kind: "ClusterRole", 150 | Name: rb.ClusterRole, 151 | } 152 | } else if rb.Role != "" { 153 | logrus.Debugf("Processing Requested Role %v <> %v <> %v", rb.Role, rb.Namespace, rb) 154 | requestedRoleName = fmt.Sprintf("%v-%v", rb.Role, rb.Namespace) 155 | roleRef = rbacv1.RoleRef{ 156 | Kind: "Role", 157 | Name: rb.Role, 158 | } 159 | } else { 160 | return errors.New("Invalid role binding, role or clusterRole required") 161 | } 162 | 163 | objectMeta.Name = fmt.Sprintf("%v-%v", prefix, requestedRoleName) 164 | 165 | if rb.NamespaceSelector.MatchLabels != nil || len(rb.NamespaceSelector.MatchExpressions) > 0 { 166 | logrus.Debugf("Processing Namespace Selector %v", rb.NamespaceSelector) 167 | 168 | selector, err := metav1.LabelSelectorAsSelector(&rb.NamespaceSelector) 169 | if err != nil { 170 | logrus.Infof("Error parsing label selector: %s", err.Error()) 171 | return err 172 | } 173 | 174 | for _, namespace := range namespaces.Items { 175 | // Lazy way to marshal map[] of labels in to a Set, which we can then match on. 176 | if selector.Matches(labels.Merge(namespace.Labels, namespace.Labels)) { 177 | logrus.Debugf("Adding Role Binding With Dynamic Namespace %v", namespace.Name) 178 | 179 | om := objectMeta 180 | om.Namespace = namespace.Name 181 | subs := managerSubjectsToRbacSubjects(subjects) 182 | 183 | p.parsedRoleBindings = append(p.parsedRoleBindings, rbacv1.RoleBinding{ 184 | ObjectMeta: om, 185 | RoleRef: roleRef, 186 | Subjects: subs, 187 | }) 188 | } 189 | } 190 | 191 | } else if rb.Namespace != "" { 192 | objectMeta.Namespace = rb.Namespace 193 | subs := managerSubjectsToRbacSubjects(subjects) 194 | 195 | p.parsedRoleBindings = append(p.parsedRoleBindings, rbacv1.RoleBinding{ 196 | ObjectMeta: objectMeta, 197 | RoleRef: roleRef, 198 | Subjects: subs, 199 | }) 200 | 201 | } else { 202 | return errors.New("Invalid role binding, namespace or namespace selector required") 203 | } 204 | 205 | return nil 206 | } 207 | 208 | func (p *Parser) hasNamespaceSelectors(rbacDef *rbacmanagerv1beta1.RBACDefinition) bool { 209 | for _, rbacBinding := range rbacDef.RBACBindings { 210 | for _, roleBinding := range rbacBinding.RoleBindings { 211 | if roleBinding.Namespace == "" { 212 | // Split these up instead of using || so we can test both paths. 213 | if roleBinding.NamespaceSelector.MatchLabels != nil { 214 | return true 215 | } 216 | if roleBinding.NamespaceSelector.MatchExpressions != nil { 217 | return true 218 | } 219 | } 220 | } 221 | } 222 | return false 223 | } 224 | 225 | func (p *Parser) parseClusterRoleBindings(rbacDef *rbacmanagerv1beta1.RBACDefinition) { 226 | for _, rbacBinding := range rbacDef.RBACBindings { 227 | for _, clusterRoleBinding := range rbacBinding.ClusterRoleBindings { 228 | namePrefix := rdNamePrefix(rbacDef, &rbacBinding) 229 | _ = p.parseClusterRoleBinding(clusterRoleBinding, rbacBinding.Subjects, namePrefix) 230 | } 231 | } 232 | } 233 | 234 | func (p *Parser) parseRoleBindings(rbacDef *rbacmanagerv1beta1.RBACDefinition, namespaces *v1.NamespaceList) { 235 | for _, rbacBinding := range rbacDef.RBACBindings { 236 | for _, roleBinding := range rbacBinding.RoleBindings { 237 | namePrefix := rdNamePrefix(rbacDef, &rbacBinding) 238 | _ = p.parseRoleBinding(roleBinding, rbacBinding.Subjects, namePrefix, namespaces) 239 | } 240 | } 241 | } 242 | 243 | func rdNamePrefix(rbacDef *rbacmanagerv1beta1.RBACDefinition, rbacBinding *rbacmanagerv1beta1.RBACBinding) string { 244 | return fmt.Sprintf("%v-%v", rbacDef.Name, rbacBinding.Name) 245 | } 246 | 247 | func managerSubjectsToRbacSubjects(subjects []rbacmanagerv1beta1.Subject) []rbacv1.Subject { 248 | var subs []rbacv1.Subject 249 | for _, sub := range subjects { 250 | subs = append(subs, rbacv1.Subject{ 251 | Kind: sub.Kind, 252 | APIGroup: sub.APIGroup, 253 | Name: sub.Name, 254 | Namespace: sub.Namespace, 255 | }) 256 | } 257 | return subs 258 | } 259 | -------------------------------------------------------------------------------- /pkg/reconciler/parser_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 reconciler 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | corev1 "k8s.io/api/core/v1" 23 | rbacv1 "k8s.io/api/rbac/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/client-go/kubernetes/fake" 26 | 27 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 28 | ) 29 | 30 | func TestParseEmpty(t *testing.T) { 31 | client := fake.NewSimpleClientset() 32 | testEmpty(t, client, "empty-example") 33 | } 34 | 35 | func TestParseStandard(t *testing.T) { 36 | client := fake.NewSimpleClientset() 37 | rbacDef := rbacmanagerv1beta1.RBACDefinition{} 38 | rbacDef.Name = "rbac-config" 39 | 40 | createNamespace(t, client, "web", map[string]string{"app": "web", "team": "devs"}) 41 | createNamespace(t, client, "api", map[string]string{"app": "api", "team": "devs"}) 42 | createNamespace(t, client, "db", map[string]string{"app": "db", "team": "db"}) 43 | 44 | rbacDef.RBACBindings = []rbacmanagerv1beta1.RBACBinding{{ 45 | Name: "ci-bot", 46 | Subjects: []rbacmanagerv1beta1.Subject{{ 47 | Subject: rbacv1.Subject{ 48 | Kind: rbacv1.ServiceAccountKind, 49 | Name: "ci-bot", 50 | Namespace: "bots", 51 | }, 52 | }}, 53 | RoleBindings: []rbacmanagerv1beta1.RoleBinding{{ 54 | Namespace: "bots", 55 | Role: "custom", 56 | }}, 57 | }, { 58 | Name: "devs", 59 | Subjects: []rbacmanagerv1beta1.Subject{{ 60 | Subject: rbacv1.Subject{ 61 | Kind: rbacv1.UserKind, 62 | Name: "joe", 63 | }, 64 | }, { 65 | Subject: rbacv1.Subject{ 66 | Kind: rbacv1.UserKind, 67 | Name: "sue", 68 | }, 69 | }}, 70 | RoleBindings: []rbacmanagerv1beta1.RoleBinding{{ 71 | NamespaceSelector: metav1.LabelSelector{ 72 | MatchLabels: map[string]string{"team": "devs"}, 73 | }, 74 | ClusterRole: "edit", 75 | }}, 76 | }} 77 | 78 | newParseTest(t, client, rbacDef, []rbacv1.RoleBinding{{ 79 | ObjectMeta: metav1.ObjectMeta{ 80 | Name: "rbac-config-ci-bot-custom-bots", 81 | Namespace: "bots", 82 | }, 83 | RoleRef: rbacv1.RoleRef{ 84 | Kind: "Role", 85 | Name: "custom", 86 | }, 87 | Subjects: []rbacv1.Subject{{ 88 | Kind: rbacv1.ServiceAccountKind, 89 | Name: "ci-bot", 90 | Namespace: "bots", 91 | }}, 92 | }, { 93 | ObjectMeta: metav1.ObjectMeta{ 94 | Name: "rbac-config-devs-edit", 95 | Namespace: "web", 96 | }, 97 | RoleRef: rbacv1.RoleRef{ 98 | Kind: "ClusterRole", 99 | Name: "edit", 100 | }, 101 | Subjects: []rbacv1.Subject{{ 102 | Kind: rbacv1.UserKind, 103 | Name: "joe", 104 | }, { 105 | Kind: rbacv1.UserKind, 106 | Name: "sue", 107 | }}, 108 | }, { 109 | ObjectMeta: metav1.ObjectMeta{ 110 | Name: "rbac-config-devs-edit", 111 | Namespace: "api", 112 | }, 113 | RoleRef: rbacv1.RoleRef{ 114 | Kind: "ClusterRole", 115 | Name: "edit", 116 | }, 117 | Subjects: []rbacv1.Subject{{ 118 | Kind: rbacv1.UserKind, 119 | Name: "joe", 120 | }, { 121 | Kind: rbacv1.UserKind, 122 | Name: "sue", 123 | }}, 124 | }}, []rbacv1.ClusterRoleBinding{}, []corev1.ServiceAccount{{ 125 | ObjectMeta: metav1.ObjectMeta{ 126 | Name: "ci-bot", 127 | Namespace: "bots", 128 | }, 129 | }}) 130 | } 131 | 132 | func TestParseLabels(t *testing.T) { 133 | client := fake.NewSimpleClientset() 134 | rbacDef := rbacmanagerv1beta1.RBACDefinition{} 135 | rbacDef.Name = "rbac-config" 136 | 137 | createNamespace(t, client, "web", map[string]string{"app": "web", "team": "devs"}) 138 | createNamespace(t, client, "api", map[string]string{"app": "api", "team": "devs"}) 139 | 140 | rbacDef.RBACBindings = []rbacmanagerv1beta1.RBACBinding{{ 141 | Name: "devs", 142 | Subjects: []rbacmanagerv1beta1.Subject{{ 143 | Subject: rbacv1.Subject{ 144 | Kind: rbacv1.UserKind, 145 | Name: "sue", 146 | }, 147 | }}, 148 | RoleBindings: []rbacmanagerv1beta1.RoleBinding{{ 149 | NamespaceSelector: metav1.LabelSelector{ 150 | MatchLabels: map[string]string{"team": "devs"}, 151 | }, 152 | ClusterRole: "edit", 153 | }}, 154 | }} 155 | 156 | // api and web edit access 157 | newParseTest(t, client, rbacDef, []rbacv1.RoleBinding{{ 158 | ObjectMeta: metav1.ObjectMeta{ 159 | Name: "rbac-config-devs-edit", 160 | Namespace: "web", 161 | }, 162 | RoleRef: rbacv1.RoleRef{ 163 | Kind: "ClusterRole", 164 | Name: "edit", 165 | }, 166 | Subjects: []rbacv1.Subject{{ 167 | Kind: rbacv1.UserKind, 168 | Name: "sue", 169 | }}, 170 | }, { 171 | ObjectMeta: metav1.ObjectMeta{ 172 | Name: "rbac-config-devs-edit", 173 | Namespace: "api", 174 | }, 175 | RoleRef: rbacv1.RoleRef{ 176 | Kind: "ClusterRole", 177 | Name: "edit", 178 | }, 179 | Subjects: []rbacv1.Subject{{ 180 | Kind: rbacv1.UserKind, 181 | Name: "sue", 182 | }}, 183 | }}, []rbacv1.ClusterRoleBinding{}, []corev1.ServiceAccount{}) 184 | 185 | rbacDef.RBACBindings[0].RoleBindings[0].NamespaceSelector = metav1.LabelSelector{ 186 | MatchLabels: map[string]string{"team": "devs"}, 187 | MatchExpressions: []metav1.LabelSelectorRequirement{{ 188 | Key: "app", 189 | Operator: metav1.LabelSelectorOpIn, 190 | Values: []string{"web", "queue"}, 191 | }}, 192 | } 193 | 194 | // api edit access 195 | newParseTest(t, client, rbacDef, []rbacv1.RoleBinding{{ 196 | ObjectMeta: metav1.ObjectMeta{ 197 | Name: "rbac-config-devs-edit", 198 | Namespace: "web", 199 | }, 200 | RoleRef: rbacv1.RoleRef{ 201 | Kind: "ClusterRole", 202 | Name: "edit", 203 | }, 204 | Subjects: []rbacv1.Subject{{ 205 | Kind: rbacv1.UserKind, 206 | Name: "sue", 207 | }}, 208 | }}, []rbacv1.ClusterRoleBinding{}, []corev1.ServiceAccount{}) 209 | 210 | rbacDef.RBACBindings[0].RoleBindings[0].NamespaceSelector = metav1.LabelSelector{ 211 | MatchExpressions: []metav1.LabelSelectorRequirement{{ 212 | Key: "app", 213 | Operator: metav1.LabelSelectorOpNotIn, 214 | Values: []string{"api", "queue"}, 215 | }}, 216 | } 217 | 218 | // web edit access 219 | newParseTest(t, client, rbacDef, []rbacv1.RoleBinding{{ 220 | ObjectMeta: metav1.ObjectMeta{ 221 | Name: "rbac-config-devs-edit", 222 | Namespace: "web", 223 | }, 224 | RoleRef: rbacv1.RoleRef{ 225 | Kind: "ClusterRole", 226 | Name: "edit", 227 | }, 228 | Subjects: []rbacv1.Subject{{ 229 | Kind: rbacv1.UserKind, 230 | Name: "sue", 231 | }}, 232 | }}, []rbacv1.ClusterRoleBinding{}, []corev1.ServiceAccount{}) 233 | 234 | } 235 | 236 | func TestParseMissingNamespace(t *testing.T) { 237 | client := fake.NewSimpleClientset() 238 | rbacDef := rbacmanagerv1beta1.RBACDefinition{} 239 | rbacDef.Name = "rbac-config" 240 | 241 | createNamespace(t, client, "web", map[string]string{"app": "web", "team": "devs"}) 242 | createNamespace(t, client, "api", map[string]string{"app": "api", "team": "devs"}) 243 | createNamespace(t, client, "db", map[string]string{"app": "db", "team": "db"}) 244 | 245 | rbacDef.RBACBindings = []rbacmanagerv1beta1.RBACBinding{{ 246 | Name: "devs", 247 | Subjects: []rbacmanagerv1beta1.Subject{ 248 | { 249 | Subject: rbacv1.Subject{ 250 | Kind: rbacv1.UserKind, 251 | Name: "joe", 252 | }, 253 | }, 254 | { 255 | Subject: rbacv1.Subject{ 256 | Kind: rbacv1.UserKind, 257 | Name: "sue", 258 | }, 259 | }, 260 | }, 261 | RoleBindings: []rbacmanagerv1beta1.RoleBinding{{ 262 | NamespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"team": "other-devs"}}, 263 | ClusterRole: "edit", 264 | }}, 265 | }} 266 | 267 | newParseTest(t, client, rbacDef, []rbacv1.RoleBinding{}, []rbacv1.ClusterRoleBinding{}, []corev1.ServiceAccount{}) 268 | } 269 | 270 | func TestManagerToRbacSubjects(t *testing.T) { 271 | expected := []rbacv1.Subject{ 272 | { 273 | Kind: rbacv1.ServiceAccountKind, 274 | Name: "robot", 275 | Namespace: "default", 276 | }, 277 | } 278 | subjects := []rbacmanagerv1beta1.Subject{ 279 | { 280 | Subject: expected[0], 281 | }, 282 | } 283 | actual := managerSubjectsToRbacSubjects(subjects) 284 | assert.ElementsMatch(t, expected, actual, "expected subjects to match") 285 | } 286 | 287 | func TestServiceAccountParsing(t *testing.T) { 288 | for _, n := range saTestCases { 289 | t.Run(n.name, func(t *testing.T) { 290 | client := fake.NewSimpleClientset() 291 | rbacDef := rbacmanagerv1beta1.RBACDefinition{} 292 | rbacDef.Name = "rbac-config" 293 | 294 | rbacDef.RBACBindings = []rbacmanagerv1beta1.RBACBinding{{ 295 | Name: "devs", 296 | Subjects: n.given, 297 | ClusterRoleBindings: []rbacmanagerv1beta1.ClusterRoleBinding{}, 298 | }} 299 | 300 | newParseTest(t, client, rbacDef, []rbacv1.RoleBinding{}, []rbacv1.ClusterRoleBinding{}, n.expected) 301 | }) 302 | } 303 | } 304 | 305 | func newParseTest(t *testing.T, client *fake.Clientset, rbacDef rbacmanagerv1beta1.RBACDefinition, expectedRb []rbacv1.RoleBinding, expectedCrb []rbacv1.ClusterRoleBinding, expectedSa []corev1.ServiceAccount) { 306 | p := Parser{Clientset: client} 307 | 308 | err := p.Parse(rbacDef) 309 | if err != nil { 310 | t.Logf("Error parsing RBAC Definition: %v", err) 311 | } 312 | 313 | expectParsedRB(t, p, expectedRb) 314 | expectParsedCRB(t, p, expectedCrb) 315 | expectParsedSA(t, p, expectedSa) 316 | } 317 | 318 | func testEmpty(t *testing.T, client *fake.Clientset, name string) { 319 | rbacDef := rbacmanagerv1beta1.RBACDefinition{} 320 | rbacDef.Name = name 321 | rbacDef.RBACBindings = []rbacmanagerv1beta1.RBACBinding{} 322 | 323 | newParseTest(t, client, rbacDef, []rbacv1.RoleBinding{}, []rbacv1.ClusterRoleBinding{}, []corev1.ServiceAccount{}) 324 | } 325 | 326 | func expectParsedCRB(t *testing.T, p Parser, expected []rbacv1.ClusterRoleBinding) { 327 | assert.Len(t, p.parsedClusterRoleBindings, len(expected), "Expected length to match") 328 | 329 | for _, expectedCrb := range expected { 330 | matchFound := false 331 | for _, actualCrb := range p.parsedClusterRoleBindings { 332 | if actualCrb.Name == expectedCrb.Name { 333 | matchFound = true 334 | assert.ElementsMatch(t, expectedCrb.Subjects, actualCrb.Subjects, "Expected subjects to match") 335 | assert.EqualValues(t, expectedCrb.RoleRef, actualCrb.RoleRef, "Expected role ref to match") 336 | break 337 | } 338 | } 339 | 340 | if !matchFound { 341 | t.Fatalf("Matching cluster role binding not found for %v", expectedCrb.Name) 342 | } 343 | } 344 | } 345 | 346 | func expectParsedRB(t *testing.T, p Parser, expected []rbacv1.RoleBinding) { 347 | assert.Len(t, p.parsedRoleBindings, len(expected), "Expected length to match") 348 | 349 | for _, expectedRb := range expected { 350 | matchFound := false 351 | for _, actualRb := range p.parsedRoleBindings { 352 | if actualRb.Name == expectedRb.Name && expectedRb.Namespace == actualRb.Namespace { 353 | matchFound = true 354 | assert.ElementsMatch(t, expectedRb.Subjects, actualRb.Subjects, "Expected subjects to match") 355 | assert.EqualValues(t, expectedRb.RoleRef, actualRb.RoleRef, "Expected role ref to match") 356 | break 357 | } 358 | } 359 | 360 | if !matchFound { 361 | t.Fatalf("Matching role binding not found for %v", expectedRb.Name) 362 | } 363 | } 364 | } 365 | 366 | func expectParsedSA(t *testing.T, p Parser, expected []corev1.ServiceAccount) { 367 | assert.Len(t, p.parsedServiceAccounts, len(expected), "Expected length to match") 368 | 369 | for _, expectedSa := range expected { 370 | matchFound := false 371 | for _, actualSa := range p.parsedServiceAccounts { 372 | if actualSa.Name == expectedSa.Name && expectedSa.Namespace == actualSa.Namespace { 373 | matchFound = true 374 | assert.ElementsMatch(t, actualSa.ImagePullSecrets, expectedSa.ImagePullSecrets) 375 | assert.EqualValues(t, expectedSa.AutomountServiceAccountToken, actualSa.AutomountServiceAccountToken) 376 | break 377 | } 378 | } 379 | 380 | if !matchFound { 381 | t.Fatalf("Matching service account not found for %s in namespace %s", expectedSa.Name, expectedSa.Namespace) 382 | } 383 | } 384 | } 385 | 386 | func createNamespace(t *testing.T, client *fake.Clientset, name string, labels map[string]string) { 387 | _, err := client.CoreV1().Namespaces().Create( 388 | context.TODO(), 389 | &corev1.Namespace{ 390 | ObjectMeta: metav1.ObjectMeta{ 391 | Name: name, 392 | Labels: labels, 393 | }, 394 | }, 395 | metav1.CreateOptions{}, 396 | ) 397 | 398 | if err != nil { 399 | t.Fatalf("Error creating namespace %v", err) 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /pkg/reconciler/reconciler.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 reconciler 16 | 17 | import ( 18 | "context" 19 | "reflect" 20 | "sync" 21 | 22 | "github.com/sirupsen/logrus" 23 | v1 "k8s.io/api/core/v1" 24 | rbacv1 "k8s.io/api/rbac/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime/schema" 27 | "k8s.io/client-go/kubernetes" 28 | 29 | rbacmanagerv1beta1 "github.com/fairwindsops/rbac-manager/pkg/apis/rbacmanager/v1beta1" 30 | "github.com/fairwindsops/rbac-manager/pkg/kube" 31 | "github.com/fairwindsops/rbac-manager/pkg/metrics" 32 | ) 33 | 34 | // Reconciler creates and deletes Kubernetes resources to achieve the desired state of an RBAC Definition 35 | type Reconciler struct { 36 | Clientset kubernetes.Interface 37 | ownerRefs []metav1.OwnerReference 38 | } 39 | 40 | var mux = sync.Mutex{} 41 | 42 | // ReconcileNamespaceChange reconciles relevant portions of RBAC Definitions 43 | // 44 | // after changes to namespaces within the cluster 45 | func (r *Reconciler) ReconcileNamespaceChange(rbacDef *rbacmanagerv1beta1.RBACDefinition, namespace *v1.Namespace) error { 46 | mux.Lock() 47 | defer mux.Unlock() 48 | 49 | r.ownerRefs = rbacDefOwnerRefs(rbacDef) 50 | 51 | p := Parser{ 52 | Clientset: r.Clientset, 53 | ownerRefs: r.ownerRefs, 54 | } 55 | 56 | err := p.Parse(*rbacDef) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | err = r.reconcileServiceAccounts(&p.parsedServiceAccounts) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | if p.hasNamespaceSelectors(rbacDef) { 67 | logrus.Infof("Reconciling %v namespace for %v", namespace.Name, rbacDef.Name) 68 | err := r.reconcileRoleBindings(&p.parsedRoleBindings) 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // ReconcileOwners reconciles any RBACDefinitions found in owner references 78 | func (r *Reconciler) ReconcileOwners(ownerRefs []metav1.OwnerReference, kind string) error { 79 | mux.Lock() 80 | defer mux.Unlock() 81 | 82 | namespaces, err := r.Clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 83 | if err != nil { 84 | logrus.Debug("Error listing namespaces") 85 | return err 86 | } 87 | 88 | for _, ownerRef := range ownerRefs { 89 | if ownerRef.Kind == "RBACDefinition" { 90 | rbacDef, err := kube.GetRbacDefinition(ownerRef.Name) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | r.ownerRefs = rbacDefOwnerRefs(&rbacDef) 96 | 97 | p := Parser{ 98 | Clientset: r.Clientset, 99 | ownerRefs: r.ownerRefs, 100 | } 101 | 102 | if kind == "RoleBinding" { 103 | p.parseRoleBindings(&rbacDef, namespaces) 104 | return r.reconcileRoleBindings(&p.parsedRoleBindings) 105 | } else if kind == "ClusterRoleBinding" { 106 | p.parseClusterRoleBindings(&rbacDef) 107 | return r.reconcileClusterRoleBindings(&p.parsedClusterRoleBindings) 108 | } else if kind == "ServiceAccount" { 109 | err := p.Parse(rbacDef) 110 | if err != nil { 111 | return err 112 | } 113 | return r.reconcileServiceAccounts(&p.parsedServiceAccounts) 114 | } 115 | } 116 | } 117 | return nil 118 | } 119 | 120 | // Reconcile creates, updates, or deletes Kubernetes resources to match 121 | // 122 | // the desired state defined in an RBAC Definition 123 | func (r *Reconciler) Reconcile(rbacDef *rbacmanagerv1beta1.RBACDefinition) error { 124 | mux.Lock() 125 | defer mux.Unlock() 126 | 127 | logrus.Infof("Reconciling RBACDefinition %v", rbacDef.Name) 128 | 129 | r.ownerRefs = rbacDefOwnerRefs(rbacDef) 130 | 131 | p := Parser{ 132 | Clientset: r.Clientset, 133 | ownerRefs: r.ownerRefs, 134 | } 135 | 136 | var err error 137 | 138 | err = p.Parse(*rbacDef) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | err = r.reconcileServiceAccounts(&p.parsedServiceAccounts) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | err = r.reconcileClusterRoleBindings(&p.parsedClusterRoleBindings) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | err = r.reconcileRoleBindings(&p.parsedRoleBindings) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | return nil 159 | } 160 | 161 | func (r *Reconciler) reconcileServiceAccounts(requested *[]v1.ServiceAccount) error { 162 | existing, err := r.Clientset.CoreV1().ServiceAccounts("").List(context.TODO(), kube.ListOptions) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | matchingServiceAccounts := []v1.ServiceAccount{} 168 | serviceAccountsToCreate := []v1.ServiceAccount{} 169 | 170 | for _, requestedSA := range *requested { 171 | alreadyExists := false 172 | for _, existingSA := range existing.Items { 173 | if saMatches(&existingSA, &requestedSA) { 174 | alreadyExists = true 175 | matchingServiceAccounts = append(matchingServiceAccounts, existingSA) 176 | break 177 | } 178 | } 179 | 180 | if !alreadyExists { 181 | serviceAccountsToCreate = append(serviceAccountsToCreate, requestedSA) 182 | } else { 183 | logrus.Debugf("Service Account already exists %v", requestedSA.Name) 184 | } 185 | } 186 | 187 | for _, existingSA := range existing.Items { 188 | if reflect.DeepEqual(existingSA.ObjectMeta.OwnerReferences, r.ownerRefs) { 189 | matchingRequest := false 190 | for _, matchingSA := range matchingServiceAccounts { 191 | if saMatches(&existingSA, &matchingSA) { 192 | matchingRequest = true 193 | break 194 | } 195 | } 196 | 197 | if !matchingRequest { 198 | logrus.Infof("Deleting Service Account %v", existingSA.Name) 199 | err := r.Clientset.CoreV1().ServiceAccounts(existingSA.Namespace).Delete(context.TODO(), existingSA.Name, metav1.DeleteOptions{}) 200 | if err != nil { 201 | logrus.Infof("Error deleting Service Account: %v", err) 202 | metrics.ErrorCounter.Inc() 203 | } else { 204 | metrics.ChangeCounter.WithLabelValues("serviceaccounts", "delete").Inc() 205 | } 206 | } else { 207 | logrus.Debugf("Matches requested Service Account %v", existingSA.Name) 208 | } 209 | } 210 | } 211 | 212 | for _, serviceAccountToCreate := range serviceAccountsToCreate { 213 | logrus.Infof("Creating Service Account: %v", serviceAccountToCreate.Name) 214 | _, err := r.Clientset.CoreV1().ServiceAccounts(serviceAccountToCreate.ObjectMeta.Namespace).Create(context.TODO(), &serviceAccountToCreate, metav1.CreateOptions{}) 215 | if err != nil { 216 | logrus.Errorf("Error creating Service Account: %v", err) 217 | metrics.ErrorCounter.Inc() 218 | } else { 219 | metrics.ChangeCounter.WithLabelValues("serviceaccounts", "create").Inc() 220 | } 221 | } 222 | 223 | return nil 224 | } 225 | 226 | func (r *Reconciler) reconcileClusterRoleBindings(requested *[]rbacv1.ClusterRoleBinding) error { 227 | existing, err := r.Clientset.RbacV1().ClusterRoleBindings().List(context.TODO(), kube.ListOptions) 228 | if err != nil { 229 | metrics.ErrorCounter.Inc() 230 | return err 231 | } 232 | 233 | matchingClusterRoleBindings := []rbacv1.ClusterRoleBinding{} 234 | clusterRoleBindingsToCreate := []rbacv1.ClusterRoleBinding{} 235 | 236 | for _, requestedCRB := range *requested { 237 | alreadyExists := false 238 | for _, existingCRB := range existing.Items { 239 | if crbMatches(&existingCRB, &requestedCRB) { 240 | alreadyExists = true 241 | matchingClusterRoleBindings = append(matchingClusterRoleBindings, existingCRB) 242 | break 243 | } 244 | } 245 | 246 | if !alreadyExists { 247 | clusterRoleBindingsToCreate = append(clusterRoleBindingsToCreate, requestedCRB) 248 | } else { 249 | logrus.Debugf("Cluster Role Binding already exists %v", requestedCRB.Name) 250 | } 251 | } 252 | 253 | for _, existingCRB := range existing.Items { 254 | if reflect.DeepEqual(existingCRB.OwnerReferences, r.ownerRefs) { 255 | matchingRequest := false 256 | for _, requestedCRB := range matchingClusterRoleBindings { 257 | if crbMatches(&existingCRB, &requestedCRB) { 258 | matchingRequest = true 259 | break 260 | } 261 | } 262 | 263 | if !matchingRequest { 264 | logrus.Infof("Deleting Cluster Role Binding: %v", existingCRB.Name) 265 | err := r.Clientset.RbacV1().ClusterRoleBindings().Delete(context.TODO(), existingCRB.Name, metav1.DeleteOptions{}) 266 | if err != nil { 267 | logrus.Errorf("Error deleting Cluster Role Binding: %v", err) 268 | metrics.ErrorCounter.Inc() 269 | } else { 270 | metrics.ChangeCounter.WithLabelValues("clusterrolebindings", "delete").Inc() 271 | } 272 | } else { 273 | logrus.Debugf("Matches requested Cluster Role Binding: %v", existingCRB.Name) 274 | } 275 | } 276 | } 277 | 278 | for _, clusterRoleBindingToCreate := range clusterRoleBindingsToCreate { 279 | logrus.Infof("Creating Cluster Role Binding: %v", clusterRoleBindingToCreate.Name) 280 | _, err := r.Clientset.RbacV1().ClusterRoleBindings().Create(context.TODO(), &clusterRoleBindingToCreate, metav1.CreateOptions{}) 281 | if err != nil { 282 | logrus.Errorf("Error creating Cluster Role Binding: %v", err) 283 | metrics.ErrorCounter.Inc() 284 | } else { 285 | metrics.ChangeCounter.WithLabelValues("clusterrolebindings", "create").Inc() 286 | } 287 | } 288 | 289 | return nil 290 | } 291 | 292 | func (r *Reconciler) reconcileRoleBindings(requested *[]rbacv1.RoleBinding) error { 293 | existing, err := r.Clientset.RbacV1().RoleBindings("").List(context.TODO(), kube.ListOptions) 294 | if err != nil { 295 | return err 296 | } 297 | 298 | matchingRoleBindings := []rbacv1.RoleBinding{} 299 | roleBindingsToCreate := []rbacv1.RoleBinding{} 300 | 301 | for _, requestedRB := range *requested { 302 | alreadyExists := false 303 | for _, existingRB := range existing.Items { 304 | if rbMatches(&existingRB, &requestedRB) { 305 | alreadyExists = true 306 | matchingRoleBindings = append(matchingRoleBindings, existingRB) 307 | break 308 | } 309 | } 310 | 311 | if !alreadyExists { 312 | roleBindingsToCreate = append(roleBindingsToCreate, requestedRB) 313 | } else { 314 | logrus.Debugf("Role Binding already exists %v", requestedRB.Name) 315 | } 316 | } 317 | 318 | for _, existingRB := range existing.Items { 319 | if reflect.DeepEqual(existingRB.OwnerReferences, r.ownerRefs) { 320 | matchingRequest := false 321 | for _, requestedRB := range matchingRoleBindings { 322 | if rbMatches(&existingRB, &requestedRB) { 323 | matchingRequest = true 324 | break 325 | } 326 | } 327 | 328 | if !matchingRequest { 329 | logrus.Infof("Deleting Role Binding %v", existingRB.Name) 330 | err := r.Clientset.RbacV1().RoleBindings(existingRB.Namespace).Delete(context.TODO(), existingRB.Name, metav1.DeleteOptions{}) 331 | if err != nil { 332 | logrus.Infof("Error deleting Role Binding: %v", err) 333 | metrics.ErrorCounter.Inc() 334 | } else { 335 | metrics.ChangeCounter.WithLabelValues("rolebindings", "delete").Inc() 336 | } 337 | } else { 338 | logrus.Debugf("Matches requested Role Binding %v", existingRB.Name) 339 | } 340 | } 341 | } 342 | 343 | for _, roleBindingToCreate := range roleBindingsToCreate { 344 | logrus.Infof("Creating Role Binding: %v", roleBindingToCreate.Name) 345 | _, err := r.Clientset.RbacV1().RoleBindings(roleBindingToCreate.ObjectMeta.Namespace).Create(context.TODO(), &roleBindingToCreate, metav1.CreateOptions{}) 346 | if err != nil { 347 | logrus.Errorf("Error creating Role Binding: %v", err) 348 | metrics.ErrorCounter.Inc() 349 | } else { 350 | metrics.ChangeCounter.WithLabelValues("rolebindings", "create").Inc() 351 | } 352 | } 353 | 354 | return nil 355 | } 356 | 357 | func rbacDefOwnerRefs(rbacDef *rbacmanagerv1beta1.RBACDefinition) []metav1.OwnerReference { 358 | return []metav1.OwnerReference{ 359 | *metav1.NewControllerRef(rbacDef, schema.GroupVersionKind{ 360 | Group: rbacmanagerv1beta1.SchemeGroupVersion.Group, 361 | Version: rbacmanagerv1beta1.SchemeGroupVersion.Version, 362 | Kind: "RBACDefinition", 363 | }), 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /pkg/watcher/clusterrolebinding.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package watcher 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/sirupsen/logrus" 23 | 24 | rbacv1 "k8s.io/api/rbac/v1" 25 | "k8s.io/apimachinery/pkg/util/runtime" 26 | "k8s.io/apimachinery/pkg/watch" 27 | "k8s.io/client-go/kubernetes" 28 | 29 | "github.com/fairwindsops/rbac-manager/pkg/kube" 30 | "github.com/fairwindsops/rbac-manager/pkg/reconciler" 31 | ) 32 | 33 | func watchClusterRoleBindings(clientset *kubernetes.Clientset) { 34 | watcher, err := clientset.RbacV1().ClusterRoleBindings().Watch(context.TODO(), kube.ListOptions) 35 | 36 | if err != nil { 37 | logrus.Error(err, "unable to watch Cluster Role Bindings") 38 | runtime.HandleError(err) 39 | } 40 | 41 | ch := watcher.ResultChan() 42 | 43 | for event := range ch { 44 | crb, ok := event.Object.(*rbacv1.ClusterRoleBinding) 45 | if !ok { 46 | logrus.Error("Could not parse Cluster Role Binding") 47 | } else if event.Type == watch.Modified || event.Type == watch.Deleted { 48 | logrus.Debugf("Reconciling RBACDefinition for %s ClusterRoleBinding after %s event", crb.Name, event.Type) 49 | r := reconciler.Reconciler{Clientset: kube.GetClientsetOrDie()} 50 | _ = r.ReconcileOwners(crb.OwnerReferences, "ClusterRoleBinding") 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/watcher/rolebinding.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package watcher 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/sirupsen/logrus" 23 | rbacv1 "k8s.io/api/rbac/v1" 24 | "k8s.io/apimachinery/pkg/util/runtime" 25 | "k8s.io/apimachinery/pkg/watch" 26 | "k8s.io/client-go/kubernetes" 27 | 28 | "github.com/fairwindsops/rbac-manager/pkg/kube" 29 | "github.com/fairwindsops/rbac-manager/pkg/reconciler" 30 | ) 31 | 32 | func watchRoleBindings(clientset *kubernetes.Clientset) { 33 | watcher, err := clientset.RbacV1().RoleBindings("").Watch(context.TODO(), kube.ListOptions) 34 | 35 | if err != nil { 36 | logrus.Error(err, "unable to watch Role Bindings") 37 | runtime.HandleError(err) 38 | } 39 | 40 | ch := watcher.ResultChan() 41 | 42 | for event := range ch { 43 | rb, ok := event.Object.(*rbacv1.RoleBinding) 44 | if !ok { 45 | logrus.Error("Could not parse Role Binding") 46 | } else if event.Type == watch.Modified || event.Type == watch.Deleted { 47 | logrus.Debugf("Reconciling RBACDefinition for %s RoleBinding after %s event", rb.Name, event.Type) 48 | r := reconciler.Reconciler{Clientset: kube.GetClientsetOrDie()} 49 | _ = r.ReconcileOwners(rb.OwnerReferences, "RoleBinding") 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/watcher/serviceaccount.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package watcher 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/sirupsen/logrus" 23 | corev1 "k8s.io/api/core/v1" 24 | "k8s.io/apimachinery/pkg/util/runtime" 25 | "k8s.io/apimachinery/pkg/watch" 26 | "k8s.io/client-go/kubernetes" 27 | 28 | "github.com/fairwindsops/rbac-manager/pkg/kube" 29 | "github.com/fairwindsops/rbac-manager/pkg/reconciler" 30 | ) 31 | 32 | func watchServiceAccounts(clientset *kubernetes.Clientset) { 33 | watcher, err := clientset.CoreV1().ServiceAccounts("").Watch(context.TODO(), kube.ListOptions) 34 | 35 | if err != nil { 36 | logrus.Error(err, "unable to watch Service Accounts") 37 | runtime.HandleError(err) 38 | } 39 | 40 | ch := watcher.ResultChan() 41 | 42 | for event := range ch { 43 | sa, ok := event.Object.(*corev1.ServiceAccount) 44 | if !ok { 45 | logrus.Error("Could not parse Service Account") 46 | } else if event.Type == watch.Modified || event.Type == watch.Deleted { 47 | logrus.Debugf("Reconciling RBACDefinition for %s ServiceAccount after %s event", sa.Name, event.Type) 48 | r := reconciler.Reconciler{Clientset: kube.GetClientsetOrDie()} 49 | _ = r.ReconcileOwners(sa.OwnerReferences, "ServiceAccount") 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/watcher/shared.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 FairwindsOps Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package watcher 18 | 19 | import ( 20 | "github.com/fairwindsops/rbac-manager/pkg/kube" 21 | ) 22 | 23 | // WatchRelatedResources watches all resources owned by RBAC Definitions 24 | func WatchRelatedResources() { 25 | clientset := kube.GetClientsetOrDie() 26 | go watchClusterRoleBindings(clientset) 27 | go watchRoleBindings(clientset) 28 | go watchServiceAccounts(clientset) 29 | } 30 | -------------------------------------------------------------------------------- /version/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 version 16 | 17 | var ( 18 | // Version represents the current version of RBAC Manager 19 | Version = "VERSION" 20 | ) 21 | --------------------------------------------------------------------------------