From e617ef4d6c87ed73b60e3b304cce213019bd92d4 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Tue, 6 Aug 2024 23:03:10 +0200 Subject: [PATCH] Event listener for temporary admin account logging Signed-off-by: Peter Zaoral --- .../realm_settings/RealmSettingsPage.ts | 2 + ...raryAdminAccountEventListenerProvider.java | 51 +++++++++++++++++++ ...inAccountEventListenerProviderFactory.java | 37 ++++++++++++++ .../services/managers/RealmManager.java | 8 +-- ...ycloak.events.EventListenerProviderFactory | 3 +- .../admin/event/EventConfigTest.java | 4 +- 6 files changed, 99 insertions(+), 6 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/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts index 50d5092afbc0..ee5b746d4e6d 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts @@ -767,10 +767,12 @@ export default class RealmSettingsPage extends CommonPage { } shouldRemoveAllEventListeners() { + cy.get(".pf-v5-c-chip__actions").first().click(); cy.get(".pf-v5-c-chip__actions").first().click(); cy.get(".pf-v5-c-chip__actions").click(); cy.findByTestId(this.#eventListenersSaveBtn).click(); cy.get(this.#eventListenersDrpDwn).should("not.have.text", "jboss-logging"); + cy.get(this.#eventListenersDrpDwn).should("not.have.text", "temp-admin-account"); cy.get(this.#eventListenersDrpDwn).should("not.have.text", "email"); } 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/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