Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
SBlechmann committed Mar 29, 2023
2 parents 691c303 + 82c0c27 commit 324be36
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 112 deletions.
12 changes: 10 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM kong:latest

LABEL description="Alpine + Kong + kong-oidc plugin + LUA Plugins"
# Install the js-pluginserver

USER root
# RUN apk add --update nodejs npm python3 make g++ && rm -rf /var/cache/apk/*
# RUN npm install --unsafe -g [email protected]
Expand All @@ -12,9 +12,13 @@ RUN apk add --update vim nano
RUN apk update && apk add curl git gcc musl-dev
RUN luarocks install luaossl OPENSSL_DIR=/usr/local/kong CRYPTO_DIR=/usr/local/kong
RUN luarocks install --pin lua-resty-jwt
RUN luarocks install kong-oidc
# RUN luarocks install kong-oidc -- deprecated
RUN luarocks install lunajson

COPY ./luaplugins/oidc /plugins/oidc
WORKDIR /plugins/oidc
RUN luarocks make

COPY ./luaplugins/query-checker /plugins/query-checker
WORKDIR /plugins/query-checker
RUN luarocks make
Expand All @@ -27,4 +31,8 @@ COPY ./luaplugins/rbac /plugins/rbac
WORKDIR /plugins/rbac
RUN luarocks make

COPY ./luaplugins/scope-checker /plugins/scope-checker
WORKDIR /plugins/scope-checker
RUN luarocks make

USER kong
100 changes: 93 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

This repo shows how to protect your APIs using the Kong API Gateway working as PEP proxy with a Keycloak integration for authentication and authorization of incoming requests.

