├── .deps ├── k3d.yaml ├── kubectl.yaml └── kustomize.yaml ├── .docker ├── Dockerfile-build ├── Dockerfile-kubebuilder └── Dockerfile-release ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── BUG-REPORT.yml │ ├── DESIGN-DOC.yml │ ├── FEATURE-REQUEST.yml │ └── config.yml ├── actions │ └── deps-setup │ │ └── action.yaml ├── auto_assign.yml ├── config.yml ├── pull_request_template.md └── workflows │ ├── ci.yaml │ ├── closed_references.yml │ ├── conventional_commits.yml │ ├── cve-scan.yaml │ ├── format.yml │ ├── labels.yml │ ├── licenses.yml │ └── stale.yml ├── .gitignore ├── .goreleaser.yml ├── .grype.yaml ├── .prettierignore ├── .reference-ignore ├── .reports └── dep-licenses.csv ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── SECURITY.md ├── api └── v1alpha1 │ ├── defaults.go │ ├── groupversion_info.go │ ├── json.go │ ├── rule_json.go │ ├── rule_types.go │ ├── rule_types_test.go │ ├── tests │ ├── rules.json │ ├── sample_config.json │ └── sample_config2.json │ └── zz_generated.deepcopy.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── oathkeeper.ory.sh_rules.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_rules.yaml │ │ └── webhook_in_rules.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_image_patch.yaml │ ├── manager_image_patch.yaml-e │ ├── manager_prometheus_metrics_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── rbac │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ └── role_binding.yaml ├── samples │ └── oathkeeper_v1alpha1_rule.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── controllers ├── operators.go ├── rule_controller.go └── rule_controller_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── internal └── validation │ ├── validation.go │ └── validation_test.go ├── main.go ├── package-lock.json ├── package.json └── tests └── integration ├── README.md ├── files ├── rule1.json └── rule2.json ├── integration_suite_test.go ├── retry.go ├── rules_test.go └── validation.go /.deps/k3d.yaml: -------------------------------------------------------------------------------- 1 | version: v5.4.9 2 | url: https://github.com/rancher/k3d/releases/download/{{.Version}}/k3d-{{.Os}}-{{.Architecture}} 3 | -------------------------------------------------------------------------------- /.deps/kubectl.yaml: -------------------------------------------------------------------------------- 1 | version: v1.26.5 2 | url: https://storage.googleapis.com/kubernetes-release/release/{{.Version}}/bin/{{.Os}}/{{.Architecture}}/kubectl 3 | -------------------------------------------------------------------------------- /.deps/kustomize.yaml: -------------------------------------------------------------------------------- 1 | version: v5.0.3 2 | url: https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F{{.Version}}/kustomize_{{.Version}}_{{.Os}}_{{.Architecture}}.tar.gz 3 | -------------------------------------------------------------------------------- /.docker/Dockerfile-build: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.23 as builder 3 | WORKDIR /go/src/app 4 | COPY . . 5 | RUN make manager 6 | 7 | # Use distroless as minimal base image to package the manager binary 8 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 9 | FROM gcr.io/distroless/static:nonroot 10 | WORKDIR / 11 | COPY --from=builder /go/src/app/manager . 12 | USER 65532:65532 13 | 14 | ENTRYPOINT ["/manager"] 15 | -------------------------------------------------------------------------------- /.docker/Dockerfile-kubebuilder: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 as builder 2 | WORKDIR /go/src/app 3 | 4 | ENV PATH=$PATH:/go/src/app/.bin 5 | 6 | COPY . . 7 | RUN make test &&\ 8 | make manager 9 | 10 | # Use distroless as minimal base image to package the manager binary 11 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 12 | FROM gcr.io/distroless/static:nonroot 13 | WORKDIR / 14 | COPY --from=builder /go/src/app/manager . 15 | 16 | ENTRYPOINT ["/manager"] 17 | -------------------------------------------------------------------------------- /.docker/Dockerfile-release: -------------------------------------------------------------------------------- 1 | # Use distroless as minimal base image to package the manager binary 2 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 3 | FROM gcr.io/distroless/static:nonroot 4 | WORKDIR / 5 | COPY manager . 6 | USER 65532:65532 7 | 8 | ENTRYPOINT ["/manager"] 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @piotrmsc @Demonsthere 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/FUNDING.yml 3 | 4 | # These are supported funding model platforms 5 | 6 | # github: 7 | patreon: _ory 8 | open_collective: ory 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG-REPORT.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/BUG-REPORT.yml 3 | 4 | description: "Create a bug report" 5 | labels: 6 | - bug 7 | name: "Bug Report" 8 | body: 9 | - attributes: 10 | value: "Thank you for taking the time to fill out this bug report!\n" 11 | type: markdown 12 | - attributes: 13 | label: "Preflight checklist" 14 | options: 15 | - label: 16 | "I could not find a solution in the existing issues, docs, nor 17 | discussions." 18 | required: true 19 | - label: 20 | "I agree to follow this project's [Code of 21 | Conduct](https://github.com/ory/oathkeeper-maester/blob/master/CODE_OF_CONDUCT.md)." 22 | required: true 23 | - label: 24 | "I have read and am following this repository's [Contribution 25 | Guidelines](https://github.com/ory/oathkeeper-maester/blob/master/CONTRIBUTING.md)." 26 | required: true 27 | - label: 28 | "I have joined the [Ory Community Slack](https://slack.ory.sh)." 29 | - label: 30 | "I am signed up to the [Ory Security Patch 31 | Newsletter](https://www.ory.sh/l/sign-up-newsletter)." 32 | id: checklist 33 | type: checkboxes 34 | - attributes: 35 | description: 36 | "Enter the slug or API URL of the affected Ory Network project. Leave 37 | empty when you are self-hosting." 38 | label: "Ory Network Project" 39 | placeholder: "https://.projects.oryapis.com" 40 | id: ory-network-project 41 | type: input 42 | - attributes: 43 | description: "A clear and concise description of what the bug is." 44 | label: "Describe the bug" 45 | placeholder: "Tell us what you see!" 46 | id: describe-bug 47 | type: textarea 48 | validations: 49 | required: true 50 | - attributes: 51 | description: | 52 | Clear, formatted, and easy to follow steps to reproduce the behavior: 53 | placeholder: | 54 | Steps to reproduce the behavior: 55 | 56 | 1. Run `docker run ....` 57 | 2. Make API Request to with `curl ...` 58 | 3. Request fails with response: `{"some": "error"}` 59 | label: "Reproducing the bug" 60 | id: reproduce-bug 61 | type: textarea 62 | validations: 63 | required: true 64 | - attributes: 65 | description: 66 | "Please copy and paste any relevant log output. This will be 67 | automatically formatted into code, so no need for backticks. Please 68 | redact any sensitive information" 69 | label: "Relevant log output" 70 | render: shell 71 | placeholder: | 72 | log=error .... 73 | id: logs 74 | type: textarea 75 | - attributes: 76 | description: 77 | "Please copy and paste any relevant configuration. This will be 78 | automatically formatted into code, so no need for backticks. Please 79 | redact any sensitive information!" 80 | label: "Relevant configuration" 81 | render: yml 82 | placeholder: | 83 | server: 84 | admin: 85 | port: 1234 86 | id: config 87 | type: textarea 88 | - attributes: 89 | description: "What version of our software are you running?" 90 | label: Version 91 | id: version 92 | type: input 93 | validations: 94 | required: true 95 | - attributes: 96 | label: "On which operating system are you observing this issue?" 97 | options: 98 | - Ory Network 99 | - macOS 100 | - Linux 101 | - Windows 102 | - FreeBSD 103 | - Other 104 | id: operating-system 105 | type: dropdown 106 | - attributes: 107 | label: "In which environment are you deploying?" 108 | options: 109 | - Ory Network 110 | - Docker 111 | - "Docker Compose" 112 | - "Kubernetes with Helm" 113 | - Kubernetes 114 | - Binary 115 | - Other 116 | id: deployment 117 | type: dropdown 118 | - attributes: 119 | description: "Add any other context about the problem here." 120 | label: Additional Context 121 | id: additional 122 | type: textarea 123 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/DESIGN-DOC.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/DESIGN-DOC.yml 3 | 4 | description: 5 | "A design document is needed for non-trivial changes to the code base." 6 | labels: 7 | - rfc 8 | name: "Design Document" 9 | body: 10 | - attributes: 11 | value: | 12 | Thank you for writing this design document. 13 | 14 | One of the key elements of Ory's software engineering culture is the use of defining software designs through design docs. These are relatively informal documents that the primary author or authors of a software system or application create before they embark on the coding project. The design doc documents the high level implementation strategy and key design decisions with emphasis on the trade-offs that were considered during those decisions. 15 | 16 | Ory is leaning heavily on [Google's design docs process](https://www.industrialempathy.com/posts/design-docs-at-google/) 17 | and [Golang Proposals](https://github.com/golang/proposal). 18 | 19 | Writing a design doc before contributing your change ensures that your ideas are checked with 20 | the community and maintainers. It will save you a lot of time developing things that might need to be changed 21 | after code reviews, and your pull requests will be merged faster. 22 | type: markdown 23 | - attributes: 24 | label: "Preflight checklist" 25 | options: 26 | - label: 27 | "I could not find a solution in the existing issues, docs, nor 28 | discussions." 29 | required: true 30 | - label: 31 | "I agree to follow this project's [Code of 32 | Conduct](https://github.com/ory/oathkeeper-maester/blob/master/CODE_OF_CONDUCT.md)." 33 | required: true 34 | - label: 35 | "I have read and am following this repository's [Contribution 36 | Guidelines](https://github.com/ory/oathkeeper-maester/blob/master/CONTRIBUTING.md)." 37 | required: true 38 | - label: 39 | "I have joined the [Ory Community Slack](https://slack.ory.sh)." 40 | - label: 41 | "I am signed up to the [Ory Security Patch 42 | Newsletter](https://www.ory.sh/l/sign-up-newsletter)." 43 | id: checklist 44 | type: checkboxes 45 | - attributes: 46 | description: 47 | "Enter the slug or API URL of the affected Ory Network project. Leave 48 | empty when you are self-hosting." 49 | label: "Ory Network Project" 50 | placeholder: "https://.projects.oryapis.com" 51 | id: ory-network-project 52 | type: input 53 | - attributes: 54 | description: | 55 | This section gives the reader a very rough overview of the landscape in which the new system is being built and what is actually being built. This isn’t a requirements doc. Keep it succinct! The goal is that readers are brought up to speed but some previous knowledge can be assumed and detailed info can be linked to. This section should be entirely focused on objective background facts. 56 | label: "Context and scope" 57 | id: scope 58 | type: textarea 59 | validations: 60 | required: true 61 | 62 | - attributes: 63 | description: | 64 | A short list of bullet points of what the goals of the system are, and, sometimes more importantly, what non-goals are. Note, that non-goals aren’t negated goals like “The system shouldn’t crash”, but rather things that could reasonably be goals, but are explicitly chosen not to be goals. A good example would be “ACID compliance”; when designing a database, you’d certainly want to know whether that is a goal or non-goal. And if it is a non-goal you might still select a solution that provides it, if it doesn’t introduce trade-offs that prevent achieving the goals. 65 | label: "Goals and non-goals" 66 | id: goals 67 | type: textarea 68 | validations: 69 | required: true 70 | 71 | - attributes: 72 | description: | 73 | This section should start with an overview and then go into details. 74 | The design doc is the place to write down the trade-offs you made in designing your software. Focus on those trade-offs to produce a useful document with long-term value. That is, given the context (facts), goals and non-goals (requirements), the design doc is the place to suggest solutions and show why a particular solution best satisfies those goals. 75 | 76 | The point of writing a document over a more formal medium is to provide the flexibility to express the problem at hand in an appropriate manner. Because of this, there is no explicit guidance on how to actually describe the design. 77 | label: "The design" 78 | id: design 79 | type: textarea 80 | validations: 81 | required: true 82 | 83 | - attributes: 84 | description: | 85 | If the system under design exposes an API, then sketching out that API is usually a good idea. In most cases, however, one should withstand the temptation to copy-paste formal interface or data definitions into the doc as these are often verbose, contain unnecessary detail and quickly get out of date. Instead, focus on the parts that are relevant to the design and its trade-offs. 86 | label: "APIs" 87 | id: apis 88 | type: textarea 89 | 90 | - attributes: 91 | description: | 92 | Systems that store data should likely discuss how and in what rough form this happens. Similar to the advice on APIs, and for the same reasons, copy-pasting complete schema definitions should be avoided. Instead, focus on the parts that are relevant to the design and its trade-offs. 93 | label: "Data storage" 94 | id: persistence 95 | type: textarea 96 | 97 | - attributes: 98 | description: | 99 | Design docs should rarely contain code, or pseudo-code except in situations where novel algorithms are described. As appropriate, link to prototypes that show the feasibility of the design. 100 | label: "Code and pseudo-code" 101 | id: pseudocode 102 | type: textarea 103 | 104 | - attributes: 105 | description: | 106 | One of the primary factors that would influence the shape of a software design and hence the design doc, is the degree of constraint of the solution space. 107 | 108 | On one end of the extreme is the “greenfield software project”, where all we know are the goals, and the solution can be whatever makes the most sense. Such a document may be wide-ranging, but it also needs to quickly define a set of rules that allow zooming in on a manageable set of solutions. 109 | 110 | On the other end are systems where the possible solutions are very well defined, but it isn't at all obvious how they could even be combined to achieve the goals. This may be a legacy system that is difficult to change and wasn't designed to do what you want it to do or a library design that needs to operate within the constraints of the host programming language. 111 | 112 | In this situation, you may be able to enumerate all the things you can do relatively easily, but you need to creatively put those things together to achieve the goals. There may be multiple solutions, and none of them are great, and hence such a document should focus on selecting the best way given all identified trade-offs. 113 | label: "Degree of constraint" 114 | id: constrait 115 | type: textarea 116 | 117 | - attributes: 118 | description: | 119 | This section lists alternative designs that would have reasonably achieved similar outcomes. The focus should be on the trade-offs that each respective design makes and how those trade-offs led to the decision to select the design that is the primary topic of the document. 120 | 121 | While it is fine to be succinct about a solution that ended up not being selected, this section is one of the most important ones as it shows very explicitly why the selected solution is the best given the project goals and how other solutions, that the reader may be wondering about, introduce trade-offs that are less desirable given the goals. 122 | 123 | label: Alternatives considered 124 | id: alternatives 125 | type: textarea 126 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml 3 | 4 | description: 5 | "Suggest an idea for this project without a plan for implementation" 6 | labels: 7 | - feat 8 | name: "Feature Request" 9 | body: 10 | - attributes: 11 | value: | 12 | Thank you for suggesting an idea for this project! 13 | 14 | If you already have a plan to implement a feature or a change, please create a [design document](https://github.com/aeneasr/gh-template-test/issues/new?assignees=&labels=rfc&template=DESIGN-DOC.yml) instead if the change is non-trivial! 15 | type: markdown 16 | - attributes: 17 | label: "Preflight checklist" 18 | options: 19 | - label: 20 | "I could not find a solution in the existing issues, docs, nor 21 | discussions." 22 | required: true 23 | - label: 24 | "I agree to follow this project's [Code of 25 | Conduct](https://github.com/ory/oathkeeper-maester/blob/master/CODE_OF_CONDUCT.md)." 26 | required: true 27 | - label: 28 | "I have read and am following this repository's [Contribution 29 | Guidelines](https://github.com/ory/oathkeeper-maester/blob/master/CONTRIBUTING.md)." 30 | required: true 31 | - label: 32 | "I have joined the [Ory Community Slack](https://slack.ory.sh)." 33 | - label: 34 | "I am signed up to the [Ory Security Patch 35 | Newsletter](https://www.ory.sh/l/sign-up-newsletter)." 36 | id: checklist 37 | type: checkboxes 38 | - attributes: 39 | description: 40 | "Enter the slug or API URL of the affected Ory Network project. Leave 41 | empty when you are self-hosting." 42 | label: "Ory Network Project" 43 | placeholder: "https://.projects.oryapis.com" 44 | id: ory-network-project 45 | type: input 46 | - attributes: 47 | description: 48 | "Is your feature request related to a problem? Please describe." 49 | label: "Describe your problem" 50 | placeholder: 51 | "A clear and concise description of what the problem is. Ex. I'm always 52 | frustrated when [...]" 53 | id: problem 54 | type: textarea 55 | validations: 56 | required: true 57 | - attributes: 58 | description: | 59 | Describe the solution you'd like 60 | placeholder: | 61 | A clear and concise description of what you want to happen. 62 | label: "Describe your ideal solution" 63 | id: solution 64 | type: textarea 65 | validations: 66 | required: true 67 | - attributes: 68 | description: "Describe alternatives you've considered" 69 | label: "Workarounds or alternatives" 70 | id: alternatives 71 | type: textarea 72 | validations: 73 | required: true 74 | - attributes: 75 | description: "What version of our software are you running?" 76 | label: Version 77 | id: version 78 | type: input 79 | validations: 80 | required: true 81 | - attributes: 82 | description: 83 | "Add any other context or screenshots about the feature request here." 84 | label: Additional Context 85 | id: additional 86 | type: textarea 87 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/config.yml 3 | 4 | blank_issues_enabled: false 5 | contact_links: 6 | - name: Ory Ory Oathkeeper Maester Forum 7 | url: https://github.com/orgs/ory/discussions 8 | about: 9 | Please ask and answer questions here, show your implementations and 10 | discuss ideas. 11 | - name: Ory Chat 12 | url: https://www.ory.sh/chat 13 | about: 14 | Hang out with other Ory community members to ask and answer questions. 15 | -------------------------------------------------------------------------------- /.github/actions/deps-setup/action.yaml: -------------------------------------------------------------------------------- 1 | name: "Dependencies setup" 2 | description: "Sets up dependencies, uses cache to speedup execution" 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Extract branch name 7 | shell: bash 8 | run: | 9 | echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >> "$GITHUB_ENV" 10 | id: extract_branch 11 | 12 | - uses: actions/cache@v3 13 | id: cache-packages 14 | with: 15 | path: | 16 | ~/go/pkg/mod 17 | ~/go/bin 18 | ~/.config/helm 19 | ~/.local/share/helm 20 | ~/.cache/helm 21 | ${{ github.workspace }}/.bin 22 | key: 23 | ${{ runner.os }}-${{ steps.extract_branch.outputs.branch }}-${{ 24 | hashFiles('**/go.sum', '.deps/*') }} 25 | restore-keys: | 26 | ${{ runner.os }}-${{ steps.extract_branch.outputs.branch }}- 27 | 28 | - name: Setup dependencies 29 | if: steps.cache-packages.outputs.cache-hit != 'true' 30 | shell: bash 31 | env: 32 | HELM_INSTALL_DIR: ${{ github.workspace }}/.bin 33 | HELM_PLUGINS: ${{ github.workspace }}/.bin/plugins 34 | K3D_INSTALL_DIR: ${{ github.workspace }}/.bin 35 | run: | 36 | #Export .bin into PATH so k3d doesn't fail when installing 37 | export PATH=".bin:$PATH" 38 | echo "PATH=.bin:$PATH" >> $GITHUB_ENV 39 | make deps 40 | -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/auto_assign.yml 3 | 4 | # Set to true to add reviewers to pull requests 5 | addReviewers: true 6 | 7 | # Set to true to add assignees to pull requests 8 | addAssignees: true 9 | 10 | # A list of reviewers to be added to pull requests (GitHub user name) 11 | assignees: 12 | - ory/maintainers 13 | 14 | # A number of reviewers added to the pull request 15 | # Set 0 to add all the reviewers (default: 0) 16 | numberOfReviewers: 0 17 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/config.yml 3 | 4 | todo: 5 | keyword: "@todo" 6 | label: todo 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | ## Related Issue or Design Document 14 | 15 | 29 | 30 | ## Checklist 31 | 32 | 36 | 37 | - [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md) and signed the CLA. 38 | - [ ] I have referenced an issue containing the design document if my change introduces a new feature. 39 | - [ ] I have read the [security policy](../security/policy). 40 | - [ ] I confirm that this pull request does not address a security vulnerability. 41 | If this pull request addresses a security vulnerability, 42 | I confirm that I got approval (please contact [security@ory.sh](mailto:security@ory.sh)) from the maintainers to push the changes. 43 | - [ ] I have added tests that prove my fix is effective or that my feature works. 44 | - [ ] I have added the necessary documentation within the code base (if appropriate). 45 | 46 | ## Further comments 47 | 48 | 52 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - "master" 6 | tags: 7 | - "v*" 8 | pull_request: 9 | 10 | concurrency: 11 | group: ci-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | dependencies: 16 | name: Prepare Dependencies 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | - uses: actions/setup-go@v3 22 | with: 23 | go-version: "1.23" 24 | - name: Setup dependencies 25 | uses: ./.github/actions/deps-setup 26 | 27 | detect-repo-changes: 28 | name: Detected Repo Changes 29 | runs-on: ubuntu-latest 30 | outputs: 31 | code-changed: ${{ steps.filter.outputs.code }} 32 | dockerfile-changed: ${{ steps.filter.outputs.docker }} 33 | cicd-definition-changed: ${{ steps.filter.outputs.cicd-definitions }} 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v3 37 | - uses: dorny/paths-filter@v2.11.1 38 | id: filter 39 | with: 40 | base: master 41 | filters: | 42 | code: 43 | - 'api/**' 44 | - 'config/**' 45 | - 'controllers/**' 46 | - 'internal/**' 47 | - 'go.mod' 48 | - 'go.sum' 49 | - '*.go' 50 | - 'PROJECT' 51 | docker: 52 | - '.docker/**' 53 | cicd-definitions: 54 | - '.github/workflows/**' 55 | - '.github/actions/**' 56 | 57 | gha-lint: 58 | name: Lint GithubAction files 59 | if: | 60 | needs.detect-repo-changes.outputs.cicd-definition-changed == 'true' 61 | needs: 62 | - detect-repo-changes 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Checkout 66 | uses: actions/checkout@v3 67 | - name: actionlint 68 | id: actionlint 69 | uses: raven-actions/actionlint@v1 70 | with: 71 | fail-on-error: true 72 | 73 | test-build: 74 | name: Compile and test 75 | runs-on: ubuntu-latest 76 | if: | 77 | needs.detect-repo-changes.outputs.code-changed == 'true' || 78 | github.ref_type == 'tag' 79 | needs: 80 | - detect-repo-changes 81 | - dependencies 82 | steps: 83 | - name: Checkout 84 | uses: actions/checkout@v3 85 | - name: Checkout dependencies 86 | uses: ./.github/actions/deps-setup 87 | - name: Build 88 | run: make manager 89 | - name: Test 90 | run: make test 91 | 92 | test-integration: 93 | name: Run integration tests 94 | runs-on: ubuntu-latest 95 | if: | 96 | needs.detect-repo-changes.outputs.code-changed == 'true' || 97 | needs.detect-repo-changes.outputs.dockerfile-changed == 'true' || 98 | github.ref_type == 'tag' 99 | needs: 100 | - detect-repo-changes 101 | - dependencies 102 | steps: 103 | - name: Checkout 104 | uses: actions/checkout@v3 105 | - name: Checkout dependencies 106 | uses: ./.github/actions/deps-setup 107 | - uses: actions/setup-go@v4 108 | with: 109 | go-version: "1.23" 110 | cache: false 111 | - name: Test 112 | run: make test-integration 113 | 114 | test-docker: 115 | name: Build docker image 116 | runs-on: ubuntu-latest 117 | if: | 118 | needs.detect-repo-changes.outputs.dockerfile-changed == 'true' || 119 | github.ref_type == 'tag' 120 | needs: 121 | - detect-repo-changes 122 | - dependencies 123 | steps: 124 | - name: Checkout 125 | uses: actions/checkout@v3 126 | - name: Checkout dependencies 127 | uses: ./.github/actions/deps-setup 128 | - name: Build image 129 | run: make docker-build-notest 130 | 131 | release: 132 | if: ${{ github.ref_type == 'tag' }} 133 | needs: 134 | - test-build 135 | - test-integration 136 | - test-docker 137 | runs-on: ubuntu-latest 138 | steps: 139 | - name: Checkout 140 | uses: actions/checkout@v3 141 | - name: Checkout dependencies 142 | uses: ./.github/actions/deps-setup 143 | - name: Set up Go 144 | uses: actions/setup-go@v4 145 | - name: Login to Docker Hub 146 | uses: docker/login-action@v3 147 | with: 148 | username: ${{ secrets.DOCKERHUB_USERNAME }} 149 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 150 | - name: Run GoReleaser 151 | uses: goreleaser/goreleaser-action@v6 152 | with: 153 | distribution: goreleaser 154 | version: latest 155 | args: release --clean 156 | env: 157 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 158 | -------------------------------------------------------------------------------- /.github/workflows/closed_references.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/closed_references.yml 3 | 4 | name: Closed Reference Notifier 5 | 6 | on: 7 | schedule: 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | inputs: 11 | issueLimit: 12 | description: Max. number of issues to create 13 | required: true 14 | default: "5" 15 | 16 | jobs: 17 | find_closed_references: 18 | if: github.repository_owner == 'ory' 19 | runs-on: ubuntu-latest 20 | name: Find closed references 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/setup-node@v2-beta 24 | with: 25 | node-version: "14" 26 | - uses: ory/closed-reference-notifier@v1 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | issueLabels: upstream,good first issue,help wanted 30 | issueLimit: ${{ github.event.inputs.issueLimit || '5' }} 31 | -------------------------------------------------------------------------------- /.github/workflows/conventional_commits.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/conventional_commits.yml 3 | 4 | name: Conventional commits 5 | 6 | # This GitHub CI Action enforces that pull request titles follow conventional commits. 7 | # More info at https://www.conventionalcommits.org. 8 | # 9 | # The Ory-wide defaults for commit titles and scopes are below. 10 | # Your repository can add/replace elements via a configuration file at the path below. 11 | # More info at https://github.com/ory/ci/blob/master/conventional_commit_config/README.md 12 | 13 | on: 14 | pull_request_target: 15 | types: 16 | - edited 17 | - opened 18 | - ready_for_review 19 | - reopened 20 | # pull_request: # for debugging, uses config in local branch but supports only Pull Requests from this repo 21 | 22 | jobs: 23 | main: 24 | name: Validate PR title 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - id: config 29 | uses: ory/ci/conventional_commit_config@master 30 | with: 31 | config_path: .github/conventional_commits.json 32 | default_types: | 33 | feat 34 | fix 35 | revert 36 | docs 37 | style 38 | refactor 39 | test 40 | build 41 | autogen 42 | security 43 | ci 44 | chore 45 | default_scopes: | 46 | deps 47 | docs 48 | default_require_scope: false 49 | - uses: amannn/action-semantic-pull-request@v4 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | types: ${{ steps.config.outputs.types }} 54 | scopes: ${{ steps.config.outputs.scopes }} 55 | requireScope: ${{ steps.config.outputs.requireScope }} 56 | subjectPattern: ^(?![A-Z]).+$ 57 | subjectPatternError: | 58 | The subject should start with a lowercase letter, yours is uppercase: 59 | "{subject}" 60 | -------------------------------------------------------------------------------- /.github/workflows/cve-scan.yaml: -------------------------------------------------------------------------------- 1 | name: Docker Image Scanners 2 | on: 3 | push: 4 | branches: 5 | - "master" 6 | tags: 7 | - "v*.*.*" 8 | pull_request: 9 | branches: 10 | - "master" 11 | 12 | jobs: 13 | scanners: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | - uses: actions/setup-go@v4 19 | name: Setup Golang 20 | with: 21 | go-version: "1.23" 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v3 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v3 26 | - name: Build images 27 | shell: bash 28 | run: | 29 | make docker-build-notest 30 | - name: Anchore Scanner 31 | uses: anchore/scan-action@v3 32 | id: grype-scan 33 | with: 34 | image: controller:latest 35 | fail-build: true 36 | severity-cutoff: high 37 | debug: false 38 | acs-report-enable: true 39 | - name: Inspect action SARIF report 40 | shell: bash 41 | if: ${{ always() }} 42 | run: | 43 | echo "::group::Anchore Scan Details" 44 | jq '.runs[0].results' ${{ steps.grype-scan.outputs.sarif }} 45 | echo "::endgroup::" 46 | - name: Trivy Scanner 47 | uses: aquasecurity/trivy-action@master 48 | if: ${{ always() }} 49 | with: 50 | image-ref: controller:latest 51 | format: "table" 52 | exit-code: "42" 53 | ignore-unfixed: true 54 | vuln-type: "os,library" 55 | severity: "CRITICAL,HIGH" 56 | - name: Dockle Linter 57 | uses: erzz/dockle-action@v1.1.1 58 | if: ${{ always() }} 59 | with: 60 | image: controller:latest 61 | exit-code: 42 62 | failure-threshold: fatal 63 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | format: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-go@v4 13 | with: 14 | go-version: "1.23" 15 | - run: make format 16 | - name: Indicate formatting issues 17 | run: git diff HEAD --exit-code --color 18 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/labels.yml 3 | 4 | name: Synchronize Issue Labels 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | milestone: 14 | if: github.repository_owner == 'ory' 15 | name: Synchronize Issue Labels 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | - name: Synchronize Issue Labels 21 | uses: ory/label-sync-action@v0 22 | with: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | dry: false 25 | forced: true 26 | -------------------------------------------------------------------------------- /.github/workflows/licenses.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/licenses.yml 3 | 4 | name: Licenses 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | - v3 12 | - master 13 | 14 | jobs: 15 | licenses: 16 | name: License compliance 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Install script 20 | uses: ory/ci/licenses/setup@master 21 | with: 22 | token: ${{ secrets.ORY_BOT_PAT || secrets.GITHUB_TOKEN }} 23 | - name: Check licenses 24 | uses: ory/ci/licenses/check@master 25 | - name: Write, commit, push licenses 26 | uses: ory/ci/licenses/write@master 27 | if: 28 | ${{ github.ref == 'refs/heads/main' || github.ref == 29 | 'refs/heads/master' || github.ref == 'refs/heads/v3' }} 30 | with: 31 | author-email: 32 | ${{ secrets.ORY_BOT_PAT && 33 | '60093411+ory-bot@users.noreply.github.com' || 34 | format('{0}@users.noreply.github.com', github.actor) }} 35 | author-name: ${{ secrets.ORY_BOT_PAT && 'ory-bot' || github.actor }} 36 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED, DO NOT EDIT! 2 | # Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/stale.yml 3 | 4 | name: "Close Stale Issues" 5 | on: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: "0 0 * * *" 9 | 10 | jobs: 11 | stale: 12 | if: github.repository_owner == 'ory' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/stale@v4 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | stale-issue-message: | 19 | Hello contributors! 20 | 21 | I am marking this issue as stale as it has not received any engagement from the community or maintainers for a year. That does not imply that the issue has no merit! If you feel strongly about this issue 22 | 23 | - open a PR referencing and resolving the issue; 24 | - leave a comment on it and discuss ideas on how you could contribute towards resolving it; 25 | - leave a comment and describe in detail why this issue is critical for your use case; 26 | - open a new issue with updated details and a plan for resolving the issue. 27 | 28 | Throughout its lifetime, Ory has received over 10.000 issues and PRs. To sustain that growth, we need to prioritize and focus on issues that are important to the community. A good indication of importance, and thus priority, is activity on a topic. 29 | 30 | Unfortunately, [burnout](https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes) has become a [topic](https://opensource.guide/best-practices/#its-okay-to-hit-pause) of [concern](https://docs.brew.sh/Maintainers-Avoiding-Burnout) amongst open-source projects. 31 | 32 | It can lead to severe personal and health issues as well as [opening](https://haacked.com/archive/2019/05/28/maintainer-burnout/) catastrophic [attack vectors](https://www.gradiant.org/en/blog/open-source-maintainer-burnout-as-an-attack-surface/). 33 | 34 | The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone. 35 | 36 | If this issue was marked as stale erroneously you can exempt it by adding the `backlog` label, assigning someone, or setting a milestone for it. 37 | 38 | Thank you for your understanding and to anyone who participated in the conversation! And as written above, please do participate in the conversation if this topic is important to you! 39 | 40 | Thank you 🙏✌️ 41 | stale-issue-label: "stale" 42 | exempt-issue-labels: "bug,blocking,docs,backlog" 43 | days-before-stale: 365 44 | days-before-close: 30 45 | exempt-milestones: true 46 | exempt-assignees: true 47 | only-pr-labels: "stale" 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .bin 8 | bin 9 | .bin/ 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | CONTRIBUTING.md 28 | dist/ 29 | manager 30 | node_modules/ 31 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | project_name: oathkeeper-maester 4 | version: 2 5 | before: 6 | hooks: 7 | - "go mod download" 8 | 9 | builds: 10 | - id: linux-amd64 11 | flags: 12 | - -a 13 | binary: manager 14 | env: 15 | - CGO_ENABLED=0 16 | goarch: 17 | - amd64 18 | goos: 19 | - linux 20 | - id: linux-arm64 21 | flags: 22 | - -a 23 | binary: manager 24 | env: 25 | - CGO_ENABLED=0 26 | goarch: 27 | - arm64 28 | goos: 29 | - linux 30 | - id: linux-i386 31 | flags: 32 | - -a 33 | binary: manager 34 | env: 35 | - CGO_ENABLED=0 36 | goarch: 37 | - 386 38 | goos: 39 | - linux 40 | - id: darwin-amd64 41 | flags: 42 | - -a 43 | binary: manager 44 | env: 45 | - CGO_ENABLED=0 46 | goarch: 47 | - amd64 48 | goos: 49 | - darwin 50 | - id: darwin-arm64 51 | flags: 52 | - -a 53 | binary: manager 54 | env: 55 | - CGO_ENABLED=0 56 | goarch: 57 | - arm64 58 | goos: 59 | - darwin 60 | 61 | snapshot: 62 | version_template: "{{ .Tag }}-next" 63 | 64 | changelog: 65 | sort: asc 66 | 67 | dockers: 68 | - image_templates: 69 | - "oryd/oathkeeper-maester:{{ .Tag }}-amd64" 70 | - "oryd/oathkeeper-maester:{{ .ShortCommit }}-amd64" 71 | - "oryd/oathkeeper-maester:latest" 72 | goarch: amd64 73 | build_flag_templates: 74 | - "--platform=linux/amd64" 75 | dockerfile: ".docker/Dockerfile-release" 76 | ids: 77 | - "linux-amd64" 78 | - image_templates: 79 | - "oryd/oathkeeper-maester:{{ .Tag }}-arm64" 80 | - "oryd/oathkeeper-maester:{{ .ShortCommit }}-arm64" 81 | goarch: arm64 82 | build_flag_templates: 83 | - "--platform=linux/arm64" 84 | dockerfile: ".docker/Dockerfile-release" 85 | ids: 86 | - "linux-arm64" 87 | 88 | docker_manifests: 89 | - name_template: "oryd/oathkeeper-maester:{{ .Tag }}" 90 | id: "tag" 91 | image_templates: 92 | - "oryd/oathkeeper-maester:{{ .Tag }}-amd64" 93 | - "oryd/oathkeeper-maester:{{ .Tag }}-arm64" 94 | - name_template: "oryd/oathkeeper-maester:latest" 95 | id: "latest" 96 | image_templates: 97 | - "oryd/oathkeeper-maester:{{ .Tag }}-amd64" 98 | - "oryd/oathkeeper-maester:{{ .Tag }}-arm64" 99 | 100 | release: 101 | prerelease: auto 102 | name_template: "{{ .Tag }}" 103 | -------------------------------------------------------------------------------- /.grype.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | # false positive: https://github.com/anchore/grype/issues/558 3 | - vulnerability: CVE-2015-5237 4 | # false positive: https://github.com/anchore/grype/issues/558 5 | - vulnerability: CVE-2021-22570 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | CONTRIBUTING.md 3 | .github/pull_request_template.md 4 | api/v1alpha1/tests/* 5 | tests/integration/files/* -------------------------------------------------------------------------------- /.reference-ignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | docs 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /.reports/dep-licenses.csv: -------------------------------------------------------------------------------- 1 | 2 | "github.com/avast/retry-go","MIT" 3 | "github.com/bitly/go-simplejson","MIT" 4 | "github.com/go-logr/logr","Apache-2.0" 5 | "github.com/fsnotify/fsnotify","BSD-3-Clause" 6 | "github.com/nxadm/tail","MIT" 7 | "github.com/nxadm/tail/ratelimiter","MIT" 8 | "github.com/onsi/ginkgo","MIT" 9 | "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable","MIT" 10 | "golang.org/x/sys/unix","BSD-3-Clause" 11 | "gopkg.in/tomb.v1","BSD-3-Clause" 12 | "github.com/google/go-cmp/cmp","BSD-3-Clause" 13 | "github.com/onsi/gomega","MIT" 14 | "golang.org/x/net/html","BSD-3-Clause" 15 | "golang.org/x/text","BSD-3-Clause" 16 | "gopkg.in/yaml.v3","MIT" 17 | "github.com/avast/retry-go","MIT" 18 | "github.com/beorn7/perks/quantile","MIT" 19 | "github.com/cespare/xxhash/v2","MIT" 20 | "github.com/davecgh/go-spew/spew","ISC" 21 | "github.com/emicklei/go-restful/v3","MIT" 22 | "github.com/evanphx/json-patch/v5","BSD-3-Clause" 23 | "github.com/fsnotify/fsnotify","BSD-3-Clause" 24 | "github.com/go-logr/logr","Apache-2.0" 25 | "github.com/go-logr/zapr","Apache-2.0" 26 | "github.com/go-openapi/jsonpointer","Apache-2.0" 27 | "github.com/go-openapi/jsonreference","Apache-2.0" 28 | "github.com/go-openapi/swag","Apache-2.0" 29 | "github.com/gogo/protobuf","BSD-3-Clause" 30 | "github.com/golang/groupcache/lru","Apache-2.0" 31 | "github.com/golang/protobuf","BSD-3-Clause" 32 | "github.com/google/gnostic-models","Apache-2.0" 33 | "github.com/google/go-cmp/cmp","BSD-3-Clause" 34 | "github.com/google/gofuzz","Apache-2.0" 35 | "github.com/google/uuid","BSD-3-Clause" 36 | "github.com/imdario/mergo","BSD-3-Clause" 37 | "github.com/josharian/intern","MIT" 38 | "github.com/json-iterator/go","MIT" 39 | "github.com/mailru/easyjson","MIT" 40 | "github.com/matttproud/golang_protobuf_extensions/pbutil","Apache-2.0" 41 | "github.com/modern-go/concurrent","Apache-2.0" 42 | "github.com/modern-go/reflect2","Apache-2.0" 43 | "github.com/munnerz/goautoneg","BSD-3-Clause" 44 | "github.com/ory/oathkeeper-maester","Apache-2.0" 45 | "github.com/pkg/errors","BSD-2-Clause" 46 | "github.com/prometheus/client_golang/prometheus","Apache-2.0" 47 | "github.com/prometheus/client_model/go","Apache-2.0" 48 | "github.com/prometheus/common","Apache-2.0" 49 | "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg","BSD-3-Clause" 50 | "github.com/prometheus/procfs","Apache-2.0" 51 | "github.com/spf13/pflag","BSD-3-Clause" 52 | "go.uber.org/multierr","MIT" 53 | "go.uber.org/zap","MIT" 54 | "golang.org/x/net","BSD-3-Clause" 55 | "golang.org/x/oauth2","BSD-3-Clause" 56 | "golang.org/x/sys/unix","BSD-3-Clause" 57 | "golang.org/x/term","BSD-3-Clause" 58 | "golang.org/x/text","BSD-3-Clause" 59 | "golang.org/x/time/rate","BSD-3-Clause" 60 | "gomodules.xyz/jsonpatch/v2","Apache-2.0" 61 | "google.golang.org/protobuf","BSD-3-Clause" 62 | "gopkg.in/inf.v0","BSD-3-Clause" 63 | "gopkg.in/yaml.v2","Apache-2.0" 64 | "gopkg.in/yaml.v3","MIT" 65 | "k8s.io/api","Apache-2.0" 66 | "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions","Apache-2.0" 67 | "k8s.io/apimachinery/pkg","Apache-2.0" 68 | "k8s.io/apimachinery/third_party/forked/golang","BSD-3-Clause" 69 | "k8s.io/client-go","Apache-2.0" 70 | "k8s.io/component-base/config","Apache-2.0" 71 | "k8s.io/klog/v2","Apache-2.0" 72 | "k8s.io/kube-openapi/pkg","Apache-2.0" 73 | "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json","BSD-3-Clause" 74 | "k8s.io/kube-openapi/pkg/validation/spec","Apache-2.0" 75 | "k8s.io/utils","Apache-2.0" 76 | "k8s.io/utils/internal/third_party/forked/golang/net","BSD-3-Clause" 77 | "sigs.k8s.io/controller-runtime","Apache-2.0" 78 | "sigs.k8s.io/json","Apache-2.0" 79 | "sigs.k8s.io/json","BSD-3-Clause" 80 | "sigs.k8s.io/structured-merge-diff/v4","Apache-2.0" 81 | "sigs.k8s.io/yaml","MIT" 82 | "sigs.k8s.io/yaml","BSD-3-Clause" 83 | "k8s.io/api","Apache-2.0" 84 | "k8s.io/apimachinery","Apache-2.0" 85 | "k8s.io/client-go","Apache-2.0" 86 | "github.com/beorn7/perks/quantile","MIT" 87 | "github.com/cespare/xxhash/v2","MIT" 88 | "github.com/davecgh/go-spew/spew","ISC" 89 | "github.com/emicklei/go-restful/v3","MIT" 90 | "github.com/evanphx/json-patch/v5","BSD-3-Clause" 91 | "github.com/fsnotify/fsnotify","BSD-3-Clause" 92 | "github.com/go-logr/logr","Apache-2.0" 93 | "github.com/go-openapi/jsonpointer","Apache-2.0" 94 | "github.com/go-openapi/jsonreference","Apache-2.0" 95 | "github.com/go-openapi/swag","Apache-2.0" 96 | "github.com/gogo/protobuf","BSD-3-Clause" 97 | "github.com/golang/groupcache/lru","Apache-2.0" 98 | "github.com/golang/protobuf","BSD-3-Clause" 99 | "github.com/google/gnostic-models","Apache-2.0" 100 | "github.com/google/go-cmp/cmp","BSD-3-Clause" 101 | "github.com/google/gofuzz","Apache-2.0" 102 | "github.com/google/uuid","BSD-3-Clause" 103 | "github.com/imdario/mergo","BSD-3-Clause" 104 | "github.com/josharian/intern","MIT" 105 | "github.com/json-iterator/go","MIT" 106 | "github.com/mailru/easyjson","MIT" 107 | "github.com/matttproud/golang_protobuf_extensions/pbutil","Apache-2.0" 108 | "github.com/modern-go/concurrent","Apache-2.0" 109 | "github.com/modern-go/reflect2","Apache-2.0" 110 | "github.com/munnerz/goautoneg","BSD-3-Clause" 111 | "github.com/pkg/errors","BSD-2-Clause" 112 | "github.com/prometheus/client_golang/prometheus","Apache-2.0" 113 | "github.com/prometheus/client_model/go","Apache-2.0" 114 | "github.com/prometheus/common","Apache-2.0" 115 | "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg","BSD-3-Clause" 116 | "github.com/prometheus/procfs","Apache-2.0" 117 | "github.com/spf13/pflag","BSD-3-Clause" 118 | "golang.org/x/net","BSD-3-Clause" 119 | "golang.org/x/oauth2","BSD-3-Clause" 120 | "golang.org/x/sys/unix","BSD-3-Clause" 121 | "golang.org/x/term","BSD-3-Clause" 122 | "golang.org/x/text","BSD-3-Clause" 123 | "golang.org/x/time/rate","BSD-3-Clause" 124 | "gomodules.xyz/jsonpatch/v2","Apache-2.0" 125 | "google.golang.org/protobuf","BSD-3-Clause" 126 | "gopkg.in/inf.v0","BSD-3-Clause" 127 | "gopkg.in/yaml.v2","Apache-2.0" 128 | "gopkg.in/yaml.v3","MIT" 129 | "k8s.io/api","Apache-2.0" 130 | "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions","Apache-2.0" 131 | "k8s.io/apimachinery/pkg","Apache-2.0" 132 | "k8s.io/apimachinery/third_party/forked/golang","BSD-3-Clause" 133 | "k8s.io/client-go","Apache-2.0" 134 | "k8s.io/component-base/config","Apache-2.0" 135 | "k8s.io/klog/v2","Apache-2.0" 136 | "k8s.io/kube-openapi/pkg","Apache-2.0" 137 | "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json","BSD-3-Clause" 138 | "k8s.io/kube-openapi/pkg/validation/spec","Apache-2.0" 139 | "k8s.io/utils","Apache-2.0" 140 | "k8s.io/utils/internal/third_party/forked/golang/net","BSD-3-Clause" 141 | "sigs.k8s.io/controller-runtime","Apache-2.0" 142 | "sigs.k8s.io/json","Apache-2.0" 143 | "sigs.k8s.io/json","BSD-3-Clause" 144 | "sigs.k8s.io/structured-merge-diff/v4","Apache-2.0" 145 | "sigs.k8s.io/yaml","MIT" 146 | "sigs.k8s.io/yaml","BSD-3-Clause" 147 | 148 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Contributor Covenant Code of Conduct 5 | 6 | ## Our Pledge 7 | 8 | We as members, contributors, and leaders pledge to make participation in our 9 | community a harassment-free experience for everyone, regardless of age, body 10 | size, visible or invisible disability, ethnicity, sex characteristics, gender 11 | identity and expression, level of experience, education, socio-economic status, 12 | nationality, personal appearance, race, caste, color, religion, or sexual 13 | identity and orientation. 14 | 15 | We pledge to act and interact in ways that contribute to an open, welcoming, 16 | diverse, inclusive, and healthy community. 17 | 18 | ## Our Standards 19 | 20 | Examples of behavior that contributes to a positive environment for our 21 | community include: 22 | 23 | - Demonstrating empathy and kindness toward other people 24 | - Being respectful of differing opinions, viewpoints, and experiences 25 | - Giving and gracefully accepting constructive feedback 26 | - Accepting responsibility and apologizing to those affected by our mistakes, 27 | and learning from the experience 28 | - Focusing on what is best not just for us as individuals, but for the overall 29 | community 30 | 31 | Examples of unacceptable behavior include: 32 | 33 | - The use of sexualized language or imagery, and sexual attention or advances of 34 | any kind 35 | - Trolling, insulting or derogatory comments, and personal or political attacks 36 | - Public or private harassment 37 | - Publishing others' private information, such as a physical or email address, 38 | without their explicit permission 39 | - Other conduct which could reasonably be considered inappropriate in a 40 | professional setting 41 | 42 | ## Open Source Community Support 43 | 44 | Ory Open source software is collaborative and based on contributions by 45 | developers in the Ory community. There is no obligation from Ory to help with 46 | individual problems. If Ory open source software is used in production in a 47 | for-profit company or enterprise environment, we mandate a paid support contract 48 | where Ory is obligated under their service level agreements (SLAs) to offer a 49 | defined level of availability and responsibility. For more information about 50 | paid support please contact us at sales@ory.sh. 51 | 52 | ## Enforcement Responsibilities 53 | 54 | Community leaders are responsible for clarifying and enforcing our standards of 55 | acceptable behavior and will take appropriate and fair corrective action in 56 | response to any behavior that they deem inappropriate, threatening, offensive, 57 | or harmful. 58 | 59 | Community leaders have the right and responsibility to remove, edit, or reject 60 | comments, commits, code, wiki edits, issues, and other contributions that are 61 | not aligned to this Code of Conduct, and will communicate reasons for moderation 62 | decisions when appropriate. 63 | 64 | ## Scope 65 | 66 | This Code of Conduct applies within all community spaces, and also applies when 67 | an individual is officially representing the community in public spaces. 68 | Examples of representing our community include using an official e-mail address, 69 | posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. 71 | 72 | ## Enforcement 73 | 74 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 75 | reported to the community leaders responsible for enforcement at 76 | [office@ory.sh](mailto:office@ory.sh). All complaints will be reviewed and 77 | investigated promptly and fairly. 78 | 79 | All community leaders are obligated to respect the privacy and security of the 80 | reporter of any incident. 81 | 82 | ## Enforcement Guidelines 83 | 84 | Community leaders will follow these Community Impact Guidelines in determining 85 | the consequences for any action they deem in violation of this Code of Conduct: 86 | 87 | ### 1. Correction 88 | 89 | **Community Impact**: Use of inappropriate language or other behavior deemed 90 | unprofessional or unwelcome in the community. 91 | 92 | **Consequence**: A private, written warning from community leaders, providing 93 | clarity around the nature of the violation and an explanation of why the 94 | behavior was inappropriate. A public apology may be requested. 95 | 96 | ### 2. Warning 97 | 98 | **Community Impact**: A violation through a single incident or series of 99 | actions. 100 | 101 | **Consequence**: A warning with consequences for continued behavior. No 102 | interaction with the people involved, including unsolicited interaction with 103 | those enforcing the Code of Conduct, for a specified period of time. This 104 | includes avoiding interactions in community spaces as well as external channels 105 | like social media. Violating these terms may lead to a temporary or permanent 106 | ban. 107 | 108 | ### 3. Temporary Ban 109 | 110 | **Community Impact**: A serious violation of community standards, including 111 | sustained inappropriate behavior. 112 | 113 | **Consequence**: A temporary ban from any sort of interaction or public 114 | communication with the community for a specified period of time. No public or 115 | private interaction with the people involved, including unsolicited interaction 116 | with those enforcing the Code of Conduct, is allowed during this period. 117 | Violating these terms may lead to a permanent ban. 118 | 119 | ### 4. Permanent Ban 120 | 121 | **Community Impact**: Demonstrating a pattern of violation of community 122 | standards, including sustained inappropriate behavior, harassment of an 123 | individual, or aggression toward or disparagement of classes of individuals. 124 | 125 | **Consequence**: A permanent ban from any sort of public interaction within the 126 | community. 127 | 128 | ## Attribution 129 | 130 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 131 | version 2.1, available at 132 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 133 | 134 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 135 | enforcement ladder][mozilla coc]. 136 | 137 | For answers to common questions about this code of conduct, see the FAQ at 138 | [https://www.contributor-covenant.org/faq][faq]. Translations are available at 139 | [https://www.contributor-covenant.org/translations][translations]. 140 | 141 | [homepage]: https://www.contributor-covenant.org 142 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 143 | [mozilla coc]: https://github.com/mozilla/diversity 144 | [faq]: https://www.contributor-covenant.org/faq 145 | [translations]: https://www.contributor-covenant.org/translations 146 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Contribute to Ory Ory Oathkeeper Maester 5 | 6 | 7 | 8 | 9 | - [Introduction](#introduction) 10 | - [FAQ](#faq) 11 | - [How can I contribute?](#how-can-i-contribute) 12 | - [Communication](#communication) 13 | - [Contribute examples or community projects](#contribute-examples-or-community-projects) 14 | - [Contribute code](#contribute-code) 15 | - [Contribute documentation](#contribute-documentation) 16 | - [Disclosing vulnerabilities](#disclosing-vulnerabilities) 17 | - [Code style](#code-style) 18 | - [Working with forks](#working-with-forks) 19 | - [Conduct](#conduct) 20 | 21 | 22 | 23 | ## Introduction 24 | 25 | _Please note_: We take Ory Ory Oathkeeper Maester's security and our users' trust very 26 | seriously. If you believe you have found a security issue in Ory Ory Oathkeeper Maester, 27 | please disclose it by contacting us at security@ory.sh. 28 | 29 | There are many ways in which you can contribute. The goal of this document is to 30 | provide a high-level overview of how you can get involved in Ory. 31 | 32 | As a potential contributor, your changes and ideas are welcome at any hour of 33 | the day or night, on weekdays, weekends, and holidays. Please do not ever 34 | hesitate to ask a question or send a pull request. 35 | 36 | If you are unsure, just ask or submit the issue or pull request anyways. You 37 | won't be yelled at for giving it your best effort. The worst that can happen is 38 | that you'll be politely asked to change something. We appreciate any sort of 39 | contributions and don't want a wall of rules to get in the way of that. 40 | 41 | That said, if you want to ensure that a pull request is likely to be merged, 42 | talk to us! You can find out our thoughts and ensure that your contribution 43 | won't clash with Ory 44 | Ory Oathkeeper Maester's direction. A great way to 45 | do this is via 46 | [Ory Ory Oathkeeper Maester Discussions](https://github.com/orgs/ory/discussions) 47 | or the [Ory Chat](https://www.ory.sh/chat). 48 | 49 | ## FAQ 50 | 51 | - I am new to the community. Where can I find the 52 | [Ory Community Code of Conduct?](https://github.com/ory/oathkeeper-maester/blob/master/CODE_OF_CONDUCT.md) 53 | 54 | - I have a question. Where can I get 55 | [answers to questions regarding Ory Ory Oathkeeper Maester?](#communication) 56 | 57 | - I would like to contribute but I am not sure how. Are there 58 | [easy ways to contribute?](#how-can-i-contribute) 59 | [Or good first issues?](https://github.com/search?l=&o=desc&q=label%3A%22help+wanted%22+label%3A%22good+first+issue%22+is%3Aopen+user%3Aory+user%3Aory-corp&s=updated&type=Issues) 60 | 61 | - I want to talk to other Ory Ory Oathkeeper Maester users. 62 | [How can I become a part of the community?](#communication) 63 | 64 | - I would like to know what I am agreeing to when I contribute to Ory 65 | Ory Oathkeeper Maester. 66 | Does Ory have 67 | [a Contributors License Agreement?](https://cla-assistant.io/ory/oathkeeper-maester) 68 | 69 | - I would like updates about new versions of Ory Ory Oathkeeper Maester. 70 | [How are new releases announced?](https://www.ory.sh/l/sign-up-newsletter) 71 | 72 | ## How can I contribute? 73 | 74 | If you want to start to contribute code right away, take a look at the 75 | [list of good first issues](https://github.com/ory/oathkeeper-maester/labels/good%20first%20issue). 76 | 77 | There are many other ways you can contribute. Here are a few things you can do 78 | to help out: 79 | 80 | - **Give us a star.** It may not seem like much, but it really makes a 81 | difference. This is something that everyone can do to help out Ory Ory Oathkeeper Maester. 82 | Github stars help the project gain visibility and stand out. 83 | 84 | - **Join the community.** Sometimes helping people can be as easy as listening 85 | to their problems and offering a different perspective. Join our Slack, have a 86 | look at discussions in the forum and take part in community events. More info 87 | on this in [Communication](#communication). 88 | 89 | - **Answer discussions.** At all times, there are several unanswered discussions 90 | on GitHub. You can see an 91 | [overview here](https://github.com/discussions?discussions_q=is%3Aunanswered+org%3Aory+sort%3Aupdated-desc). 92 | If you think you know an answer or can provide some information that might 93 | help, please share it! Bonus: You get GitHub achievements for answered 94 | discussions. 95 | 96 | - **Help with open issues.** We have a lot of open issues for Ory Ory Oathkeeper Maester and 97 | some of them may lack necessary information, some are duplicates of older 98 | issues. You can help out by guiding people through the process of filling out 99 | the issue template, asking for clarifying information or pointing them to 100 | existing issues that match their description of the problem. 101 | 102 | - **Review documentation changes.** Most documentation just needs a review for 103 | proper spelling and grammar. If you think a document can be improved in any 104 | way, feel free to hit the `edit` button at the top of the page. More info on 105 | contributing to the documentation [here](#contribute-documentation). 106 | 107 | - **Help with tests.** Pull requests may lack proper tests or test plans. These 108 | are needed for the change to be implemented safely. 109 | 110 | ## Communication 111 | 112 | We use [Slack](https://www.ory.sh/chat). You are welcome to drop in and ask 113 | questions, discuss bugs and feature requests, talk to other users of Ory, etc. 114 | 115 | Check out [Ory Ory Oathkeeper Maester Discussions](https://github.com/orgs/ory/discussions). This is a great place for 116 | in-depth discussions and lots of code examples, logs and similar data. 117 | 118 | You can also join our community calls if you want to speak to the Ory team 119 | directly or ask some questions. You can find more info and participate in 120 | [Slack](https://www.ory.sh/chat) in the #community-call channel. 121 | 122 | If you want to receive regular notifications about updates to Ory Ory Oathkeeper Maester, 123 | consider joining the mailing list. We will _only_ send you vital information on 124 | the projects that you are interested in. 125 | 126 | Also, [follow us on Twitter](https://twitter.com/orycorp). 127 | 128 | ## Contribute examples or community projects 129 | 130 | One of the most impactful ways to contribute is by adding code examples or other 131 | Ory-related code. You can find an overview of community code in the 132 | [awesome-ory](https://github.com/ory/awesome-ory) repository. 133 | 134 | _If you would like to contribute a new example, we would love to hear from you!_ 135 | 136 | Please [open a pull request at awesome-ory](https://github.com/ory/awesome-ory/) 137 | to add your example or Ory-related project to the awesome-ory README. 138 | 139 | ## Contribute code 140 | 141 | Unless you are fixing a known bug, we **strongly** recommend discussing it with 142 | the core team via a GitHub issue or [in our chat](https://www.ory.sh/chat) 143 | before getting started to ensure your work is consistent with Ory Ory Oathkeeper Maester's 144 | roadmap and architecture. 145 | 146 | All contributions are made via pull requests. To make a pull request, you will 147 | need a GitHub account; if you are unclear on this process, see GitHub's 148 | documentation on [forking](https://help.github.com/articles/fork-a-repo) and 149 | [pull requests](https://help.github.com/articles/using-pull-requests). Pull 150 | requests should be targeted at the `master` branch. Before creating a pull 151 | request, go through this checklist: 152 | 153 | 1. Create a feature branch off of `master` so that changes do not get mixed up. 154 | 1. [Rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) your local 155 | changes against the `master` branch. 156 | 1. Run the full project test suite with the `go test -tags sqlite ./...` (or 157 | equivalent) command and confirm that it passes. 158 | 1. Run `make format` 159 | 1. Add a descriptive prefix to commits. This ensures a uniform commit history 160 | and helps structure the changelog. Please refer to this 161 | [Convential Commits configuration](https://github.com/ory/oathkeeper-maester/blob/master/.github/workflows/conventional_commits.yml) 162 | for the list of accepted prefixes. You can read more about the Conventional 163 | Commit specification 164 | [at their site](https://www.conventionalcommits.org/en/v1.0.0/). 165 | 166 | If a pull request is not ready to be reviewed yet 167 | [it should be marked as a "Draft"](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request). 168 | 169 | Before your contributions can be reviewed you need to sign our 170 | [Contributor License Agreement](https://cla-assistant.io/ory/oathkeeper-maester). 171 | 172 | This agreement defines the terms under which your code is contributed to Ory. 173 | More specifically it declares that you have the right to, and actually do, grant 174 | us the rights to use your contribution. You can see the Apache 2.0 license under 175 | which our projects are published 176 | [here](https://github.com/ory/meta/blob/master/LICENSE). 177 | 178 | When pull requests fail the automated testing stages (for example unit or E2E 179 | tests), authors are expected to update their pull requests to address the 180 | failures until the tests pass. 181 | 182 | Pull requests eligible for review 183 | 184 | 1. follow the repository's code formatting conventions; 185 | 2. include tests that prove that the change works as intended and does not add 186 | regressions; 187 | 3. document the changes in the code and/or the project's documentation; 188 | 4. pass the CI pipeline; 189 | 5. have signed our 190 | [Contributor License Agreement](https://cla-assistant.io/ory/oathkeeper-maester); 191 | 6. include a proper git commit message following the 192 | [Conventional Commit Specification](https://www.conventionalcommits.org/en/v1.0.0/). 193 | 194 | If all of these items are checked, the pull request is ready to be reviewed and 195 | you should change the status to "Ready for review" and 196 | [request review from a maintainer](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review). 197 | 198 | Reviewers will approve the pull request once they are satisfied with the patch. 199 | 200 | ## Contribute documentation 201 | 202 | Please provide documentation when changing, removing, or adding features. All 203 | Ory Documentation resides in the 204 | [Ory documentation repository](https://github.com/ory/docs/). For further 205 | instructions please head over to the Ory Documentation 206 | [README.md](https://github.com/ory/docs/blob/master/README.md). 207 | 208 | ## Disclosing vulnerabilities 209 | 210 | Please disclose vulnerabilities exclusively to 211 | [security@ory.sh](mailto:security@ory.sh). Do not use GitHub issues. 212 | 213 | ## Code style 214 | 215 | Please run `make format` to format all source code following the Ory standard. 216 | 217 | ### Working with forks 218 | 219 | ```bash 220 | # First you clone the original repository 221 | git clone git@github.com:ory/ory/oathkeeper-maester.git 222 | 223 | # Next you add a git remote that is your fork: 224 | git remote add fork git@github.com:/ory/oathkeeper-maester.git 225 | 226 | # Next you fetch the latest changes from origin for master: 227 | git fetch origin 228 | git checkout master 229 | git pull --rebase 230 | 231 | # Next you create a new feature branch off of master: 232 | git checkout my-feature-branch 233 | 234 | # Now you do your work and commit your changes: 235 | git add -A 236 | git commit -a -m "fix: this is the subject line" -m "This is the body line. Closes #123" 237 | 238 | # And the last step is pushing this to your fork 239 | git push -u fork my-feature-branch 240 | ``` 241 | 242 | Now go to the project's GitHub Pull Request page and click "New pull request" 243 | 244 | ## Conduct 245 | 246 | Whether you are a regular contributor or a newcomer, we care about making this 247 | community a safe place for you and we've got your back. 248 | 249 | [Ory Community Code of Conduct](https://github.com/ory/oathkeeper-maester/blob/master/CODE_OF_CONDUCT.md) 250 | 251 | We welcome discussion about creating a welcoming, safe, and productive 252 | environment for the community. If you have any questions, feedback, or concerns 253 | [please let us know](https://www.ory.sh/chat). 254 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 | ifeq ($(OS),Windows_NT) 2 | ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) 3 | ARCH=amd64 4 | OS=windows 5 | endif 6 | else 7 | UNAME_S := $(shell uname -s) 8 | ifeq ($(UNAME_S),Linux) 9 | OS=linux 10 | ARCH=amd64 11 | endif 12 | ifeq ($(UNAME_S),Darwin) 13 | OS=darwin 14 | ifeq ($(shell uname -m),x86_64) 15 | ARCH=amd64 16 | endif 17 | ifeq ($(shell uname -m),arm64) 18 | ARCH=arm64 19 | endif 20 | endif 21 | endif 22 | ##@ Build Dependencies 23 | 24 | ## Location to install dependencies to 25 | LOCALBIN ?= $(shell pwd)/.bin 26 | $(LOCALBIN): 27 | mkdir -p $(LOCALBIN) 28 | 29 | SHELL=/bin/bash -euo pipefail 30 | 31 | export PATH := .bin:${PATH} 32 | export PWD := $(shell pwd) 33 | export K3SIMAGE := docker.io/rancher/k3s:v1.26.1-k3s1 34 | ## Tool Binaries 35 | CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen 36 | ENVTEST ?= $(LOCALBIN)/setup-envtest 37 | 38 | ## Tool Versions 39 | CONTROLLER_TOOLS_VERSION ?= v0.15.0 40 | ENVTEST_K8S_VERSION = 1.29.0 41 | 42 | # Image URL to use all building/pushing image targets 43 | IMG ?= controller:latest 44 | 45 | run-with-cleanup = $(1) && $(2) || (ret=$$?; $(2) && exit $$ret) 46 | 47 | # find or download controller-gen 48 | # download controller-gen if necessary 49 | .PHONY: controller-gen 50 | controller-gen: $(CONTROLLER_GEN) 51 | $(CONTROLLER_GEN): $(LOCALBIN) 52 | test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ 53 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) 54 | 55 | ## Download envtest-setup locally if necessary. 56 | .PHONY: envtest 57 | envtest: $(ENVTEST) 58 | $(ENVTEST): $(LOCALBIN) 59 | test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 60 | 61 | .bin/ory: Makefile 62 | curl https://raw.githubusercontent.com/ory/meta/master/install.sh | bash -s -- -b .bin ory 63 | touch .bin/ory 64 | 65 | .bin/kubectl: Makefile 66 | @URL=$$(.bin/ory dev ci deps url -o ${OS} -a ${ARCH} -c .deps/kubectl.yaml); \ 67 | echo "Downloading 'kubectl' $${URL}...."; \ 68 | curl -Lo .bin/kubectl $${URL}; \ 69 | chmod +x .bin/kubectl; 70 | 71 | .bin/kustomize: Makefile 72 | @URL=$$(.bin/ory dev ci deps url -o ${OS} -a ${ARCH} -c .deps/kustomize.yaml); \ 73 | echo "Downloading 'kustomize' $${URL}...."; \ 74 | curl -L $${URL} | tar -xmz -C .bin kustomize; \ 75 | chmod +x .bin/kustomize; 76 | 77 | .bin/k3d: Makefile 78 | @URL=$$(.bin/ory dev ci deps url -o ${OS} -a ${ARCH} -c .deps/k3d.yaml); \ 79 | echo "Downloading 'k3d' $${URL}...."; \ 80 | curl -Lo .bin/k3d $${URL}; \ 81 | chmod +x .bin/k3d; 82 | 83 | .PHONY: deps 84 | deps: .bin/ory .bin/k3d .bin/kubectl .bin/kustomize 85 | 86 | .PHONY: all 87 | all: manager 88 | 89 | # Run tests 90 | .PHONY: test 91 | test: manifests generate vet envtest 92 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./api/... ./controllers/... ./internal/... -coverprofile cover.out 93 | 94 | .PHONY: k3d-up 95 | k3d-up: 96 | k3d cluster create --image $${K3SIMAGE} ory \ 97 | --k3s-arg=--kube-apiserver-arg="enable-admission-plugins=NodeRestriction,ServiceAccount@server:0" \ 98 | --k3s-arg=feature-gates="NamespaceDefaultLabelName=true@server:0"; 99 | 100 | .PHONY: k3d-down 101 | k3d-down: 102 | k3d cluster delete ory || true 103 | 104 | .PHONY: k3d-deploy 105 | k3d-deploy: manager manifests docker-build-notest k3d-up 106 | kubectl config set-context k3d-ory 107 | k3d image load controller:latest -c ory 108 | kubectl apply -f config/crd/bases 109 | kustomize build config/default | kubectl apply -f - 110 | 111 | .PHONY: k3d-test 112 | k3d-test: k3d-deploy 113 | kubectl config set-context k3d-ory 114 | go install github.com/onsi/ginkgo/ginkgo@latest 115 | USE_EXISTING_CLUSTER=true ginkgo -v ./tests/integration/... 116 | 117 | # Run integration tests on local cluster 118 | .PHONY: test-integration 119 | test-integration: 120 | $(call run-with-cleanup, $(MAKE) k3d-test, $(MAKE) k3d-down) 121 | 122 | # Build manager binary 123 | .PHONY: manager 124 | manager: generate vet 125 | rm -f manager || true 126 | CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -a -o manager main.go 127 | 128 | # Build manager binary for CI 129 | .PHONY: manager-ci 130 | manager-ci: generate vet 131 | rm -f manager || true 132 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go 133 | 134 | # Run against the configured Kubernetes cluster in ~/.kube/config 135 | .PHONY: run 136 | run: generate vet 137 | go run ./main.go 138 | 139 | # Install CRDs into a cluster 140 | .PHONY: install 141 | install: manifests 142 | kubectl apply -f config/crd/bases 143 | 144 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 145 | .PHONY: deploy 146 | deploy: manifests 147 | kubectl apply -f config/crd/bases 148 | kustomize build config/default | kubectl apply -f - 149 | 150 | # Generate manifests e.g. CRD, RBAC etc. 151 | .PHONY: manifests 152 | manifests: controller-gen 153 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 154 | 155 | # Format the source code 156 | format: .bin/ory node_modules 157 | .bin/ory dev headers copyright --type=open-source 158 | go fmt ./... 159 | npm exec -- prettier --write . 160 | 161 | # Run go vet against code 162 | .PHONY: vet 163 | vet: 164 | go vet ./... 165 | 166 | # Generate code 167 | .PHONY: generate 168 | generate: controller-gen 169 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 170 | 171 | # Build the docker image 172 | .PHONY: docker-build-notest 173 | docker-build-notest: 174 | docker build . -t ${IMG} -f .docker/Dockerfile-build 175 | @echo "updating kustomize image patch file for manager resource" 176 | sed -i'' -e 's@image: .*@image: '"${IMG}"'@' ./config/default/manager_image_patch.yaml 177 | 178 | .PHONY: docker-build 179 | docker-build: test docker-build-notest 180 | 181 | # Push the docker image 182 | .PHONY: docker-push 183 | docker-push: 184 | docker push ${IMG} 185 | 186 | licenses: .bin/licenses node_modules # checks open-source licenses 187 | .bin/licenses 188 | 189 | .bin/licenses: Makefile 190 | curl https://raw.githubusercontent.com/ory/ci/master/licenses/install | sh 191 | 192 | node_modules: package-lock.json 193 | npm ci 194 | touch node_modules 195 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | version: "3" 2 | domain: ory.sh 3 | repo: github.com/ory/oathkeeper-maester 4 | projectName: oathkeeper-maester 5 | layout: 6 | - go.kubebuilder.io/v4 7 | resources: 8 | - group: oathkeeper 9 | version: v1alpha1 10 | kind: Rule 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [Ory Oathkeeper Maester](#ory-oathkeeper-maester) 5 | - [Prerequisites](#prerequisites) 6 | - [How to use it](#how-to-use-it) 7 | - [Command-line parameters](#command-line-parameters) 8 | - [Mode options](#mode-options) 9 | - [Global flags](#global-flags) 10 | - [Controller mode flags](#controller-mode-flags) 11 | - [Sidecar mode flags](#sidecar-mode-flags) 12 | - [Environment variables](#environment-variables) 13 | 14 | 15 | 16 | # Ory Oathkeeper Maester 17 | 18 | ⚠️ ⚠️ ⚠️ 19 | 20 | > Ory Oathkeeper Maester is developed by the Ory community and is not actively 21 | > maintained by Ory core maintainers due to lack of resources, time, and 22 | > knolwedge. As such please be aware that there might be issues with the system. 23 | > If you have ideas for better testing and development principles please open an 24 | > issue or PR! 25 | 26 | ⚠️ ⚠️ ⚠️ 27 | 28 | ORY Maester is a Kubernetes controller that watches for instances of 29 | `rules.oathkeeper.ory.sh/v1alpha1` custom resource (CR) and creates or updates 30 | the Oathkeeper ConfigMap with Access Rules found in the CRs. The controller 31 | passes the Access Rules as an array in a format recognized by the Oathkeeper. 32 | 33 | The project is based on 34 | [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) 35 | 36 | ## Prerequisites 37 | 38 | - recent version of Go language with support for modules (e.g: 1.12.6) 39 | - make 40 | - kubectl 41 | - kustomize 42 | - [kind](https://github.com/kubernetes-sigs/kind) for local integration testing 43 | - [ginkgo](https://onsi.github.io/ginkgo/) for local integration testing 44 | - access to K8s environment: minikube or KIND 45 | (https://github.com/kubernetes-sigs/kind), or a remote K8s cluster 46 | 47 | ## How to use it 48 | 49 | - `make` to build the binary 50 | - `make test` to run tests 51 | - `make test-integration` to run integration tests with local KIND environment 52 | 53 | Other targets require a working K8s environment. Set `KUBECONFIG` environment 54 | variable to the proper value. 55 | 56 | - `make install` to generate CRD file from go sources and install it in the 57 | cluster 58 | - `make run` to run controller locally 59 | 60 | Refer to the Makefile for the details. 61 | 62 | ## Command-line parameters 63 | 64 | Usage example: `./manager [--global-flags] mode [--mode-flags]` 65 | 66 | ### Mode options 67 | 68 | | Name | Description | 69 | | :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 70 | | **controller** | This is the **default** mode of operation, in which `oathkeeper-maester` is expected to be deployed as a separate deployment. It uses the kubernetes api-server and ConfigMaps to store data. | 71 | | **sidecar** | Alternative mode of operation, in which the `oathkeeper-maester` is expected to be deployed as a sidecar container to the main application. It uses local filesystem to create the access rules file. | 72 | 73 | ### Global flags 74 | 75 | | Name | Description | Default values | 76 | | :------------------------- | :-------------------------------------------------------------------------------------------------------------------- | :------------: | 77 | | **metrics-addr** | The address the metric endpoint binds to | `8080` | 78 | | **enable-leader-election** | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. | `false` | 79 | | **kubeconfig** | Paths to a kubeconfig. Only required if out-of-cluster. | `$KUBECONFIG` | 80 | 81 | ### Controller mode flags 82 | 83 | | Name | Description | Default values | 84 | | :-------------------------- | :------------------------------------------------------- | :-------------------------: | 85 | | **rulesConfigmapName** | Name of the Configmap that stores Oathkeeper rules. | `oathkeeper-rules` | 86 | | **rulesConfigmapNamespace** | Namespace of the Configmap that stores Oathkeeper rules. | `oathkeeper-maester-system` | 87 | | **rulesFileName** | Name of the key in ConfigMap containing the rules.json | `access-rules.json` | 88 | 89 | ### Sidecar mode flags 90 | 91 | | Name | Description | Default values | 92 | | :---------------- | :----------------------------------------------- | :-----------------------------: | 93 | | **rulesFilePath** | Path to the file with converted Oathkeeper rules | `/etc/config/access-rules.json` | 94 | 95 | ### Environment variables 96 | 97 | | Name | Description | Default values | 98 | | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------: | 99 | | **NAMESPACE** | Namespace option to scope Oathkeeper maester to one namespace only - useful for running several instances in one cluster. Defaults to "" which means that there is no namespace scope. | `` | 100 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Ory Security Policy 5 | 6 | This policy outlines Ory's security commitments and practices for users across 7 | different licensing and deployment models. 8 | 9 | To learn more about Ory's security service level agreements (SLAs) and 10 | processes, please [contact us](https://www.ory.sh/contact/). 11 | 12 | ## Ory Network Users 13 | 14 | - **Security SLA:** Ory addresses vulnerabilities in the Ory Network according 15 | to the following guidelines: 16 | - Critical: Typically addressed within 14 days. 17 | - High: Typically addressed within 30 days. 18 | - Medium: Typically addressed within 90 days. 19 | - Low: Typically addressed within 180 days. 20 | - Informational: Addressed as necessary. 21 | These timelines are targets and may vary based on specific circumstances. 22 | - **Release Schedule:** Updates are deployed to the Ory Network as 23 | vulnerabilities are resolved. 24 | - **Version Support:** The Ory Network always runs the latest version, ensuring 25 | up-to-date security fixes. 26 | 27 | ## Ory Enterprise License Customers 28 | 29 | - **Security SLA:** Ory addresses vulnerabilities based on their severity: 30 | - Critical: Typically addressed within 14 days. 31 | - High: Typically addressed within 30 days. 32 | - Medium: Typically addressed within 90 days. 33 | - Low: Typically addressed within 180 days. 34 | - Informational: Addressed as necessary. 35 | These timelines are targets and may vary based on specific circumstances. 36 | - **Release Schedule:** Updates are made available as vulnerabilities are 37 | resolved. Ory works closely with enterprise customers to ensure timely updates 38 | that align with their operational needs. 39 | - **Version Support:** Ory may provide security support for multiple versions, 40 | depending on the terms of the enterprise agreement. 41 | 42 | ## Apache 2.0 License Users 43 | 44 | - **Security SLA:** Ory does not provide a formal SLA for security issues under 45 | the Apache 2.0 License. 46 | - **Release Schedule:** Releases prioritize new functionality and include fixes 47 | for known security vulnerabilities at the time of release. While major 48 | releases typically occur one to two times per year, Ory does not guarantee a 49 | fixed release schedule. 50 | - **Version Support:** Security patches are only provided for the latest release 51 | version. 52 | 53 | ## Reporting a Vulnerability 54 | 55 | For details on how to report security vulnerabilities, visit our 56 | [security policy documentation](https://www.ory.sh/docs/ecosystem/security). 57 | -------------------------------------------------------------------------------- /api/v1alpha1/defaults.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | var ( 7 | DefaultAuthenticatorsAvailable = [...]string{"noop", "unauthorized", "anonymous", "cookie_session", "oauth2_client_credentials", "oauth2_introspection", "jwt", "bearer_token"} 8 | DefaultAuthorizersAvailable = [...]string{"allow", "deny", "keto_engine_acp_ory", "remote", "remote_json"} 9 | DefaultMutatorsAvailable = [...]string{"noop", "id_token", "header", "cookie", "hydrator"} 10 | DefaultErrorsAvailable = [...]string{"json", "redirect", "www_authenticate"} 11 | ) 12 | 13 | const ( 14 | AuthenticatorsAvailableEnv = "authenticatorsAvailable" 15 | AuthorizersAvailableEnv = "authorizersAvailable" 16 | MutatorsAvailableEnv = "mutatorsAvailable" 17 | ErrorsAvailableEnv = "errorsAvailable" 18 | RulesFileNameRegexp = "\\A[-._a-zA-Z0-9]+\\z" 19 | ) 20 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package v1alpha1 contains API Schema definitions for the oathkeeper v1alpha1 API group 5 | // +kubebuilder:object:generate=true 6 | // +groupName=oathkeeper.ory.sh 7 | package v1alpha1 8 | 9 | import ( 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | "sigs.k8s.io/controller-runtime/pkg/scheme" 12 | ) 13 | 14 | var ( 15 | // GroupVersion is group version used to register these objects 16 | GroupVersion = schema.GroupVersion{Group: "oathkeeper.ory.sh", Version: "v1alpha1"} 17 | 18 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 19 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 20 | 21 | // AddToScheme adds the types in this group-version to the given scheme. 22 | AddToScheme = SchemeBuilder.AddToScheme 23 | ) 24 | -------------------------------------------------------------------------------- /api/v1alpha1/json.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | ) 10 | 11 | func unescapedMarshalIndent(in interface{}, prefix, indent string) ([]byte, error) { 12 | var b bytes.Buffer 13 | enc := json.NewEncoder(&b) 14 | enc.SetEscapeHTML(false) 15 | enc.SetIndent(prefix, indent) 16 | if err := enc.Encode(in); err != nil { 17 | return nil, err 18 | } 19 | 20 | result := b.Bytes() 21 | return result[:len(result)-1], nil 22 | } 23 | 24 | func unescapedMarshal(in interface{}) ([]byte, error) { 25 | var b bytes.Buffer 26 | enc := json.NewEncoder(&b) 27 | enc.SetEscapeHTML(false) 28 | if err := enc.Encode(in); err != nil { 29 | return nil, err 30 | } 31 | 32 | result := b.Bytes() 33 | return result[:len(result)-1], nil 34 | } 35 | -------------------------------------------------------------------------------- /api/v1alpha1/rule_json.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | // RuleJson is a representation of an Oathkeeper rule. 7 | type RuleJSON struct { 8 | ID string `json:"id"` 9 | RuleSpec `json:",inline"` 10 | } 11 | 12 | // MarshalJSON is a custom marshal function that converts RuleJSON objects into JSON objects digestible by Oathkeeper 13 | func (rj RuleJSON) MarshalJSON() ([]byte, error) { 14 | 15 | type Alias RuleJSON 16 | 17 | return unescapedMarshal(&struct { 18 | Upstream *UpstreamJSON `json:"upstream,omitempty"` 19 | Alias 20 | }{ 21 | Upstream: &UpstreamJSON{ 22 | URL: rj.Upstream.URL, 23 | PreserveHost: rj.Upstream.PreserveHost, 24 | StripPath: rj.Upstream.StripPath, 25 | }, 26 | Alias: (Alias)(rj), 27 | }) 28 | } 29 | 30 | // UpstreamJSON is a helper struct that representats Oathkeeper's upstream object. 31 | type UpstreamJSON struct { 32 | URL string `json:"url"` 33 | StripPath *string `json:"strip_path,omitempty"` 34 | PreserveHost *bool `json:"preserve_host"` 35 | } 36 | -------------------------------------------------------------------------------- /api/v1alpha1/rule_types.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "fmt" 8 | 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | 12 | "github.com/ory/oathkeeper-maester/internal/validation" 13 | ) 14 | 15 | const ( 16 | deny = "deny" 17 | noop = "noop" 18 | unauthorized = "unauthorized" 19 | ) 20 | 21 | var ( 22 | denyHandler = &Handler{Name: deny} 23 | noopHandler = &Handler{Name: noop} 24 | unauthorizedHandler = &Handler{Name: unauthorized} 25 | preserveHostDefault = false 26 | ) 27 | 28 | // +kubebuilder:object:root=true 29 | // Rule is the Schema for the rules API 30 | type Rule struct { 31 | metav1.TypeMeta `json:",inline"` 32 | metav1.ObjectMeta `json:"metadata,omitempty"` 33 | 34 | Spec RuleSpec `json:"spec,omitempty"` 35 | Status RuleStatus `json:"status,omitempty"` 36 | } 37 | 38 | // +kubebuilder:object:root=true 39 | // RuleList contains a list of Rule 40 | type RuleList struct { 41 | metav1.TypeMeta `json:",inline"` 42 | metav1.ListMeta `json:"metadata,omitempty"` 43 | Items []Rule `json:"items"` 44 | } 45 | 46 | // RuleSpec defines the desired state of Rule 47 | type RuleSpec struct { 48 | // +kubebuilder:validation:Optional 49 | // +optional 50 | Upstream *Upstream `json:"upstream,omitempty"` 51 | Match *Match `json:"match"` 52 | Authenticators []*Authenticator `json:"authenticators,omitempty"` 53 | Authorizer *Authorizer `json:"authorizer,omitempty"` 54 | Mutators []*Mutator `json:"mutators,omitempty"` 55 | Errors []*Error `json:"errors,omitempty"` 56 | // +kubebuilder:validation:MinLength=1 57 | // +kubebuilder:validation:MaxLength=253 58 | // +kubebuilder:validation:Pattern=[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* 59 | // 60 | // ConfigMapName points to the K8s ConfigMap that contains these rules 61 | ConfigMapName *string `json:"configMapName,omitempty"` 62 | } 63 | 64 | // Validation defines the validation state of Rule 65 | type Validation struct { 66 | // +optional 67 | Valid *bool `json:"valid,omitempty"` 68 | // +optional 69 | Error *string `json:"validationError,omitempty"` 70 | } 71 | 72 | // RuleStatus defines the observed state of Rule 73 | type RuleStatus struct { 74 | // +optional 75 | Validation *Validation `json:"validation,omitempty"` 76 | } 77 | 78 | // Upstream represents the location of a server where requests matching a rule should be forwarded to. 79 | type Upstream struct { 80 | // URL defines the target URL for incoming requests 81 | // +kubebuilder:validation:MinLength=3 82 | // +kubebuilder:validation:MaxLength=256 83 | // +kubebuilder:validation:Pattern=`^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)` 84 | URL string `json:"url"` 85 | // StripPath replaces the provided path prefix when forwarding the requested URL to the upstream URL. 86 | // +optional 87 | StripPath *string `json:"stripPath,omitempty"` 88 | // PreserveHost includes the host and port of the url value if set to false. If true, the host and port of the ORY Oathkeeper Proxy will be used instead. 89 | // +optional 90 | PreserveHost *bool `json:"preserveHost,omitempty"` 91 | } 92 | 93 | // Match defines the URL(s) that an access rule should match. 94 | type Match struct { 95 | // URL is the URL that should be matched. It supports regex templates. 96 | URL string `json:"url"` 97 | // Methods represent an array of HTTP methods (e.g. GET, POST, PUT, DELETE, ...) 98 | Methods []string `json:"methods"` 99 | } 100 | 101 | // Authenticator represents a handler that authenticates provided credentials. 102 | type Authenticator struct { 103 | *Handler `json:",inline"` 104 | } 105 | 106 | // Authorizer represents a handler that authorizes the subject ("user") from the previously validated credentials making the request. 107 | type Authorizer struct { 108 | *Handler `json:",inline"` 109 | } 110 | 111 | // Mutator represents a handler that transforms the HTTP request before forwarding it. 112 | type Mutator struct { 113 | *Handler `json:",inline"` 114 | } 115 | 116 | // Error represents a handler that is responsible for executing logic when an error happens. 117 | type Error struct { 118 | *Handler `json:",inline"` 119 | } 120 | 121 | // Handler represents an Oathkeeper routine that operates on incoming requests. It is used to either validate a request (Authenticator, Authorizer) or modify it (Mutator). 122 | type Handler struct { 123 | // Name is the name of a handler 124 | Name string `json:"handler"` 125 | // Config configures the handler. Configuration keys vary per handler. 126 | // +kubebuilder:validation:Type=object 127 | // +kubebuilder:pruning:PreserveUnknownFields 128 | // +kubebuilder:validation:XPreserveUnknownFields 129 | Config *runtime.RawExtension `json:"config,omitempty"` 130 | } 131 | 132 | // ToOathkeeperRules transforms a RuleList object into a JSON object digestible by Oathkeeper. 133 | func (rl RuleList) ToOathkeeperRules() ([]byte, error) { 134 | 135 | rules := make([]*RuleJSON, len(rl.Items)) 136 | 137 | for i := range rl.Items { 138 | rules[i] = rl.Items[i].ToRuleJSON() 139 | } 140 | 141 | return unescapedMarshalIndent(rules, "", " ") 142 | } 143 | 144 | // FilterNotValid filters out Rules which doesn't pass validation due to being not processed yet or due to negative result of validation. It returns a list of Rules which passed validation successfully. 145 | func (rl RuleList) FilterNotValid() RuleList { 146 | rlCopy := rl 147 | validRules := []Rule{} 148 | for _, rule := range rl.Items { 149 | if rule.Status.Validation != nil && rule.Status.Validation.Valid != nil && *rule.Status.Validation.Valid { 150 | validRules = append(validRules, rule) 151 | } 152 | } 153 | rlCopy.Items = validRules 154 | return rlCopy 155 | } 156 | 157 | // FilterConfigMapName filters out Rules that don't effect the given ConfigMap 158 | func (rl RuleList) FilterConfigMapName(name *string) RuleList { 159 | rlCopy := rl 160 | validRules := []Rule{} 161 | for _, rule := range rl.Items { 162 | if rule.Spec.ConfigMapName == nil { 163 | if name == nil { 164 | validRules = append(validRules, rule) 165 | } 166 | } else if *rule.Spec.ConfigMapName == *name { 167 | validRules = append(validRules, rule) 168 | } 169 | } 170 | rlCopy.Items = validRules 171 | return rlCopy 172 | } 173 | 174 | // FilterOutRule filters out the provided rule from the rule list, for re-generating the rules when a rule is deleted 175 | func (rl RuleList) FilterOutRule(r Rule) RuleList { 176 | rlCopy := rl 177 | validRules := []Rule{} 178 | for _, rule := range rl.Items { 179 | if rule.ObjectMeta.UID != r.ObjectMeta.UID { 180 | validRules = append(validRules, rule) 181 | } 182 | } 183 | rlCopy.Items = validRules 184 | return rlCopy 185 | } 186 | 187 | // ValidateWith uses provided validation configuration to check whether the rule have proper handlers set. Nil is a valid handler. 188 | func (r Rule) ValidateWith(config validation.Config) error { 189 | 190 | var invalidHandlers []string 191 | 192 | if r.Spec.Authenticators != nil { 193 | for _, authenticator := range r.Spec.Authenticators { 194 | if valid := config.IsAuthenticatorValid(authenticator.Name); !valid { 195 | invalidHandlers = append(invalidHandlers, fmt.Sprintf("authenticator/%s", authenticator.Name)) 196 | } 197 | } 198 | } 199 | 200 | if r.Spec.Authorizer != nil { 201 | if valid := config.IsAuthorizerValid(r.Spec.Authorizer.Name); !valid { 202 | invalidHandlers = append(invalidHandlers, fmt.Sprintf("authorizer/%s", r.Spec.Authorizer.Name)) 203 | } 204 | } 205 | 206 | if r.Spec.Mutators != nil { 207 | for _, m := range r.Spec.Mutators { 208 | if valid := config.IsMutatorValid(m.Name); !valid { 209 | invalidHandlers = append(invalidHandlers, m.Name) 210 | } 211 | } 212 | } 213 | 214 | if len(invalidHandlers) != 0 { 215 | return fmt.Errorf("invalid handlers: %s, please check the configuration", invalidHandlers) 216 | } 217 | 218 | return nil 219 | } 220 | 221 | // ToRuleJSON transforms a Rule object into an intermediary RuleJSON object 222 | func (r Rule) ToRuleJSON() *RuleJSON { 223 | 224 | ruleJSON := &RuleJSON{ 225 | ID: r.Name + "." + r.Namespace, 226 | RuleSpec: r.Spec, 227 | } 228 | 229 | if ruleJSON.Authenticators == nil { 230 | ruleJSON.Authenticators = []*Authenticator{{unauthorizedHandler}} 231 | } 232 | if ruleJSON.Authorizer == nil { 233 | ruleJSON.Authorizer = &Authorizer{denyHandler} 234 | } 235 | if ruleJSON.Mutators == nil { 236 | ruleJSON.Mutators = []*Mutator{{noopHandler}} 237 | } 238 | 239 | if ruleJSON.Upstream == nil { 240 | ruleJSON.Upstream = &Upstream{} 241 | } 242 | 243 | if ruleJSON.Upstream.PreserveHost == nil { 244 | ruleJSON.Upstream.PreserveHost = &preserveHostDefault 245 | } 246 | 247 | return ruleJSON 248 | } 249 | 250 | func init() { 251 | SchemeBuilder.Register(&Rule{}, &RuleList{}) 252 | } 253 | -------------------------------------------------------------------------------- /api/v1alpha1/rule_types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | _ "embed" 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/ory/oathkeeper-maester/internal/validation" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/apimachinery/pkg/runtime" 16 | ) 17 | 18 | //go:embed tests/rules.json 19 | var template string 20 | 21 | //go:embed tests/sample_config.json 22 | var sampleConfig string 23 | 24 | //go:embed tests/sample_config2.json 25 | var sampleConfig2 string 26 | 27 | func TestToOathkeeperRules(t *testing.T) { 28 | 29 | t.Run("Should convert a RuleList object into a valid JSON array", func(t *testing.T) { 30 | 31 | var list = &RuleList{} 32 | 33 | t.Run("with no elements if the 'Item' field in the RuleList object is empty", func(t *testing.T) { 34 | 35 | //given 36 | list.Items = []Rule{} 37 | 38 | //when 39 | raw, err := list.ToOathkeeperRules() 40 | 41 | //then 42 | require.NoError(t, err) 43 | assert.Equal(t, "[]", string(raw)) 44 | }) 45 | 46 | t.Run("with raw Oathkeeper rule(s) if the 'Item' field in the RuleList object is not empty", func(t *testing.T) { 47 | 48 | //given 49 | h1 := newHandler("handler1", sampleConfig) 50 | h2 := newHandler("handler2", sampleConfig2) 51 | 52 | rule1 := newRule( 53 | "foo1", 54 | "default", 55 | "http://my-backend-service1", 56 | "http://my-app/some-route1<.*>", 57 | newStringPtr("/api/v1"), 58 | nil, 59 | newBoolPtr(true), 60 | []*Authenticator{{h1}}, 61 | nil, 62 | []*Mutator{{h1}, {h2}}, 63 | []*Error{{h1}}, 64 | ) 65 | 66 | rule2 := newRule( 67 | "foo2", 68 | "default", 69 | "http://my-backend-service2", 70 | "http://my-app/some-route2", 71 | nil, 72 | nil, 73 | newBoolPtr(false), 74 | []*Authenticator{{h1}, {h2}}, 75 | nil, 76 | nil, 77 | []*Error{{h2}}) 78 | 79 | rule3 := newRule( 80 | "foo3", 81 | "default", 82 | "http://my-backend-service3", 83 | "http://my-app/some-route3", 84 | nil, 85 | nil, 86 | nil, 87 | nil, 88 | &Authorizer{h1}, 89 | nil, 90 | []*Error{{h1}, {h2}}) 91 | 92 | rule4 := newRule( 93 | "fooNoUpstream", 94 | "default", 95 | "", 96 | "http://my-app/some-route3", 97 | nil, 98 | nil, 99 | nil, 100 | nil, 101 | &Authorizer{h1}, 102 | nil, 103 | nil) 104 | 105 | list.Items = []Rule{*rule1, *rule2, *rule3, *rule4} 106 | 107 | //when 108 | raw, err := list.ToOathkeeperRules() 109 | 110 | //then 111 | require.NoError(t, err) 112 | assert.Equal(t, template, string(raw)) 113 | }) 114 | }) 115 | } 116 | 117 | func TestToRuleJson(t *testing.T) { 118 | 119 | assert := assert.New(t) 120 | 121 | var actual *RuleJSON 122 | var testHandler = newHandler("test-handler", "") 123 | var testHandler2 = newHandler("test-handler2", "") 124 | 125 | for k, tc := range []struct { 126 | desc string 127 | r *Rule 128 | extra func(json *RuleJSON) 129 | }{ 130 | 131 | { 132 | "If no handlers have been specified, it should generate an ID and add default values for missing handlers", 133 | newStaticRule(nil, nil, nil, nil), 134 | func(r *RuleJSON) { 135 | assert.Equal(unauthorizedHandler, r.Authenticators[0].Handler) 136 | assert.Equal(denyHandler, r.Authorizer.Handler) 137 | assert.Equal(noopHandler, r.Mutators[0].Handler) 138 | }, 139 | }, 140 | { 141 | "If one handler type has been provided, it should generate an ID, rewrite the provided handler and add default values for missing handlers", 142 | newStaticRule(nil, nil, []*Mutator{{testHandler}}, nil), 143 | func(r *RuleJSON) { 144 | assert.Equal(unauthorizedHandler, r.Authenticators[0].Handler) 145 | assert.Equal(denyHandler, r.Authorizer.Handler) 146 | assert.Equal(testHandler, r.Mutators[0].Handler) 147 | }, 148 | }, 149 | { 150 | "If all handler types are defined, it should generate an ID and rewrite the handlers", 151 | newStaticRule([]*Authenticator{{testHandler}}, &Authorizer{testHandler}, []*Mutator{{testHandler}}, nil), 152 | func(r *RuleJSON) { 153 | assert.Equal(testHandler, r.Authenticators[0].Handler) 154 | assert.Equal(testHandler, r.Authorizer.Handler) 155 | assert.Equal(testHandler, r.Mutators[0].Handler) 156 | }, 157 | }, 158 | { 159 | "if multiple authentication and/or mutation handlers have been provided, it should rewrite all of them", 160 | newStaticRule([]*Authenticator{{testHandler}, {testHandler2}}, nil, []*Mutator{{testHandler}, {testHandler2}}, []*Error{{testHandler}}), 161 | func(r *RuleJSON) { 162 | assert.Equal(testHandler, r.Authenticators[0].Handler) 163 | assert.Equal(testHandler2, r.Authenticators[1].Handler) 164 | assert.Equal(testHandler, r.Mutators[0].Handler) 165 | assert.Equal(testHandler2, r.Mutators[1].Handler) 166 | assert.Equal(denyHandler, r.Authorizer.Handler) 167 | assert.Equal(testHandler, r.Errors[0].Handler) 168 | }, 169 | }, 170 | } { 171 | t.Run(fmt.Sprintf("case %d: %s", k, tc.desc), func(t *testing.T) { 172 | 173 | //when 174 | actual = tc.r.ToRuleJSON() 175 | 176 | //then 177 | assert.Equal("r1.test", actual.ID) 178 | 179 | require.NotNil(t, actual.RuleSpec.Authenticators) 180 | require.NotNil(t, actual.RuleSpec.Authorizer) 181 | require.NotNil(t, actual.RuleSpec.Mutators) 182 | 183 | require.NotEmpty(t, actual.RuleSpec.Authenticators) 184 | require.NotEmpty(t, actual.RuleSpec.Mutators) 185 | 186 | //testcase-specific assertions 187 | tc.extra(actual) 188 | 189 | }) 190 | } 191 | } 192 | 193 | func TestValidateWith(t *testing.T) { 194 | 195 | var validationError error 196 | var testHandler = newHandler("handler1", sampleConfig) 197 | var rule = newRule( 198 | "foo1", 199 | "default", 200 | "http://my-backend-service1", 201 | "http://my-app/some-route1", 202 | newStringPtr("/api/v1"), 203 | nil, 204 | newBoolPtr(true), 205 | nil, 206 | nil, 207 | nil, 208 | nil) 209 | 210 | var validationConfig = validation.Config{ 211 | AuthenticatorsAvailable: []string{testHandler.Name}, 212 | AuthorizersAvailable: []string{testHandler.Name}, 213 | MutatorsAvailable: []string{testHandler.Name}, 214 | } 215 | 216 | t.Run("Should return no error for a rule with", func(t *testing.T) { 217 | 218 | t.Run("no handlers", func(t *testing.T) { 219 | 220 | //when 221 | validationError = rule.ValidateWith(validationConfig) 222 | 223 | //then 224 | require.NoError(t, validationError) 225 | }) 226 | 227 | t.Run("allowed handlers", func(t *testing.T) { 228 | 229 | //given 230 | rule.Spec.Authenticators = []*Authenticator{{testHandler}} 231 | rule.Spec.Authorizer = &Authorizer{testHandler} 232 | rule.Spec.Mutators = []*Mutator{{testHandler}} 233 | 234 | //when 235 | validationError = rule.ValidateWith(validationConfig) 236 | 237 | //then 238 | require.NoError(t, validationError) 239 | }) 240 | }) 241 | 242 | t.Run("Should return an error for a rule with", func(t *testing.T) { 243 | 244 | t.Run("forbidden handler(s)", func(t *testing.T) { 245 | 246 | //given 247 | invalidTestHandler := newHandler("notValidHandlerName", sampleConfig) 248 | rule.Spec.Authenticators = []*Authenticator{{testHandler}, {invalidTestHandler}} 249 | 250 | //when 251 | validationError = rule.ValidateWith(validationConfig) 252 | 253 | //then 254 | require.Error(t, validationError) 255 | }) 256 | }) 257 | } 258 | 259 | func TestFilterNotValid(t *testing.T) { 260 | 261 | t.Run("Should return only valid rules", func(t *testing.T) { 262 | 263 | //given 264 | rule1 := newRuleWithStatusOnly(false, newStringPtr("authenticator: sample is invalid")) 265 | rule2 := newRuleWithStatusOnly(false, nil) 266 | rule3 := newRuleWithStatusOnly(true, nil) 267 | 268 | list := &RuleList{Items: []Rule{rule1, rule2, rule3}} 269 | 270 | //when 271 | validationResult := list.FilterNotValid().Items 272 | 273 | //then 274 | require.NotEmpty(t, validationResult) 275 | require.Len(t, validationResult, 1) 276 | 277 | assert.Equal(t, rule3, validationResult[0]) 278 | }) 279 | } 280 | 281 | func newStaticRule(authenticators []*Authenticator, authorizer *Authorizer, mutators []*Mutator, errors []*Error) *Rule { 282 | return newRule("r1", "test", "", "", newStringPtr(""), nil, newBoolPtr(false), authenticators, authorizer, mutators, errors) 283 | } 284 | 285 | func newRule(name, namespace, upstreamURL, matchURL string, stripURLPath, configMapName *string, preserveURLHost *bool, authenticators []*Authenticator, authorizer *Authorizer, mutators []*Mutator, errors []*Error) *Rule { 286 | 287 | spec := RuleSpec{ 288 | Upstream: &Upstream{ 289 | URL: upstreamURL, 290 | PreserveHost: preserveURLHost, 291 | StripPath: stripURLPath, 292 | }, 293 | Match: &Match{ 294 | URL: matchURL, 295 | Methods: []string{"GET", "POST"}, 296 | }, 297 | Authenticators: authenticators, 298 | Authorizer: authorizer, 299 | Mutators: mutators, 300 | Errors: errors, 301 | ConfigMapName: configMapName, 302 | } 303 | 304 | return &Rule{ 305 | ObjectMeta: metav1.ObjectMeta{ 306 | Name: name, 307 | Namespace: namespace, 308 | }, 309 | Spec: spec, 310 | } 311 | } 312 | 313 | func newRuleWithStatusOnly(valid bool, validationError *string) Rule { 314 | return Rule{ 315 | Status: RuleStatus{ 316 | Validation: &Validation{ 317 | Valid: &valid, 318 | Error: validationError, 319 | }, 320 | }, 321 | } 322 | } 323 | 324 | func newHandler(name, config string) *Handler { 325 | h := &Handler{ 326 | Name: name, 327 | } 328 | 329 | if config != "" { 330 | h.Config = &runtime.RawExtension{ 331 | Raw: []byte(config), 332 | } 333 | } 334 | 335 | return h 336 | } 337 | 338 | func newBoolPtr(b bool) *bool { 339 | return &b 340 | } 341 | 342 | func newStringPtr(s string) *string { 343 | return &s 344 | } 345 | -------------------------------------------------------------------------------- /api/v1alpha1/tests/rules.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "upstream": { 4 | "url": "http://my-backend-service1", 5 | "strip_path": "/api/v1", 6 | "preserve_host": true 7 | }, 8 | "id": "foo1.default", 9 | "match": { 10 | "url": "http://my-app/some-route1<.*>", 11 | "methods": [ 12 | "GET", 13 | "POST" 14 | ] 15 | }, 16 | "authenticators": [ 17 | { 18 | "handler": "handler1", 19 | "config": { 20 | "key1": "val1" 21 | } 22 | } 23 | ], 24 | "authorizer": { 25 | "handler": "deny" 26 | }, 27 | "mutators": [ 28 | { 29 | "handler": "handler1", 30 | "config": { 31 | "key1": "val1" 32 | } 33 | }, 34 | { 35 | "handler": "handler2", 36 | "config": { 37 | "key1": [ 38 | "val1", 39 | "val2", 40 | "val3" 41 | ] 42 | } 43 | } 44 | ], 45 | "errors": [ 46 | { 47 | "handler": "handler1", 48 | "config": { 49 | "key1": "val1" 50 | } 51 | } 52 | ] 53 | }, 54 | { 55 | "upstream": { 56 | "url": "http://my-backend-service2", 57 | "preserve_host": false 58 | }, 59 | "id": "foo2.default", 60 | "match": { 61 | "url": "http://my-app/some-route2", 62 | "methods": [ 63 | "GET", 64 | "POST" 65 | ] 66 | }, 67 | "authenticators": [ 68 | { 69 | "handler": "handler1", 70 | "config": { 71 | "key1": "val1" 72 | } 73 | }, 74 | { 75 | "handler": "handler2", 76 | "config": { 77 | "key1": [ 78 | "val1", 79 | "val2", 80 | "val3" 81 | ] 82 | } 83 | } 84 | ], 85 | "authorizer": { 86 | "handler": "deny" 87 | }, 88 | "mutators": [ 89 | { 90 | "handler": "noop" 91 | } 92 | ], 93 | "errors": [ 94 | { 95 | "handler": "handler2", 96 | "config": { 97 | "key1": [ 98 | "val1", 99 | "val2", 100 | "val3" 101 | ] 102 | } 103 | } 104 | ] 105 | }, 106 | { 107 | "upstream": { 108 | "url": "http://my-backend-service3", 109 | "preserve_host": false 110 | }, 111 | "id": "foo3.default", 112 | "match": { 113 | "url": "http://my-app/some-route3", 114 | "methods": [ 115 | "GET", 116 | "POST" 117 | ] 118 | }, 119 | "authenticators": [ 120 | { 121 | "handler": "unauthorized" 122 | } 123 | ], 124 | "authorizer": { 125 | "handler": "handler1", 126 | "config": { 127 | "key1": "val1" 128 | } 129 | }, 130 | "mutators": [ 131 | { 132 | "handler": "noop" 133 | } 134 | ], 135 | "errors": [ 136 | { 137 | "handler": "handler1", 138 | "config": { 139 | "key1": "val1" 140 | } 141 | }, 142 | { 143 | "handler": "handler2", 144 | "config": { 145 | "key1": [ 146 | "val1", 147 | "val2", 148 | "val3" 149 | ] 150 | } 151 | } 152 | ] 153 | }, 154 | { 155 | "upstream": { 156 | "url": "", 157 | "preserve_host": false 158 | }, 159 | "id": "fooNoUpstream.default", 160 | "match": { 161 | "url": "http://my-app/some-route3", 162 | "methods": [ 163 | "GET", 164 | "POST" 165 | ] 166 | }, 167 | "authenticators": [ 168 | { 169 | "handler": "unauthorized" 170 | } 171 | ], 172 | "authorizer": { 173 | "handler": "handler1", 174 | "config": { 175 | "key1": "val1" 176 | } 177 | }, 178 | "mutators": [ 179 | { 180 | "handler": "noop" 181 | } 182 | ] 183 | } 184 | ] -------------------------------------------------------------------------------- /api/v1alpha1/tests/sample_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "val1" 3 | } -------------------------------------------------------------------------------- /api/v1alpha1/tests/sample_config2.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": [ 3 | "val1", 4 | "val2", 5 | "val3" 6 | ] 7 | } -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2024 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !ignore_autogenerated 5 | 6 | /* 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | */ 20 | 21 | // Code generated by controller-gen. DO NOT EDIT. 22 | 23 | package v1alpha1 24 | 25 | import ( 26 | "k8s.io/apimachinery/pkg/runtime" 27 | ) 28 | 29 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 30 | func (in *Authenticator) DeepCopyInto(out *Authenticator) { 31 | *out = *in 32 | if in.Handler != nil { 33 | in, out := &in.Handler, &out.Handler 34 | *out = new(Handler) 35 | (*in).DeepCopyInto(*out) 36 | } 37 | } 38 | 39 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authenticator. 40 | func (in *Authenticator) DeepCopy() *Authenticator { 41 | if in == nil { 42 | return nil 43 | } 44 | out := new(Authenticator) 45 | in.DeepCopyInto(out) 46 | return out 47 | } 48 | 49 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 50 | func (in *Authorizer) DeepCopyInto(out *Authorizer) { 51 | *out = *in 52 | if in.Handler != nil { 53 | in, out := &in.Handler, &out.Handler 54 | *out = new(Handler) 55 | (*in).DeepCopyInto(*out) 56 | } 57 | } 58 | 59 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authorizer. 60 | func (in *Authorizer) DeepCopy() *Authorizer { 61 | if in == nil { 62 | return nil 63 | } 64 | out := new(Authorizer) 65 | in.DeepCopyInto(out) 66 | return out 67 | } 68 | 69 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 70 | func (in *Error) DeepCopyInto(out *Error) { 71 | *out = *in 72 | if in.Handler != nil { 73 | in, out := &in.Handler, &out.Handler 74 | *out = new(Handler) 75 | (*in).DeepCopyInto(*out) 76 | } 77 | } 78 | 79 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Error. 80 | func (in *Error) DeepCopy() *Error { 81 | if in == nil { 82 | return nil 83 | } 84 | out := new(Error) 85 | in.DeepCopyInto(out) 86 | return out 87 | } 88 | 89 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 90 | func (in *Handler) DeepCopyInto(out *Handler) { 91 | *out = *in 92 | if in.Config != nil { 93 | in, out := &in.Config, &out.Config 94 | *out = new(runtime.RawExtension) 95 | (*in).DeepCopyInto(*out) 96 | } 97 | } 98 | 99 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Handler. 100 | func (in *Handler) DeepCopy() *Handler { 101 | if in == nil { 102 | return nil 103 | } 104 | out := new(Handler) 105 | in.DeepCopyInto(out) 106 | return out 107 | } 108 | 109 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 110 | func (in *Match) DeepCopyInto(out *Match) { 111 | *out = *in 112 | if in.Methods != nil { 113 | in, out := &in.Methods, &out.Methods 114 | *out = make([]string, len(*in)) 115 | copy(*out, *in) 116 | } 117 | } 118 | 119 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Match. 120 | func (in *Match) DeepCopy() *Match { 121 | if in == nil { 122 | return nil 123 | } 124 | out := new(Match) 125 | in.DeepCopyInto(out) 126 | return out 127 | } 128 | 129 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 130 | func (in *Mutator) DeepCopyInto(out *Mutator) { 131 | *out = *in 132 | if in.Handler != nil { 133 | in, out := &in.Handler, &out.Handler 134 | *out = new(Handler) 135 | (*in).DeepCopyInto(*out) 136 | } 137 | } 138 | 139 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mutator. 140 | func (in *Mutator) DeepCopy() *Mutator { 141 | if in == nil { 142 | return nil 143 | } 144 | out := new(Mutator) 145 | in.DeepCopyInto(out) 146 | return out 147 | } 148 | 149 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 150 | func (in *Rule) DeepCopyInto(out *Rule) { 151 | *out = *in 152 | out.TypeMeta = in.TypeMeta 153 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 154 | in.Spec.DeepCopyInto(&out.Spec) 155 | in.Status.DeepCopyInto(&out.Status) 156 | } 157 | 158 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule. 159 | func (in *Rule) DeepCopy() *Rule { 160 | if in == nil { 161 | return nil 162 | } 163 | out := new(Rule) 164 | in.DeepCopyInto(out) 165 | return out 166 | } 167 | 168 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 169 | func (in *Rule) DeepCopyObject() runtime.Object { 170 | if c := in.DeepCopy(); c != nil { 171 | return c 172 | } 173 | return nil 174 | } 175 | 176 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 177 | func (in *RuleJSON) DeepCopyInto(out *RuleJSON) { 178 | *out = *in 179 | in.RuleSpec.DeepCopyInto(&out.RuleSpec) 180 | } 181 | 182 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleJSON. 183 | func (in *RuleJSON) DeepCopy() *RuleJSON { 184 | if in == nil { 185 | return nil 186 | } 187 | out := new(RuleJSON) 188 | in.DeepCopyInto(out) 189 | return out 190 | } 191 | 192 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 193 | func (in *RuleList) DeepCopyInto(out *RuleList) { 194 | *out = *in 195 | out.TypeMeta = in.TypeMeta 196 | in.ListMeta.DeepCopyInto(&out.ListMeta) 197 | if in.Items != nil { 198 | in, out := &in.Items, &out.Items 199 | *out = make([]Rule, len(*in)) 200 | for i := range *in { 201 | (*in)[i].DeepCopyInto(&(*out)[i]) 202 | } 203 | } 204 | } 205 | 206 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleList. 207 | func (in *RuleList) DeepCopy() *RuleList { 208 | if in == nil { 209 | return nil 210 | } 211 | out := new(RuleList) 212 | in.DeepCopyInto(out) 213 | return out 214 | } 215 | 216 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 217 | func (in *RuleList) DeepCopyObject() runtime.Object { 218 | if c := in.DeepCopy(); c != nil { 219 | return c 220 | } 221 | return nil 222 | } 223 | 224 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 225 | func (in *RuleSpec) DeepCopyInto(out *RuleSpec) { 226 | *out = *in 227 | if in.Upstream != nil { 228 | in, out := &in.Upstream, &out.Upstream 229 | *out = new(Upstream) 230 | (*in).DeepCopyInto(*out) 231 | } 232 | if in.Match != nil { 233 | in, out := &in.Match, &out.Match 234 | *out = new(Match) 235 | (*in).DeepCopyInto(*out) 236 | } 237 | if in.Authenticators != nil { 238 | in, out := &in.Authenticators, &out.Authenticators 239 | *out = make([]*Authenticator, len(*in)) 240 | for i := range *in { 241 | if (*in)[i] != nil { 242 | in, out := &(*in)[i], &(*out)[i] 243 | *out = new(Authenticator) 244 | (*in).DeepCopyInto(*out) 245 | } 246 | } 247 | } 248 | if in.Authorizer != nil { 249 | in, out := &in.Authorizer, &out.Authorizer 250 | *out = new(Authorizer) 251 | (*in).DeepCopyInto(*out) 252 | } 253 | if in.Mutators != nil { 254 | in, out := &in.Mutators, &out.Mutators 255 | *out = make([]*Mutator, len(*in)) 256 | for i := range *in { 257 | if (*in)[i] != nil { 258 | in, out := &(*in)[i], &(*out)[i] 259 | *out = new(Mutator) 260 | (*in).DeepCopyInto(*out) 261 | } 262 | } 263 | } 264 | if in.Errors != nil { 265 | in, out := &in.Errors, &out.Errors 266 | *out = make([]*Error, len(*in)) 267 | for i := range *in { 268 | if (*in)[i] != nil { 269 | in, out := &(*in)[i], &(*out)[i] 270 | *out = new(Error) 271 | (*in).DeepCopyInto(*out) 272 | } 273 | } 274 | } 275 | if in.ConfigMapName != nil { 276 | in, out := &in.ConfigMapName, &out.ConfigMapName 277 | *out = new(string) 278 | **out = **in 279 | } 280 | } 281 | 282 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleSpec. 283 | func (in *RuleSpec) DeepCopy() *RuleSpec { 284 | if in == nil { 285 | return nil 286 | } 287 | out := new(RuleSpec) 288 | in.DeepCopyInto(out) 289 | return out 290 | } 291 | 292 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 293 | func (in *RuleStatus) DeepCopyInto(out *RuleStatus) { 294 | *out = *in 295 | if in.Validation != nil { 296 | in, out := &in.Validation, &out.Validation 297 | *out = new(Validation) 298 | (*in).DeepCopyInto(*out) 299 | } 300 | } 301 | 302 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleStatus. 303 | func (in *RuleStatus) DeepCopy() *RuleStatus { 304 | if in == nil { 305 | return nil 306 | } 307 | out := new(RuleStatus) 308 | in.DeepCopyInto(out) 309 | return out 310 | } 311 | 312 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 313 | func (in *Upstream) DeepCopyInto(out *Upstream) { 314 | *out = *in 315 | if in.StripPath != nil { 316 | in, out := &in.StripPath, &out.StripPath 317 | *out = new(string) 318 | **out = **in 319 | } 320 | if in.PreserveHost != nil { 321 | in, out := &in.PreserveHost, &out.PreserveHost 322 | *out = new(bool) 323 | **out = **in 324 | } 325 | } 326 | 327 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Upstream. 328 | func (in *Upstream) DeepCopy() *Upstream { 329 | if in == nil { 330 | return nil 331 | } 332 | out := new(Upstream) 333 | in.DeepCopyInto(out) 334 | return out 335 | } 336 | 337 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 338 | func (in *UpstreamJSON) DeepCopyInto(out *UpstreamJSON) { 339 | *out = *in 340 | if in.StripPath != nil { 341 | in, out := &in.StripPath, &out.StripPath 342 | *out = new(string) 343 | **out = **in 344 | } 345 | if in.PreserveHost != nil { 346 | in, out := &in.PreserveHost, &out.PreserveHost 347 | *out = new(bool) 348 | **out = **in 349 | } 350 | } 351 | 352 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpstreamJSON. 353 | func (in *UpstreamJSON) DeepCopy() *UpstreamJSON { 354 | if in == nil { 355 | return nil 356 | } 357 | out := new(UpstreamJSON) 358 | in.DeepCopyInto(out) 359 | return out 360 | } 361 | 362 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 363 | func (in *Validation) DeepCopyInto(out *Validation) { 364 | *out = *in 365 | if in.Valid != nil { 366 | in, out := &in.Valid, &out.Valid 367 | *out = new(bool) 368 | **out = **in 369 | } 370 | if in.Error != nil { 371 | in, out := &in.Error, &out.Error 372 | *out = new(string) 373 | **out = **in 374 | } 375 | } 376 | 377 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Validation. 378 | func (in *Validation) DeepCopy() *Validation { 379 | if in == nil { 380 | return nil 381 | } 382 | out := new(Validation) 383 | in.DeepCopyInto(out) 384 | return out 385 | } 386 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: certmanager.k8s.io/v1alpha1 4 | kind: Issuer 5 | metadata: 6 | name: selfsigned-issuer 7 | namespace: system 8 | spec: 9 | selfSigned: {} 10 | --- 11 | apiVersion: certmanager.k8s.io/v1alpha1 12 | kind: Certificate 13 | metadata: 14 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 15 | namespace: system 16 | spec: 17 | # $(SERVICENAME) and $(NAMESPACE) will be substituted by kustomize 18 | commonName: $(SERVICENAME).$(NAMESPACE).svc 19 | dnsNames: 20 | - $(SERVICENAME).$(NAMESPACE).svc.cluster.local 21 | issuerRef: 22 | kind: Issuer 23 | name: selfsigned-issuer 24 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 25 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | # the following config is for teaching kustomize how to do var substitution 5 | vars: 6 | - fieldref: 7 | fieldpath: metadata.namespace 8 | name: NAMESPACE 9 | objref: 10 | kind: Service 11 | name: webhook-service 12 | version: v1 13 | - fieldref: 14 | fieldpath: "" 15 | name: CERTIFICATENAME 16 | objref: 17 | group: certmanager.k8s.io 18 | kind: Certificate 19 | name: serving-cert 20 | version: v1alpha1 21 | - fieldref: 22 | fieldpath: metadata.name 23 | name: SERVICENAME 24 | objref: 25 | kind: Service 26 | name: webhook-service 27 | version: v1 28 | 29 | configurations: 30 | - kustomizeconfig.yaml 31 | apiVersion: kustomize.config.k8s.io/v1beta1 32 | kind: Kustomization 33 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: certmanager.k8s.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: certmanager.k8s.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: certmanager.k8s.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: certmanager.k8s.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/oathkeeper.ory.sh_rules.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.15.0 7 | name: rules.oathkeeper.ory.sh 8 | spec: 9 | group: oathkeeper.ory.sh 10 | names: 11 | kind: Rule 12 | listKind: RuleList 13 | plural: rules 14 | singular: rule 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: Rule is the Schema for the rules API 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: RuleSpec defines the desired state of Rule 41 | properties: 42 | authenticators: 43 | items: 44 | description: 45 | Authenticator represents a handler that authenticates 46 | provided credentials. 47 | properties: 48 | config: 49 | description: 50 | Config configures the handler. Configuration keys vary 51 | per handler. 52 | type: object 53 | x-kubernetes-preserve-unknown-fields: true 54 | handler: 55 | description: Name is the name of a handler 56 | type: string 57 | required: 58 | - handler 59 | type: object 60 | type: array 61 | authorizer: 62 | description: 63 | Authorizer represents a handler that authorizes the subject 64 | ("user") from the previously validated credentials making 65 | the request. 66 | properties: 67 | config: 68 | description: 69 | Config configures the handler. Configuration keys vary 70 | per handler. 71 | type: object 72 | x-kubernetes-preserve-unknown-fields: true 73 | handler: 74 | description: Name is the name of a handler 75 | type: string 76 | required: 77 | - handler 78 | type: object 79 | configMapName: 80 | description: 81 | ConfigMapName points to the K8s ConfigMap that contains 82 | these rules 83 | maxLength: 253 84 | minLength: 1 85 | pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' 86 | type: string 87 | errors: 88 | items: 89 | description: 90 | Error represents a handler that is responsible for 91 | executing logic when an error happens. 92 | properties: 93 | config: 94 | description: 95 | Config configures the handler. Configuration keys vary 96 | per handler. 97 | type: object 98 | x-kubernetes-preserve-unknown-fields: true 99 | handler: 100 | description: Name is the name of a handler 101 | type: string 102 | required: 103 | - handler 104 | type: object 105 | type: array 106 | match: 107 | description: 108 | Match defines the URL(s) that an access rule should match. 109 | properties: 110 | methods: 111 | description: 112 | Methods represent an array of HTTP methods (e.g. GET, 113 | POST, PUT, DELETE, ...) 114 | items: 115 | type: string 116 | type: array 117 | url: 118 | description: 119 | URL is the URL that should be matched. It supports regex 120 | templates. 121 | type: string 122 | required: 123 | - methods 124 | - url 125 | type: object 126 | mutators: 127 | items: 128 | description: 129 | Mutator represents a handler that transforms the HTTP 130 | request before forwarding it. 131 | properties: 132 | config: 133 | description: 134 | Config configures the handler. Configuration keys vary 135 | per handler. 136 | type: object 137 | x-kubernetes-preserve-unknown-fields: true 138 | handler: 139 | description: Name is the name of a handler 140 | type: string 141 | required: 142 | - handler 143 | type: object 144 | type: array 145 | upstream: 146 | description: 147 | Upstream represents the location of a server where requests 148 | matching a rule should be forwarded to. 149 | properties: 150 | preserveHost: 151 | description: 152 | PreserveHost includes the host and port of the url value 153 | if set to false. If true, the host and port of the ORY 154 | Oathkeeper Proxy will be used instead. 155 | type: boolean 156 | stripPath: 157 | description: 158 | StripPath replaces the provided path prefix when 159 | forwarding the requested URL to the upstream URL. 160 | type: string 161 | url: 162 | description: 163 | URL defines the target URL for incoming requests 164 | maxLength: 256 165 | minLength: 3 166 | pattern: ^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+) 167 | type: string 168 | required: 169 | - url 170 | type: object 171 | required: 172 | - match 173 | type: object 174 | status: 175 | description: RuleStatus defines the observed state of Rule 176 | properties: 177 | validation: 178 | description: Validation defines the validation state of Rule 179 | properties: 180 | valid: 181 | type: boolean 182 | validationError: 183 | type: string 184 | type: object 185 | type: object 186 | type: object 187 | served: true 188 | storage: true 189 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/oathkeeper.ory.sh_rules.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | # [WEBHOOK] patches here are for enabling the conversion webhook for each CRD 9 | #- patches/webhook_in_rules.yaml 10 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 11 | 12 | # [CAINJECTION] patches here are for enabling the CA injection for each CRD 13 | #- patches/cainjection_in_rules.yaml 14 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 15 | 16 | # the following config is for teaching kustomize how to do kustomization for CRDs. 17 | configurations: 18 | - kustomizeconfig.yaml 19 | apiVersion: kustomize.config.k8s.io/v1beta1 20 | kind: Kustomization 21 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_rules.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME) 8 | name: rules.oathkeeper.ory.sh 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_rules.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: rules.oathkeeper.ory.sh 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: oathkeeper-maester-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: oathkeeper-maester- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 16 | #- ../webhook 17 | # [CERTMANAGER] To enable cert-manager, uncomment next line. 'WEBHOOK' components are required. 18 | #- ../certmanager 19 | 20 | # Protect the /metrics endpoint by putting it behind auth. 21 | # Only one of manager_auth_proxy_patch.yaml and 22 | # manager_prometheus_metrics_patch.yaml should be enabled. 23 | apiVersion: kustomize.config.k8s.io/v1beta1 24 | kind: Kustomization 25 | resources: 26 | - ../crd 27 | - ../rbac 28 | - ../manager 29 | patches: 30 | - path: manager_image_patch.yaml 31 | - path: manager_auth_proxy_patch.yaml 32 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the controller manager, 2 | # it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | -------------------------------------------------------------------------------- /config/default/manager_image_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | # Change the value of image field below to your controller image URL 11 | - image: controller:latest 12 | name: manager 13 | -------------------------------------------------------------------------------- /config/default/manager_image_patch.yaml-e: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | # Change the value of image field below to your controller image URL 11 | - image: controller:latest 12 | name: manager 13 | -------------------------------------------------------------------------------- /config/default/manager_prometheus_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch enables Prometheus scraping for the manager pod. 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | metadata: 10 | annotations: 11 | prometheus.io/scrape: "true" 12 | spec: 13 | containers: 14 | # Expose the prometheus metrics on default port 15 | - name: manager 16 | ports: 17 | - containerPort: 8080 18 | name: metrics 19 | protocol: TCP 20 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(NAMESPACE) and $(CERTIFICATENAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME) 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | containers: 26 | - command: 27 | - /manager 28 | args: 29 | - --enable-leader-election 30 | image: controller:latest 31 | name: manager 32 | imagePullPolicy: IfNotPresent 33 | resources: 34 | requests: 35 | cpu: 100m 36 | memory: 64Mi 37 | terminationGracePeriodSeconds: 10 38 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | prometheus.io/port: "8443" 6 | prometheus.io/scheme: https 7 | prometheus.io/scrape: "true" 8 | labels: 9 | control-plane: controller-manager 10 | name: controller-manager-metrics-service 11 | namespace: system 12 | spec: 13 | ports: 14 | - name: https 15 | port: 8443 16 | targetPort: https 17 | selector: 18 | control-plane: controller-manager 19 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Comment the following 3 lines if you want to disable 2 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 3 | # which protects your /metrics endpoint. 4 | resources: 5 | - role.yaml 6 | - role_binding.yaml 7 | - leader_election_role.yaml 8 | - leader_election_role_binding.yaml 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | apiVersion: kustomize.config.k8s.io/v1beta1 13 | kind: Kustomization 14 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - oathkeeper.ory.sh 21 | resources: 22 | - rules 23 | verbs: 24 | - create 25 | - delete 26 | - get 27 | - list 28 | - patch 29 | - update 30 | - watch 31 | - apiGroups: 32 | - oathkeeper.ory.sh 33 | resources: 34 | - rules/status 35 | verbs: 36 | - get 37 | - patch 38 | - update 39 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/oathkeeper_v1alpha1_rule.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: test-ns-1 6 | --- 7 | apiVersion: oathkeeper.ory.sh/v1alpha1 8 | kind: Rule 9 | metadata: 10 | name: sample-rule-1 11 | namespace: test-ns-1 12 | spec: 13 | upstream: 14 | url: "http://abc.ef" 15 | preserveHost: false 16 | match: 17 | methods: ["GET"] 18 | url: ://foo.bar 19 | authenticators: 20 | - handler: anonymous 21 | authorizer: 22 | handler: allow 23 | mutators: 24 | - handler: noop 25 | config: {} 26 | --- 27 | apiVersion: oathkeeper.ory.sh/v1alpha1 28 | kind: Rule 29 | metadata: 30 | name: sample-rule-no-upstream 31 | namespace: test-ns-1 32 | spec: 33 | match: 34 | methods: ["GET"] 35 | url: ://foo.bar 36 | authenticators: 37 | - handler: anonymous 38 | authorizer: 39 | handler: allow 40 | mutators: 41 | - handler: noop 42 | config: {} 43 | --- 44 | apiVersion: oathkeeper.ory.sh/v1alpha1 45 | kind: Rule 46 | metadata: 47 | name: sample-rule-2 48 | namespace: test-ns-1 49 | spec: 50 | upstream: 51 | url: "http://abc.ef" 52 | preserveHost: false 53 | match: 54 | methods: ["GET"] 55 | url: ://foo.bar 56 | authenticators: 57 | - handler: anonymous 58 | authorizer: 59 | handler: allow 60 | mutators: 61 | - handler: noop 62 | config: {} 63 | --- 64 | apiVersion: v1 65 | kind: Namespace 66 | metadata: 67 | name: test-ns-2 68 | --- 69 | apiVersion: oathkeeper.ory.sh/v1alpha1 70 | kind: Rule 71 | metadata: 72 | name: sample-rule-1 73 | namespace: test-ns-2 74 | spec: 75 | upstream: 76 | url: "http://abc.ef" 77 | preserveHost: false 78 | match: 79 | methods: ["GET"] 80 | url: ://foo.bar 81 | authenticators: 82 | - handler: anonymous 83 | authorizer: 84 | handler: allow 85 | mutators: 86 | - handler: noop 87 | config: {} 88 | --- 89 | apiVersion: oathkeeper.ory.sh/v1alpha1 90 | kind: Rule 91 | metadata: 92 | name: sample-rule-2 93 | namespace: test-ns-2 94 | spec: 95 | upstream: 96 | url: "http://abc.ef" 97 | preserveHost: false 98 | match: 99 | methods: ["GET"] 100 | url: ://foo.bar 101 | authenticators: 102 | - handler: anonymous 103 | authorizer: 104 | handler: allow 105 | mutators: 106 | - handler: noop 107 | config: {} 108 | --- 109 | apiVersion: oathkeeper.ory.sh/v1alpha1 110 | kind: Rule 111 | metadata: 112 | name: sample-rule-cm 113 | namespace: default 114 | spec: 115 | configMapName: some-cm 116 | upstream: 117 | url: "http://abc.ef" 118 | preserveHost: false 119 | match: 120 | methods: ["GET"] 121 | url: ://foo.bar 122 | authenticators: 123 | - handler: anonymous 124 | authorizer: 125 | handler: allow 126 | mutators: 127 | - handler: noop 128 | config: {} 129 | --- 130 | apiVersion: oathkeeper.ory.sh/v1alpha1 131 | kind: Rule 132 | metadata: 133 | name: sample-rule-handlers-1 134 | namespace: default 135 | spec: 136 | configMapName: handlers-test-cm 137 | upstream: 138 | url: "http://abc.ef" 139 | preserveHost: false 140 | match: 141 | methods: ["GET"] 142 | url: ://foo.bar 143 | authenticators: 144 | - handler: anonymous 145 | config: 146 | foo: bar 147 | long: | 148 | { 149 | "bar": "foo" 150 | } 151 | authorizer: 152 | handler: allow 153 | config: 154 | lorem: ipsum 155 | answer: 42 156 | mutators: 157 | - handler: noop 158 | config: {} 159 | - handler: id_token 160 | config: 161 | ttl: 3600s 162 | claims: | 163 | { 164 | "aud": [ "hub.animeapis.dev" ], 165 | "session": {{ .Extra | toJson }} 166 | } 167 | --- 168 | apiVersion: oathkeeper.ory.sh/v1alpha1 169 | kind: Rule 170 | metadata: 171 | name: sample-rule-handlers-2 172 | namespace: default 173 | spec: 174 | configMapName: handlers-test-cm 175 | authenticators: 176 | - handler: anonymous 177 | - handler: cookie_session 178 | authorizer: 179 | handler: allow 180 | mutators: 181 | - handler: id_token 182 | config: 183 | ttl: 3600s 184 | claims: | 185 | { 186 | "aud": [ "hub.animeapis.dev" ], 187 | "session": {{ .Extra | toJson }} 188 | } 189 | match: 190 | url: <{https,http}>://hub.animeshon.dev/<**> 191 | methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"] 192 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | apiVersion: kustomize.config.k8s.io/v1beta1 8 | kind: Kustomization 9 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ory/oathkeeper-maester/20f674ef03c865826af7f90db7666f855adc2738/config/webhook/manifests.yaml -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: webhook-service 5 | namespace: system 6 | spec: 7 | ports: 8 | - port: 443 9 | targetPort: 443 10 | selector: 11 | control-plane: controller-manager 12 | -------------------------------------------------------------------------------- /controllers/operators.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package controllers 5 | 6 | import ( 7 | "bufio" 8 | "context" 9 | "fmt" 10 | "os" 11 | 12 | "github.com/avast/retry-go" 13 | "github.com/go-logr/logr" 14 | oathkeeperv1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" 15 | apiv1 "k8s.io/api/core/v1" 16 | apierrs "k8s.io/apimachinery/pkg/api/errors" 17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 | "k8s.io/apimachinery/pkg/types" 19 | "sigs.k8s.io/controller-runtime/pkg/client" 20 | ) 21 | 22 | // OperatorMode is an interface that provides runtime strategy for operating mode ("controller" or "sidecar"). 23 | type OperatorMode interface { 24 | // CreateOrUpdate ORY Oathkeeper Access Rule list using implementation-specific means. 25 | // oathkeeperRulesJSON - serialized JSON with an array of objects that conform to Oathkeeper Rule syntax 26 | // triggeredBy - the recently created/update rule that triggered the operation 27 | CreateOrUpdate(ctx context.Context, oathkeeperRulesJSON []byte, triggeredBy *oathkeeperv1alpha1.Rule) error 28 | } 29 | 30 | // ConfigMapOperator that maintains Oathkeeper rules as an json-formatted entry in a ConfigMap 31 | type ConfigMapOperator struct { 32 | client.Client 33 | Log logr.Logger 34 | DefaultConfigMap types.NamespacedName 35 | RulesFileName string 36 | } 37 | 38 | // FilesOperator that maintains Oathkeeper rules as a flat json file in a local filesystem 39 | type FilesOperator struct { 40 | Log logr.Logger 41 | RulesFilePath string 42 | } 43 | 44 | func (cmo *ConfigMapOperator) updateOrCreateRulesConfigmap(ctx context.Context, configMap types.NamespacedName, data string) error { 45 | 46 | var oathkeeperRulesConfigmap apiv1.ConfigMap 47 | var exists = false 48 | 49 | fetchMapFunc := func() error { 50 | 51 | if err := cmo.Get(ctx, configMap, &oathkeeperRulesConfigmap); err != nil { 52 | 53 | if apierrs.IsForbidden(err) { 54 | return retry.Unrecoverable(err) 55 | } 56 | 57 | if apierrs.IsNotFound(err) { 58 | return nil 59 | } 60 | 61 | return err 62 | } 63 | 64 | exists = true 65 | return nil 66 | } 67 | 68 | createMapFunc := func() error { 69 | cmo.Log.Info("creating ConfigMap") 70 | oathkeeperRulesConfigmap = apiv1.ConfigMap{ 71 | ObjectMeta: metav1.ObjectMeta{ 72 | Name: configMap.Name, 73 | Namespace: configMap.Namespace, 74 | }, 75 | Data: map[string]string{cmo.RulesFileName: data}, 76 | } 77 | return cmo.Create(ctx, &oathkeeperRulesConfigmap) 78 | } 79 | 80 | updateMapFunc := func() error { 81 | cmo.Log.Info("updating ConfigMap") 82 | oathkeeperRulesConfigmap.Data = map[string]string{cmo.RulesFileName: data} 83 | err := cmo.Update(ctx, &oathkeeperRulesConfigmap) 84 | return err 85 | } 86 | 87 | return retryOnError(func() error { 88 | exists = false 89 | 90 | if err := fetchMapFunc(); err != nil { 91 | return err 92 | } 93 | 94 | if exists { 95 | err := updateMapFunc() 96 | if err != nil { 97 | if isObjectHasBeenModified(err) { 98 | cmo.Log.Error(err, "incorrect object version during ConfigMap update") 99 | } 100 | } 101 | return err 102 | } 103 | 104 | return createMapFunc() 105 | }) 106 | } 107 | 108 | func (cmo *ConfigMapOperator) CreateOrUpdate(ctx context.Context, oathkeeperRulesJSON []byte, triggeredBy *oathkeeperv1alpha1.Rule) error { 109 | 110 | configMapRef := cmo.DefaultConfigMap 111 | if triggeredBy != nil && triggeredBy.Spec.ConfigMapName != nil && len(*triggeredBy.Spec.ConfigMapName) > 0 { 112 | configMapRef = types.NamespacedName{ 113 | Name: *triggeredBy.Spec.ConfigMapName, 114 | Namespace: triggeredBy.ObjectMeta.Namespace, 115 | } 116 | } 117 | return cmo.updateOrCreateRulesConfigmap(ctx, configMapRef, string(oathkeeperRulesJSON)) 118 | } 119 | 120 | func (fo *FilesOperator) updateOrCreateRulesFile(ctx context.Context, data string) error { 121 | var f *os.File 122 | f, err := os.Create(fo.RulesFilePath) 123 | if err != nil { 124 | fo.Log.Error(err, "error while creating config file") 125 | return err 126 | } 127 | defer f.Close() 128 | w := bufio.NewWriter(f) 129 | byteCount, err := w.WriteString(data) 130 | fo.Log.Info(fmt.Sprintf("writing %d bytes of data into %s", byteCount, fo.RulesFilePath)) 131 | w.Flush() 132 | if err != nil { 133 | fo.Log.Error(err, "error while writing to file") 134 | return err 135 | } 136 | return nil 137 | } 138 | 139 | func (fo *FilesOperator) CreateOrUpdate(ctx context.Context, oathkeeperRulesJSON []byte, triggeredBy *oathkeeperv1alpha1.Rule) error { 140 | if triggeredBy != nil && triggeredBy.Spec.ConfigMapName != nil && len(*triggeredBy.Spec.ConfigMapName) > 0 { 141 | fo.Log.Info("Ignoring Spec.ConfigMapName value - sidecar mode enabled") 142 | } 143 | 144 | return fo.updateOrCreateRulesFile(ctx, string(oathkeeperRulesJSON)) 145 | } 146 | -------------------------------------------------------------------------------- /controllers/rule_controller.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package controllers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | "github.com/go-logr/logr" 14 | oathkeeperv1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" 15 | "github.com/ory/oathkeeper-maester/internal/validation" 16 | 17 | "github.com/avast/retry-go" 18 | apierrs "k8s.io/apimachinery/pkg/api/errors" 19 | ctrl "sigs.k8s.io/controller-runtime" 20 | "sigs.k8s.io/controller-runtime/pkg/client" 21 | ) 22 | 23 | const ( 24 | retryAttempts = 5 25 | retryDelay = time.Second * 2 26 | // FinalizerName name of the finalier 27 | FinalizerName = "finalizer.oathkeeper.ory.sh" 28 | ) 29 | 30 | // RuleReconciler reconciles a Rule object 31 | type RuleReconciler struct { 32 | client.Client 33 | Log logr.Logger 34 | ValidationConfig validation.Config 35 | OperatorMode 36 | } 37 | 38 | // +kubebuilder:rbac:groups=oathkeeper.ory.sh,resources=rules,verbs=get;list;watch;create;update;patch;delete 39 | // +kubebuilder:rbac:groups=oathkeeper.ory.sh,resources=rules/status,verbs=get;update;patch 40 | // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete 41 | 42 | // Reconcile main reconcile loop 43 | func (r *RuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 44 | 45 | _ = r.Log.WithValues("rule", req.NamespacedName) 46 | 47 | var rule oathkeeperv1alpha1.Rule 48 | 49 | skipValidation := false 50 | 51 | if err := r.Get(ctx, req.NamespacedName, &rule); err != nil { 52 | if apierrs.IsNotFound(err) { 53 | // just return here, the finalizers have already run 54 | return ctrl.Result{}, nil 55 | } 56 | return ctrl.Result{}, err 57 | } 58 | 59 | if !skipValidation { 60 | if err := rule.ValidateWith(r.ValidationConfig); err != nil { 61 | rule.Status.Validation = &oathkeeperv1alpha1.Validation{} 62 | rule.Status.Validation.Valid = boolPtr(false) 63 | rule.Status.Validation.Error = stringPtr(err.Error()) 64 | r.Log.Info(fmt.Sprintf("validation error in Rule %s/%s: \"%s\"", rule.Namespace, rule.Name, err.Error())) 65 | if err := r.Update(ctx, &rule); err != nil { 66 | r.Log.Error(err, "unable to update Rule status") 67 | //Invoke requeue directly without logging error with whole stacktrace 68 | return ctrl.Result{Requeue: true}, nil 69 | } 70 | // continue, as validation can't be fixed by requeuing request and we still have to update the configmap 71 | } else { 72 | // rule valid - set the status 73 | rule.Status.Validation = &oathkeeperv1alpha1.Validation{} 74 | rule.Status.Validation.Valid = boolPtr(true) 75 | if err := r.Update(ctx, &rule); err != nil { 76 | r.Log.Error(err, "unable to update Rule status") 77 | //Invoke requeue directly without logging error with whole stacktrace 78 | return ctrl.Result{Requeue: true}, nil 79 | } 80 | } 81 | } 82 | 83 | var rulesList oathkeeperv1alpha1.RuleList 84 | 85 | if rule.Spec.ConfigMapName != nil { 86 | if err := r.List(ctx, &rulesList, client.InNamespace(req.NamespacedName.Namespace)); err != nil { 87 | return ctrl.Result{}, err 88 | } 89 | } else { 90 | if err := r.List(ctx, &rulesList); err != nil { 91 | return ctrl.Result{}, err 92 | } 93 | } 94 | 95 | // examine DeletionTimestamp to determine if object is under deletion 96 | if rule.ObjectMeta.DeletionTimestamp.IsZero() { 97 | // The object is not being deleted, so if it does not have our finalizer, 98 | // then lets add the finalizer and update the object. This is equivalent 99 | // registering our finalizer. 100 | if !containsString(rule.ObjectMeta.Finalizers, FinalizerName) { 101 | rule.ObjectMeta.Finalizers = append(rule.ObjectMeta.Finalizers, FinalizerName) 102 | if err := r.Update(ctx, &rule); err != nil { 103 | return ctrl.Result{}, err 104 | } 105 | } 106 | } else { 107 | // The object is being deleted 108 | if containsString(rule.ObjectMeta.Finalizers, FinalizerName) { 109 | // our finalizer is present, so lets handle any external dependency 110 | rulesList = rulesList.FilterOutRule(rule) 111 | 112 | // remove our finalizer from the list and update it. 113 | rule.ObjectMeta.Finalizers = removeString(rule.ObjectMeta.Finalizers, FinalizerName) 114 | if err := r.Update(ctx, &rule); err != nil { 115 | return ctrl.Result{}, err 116 | } 117 | } 118 | } 119 | 120 | var err error 121 | var oathkeeperRulesJSON []byte 122 | 123 | if rule.Spec.ConfigMapName != nil { 124 | r.Log.Info(fmt.Sprintf("Found ConfigMap definition in Rule %s/%s: Writing data to \"%s\"", rule.Namespace, rule.Name, *rule.Spec.ConfigMapName)) 125 | oathkeeperRulesJSON, err = rulesList.FilterNotValid().FilterConfigMapName(rule.Spec.ConfigMapName).ToOathkeeperRules() 126 | if err != nil { 127 | return ctrl.Result{}, err 128 | } 129 | } else { 130 | oathkeeperRulesJSON, err = rulesList.FilterNotValid().ToOathkeeperRules() 131 | if err != nil { 132 | return ctrl.Result{}, err 133 | } 134 | } 135 | 136 | if err := r.OperatorMode.CreateOrUpdate(ctx, oathkeeperRulesJSON, &rule); err != nil { 137 | r.Log.Error(err, "unable to process rules Configmap") 138 | os.Exit(1) 139 | } 140 | return ctrl.Result{}, nil 141 | } 142 | 143 | // SetupWithManager ?? 144 | func (r *RuleReconciler) SetupWithManager(mgr ctrl.Manager) error { 145 | return ctrl.NewControllerManagedBy(mgr). 146 | For(&oathkeeperv1alpha1.Rule{}). 147 | Complete(r) 148 | } 149 | 150 | func isObjectHasBeenModified(err error) bool { 151 | return apierrs.IsConflict(err) && strings.Contains(err.Error(), "the object has been modified; please apply your changes to the latest version") 152 | } 153 | 154 | func retryOnError(retryable func() error) error { 155 | return retryOnErrorWith(retryable, retryAttempts, retryDelay) 156 | } 157 | 158 | func retryOnErrorWith(retryable func() error, attempts int, delay time.Duration) error { 159 | return retry.Do(retryable, 160 | retry.Attempts(uint(attempts)), 161 | retry.Delay(delay), 162 | retry.DelayType(retry.FixedDelay)) 163 | } 164 | 165 | func boolPtr(b bool) *bool { 166 | return &b 167 | } 168 | 169 | func stringPtr(s string) *string { 170 | return &s 171 | } 172 | 173 | // Helper functions to check and remove string from a slice of strings. 174 | func containsString(slice []string, s string) bool { 175 | for _, item := range slice { 176 | if item == s { 177 | return true 178 | } 179 | } 180 | return false 181 | } 182 | 183 | func removeString(slice []string, s string) (result []string) { 184 | for _, item := range slice { 185 | if item == s { 186 | continue 187 | } 188 | result = append(result, item) 189 | } 190 | return 191 | } 192 | -------------------------------------------------------------------------------- /controllers/rule_controller_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package controllers 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | const delay = time.Millisecond 16 | 17 | func TestRetryOnErrorWith(t *testing.T) { 18 | 19 | assert := assert.New(t) 20 | 21 | var cnt, attempts int 22 | var createMapFunc func() error 23 | 24 | t.Run("should retry on error and exit on first successful attempt with no error", func(t *testing.T) { 25 | 26 | //Given 27 | cnt, attempts = 0, 3 28 | 29 | createMapFunc = func() error { 30 | cnt++ 31 | if cnt == 1 { 32 | return errors.New("error only on first invocation") 33 | } 34 | return nil 35 | } 36 | 37 | //when 38 | err := retryOnErrorWith(createMapFunc, attempts, delay) 39 | 40 | //then 41 | assert.Nil(err) 42 | assert.NotEqual(attempts, cnt) 43 | assert.Equal(2, cnt) 44 | }) 45 | 46 | t.Run("should return no error if the last attempt is successful", func(t *testing.T) { 47 | 48 | //Given 49 | cnt, attempts = 0, 5 50 | 51 | createMapFunc = func() error { 52 | cnt++ 53 | if cnt < 5 { 54 | return errors.New(fmt.Sprintf("error only on first four invocations (current: %d)", cnt)) 55 | } 56 | return nil 57 | } 58 | 59 | //when 60 | err := retryOnErrorWith(createMapFunc, attempts, delay) 61 | 62 | //then 63 | assert.Nil(err) 64 | assert.Equal(attempts, cnt) 65 | }) 66 | 67 | t.Run("should return an error if all attempts fail", func(t *testing.T) { 68 | 69 | //Given 70 | cnt, attempts = 0, 2 71 | 72 | createMapFunc = func() error { 73 | cnt++ 74 | return errors.New(fmt.Sprintf("error on every invocation (current: %d)", cnt)) 75 | } 76 | 77 | //when 78 | err := retryOnErrorWith(createMapFunc, attempts, delay) 79 | 80 | //then 81 | assert.NotNil(err) 82 | assert.Equal(attempts, cnt) 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ory/oathkeeper-maester 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/avast/retry-go v3.0.0+incompatible 7 | github.com/bitly/go-simplejson v0.5.1 8 | github.com/go-logr/logr v1.2.4 9 | github.com/gogo/protobuf v1.3.2 // indirect 10 | github.com/onsi/ginkgo v1.16.5 11 | github.com/onsi/gomega v1.28.1 12 | github.com/stretchr/testify v1.8.4 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | k8s.io/api v0.28.3 15 | k8s.io/apiextensions-apiserver v0.28.3 // indirect 16 | k8s.io/apimachinery v0.28.3 17 | k8s.io/client-go v0.28.3 18 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect 19 | sigs.k8s.io/controller-runtime v0.15.0 20 | ) 21 | 22 | require ( 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 25 | github.com/davecgh/go-spew v1.1.1 // indirect 26 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 27 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 28 | github.com/fsnotify/fsnotify v1.6.0 // indirect 29 | github.com/go-logr/zapr v1.2.4 // indirect 30 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 31 | github.com/go-openapi/jsonreference v0.20.2 // indirect 32 | github.com/go-openapi/swag v0.22.3 // indirect 33 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 34 | github.com/golang/protobuf v1.5.3 // indirect 35 | github.com/google/gnostic-models v0.6.8 // indirect 36 | github.com/google/go-cmp v0.6.0 // indirect 37 | github.com/google/gofuzz v1.2.0 // indirect 38 | github.com/google/uuid v1.3.0 // indirect 39 | github.com/imdario/mergo v0.3.10 // indirect 40 | github.com/josharian/intern v1.0.0 // indirect 41 | github.com/json-iterator/go v1.1.12 // indirect 42 | github.com/mailru/easyjson v0.7.7 // indirect 43 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 45 | github.com/modern-go/reflect2 v1.0.2 // indirect 46 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 47 | github.com/nxadm/tail v1.4.8 // indirect 48 | github.com/pkg/errors v0.9.1 // indirect 49 | github.com/pmezard/go-difflib v1.0.0 // indirect 50 | github.com/prometheus/client_golang v1.16.0 // indirect 51 | github.com/prometheus/client_model v0.4.0 // indirect 52 | github.com/prometheus/common v0.44.0 // indirect 53 | github.com/prometheus/procfs v0.10.1 // indirect 54 | github.com/spf13/pflag v1.0.5 // indirect 55 | go.uber.org/multierr v1.11.0 // indirect 56 | go.uber.org/zap v1.25.0 // indirect 57 | golang.org/x/net v0.17.0 // indirect 58 | golang.org/x/oauth2 v0.8.0 // indirect 59 | golang.org/x/sys v0.13.0 // indirect 60 | golang.org/x/term v0.13.0 // indirect 61 | golang.org/x/text v0.13.0 // indirect 62 | golang.org/x/time v0.3.0 // indirect 63 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 64 | google.golang.org/appengine v1.6.7 // indirect 65 | google.golang.org/protobuf v1.30.0 // indirect 66 | gopkg.in/inf.v0 v0.9.1 // indirect 67 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 68 | gopkg.in/yaml.v2 v2.4.0 // indirect 69 | k8s.io/component-base v0.28.3 // indirect 70 | k8s.io/klog/v2 v2.100.1 // indirect 71 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect 72 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 73 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 74 | sigs.k8s.io/yaml v1.3.0 // indirect 75 | ) 76 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 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 | */ -------------------------------------------------------------------------------- /internal/validation/validation.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package validation 5 | 6 | type Config struct { 7 | AuthenticatorsAvailable []string 8 | AuthorizersAvailable []string 9 | MutatorsAvailable []string 10 | ErrorsAvailable []string 11 | } 12 | 13 | func (c Config) IsAuthenticatorValid(authenticator string) bool { 14 | return isValid(authenticator, c.AuthenticatorsAvailable) 15 | } 16 | func (c Config) IsAuthorizerValid(authorizer string) bool { 17 | return isValid(authorizer, c.AuthorizersAvailable) 18 | } 19 | func (c Config) IsMutatorValid(mutator string) bool { 20 | return isValid(mutator, c.MutatorsAvailable) 21 | } 22 | func (c Config) IsErrorValid(err string) bool { 23 | return isValid(err, c.ErrorsAvailable) 24 | } 25 | 26 | func isValid(current string, available []string) bool { 27 | for _, a := range available { 28 | if current == a { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | -------------------------------------------------------------------------------- /internal/validation/validation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package validation 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestIsValid(t *testing.T) { 13 | t.Run("validates correctly", func(t *testing.T) { 14 | tests := map[string]struct { 15 | current string 16 | available []string 17 | expected bool 18 | }{ 19 | "when available is nil": {"a", nil, false}, 20 | "when available is empty": {"a", []string{}, false}, 21 | "when available does not contain current": {"a", []string{"b", "c", "d"}, false}, 22 | "when available does contain current": {"a", []string{"b", "a", "c", "d"}, true}, 23 | } 24 | for name, test := range tests { 25 | t.Run(name, func(t *testing.T) { 26 | result := isValid(test.current, test.available) 27 | assert.Equal(t, test.expected, result) 28 | }) 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/ory/oathkeeper-maester/internal/validation" 14 | 15 | oathkeeperv1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" 16 | "github.com/ory/oathkeeper-maester/controllers" 17 | apiv1 "k8s.io/api/core/v1" 18 | "k8s.io/apimachinery/pkg/runtime" 19 | "k8s.io/apimachinery/pkg/types" 20 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 21 | ctrl "sigs.k8s.io/controller-runtime" 22 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 23 | // +kubebuilder:scaffold:imports 24 | ) 25 | 26 | var ( 27 | scheme = runtime.NewScheme() 28 | setupLog = ctrl.Log.WithName("setup") 29 | ) 30 | 31 | func init() { 32 | 33 | apiv1.AddToScheme(scheme) 34 | oathkeeperv1alpha1.AddToScheme(scheme) 35 | // +kubebuilder:scaffold:scheme 36 | } 37 | 38 | func main() { 39 | var metricsAddr string 40 | var enableLeaderElection bool 41 | var rulesConfigmapName string 42 | var rulesConfigmapNamespace string 43 | var rulesFileName string 44 | var rulesFilePath string 45 | 46 | var operator controllers.OperatorMode 47 | 48 | controllerCommand := flag.NewFlagSet("controller", flag.ExitOnError) 49 | sidecarCommand := flag.NewFlagSet("sidecar", flag.ExitOnError) 50 | 51 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 52 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 53 | "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") 54 | 55 | controllerCommand.StringVar(&rulesConfigmapName, "rulesConfigmapName", "oathkeeper-rules", "Name of the Configmap that stores Oathkeeper rules.") 56 | controllerCommand.StringVar(&rulesConfigmapNamespace, "rulesConfigmapNamespace", "oathkeeper-maester-system", "Namespace of the Configmap that stores Oathkeeper rules.") 57 | controllerCommand.StringVar(&rulesFileName, "rulesFileName", "access-rules.json", "Name of the key in ConfigMap containing the rules.json") 58 | 59 | sidecarCommand.StringVar(&rulesFilePath, "rulesFilePath", "/etc/config/access-rules.json", "Path to the file with converted Oathkeeper rules") 60 | 61 | flag.Parse() 62 | 63 | ctrl.SetLogger(zap.New(zap.UseDevMode(true))) 64 | 65 | sideCarMode, err := selectMode(flag.Args(), controllerCommand, sidecarCommand) 66 | if err != nil { 67 | setupLog.Error(err, "problem parsing flags") 68 | os.Exit(1) 69 | } 70 | 71 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 72 | Scheme: scheme, 73 | MetricsBindAddress: metricsAddr, 74 | LeaderElection: enableLeaderElection, 75 | // Defaults to "" which means all namespaces 76 | Namespace: os.Getenv("NAMESPACE"), 77 | }) 78 | if err != nil { 79 | setupLog.Error(err, "unable to start manager") 80 | os.Exit(1) 81 | } 82 | 83 | if err := validateRulesFileName(rulesFileName); err != nil { 84 | setupLog.Error(err, "Validation error") 85 | os.Exit(1) 86 | } 87 | 88 | validationConfig := initValidationConfig() 89 | 90 | if sideCarMode { 91 | operator = &controllers.FilesOperator{ 92 | Log: ctrl.Log.WithName("controllers").WithName("Rule"), 93 | RulesFilePath: rulesFilePath, 94 | } 95 | } else { 96 | operator = &controllers.ConfigMapOperator{ 97 | Client: mgr.GetClient(), 98 | Log: ctrl.Log.WithName("controllers").WithName("Rule"), 99 | DefaultConfigMap: types.NamespacedName{ 100 | Name: rulesConfigmapName, 101 | Namespace: rulesConfigmapNamespace, 102 | }, 103 | RulesFileName: rulesFileName, 104 | } 105 | } 106 | 107 | ruleReconciler := &controllers.RuleReconciler{ 108 | Client: mgr.GetClient(), 109 | Log: ctrl.Log.WithName("controllers").WithName("Rule"), 110 | ValidationConfig: validationConfig, 111 | OperatorMode: operator, 112 | } 113 | 114 | if err := ruleReconciler.SetupWithManager(mgr); err != nil { 115 | setupLog.Error(err, "unable to create controller", "controller", "Rule") 116 | os.Exit(1) 117 | } 118 | // +kubebuilder:scaffold:builder 119 | 120 | setupLog.Info("starting manager") 121 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 122 | setupLog.Error(err, "problem running manager") 123 | os.Exit(1) 124 | } 125 | } 126 | 127 | func parseListOrDefault(list string, defaultArr []string, name string) []string { 128 | if list == "" { 129 | setupLog.Info(fmt.Sprintf("using default values for %s", name)) 130 | return defaultArr 131 | } 132 | return parseList(list) 133 | } 134 | 135 | func parseList(list string) []string { 136 | return removeEmptyStrings(strings.Split(list, ",")) 137 | } 138 | 139 | func removeEmptyStrings(list []string) []string { 140 | result := make([]string, 0) 141 | for _, s := range list { 142 | ts := strings.TrimSpace(s) 143 | if ts != "" { 144 | result = append(result, ts) 145 | } 146 | } 147 | 148 | return result 149 | } 150 | 151 | func initValidationConfig() validation.Config { 152 | authenticatorsAvailable := os.Getenv(oathkeeperv1alpha1.AuthenticatorsAvailableEnv) 153 | authorizersAvailable := os.Getenv(oathkeeperv1alpha1.AuthorizersAvailableEnv) 154 | mutatorsAvailable := os.Getenv(oathkeeperv1alpha1.MutatorsAvailableEnv) 155 | errorsAvailable := os.Getenv(oathkeeperv1alpha1.ErrorsAvailableEnv) 156 | return validation.Config{ 157 | AuthenticatorsAvailable: parseListOrDefault(authenticatorsAvailable, oathkeeperv1alpha1.DefaultAuthenticatorsAvailable[:], oathkeeperv1alpha1.AuthenticatorsAvailableEnv), 158 | AuthorizersAvailable: parseListOrDefault(authorizersAvailable, oathkeeperv1alpha1.DefaultAuthorizersAvailable[:], oathkeeperv1alpha1.AuthorizersAvailableEnv), 159 | MutatorsAvailable: parseListOrDefault(mutatorsAvailable, oathkeeperv1alpha1.DefaultMutatorsAvailable[:], oathkeeperv1alpha1.MutatorsAvailableEnv), 160 | ErrorsAvailable: parseListOrDefault(errorsAvailable, oathkeeperv1alpha1.DefaultErrorsAvailable[:], oathkeeperv1alpha1.ErrorsAvailableEnv), 161 | } 162 | } 163 | 164 | func validateRulesFileName(rfn string) error { 165 | match, _ := regexp.MatchString(oathkeeperv1alpha1.RulesFileNameRegexp, rfn) 166 | if match { 167 | return nil 168 | } 169 | return fmt.Errorf("rulesFileName: %s is not a valid name", rfn) 170 | } 171 | 172 | func selectMode(args []string, controllerCommand *flag.FlagSet, sidecarCommand *flag.FlagSet) (bool, error) { 173 | if len(args) < 1 { 174 | setupLog.Info("running in controller mode") 175 | return false, nil 176 | } 177 | 178 | switch args[0] { 179 | case "controller": 180 | if err := controllerCommand.Parse(args[1:]); err != nil { 181 | return false, err 182 | } 183 | setupLog.Info("running in controller mode") 184 | return false, nil 185 | case "sidecar": 186 | if err := sidecarCommand.Parse(args[1:]); err != nil { 187 | return false, err 188 | } 189 | setupLog.Info("running in sidecar mode") 190 | return true, nil 191 | default: 192 | return false, fmt.Errorf(`modes "controller" and "sidecar" are supported but got: %s`, args[0]) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "prettier": "ory-prettier-styles", 4 | "devDependencies": { 5 | "license-checker": "^25.0.1", 6 | "ory-prettier-styles": "1.3.0", 7 | "prettier": "2.7.1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/integration/README.md: -------------------------------------------------------------------------------- 1 | # oathkeeper-maester integration tests. 2 | 3 | This directory contains integration tests for oathkeeper-maester The tests 4 | execute against a cluster. For local testing use either minikube or KIND 5 | environment. 6 | 7 | ## How to run in with "KIND" 8 | 9 | - ensure KUBECONFIG is not set: `unset KUBECONFIG` 10 | - execute `make test-integration` from project's root directory 11 | 12 | ## How to run it against a cluster 13 | 14 | - Setup a test environment: either a K8s cluster or minikube. Install the 15 | controller. 16 | - Export KUBECONFIG environment variable 17 | - Execute the tests with: `ginkgo -v ./tests/integration/...` If you don't have 18 | ginkgo binary installed, standard `go test -v ./tests/integration/...` also 19 | works, but the output isn't formatted nicely. 20 | -------------------------------------------------------------------------------- /tests/integration/files/rule1.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "oathkeeper.ory.sh/v1alpha1", 3 | "kind": "Rule", 4 | "metadata": { 5 | "name": "test-rule-1" 6 | }, 7 | "spec": { 8 | "match": { 9 | "methods": [ 10 | "GET", 11 | "POST" 12 | ], 13 | "url": "http://gh.ij" 14 | }, 15 | "upstream": { 16 | "preserveHost": false, 17 | "url": "http://abc.def" 18 | }, 19 | "authenticators": [ 20 | { 21 | "handler": "anonymous" 22 | } 23 | ], 24 | "authorizer": { 25 | "handler": "allow" 26 | }, 27 | "mutators": [ 28 | { 29 | "handler": "header", 30 | "config": { 31 | "headers": { 32 | "X-User": "{{ print .Subject }}", 33 | "X-Some-Arbitrary-Data": "{{ print .Extra.some.arbitrary.data }}" 34 | } 35 | } 36 | } 37 | ], 38 | "errors": [ 39 | { 40 | "handler": "redirect" 41 | } 42 | ] 43 | } 44 | } -------------------------------------------------------------------------------- /tests/integration/files/rule2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "oathkeeper.ory.sh/v1alpha1", 3 | "kind": "Rule", 4 | "metadata": { 5 | "name": "test-rule-2" 6 | }, 7 | "spec": { 8 | "match": { 9 | "methods": [ 10 | "POST", 11 | "PUT" 12 | ], 13 | "url": "http://xyz.com" 14 | }, 15 | "upstream": { 16 | "url": "http://abcde.fgh" 17 | }, 18 | "authenticators": [ 19 | { 20 | "handler": "oauth2_client_credentials", 21 | "config": { 22 | "required_scope": [ 23 | "scope-a", 24 | "scope-b" 25 | ] 26 | } 27 | }, 28 | { 29 | "handler": "anonymous" 30 | } 31 | ], 32 | "authorizer": { 33 | "handler": "keto_engine_acp_ory", 34 | "config": { 35 | "required_action": "my:action:1234", 36 | "required_resource": "my:resource:foobar:foo:1234" 37 | } 38 | }, 39 | "mutators": [ 40 | { 41 | "handler": "id_token", 42 | "config": { 43 | "aud": [ 44 | "audience1", 45 | "audience2" 46 | ] 47 | } 48 | } 49 | ], 50 | "errors": [ 51 | { 52 | "handler": "redirect" 53 | } 54 | ] 55 | } 56 | } -------------------------------------------------------------------------------- /tests/integration/integration_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package integration 5 | 6 | import ( 7 | "testing" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | func TestIntegration(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Oathkeeper controller") 16 | } 17 | -------------------------------------------------------------------------------- /tests/integration/retry.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package integration 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | 10 | "github.com/avast/retry-go" 11 | ) 12 | 13 | // Modify this var to provide different function for logging retires. 14 | var logRetry = func(msg string, args ...interface{}) { 15 | fmt.Printf(msg, args...) 16 | } 17 | 18 | // Function to be executed - and possibly retried. 19 | type workerFunc func() (interface{}, error) 20 | 21 | // Helper function introduced to inject maxRetries into retry logging - so that users provide maxRetries only once, in `withRetries` function 22 | type getOnRetryLoggingFunc func(maxRetries uint) retry.OnRetryFunc 23 | 24 | // Generic function to handle retries. 25 | // getRetryLogging can be nil if you don't want to log retries. To log retries, use the result of `onRetryLogMsg` as an argument. 26 | func withRetries(maxRetries uint, delay time.Duration, getRetryLogging getOnRetryLoggingFunc, worker workerFunc) (interface{}, error) { 27 | 28 | var response interface{} = nil 29 | 30 | err := retry.Do(func() error { 31 | var err error 32 | response, err = worker() 33 | 34 | if err != nil { 35 | return err 36 | } 37 | return nil 38 | }, 39 | retry.Attempts(maxRetries), 40 | retry.Delay(delay), 41 | retry.DelayType(retry.FixedDelay), 42 | retry.OnRetry(getRetryLogging(maxRetries)), 43 | ) 44 | 45 | return response, err 46 | } 47 | 48 | // Returns `getOnRetryLoggingFunc` instance for given message. 49 | func onRetryLogMsg(msg string) getOnRetryLoggingFunc { 50 | return func(maxRetries uint) retry.OnRetryFunc { 51 | return func(retryNo uint, err error) { 52 | logRetry("[%d / %d] %s: %v\n", retryNo+1, maxRetries, msg, err) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/integration/rules_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package integration 5 | 6 | import ( 7 | "context" 8 | _ "embed" 9 | "errors" 10 | "fmt" 11 | "k8s.io/client-go/rest" 12 | "os" 13 | "time" 14 | 15 | json "github.com/bitly/go-simplejson" 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | oathkeeperv1alpha1 "github.com/ory/oathkeeper-maester/api/v1alpha1" 19 | "k8s.io/api/core/v1" 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "k8s.io/client-go/dynamic" 25 | "k8s.io/client-go/kubernetes" 26 | "k8s.io/client-go/tools/clientcmd" 27 | ) 28 | 29 | const ( 30 | namespaceName = "test-namespace" //TODO: Randomize? 31 | defaultTargetMapNamespace = "oathkeeper-maester-system" 32 | defaultTargetMapName = "oathkeeper-rules" 33 | maxRetriesWaitingForConfigMap = 35 //Twice as max registered on my machine. 34 | maxRetriesWaitingForRule = 10 35 | rulesFileName = "access-rules.json" 36 | ) 37 | 38 | var ( 39 | k8sClient *kubernetes.Clientset = getK8sClientOrDie() 40 | k8sDynamicClient dynamic.Interface = getK8sDynamicClientOrDie() 41 | ruleResource schema.GroupVersionResource = schema.GroupVersionResource{Group: oathkeeperv1alpha1.GroupVersion.Group, Version: oathkeeperv1alpha1.GroupVersion.Version, Resource: "rules"} 42 | ) 43 | 44 | //go:embed files/rule1.json 45 | var rule1 string 46 | 47 | //go:embed files/rule2.json 48 | var rule2 string 49 | 50 | var _ = BeforeSuite(func() { 51 | //Create namespace 52 | _, err := createNamespace(namespaceName, k8sClient) 53 | Expect(err).ToNot(HaveOccurred()) 54 | }) 55 | 56 | var _ = AfterSuite(func() { 57 | //Delete namespace 58 | err := deleteNamespace(namespaceName, k8sClient) 59 | Expect(err).ToNot(HaveOccurred()) 60 | }) 61 | 62 | var _ = Describe("Oathkeeper controller", func() { 63 | 64 | Context("should manage rules as ConfigMap entries", func() { 65 | 66 | It("in happy path scenario", func() { 67 | 68 | By("create a valid ConfigMap from a single Rule") 69 | //Given 70 | rule, err := getRule(rule1) 71 | Expect(err).To(BeNil()) 72 | Expect(rule).ToNot(BeNil()) 73 | 74 | //When 75 | _, createErr := ensureRule(rule) 76 | Expect(createErr).To(BeNil()) 77 | 78 | //Then 79 | rulesArray, validateErr := validateConfigMapContains(rule) 80 | Expect(validateErr).To(BeNil()) 81 | expectRuleCount(rulesArray, 1) 82 | 83 | By("add an entry to the ConfigMap after adding another Rule") 84 | //Given 85 | rule, err = getRule(rule2) 86 | Expect(err).To(BeNil()) 87 | Expect(rule).ToNot(BeNil()) 88 | 89 | //When 90 | _, createErr = ensureRule(rule) 91 | Expect(createErr).To(BeNil()) 92 | 93 | //Then 94 | rulesArray, validateErr = validateConfigMapContains(rule) 95 | Expect(validateErr).To(BeNil()) 96 | expectRuleCount(rulesArray, 2) 97 | 98 | By("update a ConfigMap entry after Rule update") 99 | //Given 100 | rule, getErr := k8sDynamicClient.Resource(ruleResource).Namespace(namespaceName).Get(context.TODO(), "test-rule-2", metav1.GetOptions{}) 101 | Expect(getErr).To(BeNil()) 102 | 103 | //When 104 | updateRule(rule) 105 | _, updateErr := k8sDynamicClient.Resource(ruleResource).Namespace(namespaceName).Update(context.TODO(), rule, metav1.UpdateOptions{}) 106 | Expect(updateErr).To(BeNil()) 107 | 108 | //Allow for some processing time 109 | time.Sleep(5 * time.Second) 110 | 111 | //Then 112 | rulesArray, validateErr = validateConfigMapContains(rule) 113 | Expect(validateErr).To(BeNil()) 114 | expectRuleCount(rulesArray, 2) 115 | By("delete a ConfigMap entry after Rule delete") 116 | //When 117 | deleteErr := k8sDynamicClient.Resource(ruleResource).Namespace(namespaceName).Delete(context.TODO(), "test-rule-2", metav1.DeleteOptions{}) 118 | Expect(deleteErr).To(BeNil()) 119 | 120 | //Allow for some processing time 121 | time.Sleep(3 * time.Second) 122 | 123 | //Then 124 | rule, err = getRule(rule1) 125 | Expect(err).To(BeNil()) 126 | 127 | rulesArray, validateErr = validateConfigMapContains(rule) 128 | Expect(validateErr).To(BeNil()) 129 | expectRuleCount(rulesArray, 1) 130 | 131 | By("delete last ConfigMap entry after all Rules are deleted") 132 | //When 133 | deleteErr = k8sDynamicClient.Resource(ruleResource).Namespace(namespaceName).Delete(context.TODO(), "test-rule-1", metav1.DeleteOptions{}) 134 | Expect(deleteErr).To(BeNil()) 135 | 136 | //Allow for some processing time 137 | time.Sleep(3 * time.Second) 138 | 139 | //Then 140 | emptyMap, err := getTargetMap() 141 | Expect(err).To(BeNil()) 142 | Expect(emptyMap.Data[rulesFileName]).To(Equal("[]")) 143 | }) 144 | }) 145 | 146 | }) 147 | 148 | // Converts Rule CRD instance to a *json.Json representation of an entry that should be created in the ConfigMap. 149 | // At the moment CRD structure and ConfigMap entry structure is very similar, we can use that to avoid separate json file with "expected" data. 150 | func toExpected(rule *unstructured.Unstructured) *json.Json { 151 | expected := wrapSpecAsJson(rule) 152 | 153 | expectedRuleId := rule.GetName() + "." + namespaceName 154 | expected.Set("id", expectedRuleId) 155 | 156 | upstream := expected.GetPath("upstream") 157 | 158 | //preserveHost in Rule => preserve_host in ConfigMap 159 | upstream.Set("preserve_host", upstream.Get("preserveHost").Interface()) 160 | upstream.Del("preserveHost") 161 | 162 | //The controller always seserializes preserve_host, nil in Rule gets translated to false. 163 | if upstream.Get("preserve_host").Interface() == nil { 164 | upstream.Set("preserve_host", false) 165 | } 166 | 167 | return expected 168 | } 169 | 170 | func updateRule(rule *unstructured.Unstructured) { 171 | emptyMap := map[string]interface{}{} 172 | ruleJson := wrapSpecAsJson(rule) 173 | ruleJson.SetPath([]string{"upstream", "preserveHost"}, true) 174 | ruleJson.SetPath([]string{"upstream", "url"}, "https://xyz.org") 175 | ruleJson.SetPath([]string{"match", "url"}, "https://fedcba.com") 176 | ruleJson.SetPath([]string{"authorizer", "handler"}, "deny") 177 | ruleJson.SetPath([]string{"authorizer", "config"}, emptyMap) 178 | } 179 | 180 | func expectRuleCount(rulesArray *json.Json, cnt int) { 181 | Expect(rulesArray.Interface()).To(HaveLen(cnt)) 182 | } 183 | 184 | func getK8sDynamicClientOrDie() dynamic.Interface { 185 | config := getKubeConfigOrDie() 186 | 187 | // create the client 188 | client, err := dynamic.NewForConfig(config) 189 | if err != nil { 190 | fmt.Printf("\nError creating K8s dynamic client: %s\n", err.Error()) 191 | os.Exit(1) 192 | } 193 | 194 | return client 195 | } 196 | 197 | func getK8sClientOrDie() *kubernetes.Clientset { 198 | config := getKubeConfigOrDie() 199 | 200 | // create the clientset 201 | clientset, err := kubernetes.NewForConfig(config) 202 | if err != nil { 203 | fmt.Printf("\nError creating K8s clientset: %s\n", err.Error()) 204 | os.Exit(1) 205 | } 206 | 207 | return clientset 208 | } 209 | 210 | func getKubeConfigOrDie() *rest.Config { 211 | if _, err := os.Stat(clientcmd.RecommendedHomeFile); os.IsNotExist(err) { 212 | cfg, err := rest.InClusterConfig() 213 | if err != nil { 214 | fmt.Printf("Cannot create in-cluster config: %v", err) 215 | panic(err) 216 | } 217 | return cfg 218 | } 219 | 220 | cfg, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile) 221 | if err != nil { 222 | fmt.Printf("Cannot read kubeconfig: %s", err) 223 | panic(err) 224 | } 225 | return cfg 226 | } 227 | 228 | func toNamespace(name string) *corev1.Namespace { 229 | return &corev1.Namespace{ 230 | ObjectMeta: metav1.ObjectMeta{ 231 | Name: name, 232 | }, 233 | Spec: corev1.NamespaceSpec{}, 234 | } 235 | } 236 | 237 | func deleteNamespace(name string, k8sClient *kubernetes.Clientset) error { 238 | 239 | worker := func() (interface{}, error) { 240 | err := k8sClient.CoreV1().Namespaces().Delete(context.TODO(), name, metav1.DeleteOptions{}) 241 | return nil, err 242 | } 243 | 244 | const maxRetries = 2 245 | _, err := withRetries(maxRetries, time.Second*1, onRetryLogMsg("retry deleting namespace"), worker) 246 | return err 247 | } 248 | 249 | func createNamespace(name string, k8sClient *kubernetes.Clientset) (*corev1.Namespace, error) { 250 | 251 | testNamespace := toNamespace(name) 252 | return k8sClient.CoreV1().Namespaces().Create(context.TODO(), testNamespace, metav1.CreateOptions{}) 253 | } 254 | 255 | func getRule(json string) (*unstructured.Unstructured, error) { 256 | res := unstructured.Unstructured{} 257 | 258 | err := res.UnmarshalJSON([]byte(json)) 259 | 260 | if err != nil { 261 | return nil, err 262 | } 263 | return &res, nil 264 | } 265 | 266 | func getTargetMap() (*v1.ConfigMap, error) { 267 | return k8sClient.CoreV1().ConfigMaps(getTargetMapNamespace()).Get(context.TODO(), getTargetMapName(), metav1.GetOptions{}) 268 | } 269 | 270 | // Entry point for validation 271 | // Returns parsed rules array as json.Json object 272 | func validateConfigMapContains(sourceRule *unstructured.Unstructured) (*json.Json, error) { 273 | 274 | //It's a copy! 275 | expectedRule := toExpected(sourceRule.DeepCopy()) 276 | 277 | expectedRuleId, ok := expectedRule.Get("id").Interface().(string) 278 | if !ok { 279 | return nil, errors.New("Can't find \"id\" in expectedRule") 280 | } 281 | 282 | workerFunc := func() (interface{}, error) { 283 | 284 | //Fetch target ConfigMap 285 | targetMap, err := getTargetMap() 286 | if err != nil { 287 | return nil, err 288 | } 289 | 290 | //Parse data from ConfigMap 291 | jsonString := targetMap.Data[rulesFileName] 292 | if jsonString == "" || jsonString == "null" { 293 | return nil, errors.New("No rules in ConfigMap") 294 | } 295 | jsonRules, err := json.NewJson([]byte(jsonString)) 296 | if err != nil { 297 | return nil, err 298 | } 299 | 300 | //Look for the rule 301 | actualRule, err := findRule(jsonRules, expectedRuleId) 302 | if err != nil { 303 | return nil, err 304 | } 305 | 306 | //Validate 307 | validateRuleEquals(actualRule, expectedRule) 308 | return jsonRules, nil 309 | } 310 | 311 | //Execute with retries 312 | res, err := withRetries(maxRetriesWaitingForConfigMap, time.Second*3, onRetryLogMsg("retry validating ConfigMap"), workerFunc) 313 | if err != nil { 314 | return nil, err 315 | } 316 | 317 | rulesArray, ok := res.(*json.Json) 318 | if !ok { 319 | return nil, errors.New("Can't convert to rules array") 320 | } 321 | 322 | return rulesArray, nil 323 | } 324 | 325 | // Creates a Rule and ensures it can be read back from the cluster. 326 | func ensureRule(rule *unstructured.Unstructured) (*unstructured.Unstructured, error) { 327 | 328 | //Create 329 | _, createErr := k8sDynamicClient.Resource(ruleResource).Namespace(namespaceName).Create(context.TODO(), rule, metav1.CreateOptions{}) 330 | Expect(createErr).To(BeNil()) 331 | 332 | //Wait until Rule can be read from the cluster. 333 | workerFunc := func() (interface{}, error) { 334 | return k8sDynamicClient.Resource(ruleResource).Namespace(namespaceName).Get(context.TODO(), rule.GetName(), metav1.GetOptions{}) 335 | } 336 | 337 | //Execute with retries 338 | res, err := withRetries(maxRetriesWaitingForRule, time.Second*3, onRetryLogMsg("retry getting new Rule"), workerFunc) 339 | if err != nil { 340 | return nil, err 341 | } 342 | 343 | createdRule, ok := res.(*unstructured.Unstructured) 344 | if !ok { 345 | return nil, errors.New("Can't convert to a Rule") 346 | } 347 | 348 | return createdRule, nil 349 | } 350 | 351 | // Finds and returns a rule with specified id 352 | // actualRules must be an array of rules (sadly go-simplejson doesn't offer such type) 353 | func findRule(actualRules *json.Json, expectedRuleId string) (*json.Json, error) { 354 | 355 | //Can it be done simpler? go-simplejson doesn't seem to offer any generic way of iterating over array. 356 | testIfArray, err := actualRules.Array() 357 | if err != nil { 358 | return nil, err 359 | } 360 | 361 | ruleCount := len(testIfArray) 362 | 363 | for i := 0; i < ruleCount; i++ { 364 | rule := actualRules.GetIndex(i) 365 | ruleId, err := rule.Get("id").String() 366 | if err != nil { 367 | return nil, err 368 | } 369 | if ruleId == expectedRuleId { 370 | return rule, nil 371 | } 372 | } 373 | 374 | return nil, errors.New(fmt.Sprintf("Rule with id %s not found", expectedRuleId)) 375 | } 376 | 377 | func getTargetMapNamespace() string { 378 | res := os.Getenv("TARGET_MAP_NAMESPACE") 379 | if res == "" { 380 | res = defaultTargetMapNamespace 381 | } 382 | 383 | return res 384 | } 385 | 386 | func getTargetMapName() string { 387 | res := os.Getenv("TARGET_MAP_NAME") 388 | 389 | if res == "" { 390 | res = defaultTargetMapName 391 | } 392 | 393 | return res 394 | } 395 | -------------------------------------------------------------------------------- /tests/integration/validation.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Ory Corp 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package integration 5 | 6 | import ( 7 | "fmt" 8 | 9 | json "github.com/bitly/go-simplejson" 10 | . "github.com/onsi/gomega" 11 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 | ) 13 | 14 | // Validates rules are equal 15 | // `actual` is a representation of an entry from the ConfigMap handled by the Controller 16 | func validateRuleEquals(actual *json.Json, expected *json.Json) { 17 | Expect(actual).To(Equal(expected)) 18 | expectOnlyKeys(actual, "id", "upstream", "match", "authenticators", "authorizer", "mutators", "errors") 19 | 20 | expectString(actual, "id") 21 | compareUpstreams(actual.Get("upstream"), expected.Get("upstream")) 22 | compareMatches(actual.Get("match"), expected.Get("match")) 23 | compareHandlerArrays(actual.Get("authenticators"), expected.Get("authenticators")) 24 | compareHandlers(actual.Get("authorizer"), expected.Get("authorizer")) 25 | compareHandlerArrays(actual.Get("mutators"), expected.Get("mutators")) 26 | compareHandlerArrays(actual.Get("errors"), expected.Get("errors")) 27 | } 28 | 29 | func compareUpstreams(actual *json.Json, expected *json.Json) { 30 | Expect(actual).To(Equal(expected)) 31 | expectOnlyKeys(actual, "url", "preserve_host") 32 | expectString(actual, "url") 33 | expectBoolean(actual.Get("preserve_host")) 34 | } 35 | 36 | func compareMatches(actual *json.Json, expected *json.Json) { 37 | Expect(actual).To(Equal(expected)) 38 | expectOnlyKeys(actual, "url", "methods") 39 | expectString(actual, "url") 40 | expectStringArray(actual, "methods") 41 | } 42 | 43 | func compareHandlerArrays(actual *json.Json, expected *json.Json) { 44 | //both are equal 45 | Expect(actual).To(Equal(expected)) 46 | 47 | //expected is an Array 48 | expectedArray, err := expected.Array() 49 | Expect(err).To(BeNil()) 50 | 51 | //All elements are proper handlers 52 | length := len(expectedArray) 53 | for i := 0; i < length; i++ { 54 | compareHandlers(actual.GetIndex(i), expected.GetIndex(i)) 55 | } 56 | 57 | } 58 | 59 | // Compares `handler` objects, a common type for `authenticators`, `authorizer`, `mutator` and `errors` configurations 60 | // The object consists of two properties: `hander`:string` and `config`:object 61 | func compareHandlers(actual *json.Json, expected *json.Json) { 62 | //expected.SetPath( 63 | Expect(actual).To(Equal(expected)) 64 | 65 | expectAllowedKeys(actual, "handler", "config") 66 | 67 | expectString(actual, "handler") 68 | expectObjectOrNil(actual, "config") 69 | } 70 | 71 | func expectBoolean(data *json.Json) { 72 | _, err := data.Bool() 73 | Expect(err).To(BeNil()) 74 | } 75 | 76 | func expectString(data *json.Json, attributeName string) { 77 | errMsg := "" 78 | _, err := data.Get(attributeName).String() 79 | if err != nil { 80 | errMsg = fmt.Sprintf("Cannot convert %s to string. Details: %v", attributeName, err) 81 | } 82 | Expect(errMsg).To(BeEmpty()) 83 | } 84 | 85 | func expectStringArray(data *json.Json, attributeName string) { 86 | errMsg := "" 87 | arr, err := data.Get(attributeName).Array() 88 | if err != nil { 89 | errMsg = fmt.Sprintf("Cannot convert %s to slice/array. Details: %v", attributeName, err) 90 | } 91 | Expect(errMsg).To(BeEmpty()) 92 | 93 | length := len(arr) 94 | for i := 0; i < length; i++ { 95 | _, ok := arr[i].(string) 96 | if !ok { 97 | errMsg = fmt.Sprintf("Cannot convert element of %s array [%d] to string. Details: %v", attributeName, i, err) 98 | } 99 | Expect(errMsg).To(BeEmpty()) 100 | } 101 | } 102 | 103 | func expectObjectOrNil(data *json.Json, attributeName string) { 104 | if data.Get("attributeName").Interface() == nil { 105 | return 106 | } 107 | 108 | errMsg := "" 109 | _, err := data.Map() 110 | if err != nil { 111 | errMsg = fmt.Sprintf("Cannot convert %s to an object. Details: %v", attributeName, err) 112 | } 113 | Expect(errMsg).To(BeEmpty()) 114 | } 115 | 116 | func expectAllowedKeys(genericMap *json.Json, allowedKeys ...string) { 117 | aMap, ok := genericMap.Interface().(map[string]interface{}) 118 | Expect(ok).To(BeTrue()) 119 | actualKeys := getKeysOf(aMap) 120 | for _, v := range actualKeys { 121 | Expect(allowedKeys).To(ContainElement(v)) 122 | } 123 | } 124 | 125 | func expectOnlyKeys(genericMap *json.Json, keys ...string) { 126 | aMap, ok := genericMap.Interface().(map[string]interface{}) 127 | Expect(ok).To(BeTrue()) 128 | Expect(getKeysOf(aMap)).To(ConsistOf(keys)) 129 | } 130 | 131 | func getKeysOf(input map[string]interface{}) []string { 132 | keys := make([]string, len(input)) 133 | i := 0 134 | 135 | for k := range input { 136 | keys[i] = k 137 | i++ 138 | } 139 | return keys 140 | } 141 | 142 | // Converts from dynamic client representation to *json.Json. 143 | // "spec" must be a top-level attribute of dynamicObject. 144 | func wrapSpecAsJson(dynamicObject *unstructured.Unstructured) *json.Json { 145 | //A little trick since go-simplejson doesn't offer a constructor for arbitrary data 146 | res := json.New() 147 | res.Set("data", dynamicObject.UnstructuredContent()) 148 | return res.GetPath("data", "spec") 149 | } 150 | --------------------------------------------------------------------------------