Versions Compared
Key
- This line was added.
- This line was removed.
- Formatting was changed.
Table of Contents
Table of Contents |
---|
Motivation
- get rid of the insecure basic authentication of opendaylight for Restconf
- instead implement JsonWebToken(JWT) authorization
- JWT is stateless because its signed
- so roles can be put inside of the token and the token only has to be verified (checked signature) to get the roles of the user)
Problems
- Opendaylight AAA project for aluminium-SR1 is only supporting authorization header starting with "Basic" and JWT is a Bearer token
- So we had to patch the org.opendaylight.aaa:aaa-shiro:0.12.1 bundle with
- some backported classes from org.apache.shiro:shiro-core:1.7 package
- two modifications on the Authenticator to Accept also Bearer tokens
- we realized that an entry in aaa-app-config.xml like
Code Block |
---|
<urls> <pair-key>/**</pair-key> <pair-value>authcBasic, roles["admin,provision"]</pair-value> </urls> |
means that the user which wants to access this url pattern needs to have both roles, which does not really make sense. Therefor we also implemented a so called AnyRolesAuthenticationFilter which accepts the connection if one of the given roles matches.
OAuth Provider bundle
API
request | params | response | description |
---|---|---|---|
GET /oauth/providers | OAuthProvider array | list of configured identity providers | |
GET /oauth/redirect | code={}&state={} or session_state={} or token={} | TokenResponse | called by the 301 Response from the identity provider |
POST /oauth/login | username={}&password={} | TokenResponse |
Environment Vars
env | default value | description |
---|---|---|
OAUTH_TOKEN_SECRET | secret | key to sign the token |
OAUTH_TOKEN_ISSUER | ONAP SDNC | |
OAUTH_HOST_URL | null => autodetected | important for reverse proxy use case |
OAUTH_ODLUX_REDIRECT_URI | /odlux/index.html#/oauth?token= | OAuth redirect will be responded |
OAUTH_SUPPORT_ODLUSERS | true | login interface enabled for internal odl configured users |
Dataflow example
for Login with external Identity Provider (KeyCloak)
2:
Code Block |
---|
[{ "id":"keycloak", "title":"OSNL Keycloak Provider", "loginUrl":"http://10.20.11.160:8080/auth/realms/onap/protocol/openid-connect/auth?client_id=odlux.app&response_type=code&scope=openid&redirect_uri=http%3A%2F%10.20.11.159%3A8181%2Foauth%2Fredirect%2Fkeycloak" }] |
4:
Code Block |
---|
GET https://identity/..../...?client_id=abc&response_type=code&redirect_uri=https://sdnc-web |
8:
Code Block |
---|
301 Location: http://10.20.11.159:8181/oauth/redirect/keycloak?state=odlux.app&code=4e4b717f-4a23-4f75-8bf1-76514f4b65dc.b0270d58-d281-4533-910f-19cb938ea189.dbd662ad-e959-44c9-bd18-859ca0142927 |
10:
Code Block |
---|
POST /auth/realms/onap/protocol/openid-connect/token grant_type: "authorization_code" code: "4e4b717f-4a23-4f75-8bf1-76514f4b65dc.b0270d58-d281-4533-910f-19cb938ea189.dbd662ad-e959-44c9-bd18-859ca0142927" client_id: "odlux.app" client_secret: "5da4ea3d-8cc9-4669-bd7e-3ecb91d120cd" redirect_uri: "http://10.20.11.159:8181/oauth/redirect" |
11:
Code Block |
---|
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkbWFSWXRkaHFkVXFDV2lmRWdNRHFBcWVBcU8tMnFoTDBjdnByelRGdWRRIn0.eyJleHAiOjE2MTExMzU5MjEsImlhdCI6MTYxMTEzNDEyMSwiYXV0aF90aW1lIjoxNjExMTM0MDkxLCJqdGkiOiIzYzFlZmMzZi1lMjFiLTQ3MzktYTY1YS1jNjY1M2ZhOGRjNTQiLCJpc3MiOiJodHRwOi8vMTAuMjAuMTEuMTYwOjgwODAvYXV0aC9yZWFsbXMvb25hcCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI0NDZhMjRiYy1kOGEwLTQzZGQtYWZhNS1lNTZlZWQ3NWRlYjgiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJvZGx1eC5hcHAiLCJzZXNzaW9uX3N0YXRlIjoiMTI5YjRhNjMtNzBhMS00MjFmLWEzM2YtOWFjZDkyZTIzM2ZmIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJwcm92aXNpb24iLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ikx1a2UgU2t5d2Fsa2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVrZS5za3l3YWxrZXIiLCJnaXZlbl9uYW1lIjoiTHVrZSIsImZhbWlseV9uYW1lIjoiU2t5d2Fsa2VyIiwiZW1haWwiOiJsdWtlLnNreXdhbGtlckBzZG5yLm9uYXAub3JnIn0.tn2NrEGYLRq1u0DkqxD2iDM72hFrDBPGA_q23S-htiRH113yt14a0CzJxU9El0YDobbzog9xm0ELbx6W4jYsGguMABqIi4W5wtTqfbaCh7gmF208CqNpwzA7nG2palMLbBPpmGXiagUm4qLWQxrBP_VOaeW_kK0VHLaiTRJ-4vHuOXSNPYEDQZNCI2QCJQS_dn83K_JI4ecBHl8UeHFLB65BqmocpDHUvf2h835xuNFFQpXJWMcPM_j_FmFQeOSUDM4HmqgdVU9_b4APnDEVFiUezQdoEOfEYNsNlhCoXlaEEn2tCZfEkZ7k72DlhqJMQzomdaGKPk2g8XhKJNwMJg", "expires_in": 1800, "refresh_expires_in": 1800, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhOGUzMDUwZS0wZmQxLTRjYjQtYjRiZS1jMDVlOGY4OGJhZGUifQ.eyJleHAiOjE2MTExMzU5MjEsImlhdCI6MTYxMTEzNDEyMSwianRpIjoiZmZiYWE3NDktZGVkNi00ZWMzLWI4MjYtYTI4NWY0ODY1ZGI0IiwiaXNzIjoiaHR0cDovLzEwLjIwLjExLjE2MDo4MDgwL2F1dGgvcmVhbG1zL29uYXAiLCJhdWQiOiJodHRwOi8vMTAuMjAuMTEuMTYwOjgwODAvYXV0aC9yZWFsbXMvb25hcCIsInN1YiI6IjQ0NmEyNGJjLWQ4YTAtNDNkZC1hZmE1LWU1NmVlZDc1ZGViOCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvZGx1eC5hcHAiLCJzZXNzaW9uX3N0YXRlIjoiMTI5YjRhNjMtNzBhMS00MjFmLWEzM2YtOWFjZDkyZTIzM2ZmIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCJ9.mt9VHtiBZycHcEuVCOZVjjtyoOGYNaDVvtcA1NPScIQ", "token_type": "bearer", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkbWFSWXRkaHFkVXFDV2lmRWdNRHFBcWVBcU8tMnFoTDBjdnByelRGdWRRIn0.eyJleHAiOjE2MTExMzU5MjEsImlhdCI6MTYxMTEzNDEyMSwiYXV0aF90aW1lIjoxNjExMTM0MDkxLCJqdGkiOiJjZjUzZTc0ZC1kYjZiLTQ4YTUtODkyOS1jYzU3YjY3YjAxN2QiLCJpc3MiOiJodHRwOi8vMTAuMjAuMTEuMTYwOjgwODAvYXV0aC9yZWFsbXMvb25hcCIsImF1ZCI6Im9kbHV4LmFwcCIsInN1YiI6IjQ0NmEyNGJjLWQ4YTAtNDNkZC1hZmE1LWU1NmVlZDc1ZGViOCIsInR5cCI6IklEIiwiYXpwIjoib2RsdXguYXBwIiwic2Vzc2lvbl9zdGF0ZSI6IjEyOWI0YTYzLTcwYTEtNDIxZi1hMzNmLTlhY2Q5MmUyMzNmZiIsImF0X2hhc2giOiJSUXdDclpkQmFKV0VFdmxsRVNxRjV3IiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ikx1a2UgU2t5d2Fsa2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVrZS5za3l3YWxrZXIiLCJnaXZlbl9uYW1lIjoiTHVrZSIsImZhbWlseV9uYW1lIjoiU2t5d2Fsa2VyIiwiZW1haWwiOiJsdWtlLnNreXdhbGtlckBzZG5yLm9uYXAub3JnIn0.rueTNrnvRa4PMo7NS8l4xxRhhNiGzXLmtcUeyWnj3AjFaUoNKuS9l85K3KjRT3zjq494YsepIGuK33I20rvFwDLclcJNHuumAgBnR5dRBi5fLhm7x8YkebhdTHPiYL4hfygpZ7APN1PtcDZnb-uEjjT-RAtjnfk3r-oP6CtqWzI5MjOPnf5HaEwWpkuTjmJf3kyyf_pdhhVkgTwuC-kD8iMjyRIzuZJxVwWVA3S43eL0R7MaIDlpJrOp9EBRfMlObAypc1bLtKwopT0sBla1CM9GmUU2ZYbQb79-hey0rd7CWx1uBkZUxt5myiExBm3pI46boXLP7dzjzxHUKg0m-A", "not-before-policy": 1611134054, "session_state": "129b4a63-70a1-421f-a33f-9acd92e233ff", "scope": "openid profile email" } |
which can be decoded to:
Code Block |
---|
{ "exp": 1611135921, "iat": 1611134121, "auth_time": 1611134091, "jti": "3c1efc3f-e21b-4739-a65a-c6653fa8dc54", "iss": "http://10.20.11.160:8080/auth/realms/onap", "aud": "account", "sub": "446a24bc-d8a0-43dd-afa5-e56eed75deb8", "typ": "Bearer", "azp": "odlux.app", "session_state": "129b4a63-70a1-421f-a33f-9acd92e233ff", "acr": "1", "realm_access": { "roles": [ "provision", "offline_access", "uma_authorization" ] }, "resource_access": { "account": { "roles": [ "manage-account", "manage-account-links", "view-profile" ] } }, "scope": "openid profile email", "email_verified": false, "name": "Luke Skywalker", "preferred_username": "luke.skywalker", "given_name": "Luke", "family_name": "Skywalker", "email": "luke.skywalker@sdnr.onap.org" } |
where /real_access/roles are the important ones for us which were configured in the keycloak backend.
Hint: offline_access and uma_authorization are built-in keycloak roles. These ones are filtered by oauth-provider bundle. So delivered role in this case is only provision.
The Opendaylight Roles access problem
As described on top we found out that an entry in aaa-app-config.xml like
Code Block |
---|
<urls> <pair-key>/**</pair-key> <pair-value>authcBasic, roles["admin,provision"]</pair-value> </urls> |
results in a restriction for the configured url that the user has to be in both rules. That's why we implement a new Filter AnyRoleHttpAuthenticationFilter
. That means if you enable it for a url you just have to be in at least one of this groups to get access.
Code Block |
---|
<main> <pair-key>anyroles</pair-key> <pair-value>org.opendaylight.aaa.shiro.filters.AnyRoleHttpAuthenticationFilter</pair-value> </main> |
So usage changes to:
Code Block |
---|
<urls> <pair-key>/**</pair-key> <pair-value>authcBasic, anyroles["admin,provision"]</pair-value> </urls> |
Configuration
ConfigFile $ODL_HOME/etc/oauth-provider.config.json
Code Block |
---|
{ "tokenSecret": "${OAUTH_TOKEN_SECRET}", "tokenIssuer": "${OAUTH_TOKEN_ISSUER}", "publicUrl": "", "redirectUri": "${OAUTH_ODLUX_REDIRECT_URI}", "supportOdlUsers": "${OAUTH_SUPPORT_ODLUSERS}", "providers": [] } |
key | default | description |
---|---|---|
tokenSecret | randomgeneratedString() | secret to create JWT |
tokenIssuer | "Opendaylight" | issuer for JWT |
publicUrl | autodetect() | url on which odlux webserver is reachable for you. Attention!!!! Be aware behind reverse proxy!! pls set to your reverse proxy url |
redirectUri | "/odlux/index.html#/oauth?token=" | redirect after successful oauth login |
supportOdlUsers | "true" | enable login of internal odl configured users |
Gitlab as a OAuth provider
Code Block |
---|
{ "tokenSecret": "${OAUTH_TOKEN_SECRET}", "tokenIssuer": "${OAUTH_TOKEN_ISSUER}", "publicUrl": "", "redirectUri": "${OAUTH_ODLUX_REDIRECT_URI}", "supportOdlUsers": "true", "providers": [ { "id": "mygit", "type": "GITLAB", "url": "https://my-gitlab-server.com", "clientId": "db312fb791ebc97fd199df1569ebbd45916f52444bb75", "secret": "d376abb4524bc7fbd80833ad34f649584624e0c2b791da", "scope": "api+openid+read_user+profile", "title": "my Gitlab", "roleMapping":{ "mygitlabgroup":"admin" } } ] } |
KeyCloak as a OAuth provider
Code Block |
---|
{ "tokenSecret": "${OAUTH_TOKEN_SECRET}", "tokenIssuer": "${OAUTH_TOKEN_ISSUER}", "publicUrl": "", "redirectUri": "${OAUTH_ODLUX_REDIRECT_URI}", "supportOdlUsers": "true", "providers": [ { "id": "mykeycloak", "type": "KEYCLOAK", "url": "https://my-keycloak-server.com", "internalUrl": "https://my-keycloak-server.com:8443", "clientId": "db312fb791ebc97fd199df1569ebbd45916f52444bb75", "secret": "d376abb4524bc7fbd80833ad34f649584624e0c2b791da", "scope": "openid", "title": "My KeyCloak Login", "realmName": "onap", "trustAll": true } ] } |
key | mandatory | description |
---|---|---|
id | yes | identifier for provider-entry ( regex: [ a-zA-Z0-9]+ ) |
type | yes | implementation-type GITLAB | KEYCLOAK | NEXTCLOUD |
url | yes | url of server |
internalUrl | no | url of the oauth provider server to use for internal requests. If not set url is used |
clientId | yes | shared client-id between OAuth provider and Oauth client |
secret | yes | shared secret between OAuth provider and Oauth client |
scope | yes | enabled scopes on oauth-provider side |
title | yes | title shown in odlux GUI |
realmName | if type is KEYCLOAK | realm name set up in keycloak |
trustAll | no | trust all SSL server certificates |
roleMapping | no | HashMap for roles from oauth-provider to odl { "oauth-provider-role":"odl-role" } |