Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
Expand Up @@ -66,6 +66,10 @@ snyk.api.organization=
# Default: 24 hours (1 day)
#snyk.scanner.extendTestDeadline.hours=24

# A delay in number of days since the package was created in Artifactory. Any packages that were created more recently
# than the current time minus the number of days in this configuration will be blocked from download.
#snyk.scanner.createdDelay.days

# By default, if Snyk API fails while scanning an artifact for any reason, the download will be allowed.
# Setting this property to "true" will block downloads when Snyk API fails.
# Accepts: "true", "false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public enum PluginConfiguration implements Configuration {
SCANNER_PACKAGE_TYPE_COCOAPODS("snyk.scanner.packageType.cocoapods", "false"),
TEST_CONTINUOUSLY("snyk.scanner.test.continuously","false"),
TEST_FREQUENCY_HOURS("snyk.scanner.frequency.hours", "168"),
EXTEND_TEST_DEADLINE_HOURS("snyk.scanner.extendTestDeadline.hours", "24");
EXTEND_TEST_DEADLINE_HOURS("snyk.scanner.extendTestDeadline.hours", "24"),
SCANNER_CREATED_DELAY_DAYS("snyk.scanner.createdDelay.days", "0");

private final String propertyKey;
private final String defaultValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static io.snyk.plugins.artifactory.configuration.properties.ArtifactProperty.*;
import static org.slf4j.LoggerFactory.getLogger;

import java.time.Instant;

