Using Keycloak for defining subjects in policies in Eclispe Ditto - keycloak

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.
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:
Keycloak container has the following alias: keycloak.localhost
Oauth configuration in the gateway looks like this:
oauth {
protocol = "http"
openid-connect-issuers = {
keycloak = "keycloak.localhost/auth/realms/twin"
}
}
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

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.

Related

Register confidential OIDC client through registration endpoint

I would like to programmatically register confidential OIDC client (the client is a backend service). I checked the keycloak document about it. And I use the “Initial access token“ approach as recommended.
After I created a "initial access token" on the UI console, I register a new client by:
POST https://my-keycloak-host/auth/realms/MyRealm/clients-registrations/default
Headers: Authorization: Bearer <initial access token>
{ "clientId": "my-client" }
The response contains a registrationAccessToken. But I expect to get a client secret. How can I get it? And what is the usage for that registrationAccessToken?
For some unknown reason Keycloak doesn't set secret properly. But you can define own secret with secret property in the payload (tested with Keycloak 16.1.1), e.g.:
{
"clientId": "my-client",
"secret": "my-secret"
}
Doc:
https://openid.net/specs/openid-connect-registration-1_0.html
https://www.keycloak.org/docs/latest/securing_apps/index.html#_client_registration

How to configure Keycloak to work with Guacamole's OpenID plugin?

