Skip to content

Commit

Permalink
Merge pull request #954 from eclipsesource/compliance-check
Browse files Browse the repository at this point in the history
feat: add publisher agreement compliance check
  • Loading branch information
amvanbaren authored Jul 4, 2024
2 parents 0514d8b + 286a976 commit 66f62a2
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 3 deletions.
15 changes: 15 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

Expand Down Expand Up @@ -562,4 +563,18 @@ public FileResource getVsixManifest(ExtensionVersion extVersion) {
vsixManifest.setContent(ArchiveUtil.readEntry(zipFile, VSIX_MANIFEST, ObservationRegistry.NOOP));
return vsixManifest;
}

public boolean isPotentiallyMalicious() {
readInputStream();
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getExtra() != null) {
logger.warn("Potentially harmful zip entry with extra fields detected: {}", entry.getName());
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public ExtensionVersion mirrorVersion(TempFile extensionFile, String signatureNa
return download.getExtension();
}

public ExtensionVersion publishVersion(InputStream content, PersonalAccessToken token) {
public ExtensionVersion publishVersion(InputStream content, PersonalAccessToken token) throws ErrorResultException {
return Observation.createNotStarted("ExtensionService#publishVersion", observations).observe(() -> {
var extensionFile = createExtensionFile(content);
var download = doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public enum Type {

boolean active;

boolean potentiallyMalicious;

String displayName;

@Column(length = 2048)
Expand Down Expand Up @@ -318,6 +320,14 @@ public void setActive(boolean active) {
this.active = active;
}

public boolean isPotentiallyMalicious() {
return potentiallyMalicious;
}

public void setPotentiallyMalicious(boolean potentiallyMalicious) {
this.potentiallyMalicious = potentiallyMalicious;
}

public String getDisplayName() {
return displayName;
}
Expand Down Expand Up @@ -487,6 +497,7 @@ public boolean equals(Object o) {
&& preRelease == that.preRelease
&& preview == that.preview
&& active == that.active
&& potentiallyMalicious == that.potentiallyMalicious
&& Objects.equals(getId(extension), getId(that.extension)) // use id to prevent infinite recursion
&& Objects.equals(version, that.version)
&& Objects.equals(targetPlatform, that.targetPlatform)
Expand Down Expand Up @@ -517,7 +528,7 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(
id, getId(extension), version, targetPlatform, semver, preRelease, preview, timestamp, getId(publishedWith),
active, displayName, description, engines, categories, tags, extensionKind, license, homepage, repository,
active, potentiallyMalicious, displayName, description, engines, categories, tags, extensionKind, license, homepage, repository,
sponsorLink, bugs, markdown, galleryColor, galleryTheme, localizedLanguages, qna, dependencies,
bundledExtensions, signatureKeyPair, type
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/********************************************************************************
* Copyright (c) 2024 STMicroelectronics and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.migration;

import io.micrometer.observation.ObservationRegistry;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import org.jobrunr.jobs.context.JobRunrDashboardLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.openvsx.ExtensionProcessor;
import org.eclipse.openvsx.entities.ExtensionVersion;
import org.eclipse.openvsx.util.NamingUtil;
import org.eclipse.openvsx.util.TempFile;
import org.springframework.stereotype.Component;

@Component
public class CheckPotentiallyMaliciousExtensionVersionsService {

protected final Logger logger = new JobRunrDashboardLogger(LoggerFactory.getLogger(PotentiallyMaliciousJobRequestHandler.class));

private final EntityManager entityManager;

public CheckPotentiallyMaliciousExtensionVersionsService(EntityManager entityManager) {
this.entityManager = entityManager;
}

@Transactional
public void checkPotentiallyMaliciousExtensionVersion(ExtensionVersion extVersion, TempFile extensionFile) {
try(var extProcessor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) {
boolean isMalicious = extProcessor.isPotentiallyMalicious();
extVersion.setPotentiallyMalicious(isMalicious);
if (isMalicious) {
logger.warn("Extension version is potentially malicious: {}", NamingUtil.toLogFormat(extVersion));
}
}
entityManager.merge(extVersion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public void run(HandlerJobRequest<?> jobRequest) throws Exception {
fixTargetPlatformMigration();
generateSha256ChecksumMigration();
extensionVersionSignatureMigration();
checkPotentiallyMaliciousExtensionVersions();
}

private void extractResourcesMigration() {
Expand Down Expand Up @@ -93,4 +94,10 @@ private void extensionVersionSignatureMigration() {
scheduler.enqueue(new HandlerJobRequest<>(GenerateKeyPairJobRequestHandler.class));
}
}

private void checkPotentiallyMaliciousExtensionVersions() {
var jobName = "CheckPotentiallyMaliciousExtensionVersions";
var handler = PotentiallyMaliciousJobRequestHandler.class;
repositories.findNotMigratedPotentiallyMalicious().forEach(item -> migrations.enqueueMigration(jobName, handler, item));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/********************************************************************************
* Copyright (c) 2024 STMicroelectronics and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.migration;

import org.eclipse.openvsx.util.NamingUtil;
import org.jobrunr.jobs.annotations.Job;
import org.jobrunr.jobs.context.JobRunrDashboardLogger;
import org.jobrunr.jobs.lambdas.JobRequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.nio.file.Files;
import java.util.AbstractMap;

@Component
public class PotentiallyMaliciousJobRequestHandler implements JobRequestHandler<MigrationJobRequest> {

protected final Logger logger = new JobRunrDashboardLogger(LoggerFactory.getLogger(PotentiallyMaliciousJobRequestHandler.class));

private final MigrationService migrations;
private final CheckPotentiallyMaliciousExtensionVersionsService service;

public PotentiallyMaliciousJobRequestHandler(MigrationService migrations, CheckPotentiallyMaliciousExtensionVersionsService service) {
this.migrations = migrations;
this.service = service;
}

@Override
@Job(name = "Check published extensions for potentially malicious vsix file", retries = 3)
public void run(MigrationJobRequest jobRequest) throws Exception {
var download = migrations.getResource(jobRequest);
var extVersion = download.getExtension();
logger.info("Checking extension version for potentially malicious vsix file: {}", NamingUtil.toLogFormat(extVersion));

var content = migrations.getContent(download);
var entry = new AbstractMap.SimpleEntry<>(download, content);
try(var extensionFile = migrations.getExtensionFile(entry)) {
if(Files.size(extensionFile.getPath()) == 0) {
logger.info("Extension file is empty, skipping: {}", download.getName());
return;
}

logger.info("Checking vsix file for potentially malicious metadata: {}", download.getName());
service.checkPotentiallyMaliciousExtensionVersion(extVersion, extensionFile);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ public void publishAsync(FileResource download, TempFile extensionFile, Extensio
service.storeDownload(download, extensionFile);
service.persistResource(download);
try(var processor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) {
extVersion.setPotentiallyMalicious(processor.isPotentiallyMalicious());
if (extVersion.isPotentiallyMalicious()) {
logger.warn("Extension version is potentially malicious: {}", NamingUtil.toLogFormat(extVersion));
return;
}

Consumer<FileResource> consumer = resource -> {
service.storeResource(resource);
service.persistResource(resource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public List<ExtensionVersion> findAllActiveByExtensionIdAndTargetPlatform(Collec
EXTENSION.NAME,
EXTENSION_VERSION.ID,
EXTENSION_VERSION.VERSION,
EXTENSION_VERSION.POTENTIALLY_MALICIOUS,
EXTENSION_VERSION.TARGET_PLATFORM,
EXTENSION_VERSION.PREVIEW,
EXTENSION_VERSION.PRE_RELEASE,
Expand Down Expand Up @@ -411,6 +412,7 @@ private SelectQuery<Record> findAllActive() {
USER_DATA.PROVIDER,
EXTENSION_VERSION.ID,
EXTENSION_VERSION.VERSION,
EXTENSION_VERSION.POTENTIALLY_MALICIOUS,
EXTENSION_VERSION.TARGET_PLATFORM,
EXTENSION_VERSION.PREVIEW,
EXTENSION_VERSION.PRE_RELEASE,
Expand Down Expand Up @@ -531,6 +533,7 @@ private ExtensionVersion toExtensionVersionCommon(
extVersion.setDependencies(toList(record.get(extensionVersionMapper.map(EXTENSION_VERSION.DEPENDENCIES)), converter));
extVersion.setBundledExtensions(toList(record.get(extensionVersionMapper.map(EXTENSION_VERSION.BUNDLED_EXTENSIONS)), converter));
extVersion.setSponsorLink(record.get(extensionVersionMapper.map(EXTENSION_VERSION.SPONSOR_LINK)));
extVersion.setPotentiallyMalicious(record.get(extensionVersionMapper.map(EXTENSION_VERSION.POTENTIALLY_MALICIOUS)));

if(extension == null) {
var namespace = new Namespace();
Expand Down Expand Up @@ -637,6 +640,7 @@ public ExtensionVersion findLatest(
USER_DATA.PROVIDER,
EXTENSION_VERSION.ID,
EXTENSION_VERSION.VERSION,
EXTENSION_VERSION.POTENTIALLY_MALICIOUS,
EXTENSION_VERSION.TARGET_PLATFORM,
EXTENSION_VERSION.PREVIEW,
EXTENSION_VERSION.PRE_RELEASE,
Expand Down Expand Up @@ -736,6 +740,7 @@ public List<ExtensionVersion> findLatest(UserData user) {
latestQuery.addSelect(
EXTENSION_VERSION.ID,
EXTENSION_VERSION.VERSION,
EXTENSION_VERSION.POTENTIALLY_MALICIOUS,
EXTENSION_VERSION.TARGET_PLATFORM,
EXTENSION_VERSION.PREVIEW,
EXTENSION_VERSION.PRE_RELEASE,
Expand Down Expand Up @@ -780,6 +785,7 @@ public List<ExtensionVersion> findLatest(UserData user) {
EXTENSION.LAST_UPDATED_DATE,
EXTENSION.ACTIVE,
latest.field(EXTENSION_VERSION.ID),
latest.field(EXTENSION_VERSION.POTENTIALLY_MALICIOUS),
latest.field(EXTENSION_VERSION.VERSION),
latest.field(EXTENSION_VERSION.TARGET_PLATFORM),
latest.field(EXTENSION_VERSION.PREVIEW),
Expand Down Expand Up @@ -909,6 +915,7 @@ public ExtensionVersion find(String namespaceName, String extensionName, String
EXTENSION.LAST_UPDATED_DATE,
EXTENSION_VERSION.ID,
EXTENSION_VERSION.VERSION,
EXTENSION_VERSION.POTENTIALLY_MALICIOUS,
EXTENSION_VERSION.TARGET_PLATFORM,
EXTENSION_VERSION.PREVIEW,
EXTENSION_VERSION.PRE_RELEASE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,10 @@ public Streamable<MigrationItem> findNotMigratedSha256Checksums() {
return findNotMigratedItems("V1_35__FileResource_Generate_Sha256_Checksum.sql");
}

public Streamable<MigrationItem> findNotMigratedPotentiallyMalicious() {
return findNotMigratedItems("V1_46__ExtensionVersion_PotentiallyMalicious.sql");
}

private Streamable<MigrationItem> findNotMigratedItems(String migrationScript) {
return migrationItemRepo.findByMigrationScriptAndMigrationScheduledFalseOrderById(migrationScript);
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ALTER TABLE extension_version ADD COLUMN potentially_malicious BOOLEAN;
UPDATE extension_version SET potentially_malicious = FALSE;


INSERT INTO migration_item(id, migration_script, entity_id, migration_scheduled)
SELECT nextval('hibernate_sequence'), 'V1_46__ExtensionVersion_PotentiallyMalicious.sql', fr.id, FALSE
FROM file_resource fr
JOIN extension_version ev ON ev.id = fr.extension_id
JOIN extension e ON e.id = ev.extension_id
WHERE fr.type = 'download'
ORDER BY e.download_count DESC;
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ void testExecuteQueries() {
() -> repositories.findNotMigratedVsixManifests(),
() -> repositories.findNotMigratedTargetPlatforms(),
() -> repositories.findNotMigratedSha256Checksums(),
() -> repositories.findNotMigratedPotentiallyMalicious(),
() -> repositories.topMostActivePublishingUsers(1),
() -> repositories.topNamespaceExtensions(1),
() -> repositories.topNamespaceExtensionVersions(1),
Expand Down

0 comments on commit 66f62a2

Please sign in to comment.