分享您的想法,请填写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 将其安装到 Java 密钥存储库中(有关证书和 SSL 的工作,请参阅https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html)。

相反,似乎更容易通过使用 java.next.ssl.* 属性来指示 Java 查看通用或备用密钥存储库(https://stackoverflow.com/questions/5871279/ssl-and-cert-keystore)。

在Windows上,我想大多数有如此限制性防火墙的用户都在使用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脚本传递到准备命令中 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

我想到了一些使用java属性来解决一般问题的选项,从最侵入性/最具体到通用情况

  1. 对于负责下载依赖的函数clojure.tools.deps.alpha.script.make-classpath2,如果运行在Windows上,则设置 javax.net.ssl.trustStoreType=Windows-ROOT 在运行时,
    • 例如:(System/setProperty "javax.net.ssl.trustStoreType" "Windows-ROOT")
    • cons
      • 这仅在Windows上有效。
      • 这强制要求使用通用keystore,但这可能不是用户总是想要的。
        • 可以通过增加一个新脚本的选项(例如 -W)来减轻此影响,该选项作为参数传递给 clojure.tools.deps.alpha。缺点是用户/工具必须始终在执行时设置此标志。
      • 使用 System/setProperty 在运行时设置 javax.net.ssl.tustStoreType 属性可能无效,如果代码的其他部分之前已建立了任何SSL连接。
  2. 与之前相同,但新增一个脚本选项(例如-Sssl EDN),其EDN是 javax.net.ssl.* 属性,并将作为此类传递给 clojure.tools.deps.alpha 以在运行时设置。
    • 例如:clojure -Sssl {:trustStoreType "Windows-ROOT"} -P
    • cons
      • 与#1相同的缺点,并且对于用户来说,在每次调用clojure时输入EDN映射会更加麻烦。
  3. 创建一个新的deps.edn键(例如 :clojure.tools.deps.alpha/ssl),其值将是 java.net.ssl.* 属性,可以在用户配置deps.edn中设置,并通过 clojure.tools.deps.alphamake-classpath2 fn在运行时设置属性使用 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

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

我们确实在这个问题上有一个现有的工单,在 https://clojure.atlassian.net/browse/TDEPS-165,但我还没有着手处理它。我猜最终解决方案将是创建一个新的 clj 选项或 env 变量,以便将 JVM 属性传递给构建类路径的 java 调用。

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

或者有担心将 JVM 选项传递给所有 java 调用,而不仅仅是用于类路径构造/下载的那个?
我还没有对这个想清楚,无法告诉你补丁应该做什么。我不认为应该将这些传递给每个 java 调用(尤其是用户的程序)。
请问能否详细说明一下,为什么在脚本中将 JVM 选项传递给每个 Java 调用没有意义?我想不出任何缺点。谢谢
by
我已经更新了工单并实施计划,并且实际上实现了新的 CLJ_JVM_OPTS 支持,并进行了预发布(1.11.1.1161)。但目前不支持 Windows,非常期待任何关于在 PowerShell 版本中正确等效咒语的推荐 - 找到的棘手之处在于正确处理多个参数。
by
嗨,

我已经研究过如何在 Java 调用中传递额外的 JVM 参数,唯一想出的一种在大多数(所有?)情况下都能优雅处理这些参数的方法是通过 `JAVA_TOOL_OPTIONS` 环境变量传递它们。如果我们能够将额外的参数添加到此环境变量的末尾,那么它们将被 Java 工具摘取并按预期处理。

我创建了一个草案实现以进行展示:[https://github.com/ikappaki/brew-install/commit/3584b02caced8e4a15c6993c73ceb4c202a6c623](https://github.com/ikappaki/brew-install/commit/3584b02caced8e4a15c6993c73ceb4c202a6c623)。这种方法唯一的轻微实现复杂之处在于,我们必须在 Java 命令调用结束后恢复环境变量,以确保其更新值不会泄漏到环境中。

我考虑的另一种选择是使用 `iex` 解析环境变量,例如 `$CljJvmOpts = iex "echo $env:CLJ_JVM_OPTS"`,但这会与标准引号机制冲突,而且很难在引号和双引号中传递。

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

你有什么看法?谢谢
by
是的,我不太喜欢这种。我已经为一个更改提交了代码,增加了对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环境变量。

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

谢谢
好的,现在这个版本已经发布为1.11.1.1165。

有两个属性,因为有两个上下文,(可能)有不同的需求,还有很多你可能在其中一个上想要属性但没有在另一个上的场景。
谢谢!

这里是如何使用新环境变量解决这个问题的一个示例用例,供其他人参考。

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

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

通过在PowerShell提示符中设置环境变量来使用Windows证书存储运行通过Clojure Tools的项目

$env:JAVA_OPTS = "-Djavax.net.ssl.trustStoreType=Windows-ROOT"
嗨,

希望我能加入这个讨论。这里也有类似的问题。公司防火墙对SSL进行检验并替换SSL证书(ZScaler)。我最简单的选择是将证书安装到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进行设置。
...