2024 年 Clojure 工程师状态调查!中分享您的想法!

欢迎!请查阅关于页面获取更多关于此功能的信息。

+1
Clojure CLI

你好,

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

无法从中央仓库传输munment ...
(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 密钥存储库中(请参阅https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html中关于证书和 SSL 的操作)。

相反,似乎更容易使用 java.next.ssl.* 属性(请参阅https://stackoverflow.com/questions/5871279/ssl-and-cert-keystore)指示 java 查看 general 或 alternative keystores。

在Windows上,我假设大多数使用如此限制性防火墙的用户都是这样的人,这仅仅是一个简单的问题,只需设置一个java属性,让java使用通用的Windows密钥库。

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 fn所负责的依赖项javax.net.ssl.trustStoreType=Windows-ROOT
    • 例如:(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 fn解析,在运行时使用System/setProperty设置属性。
    • 例如,示例clj用户配置{:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
    • cons
      • 使用System/setProperty在运行时设置javax.net.ssl.*属性可能不会产生任何效果,如果之前代码的其他部分已经建立 SSL 连接。
  4. 更新脚本以在-P命令的java调用中传递-J jvm选项(我已经测试过它有效)。
    • 例如: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 调用没有意义吗?我想不到任何缺点。谢谢。
我已经将计划更新到工单中,并实际实现了预发布版(1.11.1.1161),支持新的CLJ_JVM_OPTS。不过,它目前还不支持Windows,非常希望能得到关于正确等效咒语的推荐,以便在PowerShell版本中处理https://github.com/clojure/brew-install/commit/d97cf4145ccfee712ec99b906f7bf19b1406c131 - 问题的难点在于正确处理多个参数。
你好,

我研究了如何在java调用中传递额外的jvm参数,唯一想出的而且在大多数(所有?)情况下都能优雅处理的方法是通过`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
by
看起来很好,但要注意,像之前评论中提到的,不寻常的值可能会对Java调用产生不利影响。它在我的测试案例中运行良好。

请问我可以请问第二个新的JAVA_OPTS环境变量的目的,为什么Java调用在这之间分裂,而不是像预期的所有Java调用都通过一个单一的环境变量CLJ_JVM_OPTS传递。

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

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

有两个属性,因为有两个上下文(可能有)不同的需求,有很多场合你可能想在第一个上有属性,而在第二个上没有。
by
谢谢!

这里是如何使用新环境变量解决这个用例的示例,以防其他人感兴趣。

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

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

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

$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依赖项时,我遇到了和原问题一样的错误(PKIX路径构建失败: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设置它们。
...