├── .gitignore ├── .luacheckrc ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── NEWS ├── README.md ├── kong-plugin-kubernetes-sidecar-injector-0.2.1-0.rockspec ├── kong └── plugins │ └── kubernetes-sidecar-injector │ ├── api.lua │ ├── config.lua │ ├── handler.lua │ ├── schema.lua │ └── typedefs.lua └── test └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | kong-build-tools 2 | kong-dist-kubernetes 3 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "ngx_lua" 2 | files["spec"] = { 3 | std = "+busted"; 4 | } 5 | globals = { 6 | "kong"; 7 | } 8 | max_line_length = false 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: false 3 | 4 | language: generic 5 | 6 | env: 7 | matrix: 8 | - K8S_VERSION=v1.15.0 9 | - K8S_VERSION=v1.14.3 10 | - K8S_VERSION=v1.13.7 11 | - K8S_VERSION=v1.12.9 12 | - K8S_VERSION=v1.11.10 13 | matrix: 14 | allow_failures: 15 | - env: K8S_VERSION=v1.12.9 16 | - env: K8S_VERSION=v1.11.10 17 | 18 | before_script: 19 | - make setup_tests 20 | 21 | script: 22 | - make test 23 | 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM kong 2 | 3 | COPY kong/plugins/kubernetes-sidecar-injector /usr/local/share/lua/5.1/kong/plugins/kubernetes-sidecar-injector 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018-2019 Kong Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export SHELL:=/bin/bash 2 | KONG_DIST_KUBERNETES_VERSION?=origin/master 3 | KONG_BUILD_TOOLS_VERSION?=76500d371afa4b4abb4cff5dc63ae1e2e6ff9e4a 4 | K8S_VERSION?=v1.15.0 5 | 6 | setup_tests: 7 | curl -fsSL https://raw.githubusercontent.com/Kong/kong-build-tools/${KONG_BUILD_TOOLS_VERSION}/.ci/setup_kind.sh | bash 8 | -rm -rf kong-dist-kubernetes 9 | git clone https://github.com/Kong/kong-dist-kubernetes.git 10 | cd kong-dist-kubernetes; \ 11 | git reset --hard "$(KONG_DIST_KUBERNETES_VERSION)" 12 | 13 | .PHONY: test 14 | test: 15 | docker build -t localhost:5000/kong-sidecar-injector . 16 | kind load docker-image localhost:5000/kong-sidecar-injector 17 | cd kong-dist-kubernetes; \ 18 | sed -i -e 's/image: kong/image: localhost:5000\/kong-sidecar-injector/g' kong-*-postgres.yaml 19 | ./test/test.sh 20 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 0.2.1 - 2019-06-25 2 | 3 | - Fix api to call process_auto_fields on input data before validating, 4 | that makes the validation more resilient to Kubernetes changes 5 | (see https://github.com/Kong/kubernetes-sidecar-injector/issues/7) 6 | - Fix api with nicer error reporting on object schema errors 7 | 8 | - Fix schema by adding `kind` and `apiVersion` to Pod schema 9 | (see https://github.com/Kong/kubernetes-sidecar-injector/issues/7) 10 | 11 | 0.2.0 - 2019-06-07 12 | 13 | - Remove `BasePlugin` dependency (not needed anymore) 14 | 15 | 16 | 0.1.1 - 2019-03-11 17 | 18 | - Add `name` to the plugin handler to make Kong logs more readable 19 | 20 | 21 | 0.1.0 - 2019-02-13 22 | 23 | - Initial release 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | This repository has been deprecated. 4 | Please use docs for 5 | [Kong for Kubernetes](https://docs.konghq.com/1.4.x/kong-for-kubernetes/) 6 | for installation and configuration of Kong on Kubernetes. 7 | 8 | # Howto 9 | 10 | ## Minikube 11 | 12 | Start minikube with mutation admission webhook support. 13 | 14 | ``` 15 | minikube start 16 | ``` 17 | 18 | Old minikube/kubernetes < 1.9 will need this --extra-config: 19 | 20 | ``` 21 | minikube start --extra-config=apiserver.admission-control="NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota" 22 | ``` 23 | 24 | For inspecting the running system inside of minikube with docker, run: 25 | 26 | ``` 27 | eval $(minikube docker-env) 28 | ``` 29 | 30 | ## Setup Kong 31 | 32 | ``` 33 | ### Run Kong on k8s 34 | git clone https://github.com/Kong/kong-dist-kubernetes.git 35 | cd kong-dist-kubernetes 36 | make run_ 37 | kubectl -n kong get all 38 | 39 | ### Turn on kong plugins 40 | 41 | kubectl port-forward -n kong svc/kong-control-plane 8001:8001 & 42 | curl localhost:8001/plugins -d name=kubernetes-sidecar-injector 43 | 44 | ### Turn on sidecar injection 45 | cat <= 5.1"; 17 | --"lua-cjson"; -- kong comes with openresty forked lua-cjson 18 | } 19 | 20 | build = { 21 | type = "builtin"; 22 | modules = { 23 | ["kong.plugins.kubernetes-sidecar-injector.api"] = "kong/plugins/kubernetes-sidecar-injector/api.lua"; 24 | ["kong.plugins.kubernetes-sidecar-injector.config"] = "kong/plugins/kubernetes-sidecar-injector/config.lua"; 25 | ["kong.plugins.kubernetes-sidecar-injector.handler"] = "kong/plugins/kubernetes-sidecar-injector/handler.lua"; 26 | ["kong.plugins.kubernetes-sidecar-injector.schema"] = "kong/plugins/kubernetes-sidecar-injector/schema.lua"; 27 | ["kong.plugins.kubernetes-sidecar-injector.typedefs"] = "kong/plugins/kubernetes-sidecar-injector/typedefs.lua"; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /kong/plugins/kubernetes-sidecar-injector/api.lua: -------------------------------------------------------------------------------- 1 | local cjson = require "cjson" 2 | local Schema = require "kong.db.schema" 3 | local kong_pdk = require "kong.pdk".new({}, 1) 4 | local k8s_typedefs = require "kong.plugins.kubernetes-sidecar-injector.typedefs" 5 | local load_configuration = require "kong.plugins.kubernetes-sidecar-injector.config" 6 | 7 | 8 | local kong = kong 9 | local fmt = string.format 10 | local type = type 11 | local pairs = pairs 12 | local tconcat = table.concat 13 | local tinsert = table.insert 14 | local tostring = tostring 15 | local log_info = kong_pdk.log.info 16 | local encode_base64 = ngx.encode_base64 17 | 18 | 19 | local function skip_injection(plugin_config, review_request) -- luacheck: ignore 212 20 | if type(review_request) == "table" and 21 | type(review_request.object) == "table" and 22 | type(review_request.object.metadata) == "table" then 23 | 24 | local annotations = review_request.object.metadata.annotations 25 | 26 | if type(annotations) == "table" then 27 | -- Behave similar to sidecar.istio.io/inject annotation 28 | if annotations["k8s.konghq.com/sidecar-inject"] == "false" then 29 | return true 30 | end 31 | end 32 | end 33 | 34 | -- TODO: allow injection to be more configurable 35 | 36 | return false 37 | end 38 | 39 | 40 | -- Removes unknown fields from the request (i.e. from more recent Kubernetes versions) 41 | -- NOTE: this is implemented in Kong 1.3.0 with `schema:process_auto_fields`, so this is here for prior releases 42 | local function remove_unknown_keys(schema, input) 43 | if type(input) ~= "table" then 44 | return input 45 | end 46 | 47 | local result = {} 48 | for k, field in schema:each_field() do 49 | local val = input[k] 50 | if val ~= nil then 51 | if field.type == "record" then 52 | result[k] = remove_unknown_keys(Schema.new(field), val) 53 | else 54 | result[k] = val 55 | end 56 | end 57 | end 58 | 59 | return result 60 | end 61 | 62 | -- Refine schema to what we actually want to accept 63 | local podschema = Schema.new(k8s_typedefs.Pod) 64 | 65 | local admissionreviewschema = Schema.new { 66 | name = "admissionregistration.k8s.io/v1beta1 AdmissionReview", 67 | fields = { 68 | { kind = { type = "string", eq = "AdmissionReview", required = true } }, 69 | { apiVersion = { type = "string", eq = "admission.k8s.io/v1beta1", required = true } }, 70 | { request = k8s_typedefs.AdmissionRequest { required = true, custom_validator = function(review_request) 71 | if review_request.operation ~= "CREATE" then 72 | return nil, "unsupported operation (this controller only understands the CREATE operation)" 73 | end 74 | 75 | local group_kind = review_request.kind 76 | if group_kind.group ~= "" or group_kind.version ~= "v1" or group_kind.kind ~= "Pod" then 77 | return nil, "unknown resource type (this controller only accepts v1 Pods)" 78 | end 79 | 80 | local object = podschema:process_auto_fields(review_request.object, "select", false) 81 | local ok, err = podschema:validate(object) 82 | if not ok then 83 | local err_t = kong.db.errors:schema_violation({ object = err }) 84 | return nil, tostring(err_t) 85 | end 86 | 87 | if type(object.spec) ~= "table" then 88 | -- workaround for incomplete podspec definition 89 | return nil, "invalid object.spec field" 90 | end 91 | 92 | return true 93 | end } }, 94 | } 95 | } 96 | 97 | return { 98 | ["/kubernetes-sidecar-injector"] = { 99 | schema = admissionreviewschema, 100 | methods = { 101 | POST = function(self) 102 | local plugin_config = load_configuration("kubernetes-sidecar-injector") 103 | -- 404 if plugin not found/enabled 104 | if not plugin_config then 105 | return kong.response.exit(404, { message = "Not found" }) 106 | end 107 | 108 | local clean_args = remove_unknown_keys(admissionreviewschema, 109 | self.args.post) 110 | -- TODO: only accept JSON? 111 | local args = admissionreviewschema:process_auto_fields(clean_args, "select", false) 112 | local ok, err = admissionreviewschema:validate(args) 113 | if not ok then 114 | return kong.response.exit(422, { message = err }) 115 | end 116 | 117 | local review_request = args.request 118 | local object = review_request.object 119 | 120 | -- Same log format as istio 121 | log_info("AdmissionReview for", 122 | " Kind=", review_request.kind.kind, "/", review_request.kind.version, 123 | " Namespace=", review_request.namespace, 124 | " Name=", review_request.name or "", " (", object.metadata.name or "", ")", 125 | " UID=", review_request.uid, 126 | " Rfc6902PatchOperation=", review_request.operation, 127 | -- XXX: even though required=true is set on the userInfo field, it can still be nil 128 | " UserInfo=", (review_request.userInfo or {}).username or "" 129 | ) 130 | 131 | local reply = { 132 | kind = "AdmissionReview", 133 | apiVersion = "admission.k8s.io/v1beta1", 134 | response = { -- https://github.com/kubernetes/kubernetes/blob/v1.11.0/staging/src/k8s.io/api/admission/v1beta1/types.go#L77 135 | uid = review_request.uid, 136 | allowed = true, 137 | status = nil, -- only required if 'allowed' is false 138 | patch = nil, 139 | patchType = nil, 140 | } 141 | } 142 | 143 | if skip_injection(plugin_config, review_request) then 144 | log_info("Skipping ", review_request.namespace, "/", object.metadata.name, " due to policy check") 145 | return { json = reply } 146 | end 147 | 148 | -- Patches in RFC 6902 format 149 | local patches = { nil, nil } 150 | 151 | -- iptables setup container 152 | patches[1] = { 153 | op = "add", 154 | path = "/spec/initContainers/-", 155 | value = { 156 | name = "kong-iptables-setup", 157 | image = plugin_config.initImage, 158 | imagePullPolicy = plugin_config.initImagePullPolicy, 159 | args = plugin_config.initArgs, 160 | securityContext = { capabilities = { add = { "NET_ADMIN" } } }, 161 | }, 162 | } 163 | if not object.spec.initContainers then 164 | -- need to add array member instead of adding *to* it. 165 | patches[1].path = "/spec/initContainers" 166 | patches[1].value = { patches[1].value } 167 | end 168 | 169 | -- Add proxy sidecar container 170 | local config = kong.configuration 171 | local env = { 172 | -- disable admin interface in data plane 173 | { name = "KONG_ADMIN_LISTEN", value = "off" }, 174 | { name = "KONG_PROXY_LISTEN", value = 175 | fmt("0.0.0.0:%d transparent", plugin_config.http_port) .. "," .. 176 | fmt("0.0.0.0:%d ssl transparent", plugin_config.https_port) 177 | }, 178 | { name = "KONG_STREAM_LISTEN", value = 179 | fmt("0.0.0.0:%d transparent", plugin_config.stream_port) 180 | }, 181 | { name = "KONG_PROXY_ACCESS_LOG", value = "/dev/stdout" }, 182 | { name = "KONG_PROXY_ERROR_LOG", value = "/dev/stderr" }, 183 | -- need to copy relevant database configuration 184 | { name = "KONG_DATABASE", value = config.database }, 185 | } 186 | if config.database == "postgres" then 187 | tinsert(env, { name = "KONG_PG_HOST", 188 | value = config.pg_host }) 189 | tinsert(env, { name = "KONG_PG_PORT", 190 | value = fmt("%d", config.pg_port) }) 191 | tinsert(env, { name = "KONG_PG_TIMEOUT", 192 | value = fmt("%d", config.pg_timeout) }) 193 | tinsert(env, { name = "KONG_PG_USER", 194 | value = config.pg_user }) 195 | if config.pg_password then 196 | tinsert(env, { name = "KONG_PG_PASSWORD", 197 | value = config.pg_password }) 198 | end 199 | tinsert(env, { name = "KONG_PG_DATABASE", 200 | value = config.pg_database }) 201 | if config.pg_schema then 202 | tinsert(env, { name = "KONG_PG_SCHEMA", 203 | value = config.pg_schema }) 204 | end 205 | tinsert(env, { name = "KONG_PG_SSL", 206 | value = config.pg_ssl and "ON" or "OFF" }) 207 | tinsert(env, { name = "KONG_PG_SSL_VERIFY", 208 | value = config.pg_ssl_verify and "ON" or "OFF" }) 209 | elseif config.database == "cassandra" then 210 | tinsert(env, { name = "KONG_CASSANDRA_USERNAME", 211 | value = config.cassandra_username }) 212 | tinsert(env, { name = "KONG_CASSANDRA_PORT", 213 | value = fmt("%d", config.cassandra_port) }) 214 | tinsert(env, { name = "KONG_CASSANDRA_LB_POLICY", 215 | value = config.cassandra_lb_policy }) 216 | tinsert(env, { name = "KONG_CASSANDRA_DATA_CENTERS", 217 | value = tconcat(config.cassandra_data_centers, ",") }) 218 | tinsert(env, { name = "KONG_CASSANDRA_SSL", 219 | value = config.cassandra_ssl and "ON" or "OFF" }) 220 | tinsert(env, { name = "KONG_CASSANDRA_CONSISTENCY", 221 | value = config.cassandra_consistency }) 222 | tinsert(env, { name = "KONG_CASSANDRA_REPL_STRATEGY", 223 | value = config.cassandra_repl_strategy }) 224 | tinsert(env, { name = "KONG_CASSANDRA_CONTACT_POINTS", 225 | value = tconcat(config.cassandra_contact_points, ",") }) 226 | tinsert(env, { name = "KONG_CASSANDRA_SCHEMA_CONSENSUS_TIMEOUT", 227 | value = fmt("%d", config.cassandra_schema_consensus_timeout) }) 228 | tinsert(env, { name = "KONG_CASSANDRA_REPL_FACTOR", 229 | value = fmt("%d", config.cassandra_repl_factor) }) 230 | tinsert(env, { name = "KONG_CASSANDRA_TIMEOUT", 231 | value = fmt("%d", config.cassandra_timeout) }) 232 | tinsert(env, { name = "KONG_CASSANDRA_SSL_VERIFY", 233 | value = config.cassandra_ssl_verify and "ON" or "OFF" }) 234 | tinsert(env, { name = "KONG_CASSANDRA_KEYSPACE", 235 | value = config.cassandra_keyspace }) 236 | end 237 | 238 | -- Allow plugin config to add extra env vars 239 | if plugin_config.extra_env then 240 | for k, v in pairs(plugin_config.extra_env) do 241 | tinsert(env, { name = k, value = v }) 242 | end 243 | end 244 | 245 | patches[2] = { 246 | op = "add", 247 | path = "/spec/containers/-", 248 | value = { 249 | name = "kong-sidecar", 250 | image = plugin_config.image, 251 | imagePullPolicy = plugin_config.imagePullPolicy, 252 | env = env, 253 | ports = { 254 | { name = "http", containerPort = 8000, protocol = "TCP" }, 255 | { name = "https", containerPort = 8443, protocol = "TCP" }, 256 | { name = "tcp", containerPort = 7000, protocol = "TCP" }, 257 | }, 258 | }, 259 | } 260 | if not object.spec.containers then 261 | -- need to add array member instead of adding *to* it. 262 | patches[2].path = "/spec/containers" 263 | patches[2].value = { patches[2].value } 264 | end 265 | 266 | reply.response.patchType = "JSONPatch" 267 | reply.response.patch = encode_base64(cjson.encode(patches)) 268 | return { json = reply } 269 | end, 270 | }, 271 | }, 272 | } 273 | -------------------------------------------------------------------------------- /kong/plugins/kubernetes-sidecar-injector/config.lua: -------------------------------------------------------------------------------- 1 | local ngx = ngx 2 | local kong = kong 3 | local tostring = tostring 4 | 5 | 6 | -- Loads a plugin config from the datastore. 7 | -- @return plugin config table or an empty sentinel table in case of a db-miss 8 | local function load_plugin_from_db(key) 9 | local row, err = kong.db.plugins:select_by_cache_key(key) 10 | if err then 11 | return nil, tostring(err) 12 | end 13 | 14 | return row 15 | end 16 | 17 | 18 | --- Load the configuration for a plugin entry. 19 | -- Given a Route, Service, Consumer and a plugin name, retrieve the plugin's 20 | -- configuration if it exists. Results are cached in ngx.dict 21 | -- @param[type=string] name Name of the plugin being tested for configuration. 22 | -- @param[type=string] route_id Id of the route being proxied. 23 | -- @param[type=string] service_id Id of the service being proxied. 24 | -- @param[type=string] consumer_id Id of the consumer making the request (if any). 25 | -- @treturn table Plugin configuration, if retrieved. 26 | local function load_configuration(name, 27 | route_id, 28 | service_id, 29 | consumer_id) 30 | local key = kong.db.plugins:cache_key(name, 31 | route_id, 32 | service_id, 33 | consumer_id) 34 | local plugin, err = kong.cache:get(key, 35 | nil, 36 | load_plugin_from_db, 37 | key) 38 | if err then 39 | ngx.log(ngx.ERR, tostring(err)) 40 | return ngx.exit(ngx.ERROR) 41 | end 42 | 43 | if not plugin or not plugin.enabled then 44 | return 45 | end 46 | 47 | return plugin.config or {} 48 | end 49 | 50 | 51 | return load_configuration 52 | -------------------------------------------------------------------------------- /kong/plugins/kubernetes-sidecar-injector/handler.lua: -------------------------------------------------------------------------------- 1 | local Handler = { 2 | VERSION = "0.2.1", 3 | -- priority doesn't matter, just need to pick something unique for kong tests 4 | PRIORITY = 1006, 5 | } 6 | 7 | return Handler 8 | -------------------------------------------------------------------------------- /kong/plugins/kubernetes-sidecar-injector/schema.lua: -------------------------------------------------------------------------------- 1 | local typedefs = require "kong.db.schema.typedefs" 2 | local k8s_typedefs = require "kong.plugins.kubernetes-sidecar-injector.typedefs" 3 | 4 | return { 5 | name = "kubernetes-sidecar-injector", 6 | fields = { 7 | -- This plugin should only be loaded globally 8 | { route = typedefs.no_route }, 9 | { service = typedefs.no_service }, 10 | { consumer = typedefs.no_consumer }, 11 | 12 | { config = { type = "record", fields = { 13 | { initImage = { type = "string", default = "istio/proxy_init:1.0.5" } }, 14 | { initImagePullPolicy = k8s_typedefs.ImagePullPolicy { default = "IfNotPresent" } }, 15 | { initArgs = { type = "array", elements = { type = "string" }, default = { 16 | "-p", "7000", 17 | "-u", "1337", 18 | "-m", "TPROXY", 19 | "-i", "*", 20 | "-b", "*", 21 | } } }, 22 | { image = { type = "string", default = "kong" } }, 23 | { imagePullPolicy = k8s_typedefs.ImagePullPolicy { default = "IfNotPresent" } }, 24 | { extra_env = { type = "map", 25 | keys = { type = "string" }, 26 | values = { type = "string" } 27 | } }, 28 | { http_port = typedefs.port { default = 8000 } }, 29 | { https_port = typedefs.port { default = 8443 } }, 30 | { stream_port = typedefs.port { default = 7000 } }, -- should match initArgs default 31 | } } }, 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /kong/plugins/kubernetes-sidecar-injector/typedefs.lua: -------------------------------------------------------------------------------- 1 | local Schema = require "kong.db.schema" 2 | local typedefs = require "kong.db.schema.typedefs" 3 | 4 | 5 | local int32 = Schema.define { type = "integer", between = { -2147483648, 2147483647 } } 6 | 7 | -- XXX: 9223372036854775807 gets rounded by lua < 5.3. Can we use LuaJIT long longs? 8 | local int64 = Schema.define { type = "integer", between = { -9223372036854775808, 9223372036854775807 } } 9 | 10 | -- represented in RFC3339 form and is in UTC 11 | local Time = Schema.define { type = "string" } 12 | 13 | 14 | --- Kubernetes Object definitions 15 | -- These can be figured out via reading the kubernetes source. 16 | 17 | -- https://github.com/kubernetes/kubernetes/blob/v1.13.1/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go 18 | 19 | local OwnerReference = Schema.define { type = "record", fields = { 20 | { apiVersion = { type = "string", required = true } }, 21 | { kind = { type = "string", required = true } }, 22 | { name = { type = "string", required = true } }, 23 | { uid = typedefs.uuid { required = true } }, 24 | { controller = { type = "boolean" } }, 25 | { blockOwnerDeletion = { type = "boolean" } }, 26 | } } 27 | 28 | local ListMeta = Schema.define { type = "record", fields = { 29 | { selfLink = { type = "string" } }, 30 | { resourceVersion = { type = "string" } }, 31 | { continue = { type = "string" } }, 32 | } } 33 | 34 | local StatusReason = Schema.define { type = "string", len_min = 0, one_of = { 35 | "", 36 | "Unauthorized", 37 | "Forbidden", 38 | "NotFound", 39 | "AlreadyExists", 40 | "Conflict", 41 | "Gone", 42 | "Invalid", 43 | "ServerTimeout", 44 | "Timeout", 45 | "TooManyRequests", 46 | "BadRequest", 47 | "MethodNotAllowed", 48 | "NotAcceptable", 49 | "UnsupportedMediaType", 50 | "InternalError", 51 | "Expired", 52 | "ServiceUnavailable", 53 | } } 54 | 55 | local CauseType = Schema.define { type = "string", one_of = { 56 | "FieldValueNotFound", 57 | "FieldValueRequired", 58 | "FieldValueDuplicate", 59 | "FieldValueInvalid", 60 | "FieldValueNotSupported", 61 | "UnexpectedServerResponse", 62 | } } 63 | 64 | local StatusCause = Schema.define { type = "record", fields = { 65 | { reason = CauseType }, 66 | { message = { type = "string" } }, 67 | { field = { type = "string" } }, 68 | } } 69 | 70 | local StatusDetails = Schema.define { type = "record", fields = { 71 | { name = { type = "string" } }, 72 | { group = { type = "string" } }, 73 | { kind = { type = "string" } }, 74 | { uid = typedefs.uuid { } }, 75 | { causes = { type = "array", elements = StatusCause } }, 76 | { retryAfterSeconds = int32 }, 77 | } } 78 | 79 | local Status = Schema.define { type = "record", fields = { 80 | { metadata = { type = ListMeta } }, 81 | { status = { type = "string" } }, 82 | { message = { type = "string" } }, 83 | { reason = { type = StatusReason } }, 84 | { details = { type = StatusDetails } }, 85 | { code = int32 }, 86 | } } 87 | 88 | local Initializer = Schema.define { type = "record", fields = { 89 | { name = { type = "string", required = true } }, 90 | } } 91 | 92 | local Initializers = Schema.define { type = "record", fields = { 93 | { pending = { type = "array", required = true, elements = Initializer } }, 94 | { status = Status { required = true } }, 95 | } } 96 | 97 | local ObjectMeta = Schema.define { type = "record", fields = { 98 | { name = { type = "string" } }, 99 | { generateName = { type = "string" } }, 100 | { namespace = { type = "string" } }, 101 | { selfLink = { type = "string" } }, 102 | { uid = typedefs.uuid }, 103 | { resourceVersion = { type = "string" } }, 104 | { generation = int64 }, 105 | { creationTimestamp = Time }, 106 | { deletionTimestamp = Time }, 107 | { deletionGracePeriodSeconds = { type = "integer" } }, 108 | { labels = { type = "map", keys = { type = "string" }, values = { type = "string" } } }, 109 | { annotations = { type = "map", keys = { type = "string" }, values = { type = "string" } } }, 110 | { ownerReferences = { type = "array", elements = OwnerReference } }, 111 | { initializers = Initializers }, 112 | { finalizers = { type = "array", elements = { type = "string" } } }, 113 | { clusterName = { type = "string" } }, 114 | } } 115 | 116 | 117 | -- https://github.com/kubernetes/kubernetes/blob/v1.13.1/staging/src/k8s.io/apimachinery/pkg/runtime/types.go 118 | 119 | local RawExtension = Schema.define { type = "any" } 120 | 121 | 122 | -- https://github.com/kubernetes/kubernetes/blob/v1.13.1/staging/src/k8s.io/api/core/v1/types.go 123 | 124 | local ImagePullPolicy = Schema.define { type = "string", one_of = { 125 | "Always", 126 | "Never", 127 | "IfNotPresent", 128 | } } 129 | 130 | -- TODO: Complete PodSpec definition 131 | local PodSpec = Schema.define { type = "any" } 132 | 133 | local Pod = Schema.define { type = "record", fields = { 134 | { metadata = ObjectMeta }, 135 | { spec = PodSpec }, 136 | { status = Status }, 137 | { kind = { type = "string" } }, 138 | { apiVersion = { type = "string" } }, 139 | } } 140 | 141 | 142 | -- https://github.com/kubernetes/kubernetes/blob/v1.13.1/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/group_version.go 143 | 144 | local GroupVersionKind = Schema.define { type = "record", fields = { 145 | { group = { type = "string", required = true, len_min = 0 } }, 146 | { version = { type = "string", required = true } }, 147 | { kind = { type = "string", required = true } }, 148 | } } 149 | 150 | local GroupVersionResource = Schema.define { type = "record", fields = { 151 | { group = { type = "string", required = true, len_min = 0 } }, 152 | { version = { type = "string", required = true } }, 153 | { resource = { type = "string", required = true } }, 154 | } } 155 | 156 | 157 | -- https://github.com/kubernetes/kubernetes/blob/v1.13.1/staging/src/k8s.io/api/authentication/v1/types.go 158 | 159 | local UserInfo = Schema.define { type = "record", fields = { 160 | { username = { type = "string" } }, 161 | { uid = typedefs.uuid }, 162 | { groups = { type = "array", elements = { type = "string" } } }, 163 | -- TODO: kong map type doesn't allow 'null' as value 164 | -- { extra = { type = "map", keys = { type = "string" }, values = { type = "any" } } }, 165 | { extra = { type = "any" } }, 166 | } } 167 | 168 | 169 | -- https://github.com/kubernetes/kubernetes/blob/v1.13.1/staging/src/k8s.io/api/admission/v1beta1/types.go 170 | 171 | local Operation = Schema.define { type = "string", one_of = { 172 | "CREATE", 173 | "UPDATE", 174 | "DELETE", 175 | "CONNECT", 176 | } } 177 | 178 | local AdmissionRequest = Schema.define { 179 | type = "record", 180 | fields = { 181 | { uid = typedefs.uuid { required = true } }, 182 | { kind = GroupVersionKind { required = true } }, 183 | { resource = GroupVersionResource { required = true } }, 184 | { subResource = { type = "string" } }, 185 | { name = { type = "string" } }, 186 | { namespace = { type = "string" } }, 187 | { operation = Operation { required = true } }, 188 | { userInfo = UserInfo { required = true } }, 189 | { object = RawExtension }, 190 | { oldObject = RawExtension }, 191 | { dryRun = { type = "boolean" } }, 192 | }, 193 | entity_checks = { 194 | { conditional = { if_field = "operation", 195 | if_match = { type = "string", one_of = { "UPDATE" } }, 196 | then_field = "oldObject", 197 | then_match = { required = true }, 198 | then_err = "oldObject required for UPDATEs", 199 | } }, 200 | }, 201 | } 202 | 203 | 204 | return { 205 | OwnerReference = OwnerReference, 206 | ListMeta = ListMeta, 207 | StatusReason = StatusReason, 208 | CauseType = CauseType, 209 | StatusCause = StatusCause, 210 | StatusDetails = StatusDetails, 211 | Status = Status, 212 | Initializer = Initializer, 213 | Initializers = Initializers, 214 | ObjectMeta = ObjectMeta, 215 | RawExtension = RawExtension, 216 | ImagePullPolicy = ImagePullPolicy, 217 | PodSpec = PodSpec, 218 | Pod = Pod, 219 | GroupVersionKind = GroupVersionKind, 220 | GroupVersionResource = GroupVersionResource, 221 | UserInfo = UserInfo, 222 | Operation = Operation, 223 | AdmissionRequest = AdmissionRequest, 224 | } 225 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export KUBECONFIG="$(kind get kubeconfig-path --name="kind")" 6 | 7 | counter=0 8 | while [[ "$(kubectl get pod --all-namespaces | grep -v Running | grep -v Completed | wc -l)" != 1 ]]; do 9 | counter=$((counter + 1)) 10 | if [ "$counter" -gt "30" ] 11 | then 12 | exit 1 13 | fi 14 | kubectl get pod --all-namespaces -o wide 15 | echo "waiting for K8s to be ready" 16 | sleep 10; 17 | done 18 | 19 | pushd kong-dist-kubernetes; \ 20 | make run_postgres; \ 21 | popd 22 | 23 | counter=0 24 | while [[ "$(kubectl get deployment kong-control-plane -n kong | tail -n +2 | awk '{print $4}')" != 1 ]]; do 25 | counter=$((counter + 1)) 26 | if [ "$counter" -gt "30" ] 27 | then 28 | exit 1 29 | fi 30 | echo "waiting for Kong control plane to be ready" 31 | kubectl get pod --all-namespaces -o wide 32 | sleep 10; 33 | done 34 | 35 | counter=0 36 | while [[ "$(kubectl get deployment kong-ingress-data-plane -n kong | tail -n +2 | awk '{print $4}')" != 1 ]]; do 37 | counter=$((counter + 1)) 38 | if [ "$counter" -gt "30" ] 39 | then 40 | exit 1 41 | fi 42 | echo "waiting for Kong data plane to be ready" 43 | kubectl get pod --all-namespaces -o wide 44 | sleep 10; 45 | done 46 | 47 | HOST="$(kubectl get nodes --namespace default -o jsonpath='{.items[0].status.addresses[0].address}')" 48 | echo $HOST 49 | ADMIN_PORT=$(kubectl get svc --namespace kong kong-control-plane -o jsonpath='{.spec.ports[0].nodePort}') 50 | echo $ADMIN_PORT 51 | 52 | curl http://$HOST:$ADMIN_PORT/plugins -d name=kubernetes-sidecar-injector -d config.image=localhost:5000/kong-sidecar-injector 53 | 54 | sleep 10; 55 | 56 | ./kong-dist-kubernetes/setup_sidecar_injector.sh 57 | 58 | sleep 10; 59 | 60 | kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.1/samples/bookinfo/platform/kube/bookinfo.yaml 61 | 62 | sleep 10; 63 | 64 | while [[ "$(kubectl get deployment details-v1 | tail -n +2 | awk '{print $4}')" != 1 ]]; do 65 | echo "waiting for bookinfo to be ready" 66 | sleep 10; 67 | done 68 | 69 | if [[ "$(kubectl get pods | grep details | awk '{print $2}')" != '2/2' ]]; then 70 | exit 1 71 | fi 72 | --------------------------------------------------------------------------------