8000 OpenId Federation OP back end by cgeorgilakis · Pull Request #41419 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

OpenId Federation OP back end - #41419

Closed
cgeorgilakis wants to merge 3 commits into
keycloak:mainfrom
eosc-kc:40511_openid_federation_op
Closed

cgeorgilakis wants to merge 3 commits into
keycloak:mainfrom
eosc-kc:40511_openid_federation_op

Conversation

@cgeorgilakis
Copy link
Copy Markdown
Contributor

Closes #40511

This is the initial PR for supporting OpenID Federation with Keycloak being an OP and explicit registration, accepting only Entity Statement. Ui will be a separate PR, documentation will be enhanced based on it. Some code parts are taken from the OpenID Federation extensions, taking into account that we are in core Keycloak, needed bug fixes and code improvements , compliant with latest OpenID Federation specification version.

Some key points for the PR:

  • OpenID Federation is disabled by default and can be enabled with required configuration (more in ui part). Inspiration : https://www.authlete.com/developers/oidcfed/#configuring-federation
  • Keycloak as a proxy can participate in more than one Federation(Trust Anchor) with different configuration options especially for RP part, trust marks etc (future features). That’s why it is separated table.
  • Tests for explicit client registration has been done with a real test OpenID Federation(Trust Anchor, RP) via the help of golang lighthouse OpenID Federation implementation
  • I have added tests for Federation Entity Configuration. For adding testing for explicit registration we need to mock REST API for Trust Anchor Entity Configuration, fetch and list endpoints. Take into account that they are JWT signed statement. Moreover, we need to create a mock jwt valid during test execution for client explicit registrations. How could we develop it? I have only a mock Java code for create a RP entity Statement based on mock keys. Should it be a separate issue?

For more opened issues related to explicit client registration in an OP, you could see the epic issues.

@cgeorgilakis
Copy link
Copy Markdown
Contributor Author

Information for testing this PR.

  1. You will need to add the following realm configurations:

    • "openidFederationOrganizationName " - "Keycloak foundation" (optional)

    • "openidFederationLifespan" - "100000" (optinal default to 86400 – one day)

    • "openidFederationEnabled" - "true"

    • "openidFederationAuthorityHints" - "https://trust-anchor.sandbox.eosc.grnet.gr"

  2. Moreover, you will need to create a trust anchor(federation) configuration using :

curl --request POST \
--url <root>/admin/realms/{realm-name}/openid-federations \
--header 'Authorization: Bearer {token} \
--header 'Content-Type: application/json' \
--data '{
  	"trustAnchor": "https://trust-anchor.sandbox.eosc.grnet.gr",
  	"clientRegistrationTypesSupported": [
  		"EXPLICIT"
  	],
  	"entityTypes": [
  		"OPENID_PROVIDER"
  	]
  }'
  1. After you will be able to observe the signed JWT Federation Entity Configuration from /realms/{realm-name}/.well-known/openid-federation as described in the documentaion

  2. For testing explicit registration, you will need to start server commenting the aud check in the class OpenIdFederationClientRegistrationService lines 146-148.

After you could execute the request :
curl --request POST \ --url <root>/realms/{realm-name}/openid-federation/clients-registrations \ --header 'Accept: application/entity-statement+jwt' \ --header 'Content-Type: application/entity-statement+jwt' \ --data {jwt}

For jwt you could use the signed JWT Federation Entity Configuration of our OpenID Federation RP signed JWT Entity Configuration, enrolled in the mentioned Trust Anchor (https://trust-anchor.sandbox.eosc.grnet.gr). This Trust Anchor is a docker deployment of an Trust Anchor implementation (see lighthouse deployment instructions.

After successfully request, an OIDC Client will be created, with only the difference that it will contain the attribute OIDCConfigAttributes.EXPIRATION_TIME. Response will contains the Explicit Client Registration Response.

  1. On the client attribute OIDCConfigAttributes.EXPIRATION_TIME time, client will be disabled. Before or after the client expiration, you could execute the same request with an updated JWT Entity Configuration in order to enable client and extend expiration.

  2. For a more real case scenario:

    • You could provide us the JWT Federation Entity Configuration from /realms/{realm-name}/.well-known/openid-federation. We could enroll this Keycloak as OP in our OpenID Federation (https://trust-anchor.sandbox.eosc.grnet.gr).
    • After I could execute from our egi-dev RP instance, a full explicit registration scenario. This will create your Keycloak instance as OpenID Federation in our egi-dev RP instance and a client of our egi-dev RP instance in your Keycloak. Maybe we will only need to add appropriate mappers and scopes for realising user attributes.
    • After you will be able to login in our egi-dev RP instance with your users (fe account console).

@cgeorgilakis
cgeorgilakis marked this pull request as draft July 28, 2025 06:34
@cgeorgilakis
Copy link
Copy Markdown
Contributor Author

I want here to mention that our proposed implementation is based on EntityStatement which is the default body parameter described in the specification (see chapters 12.2 and 10, appendix A.3.2).
Body with Trust Chains (alternative way based on protocol) as body return Response.Status.NOT_IMPLEMENTED. I personally do not know which should be the appropriate checks in that case. Unfortunatelly, OpenID Federation conformance tests are only based on this body type.

@cgeorgilakis
cgeorgilakis force-pushed the 40511_openid_federation_op branch 2 times, most recently from 431a482 to b107ae6 Compare July 31, 2025 12:03
@pruivo pruivo left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't review the business logic as it is not my area of expertise. Added some suggestions here and there.
Related to Infinispan, I suspect the TaskCancellationEvent is broken when running in a clustered environment.

@cgeorgilakis
cgeorgilakis force-pushed the 40511_openid_federation_op branch 3 times, most recently from c63cc6e to 7ab9481 Compare August 1, 2025 12:03
@cgeorgilakis
Copy link
Copy Markdown
Contributor Author

I have made the following changes:

  • Remove Policies. Policies are required and I can provide a PR for the related issue, after merging this PR.
  • If Trunst Anchor fetch endpoint contains RPMetadata (optional), RPMetadata for registration should be changes based on fetch endpoint.
  • Propagate task cancellation for client disable in other nodes
  • Implement PR comments by @pruivo

@cgeorgilakis
cgeorgilakis requested a review from pruivo August 1, 2025 12:08
@pruivo pruivo left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good to me from the Infinispan perspective. I notice some warning reports from IntelliJ during code analysis.

try {
statement = trustChainProcessor.parseAndValidateSelfSigned(body);
} catch (InvalidTrustChainException ex) {
ex.printStackTrace();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't print the stack trace to System.out. Log an error message.

}
}
} catch (Exception ex) {
ex.printStackTrace();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

jwtProcessor.process(token, null);

} catch(IOException | ParseException | BadJOSEException | JOSEException ex) {
ex.printStackTrace();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here :)

