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 UserFederation SYSTEM "modules/user-federation.xml">
|
||||||
<!ENTITY Kerberos SYSTEM "modules/kerberos.xml">
|
<!ENTITY Kerberos SYSTEM "modules/kerberos.xml">
|
||||||
<!ENTITY ExportImport SYSTEM "modules/export-import.xml">
|
<!ENTITY ExportImport SYSTEM "modules/export-import.xml">
|
||||||
|
<!ENTITY AdminRecovery SYSTEM "modules/admin-recovery.xml">
|
||||||
<!ENTITY ServerCache SYSTEM "modules/cache.xml">
|
<!ENTITY ServerCache SYSTEM "modules/cache.xml">
|
||||||
<!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
|
<!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
|
||||||
<!ENTITY Clustering SYSTEM "modules/clustering.xml">
|
<!ENTITY Clustering SYSTEM "modules/clustering.xml">
|
||||||
|
@ -126,6 +127,7 @@ This one is short
|
||||||
&UserFederation;
|
&UserFederation;
|
||||||
&Kerberos;
|
&Kerberos;
|
||||||
&ExportImport;
|
&ExportImport;
|
||||||
|
&AdminRecovery;
|
||||||
&ServerCache;
|
&ServerCache;
|
||||||
&SAML;
|
&SAML;
|
||||||
&SecurityVulnerabilities;
|
&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);
|
KeycloakModelUtils.generateRealmKeys(realm);
|
||||||
|
|
||||||
UserModel adminUser = session.users().addUser(realm, "admin");
|
UserModel adminUser = session.users().addUser(realm, "admin");
|
||||||
|
setupAdminUser(session, realm, adminUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setupAdminUser(KeycloakSession session, RealmModel realm, UserModel adminUser) {
|
||||||
adminUser.setEnabled(true);
|
adminUser.setEnabled(true);
|
||||||
UserCredentialModel password = new UserCredentialModel();
|
UserCredentialModel password = new UserCredentialModel();
|
||||||
password.setType(UserCredentialModel.PASSWORD);
|
password.setType(UserCredentialModel.PASSWORD);
|
||||||
|
|
|
@ -42,6 +42,7 @@ import java.util.HashSet;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
import org.keycloak.offlineconfig.AdminRecovery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -88,6 +89,7 @@ public class KeycloakApplication extends Application {
|
||||||
importRealms(context);
|
importRealms(context);
|
||||||
migrateModel();
|
migrateModel();
|
||||||
|
|
||||||
|
AdminRecovery.recover(sessionFactory);
|
||||||
|
|
||||||
setupScheduledTasks(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