2024 年 Clojure 调查!分享您的看法。

欢迎!请查看关于页面了解有关此内容的一些信息。

+1投票
Clojure CLI

你好,

我在尝试使用在 MS-Windows 独立防火墙上运行 clojure 工具下载项目依赖项时遇到了 PKIX 证书异常。

无法将工件 ... 从/到中央转移
(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)。

但是,似乎更容易指导 Java 使用 java.next.ssl.* 属性来查看通用或替代密钥存储库(《关于 SSL 和证书的资料》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

以下是我能想到的几种利用java属性来解决这个通用问题的方法,从最具侵入性/特定到通用情况

  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。缺点是,在每次调用 clojure 时用户/工具必须始终传递此标志。
      • 如果在其他代码部分之前已经建立过任何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. 发明一个新依赖的 edn 键(例如 :clojure.tools.deps.alpha/ssl),其中键值对均为 java.net.ssl.* 属性,可以在用户配置的 deps.edn 中设置,并通过 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:我也尝试将属性设置在 MAVEN_OPTS 变量中,如 https://maven.apache.org/guides/mini/guide-repository-ssl.html 中所述,但没有任何效果。我怀疑这只有在直接调用 mvn 命令行工具时才会生效。

1 个回答

+1投票

我认为这是全部正确的分析,我们在一些情况下遇到了需要在生成类路径的 java 调用上设置 JVM 属性的问题,正如您所检测到的,这些参数目前无法设置。

我们已经在https://clojure.atlassian.net/browse/TDEPS-165创建了一个现有工单,但我还没有工作在前。我怀疑最终解决方案可能是通过一个新的 clj 选项或环境变量,允许将 JVM 属性传递给类路径构建的 java 调用。

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

还是有担忧将 JVM 选项传递给所有的 java 调用,而不仅仅是用于类路径构建/下载的那个?
我还没有足够思考以告诉您补丁应该做什么。我认为将这些参数传递给每个 java 调用没有意义(尤其是给用户的应用程序)。
您能否进一步说明为什么在脚本中将 JVM 选项传递给每个 java 调用没有意义?我想不出任何不利之处。谢谢。
我已经更新了工单的计划,并实际实现了带新 CLJ_JVM_OPTS 支持的预发布版本 (1.11.1.1161)。但它目前还不支持 Windows,非常希望收到对如何在 Powershell 版本中正确处理多个参数的建议 - 具体是关于 https://github.com/clojure/brew-install/commit/d97cf4145ccfee712ec99b906f7bf19b1406c131 的反馈 - 难度在于正确处理多个参数。
你好,

我对如何将额外的 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调用在这和CLJ_JVM_OPTS之间拆分?我期望将单个CLJ_JVM_OPTS环境变量传递给所有的Java调用。

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提示符中设置环境来在运行通过Clojure工具的项目时使用Windows证书存储

$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似乎没有检测到这一点。

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

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