Skip to content

Commit ea62878

Browse files
authored
Merge pull request #126 from sgala/fips_detection_certs
Fips changes for JWS in noe-core
2 parents 67be2f0 + 5705d30 commit ea62878

29 files changed

+9714
-26
lines changed

core/src/main/groovy/noe/common/DefaultProperties.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ class DefaultProperties {
6161
"${JENKINS_JOBS_DIR_PREFIX}/clusterbench-mod_cluster-mbabacek/lastSuccessful/archive/clusterbench-ee6-web-nondist/target/clusterbench.war")
6262
public static final boolean SOLARIS_DEFAULT_LIBRARY_PATH_CLEAN = Boolean.valueOf(Library.getUniversalProperty('solaris.default.library.path.clean', 'false'))
6363

64+
// Get the fips self signed directory depending on whether fips on the OS is enabled or not
65+
public static final String SELF_SIGNED_CERTIFICATE_RESOURCE = Library.getUniversalProperty('self_signed.certificate.resource',
66+
new Platform().isFips() ? "self_signed_fips" : "self_signed")
67+
public static final String FIPS_140_2_CIPHERS = "SSL_RSA_WITH_3DES_EDE_CBC_SHA,SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_anon_WITH_AES_128_CBC_SHA,TLS_ECDH_anon_WITH_AES_256_CBC_SHA"
68+
6469
/**
6570
* Method which tries to retrieve version from property, if it doesn't succeed, null is returned
6671
*/

core/src/main/groovy/noe/common/utils/Cmd.groovy

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,8 @@ public class Cmd {
854854
try {
855855
javaPids.add(Integer.parseInt(pid))
856856
} catch (NumberFormatException ex) {
857-
throw new NumberFormatException("Guys, I can't parse Integer from:${pid}", ex)
857+
log.error("Error trying to parse process ID from: \"${pid}\"")
858+
throw ex
858859
}
859860
}
860861

@@ -897,7 +898,8 @@ public class Cmd {
897898
try {
898899
pids.add(Integer.parseInt(pid))
899900
} catch (NumberFormatException ex) {
900-
throw new NumberFormatException("Guys, I can't parse Integer from:${pid}", ex)
901+
log.error("Error trying to parse process ID from: \"${pid}\"")
902+
throw ex
901903
}
902904
}
903905

