|
| 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 | + |
| 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. |
0 commit comments