This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 8418a23e4 FINERACT-1724 Read only tenant password encryption migration 
- [x] ReadOnly tenant password encryption migration script - [x] Documentation 
- [x] Encryption password generator
8418a23e4 is described below

commit 8418a23e4b246c276ccadafa3196761234454a3e
Author: Janos Haber <[email protected]>
AuthorDate: Thu Mar 16 16:25:03 2023 +0100

    FINERACT-1724 Read only tenant password encryption migration
    - [x] ReadOnly tenant password encryption migration script
    - [x] Documentation
    - [x] Encryption password generator
---
 .../en/chapters/appendix/properties-database.adoc  | 10 +++
 .../docs/en/chapters/architecture/persistence.adoc | 19 +++++
 .../database/DatabasePasswordEncryptor.java        | 14 ++++
 .../TenantReadOnlyPasswordEncryptionTask.java      | 96 ++++++++++++++++++++++
 .../tenant-store/changelog-tenant-store.xml        |  1 +
 .../0008_encrypt_existing_ro_tenant_passwords.xml} |  8 +-
 6 files changed, 143 insertions(+), 5 deletions(-)

diff --git 
a/fineract-doc/src/docs/en/chapters/appendix/properties-database.adoc 
b/fineract-doc/src/docs/en/chapters/appendix/properties-database.adoc
index 834bec705..988a9c38f 100644
--- a/fineract-doc/src/docs/en/chapters/appendix/properties-database.adoc
+++ b/fineract-doc/src/docs/en/chapters/appendix/properties-database.adoc
@@ -49,6 +49,16 @@
 |Default Demo Tenant
 |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
 |FINERACT_LIQUIBASE_ENABLED
 |true
diff --git a/fineract-doc/src/docs/en/chapters/architecture/persistence.adoc 
b/fineract-doc/src/docs/en/chapters/architecture/persistence.adoc
index df5f4558c..94ef531a3 100644
--- a/fineract-doc/src/docs/en/chapters/architecture/persistence.adoc
+++ b/fineract-doc/src/docs/en/chapters/architecture/persistence.adoc
@@ -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.
 
+== 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
 
 The data-access layer of Fineract is implemented by using JPA (Java 
Persistence API) with the EclipseLink provider.
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabasePasswordEncryptor.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabasePasswordEncryptor.java
index 033a13669..7dec6c30b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabasePasswordEncryptor.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabasePasswordEncryptor.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.infrastructure.core.service.database;
 
 import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
@@ -37,6 +38,19 @@ public class DatabasePasswordEncryptor implements 
PasswordEncryptor {
 
     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
     public String encrypt(String plainPassword) {
         String masterPassword = 
Optional.ofNullable(fineractProperties.getTenant())
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantReadOnlyPasswordEncryptionTask.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantReadOnlyPasswordEncryptionTask.java
new file mode 100644
index 000000000..e019ae163
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantReadOnlyPasswordEncryptionTask.java
@@ -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);
+    }
+}
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
index 3cc73b305..42c4aba73 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
@@ -27,4 +27,5 @@
      <include file="parts/0005_jdbc_connection_string.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/0008_encrypt_existing_ro_tenant_passwords.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0008_encrypt_existing_ro_tenant_passwords.xml
similarity index 70%
copy from 
fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
copy to 
fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0008_encrypt_existing_ro_tenant_passwords.xml
index 3cc73b305..6d138f104 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0008_encrypt_existing_ro_tenant_passwords.xml
@@ -22,9 +22,7 @@
 <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";>
-     <include file="parts/0003_reset_postgresql_sequences.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0004_readonly_database_connection.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/0007_encrypt_existing_tenant_passwords.xml" 
relativeToChangelogFile="true"/>
+    <changeSet author="fineract" id="1" context="tenant_store_db">
+        <customChange 
class="org.apache.fineract.infrastructure.core.service.migration.TenantReadOnlyPasswordEncryptionTask"/>
+    </changeSet>
 </databaseChangeLog>

Reply via email to