Skip to content

Commit

Permalink
Added event listener for temporary admin account logging
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Zaoral <[email protected]>
  • Loading branch information
Pepo48 committed Aug 7, 2024
1 parent c33296a commit 257867e
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
import java.util.List;
import java.util.Optional;

import static org.keycloak.services.managers.ApplianceBootstrap.TEMP_ADMIN_ATTR_NAME;

/**
* @author <a href="mailto:[email protected]">Marek Posolda</a>
*/
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
#

org.keycloak.events.email.EmailEventListenerProviderFactory
org.keycloak.events.log.JBossLoggingEventListenerProviderFactory
org.keycloak.events.log.JBossLoggingEventListenerProviderFactory
org.keycloak.events.log.TemporaryAdminAccountEventListenerProviderFactory
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public void defaultEventConfigTest() {
assertFalse(configRep.isEventsEnabled());

List<String> 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
Expand Down

0 comments on commit 257867e

Please sign in to comment.