Skip to content

Commit adb6a63

Browse files
committed
Added OIDC tutorial to show how authorization and authentication can be implemented
1 parent 8bb43f7 commit adb6a63

File tree

13 files changed

+2301
-11
lines changed

13 files changed

+2301
-11
lines changed

Diff for: README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The official docker image is available on docker hub at [lukasz/migrator](https:
4141
* [Deploying migrator to AWS EKS](#deploying-migrator-to-aws-eks)
4242
* [Deploying migrator to Azure AKS](#deploying-migrator-to-azure-aks)
4343
* [Securing migrator with OAuth2](#securing-migrator-with-oauth2)
44+
* [Securing migrator with OIDC](#securing-migrator-with-oidc)
4445
* [Performance](#performance)
4546
* [Change log](#change-log)
4647
* [Contributing, code style, running unit & integration tests](#contributing-code-style-running-unit--integration-tests)
@@ -669,10 +670,16 @@ You can find it in [tutorials/azure-aks](tutorials/azure-aks).
669670

670671
## Securing migrator with OAuth2
671672

672-
The goal of this tutorial is to secure migrator with OAuth2. It shows how to deploy oauth2-proxy in front of migrator which will off-load and transparently handle authentication for migrator end-users.
673+
The goal of this tutorial is to secure migrator with OAuth2. It shows how to deploy oauth2-proxy in front of migrator which will off-load and transparently handle authorization for migrator end-users.
673674

674675
You can find it in [tutorials/oauth2-proxy](tutorials/oauth2-proxy).
675676

677+
## Securing migrator with OIDC
678+
679+
The goal of this tutorial is to secure migrator with OAuth2 and OIDC. It shows how to deploy oauth2-proxy and haproxy in front of migrator which will off-load and transparently handle both authorization (oauth2-proxy) and authentication (haproxy with custom lua script) for migrator end-users.
680+
681+
You can find it in [tutorials/oauth2-proxy-oidc-haproxy](tutorials/oauth2-proxy-oidc-haproxy).
682+
676683
# Performance
677684

678685
As a benchmarks I used 2 migrations frameworks:

Diff for: tutorials/oauth2-proxy-oidc-haproxy/README.md

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Securing migrator with OIDC
2+
3+
In this tutorial I will show you how to use OIDC on top of OAuth2 to implement authentication and authorization.
4+
5+
If you are interested in simple OAuth2 authorization see [Securing migrator with OAuth2](../tutorials/oauth2-proxy).
6+
7+
## OIDC
8+
9+
OpenID Connect (OIDC) is an authentication layer on top of OAuth2 authorization framework.
10+
11+
I will use oauth2-proxy project. It supports multiple OAuth2 providers. To name a few: Google, Facebook, GitHub, LinkedIn, Azure, Keycloak, login.gov, or any OpenID Connect compatible provider.
12+
13+
As an OIDC provider I will re-use oauth2-proxy local-environment which creates and setups a ready-to-use Keycloak server. I extended the Keycloak server with additional configuration (client mappers to include roles in responses), created two migrator roles and two additional test accounts.
14+
15+
I also put haproxy between oauth2-proxy and migrator. haproxy will validate the JWT access token and implement access control based on user's roles to allow or deny access to underlying migrator resources. I re-used a great lua script written by haproxytech folks which I modified to work with Keycloak realm roles.
16+
17+
To learn more about oauth2-proxy visit https://github.com/oauth2-proxy/oauth2-proxy.
18+
19+
To learn more about Keycloak visit https://www.keycloak.org.
20+
21+
To learn more about haproxy jwtverify lua script visit https://github.com/haproxytech/haproxy-lua-jwt.
22+
23+
## Docker setup
24+
25+
The provided `docker-compose.yaml` provision the following services:
26+
27+
* keycloak - the Identity and Access Management service, available at: http://keycloak.localtest.me:9080
28+
* oauth2-proxy - proxy that protects migrator and connects to keycloak for OAuth2/OIDC authentication, available at: http://gateway.localtest.me:4180
29+
* haproxy - proxy that contains JWT access token validation and user access control logic, available at: http://haproxy.localtest.me:8080
30+
* migrator - deployed internally and accessible only from haproxy and only by authorized users
31+
32+
> Note: above setup doesn't have a database as this is to only illustrate how to setup OIDC
33+
34+
To build the test environment execute:
35+
36+
```
37+
docker-compose up -d
38+
```
39+
40+
## Testing OIDC
41+
42+
I created 2 test users in Keycloak:
43+
44+
* `[email protected]` - migrator admin, has the following roles: `migrator_admin` and `migrator_user`
45+
* `[email protected]` - migrator user, has one migrator role: `migrator_user`
46+
47+
In haproxy.cfg I implemented the following sample rules:
48+
49+
* all requests starting `/v1` will return 403 Forbidden
50+
* to access `/v2/service` user must have `migrator_user` role
51+
* to access `/v2/config` user must have `migrator_admin` role
52+
53+
### Test scenarios
54+
55+
There are two ways to access migrator:
56+
57+
1. getting JWT access token via oauth2-proxy - shown in orange
58+
1. getting JWT access token directly from Keycloak - shown in blue
59+
60+
![migrator OIDC setup](migrator-oidc.png?raw=true)
61+
62+
Let's test them.
63+
64+
### oauth2-proxy - [email protected]
65+
66+
1. Access http://gateway.localtest.me:4180/
67+
1. Authenticate using username: `[email protected]` and password: `password`.
68+
1. After a successful login you will see `/` response
69+
1. Open http://gateway.localtest.me:4180/v2/config and you will see 403 Forbidden - this user doesn't have `migrator_admin` role
70+
1. Logout from Keycloak http://keycloak.localtest.me:9080/auth/realms/master/protocol/openid-connect/logout
71+
1. Invalidate session on auth2-proxy (oauth2-proxy cookie expires in 15 minutes) http://gateway.localtest.me:4180/oauth2/sign_out
72+
73+
### oauth2-proxy - [email protected]
74+
75+
1. Access http://gateway.localtest.me:4180/
76+
1. Authenticate using username: `[email protected]` and password: `password`.
77+
1. After a successful login you will see successful `/` response
78+
1. Open http://gateway.localtest.me:4180/v2/config and now you will see migrator config
79+
1. Logout from Keycloak http://keycloak.localtest.me:9080/auth/realms/master/protocol/openid-connect/logout
80+
1. Invalidate session on auth2-proxy (oauth2-proxy cookie expires in 15 minutes) http://gateway.localtest.me:4180/oauth2/sign_out
81+
82+
### Keycloak REST API
83+
84+
1. Get JWT access token for the `[email protected]` user:
85+
86+
```
87+
access_token=$(curl -s http://keycloak.localtest.me:9080/auth/realms/master/protocol/openid-connect/token \
88+
-H 'Content-Type: application/x-www-form-urlencoded' \
89+
90+
-d 'password=password' \
91+
-d 'grant_type=password' \
92+
-d 'client_id=oauth2-proxy' \
93+
-d 'client_secret=72341b6d-7065-4518-a0e4-50ee15025608' | jq -r '.access_token')
94+
```
95+
96+
2. Execute migrator action and pass the JWT access token in HTTP Authorization header:
97+
98+
```
99+
curl http://haproxy.localtest.me:8080/v2/config \
100+
-H "Authorization: Bearer $access_token"
101+
```
102+
103+
## Miscellaneous
104+
105+
You can copy JWT access token (haproxy log or Keycloak REST API) and decode it on https://jwt.io.
106+
107+
You can verify the signature of the JWT token by providing the public key (`keycloak.pem` available in haproxy folder).
108+
109+
Public key can be also fetched from:
110+
111+
```
112+
curl http://keycloak.localtest.me:9080/auth/realms/master/
113+
```
114+
115+
> The response is a JSON and the public key is returned as a string. To be a valid PEM format you need to add `-----BEGIN PUBLIC KEY-----` header, `-----END PUBLIC KEY-----` footer, and break that string into lines of 64 characters. Compare `keycloak.pem` with the above response.
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
version: '3.0'
2+
services:
3+
4+
oauth2-proxy:
5+
image: quay.io/oauth2-proxy/oauth2-proxy:v6.1.1
6+
command: --config /oauth2-proxy.cfg
7+
hostname: gateway
8+
volumes:
9+
- "./oauth2-proxy.cfg:/oauth2-proxy.cfg"
10+
# oauth2-proxy dies when not able to connect to keycloak
11+
restart: unless-stopped
12+
networks:
13+
keycloak: {}
14+
haproxy: {}
15+
oauth2-proxy: {}
16+
depends_on:
17+
- haproxy
18+
- keycloak
19+
ports:
20+
- 4180:4180
21+
22+
haproxy:
23+
build: haproxy
24+
networks:
25+
migrator: {}
26+
haproxy: {}
27+
depends_on:
28+
- migrator
29+
ports:
30+
- 8080:8080
31+
32+
migrator:
33+
image: lukasz/migrator:latest
34+
hostname: migrator
35+
volumes:
36+
- "./migrator.yaml:/data/migrator.yaml"
37+
networks:
38+
migrator: {}
39+
40+
keycloak:
41+
image: jboss/keycloak:11.0.2
42+
hostname: keycloak
43+
command:
44+
[
45+
'-b',
46+
'0.0.0.0',
47+
'-Djboss.socket.binding.port-offset=1000',
48+
'-Dkeycloak.migration.action=import',
49+
'-Dkeycloak.migration.provider=dir',
50+
'-Dkeycloak.migration.dir=/realm-config',
51+
'-Dkeycloak.migration.strategy=IGNORE_EXISTING',
52+
]
53+
volumes:
54+
- ./keycloak:/realm-config
55+
networks:
56+
keycloak:
57+
aliases:
58+
- keycloak.localtest.me
59+
ports:
60+
- 9080:9080
61+
62+
networks:
63+
migrator: {}
64+
keycloak: {}
65+
oauth2-proxy: {}
66+
haproxy: {}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM haproxytech/haproxy-debian:2.2
2+
RUN apt-get update && apt-get install -y git
3+
RUN git clone https://github.com/haproxytech/haproxy-lua-jwt.git
4+
RUN cd haproxy-lua-jwt && chmod +x install.sh && ./install.sh luajwt
5+
COPY keycloak.pem /etc/haproxy/pem/keycloak.pem
6+
COPY jwtverify.lua /usr/local/share/lua/5.3/jwtverify.lua
7+
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
global
2+
daemon
3+
lua-load /usr/local/share/lua/5.3/jwtverify.lua
4+
5+
# Set env variables used by Lua file
6+
setenv OAUTH_PUBKEY_PATH /etc/haproxy/pem/keycloak.pem
7+
8+
# OPTIONAL: OAuth issuer
9+
setenv OAUTH_ISSUER http://keycloak.localtest.me:9080/auth/realms/master
10+
11+
# OPTIONAL: OAuth audience
12+
# not set because we use 2 different audiences
13+
# when using oauth2-proxy only the audience would be:
14+
# setenv OAUTH_AUDIENCE oauth2-proxy
15+
# when using Keycloak REST API only the audience would be:
16+
# setenv OAUTH_AUDIENCE account
17+
18+
defaults
19+
timeout connect 5s
20+
timeout client 5s
21+
timeout server 5s
22+
mode http
23+
24+
frontend api_gateway
25+
bind :8080
26+
27+
# API v1
28+
# Deny all requests
29+
http-request deny if { path_beg /v1 }
30+
31+
# Deny if no Authorization header sent
32+
http-request deny unless { req.hdr(authorization) -m found }
33+
34+
# Invoke the jwtverify Lua file
35+
http-request lua.jwtverify
36+
37+
# Deny unless jwtverify set 'authorized' to true
38+
http-request deny unless { var(txn.authorized) -m bool }
39+
40+
# API v2
41+
# /v2/config available to only migrator_admin role
42+
http-request deny if { path_beg /v2/config } ! { var(txn.oauth_roles) -m sub migrator_admin }
43+
# /v2/service available to migrator_user role
44+
http-request deny if { path_beg /v2/service } ! { var(txn.oauth_roles) -m sub migrator_user }
45+
46+
use_backend be_migrator
47+
48+
backend be_migrator
49+
balance roundrobin
50+
server s1 migrator:8080

0 commit comments

Comments
 (0)