SONAR-14175 Add a startup task to detect admin default credential usage and set reset_password flag to true
SONAR-14176 Warn administrators when default admin credential is detected
private static final String ADMINS_GROUP = "sonar-administrators";
private static final String USERS_GROUP = "sonar-users";
private static final String ADMIN_USER = "admin";
+ private static final String ADMIN_CRYPTED_PASSWORD = "$2a$12$uCkkXmhW5ThVK8mpBvnXOOJRLd64LJeHTeCkSuB3lfaR2N0AYBaSi";
private static final List<String> ADMIN_ROLES = Arrays.asList("admin", "profileadmin", "gateadmin", "provisioning", "applicationcreator", "portfoliocreator");
private final System2 system2;
"(uuid, login, name, email, external_id, external_login, external_identity_provider, user_local, crypted_password, salt, hash_method, is_root, onboarded, " +
"created_at, updated_at)" +
" values " +
- "(?, ?, 'Administrator', null, 'admin', 'admin', 'sonarqube', ?, '$2a$12$uCkkXmhW5ThVK8mpBvnXOOJRLd64LJeHTeCkSuB3lfaR2N0AYBaSi', null, 'BCRYPT', ?, ?, ?, ?)")
+ "(?, ?, 'Administrator', null, 'admin', 'admin', 'sonarqube', ?, ?, null, 'BCRYPT', ?, ?, ?, ?)")
.setString(1, uuidFactory.create())
.setString(2, ADMIN_USER)
.setBoolean(3, true)
- .setBoolean(4, false)
- .setBoolean(5, true)
- .setLong(6, now)
+ .setString(4, ADMIN_CRYPTED_PASSWORD)
+ .setBoolean(5, false)
+ .setBoolean(6, true)
.setLong(7, now)
+ .setLong(8, now)
.execute()
.commit();
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.startup;
+
+import org.picocontainer.Startable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.CredentialsLocalAuthentication;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
+
+/**
+ * Detect usage of an active admin account with default credential in order to ask this account to reset its password during authentication.
+ */
+public class DetectActiveAdminAccountWithDefaultCredential implements Startable {
+
+ private static final Logger LOGGER = Loggers.get(DetectActiveAdminAccountWithDefaultCredential.class);
+
+ private final DbClient dbClient;
+ private final CredentialsLocalAuthentication localAuthentication;
+
+ public DetectActiveAdminAccountWithDefaultCredential(DbClient dbClient, CredentialsLocalAuthentication localAuthentication) {
+ this.dbClient = dbClient;
+ this.localAuthentication = localAuthentication;
+ }
+
+ @Override
+ public void start() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ UserDto admin = dbClient.userDao().selectActiveUserByLogin(dbSession, "admin");
+ if (admin == null || !isDefaultCredentialUser(dbSession, admin)) {
+ return;
+ }
+ LOGGER.warn("*******************************************************************************************************************");
+ LOGGER.warn("Default Administrator credentials are still being used. Make sure to change the password or deactivate the account.");
+ LOGGER.warn("*******************************************************************************************************************");
+ dbClient.userDao().update(dbSession, admin.setResetPassword(true));
+ dbSession.commit();
+ }
+ }
+
+ private boolean isDefaultCredentialUser(DbSession dbSession, UserDto user) {
+ try {
+ localAuthentication.authenticate(dbSession, user, "admin", AuthenticationEvent.Method.BASIC);
+ return true;
+ } catch (AuthenticationException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public void stop() {
+ // Nothing to do
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.startup;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.CredentialsLocalAuthentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DetectActiveAdminAccountWithDefaultCredentialTest {
+
+ private static final String ADMIN_LOGIN = "admin";
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private final CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient());
+
+ private final DetectActiveAdminAccountWithDefaultCredential underTest = new DetectActiveAdminAccountWithDefaultCredential(db.getDbClient(), localAuthentication);
+
+ @After
+ public void after() {
+ underTest.stop();
+ }
+
+ @Test
+ public void set_reset_flag_to_true_and_add_log_when_admin_account_with_default_credential_is_detected() {
+ UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
+ changePassword(admin, "admin");
+
+ underTest.start();
+
+ assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isTrue();
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Default Administrator credentials are still being used. Make sure to change the password or deactivate the account.");
+ }
+
+ @Test
+ public void do_nothing_when_admin_is_not_using_default_credential() {
+ UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
+ changePassword(admin, "something_else");
+
+ underTest.start();
+
+ assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
+ assertThat(logTester.logs()).isEmpty();
+ }
+
+ @Test
+ public void do_nothing_when_no_admin_account_with_default_credential_detected() {
+ UserDto otherUser = db.users().insertUser();
+ changePassword(otherUser, "admin");
+
+ underTest.start();
+
+ assertThat(db.users().selectUserByLogin(otherUser.getLogin()).get().isResetPassword()).isFalse();
+ assertThat(logTester.logs()).isEmpty();
+ }
+
+ @Test
+ public void do_nothing_when_admin_account_with_default_credential_is_disabled() {
+ UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN).setActive(false));
+ changePassword(admin, "admin");
+
+ underTest.start();
+
+ assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
+ assertThat(logTester.logs()).isEmpty();
+ }
+
+ private void changePassword(UserDto user, String password) {
+ localAuthentication.storeHashPassword(user, password);
+ db.getDbClient().userDao().update(db.getSession(), user);
+ db.commit();
+ }
+}
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.NewUserNotifier;
-import org.sonar.server.user.UpdateUser;
import org.sonar.server.user.UserUpdater;
import org.sonar.server.user.index.UserIndexDefinition;
import org.sonar.server.user.index.UserIndexer;
private UserDto createLocalUser(String password) {
UserDto user = createLocalUser();
- userUpdater.updateAndCommit(db.getSession(), user, new UpdateUser().setLogin(user.getLogin()).setPassword(password), u -> {
- });
+ localAuthentication.storeHashPassword(user, password);
+ db.getDbClient().userDao().update(db.getSession(), user);
+ db.commit();
return user;
}
import org.sonar.server.qualityprofile.RegisterQualityProfiles;
import org.sonar.server.rule.RegisterRules;
import org.sonar.server.rule.WebServerRuleFinder;
+import org.sonar.server.startup.DetectActiveAdminAccountWithDefaultCredential;
import org.sonar.server.startup.GeneratePluginIndex;
import org.sonar.server.startup.RegisterMetrics;
import org.sonar.server.startup.RegisterPermissionTemplates;
import org.sonar.server.startup.RegisterPlugins;
import org.sonar.server.startup.RenameDeprecatedPropertyKeys;
+import org.sonar.server.startup.UpgradeSuggestionsCleaner;
import org.sonar.server.user.DoPrivileged;
import org.sonar.server.user.ThreadLocalUserSession;
-import org.sonar.server.startup.UpgradeSuggestionsCleaner;
public class PlatformLevelStartup extends PlatformLevel {
public PlatformLevelStartup(PlatformLevel parent) {
RegisterPermissionTemplates.class,
RenameDeprecatedPropertyKeys.class,
CeQueueCleaner.class,
- UpgradeSuggestionsCleaner.class);
+ UpgradeSuggestionsCleaner.class,
+ DetectActiveAdminAccountWithDefaultCredential.class);
// RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefor it
// must be started after all the other startup tasks