Keycloak: A Central Auth Service
Keycloak as an open source Identity and Access Management tool that enforces authentication and authorization and secures all the API Gateway endpoints, routes and services.
See the Keycloak Documentation for a full description of its features.
Overview
The current implementation and integration with Keycloak replaces our SURF OAuth implementation. Keycloak extends the OAuth spec with OpenID Connect to allow more features and better performance.
The larger architectural aspects of Keycloak and where it fits on the roadmap are beyond the scope of this document.
The below details the current release and features of Keycloak as it relates to the API Gateway.
Business case
Our current SURF OAuth has had maintenance issues and was never expected to be a production ready solution. But as time and priorities shifted, SURF OAuth is in production today.
- We need a more scalable and more maintainable solution to secure our infrastructure.
- Ideally, we should choose a solution to handle SSO, IDP, User Federation, LDAP/Active Directory integration as well as a list of other features on the technology roadmap for the CCCTC.
API Gateway
The API Gateway uses Keycloak to secure access to all microservices. Any gateway client invoking a service endpoint will be authenticated and authorized using Keycloak.
Clients Migrating to Keycloak
There is no upgrade or changes necessary for API Gateway clients. This is a seamless upgrade handled behind the scenes. (see below)
Historically, if a gateway client wants to make a service call, they would get a token by sending a POST to
{{OAUTHURL}}/token/v1/token
REQUEST:
client_id:{{CLIENT_ID}}
client_secret:{{SECRET}}
grant_type:client_credentials
RESPONSE:
{
"scope": "sisdata_R,sisdata_W,movies,canvasDW,canvas-api,ARTICULATE_R",
"access_token": "aba15e27-d9a8-4dfd-a4a8-19dd2b4639fd",
"token_type": "bearer",
"expires_in": 10000
}
With Keycloak, the same above REQUEST call would result in a reply from the server similar to:
RESPONSE
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMQmpqVEs3eEVxME5xQVJpSVlObTZxNU5lTU1xQ3k1NE1IMW9sdVc2MnU4In0.eyJqdGkiOiI2MDY1MTZlZi0yNWU2LTRiZWUtYjU0Yi1hYjFmNDUzYWQ5YjkiLCJleHAiOjE1MTkzMTU2NDEsIm5iZiI6MCwiaWF0IjoxNTE5MzE1MzQxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODMvYXV0aC9yZWFsbXMvZGV2ZWxvcCIsImF1ZCI6ImdhdGV3YXktY2xpZW50LXRlc3RlciIsInN1YiI6ImEzNzhjOTdiLTBkZWItNDBlNS1hYjIyLWRlYWQwMGUzYjExNCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImdhdGV3YXktY2xpZW50LXRlc3RlciIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6ImUzZDFjMDQ5LTA5OWUtNDgwNS1hZWU4LTYyMjcyMWU4ZjkzYyIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsibW92aWVzX1IiLCJzaXNkYXRhX1ciLCJ1bWFfYXV0aG9yaXphdGlvbiIsInNpc2RhdGFfUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sImNsaWVudEhvc3QiOiIxNzIuMjEuMC4xIiwiY2xpZW50SWQiOiJnYXRld2F5LWNsaWVudC10ZXN0ZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtZ2F0ZXdheS1jbGllbnQtdGVzdGVyIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMS4wLjEiLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC1nYXRld2F5LWNsaWVudC10ZXN0ZXJAcGxhY2Vob2xkZXIub3JnIn0.CQ8IDL2-k06HobYv3tlkBwkRtiVw0m35KhOBWv-D98XQqqskYLxaT0S2q88mkWqpCmT5Tbdn9APFCoxkU1Wn0yDL0CGlXFmQcy2BOrmZ6h6EmqfzhpMjEVKz_j4Klkpqbh1Y3hs2_ys4bMAJhZyMcnx45oEaH1v5rkcRW8AmVuUN-mAEYB2KzmL2bkp_zlZsXwUmmwlG4Jvj4kbnR3V-IcusElbiT6PDqNZoV3tAFQhaIBFqwARd-dQm4KaG-Xoq4SdfXb1dUp8eh5aDefV4iWUoZUQ0nVa8XDgKWzhYeNXNG9TkFHqrh6CvJU7BRudHlnVz1DNOiC5QeuaOa9GJew",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMQmpqVEs3eEVxME5xQVJpSVlObTZxNU5lTU1xQ3k1NE1IMW9sdVc2MnU4In0.eyJqdGkiOiJkYzExOWY0Ni1jNTAxLTQ1MmItODkwYy1kOThiNDAyMmM2NDkiLCJleHAiOjE1MTkzMTcxNDEsIm5iZiI6MCwiaWF0IjoxNTE5MzE1MzQxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODMvYXV0aC9yZWFsbXMvZGV2ZWxvcCIsImF1ZCI6ImdhdGV3YXktY2xpZW50LXRlc3RlciIsInN1YiI6ImEzNzhjOTdiLTBkZWItNDBlNS1hYjIyLWRlYWQwMGUzYjExNCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJnYXRld2F5LWNsaWVudC10ZXN0ZXIiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJlM2QxYzA0OS0wOTllLTQ4MDUtYWVlOC02MjI3MjFlOGY5M2MiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsibW92aWVzX1IiLCJzaXNkYXRhX1ciLCJ1bWFfYXV0aG9yaXphdGlvbiIsInNpc2RhdGFfUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX19.Dml8WZW-VU4qTvpCG2qZ9H9AzRERJE_-Tj5_3tUA_TKZcAu94HLPmevhagciVg8CVWzBRfRRpnuqTuJG3yVlQIwm9_hBawGcOZ1JB7gw2cILSq948_7C3gvSV7eLY6FedGsDqv9WTEpNiGhQE4aA8npKBUbXpOKLjW1ucKeASWXQw8GIxrHtE3YIUL0QPoz3n1PsmtLVbXX6cKGH1LWJH8rlA9bIzOcL6coegV_jfBbg0TWOZP83KDm6jPXwgGZNCi_nf3AXUPhBgioA6New-esquPv5IIc92KwOHrhtQ-Wk4UKlGCVDaMTWpWIxl08z_V3VUOTMRwroR-XPsSaPxQ",
"token_type": "bearer",
"not-before-policy": 0,
"session_state": "e3d1c049-099e-4805-aee8-622721e8f93c"
}
- As long as the clients used the /token/v1/token URI to get the OAuth token, and are using the service-router as the server (and not directly to the SURF OAuth server) - no changes are necessary for the client.
- Although the above RESPONSE is different, the same
access_token
is returned and must be included with each API request as was previously the case.
Service Router
The Spring OAuth libraries and configurations previously used have been removed. The router now relies on Keycloak Auth services.
We no longer need the OAuth libraries as we've also moved the configuration from the previous OAuthConfig
java class to a set of properties in Spring property files.
Service Conductor
All security is handled in the service-router, so no changes here.
Service Workers
All security is handled in the service-router, so no changes here.
Configuration
SURF OAuth uses very fine grained control access and defines several scopes of access for various things.
As we port those scopes to Keycloak, we'll use more course grained, or higher level control. For example, today's college-adaptor uses 9 scopes, but we think only 2 are needed in Keycloak.
Further design and 'cleaning up' our OAuth scopes is handled in another Jira ticket.
Service Endpoints
The following services are implemented behind the API Gateway and their respective roles are listed with each
Service | Role | HTTP Method | Access |
---|---|---|---|
Movies (Test/Demmy service) | MOVIES_R | GET, OPTIONS | read |
MOVIES_W | PUT | write | |
College Adaptor | SISDATA_R | GET | read |
SISDATA_W | PUT, POST, PATCH, DELETE | write | |
Data Warehouse : Canvas Data Sync | DW_CANVAS_R | TBD | read |
DW_CANVAS_W | TBD | write | |
Articulate Course | ARTICULATE_R | TBD | read |
eTranscript | TBD | TBD | TBD |
Service Router
A notable improvement in the below Keycloak configuration, over the previous OAuth configuration, is most of the configuration is done in properties files. The previous OAuth configuration had some in property files, but most was hard coded in the java source.
The below is a snapshot, the most current configuration is contained in the Service Router Config git repo
keycloak:
enabled: true
# The base URL of the Keycloak server. All other Keycloak pages and REST service endpoints are derived from this. It is usually of the form https://host:port/auth. This is REQUIRED.
auth-server-url: # set in ENV-specific profile
realm: develop
# The client-id of the application. Each application has a client-id that is used to identify the application. This is REQUIRED.
# when using fine-grained authorization, the resource name to use
resource: not-using-this
# If set to true, the adapter will not send credentials for the client to Keycloak. This is OPTIONAL. The default value is false.
public-client: true
# The below securityConstraints list securityCollections and authRoles.
# each block creates a constraint each role, method and pattern (OR'd together) (see DEBUG log entries)
# NB: This securityConstraints is a collection and cannot be added to in ENV-specific profiles but below will be _REPLACED_ if defined in ENV files
securityConstraints:
#
# Global default catch all to block access to uncovered HTTP methods
#
-
authRoles:
- DEFAULT_NO_ACCESS # require a bogus role, denying access to those without it (everyone)
securityCollections:
-
name: "default no access"
omittedMethods:
# leaving blank omits none, applies to all (You can specify methods used above, but is redundant)
patterns:
# Not adding a matching path below, you'll get ERROR log msgs warning of uncovered methods for URI paths
- /movies/*
- /adaptor/**/assessments/**
- /adaptor/**/bogfw/**
- /adaptor/**/colleagueapi/**
- /adaptor/**/courses/**
- /adaptor/**/enrollments/**
- /adaptor/**/faunits/**
- /adaptor/**/mock/**
- /adaptor/**/persons/**
- /adaptor/**/placements/**
- /adaptor/**/sections/**
- /adaptor/**/sistype
- /adaptor/**/students/**
- /adaptor/**/terms/**
- /adaptor/**/transcripts/**
#
# Movies dummy/test service
#
-
# To combine/use multiple roles, see http://www.keycloak.org/docs/latest/server_admin/index.html#_composite-roles
authRoles: # rules required for this security constraint
- MOVIES_R
securityCollections:
-
name: "movies read"
# NOTE: you can use either 'methods' or 'omittedMethods', not both
methods: # The HTTP methods explicitly covered by this security constraint., the rest are disallowed
- GET
- OPTIONS
patterns:
- /movies/*
-
authRoles:
- MOVIES_W
securityCollections:
-
name: "movies write"
methods:
- PUT
patterns:
- /movies/*