├── .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 |
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 |
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 |
2 |
3 |
4 |
13 |
14 |
15 |
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 |
--------------------------------------------------------------------------------