| -- |
| -- 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. |
| -- |
| |
| local require = require |
| local core = require("apisix.core") |
| local string = require("apisix.core.string") |
| |
| local find = string.find |
| local sub = string.sub |
| local upper = string.upper |
| local byte = string.byte |
| local type = type |
| local pcall = pcall |
| local pairs = pairs |
| |
| local _M = {} |
| |
| |
| local PREFIX = "$secret://" |
| local secrets |
| |
| local function check_secret(conf) |
| local idx = find(conf.id or "", "/") |
| if not idx then |
| return false, "no secret id" |
| end |
| local manager = sub(conf.id, 1, idx - 1) |
| |
| local ok, secret_manager = pcall(require, "apisix.secret." .. manager) |
| if not ok then |
| return false, "secret manager not exits, manager: " .. manager |
| end |
| |
| return core.schema.check(secret_manager.schema, conf) |
| end |
| |
| |
| local function secret_kv(manager, confid) |
| local secret_values |
| secret_values = core.config.fetch_created_obj("/secrets") |
| if not secret_values or not secret_values.values then |
| return nil |
| end |
| |
| local secret = secret_values:get(manager .. "/" .. confid) |
| if not secret then |
| return nil |
| end |
| |
| return secret.value |
| end |
| |
| |
| function _M.secrets() |
| if not secrets then |
| return nil, nil |
| end |
| |
| return secrets.values, secrets.conf_version |
| end |
| |
| |
| function _M.init_worker() |
| local cfg = { |
| automatic = true, |
| checker = check_secret, |
| } |
| |
| secrets = core.config.new("/secrets", cfg) |
| end |
| |
| |
| local function check_secret_uri(secret_uri) |
| -- Avoid the error caused by has_prefix to cause a crash. |
| if type(secret_uri) ~= "string" then |
| return false, "error secret_uri type: " .. type(secret_uri) |
| end |
| |
| if not string.has_prefix(secret_uri, PREFIX) and |
| not string.has_prefix(upper(secret_uri), core.env.PREFIX) then |
| return false, "error secret_uri prefix: " .. secret_uri |
| end |
| |
| return true |
| end |
| |
| _M.check_secret_uri = check_secret_uri |
| |
| |
| local function parse_secret_uri(secret_uri) |
| local is_secret_uri, err = check_secret_uri(secret_uri) |
| if not is_secret_uri then |
| return is_secret_uri, err |
| end |
| |
| local path = sub(secret_uri, #PREFIX + 1) |
| local idx1 = find(path, "/") |
| if not idx1 then |
| return nil, "error format: no secret manager" |
| end |
| local manager = sub(path, 1, idx1 - 1) |
| |
| local idx2 = find(path, "/", idx1 + 1) |
| if not idx2 then |
| return nil, "error format: no secret conf id" |
| end |
| local confid = sub(path, idx1 + 1, idx2 - 1) |
| |
| local key = sub(path, idx2 + 1) |
| if key == "" then |
| return nil, "error format: no secret key id" |
| end |
| |
| local opts = { |
| manager = manager, |
| confid = confid, |
| key = key |
| } |
| return opts |
| end |
| |
| |
| local function fetch_by_uri(secret_uri) |
| local opts, err = parse_secret_uri(secret_uri) |
| if not opts then |
| return nil, err |
| end |
| |
| local conf = secret_kv(opts.manager, opts.confid) |
| if not conf then |
| return nil, "no secret conf, secret_uri: " .. secret_uri |
| end |
| |
| local ok, sm = pcall(require, "apisix.secret." .. opts.manager) |
| if not ok then |
| return nil, "no secret manager: " .. opts.manager |
| end |
| |
| local value, err = sm.get(conf, opts.key) |
| if err then |
| return nil, err |
| end |
| |
| return value |
| end |
| |
| -- for test |
| _M.fetch_by_uri = fetch_by_uri |
| |
| |
| local function fetch(uri) |
| -- do a quick filter to improve retrieval speed |
| if byte(uri, 1, 1) ~= byte('$') then |
| return nil |
| end |
| |
| local val, err |
| if string.has_prefix(upper(uri), core.env.PREFIX) then |
| val, err = core.env.fetch_by_uri(uri) |
| elseif string.has_prefix(uri, PREFIX) then |
| val, err = fetch_by_uri(uri) |
| end |
| |
| if err then |
| core.log.error("failed to fetch secret value: ", err) |
| return |
| end |
| |
| return val |
| end |
| |
| |
| local secrets_lrucache = core.lrucache.new({ |
| ttl = 300, count = 512 |
| }) |
| |
| local fetch_secrets |
| do |
| local retrieve_refs |
| function retrieve_refs(refs) |
| for k, v in pairs(refs) do |
| local typ = type(v) |
| if typ == "string" then |
| refs[k] = fetch(v) or v |
| elseif typ == "table" then |
| retrieve_refs(v) |
| end |
| end |
| return refs |
| end |
| |
| local function retrieve(refs) |
| core.log.info("retrieve secrets refs") |
| |
| local new_refs = core.table.deepcopy(refs) |
| return retrieve_refs(new_refs) |
| end |
| |
| function fetch_secrets(refs, cache, key, version) |
| if not refs or type(refs) ~= "table" then |
| return nil |
| end |
| if not cache then |
| return retrieve(refs) |
| end |
| return secrets_lrucache(key, version, retrieve, refs) |
| end |
| end |
| |
| _M.fetch_secrets = fetch_secrets |
| |
| return _M |