0

My current use case is: I have a frontend application where a user is logged in via Keycloak. I would like to implement some parts of the Ditto HTTP API in this frontend (https://www.eclipse.org/ditto/http-api-doc.html).

For example I want to create policies (https://www.eclipse.org/ditto/basic-policy.html) for authorization. I've read in the documentation that one can use an OpenID Connect compliant provider and the form is : (https://www.eclipse.org/ditto/basic-policy.html#who-can-be-addressed).

There's basic auth example at the bottom of the page, it seems to use the username in this case.

{
    "policyId": "my.namespace:policy-a",
    "entries": {
      "owner": {
        "subjects": {
          "nginx:ditto": {
            "type": "nginx basic auth user"
          }
        },
        
    ...
}

My question is: What exactly would be the sub-claim if I want to use Keycloak? Is it also the username of the user I want to grant rights to? And how would I get this in my frontend where I want to specify the policy for sending it to Ditto afterwards?


UPDATE 1:

I tried to enable keycloak authentication in Ditto like suggested below and as stated here: https://www.eclipse.org/ditto/installation-operating.html#openid-connect

Because I'm running Ditto with Docker Compose, I added the following line as an environment variable in ditto/deployment/docker/docker-compose.yml in line 136: - Dditto.gateway.authentication.oauth.openid-connect-issuers.keycloak=http://localhost:8090/auth/realms/twin This URL is the same as in the issuer claim of my token which I'm receiving from keycloak.

Now if I try to make for example a post request with Postman to {{basePath}}/things I get the following error:

<html>

<head>
    <title>401 Authorization Required</title>
</head>

<body bgcolor="white">
    <center>
        <h1>401 Authorization Required</h1>
    </center>
    <hr>
    <center>nginx/1.13.12</center>
</body>

</html>

I chose Bearer Token as Auth in Postman and pasted a fresh token. Basic Auth with the default ditto user is still working.

enter image description here

Do I have to specify the new subject/my user in Ditto before?


UPDATE 2:

I managed to turn basic auth in nginx off by commenting out "auth_basic" and "auth_basic_user_file" in nginx.conf!

It seems to be forwarded to Ditto now, because now I get the following error with Postman:

{ 
   "status": 401, 
   "error": "gateway:jwt.issuer.notsupported", 
   "message": "The JWT issuer 'localhost:8090/auth/realms/twin' is not supported.", 
   "description": "Check if your JWT is correct." 
}

UPDATE 3:

My configuration in gateway.conf looks now like this:

oauth {
        protocol = "http"
        openid-connect-issuers = {
          keycloak = "localhost:8090/auth/realms/twin"
        }
      }

I also tried to add these two lines in the docker-compose.yml:

- Dditto.gateway.authentication.oauth.protocol=http
- Dditto.gateway.authentication.oauth.openid-connect-issuers.keycloak=localhost:8090/auth/realms/twin

Unfortunately I still had no luck, same error as above :/ It seems like an user had a similar problem with keycloak before (https://gitter.im/eclipse/ditto?at=5de3ff186a85195b9edcb1a6), but sadly he mentioned no solution.

EDIT: It turns out that I specified these variables in the wrong way, the correct solution is to add them as part of command: java ... more info here


UPDATE 4:

I tried to build Ditto locally instead of using the latest docker images and I think I might be one step further now, it seems like my oauth config is working. I get now:

{
    "status": 503,
    "error": "gateway:publickey.provider.unavailable",
    "message": "The public key provider is not available.",
    "description": "If after retry it is still unavailable, please contact the service team."
}

The error message from the log is:

gateway_1        | 2020-11-05 15:33:18,669 WARN  [] o.e.d.s.g.s.a.j.DittoPublicKeyProvider  - Got Exception from discovery endpoint <http://localhost:8090/auth/realms/twin/.well-known/openid-configuration>.
gateway_1        | akka.stream.StreamTcpException: Tcp command [Connect(localhost:8090,None,List(),Some(10 seconds),true)] failed because of java.net.ConnectException: Connection refused
gateway_1        | Caused by: java.net.ConnectException: Connection refused
...
gateway_1        | java.util.concurrent.CompletionException: org.eclipse.ditto.services.gateway.security.authentication.jwt.PublicKeyProviderUnavailableException [message='The public key provider is not available.', errorCode=gateway:publickey.provider.unavailable, statusCode=SERVICE_UNAVAILABLE, description='If after retry it is still unavailable, please contact the service team.', href=null, dittoHeaders=ImmutableDittoHeaders [{}]]
...
gateway_1        | Caused by: org.eclipse.ditto.services.gateway.security.authentication.jwt.PublicKeyProviderUnavailableException [message='The public key provider is not available.', errorCode=gateway:publickey.provider.unavailable, statusCode=SERVICE_UNAVAILABLE, description='If after retry it is still unavailable, please contact the service team.', href=null, dittoHeaders=ImmutableDittoHeaders [{}]]
...
gateway_1        | Caused by: akka.stream.StreamTcpException: Tcp command [Connect(localhost:8090,None,List(),Some(10 seconds),true)] failed because of java.net.ConnectException: Connection refused
gateway_1        | Caused by: java.net.ConnectException: Connection refused

My keyloak is definitely running, I'm able to get tokens. If I'm opening http://localhost:8090/auth/realms/twin/.well-known/openid-configuration which is in the first error message, I'm able to see my openid-configuration from keycloak config. Edit: It seems that my gateway container cannot reach my keycloak container, will try to figure this out.


FINAL UPDATE:

Unreachable keycloak docker container from the gateway docker container was the issue. I'm now using traefik:

  1. Keycloak container has the following alias: keycloak.localhost
  2. Oauth configuration in the gateway looks like this:
oauth {
        protocol = "http"
        openid-connect-issuers = {
          keycloak = "keycloak.localhost/auth/realms/twin"
        }
      }
  1. Now the gateway can find the keycloak container via the alias and I can still use the keycloak admin ui from my localhoast: http://keycloak.localhost:8090/auth/admin/

Additional info: Traefic Blog

raujonas
  • 117
  • 8
  • 1
    The 401 seems to be issued by nginx. Did you still configure basic auth in nginx? If this is the case, the request never reaches ditto. – Yannic Bürgmann Nov 03 '20 at 12:43
  • ENABLE_DUMMY_AUTH=true in the docker-compose.yml was still turned on. Is this what you meant? I turned it off, so the default ditto user is not working anymore with basic auth. Additionally I set ENABLE_PRE_AUTHENTICATION=false. But when I switch to Bearer it is still the same error message as before. I don't know how to disable basic auth properly, could you please give me another hint? Do I have to adapt the file nginx.conf? – raujonas Nov 03 '20 at 14:09
  • I managed to turn basic auth in nginx off by commenting out "auth_basic" and "auth_basic_user_file" in nginx.conf! It seems to be forwarded to Ditto now, because I get: { "status": 401, "error": "gateway:jwt.issuer.notsupported", "message": "The JWT issuer 'http://localhost:8090/auth/realms/twin' is not supported.", "description": "Check if your JWT is correct." } – raujonas Nov 03 '20 at 16:48
  • 1
    I updated the configuration section of the openid-connect-issuer. Seems like the scheme must not be part of the issuer config but needs to be configured on oauth level (for all issuers). I do not recommend to use http here. – Yannic Bürgmann Nov 04 '20 at 12:56
  • Thank you so so much again for helping! Sadly I still had no luck to get it working, I updated the question once again.. :/ I only would like to use http for testing. – raujonas Nov 04 '20 at 15:38
  • I updated it again (update 4), I think I'm one step closer now :) It would be awesome if you could take a look again. – raujonas Nov 05 '20 at 14:43
  • 1
    I solved it and updated the solution! Thank you so much again! – raujonas Nov 06 '20 at 07:09
  • Cool. Thank you for sharing your final solution and all iterations until you got it. This will be very helpful for future developers. – Yannic Bürgmann Nov 06 '20 at 12:23
  • 1
    I'm happy to help! But I'm still wondering why I can't get it to work with the prebuilt docker images. It seems like my environment variable does not work at all, therefore it says still unsupported jwt issuer because the config list is empty.. – raujonas Nov 06 '20 at 16:30
  • Seems like a bug in the config file of ditto gateway. Feel free to create an issue in our GitHub repository. We'll take care of it soon. – Yannic Bürgmann Nov 07 '20 at 17:53
  • Turns out I specified the -D configs in the wrong way! Should have posted the whole service config, edited solution in UPDATE 3! – raujonas Nov 10 '20 at 09:52

1 Answers1

2

What exactly would be the sub-claim if I want to use Keycloak?

Keycloak provides you a JWT.

A JWT is an encrypted JSON which contains multiple fields called "claims". You can check how your token looks like by visiting https://jwt.io and pasting your token there. One of those fields is called sub. This is the sub claim.

To enable your keycloak authentication in eclipse ditto you need to add the issuer to the ditto configuration.

An example can be founde here. The address must match the URL in the issuer claim of your JWT token.

ditto.gateway.authentication {
    oauth {
      protocol = "http"
      openid-connect-issuers = {
        some-name = "localhost:8090/auth/realms/twin"
      }
    }
}

Is it also the username of the user I want to grant rights to?

In eclipse ditto there is not really a concept of "user names". Eclipse ditto authentication is based on authorization subjects. For the basic authentication example you provided, the authorization subject which is generated within ditto is nginx:ditto.

For JWT authentication the authorization subject is generated as a combination of the name for the open id connect issuer which you configured (in my case some-name) and the value of the sub claim. An authorization subject could look like this: some-name:8d078113-3ee5-4dbf-8db1-eb1a6cf0fe81.

And how would I get this in my frontend where I want to specify the policy for sending it to Ditto afterwards?

I'm not sure if I understand the question correctly. If you mean how to authenticate your frontend HTTP requests to eclipse ditto, you need to provide the JWT to eclipse ditto by adding it to the authorization header of your HTTP requests in the following form:

authorization: Bearer yourJWT

If you mean how you would know the sub claim of a JWT, you need to parse the JWT to a JSON object and then read the sub claim out of the payload section.

Community
  • 1
  • 1
Yannic Bürgmann
  • 6,301
  • 5
  • 43
  • 77
  • Thanks for the detailed answer Yannic! What I meant with the last question and what I still do not understand: Let's say I want to grant rights to another subject (not me) inside a policy, like the authorization subject you mentioned above ("some-name"). I do not have access to the JWT of another user to get his specific sub-claim to write this into the policy. How would I do this? Maybe I'm completely on the wrong track. – raujonas Nov 02 '20 at 09:31
  • I mean the basic auth exmaple is clear, I can just write "subjects": {"nginx:another-user": {}}, but how do I do this with another user when using OpenID Connect compliant providers like Keycloak? – raujonas Nov 02 '20 at 09:32
  • Or can I can this like usernames from other users from keycloak? – raujonas Nov 02 '20 at 09:35
  • 1
    You can consider the "sub" claim to something equivalent to a user name. So if you have the request of someone to grant access to a resource, he or she needs to provide you the value of the sub claims of his or her tokens. – Yannic Bürgmann Nov 02 '20 at 11:02
  • Thanks for the clarification again! Because I'm running Ditto with docker compose, I added "- Dditto.gateway.authentication.oauth.openid-connect-issuers.keycloak=http://localhost:8090/auth/realms/twin" (from my issuer claim of the token) as an environment variable of the gateway service in the compose file. With Postman and my Bearer token, I still get "401 Authorization Required" and only Basic Auth is working. I tried to POST a new thing: "{{basePath}}/things". What would you recommend me to find my mistake? – raujonas Nov 03 '20 at 10:05
  • Please provide these new details in your initial question and add the response body of the 401 response. – Yannic Bürgmann Nov 03 '20 at 10:23