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
    <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)


User User GUI GUI SDNC SDNC OAUthProvider OAUthProvider 1GET /oauth/providers 2providers array 3Select OAuth provider 4/loginForm with params 5loginForm 6fill login form 7POST /login with credentials 8[301] to redirect_uri 9GET /oauth/redirect with params 10POST /oauth2/token with params 11OAuthToken with roles 12create odl bearer token with with roles 13odl bearer token

2:

[{
  "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:

GET https://oauth-provider/..../...?client_id=abc&response_type=code&redirect_uri=https://sdnc-web

8:

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:

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:

{
    "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:

{
  "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

    <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.

    <main>
        <pair-key>anyroles</pair-key>
        <pair-value>org.opendaylight.aaa.shiro.filters.AnyRoleHttpAuthenticationFilter</pair-value>
    </main>

So usage changes to: 

    <urls>
        <pair-key>/**</pair-key>
        <pair-value>authcBasic, anyroles["admin,provision"]</pair-value>
    </urls>

Configuration

ConfigFile $ODL_HOME/etc/oauth-provider.config.json

{
    "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

{
    "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

{
    "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"

}

  • No labels