请在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密钥库,这与工作站上通用的密钥库是分开的。因此,通过防火墙到互联网的任何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变量并未传递给预备命令(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来设置运行时属性
    • .e.g. (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以在运行时设置。
    • e.g. 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设置属性。
    • e.g. 示例clj用户配置{:clojure.tools.deps.alpha/ssl {:trustStore "Windows-ROOT"}}
    • cons
      • 使用System/setProperty在运行时设置javax.net.ssl.*属性可能对之前从代码的其他部分建立的任何SSL连接没有任何影响。
  4. 更新脚本,以便将-J JVM选项传递到-P命令的Java调用中(我已测试过此方法有效)。
    • e.g. clojure -J-D'javax.net.ssl.trustStoreType=Windows-ROOT' -P
    • cons
      • 用户/工具仍然必须在每个clojure调用中提供-J-Djavax.net.ssl.*选项。
  5. 与上一个类似,但引入一个CLJ_JVMOPTS环境变量,其值插入到脚本的$JvmOpts变量中。
    • e.g. 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
by

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

我们已在此问题[https://clojure.atlassian.net/browse/TDEPS-165]上创建了一个工单,但我还没有开始处理它。我怀疑最终的解决方案可能是通过打开一个新的clj选项或环境变量来允许将jvm属性传递给类路径构建Java调用。

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

还是有担忧,将jvm选项传递给所有的Java调用,而不仅仅是用于类路径构造/下载的调用?
by
我还没有想太多这个问题,所以无法告诉你补丁应该做什么。我认为将这些传递给每一个Java调用(尤其是用户的程序)是没有意义的。
by
你能解释一下为什么在脚本中将jvm选项传递给每一个Java调用不合适吗?我想不到任何缺点。谢谢。
by
我已经更新了工单计划,并且实施了预发布版(1.11.1.1161),支持新的CLJ_JVM_OPTS。不过它目前还不支持Windows,希望能得到关于在Powershell版本中相应等效咒语的推荐 - 现在的困难之处是要正确处理多个参数。
by
大家好,

我对如何将额外的JVM参数传递给Java调用进行了一些研究,我发现唯一能优雅处理大多数(所有?)情况的良方是将它们通过`JAVA_TOOL_OPTIONS`环境变量传递。如果我们能将额外的参数添加到这个环境变量的末尾,那么它们就会被Java工具捕捉到并按预期处理。

我创建了一个草稿实现来展示:https://github.com/ikappaki/brew-install/commit/3584b02caced8e4a15c6993c73ceb4c202a6c623。这种方法的一个小问题是,在Java命令调用完成后,我们必须恢复环境变量,以免其更新值泄露到环境中。

我考虑的另一种方法是像`$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)。
by
它看起来不错,但要注意,如我之前评论中提到的,非常规值可能会对Java调用产生不利影响。在我的测试用例中它运行良好。

请问第二个新的JAVA_OPTS环境变量的目的是什么?为什么Java调用在这两个变量之间分割?我原本 Expect 一个CLJ_JVM_OPTS环境变量被传递给所有的Java调用。

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依赖项时,我遇到了与OP相同的错误(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来设置它们。
...