AlinsRan commented on code in PR #13509: URL: https://github.com/apache/apisix/pull/13509#discussion_r3549401529
########## apisix/secret/kubernetes.lua: ########## @@ -0,0 +1,262 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +--- Kubernetes Secret Manager. +-- Fetches secrets from the Kubernetes API server using the pod's ServiceAccount +-- token. This allows APISIX to read Kubernetes Secrets directly from the cluster +-- it is running in, without requiring an external secrets management service. +-- +-- URI format: +-- $secret://kubernetes/{manager-id}/{namespace}/{secret-name}/{data-key} +-- +-- Example: +-- $secret://kubernetes/my-k8s/default/my-secret/password +-- +-- The manager is configured once via the Admin API: +-- PUT /apisix/admin/secrets/kubernetes/my-k8s +-- { +-- "service_account_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", +-- "kubernetes_host": "kubernetes.default.svc", +-- "kubernetes_port": "443", +-- "ssl_verify": true +-- } +-- +-- All fields have sensible defaults for in-cluster usage, so the minimal +-- valid configuration is an empty object `{}`. +-- +-- TLS note: when ssl_verify is true (the default), the TLS handshake is +-- validated against the nginx-level lua_ssl_trusted_certificate bundle. +-- For in-cluster usage, set apisix.ssl.ssl_trusted_certificate in config.yaml +-- to /var/run/secrets/kubernetes.io/serviceaccount/ca.crt so that the +-- kube-apiserver certificate (signed by the cluster CA) is trusted. + +local core = require("apisix.core") +local http = require("resty.http") +local env = core.env + +local io_open = io.open +local find = core.string.find +local sub = core.string.sub +local ngx_decode_base64 = ngx.decode_base64 + +local DEFAULT_SA_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token" + +local schema = { + type = "object", + properties = { + service_account_file = { + type = "string", + description = "Path to the ServiceAccount token file. " + .. "Defaults to the standard in-cluster path.", + default = DEFAULT_SA_FILE, + }, + kubernetes_host = { + type = "string", + description = "Kubernetes API server hostname or IP. " + .. "Defaults to the KUBERNETES_SERVICE_HOST environment variable.", + }, + kubernetes_port = { + type = "string", + description = "Kubernetes API server port. " + .. "Defaults to the KUBERNETES_SERVICE_PORT environment variable.", + }, + endpoint = { + type = "string", + description = "Full base URL of the Kubernetes API server " + .. "(e.g. https://kubernetes.default.svc:443). " + .. "When set, kubernetes_host and kubernetes_port are ignored. " + .. "Useful for testing with plain HTTP mock servers.", + }, + ssl_verify = { + type = "boolean", + description = "Verify the Kubernetes API server TLS certificate. " + .. "When true, validation uses the nginx-level " + .. "lua_ssl_trusted_certificate bundle; set " + .. "apisix.ssl.ssl_trusted_certificate to the cluster CA " + .. "(/var/run/secrets/kubernetes.io/serviceaccount/ca.crt) " + .. "in config.yaml for in-cluster usage.", + default = true, + }, + }, + required = {}, Review Comment: This one line makes the manager unregisterable. An empty Lua table serializes to an empty JSON *object*, not an array, so jsonschema reads it as "input must be an empty table" and rejects anything with properties: ``` required={} + {} -> OK required={} + {ssl_verify=true} -> the input data should be an empty table no required + {ssl_verify=true} -> OK ``` `secret.lua:52` and `admin/secrets.lua:35` both run `core.schema.check(secret_manager.schema, conf)`, so every non-empty PUT body is rejected. That's TEST 13's `error_msg`, and TEST 14 then cascades to `no secret conf`. Drop the line. vault/aws/gcp omit `required` when nothing is mandatory. ########## apisix/secret/kubernetes.lua: ########## @@ -0,0 +1,262 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +--- Kubernetes Secret Manager. +-- Fetches secrets from the Kubernetes API server using the pod's ServiceAccount +-- token. This allows APISIX to read Kubernetes Secrets directly from the cluster +-- it is running in, without requiring an external secrets management service. +-- +-- URI format: +-- $secret://kubernetes/{manager-id}/{namespace}/{secret-name}/{data-key} +-- +-- Example: +-- $secret://kubernetes/my-k8s/default/my-secret/password +-- +-- The manager is configured once via the Admin API: +-- PUT /apisix/admin/secrets/kubernetes/my-k8s +-- { +-- "service_account_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", +-- "kubernetes_host": "kubernetes.default.svc", +-- "kubernetes_port": "443", +-- "ssl_verify": true +-- } +-- +-- All fields have sensible defaults for in-cluster usage, so the minimal +-- valid configuration is an empty object `{}`. +-- +-- TLS note: when ssl_verify is true (the default), the TLS handshake is +-- validated against the nginx-level lua_ssl_trusted_certificate bundle. +-- For in-cluster usage, set apisix.ssl.ssl_trusted_certificate in config.yaml +-- to /var/run/secrets/kubernetes.io/serviceaccount/ca.crt so that the +-- kube-apiserver certificate (signed by the cluster CA) is trusted. + +local core = require("apisix.core") +local http = require("resty.http") +local env = core.env + +local io_open = io.open +local find = core.string.find +local sub = core.string.sub +local ngx_decode_base64 = ngx.decode_base64 + +local DEFAULT_SA_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token" + +local schema = { + type = "object", + properties = { + service_account_file = { + type = "string", + description = "Path to the ServiceAccount token file. " + .. "Defaults to the standard in-cluster path.", + default = DEFAULT_SA_FILE, + }, + kubernetes_host = { + type = "string", + description = "Kubernetes API server hostname or IP. " + .. "Defaults to the KUBERNETES_SERVICE_HOST environment variable.", + }, + kubernetes_port = { + type = "string", + description = "Kubernetes API server port. " + .. "Defaults to the KUBERNETES_SERVICE_PORT environment variable.", + }, + endpoint = { + type = "string", + description = "Full base URL of the Kubernetes API server " + .. "(e.g. https://kubernetes.default.svc:443). " + .. "When set, kubernetes_host and kubernetes_port are ignored. " + .. "Useful for testing with plain HTTP mock servers.", + }, + ssl_verify = { + type = "boolean", + description = "Verify the Kubernetes API server TLS certificate. " + .. "When true, validation uses the nginx-level " + .. "lua_ssl_trusted_certificate bundle; set " + .. "apisix.ssl.ssl_trusted_certificate to the cluster CA " + .. "(/var/run/secrets/kubernetes.io/serviceaccount/ca.crt) " + .. "in config.yaml for in-cluster usage.", + default = true, + }, + }, + required = {}, +} + +local _M = { + schema = schema, +} + + +local function read_file(path) + local f, err = io_open(path, "r") + if not f then + return nil, "failed to open file " .. path .. ": " .. err + end + local content = f:read("*a") + f:close() + if not content or content == "" then + return nil, "file is empty: " .. path + end + return content +end + + +local function make_request_to_k8s(conf, namespace, secret_name) + local sa_file = conf.service_account_file or DEFAULT_SA_FILE + local token, err = read_file(sa_file) + if not token then + return nil, err + end + -- strip trailing newline + token = token:gsub("%s+$", "") + + local k8s_host = conf.kubernetes_host + or env.fetch_by_uri("$ENV://KUBERNETES_SERVICE_HOST") + or os.getenv("KUBERNETES_SERVICE_HOST") Review Comment: This is the lint failure (`line 128: getting the Lua global "os"`, same on 132). Use `core.env.get("KUBERNETES_SERVICE_HOST")`. `discovery/kubernetes/core.lua:74` already switched for this reason — `os.getenv` hits a lua-resty-core prefix-collision bug during worker init (#13055). It's redundant too: `env.fetch_by_uri("$ENV://...")` above already falls back to `os.getenv` internally. `core.env.get` alone covers both. ########## apisix/secret/kubernetes.lua: ########## @@ -0,0 +1,262 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +--- Kubernetes Secret Manager. +-- Fetches secrets from the Kubernetes API server using the pod's ServiceAccount +-- token. This allows APISIX to read Kubernetes Secrets directly from the cluster +-- it is running in, without requiring an external secrets management service. +-- +-- URI format: +-- $secret://kubernetes/{manager-id}/{namespace}/{secret-name}/{data-key} +-- +-- Example: +-- $secret://kubernetes/my-k8s/default/my-secret/password +-- +-- The manager is configured once via the Admin API: +-- PUT /apisix/admin/secrets/kubernetes/my-k8s +-- { +-- "service_account_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", +-- "kubernetes_host": "kubernetes.default.svc", +-- "kubernetes_port": "443", +-- "ssl_verify": true +-- } +-- +-- All fields have sensible defaults for in-cluster usage, so the minimal +-- valid configuration is an empty object `{}`. +-- +-- TLS note: when ssl_verify is true (the default), the TLS handshake is +-- validated against the nginx-level lua_ssl_trusted_certificate bundle. +-- For in-cluster usage, set apisix.ssl.ssl_trusted_certificate in config.yaml +-- to /var/run/secrets/kubernetes.io/serviceaccount/ca.crt so that the +-- kube-apiserver certificate (signed by the cluster CA) is trusted. + +local core = require("apisix.core") +local http = require("resty.http") +local env = core.env + +local io_open = io.open +local find = core.string.find +local sub = core.string.sub +local ngx_decode_base64 = ngx.decode_base64 + +local DEFAULT_SA_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token" + +local schema = { + type = "object", + properties = { + service_account_file = { + type = "string", + description = "Path to the ServiceAccount token file. " + .. "Defaults to the standard in-cluster path.", + default = DEFAULT_SA_FILE, + }, + kubernetes_host = { + type = "string", + description = "Kubernetes API server hostname or IP. " + .. "Defaults to the KUBERNETES_SERVICE_HOST environment variable.", + }, + kubernetes_port = { + type = "string", + description = "Kubernetes API server port. " + .. "Defaults to the KUBERNETES_SERVICE_PORT environment variable.", + }, + endpoint = { + type = "string", + description = "Full base URL of the Kubernetes API server " + .. "(e.g. https://kubernetes.default.svc:443). " + .. "When set, kubernetes_host and kubernetes_port are ignored. " + .. "Useful for testing with plain HTTP mock servers.", + }, + ssl_verify = { + type = "boolean", + description = "Verify the Kubernetes API server TLS certificate. " + .. "When true, validation uses the nginx-level " + .. "lua_ssl_trusted_certificate bundle; set " + .. "apisix.ssl.ssl_trusted_certificate to the cluster CA " + .. "(/var/run/secrets/kubernetes.io/serviceaccount/ca.crt) " + .. "in config.yaml for in-cluster usage.", + default = true, + }, + }, + required = {}, +} + +local _M = { + schema = schema, +} + + +local function read_file(path) + local f, err = io_open(path, "r") + if not f then + return nil, "failed to open file " .. path .. ": " .. err + end + local content = f:read("*a") + f:close() + if not content or content == "" then + return nil, "file is empty: " .. path + end + return content +end + + +local function make_request_to_k8s(conf, namespace, secret_name) + local sa_file = conf.service_account_file or DEFAULT_SA_FILE + local token, err = read_file(sa_file) + if not token then + return nil, err + end + -- strip trailing newline + token = token:gsub("%s+$", "") + + local k8s_host = conf.kubernetes_host Review Comment: The in-cluster default won't work. Workers only see env vars declared via an `env` directive, and `cli/ngx_tpl.lua` doesn't emit `KUBERNETES_SERVICE_HOST`/`_PORT`. So with the documented minimal config `{}` this is nil and you get `kubernetes_host is not set and KUBERNETES_SERVICE_HOST env var is missing`. Either require users to declare both under `nginx_config.envs`, or drop the env fallback and require `kubernetes_host`. The doc table currently presents `$KUBERNETES_SERVICE_HOST` as a working default. No test covers this branch — every test passes an explicit `endpoint`. ########## apisix/secret/kubernetes.lua: ########## @@ -0,0 +1,262 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +--- Kubernetes Secret Manager. +-- Fetches secrets from the Kubernetes API server using the pod's ServiceAccount +-- token. This allows APISIX to read Kubernetes Secrets directly from the cluster +-- it is running in, without requiring an external secrets management service. +-- +-- URI format: +-- $secret://kubernetes/{manager-id}/{namespace}/{secret-name}/{data-key} +-- +-- Example: +-- $secret://kubernetes/my-k8s/default/my-secret/password +-- +-- The manager is configured once via the Admin API: +-- PUT /apisix/admin/secrets/kubernetes/my-k8s +-- { +-- "service_account_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", +-- "kubernetes_host": "kubernetes.default.svc", +-- "kubernetes_port": "443", +-- "ssl_verify": true +-- } +-- +-- All fields have sensible defaults for in-cluster usage, so the minimal +-- valid configuration is an empty object `{}`. +-- +-- TLS note: when ssl_verify is true (the default), the TLS handshake is +-- validated against the nginx-level lua_ssl_trusted_certificate bundle. +-- For in-cluster usage, set apisix.ssl.ssl_trusted_certificate in config.yaml +-- to /var/run/secrets/kubernetes.io/serviceaccount/ca.crt so that the +-- kube-apiserver certificate (signed by the cluster CA) is trusted. + +local core = require("apisix.core") +local http = require("resty.http") +local env = core.env + +local io_open = io.open +local find = core.string.find +local sub = core.string.sub +local ngx_decode_base64 = ngx.decode_base64 + +local DEFAULT_SA_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token" + +local schema = { + type = "object", + properties = { + service_account_file = { + type = "string", + description = "Path to the ServiceAccount token file. " + .. "Defaults to the standard in-cluster path.", + default = DEFAULT_SA_FILE, + }, + kubernetes_host = { + type = "string", + description = "Kubernetes API server hostname or IP. " + .. "Defaults to the KUBERNETES_SERVICE_HOST environment variable.", + }, + kubernetes_port = { + type = "string", + description = "Kubernetes API server port. " + .. "Defaults to the KUBERNETES_SERVICE_PORT environment variable.", + }, + endpoint = { + type = "string", + description = "Full base URL of the Kubernetes API server " + .. "(e.g. https://kubernetes.default.svc:443). " + .. "When set, kubernetes_host and kubernetes_port are ignored. " + .. "Useful for testing with plain HTTP mock servers.", + }, + ssl_verify = { + type = "boolean", + description = "Verify the Kubernetes API server TLS certificate. " + .. "When true, validation uses the nginx-level " + .. "lua_ssl_trusted_certificate bundle; set " + .. "apisix.ssl.ssl_trusted_certificate to the cluster CA " + .. "(/var/run/secrets/kubernetes.io/serviceaccount/ca.crt) " + .. "in config.yaml for in-cluster usage.", + default = true, + }, + }, + required = {}, +} + +local _M = { + schema = schema, +} + + +local function read_file(path) + local f, err = io_open(path, "r") + if not f then + return nil, "failed to open file " .. path .. ": " .. err + end + local content = f:read("*a") + f:close() + if not content or content == "" then + return nil, "file is empty: " .. path + end + return content +end + + +local function make_request_to_k8s(conf, namespace, secret_name) + local sa_file = conf.service_account_file or DEFAULT_SA_FILE + local token, err = read_file(sa_file) + if not token then + return nil, err + end + -- strip trailing newline + token = token:gsub("%s+$", "") + + local k8s_host = conf.kubernetes_host + or env.fetch_by_uri("$ENV://KUBERNETES_SERVICE_HOST") + or os.getenv("KUBERNETES_SERVICE_HOST") + + local k8s_port = conf.kubernetes_port + or env.fetch_by_uri("$ENV://KUBERNETES_SERVICE_PORT") + or os.getenv("KUBERNETES_SERVICE_PORT") + or "443" + + local base_url + if conf.endpoint then + base_url = conf.endpoint + else + if not k8s_host then + return nil, "kubernetes_host is not set and KUBERNETES_SERVICE_HOST env var is missing" + end + base_url = "https://" .. k8s_host .. ":" .. k8s_port + end + + local uri = base_url + .. "/api/v1/namespaces/" .. namespace + .. "/secrets/" .. secret_name + + core.log.info("fetching Kubernetes secret from: ", uri) + + local httpc = http.new() + httpc:set_timeout(5000) + + local ssl_verify = conf.ssl_verify + if ssl_verify == nil then + ssl_verify = true + end + + local request_opts = { + method = "GET", + headers = { + ["Authorization"] = "Bearer " .. token, + ["Accept"] = "application/json", + }, + ssl_verify = ssl_verify, + } + + local res, req_err = httpc:request_uri(uri, request_opts) + if not res then + return nil, "failed to request Kubernetes API: " .. req_err + end + + if res.status == 401 or res.status == 403 then + return nil, "unauthorized to read Kubernetes secret " + .. namespace .. "/" .. secret_name + .. " (HTTP " .. res.status .. "): check RBAC permissions for the ServiceAccount" + end + + if res.status == 404 then + return nil, "Kubernetes secret not found: " .. namespace .. "/" .. secret_name + end + + if res.status ~= 200 then + return nil, "unexpected HTTP status " .. res.status + .. " from Kubernetes API for secret " + .. namespace .. "/" .. secret_name + end + + return res.body +end + + +-- key format: {namespace}/{secret-name}/{data-key} +local function get(conf, key) + core.log.info("fetching data from Kubernetes secret for key: ", key) + + local idx1 = find(key, "/") + if not idx1 then + return nil, "invalid key format, expected {namespace}/{secret-name}/{data-key}, got: " + .. key + end + + local namespace = sub(key, 1, idx1 - 1) + if namespace == "" then + return nil, "namespace is empty in key: " .. key Review Comment: `namespace` and `secret_name` land in the request path unescaped. resty.http's `_format_request` inserts it verbatim and `parse_uri` splits on `([^\?]*)`, so `?`, `#` and CRLF pass through to the apiserver. `$secret://kubernetes/mgr/default?labelSelector=x/mysecret/key` emits `GET /api/v1/namespaces/default?labelSelector=x/secrets/mysecret`. Not a vulnerability — Admin API operators are trusted and already have `script`. But a DNS-1123 check on both is cheap and turns a malformed ref into a clear error instead of a silently reshaped request. ########## t/secret/kubernetes.t: ########## @@ -0,0 +1,463 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +BEGIN { + $ENV{KUBERNETES_SERVICE_HOST} = "127.0.0.1"; + $ENV{KUBERNETES_SERVICE_PORT} = "6443"; +} + +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +log_level("info"); + +run_tests; + +__DATA__ + +=== TEST 1: schema validation - all fields valid +--- config + location /t { + content_by_lua_block { + local kubernetes = require("apisix.secret.kubernetes") + local core = require("apisix.core") + + local test_cases = { + {}, + {ssl_verify = true}, + {ssl_verify = false}, + {ssl_verify = 1234}, + {service_account_file = "/var/run/secrets/kubernetes.io/serviceaccount/token"}, + {service_account_file = 1234}, + {kubernetes_host = "kubernetes.default.svc"}, + {kubernetes_host = 1234}, + {kubernetes_port = "443"}, + {kubernetes_port = 1234}, + { + service_account_file = "/var/run/secrets/kubernetes.io/serviceaccount/token", + kubernetes_host = "kubernetes.default.svc", + kubernetes_port = "443", + ssl_verify = true, + }, + } + + for _, conf in ipairs(test_cases) do + local ok, err = core.schema.check(kubernetes.schema, conf) + ngx.say(ok and "done" or err) + end + } + } +--- request +GET /t +--- response_body +done +done +done +property "ssl_verify" validation failed: wrong type: expected boolean, got number +done +property "service_account_file" validation failed: wrong type: expected string, got number +done +property "kubernetes_host" validation failed: wrong type: expected string, got number +done +property "kubernetes_port" validation failed: wrong type: expected string, got number +done + + + +=== TEST 2: get - invalid key format (no slashes) +--- config + location /t { + content_by_lua_block { + local kubernetes = require("apisix.secret.kubernetes") + local data, err = kubernetes.get({}, "no-slashes") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body +invalid key format, expected {namespace}/{secret-name}/{data-key}, got: no-slashes + + + +=== TEST 3: get - invalid key format (only one slash, missing data-key) +--- config + location /t { + content_by_lua_block { + local kubernetes = require("apisix.secret.kubernetes") + local data, err = kubernetes.get({}, "default/my-secret") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body +invalid key format, missing data-key, expected {namespace}/{secret-name}/{data-key}, got: default/my-secret + + + +=== TEST 4: get - empty namespace +--- config + location /t { + content_by_lua_block { + local kubernetes = require("apisix.secret.kubernetes") + local data, err = kubernetes.get({}, "/my-secret/my-key") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body +namespace is empty in key: /my-secret/my-key + + + +=== TEST 5: get - empty secret-name +--- config + location /t { + content_by_lua_block { + local kubernetes = require("apisix.secret.kubernetes") + local data, err = kubernetes.get({}, "default//my-key") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body +secret-name is empty in key: default//my-key + + + +=== TEST 6: get - empty data-key +--- config + location /t { + content_by_lua_block { + local kubernetes = require("apisix.secret.kubernetes") + local data, err = kubernetes.get({}, "default/my-secret/") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body +data-key is empty in key: default/my-secret/ + + + +=== TEST 7: get - service account file not found +--- config + location /t { + content_by_lua_block { + local kubernetes = require("apisix.secret.kubernetes") + local conf = { + service_account_file = "/nonexistent/token", + ssl_verify = false, + } + local data, err = kubernetes.get(conf, "default/my-secret/my-key") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body_like +failed to open file /nonexistent/token:.* + + + +=== TEST 8: get - connection refused (API server not reachable) +--- config + location /t { + content_by_lua_block { + -- Write a temp token file for the test + local f = io.open("/tmp/test-sa-token", "w") + f:write("test-token") + f:close() + + local kubernetes = require("apisix.secret.kubernetes") + local conf = { + service_account_file = "/tmp/test-sa-token", + endpoint = "https://127.0.0.2:9999", + ssl_verify = false, + } + local data, err = kubernetes.get(conf, "default/my-secret/my-key") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body_like +failed to request Kubernetes API:.* +--- timeout: 6 + + + +=== TEST 9: get - mock Kubernetes API returns 401 +--- config + location /mock-k8s-401 { Review Comment: Fails in CI, and can't test what the name says. The mock sits at `/mock-k8s-401` but the code requests `/api/v1/namespaces/default/secrets/my-secret`, so it hits the catch-all, gets 404, and returns `Kubernetes secret not found: default/my-secret` — not in the assertion's alternation. Put the mock at `location ~ "^/api/v1/namespaces/default/secrets/my-secret$"` returning 401 and assert the exact message. The three-way alternation would pass on nearly any failure anyway. Also: the `BEGIN { $ENV{KUBERNETES_SERVICE_HOST} = ... }` block is dead code, nothing exercises the env fallback. Untested branches: non-200/401/403/404 status, malformed JSON, secret with no `data`, and the `ngx.decode_base64` nil path. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
