Skip to content

Commit

Permalink
feat: add forward-auth plugin (#6037)
Browse files Browse the repository at this point in the history
Co-authored-by: 罗泽轩 <spacewanderlzx@gmail.com>
  • Loading branch information
bzp2010 and spacewander authored Jan 11, 2022
1 parent eb0bf87 commit 8dbdd1f
Show file tree
Hide file tree
Showing 6 changed files with 531 additions and 1 deletion.
140 changes: 140 additions & 0 deletions apisix/plugins/forward-auth.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
--
-- 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 ipairs = ipairs
local core = require("apisix.core")
local http = require("resty.http")

local schema = {
type = "object",
properties = {
host = {type = "string"},
ssl_verify = {
type = "boolean",
default = true,
},
request_headers = {
type = "array",
default = {},
items = {type = "string"},
description = "client request header that will be sent to the authorization service"
},
upstream_headers = {
type = "array",
default = {},
items = {type = "string"},
description = "authorization response header that will be sent to the upstream"
},
client_headers = {
type = "array",
default = {},
items = {type = "string"},
description = "authorization response header that will be sent to"
.. "the client when authorizing failed"
},
timeout = {
type = "integer",
minimum = 1,
maximum = 60000,
default = 3000,
description = "timeout in milliseconds",
},
keepalive = {type = "boolean", default = true},
keepalive_timeout = {type = "integer", minimum = 1000, default = 60000},
keepalive_pool = {type = "integer", minimum = 1, default = 5},
},
required = {"host"}
}


local _M = {
version = 0.1,
priority = 2002,
name = "forward-auth",
schema = schema,
}


function _M.check_schema(conf)
return core.schema.check(schema, conf)
end


function _M.access(conf, ctx)
local auth_headers = {
["X-Forwarded-Proto"] = core.request.get_scheme(ctx),
["X-Forwarded-Method"] = core.request.get_method(),
["X-Forwarded-Host"] = core.request.get_host(ctx),
["X-Forwarded-Uri"] = ctx.var.request_uri,
["X-Forwarded-For"] = core.request.get_remote_client_ip(ctx),
}

-- append headers that need to be get from the client request header
if #conf.request_headers > 0 then
for _, header in ipairs(conf.request_headers) do
if not auth_headers[header] then
auth_headers[header] = core.request.header(ctx, header)
end
end
end

local params = {
headers = auth_headers,
keepalive = conf.keepalive,
ssl_verify = conf.ssl_verify
}

if conf.keepalive then
params.keepalive_timeout = conf.keepalive_timeout
params.keepalive_pool = conf.keepalive_pool
end

local httpc = http.new()
httpc:set_timeout(conf.timeout)

local res, err = httpc:request_uri(conf.host, params)

-- block by default when authorization service is unavailable
if not res then
core.log.error("failed to process forward auth, err: ", err)
return 403
end

if res.status >= 300 then
local client_headers = {}

if #conf.client_headers > 0 then
for _, header in ipairs(conf.client_headers) do
client_headers[header] = res.headers[header]
end
end

core.response.set_header(client_headers)
return res.status, res.body
end

-- append headers that need to be get from the auth response header
for _, header in ipairs(conf.upstream_headers) do
local header_value = res.headers[header]
if header_value then
core.request.set_header(ctx, header, header_value)
end
end
end


return _M
1 change: 1 addition & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ plugins: # plugin list (sorted by priority)
- jwt-auth # priority: 2510
- key-auth # priority: 2500
- consumer-restriction # priority: 2400
- forward-auth # priority: 2002
- opa # priority: 2001
- authz-keycloak # priority: 2000
#- error-log-logger # priority: 1091
Expand Down
3 changes: 2 additions & 1 deletion docs/en/latest/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"plugins/hmac-auth",
"plugins/authz-casbin",
"plugins/ldap-auth",
"plugins/opa"
"plugins/opa",
"plugins/forward-auth"
]
},
{
Expand Down
139 changes: 139 additions & 0 deletions docs/en/latest/plugins/forward-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: forward-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

- [**Description**](#description)
- [**Attributes**](#attributes)
- [**Data Definition**](#data-definition)
- [**Example**](#example)

## Description

The `forward-auth` plugin implements a classic external authentication model. We can implement a custom error return or user redirection to the authentication page if the authentication fails.

Forward Auth cleverly moves the authentication and authorization logic to a dedicated external service, where the gateway forwards the user's request to the authentication service and blocks the original request, and replaces the result when the authentication service responds with a non-2xx status.

## Attributes

| Name | Type | Requirement | Default | Valid | Description |
| -- | -- | -- | -- | -- | -- |
| host | string | required | | | Authorization service host (eg. https://localhost:9188) |
| ssl_verify | boolean | optional | true | | Whether to verify the certificate |
| request_headers | array[string] | optional | | | `client` request header that will be sent to the `authorization` service. When it is not set, no `client` request headers are sent to the `authorization` service, except for those provided by APISIX (X-Forwarded-XXX). |
| upstream_headers | array[string] | optional | | | `authorization` service response header that will be sent to the `upstream`. When it is not set, will not forward the `authorization` service response header to the `upstream`. |
| client_headers | array[string] | optional | | | `authorization` response header that will be sent to the `client` when authorize failure. When it is not set, will not forward the `authorization` service response header to the `client`. |
| timeout | integer | optional | 3000ms | [1, 60000]ms | Authorization service HTTP call timeout |
| keepalive | boolean | optional | true | | HTTP keepalive |
| keepalive_timeout | integer | optional | 60000ms | [1000, ...]ms | keepalive idle timeout |
| keepalive_pool | integer | optional | 5 | [1, ...]ms | Connection pool limit |

## Data Definition

The request headers in the following list will have APISIX generated and sent to the `authorization` service.

| Scheme | HTTP Method | Host | URI | Source IP |
| -- | -- | -- | -- | -- |
| X-Forwarded-Proto | X-Forwarded-Method | X-Forwarded-Host | X-Forwarded-Uri | X-Forwarded-For |

## Example

First, you need to setup an external authorization service. Here is an example of using Apache APISIX's serverless plugin to mock.

```shell
$ curl -X PUT 'http://127.0.0.1:9080/apisix/admin/routes/auth' \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
-H 'Content-Type: application/json' \
-d '{
"uri": "/auth",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function (conf, ctx) local core = require(\"apisix.core\"); local authorization = core.request.header(ctx, \"Authorization\"); if authorization == \"123\" then core.response.exit(200); elseif authorization == \"321\" then core.response.set_header(\"X-User-ID\", \"i-am-user\"); core.response.exit(200); else core.response.set_header(\"Location\", \"http://example.com/auth\"); core.response.exit(403); end end"
]
}
}
}'
```

Next, we create a route for testing.

```shell
$ curl -X PUT http://127.0.0.1:9080/apisix/admin/routes/1
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'
-d '{
"uri": "/headers",
"plugins": {
"forward-auth": {
"host": "http://127.0.0.1:9080/auth",
"request_headers": ["Authorization"],
"upstream_headers": ["X-User-ID"],
"client_headers": ["Location"]
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
```

We can perform the following three tests.

1. **request_headers** Send Authorization header from `client` to `authorization` service

```shell
$ curl http://127.0.0.1:9080/headers -H 'Authorization: 123'
{
"headers": {
"Authorization": "123",
"Next": "More-headers"
}
}
```

2. **upstream_headers** Send `authorization` service response header to the `upstream`

```shell
$ curl http://127.0.0.1:9080/headers -H 'Authorization: 321'
{
"headers": {
"Authorization": "321",
"X-User-ID": "i-am-user",
"Next": "More-headers"
}
}
```

3. **client_headers** Send `authorization` service response header to `client` when authorizing failed

```shell
$ curl -i http://127.0.0.1:9080/headers
HTTP/1.1 403 Forbidden
Location: http://example.com/auth
```

Finally, you can disable the `forward-auth` plugin by removing it from the route.
1 change: 1 addition & 0 deletions t/admin/plugins.t
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ basic-auth
jwt-auth
key-auth
consumer-restriction
forward-auth
opa
authz-keycloak
proxy-mirror
Expand Down
Loading

0 comments on commit 8dbdd1f

Please sign in to comment.