Make client and IDP required when using federated client authentication (#43890)

Closes #43889

Signed-off-by: Alexander Schwartz <alexander.schwartz@gmx.net>
This commit is contained in:
Alexander Schwartz 2025-11-03 07:21:55 +01:00 committed by GitHub
parent e84a1d6363
commit 52ba359cc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 28 additions and 12 deletions

View File

@ -31,7 +31,7 @@ fips-mode-setup --enable
== BouncyCastle library
{project_name} internally uses the BouncyCastle library for many cryptography utilities. Please note that the default version of the BouncyCastle library that shipped with {project_name} is not FIPS compliant;
however, BouncyCastle also provides a FIPS validated version of its library. The FIPS validated BouncyCastle library is not shipped with {project_name} as
however, BouncyCastle also provides a FIPS validated version of its library. The FIPS validated BouncyCastle library is not shipped with {project_name} as
{project_name} cannot provide official support of it. Therefore, to run in FIPS compliant mode, you need to download BouncyCastle-FIPS bits and add them to the {project_name} distribution.
When {project_name} executes in fips mode, it will use the BCFIPS bits instead of the default BouncyCastle bits, which achieves FIPS compliance.
@ -150,7 +150,7 @@ the older `pbkdf2-sha256` can log in because their passwords may be shorter than
* RSA keys of 1024 bits do not work (2048 is the minimum). This applies for keys used by the {project_name} realm itself (Realm keys from the `Keys` tab in the admin console), but also client keys and IDP keys
* HMAC SHA-XXX keys must be at least 112 bits (or 14 characters long). For example if you use OIDC clients with the client authentication `Signed Jwt with Client Secret` (or `client-secret-jwt` in
* HMAC SHA-XXX keys must be at least 112 bits (or 14 characters long). For example if you use OIDC clients with the client authentication `Signed JWT with Client Secret` (or `client-secret-jwt` in
the OIDC notation), then your client secrets should be at least 14 characters long. Note that for good security, it is recommended to use client secrets generated by the {project_name} server, which
always fulfils this requirement.

View File

@ -943,6 +943,7 @@ addSamlProvider=Add SAML provider
addSpiffeProvider=Add SPIFFE provider
addKubernetesProvider=Add Kubernetes provider
spiffeTrustDomain=SPIFFE Trust Domain
spiffeTrustDomainHelp=Use a URL starting with 'spiffe://' followed by a domain name. For example, 'spiffe://acme.com'.
spiffeBundleEndpoint=SPIFFE Bundle or OIDC JWKs endpoint
kubernetesJWKSURL=Kubernetes JWKS URL
kubernetesJWKSURLHelp=Use Kubernetes JWKS URL when accessing an external Kubernetes cluster. The JWKS endpoint must not require authentication

View File

@ -18,6 +18,7 @@ export const SpiffeSettings = () => {
<TextControl
name="config.issuer"
label={t("spiffeTrustDomain")}
labelIcon={t("spiffeTrustDomainHelp")}
rules={{
required: t("required"),
}}
@ -26,6 +27,7 @@ export const SpiffeSettings = () => {
<TextControl
name="config.bundleEndpoint"
label={t("spiffeBundleEndpoint")}
labelIcon={t("Specify a URL starting with 'https://'.")}
rules={{
required: t("required"),
}}

View File

@ -16,6 +16,7 @@ import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.services.resources.IdentityBrokerService;
import java.util.Collections;
@ -32,10 +33,22 @@ public class FederatedJWTClientAuthenticator extends AbstractClientAuthenticator
public static final String JWT_CREDENTIAL_ISSUER_KEY = "jwt.credential.issuer";
public static final String JWT_CREDENTIAL_SUBJECT_KEY = "jwt.credential.sub";
private static final List<ProviderConfigProperty> CLIENT_CONFIG = List.of(
new ProviderConfigProperty(JWT_CREDENTIAL_ISSUER_KEY, "Identity provider", "Issuer of the client assertion", ProviderConfigProperty.STRING_TYPE, null),
new ProviderConfigProperty(JWT_CREDENTIAL_SUBJECT_KEY, "Federated subject", "External clientId (subject)", ProviderConfigProperty.STRING_TYPE, null)
);
private static final List<ProviderConfigProperty> CLIENT_CONFIG =
ProviderConfigurationBuilder.create()
.property()
.name(JWT_CREDENTIAL_ISSUER_KEY)
.label("Identity provider")
.helpText("Issuer of the client assertion. Use the alias of an identity provider set up in this realm.")
.type(ProviderConfigProperty.STRING_TYPE)
.required(true)
.add()
.property().name(JWT_CREDENTIAL_SUBJECT_KEY)
.label("Federated subject")
.helpText("External clientId (subject) as provided by the identity provider.")
.type(ProviderConfigProperty.STRING_TYPE)
.required(true)
.add()
.build();
private static final Set<String> SUPPORTED_ASSERTION_TYPES = Set.of(OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT, SpiffeConstants.CLIENT_ASSERTION_TYPE);

View File

@ -122,7 +122,7 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
@Override
public String getDisplayType() {
return "Signed Jwt";
return "Signed JWT";
}
@Override

View File

@ -167,7 +167,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
@Override
public String getDisplayType() {
return "Signed Jwt with Client Secret";
return "Signed JWT with Client Secret";
}
@Override

View File

@ -161,8 +161,8 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
execs = new LinkedList<>();
addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 10);
addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 20);
addExecInfo(execs, "Signed Jwt with Client Secret", "client-secret-jwt", false, 0, 2, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 30);
addExecInfo(execs, "Signed JWT", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 20);
addExecInfo(execs, "Signed JWT with Client Secret", "client-secret-jwt", false, 0, 2, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 30);
addExecInfo(execs, "X509 Certificate", "client-x509", false, 0, 3, ALTERNATIVE, null, new String[]{REQUIRED, ALTERNATIVE, DISABLED}, 40);
expected.add(new FlowExecutions(flow, execs));

View File

@ -82,13 +82,13 @@ public class ProvidersTest extends AbstractAuthenticationTest {
List<Map<String, Object>> result = authMgmtResource.getClientAuthenticatorProviders();
List<Map<String, Object>> expected = new LinkedList<>();
addClientAuthenticatorProviderInfo(expected, "client-jwt", "Signed Jwt",
addClientAuthenticatorProviderInfo(expected, "client-jwt", "Signed JWT",
"Validates client based on signed JWT issued by client and signed with the Client private key", false);
addClientAuthenticatorProviderInfo(expected, "client-secret", "Client Id and Secret", "Validates client based on 'client_id' and " +
"'client_secret' sent either in request parameters or in 'Authorization: Basic' header", true);
addClientAuthenticatorProviderInfo(expected, "client-x509", "X509 Certificate",
"Validates client based on a X509 Certificate", false);
addClientAuthenticatorProviderInfo(expected, "client-secret-jwt", "Signed Jwt with Client Secret",
addClientAuthenticatorProviderInfo(expected, "client-secret-jwt", "Signed JWT with Client Secret",
"Validates client based on signed JWT issued by client and signed with the Client Secret", true);
compareProviders(expected, result);