Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement credential #11601

Merged
merged 12 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions apisix/admin/consumers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
local core = require("apisix.core")
local plugins = require("apisix.admin.plugins")
local resource = require("apisix.admin.resource")
local plugin = require("apisix.plugin")
local pairs = pairs


local function check_conf(username, conf, need_username, schema)
Expand All @@ -36,18 +34,6 @@ local function check_conf(username, conf, need_username, schema)
if not ok then
return nil, {error_msg = "invalid plugins configuration: " .. err}
end

local count_auth_plugin = 0
for name, conf in pairs(conf.plugins) do
local plugin_obj = plugin.get(name)
if plugin_obj.type == 'auth' then
count_auth_plugin = count_auth_plugin + 1
end
end

if count_auth_plugin == 0 then
return nil, {error_msg = "require one auth plugin"}
end
end

if conf.group_id then
Expand Down
74 changes: 74 additions & 0 deletions apisix/admin/credentials.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
--
-- 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 core = require("apisix.core")
local plugins = require("apisix.admin.plugins")
local plugin = require("apisix.plugin")
local resource = require("apisix.admin.resource")
local pairs = pairs

local function check_conf(_id, conf, _need_id, schema)
local ok, err = core.schema.check(schema, conf)
if not ok then
return nil, {error_msg = "invalid configuration: " .. err}
end

if conf.plugins then
ok, err = plugins.check_schema(conf.plugins, core.schema.TYPE_CONSUMER)
if not ok then
return nil, {error_msg = "invalid plugins configuration: " .. err}
end

for name, _ in pairs(conf.plugins) do
local plugin_obj = plugin.get(name)
if not plugin_obj then
return nil, {error_msg = "unknown plugin " .. name}
end
if plugin_obj.type ~= "auth" then
return nil, {error_msg = "only supports auth type plugins in consumer credential"}
end
end
end

return true, nil
end

-- get_credential_etcd_key is used to splice the credential's etcd key (without prefix)
-- from credential_id and sub_path.
-- Parameter credential_id is from the uri or payload; sub_path is in the form of
-- {consumer_name}/credentials or {consumer_name}/credentials/{credential_id}.
-- Only if GET credentials list, credential_id is nil, sub_path is like {consumer_name}/credentials,
-- so return value is /consumers/{consumer_name}/credentials.
-- In the other methods, credential_id is not nil, return value is
-- /consumers/{consumer_name}/credentials/{credential_id}.
local function get_credential_etcd_key(credential_id, _conf, sub_path, _args)
if credential_id then
local uri_segs = core.utils.split_uri(sub_path)
local consumer_name = uri_segs[1]
return "/consumers/" .. consumer_name .. "/credentials/" .. credential_id
end

return "/consumers/" .. sub_path
end

return resource.new({
name = "credentials",
kind = "credential",
schema = core.schema.credential,
checker = check_conf,
get_resource_etcd_key = get_credential_etcd_key,
unsupported_methods = {"post", "patch"}
})
9 changes: 8 additions & 1 deletion apisix/admin/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ local resources = {
services = require("apisix.admin.services"),
upstreams = require("apisix.admin.upstreams"),
consumers = require("apisix.admin.consumers"),
credentials = require("apisix.admin.credentials"),
schema = require("apisix.admin.schema"),
ssls = require("apisix.admin.ssl"),
plugins = require("apisix.admin.plugins"),
Expand Down Expand Up @@ -184,6 +185,12 @@ local function run()
end
end

if seg_res == "consumers" and #uri_segs >= 6 and uri_segs[6] == "credentials" then
seg_sub_path = seg_id .. "/" .. seg_sub_path
seg_res = uri_segs[6]
seg_id = uri_segs[7]
end

local resource = resources[seg_res]
if not resource then
core.response.exit(404, {error_msg = "Unsupported resource type: ".. seg_res})
Expand Down Expand Up @@ -228,7 +235,7 @@ local function run()

if code then
if method == "get" and plugin.enable_data_encryption then
if seg_res == "consumers" then
if seg_res == "consumers" or seg_res == "credentials" then
utils.decrypt_params(plugin.decrypt_conf, data, core.schema.TYPE_CONSUMER)
elseif seg_res == "plugin_metadata" then
utils.decrypt_params(plugin.decrypt_conf, data, core.schema.TYPE_METADATA)
Expand Down
44 changes: 44 additions & 0 deletions apisix/admin/resource.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
local core = require("apisix.core")
local utils = require("apisix.admin.utils")
local apisix_ssl = require("apisix.ssl")
local apisix_consumer = require("apisix.consumer")
local setmetatable = setmetatable
local tostring = tostring
local ipairs = ipairs
Expand Down Expand Up @@ -157,6 +158,12 @@ function _M:get(id, conf, sub_path)
key = key .. "/" .. id
end

-- some resources(consumers) have sub resources(credentials),
-- the key format of sub resources will differ from the main resource
if self.get_resource_etcd_key then
key = self.get_resource_etcd_key(id, conf, sub_path)
end

local res, err = core.etcd.get(key, not id)
if not res then
core.log.error("failed to get ", self.kind, "[", key, "] from etcd: ", err)
Expand All @@ -170,6 +177,12 @@ function _M:get(id, conf, sub_path)
end
end

-- consumers etcd range response will include credentials, so need to filter out them
if self.name == "consumers" and res.body.list then
res.body.list = apisix_consumer.filter_consumers_list(res.body.list)
res.body.total = #res.body.list
end

utils.fix_count(res.body, id)
return res.status, res.body
end
Expand Down Expand Up @@ -249,6 +262,26 @@ function _M:put(id, conf, sub_path, args)

key = key .. "/" .. id

if self.get_resource_etcd_key then
key = self.get_resource_etcd_key(id, conf, sub_path, args)
end

if self.name == "credentials" then
local consumer_key = apisix_consumer.get_consumer_key_from_credential_key(key)
local res, err = core.etcd.get(consumer_key, false)
if not res then
return 503, {error_msg = err}
end
if res.status == 404 then
return res.status, {error_msg = "consumer not found"}
end
if res.status ~= 200 then
core.log.debug("failed to get consumer for the credential, credential key: ", key,
", consumer key: ", consumer_key, ", res.status: ", res.status)
return res.status, {error_msg = "failed to get the consumer"}
end
end

if self.name ~= "plugin_metadata" then
local ok, err = utils.inject_conf_with_prev_conf(self.kind, key, conf)
if not ok then
Expand Down Expand Up @@ -296,13 +329,24 @@ function _M:delete(id, conf, sub_path, uri_args)

key = key .. "/" .. id

if self.get_resource_etcd_key then
key = self.get_resource_etcd_key(id, conf, sub_path, uri_args)
end

if self.delete_checker and uri_args.force ~= "true" then
local code, err = self.delete_checker(id)
if err then
return code, err
end
end

if self.name == "consumers" then
local res, err = core.etcd.rmdir(key .. "/credentials/")
if not res then
return 503, {error_msg = err}
end
end

local res, err = core.etcd.delete(key)
if not res then
core.log.error("failed to delete ", self.kind, "[", key, "] in etcd: ", err)
Expand Down
Loading
Loading