-
Notifications
You must be signed in to change notification settings - Fork 29
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
Use Snyk as data provider #860
Changes from 2 commits
1d36d4d
373b5d6
d84cf12
8a6fc27
69b1654
e9c052c
8393c59
3660929
c9873f7
ff843d8
4a381d6
a1427c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.sap.oss.phosphor.fosstars.advice.oss; | ||
|
||
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SNYK; | ||
|
||
import com.sap.oss.phosphor.fosstars.advice.Advice; | ||
import com.sap.oss.phosphor.fosstars.advice.oss.OssAdviceContentYamlStorage.OssAdviceContext; | ||
import com.sap.oss.phosphor.fosstars.model.Subject; | ||
import com.sap.oss.phosphor.fosstars.model.Value; | ||
import com.sap.oss.phosphor.fosstars.model.score.oss.SnykDependencyScanScore; | ||
import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; | ||
import java.net.MalformedURLException; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
/** | ||
* An advisor for features related to Snyk. | ||
*/ | ||
public class SnykAdvisor extends AbstractOssAdvisor { | ||
|
||
/** | ||
* Create a new advisor. | ||
* | ||
* @param contextFactory A factory that provides contexts for advice. | ||
*/ | ||
public SnykAdvisor(OssAdviceContextFactory contextFactory) { | ||
super(OssAdviceContentYamlStorage.DEFAULT, contextFactory); | ||
} | ||
|
||
@Override | ||
protected List<Advice> adviceFor( | ||
Subject subject, List<Value<?>> usedValues, OssAdviceContext context) | ||
throws MalformedURLException { | ||
|
||
Optional<ScoreValue> snykScore = findSubScoreValue(subject, SnykDependencyScanScore.class); | ||
|
||
if (!snykScore.isPresent() || snykScore.get().isNotApplicable()) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
return adviceForBooleanFeature(usedValues, USES_SNYK, subject, context); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
package com.sap.oss.phosphor.fosstars.data.github; | ||
|
||
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_OPEN_PULL_REQUEST_FROM_SNYK; | ||
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.USES_SNYK; | ||
import static com.sap.oss.phosphor.fosstars.model.other.Utils.setOf; | ||
|
||
import com.sap.oss.phosphor.fosstars.model.Feature; | ||
import com.sap.oss.phosphor.fosstars.model.ValueSet; | ||
import com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures; | ||
import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; | ||
import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet; | ||
import java.io.IOException; | ||
import java.nio.file.Path; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.util.Date; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.function.Predicate; | ||
import org.kohsuke.github.GHIssueState; | ||
import org.kohsuke.github.GHPullRequest; | ||
import org.kohsuke.github.GHUser; | ||
|
||
/** | ||
* This data provider checks if an open-source project on GitHub uses Snyk, and fills out the {@link | ||
* OssFeatures#USES_SNYK} feature. | ||
* | ||
* <p>First, the provider checks if a repository contains a policy file for Snyk. If the policy file | ||
* exists, then the provider reports that the project uses Snyk. Next, the provider searches for | ||
* commits from Snyk in the commit history. If the commits are found, then the provider also reports | ||
* that the project uses Snyk. | ||
*/ | ||
public class UsesSnyk extends GitHubCachingDataProvider { | ||
|
||
/** | ||
* A file name containing Snyk policies in a repository. | ||
* | ||
* @see <a href="https://docs.snyk.io/snyk-cli/test-for-vulnerabilities/the-.snyk-file/">The .snyk | ||
* file</a> | ||
*/ | ||
private static String SNYK_POLICY_FILE_NAME = ".snyk"; | ||
|
||
/** | ||
* A minimal number of characters in a config for Snyk. | ||
*/ | ||
private static final int ACCEPTABLE_CONFIG_SIZE = 10; | ||
|
||
/** | ||
* A location of a Snyk configuration file in a repository. | ||
* | ||
* @see <a href="https://github.com/snyk/actions/tree/master/setup">To setup Snyk actions</a> | ||
* | ||
*/ | ||
private static final String [] SNYK_CONFIGS = { | ||
".github/workflows/snyk.yaml", | ||
".github/workflows/snyk.yml" | ||
}; | ||
|
||
/** Predicate to confirm if there is a file in open-source project with the .snyk extension. */ | ||
ManjunathMS35 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static final Predicate<Path> SNYK_FILE_PREDICATE = | ||
path -> path.getFileName().toString().endsWith(SNYK_POLICY_FILE_NAME); | ||
|
||
/** | ||
* A pattern to detect commits by Snyk. | ||
* | ||
* @see <a | ||
* href="https://docs.snyk.io/integrations/git-repository-scm-integrations/github-integration#commit-signing/">Snyk | ||
* commit signing</a> | ||
*/ | ||
private static final String SNYK_PATTERN = "snyk"; | ||
|
||
/** Period of time to be checked. */ | ||
ManjunathMS35 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static final Duration ONE_YEAR = Duration.ofDays(365); | ||
|
||
/** | ||
* Initializes a data provider. | ||
* | ||
* @param fetcher An interface to GitHub. | ||
*/ | ||
public UsesSnyk(GitHubDataFetcher fetcher) { | ||
super(fetcher); | ||
} | ||
|
||
@Override | ||
public Set<Feature<?>> supportedFeatures() { | ||
return setOf(USES_SNYK, HAS_OPEN_PULL_REQUEST_FROM_SNYK); | ||
} | ||
|
||
@Override | ||
protected ValueSet fetchValuesFor(GitHubProject project) throws IOException { | ||
logger.info("Checking how the project uses Snyk ..."); | ||
|
||
LocalRepository repository = GitHubDataFetcher.localRepositoryFor(project); | ||
|
||
return ValueHashSet.from( | ||
USES_SNYK.value( | ||
hasSnykPolicy(repository) | ||
|| hasSnykConfig(repository) | ||
|| hasSnykCommits(repository)), | ||
HAS_OPEN_PULL_REQUEST_FROM_SNYK.value(hasOpenPullRequestFromSnyk(project))); | ||
} | ||
|
||
/** | ||
* Checks if a repository has a configuration file for Snyk. | ||
* | ||
* @param repository The repository | ||
* @return True if a config was found, false otherwise. | ||
*/ | ||
private boolean hasSnykConfig(LocalRepository repository) throws IOException { | ||
for (String config : SNYK_CONFIGS) { | ||
Optional<String> content = repository.file(config); | ||
if (content.isPresent() && content.get().length() >= ACCEPTABLE_CONFIG_SIZE) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Checks if a repository has a policy file for Snyk. | ||
* | ||
* @param repository The repository | ||
* @return True if a policy file was found, false otherwise. | ||
*/ | ||
private boolean hasSnykPolicy(LocalRepository repository) throws IOException { | ||
List<Path> snykPolicyFilePaths = repository.files(SNYK_FILE_PREDICATE); | ||
return !snykPolicyFilePaths.isEmpty(); | ||
} | ||
|
||
/** | ||
* Checks whether a project has open pull requests from Snyk. | ||
* | ||
* @param project The project. | ||
* @return True if the project has open pull requests form Snyk. | ||
* @throws IOException If something went wrong. | ||
*/ | ||
private boolean hasOpenPullRequestFromSnyk(GitHubProject project) throws IOException { | ||
return fetcher.repositoryFor(project).getPullRequests(GHIssueState.OPEN).stream() | ||
.anyMatch(this::createdBySnyk); | ||
} | ||
|
||
/** | ||
* Checks if a pull request was created by Snyk. | ||
* | ||
* @param pullRequest The pull request. | ||
* @return True if the user looks like Snyk, false otherwise. | ||
*/ | ||
private boolean createdBySnyk(GHPullRequest pullRequest) { | ||
try { | ||
GHUser user = pullRequest.getUser(); | ||
return isSnyk(user.getName()) || isSnyk(user.getLogin()); | ||
} catch (IOException e) { | ||
logger.warn("Oops! Could not fetch name or login!", e); | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Checks if a repository contains commits from Snyk in the commit history. | ||
* | ||
* @param repository The repository. | ||
* @return True if at least one commit from Snyk was found, false otherwise. | ||
*/ | ||
private boolean hasSnykCommits(LocalRepository repository) { | ||
Date date = Date.from(Instant.now().minus(ONE_YEAR)); | ||
|
||
try { | ||
for (Commit commit : repository.commitsAfter(date)) { | ||
if (isSnyk(commit)) { | ||
return true; | ||
} | ||
} | ||
} catch (IOException e) { | ||
logger.warn("Something went wrong!", e); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Checks if a commit was done by Snyk. | ||
* | ||
* @param commit The commit to be checked. | ||
* @return True if the commit was done by Snyk, false otherwise. | ||
*/ | ||
private static boolean isSnyk(Commit commit) { | ||
if (isSnyk(commit.authorName()) || isSnyk(commit.committerName())) { | ||
return true; | ||
} | ||
|
||
for (String line : commit.message()) { | ||
if ((line.startsWith("Signed-off-by:") || line.startsWith("Co-authored-by:")) | ||
&& line.contains(SNYK_PATTERN)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Checks whether a name looks like Snyk. | ||
* | ||
* @param name The name. | ||
* @return True if the name looks like Snyk, false otherwise. | ||
*/ | ||
private static boolean isSnyk(String name) { | ||
return name != null && name.toLowerCase().contains(SNYK_PATTERN); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe most of the methods given here seems to be from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -171,6 +171,28 @@ private OssFeatures() { | |||
public static final BooleanFeature HAS_OPEN_PULL_REQUEST_FROM_DEPENDABOT | ||||
= new BooleanFeature("If a project has open pull requests from Dependabot"); | ||||
|
||||
/** | ||||
* <p>Shows if a project uses Snyk.</p> | ||||
* <p><a href="https://snyk.io/">Snyk</a> offers | ||||
* i) Static Application Security Testing (SAST) amd | ||||
* i) Static Application Security Testing (SAST) amd | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
* ii) Automatic dependency updates | ||||
* In particular for automatic dependency updates, | ||||
* when Snyk finds a vulnerability in dependencies, | ||||
* it opens a pull request to update the vulnerable dependency to the safe version.</p> | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you format this a bit better? It looks very congested. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
*/ | ||||
public static final Feature<Boolean> USES_SNYK | ||||
= new BooleanFeature("If a project uses Snyk"); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess here indentation seems to be a miss |
||||
|
||||
/** | ||||
* Shows if an open source project has open pull requests from Snyk which means that | ||||
* there are dependencies with known vulnerabilities. | ||||
* | ||||
* @see <a href="https://snyk.io/">Snyk</a> | ||||
ManjunathMS35 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
*/ | ||||
public static final BooleanFeature HAS_OPEN_PULL_REQUEST_FROM_SNYK | ||||
= new BooleanFeature("If a project has open pull requests from Snyk"); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feature seems completely useless, where is it used in SnykScore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a good feature to increase the case in Snyk Score |
||||
|
||||
/** | ||||
* Shows how many GitHub users starred an open-source project. | ||||
*/ | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the only form of Go Module possible for Package Management.
Maybe is there a
.lock
file?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to go.mod, the go command maintains a file named go.sum containing the expected cryptographic hashes of the content of specific module versions. Do we need to check this as well?