FINERACT-1724 Read only tenant password encryption migration
- [x] ReadOnly tenant password encryption migration script - [x] Documentation - [x] Encryption password generator
This commit is contained in:
committed by
Arnold Gálovics
parent
e41726edfd
commit
8418a23e4b
@@ -49,6 +49,16 @@
|
|||||||
|Default Demo Tenant
|
|Default Demo Tenant
|
||||||
|TBD
|
|TBD
|
||||||
|
|
||||||
|
|fineract.tenant.master-password
|
||||||
|
|FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORD
|
||||||
|
|fineract
|
||||||
|
|TBD
|
||||||
|
|
||||||
|
|fineract.tenant.encrytion
|
||||||
|
|FINERACT_DEFAULT_TENANTDB_ENCRYPTION
|
||||||
|
|AES/CBC/PKCS5Padding
|
||||||
|
|TBD
|
||||||
|
|
||||||
|spring.liquibase.enabled
|
|spring.liquibase.enabled
|
||||||
|FINERACT_LIQUIBASE_ENABLED
|
|FINERACT_LIQUIBASE_ENABLED
|
||||||
|true
|
|true
|
||||||
|
|||||||
@@ -31,6 +31,25 @@ The currently supported JDBC driver and corresponding mappings can be found belo
|
|||||||
|
|
||||||
The actual code can be found in the `DatabaseTypeResolver` class.
|
The actual code can be found in the `DatabaseTypeResolver` class.
|
||||||
|
|
||||||
|
== Tenant database security
|
||||||
|
|
||||||
|
The tenant database schema password is stored in the `tenant_server_connections` table in the tenant database.
|
||||||
|
The password and the read only schema password are encrypted using the `fineract.tenant.master-password` property.
|
||||||
|
By default, the database property will be encrypted in the first start from a plane text.
|
||||||
|
|
||||||
|
When you want to generate a new encrypted password, you can use the `org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor` class.
|
||||||
|
|
||||||
|
=== Database password encryption usage
|
||||||
|
```
|
||||||
|
java -cp fineract-provider.jar -Dloader.main=org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor org.springframework.boot.loader.PropertiesLauncher <masterPassword> <plainPassword>
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
java -cp fineract-provider-0.0.0-48f7e315.jar -Dloader.main=org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor org.springframework.boot.loader.PropertiesLauncher fineract-master-password fineract-tenant-password
|
||||||
|
The encrypted password: VLwGl7vOP/q275ZTku+PNGWnGwW4mzzNHSNaO9Pr67WT5/NZMpBr9tGYYiYsqwL1eRew2jl7O3/N1EFbLlXhSA==
|
||||||
|
```
|
||||||
|
|
||||||
== Data-access layer
|
== Data-access layer
|
||||||
|
|
||||||
The data-access layer of Fineract is implemented by using JPA (Java Persistence API) with the EclipseLink provider.
|
The data-access layer of Fineract is implemented by using JPA (Java Persistence API) with the EclipseLink provider.
|
||||||
|
|||||||
+14
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.infrastructure.core.service.database;
|
package org.apache.fineract.infrastructure.core.service.database;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
@@ -37,6 +38,19 @@ public class DatabasePasswordEncryptor implements PasswordEncryptor {
|
|||||||
|
|
||||||
private final FineractProperties fineractProperties;
|
private final FineractProperties fineractProperties;
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:regexpsinglelinejava")
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
System.out.println(
|
||||||
|
"Usage: java -cp fineract-provider.jar java -Dloader.main=org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor org.springframework.boot.loader.PropertiesLauncher <masterPassword> <plainPassword>");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
String masterPassword = args[0];
|
||||||
|
String plainPassword = args[1];
|
||||||
|
String encryptedPassword = EncryptionUtil.encryptToBase64(DEFAULT_ENCRYPTION, masterPassword, plainPassword);
|
||||||
|
System.out.println(MessageFormat.format("The encrypted password: {0}", encryptedPassword));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String encrypt(String plainPassword) {
|
public String encrypt(String plainPassword) {
|
||||||
String masterPassword = Optional.ofNullable(fineractProperties.getTenant())
|
String masterPassword = Optional.ofNullable(fineractProperties.getTenant())
|
||||||
|
|||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.apache.fineract.infrastructure.core.service.migration;
|
||||||
|
|
||||||
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import liquibase.change.custom.CustomTaskChange;
|
||||||
|
import liquibase.database.Database;
|
||||||
|
import liquibase.database.jvm.JdbcConnection;
|
||||||
|
import liquibase.exception.CustomChangeException;
|
||||||
|
import liquibase.exception.SetupException;
|
||||||
|
import liquibase.exception.ValidationErrors;
|
||||||
|
import liquibase.resource.ResourceAccessor;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class TenantReadOnlyPasswordEncryptionTask implements CustomTaskChange, ApplicationContextAware {
|
||||||
|
|
||||||
|
private static DatabasePasswordEncryptor databasePasswordEncryptor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Database database) throws CustomChangeException {
|
||||||
|
JdbcConnection dbConn = (JdbcConnection) database.getConnection(); // autocommit is false
|
||||||
|
try (Statement selectStatement = dbConn.createStatement(); Statement updateStatement = dbConn.createStatement()) {
|
||||||
|
|
||||||
|
try (ResultSet rs = selectStatement.executeQuery(
|
||||||
|
"SELECT id, readonly_schema_password FROM tenant_server_connections WHERE readonly_schema_password IS NOT NULL")) {
|
||||||
|
while (rs.next()) {
|
||||||
|
String id = rs.getString("id");
|
||||||
|
String readOnlySchemaPassword = rs.getString("readonly_schema_password");
|
||||||
|
String encryptedPassword = TenantReadOnlyPasswordEncryptionTask.databasePasswordEncryptor
|
||||||
|
.encrypt(readOnlySchemaPassword);
|
||||||
|
|
||||||
|
String updateSql = String.format(
|
||||||
|
"update tenant_server_connections set readonly_schema_password = '%s', master_password_hash = '%s' where id = %s",
|
||||||
|
encryptedPassword, TenantReadOnlyPasswordEncryptionTask.databasePasswordEncryptor.getMasterPasswordHash(), id);
|
||||||
|
updateStatement.execute(updateSql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CustomChangeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfirmationMessage() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() throws SetupException {
|
||||||
|
// Not required
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFileOpener(ResourceAccessor resourceAccessor) {
|
||||||
|
// Not required
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValidationErrors validate(Database database) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("static-access")
|
||||||
|
@SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
TenantReadOnlyPasswordEncryptionTask.databasePasswordEncryptor = applicationContext.getBean(DatabasePasswordEncryptor.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -27,4 +27,5 @@
|
|||||||
<include file="parts/0005_jdbc_connection_string.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0005_jdbc_connection_string.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0006_drop_retry_parameter_columns.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0006_drop_retry_parameter_columns.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0007_encrypt_existing_tenant_passwords.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0007_encrypt_existing_tenant_passwords.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0008_encrypt_existing_ro_tenant_passwords.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you 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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
<changeSet author="fineract" id="1" context="tenant_store_db">
|
||||||
|
<customChange class="org.apache.fineract.infrastructure.core.service.migration.TenantReadOnlyPasswordEncryptionTask"/>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
Reference in New Issue
Block a user