Comment on lines +38 to +39
private AdminPermissionEvaluator auth;
private AdminEventBuilder adminEvent;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those can be final too.

}

private ConfigurableJWTProcessor<SecurityContext> produceJwtProcessor(JSONWebKeySet jwks) throws IOException, ParseException {
String jsonKey = om.writeValueAsString(jwks);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure but I think you can remove om static field and use

Suggested change
String jsonKey = om.writeValueAsString(jwks);
String jsonKey = JsonSerialization.mapper.writeValueAsString(jwks);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could use JsonSerialization similar methods. I have removed om ObjectMapper.

package org.keycloak.models;

import java.io.Serializable;
import java.util.ArrayList;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused import

@@ -54,6 +56,7 @@
import org.keycloak.storage.UserStorageUtil;
import org.keycloak.storage.client.ClientStorageProvider;

import java.util.ArrayList;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused import

@@ -33,6 +33,8 @@
import jakarta.persistence.NamedQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;

import java.util.ArrayList;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused import

@@ -17,13 +17,15 @@

package org.keycloak.models;

import java.util.ArrayList;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused import

} catch (IllegalArgumentException e) {
return null;
}
} else if (alg instanceof String string) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intellij reports alg instanceof String string is always false. If so, it can be removed.

@cgeorgilakis
cgeorgilakis force-pushed the 40511_openid_federation_op branch from 4c1401f to a93b5a2 Compare August 7, 2025 12:45
@cgeorgilakis
Copy link
Copy Markdown
Contributor Author

It looks good to me from the Infinispan perspective. I notice some warning reports from IntelliJ during code analysis.

I have made the requested changes. See my extra comment.

@cgeorgilakis
cgeorgilakis requested a review from pruivo August 7, 2025 13:02
@stianst
stianst commented Sep 4, 2025
Copy link
Copy Markdown
Contributor

I think it would make a lot of sense to build on the concept of identity providers to provide this trust relationship rather than introducing a whole new thing. See #42197

Also, look at #42229 (client assertions via 3rd party OIDC) or #42314 (client assertions via SPIFFE).

Closes keycloak#40511

Signed-off-by: cgeorgilakis-grnet <cgeorgilakis@admin.grnet.gr>
@cgeorgilakis
cgeorgilakis force-pushed the 40511_openid_federation_op branch from 54f4346 to 1937821 Compare September 17, 2025 10:22
public void setOpenIdFederationList(List<OpenIdFederationEntity> openIdFederationList) {
this.openIdFederationList.clear();
if (openIdFederationList != null && !openIdFederationList.isEmpty()) {
this.openIdFederationList.addAll(openIdFederationList);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With my test code for Automatic Client Registration, an exception below is thrown here.

java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.addAll(ImmutableCollections.java:148)
at org.hibernate.collection.spi.PersistentBag.addAll(PersistentBag.java:435)
at org.keycloak.models.jpa.entities.RealmEntity.setOpenIdFederationList(RealmEntity.java:619)
at org.keycloak.models.jpa.RealmAdapter.setOpenIdFederationGeneralConfig(RealmAdapter.java:1178)


One of the way for you to fix that is the modification of line 153
List<OpenIdFederationEntity> openIdFederationList = List.of();
to
List<OpenIdFederationEntity> openIdFederationList = new ArrayList<>();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I have fixed it.

@cgeorgilakis
Copy link
Copy Markdown
Contributor Author

PR is closed, as it is old and we are reviewing our code with LLM for security fixes.
Moroever, need to be discussed for the way we will proceed with OPenId Federation epic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenID Federation OP with explicit registration

4 participants

0