sshconfig-resolve

OpenSSH client configuration parser, evaluator, and explainable resolver for MoonBit.

ssh
openssh
config
parser
resolver
moon add jingmo653/sshconfig-resolve@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
7 hours ago
Downloads
2

Dependencies

README

#sshconfig-resolve

sshconfig-resolve 是 OpenSSH client configuration 的 MoonBit 解析器和可解释 求值器。它读取主配置及 Include 文件,应用 Host/受支持的 Match 条件、 首值/累积语义和 % token 展开,输出目标 host 的最终配置及每个值的来源。

带 span/恢复诊断的 tokenizer,受限 Match 确定性 Include 展开(MemoryFS 与 native adapter)、常用 scalar/list/SetEnv directive、%h/%n/%p/%r/%u/%d/%% 展开,以及同一次求值生成的 explain trace。 sshcfg 是真实文件 CLI;测试使用隔离 fixture 与 ssh -G -F 比较声明的 P0 标量字段。它不是 SSH 客户端,也不应被当作所有 OpenSSH 版本的完全替代。

#环境

  • MoonBit 0.10.3 或更高版本;当前复核使用 moon 0.1.20260713
  • 运行库测试和 CLI 只需要 MoonBit;ssh -G 互操作套件额外需要 Linux、OpenSSH client 和 timeout

#安装

该库已发布到 mooncakes.io。在使用方项目根目录添加 v0.1.0

moon add jingmo653/sshconfig-resolve@0.1.0

使用当前源码时,请在仓库根目录检查并运行:

moon check --target all --warn-list +73 --deny-warn moon run examples/basic moon run --target native cmd/sshcfg -- version

#快速运行

moon check --target all --warn-list +73 --deny-warn moon build --target all --warn-list +73 --deny-warn moon test --target all --warn-list +73 --deny-warn moon run examples/basic moon run --target native cmd/sshcfg -- version moon run --target native cmd/sshcfg -- resolve \ --config test/interop/fixtures/p0-basic.conf api.example.test

库 API 的最小用法与 examples/basic 保持一致。使用者在自己的 moon.pkg 中导入:

import {
"jingmo653/sshconfig-resolve" @sshconfig,
}

然后解析并求值一段配置:

fn main raise {
let config = @sshconfig.parse("Host github.com\n User git\n")
let result = @sshconfig.resolve(
config,
@sshconfig.ResolveContext::new("github.com"),
)
guard result.get("user") is Some(user) else { fail("user should resolve") }
println(user.value)
}

CLI:

sshcfg resolve --config ./ssh_config github.com sshcfg explain --config ./ssh_config --format json github.com sshcfg lint --config ./ssh_config --deny-warnings sshcfg version

#交付范围

  • OpenSSH 风格 tokenizer、global/Host/受支持 Match block 和常用 directive;
  • case-insensitive glob pattern-list、否定优先和有界 Match 条件;
  • Include glob/递归/环检测、原位展开和完整来源链;
  • directive-specific 求值:scalar 首值、Identity/Certificate 累积、 UserKnownHostsFile 单次多路径、SendEnv 负 pattern、SetEnv、duration/forward 校验;
  • %h/%n/%p/%r/%u/%d/%% 等按 directive allowlist 展开;
  • explain/JSON/lint CLI、conformance fixture 与隔离 ssh -G 差分。

不建立 SSH 连接,不执行 Match exec,不实现 hostname canonicalization/DNS 流程,也不读取或展示私钥内容。

#支持边界

syntaxpatternloaderresolve 支持 wasm/native/JS 编译;真实 文件读取与 CLI 是 native-only。native adapter 目前做安全的 ./.. identity 归一化;由于当前依赖的 filesystem API 未提供 symlink-aware realpath,符号链接 环不在本版本的 native adapter 兼容承诺中。Match execcanonicalfinal 显式拒绝,绝不会执行外部命令。通用 loader 默认以包含文件目录解析相对 Include; CLI 按 OpenSSH 用户配置规则固定到显式/环境 home 的 ~/.ssh,包括 -F 指定文件。 这里的 -F 指 OpenSSH oracle;本 CLI 使用等价的 --config 入口。

提交前可运行:

bash scripts/run_conformance_smoke.sh SSHCFG_INTEROP_REQUIRE_PROJECT=1 bash scripts/run_interop_suite.sh

#项目文档

#来源与许可证

本项目是原创 MoonBit 实现,行为参考 OpenSSH ssh_config manual OpenSSH portable。OpenSSH 使用 BSD/ISC 风格许可证集合;本项目不复制其实现代码,采用 Apache-2.0。互操作 fixtures 必须为本项目原创配置,或记录可再分发 来源和许可证。

#
Argument

A decoded directive argument together with its source range.

Directive.arguments remains available for callers that only need values; use argument_infos when diagnostics must point at an individual argument. This syntax model is not lossless: blank lines, standalone comments, and section-header trivia are intentionally not retained.

#
Config

Parsed OpenSSH client configuration.

items is the authoritative, ordered syntax model. blocks is a backwards-compatible projection containing a synthetic Host * block for global directives followed by each Host block; it intentionally excludes Match blocks.

#
ConfigItem

An input-order configuration item.

Global is a normalized leading item. Include remains an ordinary directive in the item where it was written; the loader expands it in place.

#
Diagnostic

A parser diagnostic returned by parse_recovering.

#
Directive

One SSH configuration directive. Keywords are canonical ASCII lowercase.

#
FileSystem

The backend boundary used by the portable configuration loader.

Implementations provide text, an identity used exclusively for cycle detection, deterministic-enough glob candidates, and an explicitly injected home directory. The loader sorts glob output itself and never consults process state.

