diff --git a/apisix/plugins/ldap-auth.lua b/apisix/plugins/ldap-auth.lua index 3fce91141119..d155696b6337 100644 --- a/apisix/plugins/ldap-auth.lua +++ b/apisix/plugins/ldap-auth.lua @@ -19,7 +19,7 @@ local ngx = ngx local ngx_re = require("ngx.re") local ipairs = ipairs local consumer_mod = require("apisix.consumer") -local lualdap = require("lualdap") +local ldap = require("resty.ldap") local lrucache = core.lrucache.new({ ttl = 300, count = 512 @@ -31,8 +31,9 @@ local schema = { properties = { base_dn = { type = "string" }, ldap_uri = { type = "string" }, - use_tls = { type = "boolean" }, - uid = { type = "string" } + use_tls = { type = "boolean", default = false }, + tls_verify = { type = "boolean", default = false }, + uid = { type = "string", default = "cn" } }, required = {"base_dn","ldap_uri"}, } @@ -136,11 +137,23 @@ function _M.rewrite(conf, ctx) end -- 2. try authenticate the user against the ldap server - local uid = conf.uid or "cn" - - 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 + local ldap_host, ldap_port = core.utils.parse_addr(conf.ldap_uri) + + local userdn = conf.uid .. "=" .. user.username .. "," .. conf.base_dn + local ldapconf = { + timeout = 10000, + start_tls = false, + ldap_host = ldap_host, + ldap_port = ldap_port or 389, + ldaps = conf.use_tls, + tls_verify = conf.tls_verify, + base_dn = conf.base_dn, + attribute = conf.uid, + keepalive = 60000, + } + local res, err = ldap.ldap_authenticate(user.username, user.password, ldapconf) + if not res then + core.log.warn("ldap-auth failed: ", err) return 401, { message = "Invalid user authorization" } end diff --git a/ci/pod/docker-compose.plugin.yml b/ci/pod/docker-compose.plugin.yml index d0350860096b..226abd7ed204 100644 --- a/ci/pod/docker-compose.plugin.yml +++ b/ci/pod/docker-compose.plugin.yml @@ -126,13 +126,19 @@ services: openldap: image: bitnami/openldap:2.5.8 environment: - LDAP_ADMIN_USERNAME: amdin - LDAP_ADMIN_PASSWORD: adminpassword - LDAP_USERS: user01,user02 - LDAP_PASSWORDS: password1,password2 + - LDAP_ADMIN_USERNAME=amdin + - LDAP_ADMIN_PASSWORD=adminpassword + - LDAP_USERS=user01,user02 + - LDAP_PASSWORDS=password1,password2 + - LDAP_ENABLE_TLS=yes + - LDAP_TLS_CERT_FILE=/certs/localhost_slapd_cert.pem + - LDAP_TLS_KEY_FILE=/certs/localhost_slapd_key.pem + - LDAP_TLS_CA_FILE=/certs/apisix.crt ports: - "1389:1389" - "1636:1636" + volumes: + - ./t/certs:/certs rocketmq_namesrv: diff --git a/docs/en/latest/plugins/ldap-auth.md b/docs/en/latest/plugins/ldap-auth.md index 4bc86401dae1..b9aa130eb1e8 100644 --- a/docs/en/latest/plugins/ldap-auth.md +++ b/docs/en/latest/plugins/ldap-auth.md @@ -33,7 +33,7 @@ The `ldap-auth` Plugin can be used to add LDAP authentication to a Route or a Se This Plugin works with the Consumer object and the consumers of the API can authenticate with an LDAP server using [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). -This Plugin uses [lualdap](https://lualdap.github.io/lualdap/) for connecting with an LDAP server. +This Plugin uses [lua-resty-ldap](https://github.com/api7/lua-resty-ldap) for connecting with an LDAP server. ## Attributes @@ -49,7 +49,8 @@ For Route: |----------|---------|----------|---------|------------------------------------------------------------------------| | base_dn | string | True | | Base dn of the LDAP server. For example, `ou=users,dc=example,dc=org`. | | ldap_uri | string | True | | URI of the LDAP server. | -| use_tls | boolean | False | `true` | If set to `true` uses TLS. | +| use_tls | boolean | False | `false` | If set to `true` uses TLS. | +| tls_verify| boolean | False | `false` | Whether to verify the server certificate when `use_tls` is enabled; If set to `true`, you must set `ssl_trusted_certificate` in `config.yaml`, and make sure the host of `ldap_uri` matches the host in server certificate. | | uid | string | False | `cn` | uid attribute. | ## Enabling the plugin diff --git a/docs/zh/latest/plugins/ldap-auth.md b/docs/zh/latest/plugins/ldap-auth.md index 075e819f6aef..c27af4a6a859 100644 --- a/docs/zh/latest/plugins/ldap-auth.md +++ b/docs/zh/latest/plugins/ldap-auth.md @@ -29,7 +29,7 @@ description: 本篇文档介绍了 Apache APISIX ldap-auth 插件的相关信息 ## 描述 -`ldap-auth` 插件可用于给路由或服务添加 LDAP 身份认证,该插件使用 [lualdap](https://lualdap.github.io/lualdap/) 连接 LDAP 服务器。 +`ldap-auth` 插件可用于给路由或服务添加 LDAP 身份认证,该插件使用 [lua-resty-ldap](https://github.com/api7/lua-resty-ldap) 连接 LDAP 服务器。 该插件需要与 Consumer 一起配合使用,API 的调用方可以使用 [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) 与 LDAP 服务器进行认证。 @@ -47,7 +47,8 @@ Route 端: |----------|---------|----------|---------|------------------------------------------------------------------------| | base_dn | string | 是 | | LDAP 服务器的 dn,例如:`ou=users,dc=example,dc=org`。| | ldap_uri | string | 是 | | LDAP 服务器的 URI。 | -| use_tls | boolean | 否 | true | 如果设置为 `true` 则表示启用 TLS。 | +| use_tls | boolean | 否 | false | 如果设置为 `true` 则表示启用 TLS。 | +| tls_verify| boolean | 否 | false | 是否校验 LDAP 服务器的证书。如果设置为 `true`,你必须设置 `config.yaml` 里面的 `ssl_trusted_certificate`,并且确保 `ldap_uri` 里的 host 和服务器证书中的 host 匹配。 | | uid | string | 否 | cn | UID 属性。 | ## 启用插件 diff --git a/rockspec/apisix-master-0.rockspec b/rockspec/apisix-master-0.rockspec index e012abd7217e..2eb266d2ca31 100644 --- a/rockspec/apisix-master-0.rockspec +++ b/rockspec/apisix-master-0.rockspec @@ -77,7 +77,8 @@ dependencies = { "net-url = 0.9-1", "xml2lua = 1.5-2", "nanoid = 0.1-1", - "lua-resty-mediador = 0.1.2-1" + "lua-resty-mediador = 0.1.2-1", + "lua-resty-ldap = 0.1.0-0" } build = { diff --git a/t/certs/localhost_slapd_cert.pem b/t/certs/localhost_slapd_cert.pem new file mode 100644 index 000000000000..6140ea5f630c --- /dev/null +++ b/t/certs/localhost_slapd_cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIECDCCAnCgAwIBAgIUc40/PofbLcrqu/2MJMEkYfrxB+4wDQYJKoZIhvcNAQEL +BQAwVjELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5nRG9uZzEPMA0GA1UEBwwG +Wmh1SGFpMQ8wDQYDVQQKDAZpcmVzdHkxETAPBgNVBAMMCHRlc3QuY29tMB4XDTIy +MDgwMjA1NDI1OFoXDTIzMDgwMjA1NDI1OFowLjESMBAGA1UEAxMJbG9jYWxob3N0 +MRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCxE5zfta69uPsQVDiV0OwWHDGxTBYNzmp5zsVwOF3bOH+hyB4M ++qFxPEuH84/Ib4GJdLM67qZth1azHudKy/QGPFkoeFUW1JhB9QGyjh/URwxTy05b +Ce5w7Ee1rMV/GWu6fxMfIE3o5U0XuW1IKQFaZVdNuQlvG4VjL59BfnEF+YXb1QDB +kIpvf59q+UuZgit8CrO1dDYeJ/xO3N9v2CS2u6si9/XWgIwayw67tmb7cbTu/srB +C99w97IMP5/Vkeu6fkg2jTuvCRARzMQJ11krDmtGeYum9SSCdyTLxK1u7w33DuhQ +3HE/PfHJj9QV1MKIeruVjEvawJsRiWQG0Ai7AgMBAAGjdjB0MAwGA1UdEwEB/wQC +MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0PAQH/BAUDAwegADAdBgNVHQ4E +FgQUcGOrPCoztq5Z7mjgGtaCkPkmDWowHwYDVR0jBBgwFoAUmbUr1fJgcJdG6ZLx +bYMojlFHG7MwDQYJKoZIhvcNAQELBQADggGBABNOTIiLHNQJfyV20UxcyzZ9xTuc +DuMzEexWJ6S33yJTyp5jni0vFaF9wnT1MOtp+Zizz0hQq0d+GvsmBzjkDdipFqUB +Dt4517l4Z/H4n4FV0jhqQhhzcPRWI5H2MNU0Ezno1iCaKD29Kq61fo2qrU7SNDre +RjnGueTW6u+YLj1ss+UK2rTCRX/Nqqz+MrvIift5Kj4c/8sAD3Zn2aXlH0dXSTcX +DaqNDPQvcdlqNMRSJSthLXYBn40Ro6mH7uA+e4aIVn4jyYvyb8qY5LhQPesTcJZw +IEDmIgFEIh0k1YoGvLD6TkMdKPUG536zH+4iZjKpwGwNQ/dTBgn4+5UOqguiYgXd +MP/eeXSCGLAIjQ4+i1ghv1eAlHuHSQ3Dm75icpAL7VHFdoI7I3wqeE5+IyrUXjX0 +s1bCjIuwGxgoBBTzv25OijmTmMcLYDp04PR5qSwckvsrrxHr+2ujeqS+AGxzZ4Sk +N1JSJL69zUwfCVdE3mR+6OmmDcuVlB3u+grLFQ== +-----END CERTIFICATE----- diff --git a/t/certs/localhost_slapd_key.pem b/t/certs/localhost_slapd_key.pem new file mode 100644 index 000000000000..fa33248c6240 --- /dev/null +++ b/t/certs/localhost_slapd_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAsROc37Wuvbj7EFQ4ldDsFhwxsUwWDc5qec7FcDhd2zh/ocge +DPqhcTxLh/OPyG+BiXSzOu6mbYdWsx7nSsv0BjxZKHhVFtSYQfUBso4f1EcMU8tO +WwnucOxHtazFfxlrun8THyBN6OVNF7ltSCkBWmVXTbkJbxuFYy+fQX5xBfmF29UA +wZCKb3+favlLmYIrfAqztXQ2Hif8Ttzfb9gktrurIvf11oCMGssOu7Zm+3G07v7K +wQvfcPeyDD+f1ZHrun5INo07rwkQEczECddZKw5rRnmLpvUkgncky8Stbu8N9w7o +UNxxPz3xyY/UFdTCiHq7lYxL2sCbEYlkBtAIuwIDAQABAoIBAGDANpaEzlUbHRJu +8fvpixUJkp0s1V/1yHeFYptOMPn2hMYAcWrmBg+4wgwmKAl742sXOFaazpRJvjVg +TT+w8EP39T8HgHZY8lgXZjYJMZrqtvGRw946Lu3EK+o33DD10sazZ98551e48cZk +qjEjNnoNpQXydBUhFGB9RKakT1zTb8e+ZQdsrE+ZzgM9/xVFRx4gsfNbed/5TMHZ +QbwaqPzQRiS9ScRwvZ+TE20cGQ66qZqR6+JCatc8BpXA9Q6ZmTj61MSl6MMzCuOS +yIGm5J+siPkLV/ki+MAHk59G9iEsTjS1T1l4aQn0kTtdMx9oVCPODY6Jdi8jIaU/ +TwGWuQECgYEAxJEg/YKjZGQFhidP64OGi1ochFZxuJFwcZ17DgmZPkiU+vpC8KYl +QpR0r0zN9vqP+71nMMoVJfektXRMP4cy0ebSAbx47X5IfdYUhID+/OAlxbl1O9ah +lGWk90zknVvQKahImtYZqepQEYyetQiDB4gX2bLT+8IIt16ebGC/TyUCgYEA5p3g +Tcj69nxyy4BuGxYuNfTORTCzd9zhURN7325HVBMlhen/f1e+yjV1zth9yLDl5Wyl +99jkVCvy6p83s+1EDKdgOTYrxgD31Y934De/m53U6P/yHeic3z9dIgIAn+qcJqU6 +CL28lXEV8jKLNmlR0crWSjtSBDIpA3BWWN834l8CgYAxgcPnVZHFZROnGBue2391 +dXqdMhBuReMmGl21yWEZOLqdA478gTv9KtrAk/2D6NN+udNVjHALIfYP5XyWu3xn +NVVLLqbeWeH0H4kHXl3aXrHkvLL0ITiM4ZTM3EbwAwHInCO9K5NHIkaMRPhr6/rk +WLh5Efsl+1aqqGAKN8u3KQKBgFDjcUh3RSdtkSo12ujfR8gfHLaCFYDmVZWFev5s +hNJFgPTOlZJJ6Z6tT6wEnWHmQkzNZg1f4v5vB94piHUwtJynnIWUrZfewQ8EKmzX +wPpJSuOK2paI/3UCmZ0TDLsKpEidzZRBUMMuDh+MgO3N1Sf7uFwDIIpeOap+HZtA +eC6LAoGAFaN/0hr3kBCGGUQ0MKSEw1A4jJntR+Enz5+vJ1F/yW7E3SNp5gHz8sF1 +ppt3OZKtZeIoaCapIEr4hRZzzZr2zNHu3tyizscLAdcqKbt2o7OlPK7Z5mhREN8E +F4obLQI+YsAv2aOY2EFTSPq70N2OL45NLsdq3igpKZEIbpUgnwA= +-----END RSA PRIVATE KEY----- diff --git a/t/plugin/ldap-auth.t b/t/plugin/ldap-auth.t index 9ecac330f948..4cf5fa92b9fe 100644 --- a/t/plugin/ldap-auth.t +++ b/t/plugin/ldap-auth.t @@ -202,6 +202,8 @@ Authorization: Basic Zm9vOmZvbwo= --- error_code: 401 --- response_body {"message":"Invalid user authorization"} +--- error_log +The supplied credential is invalid @@ -302,7 +304,7 @@ find consumer user01 ngx.HTTP_GET, nil, [[ -{"title":"work with route or service object","required":["base_dn","ldap_uri"],"properties":{"base_dn":{"type":"string"},"ldap_uri":{"type":"string"},"use_tls":{"type":"boolean"},"disable":{"type":"boolean"},"uid":{"type":"string"}},"type":"object"} +{"title":"work with route or service object","required":["base_dn","ldap_uri"],"properties":{"base_dn":{"type":"string"},"ldap_uri":{"type":"string"},"use_tls":{"type":"boolean"},"tls_verify":{"type":"boolean"},"disable":{"type":"boolean"},"uid":{"type":"string"}},"type":"object"} ]] ) ngx.status = code @@ -338,8 +340,107 @@ find consumer user01 ngx.HTTP_GET, nil, [[ -{"title":"work with route or service object","required":["base_dn","ldap_uri"],"properties":{"base_dn":{"type":"string"},"ldap_uri":{"type":"string"},"use_tls":{"type":"boolean"},"disable":{"type":"boolean"},"uid":{"type":"string"}},"type":"object"} ]] +{"title":"work with route or service object","required":["base_dn","ldap_uri"],"properties":{"base_dn":{"type":"string"},"ldap_uri":{"type":"string"},"use_tls":{"type":"boolean"},"tls_verify":{"type":"boolean"},"disable":{"type":"boolean"},"uid":{"type":"string"}},"type":"object"} ]] ) ngx.status = code } } + + + +=== TEST 17: enable ldap-auth with tls +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "ldap-auth": { + "base_dn": "ou=users,dc=example,dc=org", + "ldap_uri": "localhost:1636", + "uid": "cn", + "use_tls": true + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 18: verify +--- request +GET /hello +--- more_headers +Authorization: Basic dXNlcjAxOnBhc3N3b3JkMQ== +--- response_body +hello world +--- error_log +find consumer user01 + + + +=== TEST 19: enable ldap-auth with tls, verify CA +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "ldap-auth": { + "base_dn": "ou=users,dc=example,dc=org", + "ldap_uri": "localhost:1636", + "uid": "cn", + "use_tls": true, + "tls_verify": true + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 20: verify +--- request +GET /hello +--- more_headers +Authorization: Basic dXNlcjAxOnBhc3N3b3JkMQ== +--- response_body +hello world +--- error_log +find consumer user01