-
Notifications
You must be signed in to change notification settings - Fork 143
SCAN4NET-227 Use system trusted certificate or JVM certificate store #2330
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
Conversation
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.
Looks good for me and should do the trick. I left some small comments but overall this looks promising.
|
||
// Act | ||
processor.Update(config); | ||
|
||
// Assert | ||
config.LocalSettings.Should().ContainSingle(x => x.Id == SonarProperties.TruststorePassword && x.Value == input); | ||
config.ScannerOptsSettings.Should().ContainSingle(); | ||
config.ScannerOptsSettings.Should().HaveCount(2); | ||
AssertExpectedScannerOptsSettings("javax.net.ssl.trustStore", javaHomeCacerts.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), config); | ||
AssertExpectedScannerOptsSettings("javax.net.ssl.trustStorePassword", expected, config); |
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.
You could additionally assert that fileWrapper.Exists(javaHomeCacerts)
was called once.
var config = new AnalysisConfig { LocalSettings = [] }; | ||
using var envScope = new EnvironmentVariableScope(); | ||
envScope.SetVariable("JAVA_HOME", javaHome); | ||
envScope.SetVariable("SONAR_SCANNER_OPTS", "-Djavax.net.ssl.trustStorePassword=itchange"); |
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.
What happens if we also set another property here, e.g. -Xmx2048m
?
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.
If the value is still in the env variable during the end step, it will be kept.
I have added trustedSelfSignedCertificate_ExistingValueInScannerOpts
in the ITs to test that behavior.
MapProperty(config, "javax.net.ssl.trustStore", PropertyValueOrDefault(SonarProperties.TruststorePath, LocalSettings.TruststorePath), ConvertToJavaPath, EnsureSurroundedByQuotes); | ||
var password = PropertyValueOrDefault(SonarProperties.TruststorePassword, LocalSettings.TruststorePassword); | ||
MapProperty(config, "javax.net.ssl.trustStorePassword", password, EnsureSurroundedByQuotes); | ||
if (new Uri(LocalSettings.ServerInfo.ServerUrl).Scheme != "https" |
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.
This constructor throws. Consider using TryCreate(url, UriKind.Absolute, out var uri)
instead.
MapProperty(config, "javax.net.ssl.trustStore", PropertyValueOrDefault(SonarProperties.TruststorePath, LocalSettings.TruststorePath), ConvertToJavaPath, EnsureSurroundedByQuotes); | ||
var password = PropertyValueOrDefault(SonarProperties.TruststorePassword, LocalSettings.TruststorePassword); | ||
MapProperty(config, "javax.net.ssl.trustStorePassword", password, EnsureSurroundedByQuotes); | ||
if (new Uri(LocalSettings.ServerInfo.ServerUrl).Scheme != "https" |
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.
if (new Uri(LocalSettings.ServerInfo.ServerUrl).Scheme != "https" | |
if (new Uri(LocalSettings.ServerInfo.ServerUrl).Scheme != Uri.UriSchemeHttps |
...er.MSBuild.PreProcessor/AnalysisConfigProcessing/Processors/TruststorePropertiesProcessor.cs
Outdated
Show resolved
Hide resolved
var javaHome = Environment.GetEnvironmentVariable("JAVA_HOME"); | ||
if (javaHome is null) | ||
{ | ||
// TODO: propagate the error? |
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.
I don't think so. A debug log should be enough.
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.
It will not fail before the end step then.
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.
If the JRE provisioning is enabled, JAVA_HOME
does not have to be set. There might be cases where the provisioned JRE trusts the server anyhow. The JRE comes with a default set of CA roots. If we fail it here, it would break https servers with a proper purchased certificate or one from https://letsencrypt.org/
Or am I miss something here?
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.
If this is from letsencrypt, it should be trusted without giving a store.
My understanding of purchased certificates is that you don't need to provide a truststore as they should be valid from some public CA.
I would be for failing the execution to follow the fail fast principle but I am not 100% sure that in this case there is no scenario where it would work.
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.
The scenario is:
- JRE provisioning is enabled and executed
- JAVA_HOME is not set
- The server is providing a certificate from a CA root trusted by the provisioned JRE (e.g. Let's Encrypt, Digi Cert, etc.)
- The user doesn't specify anything (
truststorePath is null
) - We are on Linux
FindJavaTruststorePath
is called
-> We would fail the begin step, even though everything would just work.
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.
Can we safely assume that the cacerts
of JAVA_HOME would be the same the one from the provisioned JRE?
If we take the same scenario with JAVA_HOME set, we will tell the provisioned JRE to use the local cacerts
rather that the one download. I wonder if there is a possibility for the scenario to succeed with one and not the other where in theory it should succeed with both.
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.
The root ca certs change on a regular basis (see e.g. here https://learn.microsoft.com/en-us/security/trusted-root/release-notes). It is likely that we will have different CA roots in both locations.
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.
Note: I think this is a problem the scanner-cli may have as well.
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.
Also: As a user, you can install custom root CAs in your JAVA_HOME, but you can not install root CAs in the provisioned JRE. Therefore, we should prefer JAVA_HOME over the provisioned JRE.
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.
Sounds good to me, I will only do a log.
return null; | ||
} | ||
|
||
var javaTruststorePath = Path.Combine(javaHome, "lib", "security", "cacerts"); |
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.
Please add a link to some kind of documentation or ticket about the source of these path.
@@ -131,6 +131,7 @@ private static bool ServerCertificateCustomValidationCallback(X509Certificate2Co | |||
{ | |||
if (errors is SslPolicyErrors.None) | |||
{ | |||
logger.LogWarning(Resources.WARN_CertificateTrustedBySystem); |
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.
The remote server certificate is trusted by the operating system. Provided truststore was not required.
If this is issued as a warning, it sounds like the command line argument can be removed from the begin step call. Is this always true? I know we set JAVAX_NET_SSL_TRUST_STORE_TYPE=Windows-ROOT
but is this always working as intended. And what about Linux, here? I would turn this into a debug message. It does no harm if the user specified the file, right?
I would also remove this part: Provided truststore was not required.
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.
I will update the message. I think it is important to keep in the message that a truststore has been provided to have context.
Something like: Provided truststore was not used.
1068943
to
70e2345
Compare
70e2345
to
7bb1f16
Compare
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.
I think we should sh
instead of bash
|
||
if (javaExePath is null || !fileWrapper.Exists(javaExePath)) | ||
{ | ||
var commandArgs = new ProcessRunnerArguments("/bin/bash", false) { CmdLineArgs = ["-c", "command -v java"], LogOutput = false }; |
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.
Have you tried using sh
instead? It is POSIX compatible.
Also we should not rely on bin/sh
according to the standard.
Applications should note that the standard PATH to the shell cannot be assumed to be either /bin/sh or /usr/bin/sh, and should be determined by interrogation of the PATH returned by getconf PATH, ensuring that the returned pathname is an absolute pathname and not a shell built-in.
On some implementations this might return:
/usr/xpg4/bin/sh
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html
I think bin/sh
is fine, but if you want to got the extra mile there is:
https://github.com/dotnet/runtime/blob/9a50493f9f1125fda5e2212b9d6718bc7cdbc5c0/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L686-L728
|
||
public class LocalJreTruststoreResolver(IFileWrapper fileWrapper, IDirectoryWrapper directoryWrapper, IProcessRunner processRunner, ILogger logger) | ||
{ | ||
public string TruststorePath(ProcessedArgs args) |
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.
public string TruststorePath(ProcessedArgs args) | |
public string UnixTruststorePath(ProcessedArgs args) |
bd5dec8
to
ab213f0
Compare
ab213f0
to
ae557b3
Compare
|
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.
Approving, but I would recommend removing the stateful properties from the ProcessRunner in a follow up PR.
processRunner.Execute(Arg.Any<ProcessRunnerArguments>()).Returns(true); | ||
processRunner.StandardOutput.Returns(new StringReader("/usr/bin/java"), new StringReader("/java/home/bin/java")); |
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.
Can you add be a bit more specific here?
Either
- Use
processRunner.Execute(Arg.Is<ProcessRunnerArguments>(x => x.Arguments is ["-c", "..."]).Returns(true);
andprocessRunner.Execute(...).Returns(true);
- Add comment about what the "/usr/bin/java" and "/java/home/bin/java" are supposed to represent.
public TextReader StandardOutput { get; } | ||
public TextReader ErrorOutput { get; } | ||
|
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.
I'm not a big fan of making IProcessRunner
statefull. Can we instead do
bool Execute(ProcessRunnerArguments runnerArgs);
bool Execute(ProcessRunnerArguments runnerArgs, out TextReader standardOutput, out TextReader errorOutput);
This makes testing a bit harder, but it is possbile:
https://nsubstitute.github.io/help/setting-out-and-ref-arguments/
SCAN4NET-227