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

SCAN4NET-227 Use system trusted certificate or JVM certificate store #2330

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

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?

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

return null;
}

return sonarScannerOpts.Split(' ').FirstOrDefault(x => x.StartsWith($"-D{JAVAX_NET_SSL_TRUST_STORE_PASSWORD}=")) is { } truststorePassword

Choose a reason for hiding this comment

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

I think it is safe to specify StringComparison.OrdinalIgnoreCase:

Suggested change
return sonarScannerOpts.Split(' ').FirstOrDefault(x => x.StartsWith($"-D{JAVAX_NET_SSL_TRUST_STORE_PASSWORD}=")) is { } truststorePassword
return sonarScannerOpts.Split(' ').FirstOrDefault(x => x.StartsWith($"-D{JAVAX_NET_SSL_TRUST_STORE_PASSWORD}=", StringComparison.OrdinalIgnoreCase)) is { } truststorePassword

Copy link
Contributor Author

@sebastien-marichal sebastien-marichal Feb 20, 2025

Choose a reason for hiding this comment

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

I am not sure, I think the properties are case sensitive.
During my test I made several times the mistake of writing down the javax properties like truststore instead of trustStore.

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

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

Quality Gate Failed Quality Gate failed

Failed conditions
6 New issues

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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