Skip to content

Commit

Permalink
feat: new cocoapods/nuget scanner
Browse files Browse the repository at this point in the history
added support for cocoapods, nuget scanning
  • Loading branch information
gwnlng authored and jacek-rzrz committed Oct 28, 2024
1 parent 4adb712 commit 56d99cb
Show file tree
Hide file tree
Showing 37 changed files with 893 additions and 323 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ snyk.api.token=
# Required.
snyk.api.organization=

# The base URL for all Snyk API endpoints.
# The base URL for all Snyk V1 API endpoints.
# Documentation: https://snyk.docs.apiary.io/#introduction/api-url
# Default: https://api.snyk.io/v1/
#snyk.api.url=https://api.snyk.io/v1/

# The base URL for Snyk REST API endpoints.
# Documentation: https://apidocs.snyk.io
# Default: https://api.snyk.io/rest/
#snyk.api.rest.url=https://api.snyk.io/rest/

# The API version for Snyk REST API endpoints.
# Documentation: https://apidocs.snyk.io
# Default: 2023-09-20
#snyk.api.rest.version=2024-01-23

# Path to an SSL Certificate for Snyk API in PEM format.
#snyk.api.sslCertificatePath=

Expand Down Expand Up @@ -74,3 +84,18 @@ snyk.api.organization=
# Accepts: "true", "false"
# Default: "false"
#snyk.scanner.packageType.pypi=false

# Scan Cocoapods repositories.
# Accepts: "true", "false"
# Default: "false"
#snyk.scanner.packageType.cocoapods=true

# Scan Nuget repositories.
# Accepts: "true", "false"
# Default: "false"
#snyk.scanner.packageType.nuget=true