I'm trying to setup Apache Guacamole with KeyCloak as OpenID Connect Authorization Server.
Guacamole is redirecting me to KeyCloak, I can Log in with my user I created on KeyCloak and I get redirected back to Guacamole, but there it says that my token is invalid
08:08:11.477 [http-nio-4432-exec-7] INFO o.a.g.a.o.t.TokenValidationService - Rejected invalid OpenID token: Unable to process JOSE object (cause: org.jose4j.lang.UnresolvableKeyException: Unable to find a suitable verification key for JWS w/ header {"alg":"RS256","typ" : "JWT","kid" : "8ZNpgh_vnmG0HMMNYdOz1lw4ECoWxmsiUGte1mJfvyI"} due to an unexpected exception (javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty) while obtaining or using keys from JWKS endpoint at https://172.16.47.229:12345/auth/realms/Guacamole-test/protocol/openid-connect/certs): JsonWebSignature{"alg":"RS256","typ" : "JWT","kid" : "8ZNpgh_vnmG0HMMNYdOz1lw4ECoWxmsiUGte1mJfvyI"}->eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4Wk5wZ2hfdm5tRzBITU1OWWRPejFsdzRFQ29XeG1zaVVHdGUxbUpmdnlJIn0.eyJleHAiOjE2MDIzOTczODgsImlhdCI6MTYwMjM5NjQ4OCwiYXV0aF90aW1lIjoxNjAyMzk2NDcwLCJqdGkiOiI5Y2RiZDVjZC01MDJhLTRjNmItYTM3Mi1jZDIxMTNjNTE1NTMiLCJpc3MiOiJodHRwczovLzE3Mi4xNi40Ny4yMjk6MTIzNDUvYXV0aC9yZWFsbXMvR3VhY2Ftb2xlLXRlc3QiLCJhdWQiOiJHdWFjYW1vbGUiLCJzdWIiOiI1YzQ3N2NiZC04ZjIzLTRlMjEtYmNhMi1kMzNlMTRhZGY0ZDYiLCJ0eXAiOiJJRCIsImF6cCI6Ikd1YWNhbW9sZSIsIm5vbmNlIjoiaTQyZDBpZTc4c2s0MjRjMHJzMmJvdTM4YnUiLCJzZXNzaW9uX3N0YXRlIjoiMjNlZjdhMTYtMDhhNS00YTNkLTgxYTItYTQ2ZmE1NmM1NjE3IiwiYWNyIjoiMCIsImlzX3N1cGVydXNlciI6IlRydWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJ0ZXN0IHRlc3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0dXNlciIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QHRlc3QuY29tIn0.eOhkDqcgfdJnO12PRDqLIHACRNVdVHoSDFjThHWc6Ug1gdoz9t_T2K7F_B6dJSbNygAJrGvc5BVRx9XCJH1fVFSYhpXVqCO0jrHm0XJKhw_kBce4x3ZluGAtktx614j9qFzUwZHXOkFAUGPtyPQKuRTfdzHqQUILLJhVdSRPmou40rX31-l7VwqWZk_Yp1JCdQsA61XvJcQrU_aiKivZFaDGiY5GrnpL8zcEwJcFemptVoGKrG63O_LjxDCxhLpO1C1fi8GjngMSfco9aAp4AaGpHWy8ofJAu-TWbLGf-UPLUhC3lf903-Q_BU3eehYxtMyN1eet0HeGm0x_gV_wvA
In KeyCloak I created a Client as follows:
(Will change the Valid Redirect URI`s once I have it working)
And my guacamole.properites look like this:
guacd-port: 4822
guacd-hostname: localhost
# OpenID Connect Properties
openid-authorization-endpoint: https://172.16.47.229:12345/auth/realms/Guacamole-test/protocol/openid-connect/auth
openid-jwks-endpoint: https://172.16.47.229:12345/auth/realms/Guacamole-test/protocol/openid-connect/certs
openid-issuer: https://172.16.47.229:12345/auth/realms/Guacamole-test
openid-client-id: Guacamole
openid-redirect-uri: http://172.16.47.229:4432/guacamole/
# Postgresql Properties
postgresql-hostname: 172.16.47.229
postgresql-port: 4444
postgresql-database: guacamoledb
postgresql-username: guacamoleuser
postgresql-password: test
What do I have to change for guacamole to accept the token?
Update: I found the configuration to be working, if I use KeyCloak with HTTP instead of HTTPS, but that is not desirable. I have now also configured Guacamole, or more precisely the tomcat that's hosting guacamole, to use https, but I still can not get it to work (without having to use HTTP for KeyCloak).
I've caught the same issue. Most probably you just have to provide valid SSL certificate for your IdP (Keycloak).
Possible workaround was found here: How to configure Keycloak to work with Guacamole's OpenID plugin?.
I've re-compiled guacamole-auth-openid extension with this change:
diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java
index 5efb09dab..27d818ee5 100644
--- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java
+++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java
## -79,6 +79,7 ## public class TokenValidationService {
// Create JWT consumer for validating received token
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
+ .setSkipSignatureVerification()
.setRequireExpirationTime()
.setMaxFutureValidityInMinutes(confService.getMaxTokenValidity())
.setAllowedClockSkewInSeconds(confService.getAllowedClockSkew())
And this solved the issue. Don't think it's applicable for production needs but in production self-signed certificates should not be used.
With Guacamole 1.4.0 and Keycloak 15.0.2 I fixed the HTTPS issue by mounting a custom cacerts keystore in the the Guacamole container. This custom keystore is just the OpenJDK 8 cacerts with Let's Encrypt CA bundle https://letsencrypt.org/certs/isrgrootx1.pem imported. Because my Keycloak and Guacamole instance use Let's Encrypt certificates.
On the host I had OpenJDK 8 installed. So Docker mount was
/etc/ssl/certs/java/cacerts:/usr/local/openjdk-8/jre/lib/security/cacerts

How to add policy to Keycloak - UI crashes

I'm trying to enable flow when some admin user by some admin client is able to create users and obtain their access tokens to be used for another clients.
I have KeyCloak setup with token exchange and fine grained authz enabled and configured clients. I'm able to login my admin user by REST api, then exchange token. But when I specify audience I got error.
This one returns token but I need token for another client/audience.
http -f POST https://my-keycloak-server.com/auth/admin/realms/my-realm/protocol/openid-connect/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange requested_subject=1a147915-53fe-454d-906a-186fecfa6974 client_id=api-admin client_secret=23a4ecbe-a9e8-448c-b36a-a45fa1082e6e subject_token=eyJhbGeiOiJSUzI1NiIs......
This one is failing with error.
http -f POST https://my-keycloak-server.com/auth/admin/realms/my-realm/protocol/openid-connect/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange requested_subject=1a147915-53fe-454d-906a-186fecfa6974 client_id=api-admin client_secret=23a4ecbe-a9e8-448c-b36a-a45fa1082e6e subject_token=eyJhbGeiOiJSUzI1NiIs...... audience=my-another-client
{
"error": "access_denied",
"error_description": "Client not allowed to exchange"
}
So I tried to setup fine grained auth for target audience client (enabled it in tab, then tried to add policy for my admin user to be able to exchange token) but when I want to add policy that will allow my admin user to perform token exchange I'm stuck on UI error.
When typing policy name I got 404 when Keycloak is looking for name colisions. Afaik 404 in this case shouldn't block form from posting because it is no name collision. Instead I got instantly redirected with error.
https://my-keycloak-server.com/auth/admin/realms/my-realm/clients/1bafa9a4-f7e2-422c-9188-58ea95db32ef/authz/resource-server/policy/search?name=some-name
In the end of the day I can't add any policy in Keycloak. All the time form validation is ending up with crash caused by 404 policy name not found.
I'm using dockerized keycloak 10.0.0
Any ideas?
I hacked it by live editing Angular JS UI script function that performs verification in line 2403.
this.checkNameAvailability = function (onSuccess) {
if (!$scope.policy.name || $scope.policy.name.trim().length == 0) {
return;
}
ResourceServerPolicy.search({
realm: $route.current.params.realm,
client: client.id,
name: $scope.policy.name
}, function(data) {
if (data && data.id && data.id != $scope.policy.id) {
Notifications.error("Name already in use by another policy or permission, please choose another one.");
} else {
onSuccess();
}
});
}
to
this.checkNameAvailability = function (onSuccess) {
onSuccess();
}
And that end up with successfuly added policy. Still looks like it's UI bug.

Error - Failed to add 'SAP-Connectivity-Authentication' header for on-premise connectivity

I am connecting an On-premise S/4 HANA with SAP Cloud Platform trial account. I am using SAP Cloud SDK to fetch all Business Partners from S/4 HANA.
My Cloud Connector is set
My Destination at Sub-Account level is set and can ping to my on-premise system
My Service instances - XSUAA/Destination/Connectivity is set with the application
But I have the following error
Failed to add 'SAP-Connectivity-Authentication' header for on-premise connectivity: no JWT bearer found in the 'Authorization' header of the request. Continuing without a header. Connecting to on-premise systems may not be possible
The code which I am using is -
final List<BusinessPartner> businessPartners =
new DefaultBusinessPartnerService()
.getAllBusinessPartner()
.select(BusinessPartner.BUSINESS_PARTNER)
.execute(destination);
It seems AppRouter is the recommended for Authorization and Access and hence I tried implementing one- but my approuter shows - Not Found
Approuter App -Name - approuter-demo
Below is the xs-app.json
{
"routes": [
{
"source": "^/s4ext/(.*)",
"target": "/s4ext/$1",
"destination": "******"
}
]
}
The Manifest file is as below:
---
applications:
- name: approuter-demo
routes:
- route: approuter-demo-*****trial.cfapps.eu10.hana.ondemand.com
path: approuter
memory: 128M
env:
TENANT_HOST_PATTERN: 'approuter-demo-(.*).cfapps.eu10.hana.ondemand.com'
destinations: '[{"name":"******", "url" :"https://s4ext-***.cfapps.eu10.hana.ondemand.com", "forwardAuthToken": true }]'
services:
- xsuaa-demo
- connectivity-demo
- destination-demo
Kindly guide me. Thanks.
Your destination type might be wrong. The authorization header is set via the destination.
Try other types in sap cp -> connectivity.
Reading your question again I can identify two issues:
This error message in your log:
Failed to add 'SAP-Connectivity-Authentication' header for on-premise connectivity: no JWT bearer found in the 'Authorization' header of the request. Continuing without a header. Connecting to on-premise systems may not be possible
It may be that this error message is actually superfluous and hence indicating a problem which is actually none. In your case this header is possibly not necessary and the SAP Cloud SDK should not try to add it. But in any case, this will not influence the actual connection, so this error message is at most confusing, but not harmful in the sense of altering functionality.
Still, I am asking you to add the stack trace of this exception to your question to be very sure here.
Your app router shows "Not Found":
Here I am missing more information. When does what exactly show "Not Found"? Is it that your browser cannot find your app router, or can your app router not find the target URL of the application?

"Access is denied due to invalid credentials" REST API error. How to solve?

I followed the documentation here: and here: Trying to integrate to a Personality Insights service via Android Java.
However, after the app runs, and using the correct username and password as mentioned in the guide... (the guide is not clear (2nd bullet point in "Before you begin") on which set of credentials to use - It says get the "service credentials" and credentials from the new service created - I tried with both and both fail with the same error below.)
Error:
12-11 01:49:56.201 29584-29632/? I/CredentialUtils: JNDI string lookups is not available. 12-11 01:49:56.269 29584-29632/? D/NetworkSecurityConfig: No Network Security Config specified, using platform default 12-11 01:49:56.723 29584-29632/? D/OkHttp: --> POST https://gateway.watsonplatform.net/personality-insights/api/v3/profile?version=2017-10-13 http/1.1 (1297-byte body) 12-11 01:49:56.803 29584-29632/? D/OkHttp: <-- 401 Not Authorized https://gateway.watsonplatform.net/personality-insights/api/v3/profile?version=2017-10-13 (78ms, unknown-length body) 12-11 01:49:56.863 29584-29632/? E/WatsonService: POST https://gateway.watsonplatform.net/personality-insights/api/v3/profile?version=2017-10-13, status: 401, error: Not Authorized 12-11 01:49:56.865 29584-29632/? E/ERROR: Unauthorized: Access is denied due to invalid credentials
com.ibm.watson.developer_cloud.service.exception.UnauthorizedException: Unauthorized: Access is denied due to invalid credentials at com.ibm.watson.developer_cloud.service.WatsonService.processServiceCall(WatsonService.java:492) at com.ibm.watson.developer_cloud.service.WatsonService$2.execute(WatsonService.java:254) at com.upen.personalityapp.MainActivity$RetrieveFeedTask.doInBackground(MainActivity.java:105) at com.upen.personalityapp.MainActivity$RetrieveFeedTask.doInBackground(MainActivity.java:87) at android.os.AsyncTask$2.call(AsyncTask.java:306) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:244) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761) 12-11 01:49:56.866 29584-29584/?
This is the code I am using; I am trying to pass a "text" input to the service.
service = new PersonalityInsights("2017-10-13");
service.setUsernameAndPassword("{myUsername}", "{myPassword}");
Profile profile = service.getProfile(text).execute();
System.out.println(profile);
return profile.toString();
I am using the com.ibm.watson.developer_cloud:personality-insights:3.8.0 dependency.
I tried connecting to the URL in the error (https://gateway.watsonplatform.net/personality-insights/api/v3/profile?version=2017-10-13 ) via a browser. It prompts for a username/password combo. I entered my details from my IBM Cloud Lite service but it throws the HTTP Error 405. Is this how it's supposed to work on the browser?
For someone in the future;
Instead of service.setUsernameAndPassword(username, password);, I tried service.setUsernameAndPassword("username", "password"); and it worked.