blob: 6ba02768db805ed25fac9da974c254ddea3ed601 [file] [log] [blame]
--
-- 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