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

requestparamsresponsedescription
GET /oauth/providers
OAuthProvider arraylist of configured identity providers
GET /oauth/redirect

code={}&state={}

or

session_state={}

or

token={}

TokenResponsecalled by the 301 Response from the identity provider
POST /oauth/loginusername={}&password={}TokenResponse

Environment Vars

envdefault valuedescription
OAUTH_TOKEN_SECRETsecretkey to sign the token
OAUTH_TOKEN_ISSUERONAP SDNC
OAUTH_HOST_URLnull => autodetectedimportant for reverse proxy use case
OAUTH_ODLUX_REDIRECT_URI/odlux/index.html#/oauth?token=OAuth redirect will be responded
OAUTH_SUPPORT_ODLUSERStruelogin 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": []
}


keydefaultdescription
tokenSecretrandomgeneratedString()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
        }
    ]
}


keymandatorydescription
idyesidentifier for provider-entry (  regex: [ a-zA-Z0-9]+ )
typeyesimplementation-type GITLAB | KEYCLOAK | NEXTCLOUD
urlyesurl of server
internalUrlnourl of the oauth provider server to use for internal requests. If not set url is used
clientIdyesshared client-id between OAuth provider and Oauth client
secretyesshared secret between OAuth provider and Oauth client
scopeyesenabled scopes on oauth-provider side
titleyestitle shown in odlux GUI
realmNameif type is KEYCLOAKrealm name set up in keycloak
trustAllnotrust all SSL server certificates
roleMappingno

HashMap for roles from oauth-provider to odl

{

    "oauth-provider-role":"odl-role"

}