FINERACT-1831 - Encrypted tenant passwords

- [x] Encrypt tenant passwords
- [x] Update integration tests
- [x] Check application database use encrypted passwords
- [x] One time migration for the existing password
This commit is contained in:
Janos Haber
2023-03-02 22:24:31 +01:00
committed by Arnold Gálovics
parent f1c89ac914
commit 3297707817
17 changed files with 419 additions and 147 deletions
@@ -22,10 +22,13 @@ import static org.mockito.Mockito.RETURNS_MOCKS;
import static org.mockito.Mockito.mock;
import com.zaxxer.hikari.HikariDataSource;
import java.util.List;
import javax.sql.DataSource;
import liquibase.change.custom.CustomTaskChange;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.database.DatabaseIndependentQueryService;
import org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
import org.apache.fineract.infrastructure.core.service.migration.ExtendedSpringLiquibaseFactory;
import org.apache.fineract.infrastructure.core.service.migration.TenantDataSourceFactory;
import org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseStateVerifier;
@@ -83,8 +86,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
public class TestConfiguration {
@Bean
public TenantDataSourceFactory tenantDataSourceFactory() {
return new TenantDataSourceFactory(null) {
public TenantDataSourceFactory tenantDataSourceFactory(DatabasePasswordEncryptor databasePasswordEncryptor) {
return new TenantDataSourceFactory(null, databasePasswordEncryptor) {
@Override
public DataSource create(FineractPlatformTenant tenant) {
@@ -151,9 +154,10 @@ public class TestConfiguration {
public TenantDatabaseUpgradeService tenantDatabaseUpgradeService(TenantDetailsService tenantDetailsService,
HikariDataSource tenantDataSource, TenantDatabaseStateVerifier tenantDatabaseStateVerifier,
ExtendedSpringLiquibaseFactory liquibaseFactory, TenantDataSourceFactory tenantDataSourceFactory,
FineractProperties fineractProperties, Environment environment) {
FineractProperties fineractProperties, Environment environment,
List<CustomTaskChange> customTaskChangesForDependencyInjection) {
return new TenantDatabaseUpgradeService(tenantDetailsService, tenantDataSource, fineractProperties, tenantDatabaseStateVerifier,
liquibaseFactory, tenantDataSourceFactory, environment);
liquibaseFactory, tenantDataSourceFactory, environment, customTaskChangesForDependencyInjection);
}
/**
@@ -38,7 +38,9 @@ import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
import org.apache.fineract.infrastructure.core.service.database.DataSourcePerTenantServiceFactory;
import org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
import org.apache.fineract.infrastructure.core.service.database.HikariDataSourceFactory;
import org.apache.fineract.infrastructure.security.utils.EncryptionUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -49,6 +51,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.security.crypto.bcrypt.BCrypt;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -81,6 +84,10 @@ public class DataSourcePerTenantServiceFactoryTest {
public static final String MASTER_DB_CONN_TEST_QUERY = "SELECT 1";
public static final boolean MASTER_DB_AUTO_COMMIT_ENABLED = true;
public static final String MASTER_MASTER_PASSWORD = "fineract";
public static final String MASTER_ENCRYPTION = "AES/CBC/PKCS5Padding";
@Mock
private FineractProperties fineractProperties;
@@ -102,6 +109,9 @@ public class DataSourcePerTenantServiceFactoryTest {
@Captor
private ArgumentCaptor<HikariConfig> hikariConfigCaptor;
@Mock
private DatabasePasswordEncryptor databasePasswordEncryptor;
@InjectMocks
private DataSourcePerTenantServiceFactory underTest;
@@ -119,15 +129,19 @@ public class DataSourcePerTenantServiceFactoryTest {
given(tenantConnection.getSchemaServerPort()).willReturn(MASTER_DB_SERVER_PORT);
given(tenantConnection.getSchemaName()).willReturn(MASTER_DB_SCHEMA_NAME);
given(tenantConnection.getSchemaUsername()).willReturn(MASTER_DB_USERNAME);
given(tenantConnection.getSchemaPassword()).willReturn(MASTER_DB_PASSWORD);
given(tenantConnection.getSchemaPassword())
.willReturn(EncryptionUtil.encryptToBase64(MASTER_ENCRYPTION, MASTER_MASTER_PASSWORD, MASTER_DB_PASSWORD));
given(tenantConnection.getSchemaConnectionParameters()).willReturn(MASTER_DB_CONN_PARAMS);
given(tenantConnection.getReadOnlySchemaServer()).willReturn(READONLY_DB_SERVER);
given(tenantConnection.getReadOnlySchemaServerPort()).willReturn(READONLY_DB_SERVER_PORT);
given(tenantConnection.getReadOnlySchemaName()).willReturn(READONLY_DB_SCHEMA_NAME);
given(tenantConnection.getReadOnlySchemaUsername()).willReturn(READONLY_DB_USERNAME);
given(tenantConnection.getReadOnlySchemaPassword()).willReturn(READONLY_DB_PASSWORD);
given(tenantConnection.getReadOnlySchemaPassword())
.willReturn(EncryptionUtil.encryptToBase64(MASTER_ENCRYPTION, MASTER_MASTER_PASSWORD, READONLY_DB_PASSWORD));
given(tenantConnection.getReadOnlySchemaConnectionParameters()).willReturn(READONLY_DB_CONN_PARAMS);
String hashedMasterPassword = BCrypt.hashpw("master-password", BCrypt.gensalt());
given(tenantConnection.getMasterPasswordHash()).willReturn(hashedMasterPassword);
given(defaultTenant.getConnection()).willReturn(tenantConnection);
given(tenantConnection.getInitialSize()).willReturn(MASTER_DB_INITIAL_SIZE);
@@ -141,6 +155,17 @@ public class DataSourcePerTenantServiceFactoryTest {
given(tenantHikariConfig.isAutoCommit()).willReturn(MASTER_DB_AUTO_COMMIT_ENABLED);
given(hikariDataSourceFactory.create(any())).willReturn(mock(HikariDataSource.class));
FineractProperties.FineractTenantProperties tenantPropertiesMock = mock(FineractProperties.FineractTenantProperties.class);
given(tenantPropertiesMock.getEncryption()).willReturn(MASTER_ENCRYPTION);
given(tenantPropertiesMock.getMasterPassword()).willReturn(MASTER_MASTER_PASSWORD);
given(fineractProperties.getTenant()).willReturn(tenantPropertiesMock);
given(databasePasswordEncryptor.isMasterPasswordHashValid(any())).willReturn(true);
given(databasePasswordEncryptor.getMasterPasswordHash()).willReturn(hashedMasterPassword);
given(databasePasswordEncryptor.decrypt(any())).will(
answer -> EncryptionUtil.decryptFromBase64(MASTER_ENCRYPTION, MASTER_MASTER_PASSWORD, answer.getArgument(0, String.class)));
given(databasePasswordEncryptor.encrypt(any())).will(
answer -> EncryptionUtil.encryptToBase64(MASTER_ENCRYPTION, MASTER_MASTER_PASSWORD, answer.getArgument(0, String.class)));
}
@Test
@@ -29,16 +29,19 @@ import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.cucumber.java8.En;
import java.util.Arrays;
import java.util.List;
import javax.sql.DataSource;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
import org.apache.fineract.infrastructure.core.service.migration.ExtendedSpringLiquibase;
import org.apache.fineract.infrastructure.core.service.migration.ExtendedSpringLiquibaseFactory;
import org.apache.fineract.infrastructure.core.service.migration.SchemaUpgradeNeededException;
import org.apache.fineract.infrastructure.core.service.migration.TenantDataSourceFactory;
import org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseStateVerifier;
import org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService;
import org.apache.fineract.infrastructure.core.service.migration.TenantPasswordEncryptionTask;
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
@@ -65,6 +68,8 @@ public class LiquibaseStepDefinitions implements En {
private DataSource defaultTenantDataSource;
private Environment environment;
private DatabasePasswordEncryptor databasePasswordEncryptor;
public LiquibaseStepDefinitions() {
Given("Liquibase is disabled with a default tenant", () -> {
initializeLiquibase(false);
@@ -169,6 +174,8 @@ public class LiquibaseStepDefinitions implements En {
defaultTenantDataSource = mock(DataSource.class);
TenantPasswordEncryptionTask tenantPasswordEncryptor = mock(TenantPasswordEncryptionTask.class);
given(databaseStateVerifier.isLiquibaseDisabled()).willReturn(!liquibaseEnabled);
given(liquibaseFactory.create(tenantStoreDataSource, "tenant_store_db", "initial_switch")).willReturn(initialTenantStoreLiquibase);
given(liquibaseFactory.create(tenantStoreDataSource, "tenant_store_db")).willReturn(tenantStoreLiquibase);
@@ -180,6 +187,6 @@ public class LiquibaseStepDefinitions implements En {
given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db", "custom_changelog")).willReturn(customChangeLogLiquibase);
tenantDatabaseUpgradeService = new TenantDatabaseUpgradeService(tenantDetailsService, tenantStoreDataSource, fineractProperties,
databaseStateVerifier, liquibaseFactory, tenantDataSourceFactory, environment);
databaseStateVerifier, liquibaseFactory, tenantDataSourceFactory, environment, Arrays.asList(tenantPasswordEncryptor));
}
}
@@ -32,6 +32,8 @@ fineract.tenant.timezone=Asia/Kolkata
fineract.tenant.identifier=default
fineract.tenant.name=fineract_default
fineract.tenant.description=Default Demo Tenant
fineract.tenant.master-password=fineract
fineract.tenant.encrytion="AES/CBC/PKCS5Padding"
fineract.mode.read-enabled=true
fineract.mode.write-enabled=true