KEYCLOAK-1404: Need recovery mechanism for master admin user
This commit is contained in:
parent
ea544c639e
commit
6812514683
6 changed files with 197 additions and 0 deletions
|
@ -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;
|
||||
|
|
15
docbook/reference/en/en-US/modules/admin-recovery.xml
Executable file
15
docbook/reference/en/en-US/modules/admin-recovery.xml
Executable 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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue