2024 Clojure 状态调查!中分享您的想法。

欢迎!请访问关于页面获取更多关于如何使用本站的信息。

+1
Clojure CLI

你好,

我在尝试使用 Clojure 工具在 MS-Windows 上通过严格的防火墙下载项目依赖时遇到了 PKIX 证书异常。

无法从/to central 转移工件...
(https://repo1.maven.org/maven2/)
sun.security.validator.ValidatorException: PKIX 路径构造失败
sun.security.provider.certpath.SunCertPathBuilderException: 无法
找到到达指定目标的有效证书路径

我认为这是由于防火墙使用自签名根证书来控制与监视流量所导致的。

以下是我的分析,很抱歉篇幅较长。


在大公司中,工作站通常位于防火墙后面,该防火墙控制和监视所有进出互联网的流量。公司也普遍创建自己的自签名根证书,并将其替换为 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 虚拟机中(请参阅https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html中的“处理证书和 SSL”)

相反,使用 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

但不起作用。结果是,在 PowerShell 脚本中,$JvmOpts 变量未传递给 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

以下是一些可以用来利用 Java 属性解决这个问题的方法,从最侵入性、最具体到一般情况

  1. 如果运行在 Windows 上,可以将负责下载依赖的 clojure.tools.deps.alpha.script.make-classpath2 函数设置为运行时设置 javax.net.ssl.trustStoreType=Windows-ROOT,如果运行在 Windows 上。
    • 例如:(System/setProperty "javax.net.ssl.trustStoreType" "Windows-ROOT")
    • cons
      • 这仅在 Windows 上有效。
      • 这会强制使用通用密钥库,而这可能不是用户始终想要的。
        • 可以通过添加一个新脚本选项(例如 -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 函数解析,在运行时使用 System/setProperty 设置属性。
    • 例如:{:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
    • cons
      • 使用 System/setProperty 在运行时设置 javax.net.ssl.* 属性可能不会有任何效果,如果其他代码部分的任何 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:我也尝试将属性设置在 MAVEN_OPTS 变量中,就像 https://maven.apache.org/guides/mini/guide-repository-ssl.html 所示,但这没有效果。我怀疑这只在直接调用 mvn 命令行工具时才有效。

1 个回答

+1

我认为这是一次完全正确的分析,我们已经遇到了少数需要设置jvm属性的情况,这些属性是在生成类路径的Java调用中设置的,正如您所发现的,目前无法设置这些属性。

我们在 https://clojure.atlassian.net/browse/TDEPS-165 处有一张现有的工单,但我还没有开始工作。我怀疑最终的解决方案可能是通过一个新的clj选项或环境变量打开,将jvm属性传递给类路径构造的Java调用。

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

或者,我们对将jvm选项传递给所有java调用有顾虑,而不仅仅是那些用于类路径构造/下载的调用?
我还没有足够思考便告诉您补丁应当做什么。我认为将这些属性传递给每个java调用(尤其是在用户的应用程序中)并不合理。
能否请您详细解释一下,为什么在脚本中将 JVM 选项传递给每个 Java 调用没有意义?我想不出任何缺点。谢谢
by
我已经在工单中更新了计划,并且实际实现了对新的 CLJ_JVM_OPTS 的支持。然而,目前还不支持 Windows,非常希望得到关于在 PowerShell 版本中正确等效祈祷文的建议 - 难题在于正确处理多个参数。
by
你好,

我对如何将额外的 JVM 参数传递给 Java 调用进行了一些研究,我找到的唯一一种在大多数(所有?)情况下优雅处理参数的好方法是通 过 `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添加了对在空白处拆分env变量并将它们分配到命令行的支持,这在我尝试的每件事上都似乎工作得相当好。这在新版本1.11.1.1165中,如果您想尝试的话(安装文件在https://download.clojure.org/install/win-install-1.11.1.1165.ps1)。
它在警告中看起来很好,即一个非常规的值可能会对如我早期评论中所述的Java调用产生不利影响。它对我的测试案例工作得很好。

请问我可以询问第二组新的JAVA_OPTS环境变量的目的,以及为何要将Java调用在这之间和CLJ_JVM_OPTS之间拆分?我一直期望有一个单一的CLJ_JVM_OPTS环境变量传递给所有的Java调用。

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

谢谢
好的,现在这已经被发布为1.11.1.1165了。

有两组属性,因为有两个上下文,它们可能有不同的需求,很多情况下你可能想在其中一组_encoder_而不是另一组中用于属性。
谢谢!

以下是如何使用新环境变量来解决这个问题,以供其他感兴趣的人参考。

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

$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依赖项时,我遇到了与原始帖子相同的问题(证书路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到目标的有效证书路径)。

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

关于如何调试这个问题的任何建议?

谢谢
我认为这个Stackoverflow回答https://stackoverflow.com/a/5871352/7671概述了可能需要设置的属性,您可以通过CLJ_JVM_OPTS进行设置。
...