├── .github └── workflows │ └── main.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── ratelimit_types.go │ └── zz_generated.deepcopy.go ├── catalog-info.yaml ├── chart └── rate-limit-operator │ ├── Chart.yaml │ ├── crds │ └── networking.softonic.io_ratelimits.yaml │ ├── templates │ ├── _helpers.tpl │ ├── auth_proxy_service.yaml │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── deployment.yaml │ ├── role.yaml │ ├── rolebinding.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── config ├── crd │ ├── bases │ │ ├── networking.istio.io_envoyfilters.yaml │ │ ├── networking.istio.io_gateways.yaml │ │ ├── networking.istio.io_virtualservices.yaml │ │ ├── networking.softonic.io_ratelimits.yaml │ │ └── networking.softonic.io_virtualservices.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_ratelimits.yaml │ │ └── webhook_in_ratelimits.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── ratelimit_editor_role.yaml │ ├── ratelimit_viewer_role.yaml │ ├── role.yaml │ └── role_binding.yaml └── samples │ ├── networking_istio_v1beta1_virtualservice.yaml │ ├── networking_v1alpha1_ratelimit.yaml │ ├── networking_v1alpha1_ratelimit_3.yaml │ ├── networking_v1alpha1_ratelimit_4.yaml │ ├── networking_v1alpha1_ratelimit_auth_header.yaml │ ├── networking_v1alpha1_ratelimit_multidescriptor.yaml │ ├── networking_v1alpha1_ratelimit_newformat.yaml │ ├── networking_v1alpha1_virtualservice.yaml │ └── virtualservice_test.yaml ├── controllers ├── actions.go ├── decommission.go ├── envoyfilterobject.go ├── helpers.go └── ratelimit_controller.go ├── docs ├── README.md └── diagram.png ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── main.go ├── mkdocs.yml └── pkg ├── log └── level.go ├── ratelimit └── types │ ├── cluster_patch.go │ └── httpfilter_patch.go └── utils └── struct.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Helm Chart 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | paths: 10 | - 'chart/**' 11 | 12 | jobs: 13 | push-chart: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: goodsmileduck/helm-push-action@v3.2.4 18 | env: 19 | SOURCE_DIR: 'chart' 20 | CHART_FOLDER: 'rate-limit-operator' 21 | CHARTMUSEUM_URL: 'https://charts.softonic.io' 22 | CHARTMUSEUM_USER: '${{ secrets.CHARTMUSEUM_USER }}' 23 | CHARTMUSEUM_PASSWORD: '${{ secrets.CHARTMUSEUM_PASSWORD }}' 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.18 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | COPY api/ api/ 15 | COPY controllers/ controllers/ 16 | COPY pkg/ pkg/ 17 | 18 | # Build 19 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go 20 | 21 | # Use distroless as minimal base image to package the manager binary 22 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 23 | FROM gcr.io/distroless/static:nonroot 24 | WORKDIR / 25 | COPY --from=builder /workspace/manager . 26 | USER nonroot:nonroot 27 | 28 | ENTRYPOINT ["/manager"] 29 | -------------------------------------------------------------------------------- /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 | IMG ?= softonic/rate-limit-operator:1.1.0 2 | CRD_OPTIONS ?= "crd:trivialVersions=true" 3 | BIN := rate-limit-operator 4 | PKG := github.com/softonic/rate-limit-operator 5 | VERSION ?= 1.1.0 6 | ARCH ?= amd64 7 | APP ?= rate-limit-operator 8 | NAMESPACE ?= rate-limit-operator-system 9 | RELEASE_NAME ?= rate-limit-operator 10 | REPOSITORY ?= softonic/rate-limit-operator 11 | 12 | IMAGE := $(BIN) 13 | 14 | BUILD_IMAGE ?= golang:1.14-buster 15 | 16 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 17 | ifeq (,$(shell go env GOBIN)) 18 | GOBIN=$(shell go env GOPATH)/bin 19 | else 20 | GOBIN=$(shell go env GOBIN) 21 | endif 22 | 23 | all: manager 24 | 25 | .PHONY: all 26 | all: dev 27 | 28 | .PHONY: build 29 | build: generate 30 | go mod download 31 | GOARCH=${ARCH} go build -ldflags "-X ${PKG}/pkg/version.Version=${VERSION}" . 32 | 33 | .PHONY: image 34 | image: 35 | docker build -t $(IMG) -f Dockerfile . 36 | docker tag $(IMG) $(REPOSITORY):latest 37 | 38 | .PHONY: docker-push 39 | docker-push: 40 | docker push $(IMG) 41 | docker push $(REPOSITORY):latest 42 | 43 | .PHONY: make-manifest 44 | make-manifest: controller-gen manifests 45 | docker run --rm -v $(PWD):/app -w /app/ alpine/helm:3.2.3 template --release-name $(RELEASE_NAME) --set "image.tag=$(VERSION)" --set "image.repository=$(REPOSITORY)" -f chart/rate-limit-operator/values.yaml chart/rate-limit-operator > manifest.yaml 46 | 47 | .PHONY: undeploy 48 | undeploy: 49 | kubectl delete -f manifest.yaml || true 50 | 51 | .PHONY: deploy 52 | deploy: make-manifest 53 | kubectl apply -f manifest.yaml 54 | 55 | .PHONY: helm-deploy 56 | helm-deploy: install-crd 57 | helm upgrade --install $(RELEASE_NAME) --namespace $(NAMESPACE) --create-namespace --set "image.tag=$(VERSION)" -f chart/rate-limit-operator/values.yaml chart/rate-limit-operator 58 | 59 | .PHONY: install-crd 60 | install-crd: 61 | cp config/crd/bases/networking.softonic.io_ratelimits.yaml chart/rate-limit-operator/crds/networking.softonic.io_ratelimits.yaml 62 | 63 | # Run tests 64 | .PHONY: test 65 | test: generate fmt vet manifests 66 | go test ./... -coverprofile cover.out 67 | 68 | # Build manager binary 69 | .PHONY: manager 70 | manager: generate fmt vet 71 | go build -o bin/manager main.go 72 | 73 | # Run against the configured Kubernetes cluster in ~/.kube/config 74 | .PHONY: run 75 | run: generate fmt vet manifests 76 | go run ./main.go 77 | 78 | # Install CRDs into a cluster 79 | .PHONY: install 80 | install: manifests 81 | kustomize build config/crd | kubectl apply -f - 82 | 83 | # Uninstall CRDs from a cluster 84 | .PHONY: uninstall 85 | uninstall: manifests 86 | kustomize build config/crd | kubectl delete -f - 87 | 88 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 89 | .PHONY: deploy 90 | deploy: manifests 91 | cd config/manager && kustomize edit set image controller=${IMG} 92 | kustomize build config/default | kubectl apply -f - 93 | 94 | # Generate manifests e.g. CRD, RBAC etc. 95 | .PHONY: manifests 96 | manifests: controller-gen 97 | # $(CONTROLLER_GEN) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 98 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 99 | 100 | # Run go fmt against code 101 | .PHONY: fmt 102 | fmt: 103 | go fmt ./... 104 | 105 | # Run go vet against code 106 | .PHONY: vet 107 | vet: 108 | go vet ./... 109 | 110 | # Generate code 111 | .PHONY: generate 112 | generate: controller-gen 113 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 114 | 115 | # find or download controller-gen 116 | # download controller-gen if necessary 117 | .PHONY: controller-gen 118 | controller-gen: 119 | ifeq (, $(shell which controller-gen)) 120 | @{ \ 121 | set -e ;\ 122 | CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ 123 | cd $$CONTROLLER_GEN_TMP_DIR ;\ 124 | go mod init tmp ;\ 125 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.9.0 ;\ 126 | rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ 127 | } 128 | CONTROLLER_GEN=$(GOBIN)/controller-gen 129 | else 130 | CONTROLLER_GEN=$(shell which controller-gen) 131 | endif 132 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: softonic.io 2 | repo: github.com/softonic/rate-limit-operator 3 | resources: 4 | - group: networking 5 | kind: RateLimit 6 | version: v1alpha1 7 | - group: networking 8 | kind: VirtualService 9 | version: v1alpha1 10 | version: "2" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/softonic/rate-limit-operator)](https://goreportcard.com/report/softonic/rate-limit-operator) 2 | [![Releases](https://img.shields.io/github/release-pre/softonic/rate-limit-operator.svg?sort=semver)](https://github.com/softonic/rate-limit-operator/releases) 3 | [![LICENSE](https://img.shields.io/github/license/softonic/rate-limit-operator.svg)](https://github.com/softonic/rate-limit-operator/blob/master/LICENSE) 4 | [![DockerHub](https://img.shields.io/docker/pulls/softonic/rate-limit-operator.svg)](https://hub.docker.com/r/softonic/rate-limit-operator) 5 | 6 | 7 | # rate-limit-operator 8 | Rate Limit operator for Envoy Proxy 9 | 10 | # Quick Start 11 | 12 | Steps to got Operator working 13 | 14 | - Deploy the Operator ( kubectl or helm ) 15 | - Deploy RateLimit CR 16 | 17 | ## Deployment 18 | 19 | ### Requirements 20 | 21 | In this example we assume you already have a k8s cluster running 22 | 23 | ### Deploy using kubectl 24 | 25 | ```bash 26 | $ make deploy 27 | ``` 28 | 29 | You can find public image in the softonic/rate-limit-operator docker hub repository. 30 | 31 | ### Deploy using Helm 32 | 33 | ```bash 34 | $ make helm-deploy 35 | ``` 36 | 37 | 38 | ## Create a CR ratelimit 39 | 40 | ```bash 41 | $ kubectl apply -f config/samples/networking_v1alpha1_ratelimit.yaml 42 | ``` 43 | 44 | # DEVEL ENVIRONMENT 45 | 46 | Compile the code and deploy the needed resources 47 | 48 | ```bash 49 | $ make generate 50 | $ make run 51 | ``` 52 | 53 | 54 | # Motivation 55 | 56 | 57 | The operator will help you configure the necessary resources to get to this 58 | 59 | https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/rate_limit_filter#config-http-filters-rate-limit 60 | 61 | from a CR that you will need to provide. 62 | 63 | This operator will need this CR, and from this configuration will create the necessary envoyfilters resources in istio 64 | control plane, and the necessary configs ratelimit system 65 | 66 | https://github.com/envoyproxy/ratelimit 67 | 68 | needs. 69 | 70 | This is accomplished with a new CRD. 71 | 72 | In this new CRD, you can set different configuration depending on your needs. 73 | 74 | Here is an example you can start with 75 | 76 | ``` 77 | apiVersion: networking.softonic.io/v1alpha1 78 | kind: RateLimit 79 | metadata: 80 | finalizers: 81 | - ratelimit.networking.softonic.io 82 | name: test 83 | spec: 84 | destinationCluster: outbound|80||server.nameSpaceTarget.svc.cluster.local 85 | rate: 86 | - dimensions: 87 | - request_header: 88 | descriptor_key: remote_address 89 | header_name: x-custom-user-ip 90 | requestPerUnit: 100 91 | unit: second 92 | targetRef: 93 | apiVersion: networking.istio.io/v1alpha3 94 | kind: VirtualService 95 | name: nameVS 96 | namespace: nameSpaceTarget 97 | ``` 98 | 99 | 100 | * targetRef will point to a VS that will have the host field that you will need in your envoyfilter in order to apply the routing 101 | * destinationCluster ( optional ) is the Cluster ( here cluster is referring to the concept of cluster in the envoy/istio language ) 102 | If you dont set this field, operator will try to find the destination cluster for your application looking at the Virtual Service http route destination 103 | * following configuration refer to what to limit. In this case we are limiting 100 per second per x-custom-ip 104 | 105 | 106 | # Diagram 107 | 108 | 109 | ![Image of operator flow](/docs/diagram.png) 110 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the networking v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=networking.softonic.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "networking.softonic.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/ratelimit_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | v1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | type OutputRatelimitsEnvoyFilter struct { 25 | RateLimits []RateLimits `json:"rate_limits"` 26 | } 27 | 28 | type RateLimits struct { 29 | Actions []Actions `json:"actions,omitempty"` 30 | } 31 | 32 | type RequestHeaders struct { 33 | DescriptorKey string `json:"descriptor_key,omitempty"` 34 | HeaderName string `json:"header_name,omitempty"` 35 | } 36 | 37 | type Actions struct { 38 | RequestHeaders *RequestHeaders `json:"request_headers,omitempty"` 39 | HeaderValueMatch *HeaderValueMatch `json:"header_value_match,omitempty"` 40 | DestinationCluster *DestinationClusterHeader `json:"destination_cluster,omitempty"` 41 | } 42 | 43 | type DestinationClusterHeader struct{} 44 | 45 | type OutputConfig struct { 46 | DescriptorsParent []DescriptorsParent `json:"descriptors"` 47 | Domain string `json:"domain"` 48 | } 49 | type RateLimitPerDescriptor struct { 50 | RequestsPerUnit int `json:"requests_per_unit"` 51 | Unit string `json:"unit"` 52 | } 53 | type Descriptors struct { 54 | Key string `json:"key"` 55 | RateLimit RateLimitPerDescriptor `json:"rate_limit"` 56 | Value string `json:"value"` 57 | } 58 | type DescriptorsParent struct { 59 | Descriptors []Descriptors `json:"descriptors"` 60 | Key string `json:"key"` 61 | Value string `json:"value,omitempty"` 62 | } 63 | 64 | type RequestHeader struct { 65 | DescriptorKey string `json:"descriptor_key"` 66 | HeaderName string `json:"header_name"` 67 | Value string `json:"value,omitempty"` 68 | } 69 | 70 | type Headers struct { 71 | Name string `json:"name"` 72 | PrefixMatch string `json:"prefix_match"` 73 | } 74 | 75 | type HeaderValueMatch struct { 76 | DescriptorValue string `json:"descriptor_value"` 77 | Headers []Headers `json:"headers"` 78 | } 79 | 80 | type Dimensions struct { 81 | RequestHeader RequestHeader `json:"request_header,omitempty"` 82 | HeaderValueMatch HeaderValueMatch `json:"header_value_match,omitempty"` 83 | } 84 | type Rate struct { 85 | Unit string `json:"unit"` 86 | RequestPerUnit int `json:"requestPerUnit"` 87 | Dimensions []Dimensions `json:"dimensions"` 88 | } 89 | 90 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 91 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 92 | 93 | type RateLimitSpec struct { 94 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 95 | // Important: Run "make" to regenerate code after modifying this file 96 | TargetRef v1.ObjectReference `json:"targetRef"` 97 | DestinationCluster string `json:"destinationCluster,omitempty"` 98 | Rate []Rate `json:"rate"` 99 | // +kubebuilder:validation:Optional 100 | ApplyToRoutes []string `json:"applytoroutes,omitempty"` 101 | } 102 | 103 | // RateLimitStatus defines the observed state of RateLimit 104 | type RateLimitStatus struct { 105 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 106 | // Important: Run "make" to regenerate code after modifying this file 107 | } 108 | 109 | // +kubebuilder:object:root=true 110 | 111 | // RateLimit is the Schema for the ratelimits API 112 | type RateLimit struct { 113 | metav1.TypeMeta `json:",inline"` 114 | metav1.ObjectMeta `json:"metadata,omitempty"` 115 | 116 | Spec RateLimitSpec `json:"spec,omitempty"` 117 | Status RateLimitStatus `json:"status,omitempty"` 118 | } 119 | 120 | // +kubebuilder:object:root=true 121 | 122 | // RateLimitList contains a list of RateLimit 123 | type RateLimitList struct { 124 | metav1.TypeMeta `json:",inline"` 125 | metav1.ListMeta `json:"metadata,omitempty"` 126 | Items []RateLimit `json:"items"` 127 | } 128 | 129 | func init() { 130 | SchemeBuilder.Register(&RateLimit{}, &RateLimitList{}) 131 | } 132 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *Actions) DeepCopyInto(out *Actions) { 30 | *out = *in 31 | if in.RequestHeaders != nil { 32 | in, out := &in.RequestHeaders, &out.RequestHeaders 33 | *out = new(RequestHeaders) 34 | **out = **in 35 | } 36 | if in.HeaderValueMatch != nil { 37 | in, out := &in.HeaderValueMatch, &out.HeaderValueMatch 38 | *out = new(HeaderValueMatch) 39 | (*in).DeepCopyInto(*out) 40 | } 41 | if in.DestinationCluster != nil { 42 | in, out := &in.DestinationCluster, &out.DestinationCluster 43 | *out = new(DestinationClusterHeader) 44 | **out = **in 45 | } 46 | } 47 | 48 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Actions. 49 | func (in *Actions) DeepCopy() *Actions { 50 | if in == nil { 51 | return nil 52 | } 53 | out := new(Actions) 54 | in.DeepCopyInto(out) 55 | return out 56 | } 57 | 58 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 59 | func (in *Descriptors) DeepCopyInto(out *Descriptors) { 60 | *out = *in 61 | out.RateLimit = in.RateLimit 62 | } 63 | 64 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Descriptors. 65 | func (in *Descriptors) DeepCopy() *Descriptors { 66 | if in == nil { 67 | return nil 68 | } 69 | out := new(Descriptors) 70 | in.DeepCopyInto(out) 71 | return out 72 | } 73 | 74 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 75 | func (in *DescriptorsParent) DeepCopyInto(out *DescriptorsParent) { 76 | *out = *in 77 | if in.Descriptors != nil { 78 | in, out := &in.Descriptors, &out.Descriptors 79 | *out = make([]Descriptors, len(*in)) 80 | copy(*out, *in) 81 | } 82 | } 83 | 84 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DescriptorsParent. 85 | func (in *DescriptorsParent) DeepCopy() *DescriptorsParent { 86 | if in == nil { 87 | return nil 88 | } 89 | out := new(DescriptorsParent) 90 | in.DeepCopyInto(out) 91 | return out 92 | } 93 | 94 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 95 | func (in *DestinationClusterHeader) DeepCopyInto(out *DestinationClusterHeader) { 96 | *out = *in 97 | } 98 | 99 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationClusterHeader. 100 | func (in *DestinationClusterHeader) DeepCopy() *DestinationClusterHeader { 101 | if in == nil { 102 | return nil 103 | } 104 | out := new(DestinationClusterHeader) 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 *Dimensions) DeepCopyInto(out *Dimensions) { 111 | *out = *in 112 | out.RequestHeader = in.RequestHeader 113 | in.HeaderValueMatch.DeepCopyInto(&out.HeaderValueMatch) 114 | } 115 | 116 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Dimensions. 117 | func (in *Dimensions) DeepCopy() *Dimensions { 118 | if in == nil { 119 | return nil 120 | } 121 | out := new(Dimensions) 122 | in.DeepCopyInto(out) 123 | return out 124 | } 125 | 126 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 127 | func (in *HeaderValueMatch) DeepCopyInto(out *HeaderValueMatch) { 128 | *out = *in 129 | if in.Headers != nil { 130 | in, out := &in.Headers, &out.Headers 131 | *out = make([]Headers, len(*in)) 132 | copy(*out, *in) 133 | } 134 | } 135 | 136 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderValueMatch. 137 | func (in *HeaderValueMatch) DeepCopy() *HeaderValueMatch { 138 | if in == nil { 139 | return nil 140 | } 141 | out := new(HeaderValueMatch) 142 | in.DeepCopyInto(out) 143 | return out 144 | } 145 | 146 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 147 | func (in *Headers) DeepCopyInto(out *Headers) { 148 | *out = *in 149 | } 150 | 151 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Headers. 152 | func (in *Headers) DeepCopy() *Headers { 153 | if in == nil { 154 | return nil 155 | } 156 | out := new(Headers) 157 | in.DeepCopyInto(out) 158 | return out 159 | } 160 | 161 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 162 | func (in *OutputConfig) DeepCopyInto(out *OutputConfig) { 163 | *out = *in 164 | if in.DescriptorsParent != nil { 165 | in, out := &in.DescriptorsParent, &out.DescriptorsParent 166 | *out = make([]DescriptorsParent, len(*in)) 167 | for i := range *in { 168 | (*in)[i].DeepCopyInto(&(*out)[i]) 169 | } 170 | } 171 | } 172 | 173 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OutputConfig. 174 | func (in *OutputConfig) DeepCopy() *OutputConfig { 175 | if in == nil { 176 | return nil 177 | } 178 | out := new(OutputConfig) 179 | in.DeepCopyInto(out) 180 | return out 181 | } 182 | 183 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 184 | func (in *OutputRatelimitsEnvoyFilter) DeepCopyInto(out *OutputRatelimitsEnvoyFilter) { 185 | *out = *in 186 | if in.RateLimits != nil { 187 | in, out := &in.RateLimits, &out.RateLimits 188 | *out = make([]RateLimits, len(*in)) 189 | for i := range *in { 190 | (*in)[i].DeepCopyInto(&(*out)[i]) 191 | } 192 | } 193 | } 194 | 195 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OutputRatelimitsEnvoyFilter. 196 | func (in *OutputRatelimitsEnvoyFilter) DeepCopy() *OutputRatelimitsEnvoyFilter { 197 | if in == nil { 198 | return nil 199 | } 200 | out := new(OutputRatelimitsEnvoyFilter) 201 | in.DeepCopyInto(out) 202 | return out 203 | } 204 | 205 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 206 | func (in *Rate) DeepCopyInto(out *Rate) { 207 | *out = *in 208 | if in.Dimensions != nil { 209 | in, out := &in.Dimensions, &out.Dimensions 210 | *out = make([]Dimensions, len(*in)) 211 | for i := range *in { 212 | (*in)[i].DeepCopyInto(&(*out)[i]) 213 | } 214 | } 215 | } 216 | 217 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rate. 218 | func (in *Rate) DeepCopy() *Rate { 219 | if in == nil { 220 | return nil 221 | } 222 | out := new(Rate) 223 | in.DeepCopyInto(out) 224 | return out 225 | } 226 | 227 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 228 | func (in *RateLimit) DeepCopyInto(out *RateLimit) { 229 | *out = *in 230 | out.TypeMeta = in.TypeMeta 231 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 232 | in.Spec.DeepCopyInto(&out.Spec) 233 | out.Status = in.Status 234 | } 235 | 236 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimit. 237 | func (in *RateLimit) DeepCopy() *RateLimit { 238 | if in == nil { 239 | return nil 240 | } 241 | out := new(RateLimit) 242 | in.DeepCopyInto(out) 243 | return out 244 | } 245 | 246 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 247 | func (in *RateLimit) DeepCopyObject() runtime.Object { 248 | if c := in.DeepCopy(); c != nil { 249 | return c 250 | } 251 | return nil 252 | } 253 | 254 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 255 | func (in *RateLimitList) DeepCopyInto(out *RateLimitList) { 256 | *out = *in 257 | out.TypeMeta = in.TypeMeta 258 | in.ListMeta.DeepCopyInto(&out.ListMeta) 259 | if in.Items != nil { 260 | in, out := &in.Items, &out.Items 261 | *out = make([]RateLimit, len(*in)) 262 | for i := range *in { 263 | (*in)[i].DeepCopyInto(&(*out)[i]) 264 | } 265 | } 266 | } 267 | 268 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitList. 269 | func (in *RateLimitList) DeepCopy() *RateLimitList { 270 | if in == nil { 271 | return nil 272 | } 273 | out := new(RateLimitList) 274 | in.DeepCopyInto(out) 275 | return out 276 | } 277 | 278 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 279 | func (in *RateLimitList) DeepCopyObject() runtime.Object { 280 | if c := in.DeepCopy(); c != nil { 281 | return c 282 | } 283 | return nil 284 | } 285 | 286 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 287 | func (in *RateLimitPerDescriptor) DeepCopyInto(out *RateLimitPerDescriptor) { 288 | *out = *in 289 | } 290 | 291 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPerDescriptor. 292 | func (in *RateLimitPerDescriptor) DeepCopy() *RateLimitPerDescriptor { 293 | if in == nil { 294 | return nil 295 | } 296 | out := new(RateLimitPerDescriptor) 297 | in.DeepCopyInto(out) 298 | return out 299 | } 300 | 301 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 302 | func (in *RateLimitSpec) DeepCopyInto(out *RateLimitSpec) { 303 | *out = *in 304 | out.TargetRef = in.TargetRef 305 | if in.Rate != nil { 306 | in, out := &in.Rate, &out.Rate 307 | *out = make([]Rate, len(*in)) 308 | for i := range *in { 309 | (*in)[i].DeepCopyInto(&(*out)[i]) 310 | } 311 | } 312 | if in.ApplyToRoutes != nil { 313 | in, out := &in.ApplyToRoutes, &out.ApplyToRoutes 314 | *out = make([]string, len(*in)) 315 | copy(*out, *in) 316 | } 317 | } 318 | 319 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitSpec. 320 | func (in *RateLimitSpec) DeepCopy() *RateLimitSpec { 321 | if in == nil { 322 | return nil 323 | } 324 | out := new(RateLimitSpec) 325 | in.DeepCopyInto(out) 326 | return out 327 | } 328 | 329 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 330 | func (in *RateLimitStatus) DeepCopyInto(out *RateLimitStatus) { 331 | *out = *in 332 | } 333 | 334 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitStatus. 335 | func (in *RateLimitStatus) DeepCopy() *RateLimitStatus { 336 | if in == nil { 337 | return nil 338 | } 339 | out := new(RateLimitStatus) 340 | in.DeepCopyInto(out) 341 | return out 342 | } 343 | 344 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 345 | func (in *RateLimits) DeepCopyInto(out *RateLimits) { 346 | *out = *in 347 | if in.Actions != nil { 348 | in, out := &in.Actions, &out.Actions 349 | *out = make([]Actions, len(*in)) 350 | for i := range *in { 351 | (*in)[i].DeepCopyInto(&(*out)[i]) 352 | } 353 | } 354 | } 355 | 356 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimits. 357 | func (in *RateLimits) DeepCopy() *RateLimits { 358 | if in == nil { 359 | return nil 360 | } 361 | out := new(RateLimits) 362 | in.DeepCopyInto(out) 363 | return out 364 | } 365 | 366 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 367 | func (in *RequestHeader) DeepCopyInto(out *RequestHeader) { 368 | *out = *in 369 | } 370 | 371 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeader. 372 | func (in *RequestHeader) DeepCopy() *RequestHeader { 373 | if in == nil { 374 | return nil 375 | } 376 | out := new(RequestHeader) 377 | in.DeepCopyInto(out) 378 | return out 379 | } 380 | 381 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 382 | func (in *RequestHeaders) DeepCopyInto(out *RequestHeaders) { 383 | *out = *in 384 | } 385 | 386 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaders. 387 | func (in *RequestHeaders) DeepCopy() *RequestHeaders { 388 | if in == nil { 389 | return nil 390 | } 391 | out := new(RequestHeaders) 392 | in.DeepCopyInto(out) 393 | return out 394 | } 395 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: rate-limit-operator 5 | description: | 6 | rate-limit-operator 7 | links: 8 | - url: https://github.com/softonic/rate-limit-operator 9 | title: GitHub Repo 10 | icon: github 11 | - title: Documentation 12 | url: https://github.com/softonic/rate-limit-operator/blob/master/README.md 13 | annotations: 14 | github.com/project-slug: softonic/rate-limit-operator 15 | backstage.io/techdocs-ref: dir:. 16 | spec: 17 | type: application 18 | owner: sre 19 | lifecycle: production 20 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: rate-limit-operator 3 | description: 4 | type: application 5 | version: 1.1.0-2 6 | appVersion: 1.1.0 7 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/crds/networking.softonic.io_ratelimits.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.2 7 | creationTimestamp: null 8 | name: ratelimits.networking.softonic.io 9 | spec: 10 | group: networking.softonic.io 11 | names: 12 | kind: RateLimit 13 | listKind: RateLimitList 14 | plural: ratelimits 15 | singular: ratelimit 16 | scope: Namespaced 17 | versions: 18 | - name: v1alpha1 19 | schema: 20 | openAPIV3Schema: 21 | description: RateLimit is the Schema for the ratelimits API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation 25 | of an object. Servers should convert recognized schemas to the latest 26 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 27 | type: string 28 | kind: 29 | description: 'Kind is a string value representing the REST resource this 30 | object represents. Servers may infer this from the endpoint the client 31 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | properties: 37 | applytoroutes: 38 | items: 39 | type: string 40 | type: array 41 | destinationCluster: 42 | type: string 43 | rate: 44 | items: 45 | properties: 46 | dimensions: 47 | items: 48 | properties: 49 | header_value_match: 50 | properties: 51 | descriptor_value: 52 | type: string 53 | headers: 54 | items: 55 | properties: 56 | name: 57 | type: string 58 | prefix_match: 59 | type: string 60 | required: 61 | - name 62 | - prefix_match 63 | type: object 64 | type: array 65 | required: 66 | - descriptor_value 67 | - headers 68 | type: object 69 | request_header: 70 | properties: 71 | descriptor_key: 72 | type: string 73 | header_name: 74 | type: string 75 | value: 76 | type: string 77 | required: 78 | - descriptor_key 79 | - header_name 80 | type: object 81 | type: object 82 | type: array 83 | requestPerUnit: 84 | type: integer 85 | unit: 86 | type: string 87 | required: 88 | - dimensions 89 | - requestPerUnit 90 | - unit 91 | type: object 92 | type: array 93 | targetRef: 94 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 95 | Important: Run "make" to regenerate code after modifying this file' 96 | properties: 97 | apiVersion: 98 | description: API version of the referent. 99 | type: string 100 | fieldPath: 101 | description: 'If referring to a piece of an object instead of 102 | an entire object, this string should contain a valid JSON/Go 103 | field access statement, such as desiredState.manifest.containers[2]. 104 | For example, if the object reference is to a container within 105 | a pod, this would take on a value like: "spec.containers{name}" 106 | (where "name" refers to the name of the container that triggered 107 | the event) or if no container name is specified "spec.containers[2]" 108 | (container with index 2 in this pod). This syntax is chosen 109 | only to have some well-defined way of referencing a part of 110 | an object. TODO: this design is not final and this field is 111 | subject to change in the future.' 112 | type: string 113 | kind: 114 | description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 115 | type: string 116 | name: 117 | description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' 118 | type: string 119 | namespace: 120 | description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' 121 | type: string 122 | resourceVersion: 123 | description: 'Specific resourceVersion to which this reference 124 | is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' 125 | type: string 126 | uid: 127 | description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' 128 | type: string 129 | type: object 130 | x-kubernetes-map-type: atomic 131 | required: 132 | - rate 133 | - targetRef 134 | type: object 135 | status: 136 | description: RateLimitStatus defines the observed state of RateLimit 137 | type: object 138 | type: object 139 | served: true 140 | storage: true 141 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "rate-limit-operator.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "rate-limit-operator.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "rate-limit-operator.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "rate-limit-operator.labels" -}} 38 | helm.sh/chart: {{ include "rate-limit-operator.chart" . }} 39 | {{ include "rate-limit-operator.selectorLabels" . }} 40 | {{- if .Chart.AppVersion }} 41 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 42 | {{- end }} 43 | app.kubernetes.io/managed-by: {{ .Release.Service }} 44 | {{- end -}} 45 | 46 | {{/* 47 | Selector labels 48 | */}} 49 | {{- define "rate-limit-operator.selectorLabels" -}} 50 | app.kubernetes.io/name: {{ include "rate-limit-operator.name" . }} 51 | app.kubernetes.io/instance: {{ .Release.Name }} 52 | {{- end -}} 53 | 54 | {{/* 55 | Create the name of the service account to use 56 | */}} 57 | {{- define "rate-limit-operator.serviceAccountName" -}} 58 | {{ default (include "rate-limit-operator.fullname" .) .Values.serviceAccount.name }} 59 | {{- end -}} 60 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | {{- with .Values.podLabels }} 7 | {{ toYaml . | indent 4 }} 8 | {{- end }} 9 | name: {{ include "rate-limit-operator.fullname" . }}-controller-manager-metrics-service 10 | spec: 11 | ports: 12 | - name: https 13 | port: 8443 14 | targetPort: https 15 | selector: 16 | control-plane: controller-manager 17 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "rate-limit-operator.fullname" . }}-manager-role 5 | rules: 6 | - apiGroups: 7 | - '*' 8 | resources: 9 | - configmaps 10 | verbs: 11 | - create 12 | - delete 13 | - get 14 | - list 15 | - patch 16 | - update 17 | - watch 18 | - apiGroups: 19 | - '*' 20 | resources: 21 | - gateways 22 | verbs: 23 | - create 24 | - delete 25 | - get 26 | - list 27 | - patch 28 | - update 29 | - watch 30 | - apiGroups: 31 | - '*' 32 | resources: 33 | - services 34 | verbs: 35 | - get 36 | - list 37 | - watch 38 | - apiGroups: 39 | - '*' 40 | resources: 41 | - virtualservices 42 | verbs: 43 | - create 44 | - delete 45 | - get 46 | - list 47 | - patch 48 | - update 49 | - watch 50 | - apiGroups: 51 | - apps 52 | resources: 53 | - deployments 54 | verbs: 55 | - create 56 | - delete 57 | - get 58 | - list 59 | - patch 60 | - update 61 | - watch 62 | - apiGroups: 63 | - batch 64 | resources: 65 | - envoyfilters/status 66 | verbs: 67 | - get 68 | - apiGroups: 69 | - "" 70 | resources: 71 | - configmaps 72 | verbs: 73 | - create 74 | - delete 75 | - get 76 | - list 77 | - patch 78 | - update 79 | - watch 80 | - apiGroups: 81 | - networking.istio.io 82 | resources: 83 | - envoyfilters 84 | verbs: 85 | - create 86 | - delete 87 | - get 88 | - list 89 | - patch 90 | - update 91 | - watch 92 | - apiGroups: 93 | - networking.softonic.io 94 | resources: 95 | - ratelimits 96 | verbs: 97 | - create 98 | - delete 99 | - get 100 | - list 101 | - patch 102 | - update 103 | - watch 104 | - apiGroups: 105 | - networking.softonic.io 106 | resources: 107 | - ratelimits/status 108 | verbs: 109 | - get 110 | - patch 111 | - update 112 | - apiGroups: 113 | - coordination.k8s.io 114 | resources: 115 | - leases 116 | verbs: 117 | - create 118 | - get 119 | - list 120 | - update 121 | 122 | --- 123 | 124 | apiVersion: rbac.authorization.k8s.io/v1 125 | kind: ClusterRole 126 | metadata: 127 | name: {{ include "rate-limit-operator.fullname" . }}-proxy-role 128 | rules: 129 | - apiGroups: 130 | - authentication.k8s.io 131 | resources: 132 | - tokenreviews 133 | verbs: 134 | - create 135 | - apiGroups: 136 | - authorization.k8s.io 137 | resources: 138 | - subjectaccessreviews 139 | verbs: 140 | - create 141 | 142 | 143 | --- 144 | apiVersion: rbac.authorization.k8s.io/v1 145 | kind: ClusterRole 146 | metadata: 147 | name: {{ include "rate-limit-operator.fullname" . }}-metrics-reader 148 | rules: 149 | - nonResourceURLs: 150 | - /metrics 151 | verbs: 152 | - get 153 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: {{ include "rate-limit-operator.fullname" . }}-manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: rate-limit-operator-manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: {{ include "rate-limit-operator.serviceAccountName" . }} 12 | namespace: {{ .Release.Namespace }} 13 | 14 | --- 15 | 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: {{ include "rate-limit-operator.fullname" . }}-proxy-rolebinding 20 | roleRef: 21 | apiGroup: rbac.authorization.k8s.io 22 | kind: ClusterRole 23 | name: rate-limit-operator-proxy-role 24 | subjects: 25 | - kind: ServiceAccount 26 | name: {{ include "rate-limit-operator.serviceAccountName" . }} 27 | namespace: {{ .Release.Namespace }} 28 | 29 | --- 30 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "rate-limit-operator.fullname" . }}-controller-manager 5 | labels: 6 | {{- include "rate-limit-operator.labels" . | nindent 6 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | app: rate-limit-operator 13 | {{- include "rate-limit-operator.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | labels: 17 | control-plane: controller-manager 18 | app: rate-limit-operator 19 | {{- include "rate-limit-operator.selectorLabels" . | nindent 8 }} 20 | spec: 21 | {{- with .Values.imagePullSecrets }} 22 | imagePullSecrets: 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | serviceAccountName: {{ include "rate-limit-operator.serviceAccountName" . }} 26 | securityContext: 27 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 28 | containers: 29 | - args: 30 | - --secure-listen-address=0.0.0.0:8443 31 | - --upstream=http://127.0.0.1:8080/ 32 | - --logtostderr=true 33 | - --v=10 34 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 35 | name: kube-rbac-proxy 36 | ports: 37 | - containerPort: 8443 38 | name: https 39 | - command: 40 | - /manager 41 | args: 42 | - --enable-leader-election 43 | env: 44 | - name: ISTIO_NAMESPACE 45 | value: {{ .Values.istioNamespace }} 46 | - name: CONTROLLER_NAMESPACE 47 | value: {{ .Values.controllerNamespace }} 48 | - name: ADDRESS_RATELIMIT_ENDPOINT 49 | value: {{ .Values.addressRatelimitEndpoint }} 50 | - name: DEPLOYMENT_NAME 51 | value: {{ .Values.deploymentName }} 52 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 53 | name: manager 54 | resources: 55 | {{- toYaml .Values.resources | nindent 10 }} 56 | terminationGracePeriodSeconds: 10 57 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: rate-limit-operator-leader-election-role 5 | namespace: rate-limit-operator-system 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 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | --- 34 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: rate-limit-operator-leader-election-rolebinding 5 | namespace: rate-limit-operator-system 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: Role 9 | name: rate-limit-operator-leader-election-role 10 | subjects: 11 | - kind: ServiceAccount 12 | name: {{ include "rate-limit-operator.serviceAccountName" . }} 13 | namespace: rate-limit-operator-system 14 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ include "rate-limit-operator.serviceAccountName" . }} 5 | labels: 6 | {{- include "rate-limit-operator.labels" . | nindent 4 }} 7 | {{- with .Values.serviceAccount.annotations }} 8 | annotations: 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /chart/rate-limit-operator/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for rate-limit-operator. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: softonic/rate-limit-operator 9 | pullPolicy: IfNotPresent 10 | tag: 0.1.1 11 | 12 | istioNamespace: "istio-system" 13 | controllerNamespace: "rate-limit-operator-system" 14 | deploymentName: "rate-limit-operator-ratelimit" 15 | 16 | addressRatelimitEndpoint: "istio-system-ratelimit.istio-system.svc.cluster.local" 17 | 18 | imagePullSecrets: [] 19 | nameOverride: "" 20 | fullnameOverride: "" 21 | 22 | serviceAccount: 23 | annotations: {} 24 | name: 25 | 26 | podSecurityContext: {} 27 | 28 | securityContext: {} 29 | 30 | resources: {} 31 | # limits: 32 | # cpu: 100m 33 | # memory: 30Mi 34 | # requests: 35 | # cpu: 100m 36 | # memory: 20Mi 37 | 38 | 39 | podLabels: 40 | service: istio 41 | site: global 42 | -------------------------------------------------------------------------------- /config/crd/bases/networking.istio.io_envoyfilters.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.4.0 8 | creationTimestamp: null 9 | name: envoyfilters.networking.istio.io 10 | spec: 11 | group: networking.istio.io 12 | names: 13 | kind: EnvoyFilter 14 | listKind: EnvoyFilterList 15 | plural: envoyfilters 16 | singular: envoyfilter 17 | scope: Namespaced 18 | versions: 19 | - name: istio_v1alpha3 20 | schema: 21 | openAPIV3Schema: 22 | description: EnvoyFilter is the Schema for the EnvoyFilters API 23 | properties: 24 | apiVersion: 25 | description: 'APIVersion defines the versioned schema of this representation 26 | of an object. Servers should convert recognized schemas to the latest 27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 28 | type: string 29 | kind: 30 | description: 'Kind is a string value representing the REST resource this 31 | object represents. Servers may infer this from the endpoint the client 32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | description: EnvoyFilterSpec defines the desired state of EnvoyFilter 38 | properties: 39 | configPatches: 40 | items: 41 | properties: 42 | applyTo: 43 | type: string 44 | match: 45 | properties: 46 | cluster: 47 | properties: 48 | name: 49 | description: The exact name of the cluster to match. 50 | To match a specific cluster by name, such as the internally 51 | generated "Passthrough" cluster, leave all fields 52 | in clusterMatch empty, except the name. 53 | type: string 54 | port_number: 55 | description: The service port for which this cluster 56 | was generated. If omitted, applies to clusters for 57 | any port. 58 | format: int32 59 | type: integer 60 | service: 61 | description: The fully qualified service name for this 62 | cluster. If omitted, applies to clusters for any service. 63 | For services defined through service entries, the 64 | service name is same as the hosts defined in the service 65 | entry. 66 | type: string 67 | subset: 68 | description: The subset associated with the service. 69 | If omitted, applies to clusters for any subset of 70 | a service. 71 | type: string 72 | type: object 73 | context: 74 | type: string 75 | listener: 76 | description: "Types that are valid to be assigned to ObjectTypes: 77 | \t*EnvoyFilter_EnvoyConfigObjectMatch_Listener \t*EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration 78 | \t*EnvoyConfigObjectMatch_Cluster ObjectTypes EnvoyConfigObjectMatch_Listener 79 | `protobuf_oneof:\"object_types\"` ObjectTypes isEnvoyFilter_EnvoyConfigObjectMatch_ObjectTypes 80 | `protobuf_oneof:\"object_types\"`" 81 | properties: 82 | filterChain: 83 | description: Match a specific filter chain in a listener. 84 | If specified, the patch will be applied to the filter 85 | chain (and a specific filter if specified) and not 86 | to other filter chains in the listener. 87 | properties: 88 | application_protocols: 89 | description: "Applies only to sidecars. If non-empty, 90 | a comma separated set of application protocols 91 | to consider when determining a filter chain match. 92 | \ This value will be compared against the application 93 | protocols of a new connection, when it's detected 94 | by one of the listener filters such as the http_inspector. 95 | \n Accepted values include: h2,http/1.1,http/1.0" 96 | type: string 97 | filter: 98 | description: The name of a specific filter to apply 99 | the patch to. Set this to envoy.filters.network.http_connection_manager 100 | to add a filter or apply a patch to the HTTP connection 101 | manager. 102 | properties: 103 | name: 104 | description: The filter name to match on. For 105 | standard Envoy filters, canonical filter names 106 | should be used. Refer to https://www.envoyproxy.io/docs/envoy/latest/version_history/v1.14.0#deprecated 107 | for canonical names. 108 | type: string 109 | subFilter: 110 | description: The next level filter within this 111 | filter to match upon. Typically used for HTTP 112 | Connection Manager filters and Thrift filters. 113 | properties: 114 | name: 115 | description: The filter name to match on. 116 | type: string 117 | type: object 118 | type: object 119 | name: 120 | description: The name assigned to the filter chain. 121 | type: string 122 | sni: 123 | description: The SNI value used by a filter chain's 124 | match condition. This condition will evaluate 125 | to false if the filter chain has no sni match. 126 | type: string 127 | transport_protocol: 128 | description: "Applies only to SIDECAR_INBOUND context. 129 | If non-empty, a transport protocol to consider 130 | when determining a filter chain match. This value 131 | will be compared against the transport protocol 132 | of a new connection, when it's detected by the 133 | tls_inspector listener filter. \n Accepted values 134 | include: \n * `raw_buffer` - default, used when 135 | no transport protocol is detected. * `tls` - set 136 | when TLS protocol is detected by the TLS inspector." 137 | type: string 138 | type: object 139 | name: 140 | description: Match a specific listener by its name. 141 | The listeners generated by Pilot are typically named 142 | as IP:Port. 143 | type: string 144 | port_name: 145 | description: Instead of using specific port numbers, 146 | a set of ports matching a given service's port name 147 | can be selected. Matching is case insensitive. Not 148 | implemented. $hide_from_docs 149 | type: string 150 | port_number: 151 | description: The service port/gateway port to which 152 | traffic is being sent/received. If not specified, 153 | matches all listeners. Even though inbound listeners 154 | are generated for the instance/pod ports, only service 155 | ports should be used to match listeners. 156 | format: int32 157 | type: integer 158 | type: object 159 | proxy: 160 | properties: 161 | metadata: 162 | additionalProperties: 163 | type: string 164 | type: object 165 | proxyVersion: 166 | type: string 167 | type: object 168 | routeConfiguration: 169 | properties: 170 | gateway: 171 | description: The Istio gateway config's namespace/name 172 | for which this route configuration was generated. 173 | Applies only if the context is GATEWAY. Should be 174 | in the namespace/name format. Use this field in conjunction 175 | with the portNumber and portName to accurately select 176 | the Envoy route configuration for a specific HTTPS 177 | server within a gateway config object. 178 | type: string 179 | name: 180 | description: Route configuration name to match on. Can 181 | be used to match a specific route configuration by 182 | name, such as the internally generated "http_proxy" 183 | route configuration for all sidecars. 184 | type: string 185 | port_name: 186 | description: Applicable only for GATEWAY context. The 187 | gateway server port name for which this route configuration 188 | was generated. 189 | type: string 190 | port_number: 191 | description: The service port number or gateway server 192 | port number for which this route configuration was 193 | generated. If omitted, applies to route configurations 194 | for all ports. 195 | format: int32 196 | type: integer 197 | vhost: 198 | description: Match a specific virtual host in a route 199 | configuration and apply the patch to the virtual host. 200 | properties: 201 | name: 202 | description: The VirtualHosts objects generated 203 | by Istio are named as host:port, where the host 204 | typically corresponds to the VirtualService's 205 | host field or the hostname of a service in the 206 | registry. 207 | type: string 208 | route: 209 | description: Match a specific route within the virtual 210 | host. 211 | properties: 212 | action: 213 | description: Match a route with specific action 214 | type. 215 | type: string 216 | name: 217 | description: The Route objects generated by 218 | default are named as "default". Route objects 219 | generated using a virtual service will carry 220 | the name used in the virtual service's HTTP 221 | routes. 222 | type: string 223 | type: object 224 | type: object 225 | type: object 226 | type: object 227 | patch: 228 | properties: 229 | filterClass: 230 | format: int32 231 | type: integer 232 | operation: 233 | type: string 234 | value: 235 | type: Any 236 | type: object 237 | type: object 238 | type: array 239 | workloadSelector: 240 | properties: 241 | labels: 242 | additionalProperties: 243 | type: string 244 | description: One or more labels that indicate a specific set of 245 | pods/VMs on which the configuration should be applied. The scope 246 | of label search is restricted to the configuration namespace 247 | in which the the resource is present. 248 | type: object 249 | type: object 250 | type: object 251 | status: 252 | description: EnvoyFilterStatus defines the observed state of EnvoyFilter 253 | type: object 254 | type: object 255 | served: true 256 | storage: true 257 | status: 258 | acceptedNames: 259 | kind: "" 260 | plural: "" 261 | conditions: [] 262 | storedVersions: [] 263 | -------------------------------------------------------------------------------- /config/crd/bases/networking.istio.io_gateways.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.2 7 | creationTimestamp: null 8 | name: gateways.networking.istio.io 9 | spec: 10 | group: networking.istio.io 11 | names: 12 | kind: Gateway 13 | listKind: GatewayList 14 | plural: gateways 15 | singular: gateway 16 | scope: Namespaced 17 | versions: 18 | - name: istio_v1beta1 19 | schema: 20 | openAPIV3Schema: 21 | description: Gateway is the Schema for the Gateways API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation 25 | of an object. Servers should convert recognized schemas to the latest 26 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 27 | type: string 28 | kind: 29 | description: 'Kind is a string value representing the REST resource this 30 | object represents. Servers may infer this from the endpoint the client 31 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | properties: 37 | selector: 38 | additionalProperties: 39 | type: string 40 | type: object 41 | type: object 42 | status: 43 | description: GatewayStatus defines the observed state of Gateway 44 | type: object 45 | type: object 46 | served: true 47 | storage: true 48 | -------------------------------------------------------------------------------- /config/crd/bases/networking.istio.io_virtualservices.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.2 7 | creationTimestamp: null 8 | name: virtualservices.networking.istio.io 9 | spec: 10 | group: networking.istio.io 11 | names: 12 | kind: VirtualService 13 | listKind: VirtualServiceList 14 | plural: virtualservices 15 | singular: virtualservice 16 | scope: Namespaced 17 | versions: 18 | - name: istio_v1beta1 19 | schema: 20 | openAPIV3Schema: 21 | description: VirtualService is the Schema for the virtualservices API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation 25 | of an object. Servers should convert recognized schemas to the latest 26 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 27 | type: string 28 | kind: 29 | description: 'Kind is a string value representing the REST resource this 30 | object represents. Servers may infer this from the endpoint the client 31 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | description: VirtualServiceSpec defines the desired state of VirtualService 37 | properties: 38 | gateways: 39 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 40 | Important: Run "make" to regenerate code after modifying this file' 41 | items: 42 | type: string 43 | type: array 44 | hosts: 45 | items: 46 | type: string 47 | type: array 48 | http: 49 | items: 50 | properties: 51 | route: 52 | items: 53 | properties: 54 | destination: 55 | properties: 56 | host: 57 | type: string 58 | subset: 59 | type: string 60 | type: object 61 | type: object 62 | type: array 63 | type: object 64 | type: array 65 | type: object 66 | status: 67 | description: VirtualServiceStatus defines the observed state of VirtualService 68 | type: object 69 | type: object 70 | served: true 71 | storage: true 72 | -------------------------------------------------------------------------------- /config/crd/bases/networking.softonic.io_ratelimits.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.9.2 7 | creationTimestamp: null 8 | name: ratelimits.networking.softonic.io 9 | spec: 10 | group: networking.softonic.io 11 | names: 12 | kind: RateLimit 13 | listKind: RateLimitList 14 | plural: ratelimits 15 | singular: ratelimit 16 | scope: Namespaced 17 | versions: 18 | - name: v1alpha1 19 | schema: 20 | openAPIV3Schema: 21 | description: RateLimit is the Schema for the ratelimits API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation 25 | of an object. Servers should convert recognized schemas to the latest 26 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 27 | type: string 28 | kind: 29 | description: 'Kind is a string value representing the REST resource this 30 | object represents. Servers may infer this from the endpoint the client 31 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | properties: 37 | applytoroutes: 38 | items: 39 | type: string 40 | type: array 41 | destinationCluster: 42 | type: string 43 | rate: 44 | items: 45 | properties: 46 | dimensions: 47 | items: 48 | properties: 49 | header_value_match: 50 | properties: 51 | descriptor_value: 52 | type: string 53 | headers: 54 | items: 55 | properties: 56 | name: 57 | type: string 58 | prefix_match: 59 | type: string 60 | required: 61 | - name 62 | - prefix_match 63 | type: object 64 | type: array 65 | required: 66 | - descriptor_value 67 | - headers 68 | type: object 69 | request_header: 70 | properties: 71 | descriptor_key: 72 | type: string 73 | header_name: 74 | type: string 75 | value: 76 | type: string 77 | required: 78 | - descriptor_key 79 | - header_name 80 | type: object 81 | type: object 82 | type: array 83 | requestPerUnit: 84 | type: integer 85 | unit: 86 | type: string 87 | required: 88 | - dimensions 89 | - requestPerUnit 90 | - unit 91 | type: object 92 | type: array 93 | targetRef: 94 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 95 | Important: Run "make" to regenerate code after modifying this file' 96 | properties: 97 | apiVersion: 98 | description: API version of the referent. 99 | type: string 100 | fieldPath: 101 | description: 'If referring to a piece of an object instead of 102 | an entire object, this string should contain a valid JSON/Go 103 | field access statement, such as desiredState.manifest.containers[2]. 104 | For example, if the object reference is to a container within 105 | a pod, this would take on a value like: "spec.containers{name}" 106 | (where "name" refers to the name of the container that triggered 107 | the event) or if no container name is specified "spec.containers[2]" 108 | (container with index 2 in this pod). This syntax is chosen 109 | only to have some well-defined way of referencing a part of 110 | an object. TODO: this design is not final and this field is 111 | subject to change in the future.' 112 | type: string 113 | kind: 114 | description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 115 | type: string 116 | name: 117 | description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' 118 | type: string 119 | namespace: 120 | description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' 121 | type: string 122 | resourceVersion: 123 | description: 'Specific resourceVersion to which this reference 124 | is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' 125 | type: string 126 | uid: 127 | description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' 128 | type: string 129 | type: object 130 | x-kubernetes-map-type: atomic 131 | required: 132 | - rate 133 | - targetRef 134 | type: object 135 | status: 136 | description: RateLimitStatus defines the observed state of RateLimit 137 | type: object 138 | type: object 139 | served: true 140 | storage: true 141 | -------------------------------------------------------------------------------- /config/crd/bases/networking.softonic.io_virtualservices.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.5 8 | creationTimestamp: null 9 | name: virtualservices.networking.softonic.io 10 | spec: 11 | group: networking.softonic.io 12 | names: 13 | kind: VirtualService 14 | listKind: VirtualServiceList 15 | plural: virtualservices 16 | singular: virtualservice 17 | scope: Namespaced 18 | validation: 19 | openAPIV3Schema: 20 | description: VirtualService is the Schema for the virtualservices API 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | kind: 28 | description: 'Kind is a string value representing the REST resource this 29 | object represents. Servers may infer this from the endpoint the client 30 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 31 | type: string 32 | metadata: 33 | type: object 34 | spec: 35 | description: VirtualServiceSpec defines the desired state of VirtualService 36 | properties: 37 | hosts: 38 | items: 39 | type: string 40 | type: array 41 | type: object 42 | status: 43 | description: VirtualServiceStatus defines the observed state of VirtualService 44 | type: object 45 | type: object 46 | version: v1alpha1 47 | versions: 48 | - name: v1alpha1 49 | served: true 50 | storage: true 51 | status: 52 | acceptedNames: 53 | kind: "" 54 | plural: "" 55 | conditions: [] 56 | storedVersions: [] 57 | -------------------------------------------------------------------------------- /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/networking.softonic.io_ratelimits.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_ratelimits.yaml 12 | #- patches/webhook_in_virtualservices.yaml 13 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 14 | 15 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 16 | # patches here are for enabling the CA injection for each CRD 17 | #- patches/cainjection_in_ratelimits.yaml 18 | #- patches/cainjection_in_virtualservices.yaml 19 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 20 | 21 | # the following config is for teaching kustomize how to do kustomization for CRDs. 22 | configurations: 23 | - kustomizeconfig.yaml 24 | -------------------------------------------------------------------------------- /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_ratelimits.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 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: ratelimits.networking.softonic.io 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_ratelimits.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: ratelimits.networking.softonic.io 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: rate-limit-operator-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: rate-limit-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 34 | # crd/kustomization.yaml 35 | #- manager_webhook_patch.yaml 36 | 37 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 38 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 39 | # 'CERTMANAGER' needs to be enabled to use ca injection 40 | #- webhookcainjection_patch.yaml 41 | 42 | # the following config is for teaching kustomize how to do var substitution 43 | vars: 44 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 45 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 46 | # objref: 47 | # kind: Certificate 48 | # group: cert-manager.io 49 | # version: v1alpha2 50 | # name: serving-cert # this name should match the one in certificate.yaml 51 | # fieldref: 52 | # fieldpath: metadata.namespace 53 | #- name: CERTIFICATE_NAME 54 | # objref: 55 | # kind: Certificate 56 | # group: cert-manager.io 57 | # version: v1alpha2 58 | # name: serving-cert # this name should match the one in certificate.yaml 59 | #- name: SERVICE_NAMESPACE # namespace of the service 60 | # objref: 61 | # kind: Service 62 | # version: v1 63 | # name: webhook-service 64 | # fieldref: 65 | # fieldpath: metadata.namespace 66 | #- name: SERVICE_NAME 67 | # objref: 68 | # kind: Service 69 | # version: v1 70 | # name: webhook-service 71 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, 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.5.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 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /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: 9443 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 $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: softonic/rate-limit-operator 8 | newTag: 0.0.0-dev 9 | -------------------------------------------------------------------------------- /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 | resources: 33 | limits: 34 | cpu: 100m 35 | memory: 30Mi 36 | requests: 37 | cpu: 100m 38 | memory: 20Mi 39 | terminationGracePeriodSeconds: 10 40 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /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 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 4 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | - auth_proxy_client_clusterrole.yaml 13 | -------------------------------------------------------------------------------- /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 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | -------------------------------------------------------------------------------- /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/ratelimit_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ratelimits. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ratelimit-editor-role 6 | rules: 7 | - apiGroups: 8 | - networking.softonic.io 9 | resources: 10 | - ratelimits 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - networking.softonic.io 21 | resources: 22 | - ratelimits/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/ratelimit_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ratelimits. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ratelimit-viewer-role 6 | rules: 7 | - apiGroups: 8 | - networking.softonic.io 9 | resources: 10 | - ratelimits 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - networking.softonic.io 17 | resources: 18 | - ratelimits/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: manager-role 7 | rules: 8 | - apiGroups: 9 | - '*' 10 | resources: 11 | - configmaps 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - '*' 22 | resources: 23 | - gateways 24 | verbs: 25 | - create 26 | - delete 27 | - get 28 | - list 29 | - patch 30 | - update 31 | - watch 32 | - apiGroups: 33 | - '*' 34 | resources: 35 | - services 36 | verbs: 37 | - get 38 | - list 39 | - watch 40 | - apiGroups: 41 | - '*' 42 | resources: 43 | - virtualservices 44 | verbs: 45 | - create 46 | - delete 47 | - get 48 | - list 49 | - patch 50 | - update 51 | - watch 52 | - apiGroups: 53 | - apps 54 | resources: 55 | - deployments 56 | verbs: 57 | - create 58 | - delete 59 | - get 60 | - list 61 | - patch 62 | - update 63 | - watch 64 | - apiGroups: 65 | - batch 66 | resources: 67 | - envoyfilters/status 68 | verbs: 69 | - get 70 | - apiGroups: 71 | - "" 72 | resources: 73 | - configmaps 74 | verbs: 75 | - create 76 | - delete 77 | - get 78 | - list 79 | - patch 80 | - update 81 | - watch 82 | - apiGroups: 83 | - networking.istio.io 84 | resources: 85 | - envoyfilters 86 | verbs: 87 | - create 88 | - delete 89 | - get 90 | - list 91 | - patch 92 | - update 93 | - watch 94 | - apiGroups: 95 | - networking.softonic.io 96 | resources: 97 | - ratelimits 98 | verbs: 99 | - create 100 | - delete 101 | - get 102 | - list 103 | - patch 104 | - update 105 | - watch 106 | - apiGroups: 107 | - networking.softonic.io 108 | resources: 109 | - ratelimits/status 110 | verbs: 111 | - get 112 | - patch 113 | - update 114 | -------------------------------------------------------------------------------- /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/networking_istio_v1beta1_virtualservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1beta1 2 | kind: VirtualService 3 | metadata: 4 | name: varnish-noodle-vs 5 | namespace: ratelimitoperatortest 6 | spec: 7 | gateways: 8 | - istio-system/istio-autogenerated-k8s-ingress 9 | hosts: 10 | - '*' 11 | http: 12 | - match: 13 | - uri: 14 | exact: /ads.txt 15 | route: 16 | - destination: 17 | host: noodle-varnish 18 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_ratelimit.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.softonic.io/v1alpha1 2 | kind: RateLimit 3 | metadata: 4 | finalizers: 5 | - ratelimit.networking.softonic.io 6 | name: test 7 | spec: 8 | rate: 9 | - dimensions: 10 | - request_header: 11 | descriptor_key: host 12 | header_name: :authority 13 | requestPerUnit: 1 14 | unit: second 15 | targetRef: 16 | apiVersion: networking.istio.io/v1beta1 17 | kind: VirtualService 18 | name: httpbin 19 | namespace: default 20 | applytoroutes: 21 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_ratelimit_3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.softonic.io/v1alpha1 2 | kind: RateLimit 3 | metadata: 4 | name: ratelimit-sample 5 | namespace: ratelimitoperatortest 6 | finalizers: 7 | - ratelimit.networking.softonic.io 8 | spec: 9 | targetRef: 10 | apiVersion: "networking.istio.io/v1alpha3" 11 | kind: VirtualService 12 | name: vs-test 13 | namespace: ratelimitoperatortest 14 | dimensions: 15 | - descriptors: 16 | - key: destination_cluster 17 | rate_limit: 18 | requests_per_unit: 50 19 | unit: second 20 | value: outbound|80||server.noodle-v1.svc.cluster.local 21 | key: remote_address_persecond 22 | actions: 23 | - request_headers: 24 | descriptor_key: remote_address_persecond 25 | header_name: x-custom-user-ip 26 | - destination_cluster: {} 27 | - descriptors: 28 | - key: destination_cluster 29 | rate_limit: 30 | requests_per_unit: 2000 31 | unit: minute 32 | value: outbound|80||server.noodle-v1.svc.cluster.local 33 | key: remote_address_perminute 34 | actions: 35 | - request_headers: 36 | descriptor_key: remote_address_perminute 37 | header_name: x-custom-user-ip 38 | - destination_cluster: {} 39 | workloadselector: 40 | app: istio-ingressgateway 41 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_ratelimit_4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.softonic.io/v1alpha1 2 | kind: RateLimit 3 | metadata: 4 | name: ratelimit-sample-2 5 | namespace: ratelimitoperatortest 6 | finalizers: 7 | - ratelimit.networking.softonic.io 8 | spec: 9 | targetRef: 10 | apiVersion: "networking.istio.io/v1alpha3" 11 | kind: VirtualService 12 | name: vs-test 13 | namespace: ratelimitoperatortest 14 | dimensions: 15 | - descriptors: 16 | - key: destination_cluster 17 | rate_limit: 18 | requests_per_unit: 50 19 | unit: second 20 | value: outbound|80||server.noodle-v1.svc.cluster.local 21 | key: remote_address_persecond 22 | actions: 23 | - request_headers: 24 | descriptor_key: remote_address_persecond 25 | header_name: x-custom-user-ip 26 | - destination_cluster: {} 27 | - descriptors: 28 | - key: destination_cluster 29 | rate_limit: 30 | requests_per_unit: 2000 31 | unit: minute 32 | value: outbound|80||server.noodle-v1.svc.cluster.local 33 | key: remote_address_perminute 34 | actions: 35 | - request_headers: 36 | descriptor_key: remote_address_perminute 37 | header_name: x-custom-user-ip 38 | - destination_cluster: {} 39 | workloadselector: 40 | app: istio-ingressgateway 41 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_ratelimit_auth_header.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.softonic.io/v1alpha1 2 | kind: RateLimit 3 | metadata: 4 | name: ratelimit-sample 5 | spec: 6 | targetRef: 7 | apiVersion: "networking.istio.io/v1alpha3" 8 | kind: VirtualService 9 | name: varnish-noodle-vs 10 | namespace: ratelimitoperatortest 11 | destinationCluster: "outbound|80||chicken-head-nginx.chicken-head.svc.cluster.local" 12 | unit: seconds 13 | requestPerUnit: 100 14 | dimensions: 15 | - request_headers: 16 | header_name: "Authorization" 17 | descriptor_key: "auth" 18 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_ratelimit_multidescriptor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.softonic.io/v1alpha1 2 | kind: RateLimit 3 | metadata: 4 | name: ratelimit-sample 5 | namespace: ratelimitoperatortest 6 | finalizers: 7 | - ratelimit.networking.softonic.io 8 | spec: 9 | targetRef: 10 | apiVersion: "networking.istio.io/v1alpha3" 11 | kind: VirtualService 12 | name: vs-test 13 | namespace: ratelimitoperatortest 14 | dimensions: 15 | - descriptors: 16 | - key: destination_cluster 17 | rate_limit: 18 | requests_per_unit: 50 19 | unit: second 20 | value: outbound|80||server.noodle-v1.svc.cluster.local 21 | key: remote_address_persecond 22 | actions: 23 | - actions: 24 | - request_headers: 25 | descriptor_key: remote_address_persecond 26 | header_name: x-custom-user-ip 27 | - destination_cluster: {} 28 | - descriptors: 29 | - key: destination_cluster 30 | rate_limit: 31 | requests_per_unit: 2000 32 | unit: minute 33 | value: outbound|80||server.noodle-v1.svc.cluster.local 34 | key: remote_address_perminute 35 | actions: 36 | - actions: 37 | - request_headers: 38 | descriptor_key: remote_address_persecond 39 | header_name: x-custom-user-ip 40 | - destination_cluster: {} 41 | workloadselector: 42 | app: istio-ingressgateway 43 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_ratelimit_newformat.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.softonic.io/v1alpha1 2 | kind: RateLimit 3 | metadata: 4 | name: ratelimit-sample-2 5 | namespace: ratelimitoperatortest 6 | finalizers: 7 | - ratelimit.networking.softonic.io 8 | spec: 9 | targetRef: 10 | apiVersion: "networking.istio.io/v1alpha3" 11 | kind: VirtualService 12 | name: vs-test 13 | namespace: ratelimitoperatortest 14 | destinationCluster: "outbound|80||chicken-head-nginx.chicken-head.svc.cluster.local" 15 | rate: 16 | - unit: seconds 17 | requestPerUnit: 100 18 | dimensions: 19 | - request_header: 20 | descriptor_key: remote_address 21 | header_name: x-custom-ip 22 | - unit: minute 23 | requestPerUnit: 2000 24 | dimensions: 25 | - request_header: 26 | descriptor_key: remote_address 27 | header_name: x-custom-ip 28 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_virtualservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.softonic.io/v1alpha1 2 | kind: VirtualService 3 | metadata: 4 | name: varnish-noodle-vs 5 | namespace: ratelimitoperatortest 6 | spec: 7 | Hosts: 8 | - '*:80' 9 | -------------------------------------------------------------------------------- /config/samples/virtualservice_test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1beta1 2 | kind: VirtualService 3 | metadata: 4 | name: vs-test 5 | spec: 6 | gateways: 7 | - istio-system/istio-autogenerated-k8s-ingress 8 | hosts: 9 | - 'chicken-head.sftapi.com' 10 | http: 11 | - match: 12 | - uri: 13 | exact: /ads.txt 14 | route: 15 | - destination: 16 | host: noodle-varnishdddd 17 | -------------------------------------------------------------------------------- /controllers/actions.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "reflect" 7 | "regexp" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/ghodss/yaml" 12 | networkingv1alpha1 "github.com/softonic/rate-limit-operator/api/v1alpha1" 13 | v1 "k8s.io/api/core/v1" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/apimachinery/pkg/types" 16 | "k8s.io/klog" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | 19 | clientIstio "istio.io/client-go/pkg/apis/networking/v1alpha3" 20 | ) 21 | 22 | func (r *RateLimitReconciler) applyEnvoyFilter(desired *clientIstio.EnvoyFilter, found *clientIstio.EnvoyFilter, nameEnvoyFilter string, controllerNamespace string) error { 23 | 24 | err := r.Get(context.TODO(), types.NamespacedName{ 25 | Namespace: controllerNamespace, 26 | Name: nameEnvoyFilter, 27 | }, found) 28 | if err != nil { 29 | klog.Infof("Cannot Found EnvoyFilter %s before creating. %v", found.Name, err) 30 | err = r.Create(context.TODO(), desired) 31 | if err != nil { 32 | klog.Infof("Cannot Create EnvoyFilter %s. Error %v", desired.Name, err) 33 | return err 34 | } 35 | klog.Infof("Creating %s...", desired.Name) 36 | } else { 37 | 38 | applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("rate-limit-controller")} 39 | 40 | err = r.Patch(context.TODO(), desired, client.Apply, applyOpts...) 41 | if err != nil { 42 | klog.Infof("Cannot Patch EnvoyFilter %s. Error %v", desired.Name, err) 43 | return err 44 | } 45 | return nil 46 | } 47 | 48 | return nil 49 | 50 | } 51 | 52 | func (r *RateLimitReconciler) CreateOrUpdateConfigMap(rateLimitInstance *networkingv1alpha1.RateLimit, controllerNamespace string, baseName string, deploymentName string) error { 53 | 54 | var err error 55 | 56 | cm, err := r.generateConfigMap(rateLimitInstance, controllerNamespace, baseName) 57 | if err != nil { 58 | klog.Infof("Cannot generate %v, Error: %v", cm, err) 59 | return err 60 | } 61 | 62 | found, err := r.getConfigMap(baseName, controllerNamespace) 63 | if err != nil { 64 | err = r.Create(context.TODO(), &cm) 65 | if err != nil { 66 | //return ctrl.Result{}, client.IgnoreNotFound(err) 67 | klog.Infof("Cannot create %v, Error: %v", cm, err) 68 | } 69 | } else if !reflect.DeepEqual(cm, found) { 70 | 71 | applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("rate-limit-controller")} 72 | 73 | klog.Infof("the 2 resources are not the same, we should patch the deployment") 74 | 75 | err = r.Patch(context.TODO(), &cm, client.Apply, applyOpts...) 76 | if err != nil { 77 | klog.Infof("Cannot patch cm. Error: %v", err) 78 | return err 79 | } 80 | 81 | r.mutex.Lock() 82 | defer r.mutex.Unlock() 83 | 84 | r.DeploymentRL, err = r.getDeployment(controllerNamespace, deploymentName) 85 | if err != nil { 86 | klog.Infof("Cannot Found Deployment %s. Error %v", deploymentName, err) 87 | return err 88 | } else { 89 | klog.Infof("This is the Deployment %s found in the patch operation. Annotations: %v", deploymentName, r.DeploymentRL.Spec.Template.Annotations) 90 | } 91 | 92 | epoch := strconv.FormatInt(time.Now().Unix(), 10) 93 | 94 | r.DeploymentRL.Spec.Template.Annotations["date"] = epoch 95 | 96 | err = r.Update(context.TODO(), &r.DeploymentRL) 97 | if err != nil { 98 | klog.Infof("Cannot Update Deployment %s in patch operation. Error %v", r.DeploymentRL.Name, err) 99 | return err 100 | } else { 101 | klog.Infof("Deployment updated inside patch loop.") 102 | } 103 | 104 | } 105 | 106 | return nil 107 | 108 | } 109 | 110 | func (r *RateLimitReconciler) generateConfigMap(rateLimitInstance *networkingv1alpha1.RateLimit, controllerNamespace string, name string) (v1.ConfigMap, error) { 111 | 112 | configMapData := make(map[string]string) 113 | 114 | var err error 115 | 116 | var output []byte 117 | 118 | descriptorOutput := networkingv1alpha1.OutputConfig{} 119 | 120 | descriptorOutput.DescriptorsParent = make([]networkingv1alpha1.DescriptorsParent, len(rateLimitInstance.Spec.Rate)) 121 | 122 | descriptorOutput.Domain = name 123 | 124 | // get Destination Cluster 125 | 126 | nameVirtualService := rateLimitInstance.Spec.TargetRef.Name 127 | namespace := rateLimitInstance.Spec.TargetRef.Namespace 128 | 129 | var value string 130 | 131 | if rateLimitInstance.Spec.DestinationCluster != "" { 132 | value = rateLimitInstance.Spec.DestinationCluster 133 | } else { 134 | value, err = r.getDestinationClusterFromVirtualService(namespace, nameVirtualService) 135 | if err != nil { 136 | klog.Infof("Cannot generate configmap as we cannot find a host destination cluster") 137 | return v1.ConfigMap{}, err 138 | } 139 | } 140 | 141 | for k, dimension := range rateLimitInstance.Spec.Rate { 142 | if dimension.Dimensions[0].RequestHeader.DescriptorKey == "" { 143 | descriptorOutput.DescriptorsParent[k].Key = "header_match" 144 | descriptorOutput.DescriptorsParent[k].Value = dimension.Dimensions[0].HeaderValueMatch.DescriptorValue 145 | } else { 146 | descriptorOutput.DescriptorsParent[k].Key = dimension.Dimensions[0].RequestHeader.DescriptorKey + "_" + dimension.Unit 147 | } 148 | 149 | descriptor := networkingv1alpha1.Descriptors{ 150 | Key: "destination_cluster", 151 | RateLimit: networkingv1alpha1.RateLimitPerDescriptor{ 152 | RequestsPerUnit: dimension.RequestPerUnit, 153 | Unit: dimension.Unit, 154 | }, 155 | Value: value, 156 | } 157 | descriptorOutput.DescriptorsParent[k].Descriptors = append(descriptorOutput.DescriptorsParent[k].Descriptors, descriptor) 158 | } 159 | 160 | output, _ = json.Marshal(descriptorOutput) 161 | 162 | y, _ := yaml.JSONToYAML(output) 163 | 164 | fileName := name + ".yaml" 165 | 166 | configMapData[fileName] = string(y) 167 | 168 | configMap := v1.ConfigMap{ 169 | TypeMeta: metav1.TypeMeta{ 170 | Kind: "ConfigMap", 171 | APIVersion: "v1", 172 | }, 173 | ObjectMeta: metav1.ObjectMeta{ 174 | Name: name, 175 | Namespace: controllerNamespace, 176 | }, 177 | Data: configMapData, 178 | } 179 | 180 | return configMap, nil 181 | 182 | } 183 | 184 | func (r *RateLimitReconciler) getDestinationClusterFromVirtualService(namespace string, nameVirtualService string) (string, error) { 185 | 186 | virtualService, err := r.getVirtualService(namespace, nameVirtualService) 187 | if err != nil { 188 | klog.Infof("Virtualservice %s does not exists. Error: %s", nameVirtualService, err) 189 | return "", err 190 | } else { 191 | klog.Infof("found the %s", nameVirtualService) 192 | } 193 | 194 | subset := "" 195 | destination := "" 196 | 197 | for k, routes := range virtualService.Spec.Http { 198 | if routes.Route[k].Destination.Host != "" { 199 | destination = routes.Route[k].Destination.Host 200 | subset = routes.Route[k].Destination.Subset 201 | break 202 | } 203 | } 204 | 205 | // look for the port 206 | 207 | // get the name of the service from destination 208 | 209 | a := regexp.MustCompile(`\.`) 210 | serviceName := a.Split(destination, -1)[0] 211 | 212 | service := &v1.Service{} 213 | err = r.Get(context.TODO(), client.ObjectKey{ 214 | Namespace: namespace, 215 | Name: serviceName, 216 | }, service) 217 | if err != nil { 218 | klog.Infof("Cannot Get Service %s. Error %v", serviceName, err) 219 | return "not found", err 220 | } 221 | 222 | var port int32 223 | 224 | for _, p := range service.Spec.Ports { 225 | 226 | regex := regexp.MustCompile(`http`) 227 | 228 | if res := regex.MatchString(p.Name); res { 229 | port = p.Port 230 | } else { 231 | port = 80 232 | } 233 | } 234 | 235 | if destination == "" { 236 | destinationCluster := "outbound|80||" + serviceName + "." + namespace + ".svc.cluster.local" 237 | klog.Infof("Desstination could not be resolved") 238 | return destinationCluster, nil 239 | 240 | } 241 | 242 | //outbound|80|prod|server.digitaltrends-v1.svc.cluster.local 243 | 244 | destinationCluster := "outbound|" + strconv.FormatInt(int64(port), 10) + "|" + subset + "|" + destination 245 | 246 | return destinationCluster, nil 247 | 248 | } 249 | 250 | func (r *RateLimitReconciler) UpdateDeployment(volumeProjectedSources []v1.VolumeProjection, volumes []v1.Volume, controllerNamespace string, deploymentName string) error { 251 | 252 | var err error 253 | 254 | r.mutex.Lock() 255 | defer r.mutex.Unlock() 256 | 257 | time.Sleep(4 * time.Second) 258 | 259 | r.DeploymentRL, err = r.getDeployment(controllerNamespace, deploymentName) 260 | if err != nil { 261 | klog.Infof("Cannot Found Deployment %s. Error %v", deploymentName, err) 262 | return err 263 | } else { 264 | klog.Infof("This is the Deployment %s found later on last function. Annotations: %v", deploymentName, r.DeploymentRL.Spec.Template.Annotations) 265 | } 266 | 267 | err = r.addVolumeFromDeployment(volumeProjectedSources, volumes) 268 | if err != nil { 269 | klog.Infof("Cannot add VolumeSource from deploy %v. Error %v", r.DeploymentRL, err) 270 | return err 271 | } 272 | 273 | err = r.Update(context.TODO(), &r.DeploymentRL) 274 | if err != nil { 275 | err = r.Update(context.TODO(), &r.DeploymentRL) 276 | if err != nil { 277 | klog.Infof("Cannot Update Deployment %s. Error %v", r.DeploymentRL.Name, err) 278 | return err 279 | } 280 | } 281 | 282 | return nil 283 | 284 | } 285 | 286 | func (r *RateLimitReconciler) addVolumeFromDeployment(volumeProjectedSources []v1.VolumeProjection, volumes []v1.Volume) error { 287 | 288 | var volumeProjectedSourcesToApply []v1.VolumeProjection 289 | 290 | defaultVolumeMount := []v1.VolumeMount{ 291 | { 292 | Name: "commonconfig-volume", 293 | MountPath: "/data/ratelimit/config", 294 | }, 295 | } 296 | 297 | //if len(r.DeploymentRL.Spec.Template.Spec.Volumes) == 0 { 298 | // r.DeploymentRL.Spec.Template.Spec.Volumes = append(r.DeploymentRL.Spec.Template.Spec.Volumes, volumes...) 299 | // r.DeploymentRL.Spec.Template.Spec.Containers[0].VolumeMounts = defaultVolumeMount 300 | // return nil 301 | //} 302 | 303 | count := 0 304 | exists := true 305 | for _, v := range r.DeploymentRL.Spec.Template.Spec.Volumes { 306 | if v.Name == "commonconfig-volume" { 307 | for _, sourceToApply := range volumeProjectedSources { 308 | for _, sourceAlreadyExists := range v.VolumeSource.Projected.Sources { 309 | if sourceToApply.ConfigMap.Name == sourceAlreadyExists.ConfigMap.Name { 310 | // this configmap is already in the volume projected sources, not need to include 311 | exists = true 312 | break 313 | } else { 314 | // there is no coincidence 315 | exists = false 316 | } 317 | } 318 | // If the sourcetoApply does not exists in the already mounted sources, append to the slice volumeProjectedSourcesToApply 319 | if !exists { 320 | volumeProjectedSourcesToApply = append(volumeProjectedSourcesToApply, sourceToApply) 321 | } 322 | } 323 | // append to the projected sources slice that will be update in the deployment 324 | v.VolumeSource.Projected.Sources = append(v.VolumeSource.Projected.Sources, volumeProjectedSourcesToApply...) 325 | } else { 326 | count++ 327 | //deploy.Spec.Template.Spec.Volumes = append(deploy.Spec.Template.Spec.Volumes, volumes...) 328 | } 329 | } 330 | 331 | if count > 0 { 332 | r.DeploymentRL.Spec.Template.Spec.Volumes = append(r.DeploymentRL.Spec.Template.Spec.Volumes, volumes...) 333 | r.DeploymentRL.Spec.Template.Spec.Containers[0].VolumeMounts = append(r.DeploymentRL.Spec.Template.Spec.Containers[0].VolumeMounts, defaultVolumeMount...) 334 | } 335 | 336 | return nil 337 | 338 | } 339 | -------------------------------------------------------------------------------- /controllers/decommission.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | v1 "k8s.io/api/core/v1" 7 | "k8s.io/klog" 8 | 9 | clientIstio "istio.io/client-go/pkg/apis/networking/v1alpha3" 10 | ) 11 | 12 | func (r *RateLimitReconciler) decomissionk8sObjectResources(baseName string, controllerNamespace string, istioNamespace string) error { 13 | 14 | envoyFilterCluster := r.getEnvoyFilter(baseName+"-cluster", istioNamespace) 15 | 16 | err := r.deleteEnvoyFilter(envoyFilterCluster) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | envoyFilterHTTPFilter := r.getEnvoyFilter(baseName+"-envoy-filter", istioNamespace) 22 | 23 | err = r.deleteEnvoyFilter(envoyFilterHTTPFilter) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | envoyFilterHTTPRoute := r.getEnvoyFilter(baseName+"-route", istioNamespace) 29 | 30 | err = r.deleteEnvoyFilter(envoyFilterHTTPRoute) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | err = r.decomissionConfigMapRatelimit(r.configMapRateLimit) 36 | if err != nil { 37 | klog.Infof("Cannot remove EFs %v. Error %v", r.configMapRateLimit, err) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func (r *RateLimitReconciler) deleteEnvoyFilter(envoyFilter *clientIstio.EnvoyFilter) error { 44 | 45 | err := r.Delete(context.TODO(), envoyFilter) 46 | if err != nil { 47 | klog.Infof("Cannot delete EnvoyFilter %s. Error %v", envoyFilter.Name, err) 48 | return err 49 | } 50 | 51 | return nil 52 | 53 | } 54 | 55 | func (r *RateLimitReconciler) decomissionConfigMapRatelimit(configMapRateLimit v1.ConfigMap) error { 56 | 57 | err := r.deleteConfigMap(configMapRateLimit) 58 | if err != nil { 59 | klog.Infof("Cannot remove ConfigMap %v. Error %v", configMapRateLimit, err) 60 | return err 61 | } 62 | 63 | return nil 64 | 65 | } 66 | 67 | func (r *RateLimitReconciler) deleteConfigMap(configMapRateLimit v1.ConfigMap) error { 68 | 69 | err := r.Delete(context.TODO(), &configMapRateLimit) 70 | if err != nil { 71 | klog.Infof("Cannot delete ConfigMap %s. Error %v", configMapRateLimit.Name, err) 72 | return err 73 | } 74 | 75 | return nil 76 | 77 | } 78 | 79 | func (r *RateLimitReconciler) decomissionDeploymentVolumes(sources []v1.VolumeProjection, volumes []v1.Volume, controllerNamespace string, deploymentName string) error { 80 | 81 | var err error 82 | 83 | r.DeploymentRL, err = r.getDeployment(controllerNamespace, deploymentName) 84 | if err != nil { 85 | klog.Infof("Cannot Found Deployment %s. Error %v", deploymentName, err) 86 | return err 87 | } else { 88 | klog.Infof("This is the Deployment %s found in decomission. Annotations: %v", deploymentName, r.DeploymentRL.Spec.Template.Annotations) 89 | } 90 | 91 | err = r.removeVolumeFromDeployment(sources, volumes) 92 | if err != nil { 93 | klog.Infof("Cannot remove VolumeSource from deploy %v. Error %v", r.DeploymentRL, err) 94 | return err 95 | } 96 | 97 | err = r.Update(context.TODO(), &r.DeploymentRL) 98 | if err != nil { 99 | klog.Infof("Cannot Update Deployment %s. Error %v", "istio-system-ratelimit", err) 100 | return err 101 | } 102 | 103 | return nil 104 | 105 | } 106 | 107 | func (r *RateLimitReconciler) removeVolumeFromDeployment(sources []v1.VolumeProjection, volumes []v1.Volume) error { 108 | 109 | for _, v := range r.DeploymentRL.Spec.Template.Spec.Volumes { 110 | klog.Infof("Volumes already deployed in deployment are: %v", v) 111 | if v.Name == "commonconfig-volume" && len(v.VolumeSource.Projected.Sources) > 1 { 112 | i := 0 113 | klog.Info("Entering first for: there are more than 1 sources and the volume is the correct one") 114 | for _, n := range v.VolumeSource.Projected.Sources { 115 | klog.Infof("the source is: %v", n) 116 | for _, p := range sources { 117 | if n.ConfigMap.Name == p.ConfigMap.Name { 118 | } else { 119 | v.VolumeSource.Projected.Sources[i] = n 120 | i++ 121 | } 122 | } 123 | } 124 | v.VolumeSource.Projected.Sources = v.VolumeSource.Projected.Sources[:i] 125 | } 126 | //else if v.Name == "commonconfig-volume" && len(v.VolumeSource.Projected.Sources) == 1 { 127 | // r.DeploymentRL.Spec.Template.Spec.Volumes = nil 128 | // r.DeploymentRL.Spec.Template.Spec.Containers[0].VolumeMounts = nil 129 | 130 | //} 131 | } 132 | 133 | return nil 134 | 135 | } 136 | -------------------------------------------------------------------------------- /controllers/envoyfilterobject.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | 7 | networkingv1alpha1 "github.com/softonic/rate-limit-operator/api/v1alpha1" 8 | //yaml "gopkg.in/yaml.v1" 9 | "gopkg.in/yaml.v2" 10 | "k8s.io/klog" 11 | 12 | ratelimitTypes "github.com/softonic/rate-limit-operator/pkg/ratelimit/types" 13 | networking "istio.io/api/networking/v1alpha3" 14 | clientIstio "istio.io/client-go/pkg/apis/networking/v1alpha3" 15 | 16 | "strings" 17 | ) 18 | 19 | type EnvoyFilterObject struct { 20 | ApplyTo networking.EnvoyFilter_ApplyTo 21 | Operation networking.EnvoyFilter_Patch_Operation 22 | RawConfig string 23 | //RawConfig json.RawMessage 24 | TypeConfigObjectMatch string 25 | ClusterEndpoint string 26 | Context string 27 | Labels map[string]string 28 | NameVhost string 29 | Routes []string 30 | } 31 | 32 | func (r *RateLimitReconciler) prepareUpdateEnvoyFilterObjects(rateLimitInstance networkingv1alpha1.RateLimit, baseName string, controllerNamespace string) error { 33 | 34 | istioNamespace := os.Getenv("ISTIO_NAMESPACE") 35 | 36 | jsonActions := retrieveJsonActions(rateLimitInstance, baseName) 37 | 38 | namespace := rateLimitInstance.Spec.TargetRef.Namespace 39 | nameVirtualService := rateLimitInstance.Spec.TargetRef.Name 40 | 41 | virtualService, err := r.getVirtualService(namespace, nameVirtualService) 42 | if err != nil { 43 | klog.Infof("Virtualservice does not exists. Error: %v", err) 44 | } 45 | 46 | gatewayIngress := strings.Split(virtualService.Spec.Gateways[0], "/") 47 | 48 | namespaceVirtualService := gatewayIngress[0] 49 | nameGatewayVirtualService := gatewayIngress[1] 50 | 51 | Gateway, err := r.getGateway(namespaceVirtualService, nameGatewayVirtualService) 52 | if err != nil { 53 | klog.Infof("Gateway does not exists") 54 | } 55 | 56 | gatewaySelector := Gateway.Spec.Selector 57 | 58 | firstElementHosts := strings.Join(virtualService.Spec.Hosts, "") 59 | 60 | nameVhost := firstElementHosts + ":80" 61 | 62 | address := os.Getenv("ADDRESS_RATELIMIT_ENDPOINT") 63 | 64 | fqdn := address + "." + controllerNamespace + ".svc.cluster.local" 65 | 66 | nameCluster := "rate_limit_service_" + baseName 67 | 68 | value, err := createClusterPatchValue(fqdn, nameCluster) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | //payload := []byte(fmt.Sprintf(`{"connect_timeout":"1.25s","load_assignment":{"cluster_name":"%s","endpoints":[{"lb_endpoints":[{"endpoint":{"address":{"socket_address":{"address":"%s","port_value":8081}}}}]}]},"http2_protocol_options":{},"lb_policy":"ROUND_ROBIN","name":"%s","type":"STRICT_DNS"}`, fqdn, fqdn, nameCluster)) 74 | 75 | //rawConfigCluster := json.RawMessage(payload) 76 | 77 | //rawConfigCluster := string(payload) 78 | 79 | labels := gatewaySelector 80 | 81 | envoyFilterObjectCluster := EnvoyFilterObject{ 82 | Operation: networking.EnvoyFilter_Patch_ADD, 83 | ApplyTo: networking.EnvoyFilter_CLUSTER, 84 | RawConfig: value, 85 | TypeConfigObjectMatch: "Cluster", 86 | ClusterEndpoint: fqdn, 87 | Labels: labels, 88 | } 89 | 90 | envoyFilterClusterDesired := envoyFilterObjectCluster.composeEnvoyFilter(baseName+"-cluster", istioNamespace) 91 | 92 | envoyFilterCluster := &clientIstio.EnvoyFilter{} 93 | 94 | err = r.applyEnvoyFilter(envoyFilterClusterDesired, envoyFilterCluster, baseName+"-cluster", istioNamespace) 95 | if err != nil { 96 | klog.Infof("Cannot apply EF") 97 | return err 98 | } 99 | 100 | domain := baseName 101 | 102 | value, err = createHttpFilterPatchValue(domain, nameCluster) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | //payload = []byte(fmt.Sprintf(`{"typed_config":{"@type":"type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit","domain":"%s","rate_limit_service":{"transport_api_version": "V3","grpc_service":{"envoy_grpc":{"cluster_name":"%s"},"timeout":"1.25s"}}},"name":"envoy.filters.http.ratelimit"}`, domain, nameCluster)) 108 | 109 | //rawConfigHTTPFilter := string(payload) 110 | 111 | envoyFilterObjectListener := EnvoyFilterObject{ 112 | Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE, 113 | ApplyTo: networking.EnvoyFilter_HTTP_FILTER, 114 | RawConfig: value, 115 | TypeConfigObjectMatch: "Listener", 116 | Context: "GATEWAY", 117 | Labels: labels, 118 | } 119 | 120 | envoyFilterHTTPFilterDesired := envoyFilterObjectListener.composeEnvoyFilter(baseName+"-envoy-filter", istioNamespace) 121 | 122 | envoyFilterHTTPFilter := &clientIstio.EnvoyFilter{} 123 | 124 | err = r.applyEnvoyFilter(envoyFilterHTTPFilterDesired, envoyFilterHTTPFilter, baseName+"-envoy-filter", istioNamespace) 125 | if err != nil { 126 | klog.Infof("Cannot apply EF") 127 | return err 128 | } 129 | 130 | initJson := []byte(`{"route":`) 131 | finalJson := []byte(`}`) 132 | 133 | intermediateJson := append(initJson, jsonActions...) 134 | 135 | rawConfigHTTPRoute := string(append(intermediateJson, finalJson...)) 136 | //rawConfigHTTPRoute := json.RawMessage(append(intermediateJson, finalJson...)) 137 | 138 | //rawConfigHTTPRoute := json.RawMessage(`{"route":{"rate_limits":[{"actions":[{"request_headers":{"descriptor_key":"remote_address","header_name":"x-custom-user-ip"}},{"destination_cluster":{}}]}]}}`) 139 | 140 | var routesToApply []string 141 | 142 | if len(rateLimitInstance.Spec.ApplyToRoutes) > 0 { 143 | routesToApply = rateLimitInstance.Spec.ApplyToRoutes 144 | } 145 | 146 | envoyFilterObjectRouteConfiguration := EnvoyFilterObject{ 147 | Operation: networking.EnvoyFilter_Patch_MERGE, 148 | ApplyTo: networking.EnvoyFilter_HTTP_ROUTE, 149 | RawConfig: rawConfigHTTPRoute, 150 | TypeConfigObjectMatch: "RouteConfiguration", 151 | Context: "GATEWAY", 152 | Labels: labels, 153 | NameVhost: nameVhost, 154 | Routes: routesToApply, 155 | } 156 | 157 | envoyFilterHTTPRouteDesired := envoyFilterObjectRouteConfiguration.composeEnvoyFilter(baseName+"-route", istioNamespace) 158 | 159 | envoyFilterHTTPRoute := &clientIstio.EnvoyFilter{} 160 | 161 | err = r.applyEnvoyFilter(envoyFilterHTTPRouteDesired, envoyFilterHTTPRoute, baseName+"-route", istioNamespace) 162 | if err != nil { 163 | klog.Infof("Cannot apply EF") 164 | return err 165 | } 166 | 167 | return nil 168 | 169 | } 170 | 171 | func BytesToString(data []byte) string { 172 | return string(data[:]) 173 | } 174 | 175 | func retrieveJsonActions(rateLimitInstance networkingv1alpha1.RateLimit, baseName string) []byte { 176 | 177 | var output []byte 178 | 179 | actionsOutput := networkingv1alpha1.OutputRatelimitsEnvoyFilter{} 180 | 181 | //var Actions []networkingv1alpha1.Actions 182 | 183 | actionsOutput.RateLimits = make([]networkingv1alpha1.RateLimits, len(rateLimitInstance.Spec.Rate)) 184 | 185 | //actions := make([]networkingv1alpha1.Actions, len(rateLimitInstance.Spec.Rate)) 186 | 187 | //dimensionarray := make([]networkingv1alpha1.Dimensions, len(rateLimitInstance.Spec.Rate)) 188 | 189 | for k, dimension := range rateLimitInstance.Spec.Rate { 190 | 191 | keyName := dimension.Dimensions[0].RequestHeader.DescriptorKey + "_" + dimension.Unit 192 | 193 | if dimension.Dimensions[0].RequestHeader.DescriptorKey == "" { 194 | actions := []networkingv1alpha1.Actions{ 195 | { 196 | HeaderValueMatch: &networkingv1alpha1.HeaderValueMatch{ 197 | DescriptorValue: dimension.Dimensions[len(dimension.Dimensions)-1].HeaderValueMatch.DescriptorValue, 198 | Headers: []networkingv1alpha1.Headers{ 199 | { 200 | Name: dimension.Dimensions[len(dimension.Dimensions)-1].HeaderValueMatch.Headers[0].Name, 201 | PrefixMatch: dimension.Dimensions[len(dimension.Dimensions)-1].HeaderValueMatch.Headers[0].PrefixMatch, 202 | }, 203 | }, 204 | }, 205 | }, 206 | { 207 | DestinationCluster: &networkingv1alpha1.DestinationClusterHeader{}, 208 | }, 209 | } 210 | actionsOutput.RateLimits[k].Actions = actions 211 | 212 | } else { 213 | actions := []networkingv1alpha1.Actions{ 214 | 215 | { 216 | RequestHeaders: &networkingv1alpha1.RequestHeaders{ 217 | DescriptorKey: keyName, 218 | HeaderName: dimension.Dimensions[len(dimension.Dimensions)-1].RequestHeader.HeaderName, 219 | }, 220 | }, 221 | { 222 | DestinationCluster: &networkingv1alpha1.DestinationClusterHeader{}, 223 | }, 224 | } 225 | 226 | actionsOutput.RateLimits[k].Actions = actions 227 | } 228 | 229 | } 230 | 231 | /* for _, dimension := range actionsOutput.RateLimitsActions { 232 | Actions = append(Actions, dimension.Actions...) 233 | }*/ 234 | 235 | output, _ = json.Marshal(actionsOutput) 236 | 237 | return output 238 | 239 | } 240 | 241 | func createHttpFilterPatchValue(domain string, nameCluster string) (string, error) { 242 | //97: payload = []byte(fmt.Sprintf(`{"typed_config":{"@type":"type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit","domain":"%s","rate_limit_service":{"transport_api_version": "V3","grpc_service":{"envoy_grpc":{"cluster_name":"%s"},"timeout":"1.25s"}}},"name":"envoy.filters.http.ratelimit"}`, domain, nameCluster)) 243 | 244 | values := ratelimitTypes.HttpFilterPatchValues{ 245 | Name: "envoy.filters.http.ratelimit", 246 | TypedConfig: ratelimitTypes.TypedConfig{ 247 | Type: "type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit", 248 | Domain: domain, 249 | RateLimitService: ratelimitTypes.RateLimitService{ 250 | TransportAPIVersion: "V3", 251 | GRPCService: ratelimitTypes.GRPCService{ 252 | Timeout: "1.25s", 253 | EnvoyGRPC: ratelimitTypes.EnvoyGRPC{ 254 | ClusterName: nameCluster, 255 | }, 256 | }, 257 | }, 258 | }, 259 | } 260 | 261 | bytes, err := yaml.Marshal(&values) 262 | if err != nil { 263 | return "", err 264 | } 265 | 266 | return string(bytes), nil 267 | } 268 | 269 | func createClusterPatchValue(fqdn string, nameCluster string) (string, error) { 270 | // payload := []byte(fmt.Sprintf(`{"connect_timeout":"1.25s","load_assignment":{"cluster_name":"%s","endpoints":[{"lb_endpoints":[{"endpoint":{"address":{"socket_address":{"address":"%s","port_value":8081}}}}]}]},"http2_protocol_options":{},"lb_policy":"ROUND_ROBIN","name":"%s","type":"STRICT_DNS"}`, fqdn, fqdn, nameCluster)) 271 | 272 | values := ratelimitTypes.ClusterPatchValues{ 273 | Name: nameCluster, 274 | Type: "STRICT_DNS", 275 | ConnectTimeout: "1.25s", 276 | HTTP2ProtocolOptions: ratelimitTypes.HTTP2ProtocolOptions{}, 277 | LbPolicy: "ROUND_ROBIN", 278 | LoadAssignment: ratelimitTypes.LoadAssignment{ 279 | ClusterName: fqdn, 280 | Endpoints: []ratelimitTypes.Endpoints{ 281 | { 282 | LbEndpoints: []ratelimitTypes.LbEndpoints{ 283 | { 284 | Endpoint: ratelimitTypes.Endpoint{ 285 | Address: ratelimitTypes.Address{ 286 | SocketAddress: ratelimitTypes.SocketAddress{ 287 | Address: fqdn, 288 | PortValue: 8081, 289 | }, 290 | }, 291 | }, 292 | }, 293 | }, 294 | }, 295 | }, 296 | }, 297 | } 298 | 299 | bytes, err := yaml.Marshal(&values) 300 | if err != nil { 301 | return "", err 302 | } 303 | 304 | return string(bytes), nil 305 | } 306 | -------------------------------------------------------------------------------- /controllers/helpers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/softonic/rate-limit-operator/pkg/utils" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/types" 9 | 10 | "k8s.io/klog" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | appsv1 "k8s.io/api/apps/v1" 14 | v1 "k8s.io/api/core/v1" 15 | 16 | networking "istio.io/api/networking/v1alpha3" 17 | clientIstio "istio.io/client-go/pkg/apis/networking/v1alpha3" 18 | ) 19 | 20 | func (r *RateLimitReconciler) getK8sResources(baseName string, istioNamespace string, controllerNamespace string, deploymentName string) error { 21 | 22 | r.getEnvoyFilters(baseName, istioNamespace) 23 | 24 | var err error 25 | 26 | r.configMapRateLimit, err = r.getConfigMap(baseName, controllerNamespace) 27 | if err != nil { 28 | klog.Infof("Cannot Found ConfigMap in the getk8sresource func %s. Error %v", baseName, err) 29 | 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func getConfigObjectMatch(typeConfigObjectMatch string, operation networking.EnvoyFilter_Patch_Operation, clusterEndpoint string, context string, nameVhost string, route string) *networking.EnvoyFilter_EnvoyConfigObjectMatch { 36 | 37 | Match := networking.EnvoyFilter_EnvoyConfigObjectMatch{} 38 | 39 | vhost := networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{} 40 | 41 | if typeConfigObjectMatch == "Listener" { 42 | 43 | Match = networking.EnvoyFilter_EnvoyConfigObjectMatch{ 44 | Context: networking.EnvoyFilter_SIDECAR_INBOUND, 45 | ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 46 | Listener: &networking.EnvoyFilter_ListenerMatch{ 47 | FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ 48 | Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ 49 | Name: "envoy.filters.network.http_connection_manager", 50 | SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{ 51 | Name: "envoy.filters.http.router", 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | } 58 | } 59 | 60 | if typeConfigObjectMatch == "Cluster" { 61 | 62 | Match = networking.EnvoyFilter_EnvoyConfigObjectMatch{ 63 | Context: networking.EnvoyFilter_GATEWAY, 64 | ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ 65 | Cluster: &networking.EnvoyFilter_ClusterMatch{ 66 | Service: clusterEndpoint, 67 | }, 68 | }, 69 | } 70 | 71 | } 72 | 73 | if route != "" { 74 | vhost = networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ 75 | Route: &networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch{ 76 | Action: networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch_ANY, 77 | Name: route, 78 | }, 79 | } 80 | } else { 81 | vhost = networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ 82 | Name: nameVhost, 83 | Route: &networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch{ 84 | Action: networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch_ANY, 85 | }, 86 | } 87 | } 88 | 89 | if typeConfigObjectMatch == "RouteConfiguration" { 90 | 91 | Match = networking.EnvoyFilter_EnvoyConfigObjectMatch{ 92 | Context: networking.EnvoyFilter_GATEWAY, 93 | ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ 94 | RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ 95 | Vhost: &vhost, 96 | }, 97 | }, 98 | } 99 | 100 | } 101 | 102 | return &Match 103 | 104 | } 105 | 106 | func getEnvoyFilterConfigPatches(applyTo networking.EnvoyFilter_ApplyTo, operation networking.EnvoyFilter_Patch_Operation, rawConfig string, typeConfigObjectMatch string, clusterEndpoint string, context string, nameVhost string, routes []string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch { 107 | 108 | ConfigPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{} 109 | element := networking.EnvoyFilter_EnvoyConfigObjectPatch{} 110 | 111 | // value, err := g.buildHttpFilterPatchValue() 112 | // if err != nil { 113 | // return nil, err 114 | // } 115 | 116 | // listener, err := g.buildHttpFilterListener() 117 | // if err != nil { 118 | // return nil, err 119 | // } 120 | 121 | if len(routes) > 0 { 122 | for _, route := range routes { 123 | element = networking.EnvoyFilter_EnvoyConfigObjectPatch{ 124 | ApplyTo: applyTo, 125 | Patch: &networking.EnvoyFilter_Patch{ 126 | Operation: operation, 127 | Value: utils.ConvertYaml2Struct(rawConfig), 128 | }, 129 | Match: getConfigObjectMatch(typeConfigObjectMatch, operation, clusterEndpoint, context, nameVhost, route), 130 | } 131 | ConfigPatches = append(ConfigPatches, &element) 132 | } 133 | } else { 134 | ConfigPatches = []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 135 | { 136 | ApplyTo: applyTo, 137 | Patch: &networking.EnvoyFilter_Patch{ 138 | Operation: operation, 139 | Value: utils.ConvertYaml2Struct(rawConfig), 140 | }, 141 | Match: getConfigObjectMatch(typeConfigObjectMatch, operation, clusterEndpoint, context, nameVhost, ""), 142 | }, 143 | } 144 | } 145 | 146 | return ConfigPatches 147 | 148 | } 149 | 150 | func (e EnvoyFilterObject) composeEnvoyFilter(name string, namespace string) *clientIstio.EnvoyFilter { 151 | 152 | envoyFilterBaseDesired := &clientIstio.EnvoyFilter{ 153 | TypeMeta: metav1.TypeMeta{ 154 | Kind: "EnvoyFilter", 155 | APIVersion: "networking.istio.io/v1alpha3", 156 | }, 157 | ObjectMeta: metav1.ObjectMeta{ 158 | Name: name, 159 | Namespace: namespace, 160 | }, 161 | Spec: networking.EnvoyFilter{ 162 | WorkloadSelector: &networking.WorkloadSelector{ 163 | Labels: e.Labels, 164 | }, 165 | ConfigPatches: getEnvoyFilterConfigPatches(e.ApplyTo, e.Operation, e.RawConfig, e.TypeConfigObjectMatch, e.ClusterEndpoint, e.Context, e.NameVhost, e.Routes), 166 | }, 167 | } 168 | 169 | return envoyFilterBaseDesired 170 | 171 | } 172 | 173 | func (r *RateLimitReconciler) getEnvoyFilters(baseName string, istioNamespace string) *[]*clientIstio.EnvoyFilter { 174 | 175 | // case switch with the type of the filter 176 | 177 | envoyFilterCluster := r.getEnvoyFilter(baseName+"-cluster", istioNamespace) 178 | 179 | envoyFilterHTTPFilter := r.getEnvoyFilter(baseName+"-envoy-filter", istioNamespace) 180 | 181 | envoyFilterHTTPRoute := r.getEnvoyFilter(baseName+"-route", istioNamespace) 182 | 183 | r.EnvoyFilters = append(r.EnvoyFilters, envoyFilterCluster, envoyFilterHTTPFilter, envoyFilterHTTPRoute) 184 | 185 | return &r.EnvoyFilters 186 | 187 | } 188 | 189 | func (r *RateLimitReconciler) getEnvoyFilter(name string, namespace string) *clientIstio.EnvoyFilter { 190 | 191 | envoyFilter := clientIstio.EnvoyFilter{} 192 | 193 | err := r.Get(context.TODO(), types.NamespacedName{ 194 | Namespace: namespace, 195 | Name: name, 196 | }, &envoyFilter) 197 | if err != nil { 198 | klog.Infof("Cannot Found EnvoyFilter %s. Error %v", name, err) 199 | return &envoyFilter 200 | } 201 | 202 | return &envoyFilter 203 | 204 | } 205 | 206 | func (r *RateLimitReconciler) getConfigMap(name string, namespace string) (v1.ConfigMap, error) { 207 | 208 | found := v1.ConfigMap{} 209 | 210 | err := r.Get(context.TODO(), types.NamespacedName{ 211 | Namespace: namespace, 212 | Name: name, 213 | }, &found) 214 | if err != nil { 215 | //klog.Infof("Cannot Found configMap %s. Error %v", found.Name, err) 216 | return found, err 217 | } 218 | 219 | return found, nil 220 | 221 | } 222 | 223 | func constructVolumeSources(name string) []v1.VolumeProjection { 224 | 225 | //sources := make([]v1.VolumeProjection, 0) 226 | 227 | sources := []v1.VolumeProjection{ 228 | { 229 | ConfigMap: &v1.ConfigMapProjection{ 230 | LocalObjectReference: v1.LocalObjectReference{ 231 | Name: name, 232 | }, 233 | }, 234 | }, 235 | } 236 | 237 | return sources 238 | } 239 | 240 | func constructVolumes(nameVolume string, nameVolumeSource string) []v1.Volume { 241 | 242 | var defaultMode int32 = 0420 243 | 244 | p := &defaultMode 245 | 246 | sources := constructVolumeSources(nameVolumeSource) 247 | 248 | // Volumes := make([]v1.Volume, 0) 249 | 250 | Volumes := []v1.Volume{ 251 | { 252 | Name: nameVolume, 253 | VolumeSource: v1.VolumeSource{ 254 | Projected: &v1.ProjectedVolumeSource{ 255 | DefaultMode: p, 256 | Sources: sources, 257 | }, 258 | }, 259 | }, 260 | } 261 | 262 | return Volumes 263 | } 264 | 265 | func (r *RateLimitReconciler) getDeployment(controllerNamespace string, name string) (appsv1.Deployment, error) { 266 | 267 | found := appsv1.Deployment{} 268 | 269 | //klog.Infof("Before getting this deployment") 270 | 271 | deploy := &appsv1.Deployment{} 272 | err := r.Get(context.TODO(), client.ObjectKey{ 273 | Namespace: controllerNamespace, 274 | Name: name, 275 | }, deploy) 276 | if err != nil { 277 | klog.Infof("Cannot Get Deployment %s. Error %v", "istio-system-ratelimit", err) 278 | return found, err 279 | } 280 | 281 | //klog.Infof("Getting this deployment %v", deploy) 282 | 283 | return *deploy, nil 284 | } 285 | 286 | func (r *RateLimitReconciler) getVirtualService(namespace string, name string) (*clientIstio.VirtualService, error) { 287 | 288 | virtualService := &clientIstio.VirtualService{} 289 | err := r.Get(context.TODO(), types.NamespacedName{ 290 | Namespace: namespace, 291 | Name: name, 292 | }, virtualService) 293 | if err != nil { 294 | return nil, err 295 | } 296 | 297 | return virtualService, nil 298 | 299 | } 300 | 301 | func (r *RateLimitReconciler) getGateway(namespace string, name string) (*clientIstio.Gateway, error) { 302 | 303 | Gateway := &clientIstio.Gateway{} 304 | err := r.Get(context.TODO(), types.NamespacedName{ 305 | Namespace: namespace, 306 | Name: name, 307 | }, Gateway) 308 | if err != nil { 309 | return nil, err 310 | } 311 | 312 | return Gateway, nil 313 | 314 | } 315 | -------------------------------------------------------------------------------- /controllers/ratelimit_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "os" 22 | "sync" 23 | 24 | "k8s.io/apimachinery/pkg/types" 25 | 26 | appsv1 "k8s.io/api/apps/v1" 27 | 28 | "github.com/go-logr/logr" 29 | 30 | _ "log" 31 | 32 | networkingv1alpha1 "github.com/softonic/rate-limit-operator/api/v1alpha1" 33 | v1 "k8s.io/api/core/v1" 34 | _ "k8s.io/apimachinery/pkg/api/errors" 35 | "k8s.io/apimachinery/pkg/runtime" 36 | ctrl "sigs.k8s.io/controller-runtime" 37 | 38 | "sigs.k8s.io/controller-runtime/pkg/client" 39 | 40 | clientIstio "istio.io/client-go/pkg/apis/networking/v1alpha3" 41 | 42 | "k8s.io/klog" 43 | ) 44 | 45 | // RateLimitReconciler reconciles a RateLimit object 46 | type RateLimitReconciler struct { 47 | client.Client 48 | Log logr.Logger 49 | Scheme *runtime.Scheme 50 | K8sObject 51 | mutex sync.RWMutex 52 | } 53 | 54 | type K8sObject struct { 55 | EnvoyFilters []*clientIstio.EnvoyFilter 56 | DeploymentRL appsv1.Deployment 57 | configMapRateLimit v1.ConfigMap 58 | } 59 | 60 | // +kubebuilder:rbac:groups=networking.softonic.io,resources=ratelimits,verbs=get;list;watch;create;update;patch;delete 61 | // +kubebuilder:rbac:groups=*,resources=virtualservices,verbs=get;list;watch;create;update;patch;delete 62 | // +kubebuilder:rbac:groups=*,resources=gateways,verbs=get;list;watch;create;update;patch;delete 63 | // +kubebuilder:rbac:groups=networking.softonic.io,resources=ratelimits/status,verbs=get;update;patch 64 | // +kubebuilder:rbac:groups=networking.istio.io,resources=envoyfilters,verbs=get;list;watch;create;update;patch;delete 65 | // +kubebuilder:rbac:groups=batch,resources=envoyfilters/status,verbs=get 66 | // +kubebuilder:rbac:groups=*,resources=configmaps,verbs=get;list;watch;create;update;patch;delete 67 | // +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete 68 | // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete 69 | // +kubebuilder:rbac:groups=*,resources=services,verbs=get;list;watch 70 | 71 | func (r *RateLimitReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 72 | _ = context.Background() 73 | _ = r.Log.WithValues("ratelimit", req.NamespacedName) 74 | 75 | rateLimitInstance := &networkingv1alpha1.RateLimit{} 76 | 77 | err := r.Get(context.TODO(), types.NamespacedName{ 78 | Namespace: req.Namespace, 79 | Name: req.Name, 80 | }, rateLimitInstance) 81 | if err != nil { 82 | klog.Infof("Cannot get Ratelimit CR %s. Error %v", rateLimitInstance.Name, err) 83 | return ctrl.Result{}, client.IgnoreNotFound(err) 84 | } 85 | 86 | // INIT VARIABLES 87 | 88 | baseName := req.Name 89 | 90 | controllerNamespace := os.Getenv("CONTROLLER_NAMESPACE") 91 | istioNamespace := os.Getenv("ISTIO_NAMESPACE") 92 | deploymentName := os.Getenv("DEPLOYMENT_NAME") 93 | 94 | nameVolume := "commonconfig-volume" 95 | 96 | finalizer := "ratelimit.networking.softonic.io" 97 | 98 | // INIT RESOURCES 99 | 100 | r.getK8sResources(baseName, istioNamespace, controllerNamespace, deploymentName) 101 | 102 | volumes := constructVolumes(nameVolume, baseName) 103 | 104 | volumeProjectedSources := constructVolumeSources(baseName) 105 | 106 | klog.Infof("The volumeProjectedSources is: %v", volumeProjectedSources) 107 | 108 | // DECOMMISSION 109 | 110 | beingDeleted := rateLimitInstance.GetDeletionTimestamp() != nil 111 | 112 | if beingDeleted { 113 | 114 | if containsString(rateLimitInstance.GetFinalizers(), finalizer) { 115 | 116 | _ = r.decomissionk8sObjectResources(baseName, controllerNamespace, istioNamespace) 117 | 118 | _ = r.decomissionDeploymentVolumes(volumeProjectedSources, volumes, controllerNamespace, deploymentName) 119 | 120 | rateLimitInstance.SetFinalizers(remove(rateLimitInstance.GetFinalizers(), finalizer)) 121 | err = r.Update(context.TODO(), rateLimitInstance) 122 | if err != nil { 123 | return ctrl.Result{}, err 124 | } 125 | } 126 | return ctrl.Result{}, nil 127 | } 128 | 129 | // prepare Envoy Filters and apply the needed changes 130 | _ = r.prepareUpdateEnvoyFilterObjects(*rateLimitInstance, baseName, controllerNamespace) 131 | 132 | // Create ConfigMap Ratelimit 133 | _ = r.CreateOrUpdateConfigMap(rateLimitInstance, controllerNamespace, baseName, deploymentName) 134 | 135 | // Update deployment with ConfigMap values 136 | 137 | err = r.UpdateDeployment(volumeProjectedSources, volumes, controllerNamespace, deploymentName) 138 | if err != nil { 139 | // try again 140 | 141 | return ctrl.Result{}, err 142 | 143 | } 144 | 145 | return ctrl.Result{}, nil 146 | 147 | } 148 | 149 | func (r *RateLimitReconciler) SetupWithManager(mgr ctrl.Manager) error { 150 | return ctrl.NewControllerManagedBy(mgr). 151 | For(&networkingv1alpha1.RateLimit{}). 152 | Complete(r) 153 | } 154 | 155 | func containsString(slice []string, s string) bool { 156 | for _, item := range slice { 157 | if item == s { 158 | return true 159 | } 160 | } 161 | return false 162 | } 163 | 164 | func remove(slice []string, s string) (result []string) { 165 | for _, item := range slice { 166 | if item == s { 167 | continue 168 | } 169 | result = append(result, item) 170 | } 171 | return 172 | } 173 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # rate-limit-operator 2 | Rate Limit operator for Envoy Proxy 3 | -------------------------------------------------------------------------------- /docs/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softonic/rate-limit-operator/608c61d4d1c7bd7b43cc7aa2d737e560b9ae0350/docs/diagram.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/softonic/rate-limit-operator 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/champly/lib4go v0.0.0-20220926082759-58c5e8c622e2 7 | github.com/ghodss/yaml v1.0.0 8 | github.com/go-logr/logr v1.2.3 9 | github.com/imdario/mergo v0.3.12 // indirect 10 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect 11 | golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect 12 | google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03 // indirect 13 | google.golang.org/protobuf v1.28.1 14 | gopkg.in/yaml.v2 v2.4.0 // indirect 15 | istio.io/api v0.0.0-20221013011440-bc935762d2b9 16 | istio.io/client-go v1.15.3 17 | k8s.io/api v0.25.0 18 | k8s.io/apimachinery v0.25.0 19 | k8s.io/client-go v0.25.0 20 | k8s.io/klog v1.0.0 21 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 22 | sigs.k8s.io/controller-runtime v0.13.0 23 | ) 24 | 25 | require sigs.k8s.io/yaml v1.3.0 // indirect 26 | 27 | require ( 28 | cloud.google.com/go v0.97.0 // indirect 29 | github.com/PuerkitoBio/purell v1.1.1 // indirect 30 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 31 | github.com/beorn7/perks v1.0.1 // indirect 32 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 33 | github.com/davecgh/go-spew v1.1.1 // indirect 34 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 35 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 36 | github.com/fsnotify/fsnotify v1.5.4 // indirect 37 | github.com/go-logr/zapr v1.2.3 // indirect 38 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 39 | github.com/go-openapi/jsonreference v0.19.5 // indirect 40 | github.com/go-openapi/swag v0.19.14 // indirect 41 | github.com/gogo/protobuf v1.3.2 // indirect 42 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 43 | github.com/golang/protobuf v1.5.2 // indirect 44 | github.com/google/gnostic v0.5.7-v3refs // indirect 45 | github.com/google/go-cmp v0.5.8 // indirect 46 | github.com/google/gofuzz v1.2.0 // indirect 47 | github.com/google/uuid v1.2.0 // indirect 48 | github.com/josharian/intern v1.0.0 // indirect 49 | github.com/json-iterator/go v1.1.12 // indirect 50 | github.com/mailru/easyjson v0.7.6 // indirect 51 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 53 | github.com/modern-go/reflect2 v1.0.2 // indirect 54 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 55 | github.com/pkg/errors v0.9.1 // indirect 56 | github.com/prometheus/client_golang v1.13.0 // indirect 57 | github.com/prometheus/client_model v0.2.0 // indirect 58 | github.com/prometheus/common v0.37.0 // indirect 59 | github.com/prometheus/procfs v0.8.0 // indirect 60 | github.com/spf13/pflag v1.0.5 // indirect 61 | go.uber.org/atomic v1.7.0 // indirect 62 | go.uber.org/multierr v1.6.0 // indirect 63 | go.uber.org/zap v1.21.0 // indirect 64 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 65 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 66 | golang.org/x/text v0.3.7 // indirect 67 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 68 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 69 | google.golang.org/appengine v1.6.7 // indirect 70 | gopkg.in/inf.v0 v0.9.1 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | k8s.io/apiextensions-apiserver v0.25.0 // indirect 73 | k8s.io/component-base v0.25.0 // indirect 74 | k8s.io/klog/v2 v2.80.0 // indirect 75 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 76 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 77 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 78 | ) 79 | 80 | replace ( 81 | k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.18.3 82 | k8s.io/code-generator => k8s.io/code-generator v0.18.3 83 | ) 84 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 25 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 28 | 29 | networkingv1alpha1 "github.com/softonic/rate-limit-operator/api/v1alpha1" 30 | clientIstio "istio.io/client-go/pkg/apis/networking/v1alpha3" 31 | "github.com/softonic/rate-limit-operator/controllers" 32 | // +kubebuilder:scaffold:imports 33 | ) 34 | 35 | var ( 36 | scheme = runtime.NewScheme() 37 | setupLog = ctrl.Log.WithName("setup") 38 | ) 39 | 40 | func init() { 41 | _ = clientgoscheme.AddToScheme(scheme) 42 | 43 | _ = networkingv1alpha1.AddToScheme(scheme) 44 | _ = clientIstio.AddToScheme(scheme) 45 | // +kubebuilder:scaffold:scheme 46 | 47 | } 48 | 49 | func main() { 50 | var metricsAddr string 51 | var enableLeaderElection bool 52 | 53 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 54 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 55 | "Enable leader election for controller manager. "+ 56 | "Enabling this will ensure there is only one active controller manager.") 57 | 58 | flag.Parse() 59 | 60 | ctrl.SetLogger(zap.New(zap.UseDevMode(true))) 61 | 62 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 63 | Scheme: scheme, 64 | MetricsBindAddress: metricsAddr, 65 | Port: 9443, 66 | LeaderElection: enableLeaderElection, 67 | LeaderElectionID: "ccfb7857.softonic.io", 68 | }) 69 | if err != nil { 70 | setupLog.Error(err, "unable to start manager") 71 | os.Exit(1) 72 | } 73 | 74 | if err = (&controllers.RateLimitReconciler{ 75 | Client: mgr.GetClient(), 76 | Log: ctrl.Log.WithName("controllers").WithName("RateLimit"), 77 | Scheme: mgr.GetScheme(), 78 | }).SetupWithManager(mgr); err != nil { 79 | setupLog.Error(err, "unable to create controller", "controller", "RateLimit") 80 | os.Exit(1) 81 | } 82 | // +kubebuilder:scaffold:builder 83 | 84 | setupLog.Info("starting manager") 85 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 86 | setupLog.Error(err, "problem running manager") 87 | os.Exit(1) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: 'Rate Limit Operator' 2 | site_description: 'Main documentation' 3 | edit_uri: 'blob/master' 4 | 5 | plugins: 6 | - techdocs-core 7 | 8 | nav: 9 | - Overview: 10 | - What is Rate Limit operator: 'README.md' 11 | -------------------------------------------------------------------------------- /pkg/log/level.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md 4 | 5 | const WARNING = 0 6 | const NOTICE = 1 7 | const INFO = 2 8 | const EXTENDED = 3 9 | const DEBUG = 4 10 | const TRACE = 5 11 | -------------------------------------------------------------------------------- /pkg/ratelimit/types/cluster_patch.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type ClusterPatchValues struct { 4 | Name string `yaml:"name,omitempty"` 5 | Type string `yaml:"type,omitempty"` 6 | ConnectTimeout string `yaml:"connect_timeout,omitempty"` 7 | LbPolicy string `yaml:"lb_policy,omitempty"` 8 | HTTP2ProtocolOptions HTTP2ProtocolOptions `yaml:"http2_protocol_options"` 9 | LoadAssignment LoadAssignment `yaml:"load_assignment,omitempty"` 10 | } 11 | 12 | type HTTP2ProtocolOptions struct { 13 | } 14 | 15 | type LoadAssignment struct { 16 | ClusterName string `yaml:"cluster_name,omitempty"` 17 | Endpoints []Endpoints `yaml:"endpoints,omitempty"` 18 | } 19 | 20 | type Endpoints struct { 21 | LbEndpoints []LbEndpoints `yaml:"lb_endpoints,omitempty"` 22 | } 23 | 24 | type LbEndpoints struct { 25 | Endpoint Endpoint `yaml:"endpoint,omitempty"` 26 | } 27 | 28 | type Endpoint struct { 29 | Address Address `yaml:"address,omitempty"` 30 | } 31 | 32 | type Address struct { 33 | SocketAddress SocketAddress `yaml:"socket_address,omitempty"` 34 | } 35 | 36 | type SocketAddress struct { 37 | Address string `yaml:"address,omitempty"` 38 | PortValue int `yaml:"port_value,omitempty"` 39 | } 40 | -------------------------------------------------------------------------------- /pkg/ratelimit/types/httpfilter_patch.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type HttpFilterPatchValues struct { 4 | Config Config `json:"config,omitempty" yaml:"config,omitempty"` 5 | TypedConfig TypedConfig `json:"typed_config,omitempty" yaml:"typed_config,omitempty"` 6 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 7 | } 8 | 9 | type Config struct { 10 | Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` 11 | FailureModeDeny *bool `json:"failure_mode_deny,omitempty" yaml:"failure_mode_deny,omitempty"` 12 | Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` 13 | RateLimitService RateLimitService `json:"rate_limit_service,omitempty" yaml:"rate_limit_service,omitempty"` 14 | } 15 | 16 | type TypedConfig struct { 17 | Type string `json:"@type,omitempty" yaml:"@type,omitempty"` 18 | Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` 19 | FailureModeDeny *bool `json:"failure_mode_deny,omitempty" yaml:"failure_mode_deny,omitempty"` 20 | Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"` 21 | RateLimitService RateLimitService `json:"rate_limit_service,omitempty" yaml:"rate_limit_service,omitempty"` 22 | } 23 | 24 | type RateLimitService struct { 25 | GRPCService GRPCService `json:"grpc_service,omitempty" yaml:"grpc_service,omitempty"` 26 | TransportAPIVersion string `json:"transport_api_version,omitempty" yaml:"transport_api_version,omitempty"` 27 | } 28 | 29 | type GRPCService struct { 30 | EnvoyGRPC EnvoyGRPC `json:"envoy_grpc,omitempty" yaml:"envoy_grpc,omitempty"` 31 | Timeout string `json:"timeout" yaml:"timeout,omitempty"` 32 | } 33 | 34 | type EnvoyGRPC struct { 35 | ClusterName string `json:"cluster_name,omitempty" yaml:"cluster_name,omitempty"` 36 | } 37 | -------------------------------------------------------------------------------- /pkg/utils/struct.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/champly/lib4go/encoding" 5 | "google.golang.org/protobuf/types/known/structpb" 6 | //"github.com/gogo/protobuf/types" 7 | ) 8 | 9 | // func ConvertJSON2Struct(str string) *structpb.Struct { 10 | // res, _ := encoding.JSON2Struct(str) 11 | // return res 12 | // } 13 | 14 | func ConvertYaml2Struct(str string) *structpb.Struct { 15 | res, _ := encoding.YAML2Struct(str) 16 | return res 17 | } 18 | --------------------------------------------------------------------------------