When you are running SonarQube on-premise, it is most likely that you want to use a self-signed certificate to keep the tool secured. Unfortunately, SonarQube is a Java based application, so getting it up and running is not as straight-forward as expected. In my case, after configuring SQ for HTTPS, the SonarQube extension for Azure Devops started complaining that it detected a self-signed certificate in the chain.
[error][SQ] API GET ‘/api/server/version’ failed, error was: {“code”:”SELF_SIGNED_CERT_IN_CHAIN”}
There are 3 basic steps that are performed in the Azure Devops pipeline. First step is to prepare the SonarQube analysis. The second step is to compile your code and run unit tests. The last step is to complete the SonarQube analysis.
SonarQube uses both NPM and Java to do its thing. This certificate error is caused by NPM. Solving the SELF_SIGNED_CERT_IN_CHAIN error requires a PEM file.
A PEM file is usually used to import several certificates at once. Creating the file is easy. You need to make an export from your entire certificate chain in Base-64 encoded X.509 format. Open each certificate with notepad and copy the entire content into your certificates.pem file.
It should look something like this:
-----BEGIN CERTIFICATE----- (Your Primary SSL certificate: your_sonarqube.cer) -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- (Your Intermediate certificate: DigiCertCA.cer) -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- (Your Root certificate: TrustedRoot.cer) -----END CERTIFICATE-----
Place your PEM file and add the following environment variable on your build server:
Name: NODE_EXTRA_CA_CERTS Value: C:\Certificates\Certificates.pem
Restart the build agents, and we have success!… euhmm, well, sort off…
After a closer look at the error log, the following line popped out:
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Apparently, the second stage does not use the NPM settings we just did. We actually need to add the certificate chain we exported to create the PEM file into Java certificate store. This can be done by the following command:
Keytool.exe -import -cacerts -storepass changeit -alias "certificate name" -file "<Location>\Certificate.cer
Keytool can be found in the bin directory of your Java folder. In my case it is C:\Program Files\OpenJDK\jdk-15\bin\keytool.exe
Are we done? Not yet. SonarQube needs to know where the keystore is located. This can be done with an extra environment variable.
Name: SONAR_SCANNER_OPTS Value: -Djavax.net.ssl.trustStore="C:\Program Files\OpenJDK\jdk-15\lib\security\cacerts" -Djavax.net.ssl.keyStore="C:\Program Files\OpenJDK\jdk-15\lib\security\cacerts"
And there we have it!
To make this all a bit easier for the future, I have written a PowerShell script for all these actions. All you need to run this script are the CERT files, the PEM file and OpenJDK installed on your build server.
Param ( [string]$Location = 'C:\Certificates', [string]$StorePass = 'changeit', [string]$PemFile = "$Location\certificates.pem" ) Write-Warning "The output of this script might contain errors. keytool.exe is using exit code 1 for a successful run :/ . `"NotSpecified: (Certificate was added to keystore`" error can safely be ignored." #Find the keytool Write-Host "Locating keytool.exe ..." [string]$Keytool = (where.exe /r "$env:programfiles\OpenJDK" keytool.exe | Sort-Object -Descending)[0] "Keytool: $Keytool" $KeyStore = $KeyTool.Replace('bin\keytool.exe', 'lib\security\cacerts') #Add certs $Certificates = (Get-ChildItem $Location -Filter *.cer).baseName Foreach ($Certificate in $Certificates) { if (-not (. "$keytool" -list -storepass "$StorePass" -alias "$Certificate" -cacerts | select-string "trustedCertEntry")) { #Add key (dirty code, but default keytool -import throws an error when it succeeds) write-host "Adding $Certificate to the keystore ..." . "$keytool" -import -cacerts -storepass "$StorePass" -alias "$Certificate" -file "$Location\$Certificate.cer" -noprompt #add key #test if its there if (-not (. "$keytool" -list -storepass "$StorePass" -alias "$Certificate" -cacerts | select-string "trustedCertEntry")) { Write-Host "##vso[task.logissue type=error]Importing $Certificate unsuccessful." } } else { Write-Host "$Certificate is already registered." } } #Add Environment Variable Write-Host "Adding 'NODE_EXTRA_CA_CERTS' to the Machine System variable." [System.Environment]::SetEnvironmentVariable('NODE_EXTRA_CA_CERTS', $PemFile, [System.EnvironmentVariableTarget]::Machine) Write-Host "Adding 'SONAR_SCANNER_OPTS' to the Machine System variable." [System.Environment]::SetEnvironmentVariable("SONAR_SCANNER_OPTS", "-Djavax.net.ssl.trustStore=`"$KeyStore`" -Djavax.net.ssl.keyStore=`"$KeyStore`"", [System.EnvironmentVariableTarget]::Machine)
Don’t forget to restart your Azure Devops Agents after running the script, and you are good to go!