public class MonitoredArtifact {

private static final Logger LOG = getLogger(MonitoredArtifact.class);
Expand All @@ -20,10 +22,17 @@ public class MonitoredArtifact {

private final Ignores ignores;

private final Optional<Instant> createdDate;

public MonitoredArtifact(String path, TestResult testResult, Ignores ignores) {
this(path, testResult, ignores, Optional.empty());
}

public MonitoredArtifact(String path, TestResult testResult, Ignores ignores, Optional<Instant> createdDate) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm aware, passing Optional as parameters is still generally considered bad practice. It's much better as a return type than as a parameter.
Better may just be to make this Instant then make getCreatedDate():

public Optional<Instant> getCreatedDate() {
  return Optional.ofNullable(createdDate);
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been changed to be just Instant. The constructor will default the value to null if its not passed.

this.path = path;
this.testResult = testResult;
this.ignores = ignores;
this.createdDate = createdDate;
}

public String getPath() {
Expand All @@ -38,6 +47,10 @@ public Ignores getIgnores() {
return ignores;
}

public Optional<Instant> getCreatedDate() {
return createdDate;
}

public MonitoredArtifact write(ArtifactProperties properties) {
testResult.write(properties);

Expand All @@ -61,7 +74,8 @@ public static Optional<MonitoredArtifact> read(ArtifactProperties properties) {
new MonitoredArtifact(
properties.getArtifactPath(),
testResult,
Ignores.read(properties)
Ignores.read(properties),
Optional.empty() // Created date not stored in properties
)
);
} catch (RuntimeException e) {
Expand All @@ -75,12 +89,12 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MonitoredArtifact artifact = (MonitoredArtifact) o;
return Objects.equals(path, artifact.path) && Objects.equals(testResult, artifact.testResult) && Objects.equals(ignores, artifact.ignores);
return Objects.equals(path, artifact.path) && Objects.equals(testResult, artifact.testResult) && Objects.equals(ignores, artifact.ignores) && Objects.equals(createdDate, artifact.createdDate);
}

@Override
public int hashCode() {
return Objects.hash(path, testResult, ignores);
return Objects.hash(path, testResult, ignores, createdDate);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,36 @@

import java.util.Optional;

import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.SCANNER_CREATED_DELAY_DAYS;
import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.SCANNER_LICENSE_THRESHOLD;
import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.SCANNER_VULNERABILITY_THRESHOLD;

public class ValidationSettings {

private final Optional<Severity> vulnSeverityThreshold;

private final Optional<Severity> licenseSeverityThreshold;
private final int createdDelayDays;

public ValidationSettings() {
this(Optional.of(Severity.HIGH), Optional.of(Severity.HIGH));
this(Optional.of(Severity.HIGH), Optional.of(Severity.HIGH), 0);
}

private ValidationSettings(Optional<Severity> vulnSeverityThreshold, Optional<Severity> licenseSeverityThreshold) {
private ValidationSettings(Optional<Severity> vulnSeverityThreshold, Optional<Severity> licenseSeverityThreshold, int createdDelayDays) {
this.vulnSeverityThreshold = vulnSeverityThreshold;
this.licenseSeverityThreshold = licenseSeverityThreshold;
this.createdDelayDays = createdDelayDays;
}

public ValidationSettings withVulnSeverityThreshold(Optional<Severity> threshold) {
return new ValidationSettings(threshold, licenseSeverityThreshold);
return new ValidationSettings(threshold, licenseSeverityThreshold, createdDelayDays);
}

public ValidationSettings withLicenseSeverityThreshold(Optional<Severity> threshold) {
return new ValidationSettings(vulnSeverityThreshold, threshold);
return new ValidationSettings(vulnSeverityThreshold, threshold, createdDelayDays);
}

public ValidationSettings withCreatedDelayDays(int days) {
return new ValidationSettings(vulnSeverityThreshold, licenseSeverityThreshold, days);
}

public Optional<Severity> getVulnSeverityThreshold() {
Expand All @@ -39,17 +45,27 @@ public Optional<Severity> getLicenseSeverityThreshold() {
return licenseSeverityThreshold;
}

public int getCreatedDelayDays() {
return createdDelayDays;
}

public static ValidationSettings from(ConfigurationModule config) {
return from(
config.getPropertyOrDefault(SCANNER_VULNERABILITY_THRESHOLD),
config.getPropertyOrDefault(SCANNER_LICENSE_THRESHOLD)
config.getPropertyOrDefault(SCANNER_LICENSE_THRESHOLD),
config.getPropertyOrDefault(SCANNER_CREATED_DELAY_DAYS)
);
}

public static ValidationSettings from(String vulnThreshold, String licenseThreshold) {
return from(vulnThreshold, licenseThreshold, "0");
}

public static ValidationSettings from(String vulnThreshold, String licenseThreshold, String createdDelayDaysStr) {
return new ValidationSettings(
parseSeverity(vulnThreshold),
parseSeverity(licenseThreshold)
parseSeverity(licenseThreshold),
Integer.parseInt(createdDelayDaysStr)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;

import static java.lang.String.format;
Expand All @@ -23,10 +25,40 @@ public PackageValidator(ValidationSettings settings) {
}

public void validate(MonitoredArtifact artifact) {
validateCreatedDelay(artifact);
validateVulnerabilityIssues(artifact);
validateLicenseIssues(artifact);
}

private void validateCreatedDelay(MonitoredArtifact artifact) {
int delayDays = settings.getCreatedDelayDays();
if (delayDays <= 0) {
LOG.debug("Created delay is disabled ({} days)", delayDays);
return;
}

Optional<Instant> createdDate = artifact.getCreatedDate();
if (createdDate.isEmpty()) {
LOG.debug("Created date not available for {}, skipping created delay check", artifact.getPath());
return;
}

Instant now = Instant.now();
long daysSinceCreation = ChronoUnit.DAYS.between(createdDate.get(), now);

if (daysSinceCreation < delayDays) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is the createdDate for an artifact? Is it the instant it was published to some base repo (e.g. maven central or npmjs registry)? Or is it the instant it was published/downloaded to this particular artifactory?

If the latter, I am concerned about how this will behave for artifacts when the artifactory is operating as a mirror.
If they want to update from [email protected] to [email protected], will the first attempt succeed due to not having createdDate` information yet, then subsequent attempts fail for X days? Or will the first attempt download the artifact to the repo, then deny the user access for X days?
Neither seems particularly good to me

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the docs, the createdDate for an artifact is when it was published to a particular Artifactory instance. My understanding is that the artifact will already be present in Artifactory when the Gatekeeper Plugin runs, which will prevent it from being downloaded out of Artifactory by a first-party user. Any downloads will be prevented for the duration of that delay parameter.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been changed to use the lastModifiedDate from Artifactory PAPI.

LOG.debug("Package created {} days ago, which is less than the delay of {} days: {}",
daysSinceCreation, delayDays, artifact.getPath());
throw new CancelException(format(
"Artifact was created %d days ago, which is less than the configured delay of %d days: %s",
daysSinceCreation, delayDays, artifact.getPath()
), 403);
}

LOG.debug("Package created {} days ago, which exceeds the delay of {} days: {}",
daysSinceCreation, delayDays, artifact.getPath());
}

private void validateVulnerabilityIssues(MonitoredArtifact artifact) {
validateIssues(
artifact.getTestResult().getVulnSummary(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.snyk.plugins.artifactory.model.TestResult;
import io.snyk.plugins.artifactory.model.ValidationSettings;
import org.artifactory.fs.FileLayoutInfo;
import org.artifactory.fs.ItemInfo;
import org.artifactory.repo.RepoPath;
import org.artifactory.repo.Repositories;
import org.jetbrains.annotations.NotNull;
Expand All @@ -19,6 +20,8 @@

import javax.annotation.Nonnull;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.Optional;

import static java.util.Objects.requireNonNull;
Expand Down Expand Up @@ -94,7 +97,23 @@ private void filter(MonitoredArtifact artifact) {

private @NotNull MonitoredArtifact toMonitoredArtifact(TestResult testResult, @NotNull RepoPath repoPath) {
Ignores ignores = Ignores.read(new RepositoryArtifactProperties(repoPath, repositories));
return new MonitoredArtifact(repoPath.toString(), testResult, ignores);
Optional<Instant> createdDate = getCreatedDate(repoPath);
return new MonitoredArtifact(repoPath.toString(), testResult, ignores, createdDate);
}

private Optional<Instant> getCreatedDate(RepoPath repoPath) {
try {
ItemInfo itemInfo = repositories.getItemInfo(repoPath);
if (itemInfo != null) {
Date created = new Date(itemInfo.getCreated());
if (created != null) {
return Optional.of(created.toInstant());
}
}
} catch (Exception e) {
LOG.debug("Could not retrieve created date for {}: {}", repoPath, e.getMessage());
}
return Optional.empty();
}

private boolean shouldTestContinuously() {
Expand Down