-
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 7 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,171 @@ | ||||||
-- | ||||||
-- 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 consumers_lrucache = core.lrucache.new({ | ||||||
type = "plugin", | ||||||
}) | ||||||
|
||||||
local schema = { | ||||||
type = "object", | ||||||
title = "work with route or service object", | ||||||
properties = { | ||||||
basedn = { type = "string" }, | ||||||
ldapuri = { type = "string" }, | ||||||
usetls = { type = "boolean" }, | ||||||
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. Would you use a snake case for the field name, like 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. Ok changed |
||||||
uid = { type = "string" }, | ||||||
auto_create_consumer = {type ="boolean"} | ||||||
}, | ||||||
required = {"basedn","ldapuri"}, | ||||||
additionalProperties = true, | ||||||
} | ||||||
|
||||||
local consumer_schema = { | ||||||
type = "object", | ||||||
title = "work with consumer object", | ||||||
properties = { | ||||||
userdn = { type = "string" }, | ||||||
}, | ||||||
required = {"userdn"}, | ||||||
additionalProperties = false, | ||||||
} | ||||||
|
||||||
local plugin_name = "ldap-auth" | ||||||
|
||||||
local _M = { | ||||||
version = 0.1, | ||||||
priority = 2520, | ||||||
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 | ||||||
|
||||||
if not ok then | ||||||
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. return 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. Ok added |
||||||
return false, err | ||||||
end | ||||||
|
||||||
return true | ||||||
end | ||||||
|
||||||
local create_consume_cache | ||||||
do | ||||||
local consumer_names = {} | ||||||
|
||||||
function create_consume_cache(consumers) | ||||||
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.
Suggested change
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 thanks I’ll update the PR |
||||||
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.userdn] = consumer | ||||||
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]) | ||||||
|
||||||
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 | ||||||
|
||||||
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 access phase, conf: ", core.json.delay_encode(conf)) | ||||||
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. Should it be "rewrite"? 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. For the name of the function? 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. For the log message. Although the |
||||||
|
||||||
-- 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.basedn | ||||||
local ld = lualdap.open_simple (conf.ldapuri, userdn, user.password, conf.usetls) | ||||||
if not ld then | ||||||
return 401, { message = "Invalid user authorization" } | ||||||
end | ||||||
|
||||||
-- 3. Retreive consumer for authorization plugin | ||||||
if conf.auto_create_consumer then | ||||||
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 avoid creating temporary consumers. The temporary consumer may be broken somewhere else. 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. Ok remove this feature . |
||||||
local tmpCustomer = {consumer_name = userdn, auth_conf = {username = userdn}} | ||||||
local tmpCustomerConf = {conf_version = "1", consumer_name="ldapauth"} | ||||||
consumer_mod.attach_consumer(ctx, tmpCustomer, tmpCustomerConf) | ||||||
else | ||||||
local consumer_conf = consumer_mod.plugin(plugin_name) | ||||||
if not consumer_conf then | ||||||
return 401, {message = "Missing related consumer"} | ||||||
end | ||||||
core.log.error(consumer_conf.conf_version) | ||||||
local consumers = lrucache("consumers_key", consumer_conf.conf_version, | ||||||
create_consume_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) | ||||||
end | ||||||
|
||||||
core.log.info("hit basic-auth access") | ||||||
end | ||||||
|
||||||
return _M |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,152 @@ | ||||
--- | ||||
title: ldap-auth | ||||
--- | ||||
|
||||
<!-- | ||||
# | ||||
# 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. | ||||
# | ||||
--> | ||||
|
||||
## Summary | ||||
|
||||
- [**Name**](#name) | ||||
- [**Attributes**](#attributes) | ||||
- [**How To Enable**](#how-to-enable) | ||||
- [**Test Plugin**](#test-plugin) | ||||
- [**Disable Plugin**](#disable-plugin) | ||||
|
||||
## Name | ||||
|
||||
`ldap-auth` is an authentication plugin that can works with `consumer`. Add Ldap Authentication to a `service` or `route`. | ||||
|
||||
The `consumer` then authenticate against the Ldap server using Basic authentication. | ||||
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. "authenticates"? 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 corrected |
||||
|
||||
For more information on Basic authentication, refer to [Wiki](https://en.wikipedia.org/wiki/Basic_access_authentication) for more information. | ||||
|
||||
This authentication plugin use [lualdap](https://lualdap.github.io/lualdap/) plugin to connect against the ldap server | ||||
|
||||
## Attributes | ||||
|
||||
| Name | Type | Requirement | Default | Valid | Description | | ||||
| -------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||||
| basedn | string | required | | | the base dn of the `ldap` server (example : `ou=users,dc=example,dc=org`) | | ||||
| ldapuri | string | required | | | the uri of the ldap server | | ||||
| usetls | boolean | optional | `true` | | Boolean flag indicating if Transport Layer Security (TLS) should be used. | | ||||
| uid | string | optional | `cn` | | the user's password | | ||||
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. Is it the password? 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. No indeed it's the |
||||
| auto_create_consumer | boolean | optional | `false` | | enable this to automatically create consumer for each authentication , the consumer name will be the full `dn` of the user | | ||||
|
||||
## How To Enable | ||||
|
||||
### 1. set a consumer and config the value of the `ldap-auth` option | ||||
|
||||
```shell | ||||
curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' | ||||
{ | ||||
"username": "foo", | ||||
"plugins": { | ||||
"ldap-auth": { | ||||
"userdn": "cn=user01,ou=users,dc=example,dc=org" | ||||
} | ||||
} | ||||
}' | ||||
``` | ||||
|
||||
you can visit Dashboard `http://127.0.0.1:9080/apisix/dashboard/` and add a Consumer through the web console. | ||||
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. We can remove this part now. The dashboard is not shipped by default. 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. Ok will remove , actually it’s a copy paste from
|
||||
|
||||
then add ldap-auth plugin in the Consumer page | ||||
|
||||
### 2. add a Route or add a Service, and enable the `ldap-auth` plugin | ||||
|
||||
```shell | ||||
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' | ||||
{ | ||||
"methods": ["GET"], | ||||
"uri": "/hello", | ||||
"plugins": { | ||||
"ldap-auth": { | ||||
"basedn": "ou=users,dc=example,dc=org", | ||||
"ldapuri": "172.19.0.1", | ||||
"uid": "cn" | ||||
}, | ||||
}, | ||||
"upstream": { | ||||
"type": "roundrobin", | ||||
"nodes": { | ||||
"127.0.0.1:1980": 1 | ||||
} | ||||
} | ||||
}' | ||||
``` | ||||
|
||||
## Test Plugin | ||||
|
||||
- missing Authorization header | ||||
|
||||
```shell | ||||
$ curl -i http://127.0.0.1:9080/hello | ||||
HTTP/1.1 401 Unauthorized | ||||
... | ||||
{"message":"Missing authorization in request"} | ||||
``` | ||||
|
||||
- user is not exists: | ||||
|
||||
```shell | ||||
$ curl -i -uuser:password1 http://127.0.0.1:9080/hello | ||||
HTTP/1.1 401 Unauthorized | ||||
... | ||||
{"message":"Invalid user key in authorization"} | ||||
``` | ||||
|
||||
- password is invalid: | ||||
|
||||
```shell | ||||
$ curl -i -uuser01:passwordfalse http://127.0.0.1:9080/hello | ||||
HTTP/1.1 401 Unauthorized | ||||
... | ||||
{"message":"Password is error"} | ||||
``` | ||||
|
||||
- success: | ||||
|
||||
```shell | ||||
$ curl -i -uuser01:password1 http://127.0.0.1:9080/hello | ||||
HTTP/1.1 200 OK | ||||
... | ||||
hello, world | ||||
``` | ||||
|
||||
## Disable Plugin | ||||
|
||||
When you want to disable the `ldap-auth` plugin, it is very simple, | ||||
you can delete the corresponding json configuration in the plugin configuration, | ||||
no need to restart the service, it will take effect immediately: | ||||
|
||||
```shell | ||||
$ curl http://127.0.0.1:2379/apisix/admin/routes/1 -X PUT -d value=' | ||||
{ | ||||
"methods": ["GET"], | ||||
"uri": "/hello", | ||||
"plugins": {}, | ||||
"upstream": { | ||||
"type": "roundrobin", | ||||
"nodes": { | ||||
"127.0.0.1:1980": 1 | ||||
} | ||||
} | ||||
}' | ||||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,6 +66,7 @@ dependencies = { | |
"luasec = 0.9-1", | ||
"lua-resty-consul = 0.3-2", | ||
"penlight = 1.9.2-1", | ||
"lualdap = 1.2.3-1", | ||
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. Why not use the latest release: 1.2.6? |
||
} | ||
|
||
build = { | ||
|
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.
Better to put all the require together and use
require("lualdap")
styleThere 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.
Agree, corrected