2024 年 Clojure 状态调查中分享你的想法!

欢迎!请参阅关于页面了解更多关于该工具的信息。

+1
Clojure CLI

你好,

我在使用 clojure 工具尝试在 MS-Windows 的严格要求防火墙后下载项目依赖关系时遇到了 PKIX 证书异常。

无法从 central 转移构件...
(https://repo1.maven.org/maven2/)
sun.security.validator.ValidatorException: PKIX 路径构建失败
sun.security.provider.certpath.SunCertPathBuilderException: unable
to find valid certification path to requested target

我认为这是由于防火墙使用的自签名根证书所导致的。

以下为分析,并对冗长的阅读表示歉意。


在大公司中,工作站通常位于一个防火墙后面,该防火墙控制并对来自和前往互联网的所有流量进行监控。公司通常还会创建自己的自签名根证书,并用它替换 https 通信中发现的证书,以便解密和分析数据流,从而有效地利用中间人攻击。

为了实现这一点,发起连接的用户工作站需要将其安装在受信任的证书存储中。在 Windows 中,这将是在工作站的通用密钥存储中。

Java 配备了自己的证书存储,即 java KeyStore,它与工作站上可用的通用密钥存储分开。因此,任何通过防火墙访问互联网的 Java https 连接很可能会因未知证书错误而失败,因为自签名证书尚未安装到 java 密钥存储中。

这会对 clojure/clj 命令行工具在下载库时产生不利影响,因为它无法在 java 密钥存储中找到自签名证书。例如,当尝试从此类计算机访问互联网时抛出异常

 > clojure -P
 
 Error building classpath ... 
 org.eclipse.aether.resolution.ArtifactDescriptorException:   Failed to
 read artifact descriptor for ....  ...  Caused by:
 org.eclipse.aether.resolution.ArtifactResolutionException:   Could not
 transfer artifact ... from/to central
 (https://repo1.maven.org/maven2/):   
 sun.security.validator.ValidatorException: PKIX path building failed:
      sun.security.provider.certpath.SunCertPathBuilderException:
        unable to find valid certification path to requested target  ...
  ...

我想到了至少两种解决此问题的方法
1. 在 java 密钥存储中安装缺失的证书,或者
2. 指示 java 实例查阅通用密钥存储。

似乎没有直接发现缺失证书的方法,可能需要用户使用 java keytool (请参阅https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html) 将它们安装到 java 密钥存储中的一些努力。

相反,似乎更简单的是通过使用 java.next.ssl.* 属性来指示 java 读取通用或备用的密钥存储(https://stackoverflow.com/questions/5871279/ssl-and-cert-keystore)。

在 Windows 上,我假设大多数使用这种限制性防火墙的用户都在此使用,只需简单地设置一个 java 属性来指示 java 使用通用的 Windows 密钥存储(https://stackoverflow.com/questions/41257366/import-windows-certificates-to-java)。

javax.net.ssl.trustStoreType=Windows-ROOT

既然如此,我本来期待以下代码能在 Windows 上工作

clojure -J-D'javax.net.ssl.trustStoreType=Windows-ROOT' -P

但是并没有。结果是 $JvmOpts 变量没有被该 powershell 脚本传递给 prep 命令,该脚本位于此处 https://github.com/clojure/brew-install/blob/b91fb78e321b5e39bedda594f5d578579d448d19/src/main/resources/clojure/install/ClojureTools.psm1#L388

& $JavaCmd -classpath $ToolsCp clojure.main -m clojure.tools.deps.alpha.script.make-classpath2 --config-user $ConfigUser --config-project $ConfigProject --basis-file $BasisFile --libs-file $LibsFile --cp-file $CpFile --jvm-file $JvmFile --main-file $MainFile --manifest-file $ManifestFile @ToolsArgs

以下是一些可行的选项,从最侵入性/具体到通用情况

  1. 如果运行在 Windows 上,负责下载依赖的 clojure.tools.deps.alpha.script.make-classpath2 函数设置 javax.net.ssl.trustStoreType=Windows-ROOT,在运行时设置此属性(
    • 例如 (System/setProperty "javax.net.ssl.trustStoreType" "Windows-ROOT")
    • cons
      • 此选项仅在 Windows 上有效。
      • 这强制使用通用密钥存储,这可能不是用户始终想要的。
        • 这可以通过添加新的脚本选项(例如 -W)来缓解,该选项作为选项传递给 clojure.tools.deps.alpha。缺点是用户/工具必须在每次调用时始终传递此标志。
      • 如果在代码的其他部分之前已经建立了任何 SSL 连接,则使用 System/setProperty 设置 javax.net.ssl.tustStoreType 属性可能没有效果。
  2. 与上一个选项相同,但是使用一个新的脚本选项(例如 -Sssl EDN),该选项的 EDN 是 javax.net.ssl.* 属性,它们将被作为此类传递给 clojure.tools.deps.alpha 以在运行时设置。
    • 例如 clojure -Sssl {:trustStoreType "Windows-ROOT"} -P
    • cons
      • 与第 1 个选项相同的缺点,而且对于用户来说,每次调用 clojure 时都不得不键入 edn 映射,这更加繁琐。
  3. 发明一个新的 dep.sedn 键(例如 :clojure.tools.deps.alpha/ssl),其值对应该是 java.net.ssl.* 属性,可以设置在用户配置 dep.sedn 中,并由 clojure.tools.deps.alphamake-classpath2 函数解析,使用 System/setProperty 在运行时设置属性。
    • 例如,用户配置 clj 的示例 {:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
    • cons
      • 如果在代码的其他部分之前已经建立了任何 SSL 连接,则使用 System/setProperty 在运行时设置 javax.net.ssl.* 属性可能没有效果。
  4. 更新脚本,以便将 -J JVM 选项传递给 -P 命令的 java 调用(我已经测试了它的工作情况)。
    • 例如 clojure -J-D'javax.net.ssl.trustStoreType=Windows-ROOT' -P
    • cons
      • 用户/工具仍然需要在每次 clojure 调用时提供 -J-Djavax.net.ssl.* 选项。
  5. 与前面的选项相同,但引入一个 CLJ_JVMOPTS 环境变量,其值将被插入到脚本的 $JvmOpts 变量中。
    • 例如 set CLJ_JVMOPTS=-D'javax.net.ssl.trustStoreType=Windows-ROOT',然后 clojure -P

我更倾向于 #5,因为它似乎没有缺点,可以设置一次并应用于所有 clojure 调用,从而为用户/工具提供更好的体验。

让我知道您的想法。我非常乐意处理脚本和/或 tools.deps.alpha 库中的更改。

谢谢

PS:我还试图像在https://maven.apache.org/guides/mini/guide-repository-ssl.html中描述的那样在 MAVEN_OPTS 变量中设置属性,但没有任何效果。我怀疑这只有在直接调用 mvn 命令行工具时才会生效。

1 个回答

+1

我认为这是一项正确的分析,我们遇到了一些情况,其中可能需要在生成类路径的Java调用中设置jvm属性,如你所发现的,目前无法设置。

我们确实为此创建了一个ticket,位于https://clojure.atlassian.net/browse/TDEPS-165,但我还没有着手处理它。我怀疑最终的解决方案将是打开一个新的clj选项或env var,以便将jvm属性传递给构建类路径的Java调用。

我是否应该继续创建一个引入新的CLJ_JVMOPTS环境变量的原型补丁,该变量将与标准-J选项合并,并传递给所有脚本中的Java调用?

或者,您担心将jvm选项传递给所有Java调用,而不仅仅是用于类路径构造/下载的那个吗?
我没有做过足够的思考,所以不能告诉您补丁应该做什么。我认为不应该将这些属性传递给每一个Java调用(尤其是用户的程序)。
请问您能否进一步说明一下为什么在脚本中为每一次 Java 调用传递 JVM 选项没有意义?我想不到任何缺点。谢谢
我已经在工单中更新了计划,并且实际上实现了支持新的 CLJ_JVM_OPTS 的预发布版本(1.11.1.1161)。目前还不支持 Windows,所以非常乐意得到关于在 PowerShell 版本中对应正确魔法的推荐 - 最棘手的事情是要正确处理多个参数。
你好,

我进行了一些研究以了解如何将额外的 JVM 参数传递给 Java 调用,而我想出的唯一一种在大多数(所有?)情况下都能优雅处理的点子就是通过 `JAVA_TOOL_OPTIONS` 环境变量传递它们。如果我们可以把额外的参数添加到这个环境变量的末尾,那么它们就会被 Java 工具捕获并按照预期的方式处理。

我创建了一个草案实现来演示:[链接](https://github.com/ikappaki/brew-install/commit/3584b02caced8e4a15c6993c73ceb4c202a6c623)。这种方法唯一的轻微问题是,我们必须在 Java 命令调用完成后恢复环境变量,以防止其更新的值泄露到环境中。

另一个我考虑的选项是使用 `iex` 解析环境变量,像 `$CljJvmOpts = iex "echo $env:CLJ_JVM_OPTS"`,但这会打乱标准的引号机制,并且很难使用引号和双引号传递。

将任何用户值传递到 $java 命令行的调用中存在一个问题,我们可能会遇到不希望的情况,比如命令行中出现分号或其他 PowerShell 操作符,这可能会破坏调用语法,更不用说它还可能成为安全风险(例如,将环境变量设置为 `;rm -fr ./;`。

您怎么看?谢谢
是的,我不太喜欢这个。我增加了一个更改,在Windows中支持在空格和划片分割环境变量,并且这将与我所尝试的每一项工作都相当好。这可以在版本1.11.1.1165中找到,如果您愿意尝试一下的话(安装程序在https://download.clojure.org/install/win-install-1.11.1.1165.ps1)。
它看起来不错,但有一个前提,即不寻常的值可能会像我早先评论中提到的那样对Java调用产生不利影响。它对我的测试案例工作得很好。

请问第二个新的JAVA_OPTS环境变量的目的是什么?Java调用为什么在这两个之间分割,而不是传递给所有的java调用中的单一的CLJ_JVM_OPTS环境变量?我原本期待有一个CLJ_JVM_OPTS环境变量传递给所有的Java调用。

https://github.com/clojure/brew-install/commit/7914954030ca21f7b928b6f064ace086efdc9057

谢谢
好的,这个功能现在已经发布为1.11.1.1165。

有这两个属性是因为有两个上下文(可能)有不同的需求和在很多情况下你可能想要一个属性在一个上下文中但是不想在另一个上下文中。
谢谢!

以下是这个用例的示例如何使用新的环境变量来解决,供有兴趣的人参考。

要使用Windows证书商店在PowerShell提示符中设置环境变量以下载项目依赖项

$env:CLJ_JVM_OPTS = "-Djavax.net.ssl.trustStoreType=Windows-ROOT"

要使用Windows证书商店在通过Clojure工具运行项目时设置环境变量,通过在PowerShell提示符中设置

$env:JAVA_OPTS = "-Djavax.net.ssl.trustStoreType=Windows-ROOT"
by
你好,

希望我可以加入这个线程。这里也有类似的问题。企业防火墙执行SSL检查并替换SSL证书(ZScaler)。我 easiest 的选择是将证书安装到CA存储中。

"%JAVA_HOME%\bin\keytool.exe" -import -trustcacerts -cacerts -storepass XXX -file somefile.cer

我可以验证它是否工作(使用 https://github.com/MichalHecko/SSLPoke
> java.exe SSLPoke download.clojure.org 443
成功连接

但在安装clojure依赖项时,我遇到了与OP相同错误(PKIX路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到请求目标的有效证书路径)。

我认为我不需要使用Windows-ROOT信任存储。我只需要默认的Java CA存储(在 $JAVA_HOME/lib/security/cacerts ),这似乎应该工作,但似乎Clojure没有检测到这一点。

有什么建议可以帮助我调试这个问题吗?

谢谢
by
我认为这个stackoverflow答案 https://stackoverflow.com/a/5871352/7671 对可能需要设置的属性有很好的概述,并且您可以通过CLJ_JVM_OPTS来设置它们。
...