- [1. Install](#1-install)
- [2. Setup](#2-setup)
- [3. Kong/Konga Configurations](#3-kongkonga-configurations)
- [3.1 Creation of a Service](#31-creation-of-a-service)
- [3.2 Creation of Routes](#32-creation-of-routes)
- [3.3 Keycloak configs for Kong](#33-keycloak-configs-for-kong)
- [3.4 Configure custom plugins](#34-configure-custom-plugins)
- [3.4.1 Configuring Milti-Tenancy Plugin](#341-configuring-milti-tenancy-plugin)
- [3.4.2 Configuring RBAC Plugin](#342-configuring-rbac-plugin)
- [3.4.3 Configuring scope-checker Plugin](#343-configuring-scope-checker-plugin)
- [3.4.4 Configuring Query-Checker plugin](#344-configuring-query-checker-plugin)
- [4. Keycloak Configuration](#4-keycloak-configurations)
- [Testing](#testing)
- [Docs](#docs)


## 1. Install

The source code of all plugins must be in the respective folder, meaning:
Expand Down Expand Up @@ -51,9 +67,8 @@ Creating new routes for a given service is fairly simple. Just like creating a n
3. Click on 'Other' and then select 'Add Plugin' option on the oidc plugin
Configure the oidc plugins as shown in the image below with the configurations as per your client.

![Keycloak](img/keycloak.png)
![Keycloak](img/oidc.png)

For client creation in Keycloak, please refer to [this guide](kongkeycloak.pdf).

### 3.4 Configure custom plugins

Expand All @@ -63,15 +78,22 @@ To add any plugin, we follow the same procedure as done before with the OIDC plu

#### 3.4.1 Configuring Milti-Tenancy Plugin

You can add the plugin and global, service or at route level. Select the plugin in the 'Add Plugin' section.
You can add the plugin at global, service or at route level. Select the plugin in the 'Add Plugin' section.

`tenant name` (required) field defines the custom header name. It can be renamed as per your custom requirement. This field will be checked against the token presented in the request.

![MultiTenancy](img/multi-tenancy.png)

Ex: As shown in the image above, we have set the tenant name to `fiware-service`. In the incoming request to kong, it is mandatory to have a request header with name "fiware-service" and the value set in keycloak for the given user.

*Note*: See section [4.5](#4-keycloak-configurations) for more details to configure keycloak.

![postman-multi-tenancy](img/postman-multi-tenancy.png)


#### 3.4.2 Configuring RBAC Plugin

Similar to the multi-tenancy plugin, this plugin can be used at global, service or routes level.
When `use custom roles` is disabled, the plugin expects the role in the form *tenantname_role*. Ex: if the tenant name is set to *fiware-service:app*, then the accepted roles are `app_read`, `app_write` and `app_admin`.

- `tenant name`(required)(String) field defines the custom header name. It can be renamed as per your custom requirement.

Expand All @@ -82,19 +104,83 @@ Similar to the multi-tenancy plugin, this plugin can be used at global, service
- `admin role` (depends on 'use custom roles')(String) Pre-defined role needed for DELETE requests.
- `include client role` (optional)(Bool) field indicated whether to use client roles or user roles configured in Keycloak.
- `client name` (depends on include client role)(String) field is used to specify the name of the client.


*Note*: See section [4.6](#4-keycloak-configurations) for more details to configure keycloak.
![RBAC](img/rbac.png)

#### 3.4.3 Configuring scope-checker Plugin

*Note*: This plugin is intended for future use with release of oriod-ld.
With this plugin its possible to validate the scopes sent as headers by the client against the permission given to the client in Keycloak. Make sure the headers and token attributes are set with the name as `scopes`

- `plus allowed` (Boolean) field used to decide plus(+) wildcard entries in scope
Ex: /fiware/+/temparature allows /fiware/kitchen/temparature, /fiware/room1/temparature ..
- `hash allowed` (Boolean) field used to decide hash(#) wildcard entries in scope
Ex: /fiware/kitchen/# allows /fiware/kitchen/temparature , /fiware/kitchen/pressure ..

![scope-checker](img/scope-checker.png)

#### 3.4.4 Configuring Query-Checker plugin
This plugin is used to authorize request path with query

- `path` (String) speicfy the full path that has to checked.
Ex: /v2/entities/urn:ngsi-ld:Product:010?type=Product
- `wildcard allowed` (Boolean) field used to include * wildcard as shown in fig

![query-checker](img/query-checker.png)

## 4. Keycloak Configurations

1. Create a new Realm if needed.
1. Select the pop-up with `Add realm` option under *Master* on the left top of Keycloak admin page.
2. Set the name of the realm, Ex: *kong* and click `create`
2. Kong client :
1. Create new client with client id `kong`
2. Under client settings, set access type to `confidential`
3. Set service accounts `enabled`
4. Set valid redirect uris `*`
5. Click on *Credentials*, use the Secret to configure kong in konga as mentioned in section [3.3](#33-keycloak-configs-for-kong)

![kong-keycloak](img/kong-keycloak.png)
3. App Client:
1. Create new client with client id `app`
2. Under client settings, set access type to `public` or `confidential`
3. Set valid redirect uris `app`

![app-keycloak](img/app-keycloak.png)
4. Create a user in keycloak to get token using client id `app`
5. To support *multi-tenancy* plugin, it is necessary to have tenant name in token:
1. Click on *Clients* on the left navigation bar
2. Click *Mappers* under Clients and *create*
3. Set the *Name,User Attribute and Token Claim Name* as to *tenant name*, Ex: `fiware-service` and save
4. Use the other follwing settings as shown in the image below.
![fiware-mapper](img/fiware-mapper.png)
5. Now go to *Users* in the left navigation bar,*view all users* and select the user.
6. Click on *Attributes* and configure for fiware service as shown below.
![user-attribute](img/user-attribute.png)
7. The attributes can be multivalued with format *attr1##attr2* Ex: test1##test2
6. To support *RBAC* plugin, it is necessary to configure roles in keycloak:
1. Go to *Roles* in the left navigation bar, select *Add Role*.
2. Set the name in the form `tenantname_role`, ex: `test1_read` and save.
3. Go to Users and select the user to set roles.
4. Select *Role Mapping* under given user and select the roles to be added under *Available Roles* and click *Add selected*.
![user-roles](img/user-roles.png)
5. You can also create `client roles` under *Clients* and then select *Roles*, create new role using *Add Role*
6. Now under a given user, select *Role Mapping*, choose the client under *Client Roles* and add avaiable roles


## Testing

After following the above steps, you can test the funcionalities of kong and its plugins using any rest client, e.g. postman or insomnia.

- Get a token from keycloak after creating a user.
- Get a token from keycloak after creating a user.
- Use the Authorization header to fill the above recieved token in the format 'Bearer <*token*>'
- Add fiware-service header in the request and make sure it is put in the user token.
- Add necessary headers depending on the plugins in the request and make sure it is put in the user token.
- Send the request and verify.

*Ex*: To use postman for testing, please check the image below to configure token.
![token-postman](img/token-postman.png)


## Docs

Expand Down
2 changes: 1 addition & 1 deletion config/kong.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ services:
- name: example-service
url: http://localhost:1026/v2/entites
plugins:
- name: multi-tenancy,query-checker,rbac
- name: multi-tenancy, rbac, scope-checker, query-checker, oidc
3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ services:
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
# Allow plugins to be used. The plugin name is your JS file name e.g. hello.js
KONG_PLUGINS: bundled,oidc,multi-tenancy,query-checker,rbac
KONG_PLUGINS: bundled,multi-tenancy, rbac, scope-checker, query-checker
KONG_PROXY_LISTEN: 0.0.0.0:8000, 0.0.0.0:8443 ssl
KONG_ADMIN_LISTEN: 0.0.0.0:8001, 0.0.0.0:8444 ssl
ports:
Expand Down
50 changes: 32 additions & 18 deletions luaplugins/multi-tenancy/plugins/multi-tenancy/filter.lua
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
local M = {}

function split_token (inputstr, sep)
function split_token(inputstr, sep)
if sep == nil then
sep = "%s"
sep = "%s"
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
local t = {}
for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
table.insert(t, str)
end
return t
end

-- decoding
function M.decode(token)
b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
token_payload = split_token(token, ".")
data = token_payload[2]
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
data = string.gsub(data, "[^" .. b .. "=]", "")
return (data:gsub(
".",
function(x)
if (x == "=") then
return ""
end
local r, f = "", (b:find(x) - 1)
for i = 6, 1, -1 do
r = r .. (f % 2 ^ i - f % 2 ^ (i - 1) > 0 and "1" or "0")
end
return r
end
):gsub(
"%d%d%d?%d?%d?%d?%d?%d?",
function(x)
if (#x ~= 8) then
return ""
end
local c = 0
for i = 1, 8 do
c = c + (x:sub(i, i) == "1" and 2 ^ (8 - i) or 0)
end
return string.char(c)
end))
end
))
end
-- end of copy

return M
return M
33 changes: 19 additions & 14 deletions luaplugins/multi-tenancy/plugins/multi-tenancy/handler.lua
Original file line number Diff line number Diff line change
@@ -1,48 +1,53 @@
local BasePlugin = require "kong.plugins.base_plugin"
-- Baseplugin deprecated in version 3.x.x
-- local BasePlugin = require "kong.plugins.base_plugin"
-- local MultiTenancyHandler = BasePlugin:extend()

local MultiTenancyHandler = {
VERSION = "0.0.2",
PRIORITY = 10
}

local filter = require("kong.plugins.multi-tenancy.filter")
local kong = kong
local lunajson = require "lunajson"

local MultiTenancyHandler = BasePlugin:extend()

function MultiTenancyHandler:new()
MultiTenancyHandler.super.new(self, "multi-tenancy")
end

local function check_tenant(conf)
local token = kong.request.get_header("Authorization")
local tenant_name = conf.tenant_name
kong.log.debug(" ##### Tenant name " , tenant_name)
kong.log.debug(" ##### Tenant name ", tenant_name)
local tenant_header = kong.request.get_header(tenant_name)
if token == nil or tenant_header == nil then
kong.log.err("Cannot process Headers: ", err)
return nil, { status = 403, message = "Headers missing !!" }
return nil, {status = 403, message = "Headers missing !!"}
end
local token_decoded = filter.decode(token)
local jsonparse = lunajson.decode( token_decoded )
local jsonparse = lunajson.decode(token_decoded)
if jsonparse[tenant_name] == nil then
kong.log.err("fiware-service missing in token")
return nil, { status = 403, message = "fiware-service missing in token" }
else
return nil, {status = 403, message = "fiware-service missing in token"}
else
arraylength = #jsonparse[tenant_name]
for a = 1, arraylength do
for a = 1, arraylength do
if jsonparse[tenant_name][a] == tenant_header then
return true
end
end
end
return false
end

end

function MultiTenancyHandler:access(conf)
MultiTenancyHandler.super.access(self)
-- MultiTenancyHandler.super.access(self)
local ok, err = check_tenant(conf)
if not ok then
return kong.response.error(403, "Permission Denied !")
else
else
return
end
end

return MultiTenancyHandler
return MultiTenancyHandler
17 changes: 14 additions & 3 deletions luaplugins/multi-tenancy/plugins/multi-tenancy/schema.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
local typedefs = require "kong.db.schema.typedefs"

return {
no_consumer = true,
name = "multi-tenancy",
fields = {
tenant_name = { type = "string", required = true , default = "fiware-service" }
{
config = {
type = "record",
fields = {
{
tenant_name = {type = "string", required = true, default = "fiware-service"}
}
}
}
}
}
}
}
Loading

0 comments on commit 324be36

Please sign in to comment.