Skip to content

Commit 8115054

Browse files
committed
LITE-31768 LITE-31797 Initial implementation
1 parent 8401529 commit 8115054

11 files changed

+242
-0
lines changed

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM kong:latest
2+
3+
ADD . /opt/kong-to-waf
4+
5+
USER root
6+
7+
RUN cd /opt/kong-to-waf && \
8+
luarocks build && \
9+
cd && rm -rf /opt/kong-to-waf
10+
11+
USER kong

docker-compose.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
volumes:
2+
kong_data: {}
3+
4+
networks:
5+
kong-net:
6+
external: false
7+
8+
services:
9+
kong:
10+
container_name: kong-test
11+
build: .
12+
user: 'root'
13+
platform: linux/amd64
14+
environment:
15+
KONG_DECLARATIVE_CONFIG: /opt/conf/kong.yml
16+
KONG_ADMIN_ACCESS_LOG: /dev/stdout
17+
KONG_ADMIN_ERROR_LOG: /dev/stderr
18+
KONG_ADMIN_LISTEN: '0.0.0.0:8001'
19+
KONG_DATABASE: 'off'
20+
KONG_PROXY_ACCESS_LOG: /dev/stdout
21+
KONG_PROXY_ERROR_LOG: /dev/stderr
22+
KONG_PLUGINS: kong-to-waf
23+
networks:
24+
- kong-net
25+
ports:
26+
- "8000:8000/tcp"
27+
- "127.0.0.1:8001:8001/tcp"
28+
- "8443:8443/tcp"
29+
- "127.0.0.1:8444:8444/tcp"
30+
healthcheck:
31+
test: ["CMD", "kong", "health"]
32+
interval: 10s
33+
timeout: 10s
34+
retries: 10
35+
restart: on-failure
36+
depends_on:
37+
- waf
38+
volumes:
39+
- .:/opt/conf/
40+
command: sh /opt/conf/run.sh
41+
42+
waf:
43+
container_name: waf-mock
44+
build:
45+
context: ./tests/integration
46+
dockerfile: waf-mock.Dockerfile
47+
networks:
48+
- kong-net
49+
expose:
50+
- "80"
51+
volumes:
52+
- ./tests/integration:/app
53+
command: uvicorn waf-mock:app --port=80 --host=0.0.0.0 --reload

kong-to-waf-1.0.0-1.rockspec

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package = "kong-to-waf"
2+
3+
version = "1.0.0-1"
4+
5+
supported_platforms = {"linux", "macosx"}
6+
7+
source = {
8+
url = "git://github.com/cloudblue/kong-to-waf",
9+
tag = "1.0.0"
10+
}
11+
12+
description = {
13+
summary = "Kong Gateway plugin for WAF offloading",
14+
license = "Apache License, Version 2.0",
15+
maintainer = "CloudBlue Connect <together@cloudblue.com>"
16+
}
17+
18+
dependencies = {
19+
}
20+
21+
build = {
22+
type = "builtin",
23+
modules = {
24+
["kong.plugins.kong-to-waf.access"] = "src/access.lua",
25+
["kong.plugins.kong-to-waf.handler"] = "src/handler.lua",
26+
["kong.plugins.kong-to-waf.schema"] = "src/schema.lua",
27+
}
28+
}

kong.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
_format_version: "1.1"
2+
3+
services:
4+
- name: upstream
5+
url: https://example.com
6+
plugins:
7+
- name: kong-to-waf
8+
config:
9+
waf_host: waf-mock
10+
routes:
11+
- name: route
12+
paths:
13+
- /

run.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cd /opt/conf/
2+
luarocks make --local
3+
kong start