Implementations must honor supplied limits before returning an oversized value. Adapters with streaming or metadata support should stop I/O before allocation; adapters without it must reject before transferring the value to the loader and document the residual backend allocation boundary.

#
FileSystemError

Errors produced by a filesystem adapter.

The loader translates these values into LoadError, so adapters never leak platform-specific error types through the public loading API.

#
HostBlock

A Host section. patterns and directives have already been decoded, but their meaning is deliberately left to the pattern and resolver packages.

#
LoadDiagnostic

A non-fatal event retained for callers that need diagnostics without changing the configured missing-Include policy.

#
LoadError

Loading failures, including resource-limit failures which are never converted into non-fatal missing-Include diagnostics.

The original constructors are retained for compatibility with callers that construct or classify legacy values. load itself raises Detailed.

#
LoadFailure

Context retained for every failure raised by load.

chain uses display paths, begins at the root, and includes attempted_path for file-loading failures. For a glob or malformed Include argument, the attempted value is kept separately while chain identifies the active file stack.

#
LoadFailureKind

Stable categories for a context-rich loading failure.

The legacy LoadError constructors remain available for source compatibility. New loader code raises LoadError::Detailed so every operational failure can retain the attempted path, Include location, and complete display-path chain.

#
LoadOptions

Resource and compatibility limits for a single load operation.

Every limit is checked before the corresponding counter is incremented.

#
LoadedConfig

A parsed configuration together with all source provenance and diagnostics.

config is parsed with the supplied display path and has every Include recursively expanded in source order.

#
MatchBlock

A Match section. Conditions are retained verbatim (after lexical decoding) so a later evaluator can support new OpenSSH predicates without changing the parser.

#
MatchContext

The context required to evaluate OpenSSH Match conditions without a dependency on the resolver's own context type.

#
MatchPredicate

P0 Match predicates. Unsupported OpenSSH conditions are rejected by the parser instead of being represented here, so this enum is always safe to evaluate without shelling out or canonicalizing a hostname.

#
MemoryFileSystem

Deterministic, in-memory filesystem for tests, tools, and non-native hosts. All stored paths are normalized at construction time.

Fields stay private so callers cannot bypass path normalization or forge adapter counters. Mutation remains available through put.

#
MergeKind

The cardinality and merge rule used for a supported OpenSSH directive.

#
MissingIncludePolicy

Behaviour when an Include pattern has no matching files.

#
ParseError

A structured syntax error. Diagnostic messages intentionally describe only syntax and never include a directive's potentially sensitive value.

#
ParseResult

The result of parsing in recovery mode. Invalid lines are omitted from the syntax tree and represented by one diagnostic each.

#
PatternDecision

Explainable result of evaluating a pattern-list. Indices refer to the original input array, are zero-based, and remain populated even when a negative item excludes a matching positive item.

#
PatternError

Structured failures produced while compiling a bounded pattern-list.

#
PatternList

A checked OpenSSH pattern-list. Construct it with compile_pattern_list before using it in a resolver or Match condition.

#
SourceFile

Provenance for one physical configuration source read during loading.

#
SourceLocation

A logical source position. Lines and columns are one-based; columns count Unicode scalar values rather than UTF-16 code units.

#
SourceSpan

A half-open source range. end_ is the first position after the range.

#
TraceDecision

The result of applying one directive candidate. The trace is ordered by source order and is suitable for explain and lint without re-running the resolver.

#
ValueKind

The validation class for a directive value. The resolver deliberately keeps this small: values that it cannot validate are never executed.

#
compile_pattern_list

Compile a pattern-list using the P0 resource limits.

#
host_matches

fn host_matches(pattern : String, host : String) -> Bool

Match one case-insensitive OpenSSH host glob containing * and ?.

This P0 matcher folds ASCII only. Inputs exceeding default_pattern_length_limit (or the value limit) are rejected as a non-match so that the compatibility Bool API remains allocation-free and non-throwing.

#
load

Read, parse, and recursively expand an OpenSSH configuration root.

By default Include patterns are resolved relative to their including file. Set LoadOptions::relative_include_base for a configuration domain such as OpenSSH's user ~/.ssh base. ~/ is expanded only through FileSystem::home_dir, and every glob result is sorted before recursive loading. The same file may be included twice from separate branches; only the active recursion chain is considered a cycle.

#
parse

Parse OpenSSH client configuration syntax strictly.

The returned config preserves input-order items and source spans. Global directives are represented by the leading normalized ConfigItem::Global.

#
parse_match_predicate

Parse one normalized Match condition and its pattern-list arguments. exec, canonical, final, and unknown conditions deliberately produce UnsupportedMatchCondition: P0 never executes a command or guesses an OpenSSH canonicalization phase.

#
parse_match_predicates

Parse interleaved Match tokens such as ["host", "*.corp", "user", "deploy,admin"]. Every condition other than all consumes its following comma-separated pattern-list token.

#
parse_recovering

fn parse_recovering(source : String, path? : String) ->
ParseResult

Parse configuration while collecting per-line syntax diagnostics.

Recovery skips the offending line and continues with the following line, making this entry point suitable for linting and editor integrations.

#
patterns_match

fn patterns_match(patterns : Array[String], host : String) -> Bool

Match an OpenSSH pattern-list. A matching negated item always excludes the host; otherwise at least one positive item must match.

#
resolve

Resolve without retaining the explain trace. It shares exactly the same evaluator as resolve_explained, preventing drift between both APIs.

#
resolve_explained

Resolve an already parsed configuration. The pure resolver has no file or environment access; callers supply every context value explicitly.

#
resolve_strict

Resolve in validation-strict mode. It shares the same evaluator and trace as the lenient API, then promotes the first rejected supported directive to a checked error. Unsupported directives stay observable through lint and explain rather than changing the default compatibility policy.

Source Files