KEYCLOAK-1404: Need recovery mechanism for master admin user

This commit is contained in:
Stan Silvert 2015-06-04 15:29:44 -04:00
parent ea544c639e
commit 6812514683
6 changed files with 197 additions and 0 deletions

View file

@ -35,6 +35,7 @@
<!ENTITY UserFederation SYSTEM "modules/user-federation.xml">
<!ENTITY Kerberos SYSTEM "modules/kerberos.xml">
<!ENTITY ExportImport SYSTEM "modules/export-import.xml">
<!ENTITY AdminRecovery SYSTEM "modules/admin-recovery.xml">
<!ENTITY ServerCache SYSTEM "modules/cache.xml">
<!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
<!ENTITY Clustering SYSTEM "modules/clustering.xml">
@ -126,6 +127,7 @@ This one is short
&UserFederation;
&Kerberos;
&ExportImport;
&AdminRecovery;
&ServerCache;
&SAML;
&SecurityVulnerabilities;

View file

@ -0,0 +1,15 @@
<chapter id="admin-recovery">
<title>Recovering the Master Admin User</title>
<para>
It is possible for the "admin" user in the master realm to become inoperable. This may be because it was
accentally deleted, its role mappings were removed, or the password was simply forgotten.
</para>
<para>
To recover the master admin user, just start the server with the following system property:
<programlisting><![CDATA[
bin/standalone.sh -Dkeycloak.recover-admin=true
]]></programlisting>
Then you can log in to the master admin account with the default password "admin". You will then be
prompted to immediately change this password.
</para>
</chapter>

View file

@ -0,0 +1,80 @@
/*
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.offlineconfig;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.services.managers.ApplianceBootstrap;
/**
* Static utility class that performs recovery on the master admin account.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class AdminRecovery {
private static final Logger log = Logger.getLogger(AdminRecovery.class);
public static final String RECOVER_ADMIN_ACCOUNT = "keycloak.recover-admin";
// Don't allow instances
private AdminRecovery() {}
public static void recover(KeycloakSessionFactory sessionFactory) {
if (!needRecovery()) return;
KeycloakSession session = sessionFactory.create();
session.getTransaction().begin();
try {
doRecover(session);
session.getTransaction().commit();
log.info("*******************************");
log.info("Recovered Master Admin account.");
log.info("*******************************");
} finally {
session.close();
System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "false");
}
}
private static boolean needRecovery() {
String strNeedRecovery = System.getProperty(RECOVER_ADMIN_ACCOUNT, "false");
return Boolean.parseBoolean(strNeedRecovery);
}
private static void doRecover(KeycloakSession session) {
RealmProvider realmProvider = session.realms();
UserProvider userProvider = session.users();
String adminRealmName = Config.getAdminRealm();
RealmModel realm = realmProvider.getRealmByName(adminRealmName);
UserModel adminUser = userProvider.getUserByUsername("admin", realm);
if (adminUser == null) {
adminUser = userProvider.addUser(realm, "admin");
}
ApplianceBootstrap.setupAdminUser(session, realm, adminUser);
}
}

View file

@ -61,6 +61,10 @@ public class ApplianceBootstrap {
KeycloakModelUtils.generateRealmKeys(realm);
UserModel adminUser = session.users().addUser(realm, "admin");
setupAdminUser(session, realm, adminUser);
}
public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser) {
adminUser.setEnabled(true);
UserCredentialModel password = new UserCredentialModel();
password.setType(UserCredentialModel.PASSWORD);

View file

@ -42,6 +42,7 @@ import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import org.keycloak.offlineconfig.AdminRecovery;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -88,6 +89,7 @@ public class KeycloakApplication extends Application {
importRealms(context);
migrateModel();
AdminRecovery.recover(sessionFactory);
setupScheduledTasks(sessionFactory);
}

View file

@ -0,0 +1,94 @@
/*
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.testsuite.offlineconfig;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.offlineconfig.AdminRecovery;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebRule;
/**
* Test the AdminRecovery class.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class AdminRecoveryTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule();
@Rule
public WebRule webRule = new WebRule(this);
@Test
public void testAdminDeletedRecovery() {
KeycloakSession session = keycloakRule.startSession();
RealmModel masterRealm = session.realms().getRealmByName("master");
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
session.users().removeUser(masterRealm, adminUser);
adminUser = session.users().getUserByUsername("admin", masterRealm);
keycloakRule.stopSession(session, true);
Assert.assertNull(adminUser);
doAdminRecovery(session);
session = keycloakRule.startSession();
adminUser = session.users().getUserByUsername("admin", masterRealm);
Assert.assertNotNull(adminUser);
Assert.assertTrue(adminUser.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD.toString()));
}
@Test
public void testAdminPasswordRecovery() {
KeycloakSession session = keycloakRule.startSession();
RealmModel masterRealm = session.realms().getRealmByName("master");
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
UserCredentialValueModel password = adminUser.getCredentialsDirectly().get(0);
password.setValue("forgotten-password");
adminUser.updateCredentialDirectly(password);
keycloakRule.stopSession(session, true);
Assert.assertEquals("forgotten-password", getAdminPassword());
doAdminRecovery(session);
Assert.assertNotEquals("forgotten-password", getAdminPassword());
}
private void doAdminRecovery(KeycloakSession session) {
System.setProperty(AdminRecovery.RECOVER_ADMIN_ACCOUNT, "true");
AdminRecovery.recover(session.getKeycloakSessionFactory());
}
private String getAdminPassword() {
KeycloakSession session = keycloakRule.startSession();
RealmModel masterRealm = session.realms().getRealmByName("master");
UserModel adminUser = session.users().getUserByUsername("admin", masterRealm);
UserCredentialValueModel password = adminUser.getCredentialsDirectly().get(0);
keycloakRule.stopSession(session, true);
return password.getValue();
}
}