# Scan Gems repositories.
# Accepts: "true", "false"
# Default: "false"
#snyk.scanner.packageType.gems=true
47 changes: 39 additions & 8 deletions core/src/main/java/io/snyk/plugins/artifactory/SnykPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import io.snyk.plugins.artifactory.exception.SnykRuntimeException;
import io.snyk.plugins.artifactory.scanner.ScannerModule;
import io.snyk.sdk.SnykConfig;
import io.snyk.sdk.api.v1.SnykClient;
import io.snyk.sdk.api.v1.SnykResult;
import io.snyk.sdk.api.SnykClient;
import io.snyk.sdk.api.rest.SnykRestClient;
import io.snyk.sdk.api.v1.SnykV1Client;
import io.snyk.sdk.api.SnykResult;
import io.snyk.sdk.model.NotificationSettings;
import org.artifactory.exception.CancelException;
import org.artifactory.fs.ItemInfo;
Expand Down Expand Up @@ -52,6 +54,8 @@ public SnykPlugin(@Nonnull Repositories repositories, File pluginsDirectory) {

LOG.info("Creating api client and modules...");
LOG.info("BaseURL:" + configurationModule.getPropertyOrDefault(API_URL));
LOG.info("RestBaseURL:" + configurationModule.getPropertyOrDefault(API_REST_URL));
LOG.info("RestVersion:" + configurationModule.getPropertyOrDefault(API_REST_VERSION));
LOG.info("Organization:" + configurationModule.getPropertyOrDefault(API_ORGANIZATION));
String token = configurationModule.getPropertyOrDefault(API_TOKEN);
if (null != token && token.length() > 4) {
Expand All @@ -60,10 +64,11 @@ public SnykPlugin(@Nonnull Repositories repositories, File pluginsDirectory) {
token = "no token configured";
}
LOG.debug("Token:" + token);
final SnykClient snykClient = createSnykClient(configurationModule, pluginVersion);
final SnykClient snykV1Client = createSnykV1Client(configurationModule, pluginVersion);
final SnykClient snykRestClient = createSnykRestClient(configurationModule, pluginVersion);

auditModule = new AuditModule();
scannerModule = new ScannerModule(configurationModule, repositories, snykClient);
scannerModule = new ScannerModule(configurationModule, repositories, pluginVersion);

LOG.info("Plugin version: {}", pluginVersion);
} catch (Exception ex) {
Expand Down Expand Up @@ -131,9 +136,11 @@ private void validateConfiguration() {
.forEach(LOG::debug);
}

private SnykClient createSnykClient(@Nonnull ConfigurationModule configurationModule, String pluginVersion) throws Exception {
private SnykConfig createSnykConfig(@Nonnull ConfigurationModule configurationModule, String pluginVersion) throws Exception {
final String token = configurationModule.getPropertyOrDefault(API_TOKEN);
String baseUrl = configurationModule.getPropertyOrDefault(API_URL);
String restBaseUrl = configurationModule.getPropertyOrDefault(API_REST_URL);
String restVersion = configurationModule.getPropertyOrDefault(API_REST_VERSION);
boolean trustAllCertificates = false;
String trustAllCertificatesProperty = configurationModule.getPropertyOrDefault(API_TRUST_ALL_CERTIFICATES);
if ("true".equals(trustAllCertificatesProperty)) {
Expand All @@ -146,6 +153,12 @@ private SnykClient createSnykClient(@Nonnull ConfigurationModule configurationMo
}
baseUrl = baseUrl + "/";
}
if (!restBaseUrl.endsWith("/")) {
if (LOG.isWarnEnabled()) {
LOG.warn("'{}' must end in /, your value is '{}'", API_REST_URL.propertyKey(), restBaseUrl);
}
restBaseUrl = restBaseUrl + "/";
}

String sslCertificatePath = configurationModule.getPropertyOrDefault(API_SSL_CERTIFICATE_PATH);
String httpProxyHost = configurationModule.getPropertyOrDefault(HTTP_PROXY_HOST);
Expand All @@ -154,6 +167,8 @@ private SnykClient createSnykClient(@Nonnull ConfigurationModule configurationMo

var config = SnykConfig.newBuilder()
.setBaseUrl(baseUrl)
.setRestBaseUrl(restBaseUrl)
.setRestVersion(restVersion)
.setToken(token)
.setUserAgent(API_USER_AGENT + pluginVersion)
.setTrustAllCertificates(trustAllCertificates)
Expand All @@ -167,13 +182,29 @@ private SnykClient createSnykClient(@Nonnull ConfigurationModule configurationMo
LOG.debug("config.httpProxyHost: " + config.httpProxyHost);
LOG.debug("config.httpProxyPort: " + config.httpProxyPort);

final SnykClient snykClient = new SnykClient(config);
return config;
}

// TODO: refactor with class newInstance()
private SnykClient createSnykV1Client(@Nonnull ConfigurationModule configurationModule, String pluginVersion) throws Exception {
SnykConfig config = createSnykConfig(configurationModule, pluginVersion);
final SnykClient snykV1Client = new SnykV1Client(config);
String org = configurationModule.getPropertyOrDefault(API_ORGANIZATION);
var res = snykV1Client.getNotificationSettings(org);
handleResponse(res);

return snykV1Client;
}

// TODO: refactor with class newInstance()
private SnykClient createSnykRestClient(@Nonnull ConfigurationModule configurationModule, String pluginVersion) throws Exception {
SnykConfig config = createSnykConfig(configurationModule, pluginVersion);
final SnykClient snykRestClient = new SnykRestClient(config);
String org = configurationModule.getPropertyOrDefault(API_ORGANIZATION);
var res = snykClient.getNotificationSettings(org);
var res = snykRestClient.getNotificationSettings(org);
handleResponse(res);

return snykClient;
return snykRestClient;
}

void handleResponse(SnykResult<NotificationSettings> res) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
public enum PluginConfiguration implements Configuration {
// general settings
API_URL("snyk.api.url", "https://api.snyk.io/v1/"),
API_REST_URL("snyk.api.rest.url", "https://api.snyk.io/rest/"),
API_REST_VERSION("snyk.api.rest.version", "2024-01-23"),
API_TOKEN("snyk.api.token", ""),
API_ORGANIZATION("snyk.api.organization", ""),
API_SSL_CERTIFICATE_PATH("snyk.api.sslCertificatePath", ""),
Expand All @@ -18,7 +20,10 @@ public enum PluginConfiguration implements Configuration {
SCANNER_LICENSE_THRESHOLD("snyk.scanner.license.threshold", "low"),
SCANNER_PACKAGE_TYPE_MAVEN("snyk.scanner.packageType.maven", "true"),
SCANNER_PACKAGE_TYPE_NPM("snyk.scanner.packageType.npm", "true"),
SCANNER_PACKAGE_TYPE_PYPI("snyk.scanner.packageType.pypi", "false");
SCANNER_PACKAGE_TYPE_PYPI("snyk.scanner.packageType.pypi", "false"),
SCANNER_PACKAGE_TYPE_COCOAPODS("snyk.scanner.packageType.cocoapods", "false"),
SCANNER_PACKAGE_TYPE_NUGET("snyk.scanner.packageType.nuget", "false"),
SCANNER_PACKAGE_TYPE_GEMS("snyk.scanner.packageType.gems", "false");

private final String propertyKey;
private final String defaultValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.snyk.plugins.artifactory.configuration;

import java.util.HashMap;
import java.util.Map;

/*
* Enum of Artifactory repository package types
*/
public enum RepoPackageType {
cocoapods,
nuget,
gems;

// Purl Type specification
private static final Map<RepoPackageType, String> packageToPurlTypeMap;
// Vulnerability Type at Snyk Security Vulnerability database
private static final Map<RepoPackageType, String> packageToVulnTypeMap;

static {
packageToPurlTypeMap = new HashMap<>();
packageToPurlTypeMap.put(cocoapods, "cocoapods");
packageToPurlTypeMap.put(nuget, "nuget");
packageToPurlTypeMap.put(gems, "gem");
packageToVulnTypeMap = new HashMap<>();
packageToVulnTypeMap.put(cocoapods, "cocoapods");
packageToVulnTypeMap.put(nuget, "nuget");
packageToVulnTypeMap.put(gems, "rubygems");
}

public String getPurlType() {
return packageToPurlTypeMap.get(this);
}

public String getVulnType() {
return packageToVulnTypeMap.get(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ public class CannotScanException extends RuntimeException {
public CannotScanException(String reason) {
super(reason);
}

public CannotScanException(String reason, Exception e) {
super(reason, e);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package io.snyk.plugins.artifactory.exception;

import io.snyk.sdk.api.v1.SnykResult;
import io.snyk.sdk.model.TestResult;
import io.snyk.sdk.api.SnykResult;
import io.snyk.sdk.model.v1.TestResult;
import io.snyk.sdk.model.rest.PurlIssues;

public class SnykAPIFailureException extends RuntimeException {
public SnykAPIFailureException(SnykResult<TestResult> result) {
super("Snyk API request was not successful. (" + result.statusCode + ")");
}

public SnykAPIFailureException(SnykResult<PurlIssues> result, String purl) {
super(String.format("Snyk REST API request was not successful. (%s, %s)", purl, result.statusCode));
}

public SnykAPIFailureException(Exception cause) {
super("Snyk API request encountered an unexpected error.", cause);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.snyk.plugins.artifactory.scanner;

import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
import io.snyk.sdk.api.SnykClient;
import org.artifactory.repo.RepoPath;
import org.artifactory.repo.Repositories;

import javax.annotation.Nonnull;

interface AbstractScannerFactory {

PackageScanner createScanner(@Nonnull ConfigurationModule configurationModule, @Nonnull Repositories repositories, @Nonnull RepoPath repoPath, String pluginVersion);
SnykClient createSnykClient(@Nonnull ConfigurationModule configurationModule, String pluginVersion, Class client);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
import io.snyk.plugins.artifactory.exception.CannotScanException;
import io.snyk.plugins.artifactory.exception.SnykAPIFailureException;
import io.snyk.sdk.api.v1.SnykClient;
import io.snyk.sdk.api.v1.SnykResult;
import io.snyk.sdk.model.TestResult;
import io.snyk.sdk.api.SnykResult;
import io.snyk.sdk.api.v1.SnykV1Client;
import io.snyk.sdk.model.v1.TestResult;
import org.artifactory.fs.FileLayoutInfo;
import org.artifactory.repo.RepoPath;
import org.slf4j.Logger;
Expand All @@ -14,19 +14,19 @@
import java.util.Optional;

import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.API_ORGANIZATION;
import static java.nio.charset.StandardCharsets.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.slf4j.LoggerFactory.getLogger;

class MavenScanner implements PackageScanner {

private static final Logger LOG = getLogger(MavenScanner.class);

private final ConfigurationModule configurationModule;
private final SnykClient snykClient;
private final SnykV1Client snykV1Client;

MavenScanner(ConfigurationModule configurationModule, SnykClient snykClient) {
MavenScanner(ConfigurationModule configurationModule, SnykV1Client snykV1Client) {
this.configurationModule = configurationModule;
this.snykClient = snykClient;
this.snykV1Client = snykV1Client;
}

public static String getArtifactDetailsURL(String groupID, String artifactID, String artifactVersion) {
Expand All @@ -43,7 +43,7 @@ public TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath) {

SnykResult<TestResult> result;
try {
result = snykClient.testMaven(
result = snykV1Client.testMaven(
groupID,
artifactID,
artifactVersion,
Expand All @@ -55,7 +55,7 @@ public TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath) {
}

TestResult testResult = result.get().orElseThrow(() -> new SnykAPIFailureException(result));
testResult.packageDetailsURL = getArtifactDetailsURL(groupID, artifactID, artifactVersion);
testResult.setPackageDetailsUrl(getArtifactDetailsURL(groupID, artifactID, artifactVersion));
return testResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,32 @@
import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
import io.snyk.plugins.artifactory.exception.CannotScanException;
import io.snyk.plugins.artifactory.exception.SnykAPIFailureException;
import io.snyk.sdk.api.v1.SnykClient;
import io.snyk.sdk.api.v1.SnykResult;
import io.snyk.sdk.model.TestResult;
import io.snyk.sdk.api.rest.SnykRestClient;
import io.snyk.sdk.api.v1.SnykV1Client;
import io.snyk.sdk.api.SnykResult;
import io.snyk.sdk.model.v1.TestResult;
import org.artifactory.fs.FileLayoutInfo;
import org.artifactory.repo.RepoPath;
import org.slf4j.Logger;

import java.net.URLEncoder;
import javax.annotation.Nonnull;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.API_ORGANIZATION;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.slf4j.LoggerFactory.getLogger;

class NpmScanner implements PackageScanner {

private static final Logger LOG = getLogger(NpmScanner.class);

private final ConfigurationModule configurationModule;
private final SnykClient snykClient;
private final SnykV1Client snykV1Client;

NpmScanner(ConfigurationModule configurationModule, SnykClient snykClient) {
NpmScanner(ConfigurationModule configurationModule, SnykV1Client snykV1Client) {
this.configurationModule = configurationModule;
this.snykClient = snykClient;
this.snykV1Client = snykV1Client;
}

public static Optional<PackageURLDetails> getPackageDetailsFromUrl(String repoPath) {
Expand All @@ -53,7 +53,7 @@ public TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath) {

SnykResult<TestResult> result;
try {
result = snykClient.testNpm(
result = snykV1Client.testNpm(
details.name,
details.version,
Optional.ofNullable(configurationModule.getProperty(API_ORGANIZATION))
Expand All @@ -63,7 +63,7 @@ public TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath) {
}

TestResult testResult = result.get().orElseThrow(() -> new SnykAPIFailureException(result));
testResult.packageDetailsURL = getPackageDetailsURL(details);
testResult.setPackageDetailsUrl(getPackageDetailsURL(details));
return testResult;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package io.snyk.plugins.artifactory.scanner;

import io.snyk.sdk.model.TestResult;
import io.snyk.sdk.model.ScanResponse;
import org.artifactory.fs.FileLayoutInfo;
import org.artifactory.repo.RepoPath;

interface PackageScanner {
TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath);
public interface PackageScanner {
//TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath);
ScanResponse scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath);
}
Loading

0 comments on commit 56d99cb

Please sign in to comment.