From 257867ea57f19840f64b709f93228085622e8666 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Tue, 6 Aug 2024 23:03:10 +0200 Subject: [PATCH] Added event listener for temporary admin account logging Signed-off-by: Peter Zaoral --- .../ClientAuthenticationFlow.java | 5 -- ...raryAdminAccountEventListenerProvider.java | 51 +++++++++++++++++++ ...inAccountEventListenerProviderFactory.java | 37 ++++++++++++++ .../managers/AuthenticationManager.java | 9 ---- .../services/managers/RealmManager.java | 8 +-- ...ycloak.events.EventListenerProviderFactory | 3 +- .../admin/event/EventConfigTest.java | 4 +- 7 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProvider.java create mode 100644 services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProviderFactory.java diff --git a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java index a5ff2bed581a..8f92018dbd16 100755 --- a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java +++ b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlow.java @@ -33,8 +33,6 @@ import java.util.List; import java.util.Optional; -import static org.keycloak.services.managers.ApplianceBootstrap.TEMP_ADMIN_ATTR_NAME; - /** * @author Marek Posolda */ @@ -98,9 +96,6 @@ public Response processFlow() { } logger.debugv("Client {0} authenticated by {1}", client.getClientId(), factory.getId()); - if (Boolean.parseBoolean(client.getAttribute(TEMP_ADMIN_ATTR_NAME))) { - logger.warn(client.getClientId() + " is a temporary admin service account. To harden security, create a permanent account and delete the temporary one."); - } processor.getEvent().detail(Details.CLIENT_AUTH_METHOD, factory.getId()); return null; } diff --git a/services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProvider.java b/services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProvider.java new file mode 100644 index 000000000000..fc4092b20b0c --- /dev/null +++ b/services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProvider.java @@ -0,0 +1,51 @@ +package org.keycloak.events.log; + +import org.jboss.logging.Logger; +import org.keycloak.events.Event; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.EventType; +import org.keycloak.events.admin.AdminEvent; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RealmProvider; +import org.keycloak.models.UserModel; + +import static org.keycloak.services.managers.ApplianceBootstrap.TEMP_ADMIN_ATTR_NAME; + +public class TemporaryAdminAccountEventListenerProvider implements EventListenerProvider { + + private static final Logger log = Logger.getLogger(TemporaryAdminAccountEventListenerProvider.class); + + private final KeycloakSession session; + private final RealmProvider realmModel; + + public TemporaryAdminAccountEventListenerProvider(KeycloakSession session) { + this.session = session; + this.realmModel = session.realms(); + } + + @Override + public void onEvent(Event event) { + RealmModel realm = this.realmModel.getRealm(event.getRealmId()); + UserModel user = this.session.users().getUserById(realm, event.getUserId()); + ClientModel client = this.session.clients().getClientByClientId(realm, event.getClientId()); + + if (EventType.LOGIN.equals(event.getType()) && Boolean.parseBoolean(user.getFirstAttribute(TEMP_ADMIN_ATTR_NAME))) { + log.warn(user.getUsername() + " is a temporary admin user account. To harden security, create a permanent account and delete the temporary one."); + } + + if (EventType.CLIENT_LOGIN.equals(event.getType()) && Boolean.parseBoolean(client.getAttribute(TEMP_ADMIN_ATTR_NAME))) { + log.warn(client.getClientId() + " is a temporary admin service account. To harden security, create a permanent account and delete the temporary one."); + } + } + + @Override + public void onEvent(AdminEvent adminEvent, boolean b) { + } + + @Override + public void close() { + } + +} diff --git a/services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProviderFactory.java b/services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProviderFactory.java new file mode 100644 index 000000000000..0eea5dac16c4 --- /dev/null +++ b/services/src/main/java/org/keycloak/events/log/TemporaryAdminAccountEventListenerProviderFactory.java @@ -0,0 +1,37 @@ +package org.keycloak.events.log; + +import org.keycloak.Config; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.EventListenerProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +public class TemporaryAdminAccountEventListenerProviderFactory implements EventListenerProviderFactory { + + public static final String ID = "temp-admin-account"; + + @Override + public EventListenerProvider create(KeycloakSession keycloakSession) { + return new TemporaryAdminAccountEventListenerProvider(keycloakSession); + } + + @Override + public void init(Config.Scope scope) { + + } + + @Override + public void postInit(KeycloakSessionFactory keycloakSessionFactory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return ID; + } +} diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 85186b7541f4..acba5312c3b5 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -117,7 +117,6 @@ import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID; import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow; -import static org.keycloak.services.managers.ApplianceBootstrap.TEMP_ADMIN_ATTR_NAME; /** * Stateless object that manages authentication @@ -1040,14 +1039,6 @@ public static Response finishedRequiredActions(KeycloakSession session, Authenti event.event(EventType.LOGIN); event.session(userSession); event.success(); - - UserModel authUser = userSession.getUser(); - boolean isTemporaryAdmin = Boolean.parseBoolean(authUser.getFirstAttribute(TEMP_ADMIN_ATTR_NAME)); - if (isTemporaryAdmin) { - logger.warnf("%s is a temporary admin user account. To harden security, " + - "create a permanent admin account and delete the temporary one.", authUser.getUsername()); - } - return redirectAfterSuccessfulFlow(session, realm, userSession, clientSessionCtx, request, uriInfo, clientConnection, event, authSession); } diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 6980bda9207a..187080d8c721 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -63,6 +63,8 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; + import org.keycloak.utils.ReservedCharValidator; import org.keycloak.utils.StringUtil; @@ -261,7 +263,7 @@ protected void setupRealmDefaults(RealmModel realm) { realm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY); realm.setLoginWithEmailAllowed(true); - realm.setEventsListeners(Collections.singleton("jboss-logging")); + realm.setEventsListeners(Set.of("jboss-logging", "temp-admin-account")); } public boolean removeRealm(RealmModel realm) { @@ -644,7 +646,7 @@ protected void rollbackImpl() { } private String determineDefaultRoleName(RealmRepresentation rep) { - String defaultRoleName = Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + rep.getRealm().toLowerCase(); + String defaultRoleName = Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + rep.getRealm().toLowerCase(); if (! hasRealmRole(rep, defaultRoleName)) { return defaultRoleName; } else { @@ -778,7 +780,7 @@ public void setupClientServiceAccountsAndAuthorizationOnImport(RealmRepresentati ClientModel clientModel = Optional.ofNullable(client.getId()) .map(realmModel::getClientById) .orElseGet(() -> realmModel.getClientByClientId(client.getClientId())); - + if (clientModel == null) { throw new RuntimeException("Cannot find provided client by dir import."); } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory index 5bc643a7f6ab..e5a5d5e349fd 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory @@ -16,4 +16,5 @@ # org.keycloak.events.email.EmailEventListenerProviderFactory -org.keycloak.events.log.JBossLoggingEventListenerProviderFactory \ No newline at end of file +org.keycloak.events.log.JBossLoggingEventListenerProviderFactory +org.keycloak.events.log.TemporaryAdminAccountEventListenerProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/EventConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/EventConfigTest.java index c3077dd2b95d..430a654b08f2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/EventConfigTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/EventConfigTest.java @@ -42,8 +42,8 @@ public void defaultEventConfigTest() { assertFalse(configRep.isEventsEnabled()); List eventListeners = configRep.getEventsListeners(); - assertEquals(1, eventListeners.size()); - assertEquals("jboss-logging", eventListeners.get(0)); + assertEquals(2, eventListeners.size()); + assertTrue(eventListeners.containsAll(Arrays.asList("jboss-logging", "temp-admin-account"))); } @Test