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://oauth-provider/..../...?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" } |