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

#103: security warning for CVEs in file tool/edition/security #119

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2237158
#103: security warning for CVEs in file tool/edition/security
MattesMrzik Oct 25, 2023
897b800
promting the user for vulnerabilities and start of Main for OWASP check
MattesMrzik Nov 8, 2023
59f6a1b
merged main into feature 103
MattesMrzik Nov 8, 2023
5f162e2
moved owasp dep from idecli pom to security pom, mor work in Main
MattesMrzik Nov 14, 2023
e5c70e7
#103: url analyzer works and vulnerabilities are retrieved
MattesMrzik Nov 15, 2023
34febf5
#103: Security Entry and determination of version Range for vulnerabi…
MattesMrzik Nov 16, 2023
ae0558b
#103: writing security json file
MattesMrzik Nov 24, 2023
ba87b95
#103: test interaction and getVersionRangeFromInterval
MattesMrzik Dec 5, 2023
ba694ab
#103: refinements
MattesMrzik Dec 6, 2023
81b8586
#103: more refinement
MattesMrzik Dec 14, 2023
9574f8d
#158: VersionRange with open boundaries
MattesMrzik Dec 18, 2023
7e2023e
Merge remote-tracking branch 'upstream/main' into feature/#103-implem…
MattesMrzik Dec 19, 2023
64c8454
Merge remote-tracking branch 'origin/feature/#158-version-range-with-…
MattesMrzik Dec 19, 2023
5518138
#103: removed duplicate VersionRange.equals
MattesMrzik Dec 19, 2023
4fbef6e
#103: versionRange with open interval
MattesMrzik Dec 19, 2023
1b9224b
#103: updated urlSecJsonFile.contains
MattesMrzik Dec 19, 2023
9a86e34
#103: rephrase interaction, mapUtil, LICENCE
MattesMrzik Dec 21, 2023
fe9109f
#103: moved urlSecurityJson to its own class
MattesMrzik Dec 22, 2023
b19b877
#103: fixed write json bug, and more
MattesMrzik Dec 22, 2023
fd64100
#103: some final cleanup
MattesMrzik Jan 2, 2024
312afdd
#103: updated to be in line with #158
MattesMrzik Jan 2, 2024
37122ff
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
MattesMrzik Jan 20, 2024
9b28679
#103: fixed small bug due to merged main
MattesMrzik Jan 20, 2024
1389057
#103: fixed bugs
MattesMrzik Jan 20, 2024
98b3da3
#103: fixed bug
MattesMrzik Jan 20, 2024
06cc433
#103: added logging test
MattesMrzik Jan 21, 2024
80ab231
#103: refactored code
MattesMrzik Jan 21, 2024
a26df56
103: first half of team review
MattesMrzik Jan 21, 2024
dbee293
#103: more change requests from team review and bug fix
MattesMrzik Jan 21, 2024
ea1bb26
Update cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
MattesMrzik Jan 22, 2024
b02bfcf
#103: checkpoint to not accidentally lose progress
MattesMrzik Jan 23, 2024
fb6b842
Merge branch 'feature/#103-implement-version-security-checks' of http…
MattesMrzik Jan 23, 2024
0c54094
#103: fixed intellij updater test
MattesMrzik Jan 23, 2024
55f139c
#103: done with change requests
MattesMrzik Jan 24, 2024
d69bea7
Merge branch 'main' of https://www.github.com/devonfw/IDEasy into fea…
MattesMrzik Jan 25, 2024
f34fc22
Update security/src/main/java/com/devonfw/tools/security/BuildSecurit…
MattesMrzik Jan 25, 2024
1c66c77
#103: small fix
MattesMrzik Jan 25, 2024
c78aad4
Merge branch 'feature/#103-implement-version-security-checks' of http…
MattesMrzik Jan 25, 2024
6a20d3c
#103: added tests
MattesMrzik Jan 25, 2024
cbe086d
#103: test for UrlSecurityJson
MattesMrzik Jan 25, 2024
20fecc3
#103: last small changes
MattesMrzik Jan 26, 2024
47ae5b7
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 19, 2024
ae52292
#103: code reformat & cleanup
jan-vcapgemini Feb 19, 2024
f66c7ea
#103: implemented requested changes
jan-vcapgemini Feb 19, 2024
7628cc9
#103: applied reformat
jan-vcapgemini Feb 19, 2024
db6e276
#103: implemented requested changes
jan-vcapgemini Feb 19, 2024
6da9066
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 22, 2024
2862e6b
#103: fixed merge issues
jan-vcapgemini Feb 22, 2024
be3ec96
#103: some fixes
jan-vcapgemini Feb 22, 2024
097bbdc
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 23, 2024
a7d686c
#103: implemented requested changes
jan-vcapgemini Feb 23, 2024
a299504
#103: implemented requested changes
jan-vcapgemini Feb 23, 2024
0f3596f
#103: implemented requested changes
jan-vcapgemini Feb 23, 2024
30d5bf2
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 26, 2024
69e1fdd
#103: fixed intellij and vscode
jan-vcapgemini Feb 29, 2024
4d6766c
#103: fixed NPEs and other issues
jan-vcapgemini Feb 29, 2024
f162e09
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 29, 2024
3834ce8
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Mar 28, 2024
ba4bc07
#103 fixed tests
jan-vcapgemini Apr 2, 2024
d794e67
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Apr 2, 2024
998387d
#103 implemented requested changes
jan-vcapgemini Apr 2, 2024
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 @@ -166,6 +166,7 @@ public Path getPath(String tool) {
*/
public void setPath(String tool, Path path) {

this.paths.add(path);
this.tool2pathMap.put(tool, path);
}
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -657,8 +657,9 @@ public <O> O question(String question, O... options) {
assert (options.length >= 2);
interaction(question);
Map<String, O> mapping = new HashMap<>(options.length);
int i = 1;
int i = 0;
for (O option : options) {
i++;
String key = "" + option;
addMapping(mapping, key, option);
String numericKey = Integer.toString(i);
Expand Down Expand Up @@ -700,6 +701,4 @@ private static <O> void addMapping(Map<String, O> mapping, String key, O option)
}
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ protected boolean doInstall(boolean silent) {
String edition = getEdition();
ToolRepository toolRepository = this.context.getDefaultToolRepository();
VersionIdentifier configuredVersion = getConfiguredVersion();
VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, configuredVersion);
VersionIdentifier selectedVersion = securityRiskInteraction(configuredVersion);
setVersion(selectedVersion, silent);
VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, selectedVersion);
// download and install the global tool
FileAccess fileAccess = this.context.getFileAccess();
Path target = toolRepository.download(this.tool, edition, resolvedVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ public Path getToolBinPath() {
protected boolean doInstall(boolean silent) {

VersionIdentifier configuredVersion = getConfiguredVersion();
VersionIdentifier selectedVersion = securityRiskInteraction(configuredVersion);
setVersion(selectedVersion, silent);
// install configured version of our tool in the software repository if not already installed
ToolInstallation installation = installInRepo(configuredVersion);

ToolInstallation installation = installInRepo(selectedVersion);
// check if we already have this version installed (linked) locally in IDE_HOME/software
VersionIdentifier installedVersion = getInstalledVersion();
VersionIdentifier resolvedVersion = installation.resolvedVersion();
Expand Down
115 changes: 112 additions & 3 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.devonfw.tools.ide.cli.CliException;
Expand All @@ -20,6 +21,8 @@
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessErrorHandling;
import com.devonfw.tools.ide.property.StringListProperty;
import com.devonfw.tools.ide.url.model.file.UrlSecurityJsonFile;
import com.devonfw.tools.ide.url.model.file.UrlSecurityJsonFile.UrlSecurityWarning;
import com.devonfw.tools.ide.util.FilenameUtil;
import com.devonfw.tools.ide.version.VersionIdentifier;

Expand Down Expand Up @@ -65,7 +68,6 @@ public String getName() {
}

/**
*
* @return the name of the binary
*/
protected String getBinaryName() {
Expand All @@ -89,7 +91,7 @@ public void run() {
* Ensures the tool is installed and then runs this tool with the given arguments.
*
* @param toolVersion the explicit version (pattern) to run. Typically {@code null} to ensure the configured version
* is installed and use that one. Otherwise the specified version will be installed in the software repository
* is installed and use that one. Otherwise, the specified version will be installed in the software repository
* without touching and IDE installation and used to run.
* @param args the commandline arguments to run the tool.
*/
Expand Down Expand Up @@ -171,6 +173,112 @@ public boolean install(boolean silent) {
return doInstall(silent);
}

protected String securityRiskInteractionQuestion(String question, String... options) {

question += "Do you want to";
for (int i = 0; i < options.length - 1; i++) {
options[i] += " or";
MattesMrzik marked this conversation as resolved.
Show resolved Hide resolved
}
options[options.length - 1] += "?";
return this.context.question(question, options);
}

/**
* Checks if the given {@link VersionIdentifier} has a matching security warning in the {@link UrlSecurityJsonFile}.
*
* @param configuredVersion the {@link VersionIdentifier} to be checked.
* @return the {@link VersionIdentifier} to be used for installation. If the configured version is safe or there are
* no save versions the potentially unresolved configured version is simply returned. Otherwise, a resolved
* version is returned.
*/
protected VersionIdentifier securityRiskInteraction(VersionIdentifier configuredVersion) {
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved

// TODO maybe instead of returning current return configuredVersion if the users chooses "stay"

// TODO webpage:\nhttps://github.com/devonfw/ide/blob/master/documentation/vulnerabilities.asciidoc\n\n";

UrlSecurityJsonFile securityFile = this.context.getUrls().getEdition(this.tool, this.getEdition())
.getSecurityJsonFile();

VersionIdentifier current = this.context.getUrls().getVersion(this.tool, this.getEdition(), configuredVersion);
// TODO oder doch eher sowas wie VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool,
// edition, selectedVersion); sollte immer das selbe ergeben

if (!securityFile.contains(current)) {
return configuredVersion;
}

List<VersionIdentifier> allVersions = this.context.getUrls().getSortedVersions(this.tool, this.getEdition());
VersionIdentifier latest = allVersions.get(0);

int currentVersionIndex = allVersions.indexOf(current);

VersionIdentifier nextSafe = null;
for (int i = currentVersionIndex - 1; i >= 0; i--) {
if (!securityFile.contains(allVersions.get(i))) {
nextSafe = allVersions.get(i);
break;
}
}
VersionIdentifier latestSafe = null;
for (int i = 0; i < allVersions.size(); i++) {
if (!securityFile.contains(allVersions.get(i))) {
latestSafe = allVersions.get(i);
break;
}
}
String cves = securityFile.getMatchingSecurityWarnings(current).stream().map(UrlSecurityWarning::cveName)
.collect(Collectors.joining(", "));
String currentIsUnsafe = "Currently, version " + current + " of " + this.getName() + " is selected, "
+ "which is has one or more vulnerabilities:\n\n" + cves + "\n\n(See also " + securityFile.getPath() + ")\n\n";
MattesMrzik marked this conversation as resolved.
Show resolved Hide resolved

String stay = "stay with the current unsafe version (" + current + ")";
String installLatestSafe = "install the latest safe version (" + latestSafe + ")";
String installSafeLatest = "install the (safe) latest version (" + latestSafe + ")";
String installNextSafe = "install the next safe version (" + nextSafe + ")";
// I don't need to offer "install latest which is unsafe" as option since the user can set to the latest and choose
// "stay"

if (latestSafe == null) {
this.context.warning(currentIsUnsafe + "There is no safe version available.");
return configuredVersion;
}

if (current.equals(latest)) {
String answer = securityRiskInteractionQuestion(currentIsUnsafe + "There are no updates available.", stay,
installLatestSafe);
return answer.startsWith(stay) ? current : latestSafe;

} else if (nextSafe == null) {
String answer = securityRiskInteractionQuestion(currentIsUnsafe + " All newer versions are also not safe.", stay,
installLatestSafe);
return answer.startsWith(stay) ? current : latestSafe;

} else if (nextSafe.equals(latest)) {
String answer = securityRiskInteractionQuestion(
currentIsUnsafe + " Of the newer versions, only the latest is safe.", stay, installSafeLatest);
return answer.startsWith(stay) ? current : latestSafe;

} else if (nextSafe.equals(latestSafe)) {
String answer = securityRiskInteractionQuestion(currentIsUnsafe + " Of the newer versions, only the version "
+ nextSafe + " is safe, Which is not the latest.", stay, "Install the safe version (" + nextSafe + ")");
return answer.startsWith(stay) ? current : nextSafe;

} else {
if (latestSafe.equals(latest)) {
String answer = securityRiskInteractionQuestion(currentIsUnsafe, stay, installNextSafe, installSafeLatest);
return answer.startsWith(stay) ? current : answer.startsWith(installNextSafe) ? nextSafe : latestSafe;

} else {
String answer = securityRiskInteractionQuestion(currentIsUnsafe, stay, installNextSafe, installLatestSafe);
return answer.startsWith(stay) ? current : answer.startsWith(installNextSafe) ? nextSafe : latestSafe;
}
}

// VersionIdentifier chosenVersion = securityRiskInteraction(configuredVersion);
// setVersion(chosenVersion, silent);
}

/**
* Installs or updates the managed {@link #getName() tool}.
*
Expand All @@ -181,7 +289,8 @@ public boolean install(boolean silent) {
protected abstract boolean doInstall(boolean silent);

/**
* This method is called after the tool has been newly installed or updated to a new version.
* This method is called after the tool has been newly installed or updated to a new version. Override it to add
* custom post installation logic.
*/
protected void postInstall() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ protected String getGithubOrganization() {
return "helm";
}

@Override
public String mapUrlVersionToCpeVersion(String version) {

return version.substring(getVersionPrefixToRemove().length());
}

@Override
protected void addVersion(UrlVersion urlVersion) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ protected String mapVersion(String version) {
return super.mapVersion(version);
}

@Override
public String getCpeVendor() {

return "eclipse";
}

@Override
public String getCpeProduct() {

return "temurin";
}

@Override
protected void addVersion(UrlVersion urlVersion) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.devonfw.tools.ide.url.model.file;

import java.io.BufferedWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.tools.ide.json.mapping.JsonMapping;
import com.devonfw.tools.ide.url.model.folder.UrlEdition;
import com.devonfw.tools.ide.version.VersionIdentifier;
import com.devonfw.tools.ide.version.VersionRange;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* {@link UrlFile} for the "security.json" file.
*/
public class UrlSecurityJsonFile extends AbstractUrlFile<UrlEdition> {

/***
* A simple container with the information about a security warning.
*
* @param versionRange the version range, specifying the versions of the tool to which the security risk applies.
* @param severity the severity of the security risk.
* @param severityVersion Indicating from which version the {@code severity} was obtained. As of December 2023, this
* is either v2 or v3.
* @param cveName the name of the CVE (Common Vulnerabilities and Exposures).
* @param description the description of the CVE.
* @param nistUrl the url to the CVE on the NIST website.
* @param referenceUrl the urls where additional information about the CVE can be found.
*/
public record UrlSecurityWarning(VersionRange versionRange, BigDecimal severity, String severityVersion,
String cveName, String description, String nistUrl, List<String> referenceUrl) {
};

/** {@link #getName() Name} of security json file. */
public static final String FILENAME_SECURITY = "security.json";

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

private Set<UrlSecurityWarning> warnings;

/**
* The constructor.
*
* @param parent the {@link #getParent() parent folder}.
*/
public UrlSecurityJsonFile(UrlEdition parent) {

super(parent, FILENAME_SECURITY);
this.warnings = new HashSet<>();
}

/***
* Adds a new security warning to the security json file.
*
* @param versionRange the version range, specifying the versions of the tool to which the security risk applies.
* @param severity the severity of the security risk.
* @param severityVersion Indicating from which version the {@code severity} was obtained. As of December 2023, this
* is either v2 or v3.
* @param cveName the name of the CVE (Common Vulnerabilities and Exposures).
* @param description the description of the CVE.
* @param nistUrl the url to the CVE on the NIST website.
* @param referenceUrl the urls where additional information about the CVE can be found.
MattesMrzik marked this conversation as resolved.
Show resolved Hide resolved
* @return {@code true} if the security match was added, {@code false} if it was already present.
*/
public boolean addSecurityWarning(VersionRange versionRange, BigDecimal severity, String severityVersion,
String cveName, String description, String nistUrl, List<String> referenceUrl) {

UrlSecurityWarning newWarning = new UrlSecurityWarning(versionRange, severity, severityVersion, cveName,
description, nistUrl, referenceUrl);
boolean added = warnings.add(newWarning);
this.modified = this.modified || added;
return added;
}

/***
* For a given version, returns whether there is a security risk by locking at the warnings in the security json file.
*
* @param version the version to check for security risks.
* @return {@code true} if there is a security risk for the given version, {@code false} otherwise.
*/
public boolean contains(VersionIdentifier version) {

for (UrlSecurityWarning warning : this.warnings) {
if (warning.versionRange().contains(version)) {
return true;
}
}
return false;
}

public Set<UrlSecurityWarning> getMatchingSecurityWarnings(VersionIdentifier version) {
MattesMrzik marked this conversation as resolved.
Show resolved Hide resolved

Set<UrlSecurityWarning> matchedWarnings = new HashSet<>();
for (UrlSecurityWarning warning : this.warnings) {
if (warning.versionRange().contains(version)) {
matchedWarnings.add(warning);
}
}
return matchedWarnings;
}

public void clearSecurityWarnings() {
MattesMrzik marked this conversation as resolved.
Show resolved Hide resolved

this.warnings.clear();
}

@Override
protected void doLoad() {

if (!Files.exists(getPath())) {
return;
}
ObjectMapper mapper = JsonMapping.create();
try {
warnings = mapper.readValue(getPath().toFile(), new TypeReference<Set<UrlSecurityWarning>>() {
});
} catch (IOException e) {
throw new IllegalStateException("The UrlSecurityJsonFile " + getPath() + " could not be parsed.", e);
}
}

@Override
protected void doSave() {

Path path = getPath();
ObjectMapper mapper = JsonMapping.create();

if (this.warnings.isEmpty() && !Files.exists(path)) {
return;
}

String jsonString;
try {
jsonString = mapper.writeValueAsString(warnings);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}

try (BufferedWriter bw = Files.newBufferedWriter(path, StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
bw.write(jsonString + "\n");
} catch (IOException e) {
throw new IllegalStateException("Failed to save file " + path, e);
}
}
}
Loading