src/access.lua

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
local http = require "resty.http"
2+
3+
local _M = {}
4+
5+
local function get_path()
6+
if kong.request.get_raw_path ~= nil then
7+
return kong.request.get_raw_path()
8+
else
9+
return kong.request.get_path()
10+
end
11+
end
12+
13+
local function call_waf(config)
14+
local ok, err
15+
local host = config.waf_host
16+
local port = tonumber(config.waf_port)
17+
local path = config.waf_uri or ""
18+
19+
local httpc = http.new()
20+
httpc:set_timeout(config.waf_timeout)
21+
ok, err = httpc:connect(host, port, {pool_size = config.waf_conn_pool_size})
22+
if not ok then
23+
kong.log.err('Failed to connect to WAF service')
24+
return
25+
end
26+
27+
local headers = kong.request.get_headers()
28+
headers['X-Forwarded-Host'] = kong.request.get_host()
29+
headers['X-Forwarded-Method'] = kong.request.get_method()
30+
headers['X-Forwarded-Path'] = get_path()
31+
headers['X-Forwarded-For'] = kong.client.get_forwarded_ip()
32+
33+
local res
34+
res, err = httpc:request({
35+
method = config.waf_method,
36+
path = path .. kong.request.get_path_with_query(),
37+
headers = headers
38+
})
39+
40+
if not res then
41+
kong.log.err('Failed to get response from WAF service')
42+
return
43+
end
44+
45+
res:read_body()
46+
httpc:set_keepalive(config.waf_keepalive)
47+
48+
if res.status >= 400 then
49+
kong.log.warn('WAF service responded with ' .. tostring(res.status) .. ': ' .. path)
50+
if config.mode == 'full' then
51+
return kong.response.exit(403)
52+
end
53+
end
54+
end
55+
56+
function _M.execute(config)
57+
local mode = config.mode
58+
if mode == 'disabled' then
59+
return
60+
end
61+
62+
call_waf(config)
63+
end
64+
65+
return _M

src/handler.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
local access = require "kong.plugins.kong-to-waf.access"
2+
3+
4+
local KongToWAF = {
5+
VERSION = "1.0.0",
6+
PRIORITY = 1090
7+
}
8+
9+
function KongToWAF:access(config)
10+
access.execute(config)
11+
end
12+
13+
return KongToWAF

src/schema.lua

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
local typedefs = require "kong.db.schema.typedefs"
2+
3+
return {
4+
name = "kong-to-waf",
5+
fields = {
6+
{ consumer = typedefs.no_consumer },
7+
{ config = {
8+
type = "record",
9+
fields = {
10+
{ mode = { type = "string", default = "full", one_of = { "disabled", "only-log", "full" } } },
11+
{ waf_method = { type = "string", default = "GET", one_of = { "GET" } } },
12+
{ waf_proto = { type = "string", default = "http", one_of = { "http" } } },
13+
-- TODO: Add support for https
14+
{ waf_host = { type = "string", default = "localhost" } },
15+
{ waf_port = { type = "number", default = 80 } },
16+
{ waf_uri = { type = "string", required = false } },
17+
{ waf_timeout = { type = "number", default = 10000 } },
18+
{ waf_keepalive = { type = "number", default = 60000 } },
19+
{ waf_conn_pool_size = { type = "number", default = 6 } },
20+
-- TODO: Add track ID
21+
},
22+
},
23+
},
24+
},
25+
}

tests/integration/waf-mock-reqs.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fastapi
2+
uvicorn

tests/integration/waf-mock.Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM python:latest
2+
3+
COPY waf-mock-reqs.txt r.txt
4+
RUN pip install -r r.txt
5+
6+
ADD . /app
7+
WORKDIR /app

tests/integration/waf-mock.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import Optional
2+
3+
from fastapi import FastAPI, Request, Response, status
4+
5+
6+
app = FastAPI()
7+
8+
9+
@app.get('/{full_path:path}')
10+
async def mock(
11+
request: Request,
12+
response: Response,
13+
full_path: Optional[str] = '',
14+
):
15+
print(request.headers)
16+
17+
response.status_code = status.HTTP_200_OK
18+
19+
if '.exe' in full_path:
20+
response.status_code = status.HTTP_403_FORBIDDEN
21+
22+
return response.status_code

0 commit comments

Comments
 (0)