@@ -952,7 +954,8 @@ public class Cmd {
952954
try {
953955
pids.add(Integer.parseInt(pid))
954956
} catch (NumberFormatException ex) {
955-
throw new NumberFormatException("Guys, I can't parse Integer from:${pid}", ex)
957+
log.error("Error trying to parse process ID from: \"${pid}\"")
958+
throw ex
956959
}
957960
} else {
958961
log.error("WIDLE match didn't succeed :-) it was: match.size():${match.size()}")
@@ -1053,9 +1056,9 @@ public class Cmd {
10531056
*/
10541057
static boolean waitForPidsRemoved(List<Integer> pids, int timeout, TimeUnit timeUnit) {
10551058
long maxTime = System.currentTimeMillis() + timeUnit.toMillis(timeout)
1056-
boolean anyPidExist = getPidList().intersect(pids).isEmpty()
1059+
boolean anyPidExist = !getPidList().intersect(pids).isEmpty()
10571060
while (anyPidExist && System.currentTimeMillis() <= maxTime) {
1058-
anyPidExist = getPidList().intersect(pids).isEmpty()
1061+
anyPidExist = !getPidList().intersect(pids).isEmpty()
10591062
Library.letsSleep(42)
10601063
}
10611064
if (anyPidExist) {

core/src/main/groovy/noe/common/utils/Platform.groovy

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,21 @@ class Platform {
152152
return isHP() && (osName ==~ /.*11.*/)
153153
}
154154

155+
boolean isFips() {
156+
if(isRHEL()) {
157+
// Using return (['/usr/bin/fips-mode-setup', '--is-enabled'].execute().waitFor() == 0)
158+
// does not work on RHEL7
159+
try {
160+
return (new File('/proc/sys/crypto/fips_enabled').readLines()[0]=='1')
161+
} catch(Exception e) {
162+
log.error("Can't detect FIPS enabled using /proc for a RHEL machine, will return false")
163+
return false
164+
}
165+
}
166+
//For the moment return false for non-RHEL targets
167+
return false
168+
}
169+
155170
public String getScriptSuffix() {
156171
return isWindows() ? 'bat' : 'sh'
157172
}

core/src/main/groovy/noe/server/ApacheDS.groovy

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import noe.common.newcmd.CmdBuilder
55
import noe.common.newcmd.CmdCommand
66
import noe.common.utils.Cmd
77
import noe.common.utils.JBFile
8+
import noe.common.utils.Java
9+
810
/**
911
* Class for manipulting with ApacheDS server
1012
* - Pure java LDAP, Kerberos server
@@ -52,6 +54,24 @@ abstract class ApacheDS extends ServerAbstract {
5254

5355
portsAvailable()
5456
CmdCommand cmdCommand = new CmdBuilder<>(start).setWorkDir(new File(apacheDSBin)).build()
57+
Map cmdProps = cmdCommand.getEnvProperties()
58+
if(platform.isFips()) {
59+
//ApacheDS won't start with FIPS
60+
def opts = cmdProps.getOrDefault("JAVA_OPTS","")
61+
if(opts.size()>0) {
62+
opts+=' '
63+
}
64+
opts+='-Dcom.redhat.fips=false'
65+
cmdProps.put("JAVA_OPTS", opts)
66+
}
67+
if(Java.isJdkXOrHigher("17")) {
68+
def opts = cmdProps.getOrDefault("JAVA_OPTS", "")
69+
if (opts.size() > 0) {
70+
opts += ' '
71+
}
72+
opts += '--add-exports=java.base/sun.security.util=ADD-UNNAMED'
73+
cmdProps.put("JAVA_OPTS", opts)
74+
}
5575
process = Cmd.startProcess(cmdCommand)
5676
process.consumeProcessOutput(System.out, System.err)
5777
waitForStartComplete()

core/src/main/groovy/noe/server/Httpd.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ abstract class Httpd extends ServerAbstract {
6767
this.cachePath = this.basedir + platform.sep + 'cache'
6868
postInstallErrFile = new File(getHttpdServerRootFull(), 'httpdPostInstallErr.log')
6969
postInstallOutFile = new File(getHttpdServerRootFull(), 'httpdPostInstallOut.log')
70-
String sslStringDir = PathHelper.join(platform.tmpDir, "ssl", "self_signed")
70+
String sslStringDir = PathHelper.join(platform.tmpDir, "ssl", DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE)
7171
this.sslCertDir = new File(sslStringDir)
7272
this.sslCertificate = new File(sslCertDir, "server.crt").absolutePath
7373
this.sslKey = new File(sslCertDir, "server.key").absolutePath

core/src/main/groovy/noe/server/ServerAbstract.groovy

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import noe.app.IApp
66
import noe.common.DefaultProperties
77
import noe.common.utils.Cmd
88
import noe.common.utils.JBFile
9+
import noe.common.utils.Java
910
import noe.common.utils.Library
1011
import noe.common.utils.Platform
1112
import noe.common.utils.Version
1213
import noe.common.utils.processid.ProcessUtils
1314

1415
import java.util.concurrent.TimeUnit
16+
import java.util.regex.Pattern
1517

1618

1719
@Slf4j
@@ -54,6 +56,8 @@ abstract class ServerAbstract implements IApp {
5456
String sslCertificate
5557
String sslKey
5658
String keystorePath
59+
String keystoreType
60+
String securityPath
5761
String sslKeystorePassword
5862

5963
// truststore related properties
@@ -98,11 +102,20 @@ abstract class ServerAbstract implements IApp {
98102
this.serverRoot = basedir
99103
this.host = (host) ?: DefaultProperties.HOST
100104
this.ignoreShutdownPort = true
101-
this.sslCertificate = getDeplSrcPath() + "${platform.sep}ssl${platform.sep}self_signed${platform.sep}server.crt"
102-
this.sslKey = getDeplSrcPath() + "${platform.sep}ssl${platform.sep}self_signed${platform.sep}server.key"
103-
this.keystorePath = getDeplSrcPath() + "${platform.sep}ssl${platform.sep}self_signed${platform.sep}server.jks"
104-
this.truststorePassword = 'changeit'
105+
this.sslCertificate = getDeplSrcPath() + "${platform.sep}ssl${platform.sep}${DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE}${platform.sep}server.crt"
106+
this.sslKey = getDeplSrcPath() + "${platform.sep}ssl${platform.sep}${DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE}${platform.sep}server.key"
107+
if( platform.isFips() ) {
108+
this.keystorePath = "${platform.tmpDir}${platform.sep}ssl${platform.sep}${DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE}${platform.sep}nssdb"
109+
this.keystoreType = 'PKCS11'
110+
def variant = (Java.openJDK ? "openjdk" : (Java.oracleJDK ? "oracle-java" : "ibm-java")) +
111+
(Java.isJdk8() ? "-1.8" : ( Java.isJdk11()? "-11" : "-17"))
112+
this.securityPath = "${platform.tmpDir}${platform.sep}ssl${platform.sep}${DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE}${platform.sep}java.security."+ variant
113+
} else{
114+
this.keystorePath = getDeplSrcPath() + "${platform.sep}ssl${platform.sep}${DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE}${platform.sep}server.jks"
115+
this.keystoreType = 'jks'
116+
}
105117
this.sslKeystorePassword = 'changeit'
118+
this.truststorePassword = 'changeit'
106119
this.pid = null
107120
setRunAs(loadRunAs())
108121
this.processCode = String.valueOf(Math.abs(this.hashCode()))
@@ -603,10 +616,10 @@ abstract class ServerAbstract implements IApp {
603616
}
604617

605618
/**
606-
* Check log files for ERRORS and WARNINGS
619+
* Check log files for ERRORS and WARNINGS, ignoring a list of patterns, empty by default
607620
* TODO add offsets for logs (or delete logs before each test - replace symlinks)
608621
*/
609-
List<String> verifyLogs() {
622+
List<String> verifyLogs( List<String>filtered=[]) {
610623
def affectedLines = []
611624

612625
logDirs.each { logDir ->
@@ -627,6 +640,13 @@ abstract class ServerAbstract implements IApp {
627640
}
628641

629642
}
643+
affectedLines.removeIf { line-> filtered.any {
644+
pat -> if(line =~ pat) {
645+
log.debug("Filtered Warning: ${line}")
646+
return true
647+
}
648+
}
649+
}
630650
}
631651
}
632652
return affectedLines
@@ -871,8 +891,12 @@ abstract class ServerAbstract implements IApp {
871891
Boolean verifySecuredUrl(int code = 200, String content = "", long timeout = 30000, Boolean allowRedirects = true, Boolean setReqProp = false,
872892
String reqKey = "", String reqValue = "", String urlPath = '') {
873893
System.setProperty('javax.net.ssl.trustStore', getKeystorePath())
894+
System.setProperty('javax.net.ssl.trustStoreType', getKeystoreType())
895+
System.setProperty('javax.net.ssl.trustStorePassword', getSslKeystorePassword())
874896
def ret = Library.verifyUrl(getUrl(urlPath, true), code, content, timeout, allowRedirects, setReqProp, reqKey, reqValue)
875897
System.clearProperty('javax.net.ssl.trustStore')
898+
System.clearProperty('javax.net.ssl.trustStoreType')
899+
System.clearProperty('javax.net.ssl.trustStorePassword')
876900
return ret
877901
}
878902

core/src/main/groovy/noe/server/Tomcat.groovy

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,10 @@ class Tomcat extends ServerAbstract implements WorkerServer {
6262
this.cfgHost = (cfgHost) ?: ''
6363
postInstallErrFile = new File(basedir, 'tomcatPostInstallErr.log')
6464
postInstallOutFile = new File(basedir, 'tomcatPostInstallOut.log')
65-
String sslStringDir = PathHelper.join(platform.tmpDir, "ssl", "self_signed")
65+
String sslStringDir = PathHelper.join(platform.tmpDir, "ssl", DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE)
6666
this.sslCertDir = new File(sslStringDir)
6767
this.sslCertificate = new File(sslCertDir, "server.crt").absolutePath
6868
this.sslKey = new File(sslCertDir, "server.key").absolutePath
69-
this.keystorePath = new File(sslCertDir, "server.jks").absolutePath
7069

7170
}
7271

@@ -121,7 +120,7 @@ class Tomcat extends ServerAbstract implements WorkerServer {
121120
File f = new File(keystorePath)
122121
URL certUrl = f.toURI().toURL()
123122
webClient.getOptions().setUseInsecureSSL(true)
124-
webClient.getOptions().setSSLClientCertificate(certUrl, sslKeystorePassword, "jks")
123+
webClient.getOptions().setSSLClientCertificate(certUrl, sslKeystorePassword, keystoreType)
125124
serverUrl = this.getUrl('', true)
126125
} else {
127126
serverUrl = this.getUrl()
@@ -192,6 +191,19 @@ class Tomcat extends ServerAbstract implements WorkerServer {
192191
}
193192
}
194193

194+
/**
195+
* Check log files for ERRORS and WARNINGS
196+
*/
197+
List<String> verifyLogs() {
198+
final List<String> defaultFilteredLines = platform.isFips() ? Arrays.asList(
199+
"Creation of SecureRandom instance for session ID generation using \\[.*\\] took \\[",
200+
"Exception initializing random number generator using algorithm \\[SHA1PRNG\\]",
201+
"ErrorReportValve\\.java"
202+
) : Arrays.asList(
203+
"Creation of SecureRandom instance for session ID generation using \\[.*\\] took \\[")
204+
return super.verifyLogs(defaultFilteredLines)
205+
}
206+
195207
/**
196208
* Start the server Tomcat with JSVC wrapper
197209
*/
@@ -636,9 +648,16 @@ class Tomcat extends ServerAbstract implements WorkerServer {
636648
def dummyComment = '<!-- Define an AJP 1.3 Connector on port 8009 -->'
637649
def enableJavaSsl = '<Connector port="' + this.mainHttpsPort.toString() + '" protocol="HTTP/1.1" SSLEnabled="true"' + nl +
638650
'maxThreads="150" scheme="https" secure="true"' + nl +
639-
'keystoreFile="' + this.keystorePath + '" keystorePass="' + this.sslKeystorePassword + '"' + nl +
651+
'keystoreFile="' + this.keystorePath + '" keystoreType="' + this.keystoreType + '" keystorePass="' + this.sslKeystorePassword + '"' + nl +
640652
'clientAuth="false" sslProtocol="TLS" />'
641-
653+
if (platform.isFips()) {
654+
enableJavaSsl = '<Connector port="' + this.mainHttpsPort.toString() + '" protocol="HTTP/1.1"' + nl +
655+
' SSLEnabled="true" maxThreads="150" scheme="https" secure="true"' + nl +
656+
' clientAuth="false" sslEnabledProtocols="TLSv1.1+TLSv1.2"' + nl +
657+
' keystorePass="' + this.sslKeystorePassword + '"' + nl +
658+
' keystoreType="' + this.keystoreType + '"' + nl +
659+
' ciphers="' + DefaultProperties.FIPS_140_2_CIPHERS+ '" />'
660+
}
642661
updateConfReplaceRegExp('server.xml', aprListener, commentAprListener, true, true)
643662
updateConfReplaceRegExp('server.xml', dummyComment, enableJavaSsl, true, true)
644663
}
@@ -651,8 +670,11 @@ class Tomcat extends ServerAbstract implements WorkerServer {
651670
'maxThreads="200" scheme="https" secure="true"' + nl +
652671
'SSLCertificateFile="' + this.sslCertificate + '"' + nl +
653672
'SSLCertificateKeyFile="' + this.sslKey + '"' + nl +
654-
'SSLPassword="' + this.sslKeystorePassword + '"' + nl +
655-
'/>'
673+
'SSLPassword="' + this.sslKeystorePassword + '"' + nl
674+
if(platform.isFips()) {
675+
enableOpenSsl += 'ciphers="' + DefaultProperties.FIPS_140_2_CIPHERS + '"'
676+
}
677+
enableOpenSsl += '/>'
656678

657679
updateConfReplaceRegExp('server.xml', dummyComment, enableOpenSsl, true, true)
658680
}
@@ -666,7 +688,13 @@ class Tomcat extends ServerAbstract implements WorkerServer {
666688
def enableNIOSsl = '<Connector port="' + this.mainHttpsPort.toString() + '" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true"' + nl +
667689
'maxThreads="150" scheme="https" secure="true"' + nl +
668690
'keystoreFile="' + this.keystorePath + '" keystorePass="' + this.sslKeystorePassword + '"' + nl +
669-
'clientAuth="false" sslProtocol="TLS" />'
691+
'clientAuth="false" '
692+
if(platform.isFips()) {
693+
enableNIOSsl += 'sslEnabledProtocols="TLSv1.1+TLSv1.2" '
694+
} else {
695+
enableNIOSsl += 'sslProtocol="TLS" '
696+
}
697+
enableNIOSsl += 'keystoreType="' + this.keystoreType + '" />'
670698

671699
updateConfReplaceRegExp('server.xml', aprListener, commentAprListener, true, true)
672700
updateConfReplaceRegExp('server.xml', dummyComment, enableNIOSsl, true, true)

core/src/main/groovy/noe/tomcat/configure/SecureHttpConnectorTomcat.groovy

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package noe.tomcat.configure
22

3+
import noe.common.DefaultProperties
34
import noe.common.utils.Platform
45

56
/**
@@ -53,7 +54,7 @@ public class SecureHttpConnectorTomcat extends ConnectorTomcatAbstract<SecureHtt
5354
}
5455

5556
/**
56-
* Configure secure http connector to expect certificates in ${SYSTEM_TEMP}/ssl/self_signed directory
57+
* Configure secure http connector to expect certificates in ${SYSTEM_TEMP}/ssl/self_signed(_fips) directory
5758
* Expected names:
5859
* <ul>
5960
* <li>certificate = server.crt</li>
@@ -65,11 +66,14 @@ public class SecureHttpConnectorTomcat extends ConnectorTomcatAbstract<SecureHtt
6566
*/
6667
SecureHttpConnectorTomcat setDefaultCertificatesConfiguration() {
6768
String sslRoot = new File(new Platform().getTmpDir(), "ssl").getCanonicalPath()
68-
String sslStringDir = new File(sslRoot, "self_signed").getCanonicalPath()
69+
String sslStringDir = new File(sslRoot, DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE).getCanonicalPath()
6970
String sslCertificate = new File(sslStringDir, "server.crt").getCanonicalPath()
7071
String sslCertificateKey = new File(sslStringDir, "server.key").getCanonicalPath()
7172
String keystoreFilePath = new File(sslStringDir, "server.jks").getCanonicalPath()
7273
String password = "changeit"
74+
if(new Platform().isFips()) {
75+
keystoreFilePath = new File(sslStringDir, "nssdb").getCanonicalPath()
76+
}
7377

7478
setSslCertificateFile(sslCertificate)
7579
setSslCertificateKeyFile(sslCertificateKey)

core/src/main/groovy/noe/workspace/WorkspaceAbstract.groovy

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,28 @@ abstract class WorkspaceAbstract implements IWorkspace {
120120
}
121121

122122
/**
123-
* Copies self-signed, pre-generated certificates from noe core to ${tmpdir}/ssl/self_signed directory.
123+
* Copies self-signed, pre-generated certificates from noe core to ${tmpdir}/ssl/self_signed(_fips) directory.
124124
*
125125
*/
126126
void copyCertificates() {
127127
List<String> certificates = ["server.crt", "server.jks", "server.key", "server.p12"]
128-
String sslStringDir = PathHelper.join(platform.tmpDir, "ssl", "self_signed")
128+
def secFiles = [
129+
"java.security.ibm-java-1.8",
130+
"java.security.openjdk-1.8",
131+
"java.security.openjdk-11",
132+
"java.security.openjdk-17",
133+
"java.security.oracle-java-1.8",
134+
"java.security.oracle-java-11",
135+
"java.security.oracle-java-17",
136+
"nss.fips.cfg",
137+
"nss.oraclefips.cfg",
138+
"nss.cfg"]
139+
if (platform.isFips()) {
140+
certificates = certificates + secFiles
141+
}
142+
String sslStringDir = PathHelper.join(platform.tmpDir, "ssl", DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE)
129143
File sslDir = new File(sslStringDir)
130-
String resourcesPath = "ssl/self_signed/" //resources jar path is always separated by /
144+
String resourcesPath = "ssl/" + DefaultProperties.SELF_SIGNED_CERTIFICATE_RESOURCE + "/" //resources jar path is always separated by /
131145

132146
if (!sslDir.exists()) {
133147
JBFile.mkdir(sslDir)
@@ -139,6 +153,18 @@ abstract class WorkspaceAbstract implements IWorkspace {
139153
File certFile = Library.retrieveResourceAsFile("${resourcesPath}${certName}")
140154
JBFile.move(certFile, sslDir)
141155
}
156+
if (platform.isFips()) {
157+
for (String file: secFiles){
158+
JBFile.replace(new File(sslDir,file),"/tmp/ssl/self_signed_fips",sslStringDir)
159+
}
160+
List<String> keystore = [ "cert9.db", "key4.db", "pkcs11.txt", "secmod.db"]
161+
File keystoreDir = new File(PathHelper.join(sslStringDir, "nssdb"))
162+
resourcesPath = resourcesPath + "nssdb/"
163+
for (String db : keystore) {
164+
File dbFile = Library.retrieveResourceAsFile("${resourcesPath}${db}")
165+
JBFile.move(dbFile,keystoreDir)
166+
}
167+
}
142168
}
143169

144170
void downloadClusterBench() {

0 commit comments

Comments
 (0)