Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add quarkus.liquibase.secure-parsing to allow disabling secure parsing #47186

Merged
merged 1 commit into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.quarkus.liquibase.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.List;

import jakarta.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.liquibase.LiquibaseFactory;
import io.quarkus.test.QuarkusUnitTest;
import liquibase.Liquibase;
import liquibase.changelog.ChangeSetStatus;

public class LiquibaseExtensionSecureParsingDisabledTest {

@Inject
LiquibaseFactory liquibaseFactory;

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource("insecure-db/changeLog.xml", "db/changeLog.xml")
.addAsResource("insecure-db/dbchangelog-3.8.xsd", "db/dbchangelog-3.8.xsd")
.addAsResource("secure-parsing-disabled.properties", "application.properties"));

@Test
public void testSecureParsingDisabled() throws Exception {
try (Liquibase liquibase = liquibaseFactory.createLiquibase()) {
List<ChangeSetStatus> status = liquibase.getChangeSetStatuses(liquibaseFactory.createContexts(),
liquibaseFactory.createLabels());
assertNotNull(status);
assertEquals(1, status.size());
assertEquals("id-1", status.get(0).getChangeSet().getId());
assertFalse(status.get(0).getWillRun());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.quarkus.liquibase.test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

import jakarta.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.liquibase.LiquibaseFactory;
import io.quarkus.test.QuarkusUnitTest;

public class LiquibaseExtensionSecureParsingEnabledTest {

@Inject
LiquibaseFactory liquibaseFactory;

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource("insecure-db/changeLog.xml", "db/changeLog.xml")
.addAsResource("insecure-db/dbchangelog-3.8.xsd", "db/dbchangelog-3.8.xsd")
.addAsResource("secure-parsing-enabled.properties", "application.properties"))
.assertException(t -> {
assertThat(t.getCause().getCause())
.hasMessageContaining("because 'file' access is not allowed");
});

@Test
public void testSecureParsing() throws Exception {
fail("should not be executed");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<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 db/dbchangelog-3.8.xsd"
logicalFilePath="changeLog.xml">

<changeSet author="dev (generated)" id="id-1">
<createTable tableName="TEST_INSECURE_PARSING">
<column name="ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)"/>
</createTable>
</changeSet>

</databaseChangeLog>

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=sa
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test-quarkus-secure-parsing-disabled;DB_CLOSE_DELAY=-1
# Liquibase config properties
quarkus.liquibase.migrate-at-start=true
quarkus.liquibase.secure-parsing=false
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=sa
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test-secure-parsing-enabled;DB_CLOSE_DELAY=-1
# Liquibase config properties
quarkus.liquibase.migrate-at-start=true
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;
Expand All @@ -20,6 +21,7 @@
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.CompositeResourceAccessor;
import liquibase.resource.DirectoryResourceAccessor;
Expand Down Expand Up @@ -126,7 +128,8 @@ public Liquibase createLiquibase() {
database.setDefaultSchemaName(config.defaultSchemaName.get());
}
}
Liquibase liquibase = new Liquibase(parsedChangeLog, resourceAccessor, database);
Liquibase liquibase = new QuarkusLiquibase(parsedChangeLog, resourceAccessor, database,
createResettableSystemProperties());

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

public ResettableSystemProperties createResettableSystemProperties() {
if (config.allowDuplicatedChangesetIdentifiers.isEmpty()) {
return ResettableSystemProperties.empty();
private ResettableSystemProperties createResettableSystemProperties() {
Map<String, String> resettableProperties = new HashMap<>();

if (config.allowDuplicatedChangesetIdentifiers.isPresent()) {
resettableProperties.put("liquibase.allowDuplicatedChangesetIdentifiers",
config.allowDuplicatedChangesetIdentifiers.get().toString());
}

if (!config.secureParsing) {
resettableProperties.put("liquibase.secureParsing", "false");
}

return new ResettableSystemProperties(resettableProperties);
}

private static class QuarkusLiquibase extends Liquibase {

private final ResettableSystemProperties resettableSystemProperties;

public QuarkusLiquibase(String changeLogFile, ResourceAccessor resourceAccessor, Database database,
ResettableSystemProperties resettableSystemProperties) {
super(changeLogFile, resourceAccessor, database);
this.resettableSystemProperties = resettableSystemProperties;
}

@Override
public void close() throws LiquibaseException {
try {
super.close();
} finally {
resettableSystemProperties.close();
}
}
return ResettableSystemProperties.of("liquibase.allowDuplicatedChangesetIdentifiers",
config.allowDuplicatedChangesetIdentifiers.get().toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ public class LiquibaseConfig {
/**
* Allows duplicated changeset identifiers without failing Liquibase execution.
*/
public Optional<Boolean> allowDuplicatedChangesetIdentifiers;
public Optional<Boolean> allowDuplicatedChangesetIdentifiers = Optional.empty();

/**
* Whether Liquibase should enforce secure parsing.
* <p>
* If secure parsing is enforced, unsecure files may not be parsed.
*/
public boolean secureParsing = true;

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public LiquibaseFactory createLiquibaseFactory(DataSource dataSource, String dat
config.cleanAtStart = liquibaseRuntimeConfig.cleanAtStart();
config.validateOnMigrate = liquibaseRuntimeConfig.validateOnMigrate();
config.allowDuplicatedChangesetIdentifiers = liquibaseRuntimeConfig.allowDuplicatedChangesetIdentifiers();
config.secureParsing = liquibaseRuntimeConfig.secureParsing();
return new LiquibaseFactory(config, dataSource, dataSourceName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,11 @@ public interface LiquibaseDataSourceRuntimeConfig {
*/
Optional<Boolean> allowDuplicatedChangesetIdentifiers();

/**
* Whether Liquibase should enforce secure parsing.
* <p>
* If secure parsing is enforced, insecure files may not be parsed.
*/
@WithDefault("true")
boolean secureParsing();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import io.quarkus.arc.InjectableInstance;
import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.liquibase.LiquibaseFactory;
import io.quarkus.runtime.ResettableSystemProperties;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import liquibase.Liquibase;
Expand Down Expand Up @@ -85,9 +84,7 @@ public void doStartActions(String dataSourceName) {
}

LiquibaseFactory liquibaseFactory = liquibaseFactoryInstance.get();
try (Liquibase liquibase = liquibaseFactory.createLiquibase();
ResettableSystemProperties resettableSystemProperties = liquibaseFactory
.createResettableSystemProperties()) {
try (Liquibase liquibase = liquibaseFactory.createLiquibase()) {
if (dataSourceConfig.cleanAtStart()) {
liquibase.dropAll();
}
Expand Down
Loading