Skip to content

Commit 1f45299

Browse files
committed
Add quarkus.liquibase.secure-parsing to allow disabling secure parsing
Fixes #47101
1 parent 4c5720e commit 1f45299

11 files changed

+1538
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.quarkus.liquibase.test;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
7+
import java.util.List;
8+
9+
import jakarta.inject.Inject;
10+
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.RegisterExtension;
13+
14+
import io.quarkus.liquibase.LiquibaseFactory;
15+
import io.quarkus.test.QuarkusUnitTest;
16+
import liquibase.Liquibase;
17+
import liquibase.changelog.ChangeSetStatus;
18+
19+
public class LiquibaseExtensionSecureParsingDisabledTest {
20+
21+
@Inject
22+
LiquibaseFactory liquibaseFactory;
23+
24+
@RegisterExtension
25+
static final QuarkusUnitTest config = new QuarkusUnitTest()
26+
.withApplicationRoot((jar) -> jar
27+
.addAsResource("insecure-db/changeLog.xml", "db/changeLog.xml")
28+
.addAsResource("insecure-db/dbchangelog-3.8.xsd", "db/dbchangelog-3.8.xsd")
29+
.addAsResource("secure-parsing-disabled.properties", "application.properties"));
30+
31+
@Test
32+
public void testSecureParsingDisabled() throws Exception {
33+
try (Liquibase liquibase = liquibaseFactory.createLiquibase()) {
34+
List<ChangeSetStatus> status = liquibase.getChangeSetStatuses(liquibaseFactory.createContexts(),
35+
liquibaseFactory.createLabels());
36+
assertNotNull(status);
37+
assertEquals(1, status.size());
38+
assertEquals("id-1", status.get(0).getChangeSet().getId());
39+
assertFalse(status.get(0).getWillRun());
40+
}
41+
}
42+
}
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+
}
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

+1,381
Large diffs are not rendered by default.
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-secure-parsing-disabled;DB_CLOSE_DELAY=-1
5+
# Liquibase config properties
6+
quarkus.liquibase.migrate-at-start=true
7+
quarkus.liquibase.secure-parsing=false
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-secure-parsing-enabled;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

+36-6
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;
@@ -20,6 +21,7 @@
2021
import liquibase.database.Database;
2122
import liquibase.database.DatabaseFactory;
2223
import liquibase.database.jvm.JdbcConnection;
24+
import liquibase.exception.LiquibaseException;
2325
import liquibase.resource.ClassLoaderResourceAccessor;
2426
import liquibase.resource.CompositeResourceAccessor;
2527
import liquibase.resource.DirectoryResourceAccessor;
@@ -126,7 +128,8 @@ public Liquibase createLiquibase() {
126128
database.setDefaultSchemaName(config.defaultSchemaName.get());
127129
}
128130
}
129-
Liquibase liquibase = new Liquibase(parsedChangeLog, resourceAccessor, database);
131+
Liquibase liquibase = new QuarkusLiquibase(parsedChangeLog, resourceAccessor, database,
132+
createResettableSystemProperties());
130133

131134
for (Map.Entry<String, String> entry : config.changeLogParameters.entrySet()) {
132135
liquibase.getChangeLogParameters().set(entry.getKey(), entry.getValue());
@@ -165,11 +168,38 @@ public String getDataSourceName() {
165168
return dataSourceName;
166169
}
167170

168-
public ResettableSystemProperties createResettableSystemProperties() {
169-
if (config.allowDuplicatedChangesetIdentifiers.isEmpty()) {
170-
return ResettableSystemProperties.empty();
171+
private ResettableSystemProperties createResettableSystemProperties() {
172+
Map<String, String> resettableProperties = new HashMap<>();
173+
174+
if (config.allowDuplicatedChangesetIdentifiers.isPresent()) {
175+
resettableProperties.put("liquibase.allowDuplicatedChangesetIdentifiers",
176+
config.allowDuplicatedChangesetIdentifiers.get().toString());
177+
}
178+
179+
if (!config.secureParsing) {
180+
resettableProperties.put("liquibase.secureParsing", "false");
181+
}
182+
183+
return new ResettableSystemProperties(resettableProperties);
184+
}
185+
186+
private static class QuarkusLiquibase extends Liquibase {
187+
188+
private final ResettableSystemProperties resettableSystemProperties;
189+
190+
public QuarkusLiquibase(String changeLogFile, ResourceAccessor resourceAccessor, Database database,
191+
ResettableSystemProperties resettableSystemProperties) {
192+
super(changeLogFile, resourceAccessor, database);
193+
this.resettableSystemProperties = resettableSystemProperties;
194+
}
195+
196+
@Override
197+
public void close() throws LiquibaseException {
198+
try {
199+
super.close();
200+
} finally {
201+
resettableSystemProperties.close();
202+
}
171203
}
172-
return ResettableSystemProperties.of("liquibase.allowDuplicatedChangesetIdentifiers",
173-
config.allowDuplicatedChangesetIdentifiers.get().toString());
174204
}
175205
}

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

+8-1
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

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public LiquibaseFactory createLiquibaseFactory(DataSource dataSource, String dat
4040
config.cleanAtStart = liquibaseRuntimeConfig.cleanAtStart();
4141
config.validateOnMigrate = liquibaseRuntimeConfig.validateOnMigrate();
4242
config.allowDuplicatedChangesetIdentifiers = liquibaseRuntimeConfig.allowDuplicatedChangesetIdentifiers();
43+
config.secureParsing = liquibaseRuntimeConfig.secureParsing();
4344
return new LiquibaseFactory(config, dataSource, dataSourceName);
4445
}
4546
}

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

+7
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
}

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import io.quarkus.arc.InjectableInstance;
1414
import io.quarkus.arc.SyntheticCreationalContext;
1515
import io.quarkus.liquibase.LiquibaseFactory;
16-
import io.quarkus.runtime.ResettableSystemProperties;
1716
import io.quarkus.runtime.RuntimeValue;
1817
import io.quarkus.runtime.annotations.Recorder;
1918
import liquibase.Liquibase;
@@ -85,9 +84,7 @@ public void doStartActions(String dataSourceName) {
8584
}
8685

8786
LiquibaseFactory liquibaseFactory = liquibaseFactoryInstance.get();
88-
try (Liquibase liquibase = liquibaseFactory.createLiquibase();
89-
ResettableSystemProperties resettableSystemProperties = liquibaseFactory
90-
.createResettableSystemProperties()) {
87+
try (Liquibase liquibase = liquibaseFactory.createLiquibase()) {
9188
if (dataSourceConfig.cleanAtStart()) {
9289
liquibase.dropAll();
9390
}

0 commit comments

Comments
 (0)