-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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: Add ldap-auth plugin #3894
Changes from all commits
a5d1ab9
2e2ffb0
6cfe4cd
7b01646
979e46b
d46d980
fad0827
d410f56
8a43bc3
6c29529
3946036
b67b69d
47d30e1
0bbc7e4
c178e17
bb2ccfd
cb8cb6f
48870cb
a2ceb94
7d10e1e
558f8a8
66b69a7
078f628
23253f4
33fc909
89f8d6a
15cf6ee
9c87c10
9663476
abe1090
10a4d87
8ab4df8
79b01a6
78032ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,160 @@ | ||||
-- | ||||
-- 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 ngx = ngx | ||||
local ngx_re = require("ngx.re") | ||||
local ipairs = ipairs | ||||
local consumer_mod = require("apisix.consumer") | ||||
local lualdap = require("lualdap") | ||||
|
||||
local lrucache = core.lrucache.new({ | ||||
ttl = 300, count = 512 | ||||
}) | ||||
|
||||
local schema = { | ||||
type = "object", | ||||
title = "work with route or service object", | ||||
properties = { | ||||
base_dn = { type = "string" }, | ||||
ldap_uri = { type = "string" }, | ||||
use_tls = { type = "boolean" }, | ||||
uid = { type = "string" } | ||||
}, | ||||
required = {"base_dn","ldap_uri"}, | ||||
} | ||||
|
||||
local consumer_schema = { | ||||
type = "object", | ||||
title = "work with consumer object", | ||||
properties = { | ||||
user_dn = { type = "string" }, | ||||
}, | ||||
required = {"user_dn"}, | ||||
} | ||||
|
||||
local plugin_name = "ldap-auth" | ||||
|
||||
local _M = { | ||||
version = 0.1, | ||||
priority = 2540, | ||||
type = 'auth', | ||||
name = plugin_name, | ||||
schema = schema, | ||||
consumer_schema = consumer_schema | ||||
} | ||||
|
||||
function _M.check_schema(conf, schema_type) | ||||
local ok, err | ||||
if schema_type == core.schema.TYPE_CONSUMER then | ||||
ok, err = core.schema.check(consumer_schema, conf) | ||||
else | ||||
ok, err = core.schema.check(schema, conf) | ||||
end | ||||
|
||||
return ok, err | ||||
end | ||||
|
||||
local create_consumer_cache | ||||
do | ||||
local consumer_names = {} | ||||
|
||||
function create_consumer_cache(consumers) | ||||
core.table.clear(consumer_names) | ||||
|
||||
for _, consumer in ipairs(consumers.nodes) do | ||||
core.log.info("consumer node: ", core.json.delay_encode(consumer)) | ||||
consumer_names[consumer.auth_conf.user_dn] = consumer | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if two users use the same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A distinguish name (dn) is per definition unique so user cannot share the same dn There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But what if the admin configured the same dn for different consumers accidentally? Is it harmless or there are some side effects? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it's harmeless, is there a way in the consumer definition to identify a unique field ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it.
We have to check it by our own. It seems that the JSON schema cannot help us to do it. Anyway, we may document it. |
||||
end | ||||
|
||||
return consumer_names | ||||
end | ||||
|
||||
end -- do | ||||
|
||||
local function extract_auth_header(authorization) | ||||
local obj = { username = "", password = "" } | ||||
|
||||
local m, err = ngx.re.match(authorization, "Basic\\s(.+)", "jo") | ||||
if err then | ||||
-- error authorization | ||||
return nil, err | ||||
end | ||||
|
||||
local decoded = ngx.decode_base64(m[1]) | ||||
|
||||
if not decoded then | ||||
return nil, "failed to decode authentication header: " .. m[1] | ||||
end | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
|
||||
local res | ||||
res, err = ngx_re.split(decoded, ":") | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to check if decoded is not nil and the split result has two elements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it’s a copy paste from apisix/apisix/plugins/basic-auth.lua Line 83 in 34df010
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feel free to submit another PR to update basic-auth. |
||||
if err then | ||||
return nil, "split authorization err:" .. err | ||||
end | ||||
if #res < 2 then | ||||
return nil, "split authorization err: invalid decoded data: " .. decoded | ||||
end | ||||
|
||||
obj.username = ngx.re.gsub(res[1], "\\s+", "", "jo") | ||||
obj.password = ngx.re.gsub(res[2], "\\s+", "", "jo") | ||||
|
||||
return obj, nil | ||||
end | ||||
|
||||
function _M.rewrite(conf, ctx) | ||||
core.log.info("plugin rewrite phase, conf: ", core.json.delay_encode(conf)) | ||||
|
||||
-- 1. extract authorization from header | ||||
local auth_header = core.request.header(ctx, "Authorization") | ||||
if not auth_header then | ||||
core.response.set_header("WWW-Authenticate", "Basic realm='.'") | ||||
return 401, { message = "Missing authorization in request" } | ||||
end | ||||
|
||||
local user, err = extract_auth_header(auth_header) | ||||
if err then | ||||
return 401, { message = err } | ||||
end | ||||
|
||||
-- 2. try authenticate the user against the ldap server | ||||
local uid = "cn" | ||||
if conf.uid then | ||||
uid = conf.uid | ||||
end | ||||
local userdn = uid .. "=" .. user.username .. "," .. conf.base_dn | ||||
local ld = lualdap.open_simple (conf.ldap_uri, userdn, user.password, conf.use_tls) | ||||
if not ld then | ||||
return 401, { message = "Invalid user authorization" } | ||||
end | ||||
|
||||
-- 3. Retrieve consumer for authorization plugin | ||||
local consumer_conf = consumer_mod.plugin(plugin_name) | ||||
if not consumer_conf then | ||||
return 401, {message = "Missing related consumer"} | ||||
end | ||||
local consumers = lrucache("consumers_key", consumer_conf.conf_version, | ||||
create_consumer_cache, consumer_conf) | ||||
local consumer = consumers[userdn] | ||||
if not consumer then | ||||
return 401, {message = "Invalid API key in request"} | ||||
end | ||||
consumer_mod.attach_consumer(ctx, consumer, consumer_conf) | ||||
|
||||
core.log.info("hit basic-auth access") | ||||
end | ||||
|
||||
return _M |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add the ldap stuff to https://github.com/apache/apisix/blob/master/docs/en/latest/install-dependencies.md