Skip to content

Commit 9bbf42b

Browse files
committed
Add quarkus.liquibase.secure-parsing to allow disabling secure parsing
Fixes quarkusio#47101
1 parent 2c3bf6d commit 9bbf42b

File tree

10 files changed

+1533
-29
lines changed

10 files changed

+1533
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.quarkus.liquibase.test;
2+
3+
import jakarta.inject.Inject;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
8+
import io.quarkus.liquibase.LiquibaseFactory;
9+
import io.quarkus.test.QuarkusUnitTest;
10+
import liquibase.Liquibase;
11+
12+
public class LiquibaseExtensionSecureParsingDisabledTest {
13+
14+
@Inject
15+
LiquibaseFactory liquibaseFactory;
16+
17+
@RegisterExtension
18+
static final QuarkusUnitTest config = new QuarkusUnitTest()
19+
.withApplicationRoot((jar) -> jar
20+
.addAsResource("insecure-db/changeLog.xml", "db/changeLog.xml")
21+
.addAsResource("insecure-db/dbchangelog-3.8.xsd", "db/dbchangelog-3.8.xsd")
22+
.addAsResource("secure-parsing-disabled.properties", "application.properties"));
23+
24+
@Test
25+
public void testSecureParsingDisabled() throws Exception {
26+
try (Liquibase liquibase = liquibaseFactory.createLiquibase()) {
27+
// TODO: this will fail as the system property is not enforced
28+
// List<ChangeSetStatus> status = liquibase.getChangeSetStatuses(liquibaseFactory.createContexts(),
29+
// liquibaseFactory.createLabels());
30+
// assertNotNull(status);
31+
// assertEquals(1, status.size());
32+
// assertEquals("id-1", status.get(0).getChangeSet().getId());
33+
// assertFalse(status.get(0).getWillRun());
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.quarkus.liquibase.test;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.fail;
5+
6+
import jakarta.inject.Inject;
7+
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.RegisterExtension;
10+
11+
import io.quarkus.liquibase.LiquibaseFactory;
12+
import io.quarkus.test.QuarkusUnitTest;
13+
14+
public class LiquibaseExtensionSecureParsingEnabledTest {
15+
16+
@Inject
17+
LiquibaseFactory liquibaseFactory;
18+
19+
@RegisterExtension
20+
static final QuarkusUnitTest config = new QuarkusUnitTest()
21+
.withApplicationRoot((jar) -> jar
22+
.addAsResource("insecure-db/changeLog.xml", "db/changeLog.xml")
23+
.addAsResource("insecure-db/dbchangelog-3.8.xsd", "db/dbchangelog-3.8.xsd")
24+
.addAsResource("secure-parsing-enabled.properties", "application.properties"))
25+
.assertException(t -> {
26+
assertThat(t.getCause().getCause())
27+
.hasMessageContaining("because 'file' access is not allowed");
28+
});
29+
30+
@Test
31+
public void testSecureParsing() throws Exception {
32+
fail("should not be executed");
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog db/dbchangelog-3.8.xsd"
4+
logicalFilePath="changeLog.xml">
5+
6+
<changeSet author="dev (generated)" id="id-1">
7+
<createTable tableName="TEST_INSECURE_PARSING">
8+
<column name="ID" type="VARCHAR(255)">
9+
<constraints nullable="false"/>
10+
</column>
11+
<column name="NAME" type="VARCHAR(255)"/>
12+
</createTable>
13+
</changeSet>
14+
15+
</databaseChangeLog>

extensions/liquibase/liquibase/deployment/src/test/resources/insecure-db/dbchangelog-3.8.xsd

Lines changed: 1381 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
quarkus.datasource.db-kind=h2
2+
quarkus.datasource.username=sa
3+
quarkus.datasource.password=sa
4+
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1
5+
# Liquibase config properties
6+
quarkus.liquibase.migrate-at-start=true
7+
quarkus.liquibase.secure-parsing=false
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
quarkus.datasource.db-kind=h2
2+
quarkus.datasource.username=sa
3+
quarkus.datasource.password=sa
4+
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1
5+
# Liquibase config properties
6+
quarkus.liquibase.migrate-at-start=true

extensions/liquibase/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.nio.file.Paths;
55
import java.sql.Connection;
66
import java.sql.DriverManager;
7+
import java.util.HashMap;
78
import java.util.Map;
89

910
import javax.sql.DataSource;
@@ -166,10 +167,19 @@ public String getDataSourceName() {
166167
}
167168

168169
public ResettableSystemProperties createResettableSystemProperties() {
169-
if (config.allowDuplicatedChangesetIdentifiers.isEmpty()) {
170-
return ResettableSystemProperties.empty();
170+
Map<String, String> resettableProperties = new HashMap<>();
171+
172+
if (config.allowDuplicatedChangesetIdentifiers.isPresent()) {
173+
resettableProperties.put("liquibase.allowDuplicatedChangesetIdentifiers",
174+
config.allowDuplicatedChangesetIdentifiers.get().toString());
171175
}
172-
return ResettableSystemProperties.of("liquibase.allowDuplicatedChangesetIdentifiers",
173-
config.allowDuplicatedChangesetIdentifiers.get().toString());
176+
177+
System.out.println("Secure parsing: " + config.secureParsing);
178+
179+
if (!config.secureParsing) {
180+
resettableProperties.put("liquibase.secureParsing", "false");
181+
}
182+
183+
return new ResettableSystemProperties(resettableProperties);
174184
}
175185
}

extensions/liquibase/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseConfig.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ public class LiquibaseConfig {
100100
/**
101101
* Allows duplicated changeset identifiers without failing Liquibase execution.
102102
*/
103-
public Optional<Boolean> allowDuplicatedChangesetIdentifiers;
103+
public Optional<Boolean> allowDuplicatedChangesetIdentifiers = Optional.empty();
104+
105+
/**
106+
* Whether Liquibase should enforce secure parsing.
107+
* <p>
108+
* If secure parsing is enforced, unsecure files may not be parsed.
109+
*/
110+
public boolean secureParsing = true;
104111

105112
}

extensions/liquibase/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseCreator.java

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,41 @@
66

77
class LiquibaseCreator {
88

9-
private final LiquibaseDataSourceRuntimeConfig liquibaseRuntimeConfig;
10-
private final LiquibaseDataSourceBuildTimeConfig liquibaseBuildTimeConfig;
9+
private final LiquibaseDataSourceRuntimeConfig liquibaseDatabaseRuntimeConfig;
10+
private final LiquibaseDataSourceBuildTimeConfig liquibaseDatabaseBuildTimeConfig;
1111

1212
public LiquibaseCreator(LiquibaseDataSourceRuntimeConfig liquibaseRuntimeConfig,
1313
LiquibaseDataSourceBuildTimeConfig liquibaseBuildTimeConfig) {
14-
this.liquibaseRuntimeConfig = liquibaseRuntimeConfig;
15-
this.liquibaseBuildTimeConfig = liquibaseBuildTimeConfig;
14+
this.liquibaseDatabaseRuntimeConfig = liquibaseRuntimeConfig;
15+
this.liquibaseDatabaseBuildTimeConfig = liquibaseBuildTimeConfig;
1616
}
1717

1818
public LiquibaseFactory createLiquibaseFactory(DataSource dataSource, String dataSourceName) {
1919
LiquibaseConfig config = new LiquibaseConfig();
20-
config.changeLog = liquibaseBuildTimeConfig.changeLog();
21-
config.searchPath = liquibaseBuildTimeConfig.searchPath();
22-
config.changeLogParameters = liquibaseRuntimeConfig.changeLogParameters();
20+
config.changeLog = liquibaseDatabaseBuildTimeConfig.changeLog();
21+
config.searchPath = liquibaseDatabaseBuildTimeConfig.searchPath();
22+
config.changeLogParameters = liquibaseDatabaseRuntimeConfig.changeLogParameters();
2323

24-
if (liquibaseRuntimeConfig.labels().isPresent()) {
25-
config.labels = liquibaseRuntimeConfig.labels().get();
24+
if (liquibaseDatabaseRuntimeConfig.labels().isPresent()) {
25+
config.labels = liquibaseDatabaseRuntimeConfig.labels().get();
2626
}
27-
if (liquibaseRuntimeConfig.contexts().isPresent()) {
28-
config.contexts = liquibaseRuntimeConfig.contexts().get();
27+
if (liquibaseDatabaseRuntimeConfig.contexts().isPresent()) {
28+
config.contexts = liquibaseDatabaseRuntimeConfig.contexts().get();
2929
}
30-
config.databaseChangeLogLockTableName = liquibaseRuntimeConfig.databaseChangeLogLockTableName();
31-
config.databaseChangeLogTableName = liquibaseRuntimeConfig.databaseChangeLogTableName();
32-
config.password = liquibaseRuntimeConfig.password();
33-
config.username = liquibaseRuntimeConfig.username();
34-
config.defaultSchemaName = liquibaseRuntimeConfig.defaultSchemaName();
35-
config.defaultCatalogName = liquibaseRuntimeConfig.defaultCatalogName();
36-
config.liquibaseTablespaceName = liquibaseRuntimeConfig.liquibaseTablespaceName();
37-
config.liquibaseSchemaName = liquibaseRuntimeConfig.liquibaseSchemaName();
38-
config.liquibaseCatalogName = liquibaseRuntimeConfig.liquibaseCatalogName();
39-
config.migrateAtStart = liquibaseRuntimeConfig.migrateAtStart();
40-
config.cleanAtStart = liquibaseRuntimeConfig.cleanAtStart();
41-
config.validateOnMigrate = liquibaseRuntimeConfig.validateOnMigrate();
42-
config.allowDuplicatedChangesetIdentifiers = liquibaseRuntimeConfig.allowDuplicatedChangesetIdentifiers();
30+
config.databaseChangeLogLockTableName = liquibaseDatabaseRuntimeConfig.databaseChangeLogLockTableName();
31+
config.databaseChangeLogTableName = liquibaseDatabaseRuntimeConfig.databaseChangeLogTableName();
32+
config.password = liquibaseDatabaseRuntimeConfig.password();
33+
config.username = liquibaseDatabaseRuntimeConfig.username();
34+
config.defaultSchemaName = liquibaseDatabaseRuntimeConfig.defaultSchemaName();
35+
config.defaultCatalogName = liquibaseDatabaseRuntimeConfig.defaultCatalogName();
36+
config.liquibaseTablespaceName = liquibaseDatabaseRuntimeConfig.liquibaseTablespaceName();
37+
config.liquibaseSchemaName = liquibaseDatabaseRuntimeConfig.liquibaseSchemaName();
38+
config.liquibaseCatalogName = liquibaseDatabaseRuntimeConfig.liquibaseCatalogName();
39+
config.migrateAtStart = liquibaseDatabaseRuntimeConfig.migrateAtStart();
40+
config.cleanAtStart = liquibaseDatabaseRuntimeConfig.cleanAtStart();
41+
config.validateOnMigrate = liquibaseDatabaseRuntimeConfig.validateOnMigrate();
42+
config.allowDuplicatedChangesetIdentifiers = liquibaseDatabaseRuntimeConfig.allowDuplicatedChangesetIdentifiers();
43+
config.secureParsing = liquibaseDatabaseRuntimeConfig.secureParsing();
4344
return new LiquibaseFactory(config, dataSource, dataSourceName);
4445
}
4546
}

extensions/liquibase/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseDataSourceRuntimeConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,11 @@ public interface LiquibaseDataSourceRuntimeConfig {
117117
*/
118118
Optional<Boolean> allowDuplicatedChangesetIdentifiers();
119119

120+
/**
121+
* Whether Liquibase should enforce secure parsing.
122+
* <p>
123+
* If secure parsing is enforced, insecure files may not be parsed.
124+
*/
125+
@WithDefault("true")
126+
boolean secureParsing();
120127
}

0 commit comments

Comments
 (0)