Skip to content

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

Merged
merged 8 commits into from
Feb 25, 2025

Conversation

sebastien-marichal
Copy link
Contributor

@sebastien-marichal sebastien-marichal commented Feb 19, 2025

Copy link
Contributor

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);

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");

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?

Copy link
Contributor Author

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"

Choose a reason for hiding this comment

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

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"

Choose a reason for hiding this comment

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

Suggested change
if (new Uri(LocalSettings.ServerInfo.ServerUrl).Scheme != "https"
if (new Uri(LocalSettings.ServerInfo.ServerUrl).Scheme != Uri.UriSchemeHttps

var javaHome = Environment.GetEnvironmentVariable("JAVA_HOME");
if (javaHome is null)
{
// TODO: propagate the error?

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.

Copy link
Contributor Author

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.

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?

Copy link
Contributor Author

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.

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.

Copy link
Contributor Author

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.

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.

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.

Copy link
Contributor

@martin-strecker-sonarsource martin-strecker-sonarsource Feb 20, 2025

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.

Copy link
Contributor Author

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");

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);

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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 };

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

found here:
https://github.com/Tyrrrz/CliWrap/blob/7676d77ec3230c893511efc83b7a98efd6bb9d02/CliWrap/Command.Execution.cs#L43


public class LocalJreTruststoreResolver(IFileWrapper fileWrapper, IDirectoryWrapper directoryWrapper, IProcessRunner processRunner, ILogger logger)
{
public string TruststorePath(ProcessedArgs args)

Choose a reason for hiding this comment

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

Suggested change
public string TruststorePath(ProcessedArgs args)
public string UnixTruststorePath(ProcessedArgs args)

@sebastien-marichal sebastien-marichal marked this pull request as ready for review February 25, 2025 13:32
Copy link

Copy link
Contributor

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.

Comment on lines +315 to +316
processRunner.Execute(Arg.Any<ProcessRunnerArguments>()).Returns(true);
processRunner.StandardOutput.Returns(new StringReader("/usr/bin/java"), new StringReader("/java/home/bin/java"));

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); and processRunner.Execute(...).Returns(true);
  • Add comment about what the "/usr/bin/java" and "/java/home/bin/java" are supposed to represent.

Comment on lines +25 to +27
public TextReader StandardOutput { get; }
public TextReader ErrorOutput { get; }

Copy link
Contributor

@martin-strecker-sonarsource martin-strecker-sonarsource Feb 25, 2025

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/

@sebastien-marichal sebastien-marichal merged commit 2ffa6f0 into master Feb 25, 2025
17 checks passed
@sebastien-marichal sebastien-marichal deleted the sma/cert-fallback branch February 25, 2025 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants