@elinpf/dsh-ops-access-ssh 0.1.7 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +11 -8
- package/README.zh.md +10 -7
- package/lib/index.d.ts +35 -9
- package/lib/index.js +102 -17
- package/lib/types.d.ts +27 -2
- package/package.json +3 -3
package/README.i18n.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
title: "@elinpf/dsh-ops-access-ssh"
|
|
2
2
|
description:
|
|
3
|
-
zh: "ops access 接缝的 SSH
|
|
4
|
-
en: "SSH
|
|
3
|
+
zh: "ops access 接缝的 SSH provider — ssh 主机条目 + ssh-cred 可复用凭证(密钥/密码),一份秘密多机共享"
|
|
4
|
+
en: "SSH provider for the ops access seam — ssh host entries + ssh-cred reusable credentials (key/password), one secret shared by many hosts"
|
package/README.md
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# @elinpf/dsh-ops-access-ssh
|
|
2
2
|
|
|
3
|
-
SSH
|
|
3
|
+
SSH provider for the ops access seam — two credential kinds: `ssh` (a HOST entry) and `ssh-cred` (a reusable CREDENTIAL entry), so one key or password is registered once and shared by any number of hosts.
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
One of
|
|
7
|
+
One of the provider plugins behind `@elinpf/dsh-ops-access` (core). Core owns the YAML credential registry (`~/.dsh-ops/access.yaml`) and the `ctx.opsAccess` service; this package contributes two credential kinds via `registerAccessProvider`:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
9
|
+
- **`ssh` — the host**: `host`, optional `user` / `key` / `password` / `port`, and optional `cred` — a reference to an `ssh-cred` entry. The intended shape is host + `cred`; inline `key`/`password` still work (legacy / one-off). At resolve time core merges the referenced credential's fields UNDER the host entry's — a `user` set on the host overrides the credential's default.
|
|
10
|
+
- **`ssh-cred` — the credential**: optional `user` (default login user), `key`, `password` — at least one of key/password required. Holds no host; it only resolves as the target of a `cred` reference. Register the secret once, point N hosts at it, rotate it in one place.
|
|
11
|
+
- **Field processing**: expands `~` in the key/password paths so `ssh -i` / `sshpass -f` see absolute paths.
|
|
12
|
+
- **Save-time validation**: key content gets a PEM armor check plus a REAL `ssh-keygen -y` parse (a structurally plausible but corrupt paste fails with a clear message instead of `error in libcrypto` mid-investigation; a passphrase-protected key gets its BatchMode explanation up front). Password content must be single-line — `sshpass -f` reads only the first line.
|
|
13
|
+
- **Pasted content**: when the admin UI receives key/password CONTENT (not a path), core writes it to a managed 0600 file under `~/.dsh-ops/credentials/`; both kinds opt into trailing-newline normalization (a key paste that lost the final newline of the END line failed in libcrypto at first use — 2026-08-27).
|
|
13
14
|
|
|
14
15
|
## Why this shape
|
|
15
16
|
|
|
16
17
|
The three-role rule (see the repo `AGENTS.md`): core owns the registry and the service, providers carry only a zod schema plus field processing, consumer tools resolve a profile and build the shell command. Keeping this package to schema + processing means no secret material ever passes through any service — profiles carry only paths and connection parameters.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
The host/credential split uses core's generic `references` mechanism (`references: { cred: 'ssh-cred' }`): one resolve consults the broker once on the HOST entry — the referenced credential is an implementation detail, not a separately gated profile. A dangling reference fails the referring resolve (and its `canResolve` precheck) with a pointer to both entries.
|
|
20
|
+
|
|
21
|
+
Registration defers through `ctx.inject(['opsAccess'], ...)` inside `registerAccessProvider` and is tied to the plugin's effect lifecycle, so fiber disposal / HMR unload removes the providers from the registry.
|
|
19
22
|
|
|
20
23
|
## Installation
|
|
21
24
|
|
|
@@ -37,4 +40,4 @@ npm run build
|
|
|
37
40
|
npx vitest run
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
The spec covers schema accept/reject, `~` expansion, registration/disposal through a mock `opsAccess` context (including HMR unload), and the validateContent armor gate + real `ssh-keygen` parse (it generates throwaway ed25519 keys in a tmp dir).
|
|
43
|
+
The spec covers schema accept/reject (both kinds), `~` expansion, the `cred → ssh-cred` reference declaration, post-merge user validation, registration/disposal through a mock `opsAccess` context (including HMR unload), and the validateContent armor gate + real `ssh-keygen` parse + single-line password rule (it generates throwaway ed25519 keys in a tmp dir).
|
package/README.zh.md
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
# @elinpf/dsh-ops-access-ssh
|
|
2
2
|
|
|
3
|
-
ops access 接缝的 SSH
|
|
3
|
+
ops access 接缝的 SSH provider — 两个凭证种类:`ssh`(主机条目)和 `ssh-cred`(可复用凭证条目),一把密钥或一个密码只登记一次,任意多台主机共享。
|
|
4
4
|
|
|
5
5
|
## 功能
|
|
6
6
|
|
|
7
|
-
`@elinpf/dsh-ops-access`(core)
|
|
7
|
+
`@elinpf/dsh-ops-access`(core) 下的 provider 插件之一。core 拥有 YAML 凭据 registry(`~/.dsh-ops/access.yaml`)和 `ctx.opsAccess` 服务;本包通过 `registerAccessProvider` 贡献两个凭证种类:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- **`ssh` — 主机**:`host`,可选 `user` / `key` / `password` / `port`,以及可选 `cred` — 指向一个 `ssh-cred` 条目的引用。推荐形态是主机 + `cred`;内联 `key`/`password` 继续可用(遗留/一次性场景)。resolve 时 core 把被引用凭证的字段合并到主机条目**之下** — 主机上设的 `user` 覆盖凭证的默认登录用户。
|
|
10
|
+
- **`ssh-cred` — 凭证**:可选 `user`(默认登录用户)、`key`、`password` — key/password 至少一个必填。不持有主机,只作为 `cred` 引用的目标被解析。秘密登记一次,N 台主机引用,轮换只改一处。
|
|
11
|
+
- **字段处理**:展开 key/password 路径中的 `~`,让 `ssh -i` / `sshpass -f` 拿到绝对路径。
|
|
12
|
+
- **保存时校验**:密钥内容先做廉价的 PEM armor 检查,再用 `ssh-keygen -y` 做真实解析(结构看似合法但内容损坏的粘贴会在此刻得到清晰报错,而不是在排查中途撞上 `error in libcrypto`;带口令的密钥也会提前得到 BatchMode 说明)。密码内容必须单行 — `sshpass -f` 只读文件第一行。
|
|
13
|
+
- **粘贴内容**:admin UI 收到的是密钥/密码内容(而非路径)时,core 会写入 `~/.dsh-ops/credentials/` 下的 0600 受管文件;两个种类都声明尾随换行规范化(密钥粘贴恰好丢了 END 行末尾换行时,首次使用会在 libcrypto 里失败 — 2026-08-27)。
|
|
13
14
|
|
|
14
15
|
## 为什么这样拆
|
|
15
16
|
|
|
16
17
|
三角色规则(见仓库 `AGENTS.md`):core 拥有 registry 和服务,provider 只带一个 zod schema 加字段处理,消费端工具解析 profile 并拼 shell 命令。本包保持 schema + 处理的最小形态,意味着任何服务都不经手秘密材料 — profile 里只有路径和连接参数。
|
|
17
18
|
|
|
19
|
+
主机/凭证分离用的是 core 的通用 `references` 机制(`references: { cred: 'ssh-cred' }`):一次 resolve 只对**主机条目**咨询一次 broker — 被引用的凭证是实现细节,不是单独门控的 profile。悬挂引用会让引用方的 resolve(及其 `canResolve` 预检)失败,并报出两端条目名。
|
|
20
|
+
|
|
18
21
|
注册在 `registerAccessProvider` 内部经 `ctx.inject(['opsAccess'], ...)` 延迟挂载,并绑定在插件的 effect 生命周期上,fiber 销毁 / HMR 卸载会把 provider 从 registry 移除。
|
|
19
22
|
|
|
20
23
|
## 安装
|
|
@@ -37,4 +40,4 @@ npm run build
|
|
|
37
40
|
npx vitest run
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
测试覆盖:两个种类的 schema 接受/拒绝、`~` 展开、`cred → ssh-cred` 引用声明、合并后用户校验、经 mock `opsAccess` 上下文的注册/销毁(含 HMR 卸载),以及 validateContent 的 armor 闸门 + 真实 `ssh-keygen` 解析 + 密码单行规则(在临时目录生成一次性 ed25519 密钥)。
|
package/lib/index.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ops access provider for SSH.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Two kinds live here:
|
|
5
|
+
*
|
|
6
|
+
* - `ssh` — a HOST entry: `{ host, user?, key?, password?, cred?, port? }`.
|
|
7
|
+
* The credential (key or password) may be carried inline, but the intended
|
|
8
|
+
* shape is a `cred` reference to a shared `ssh-cred` entry — register the
|
|
9
|
+
* secret once, point N hosts at it, rotate it in one place. Fields set on
|
|
10
|
+
* the host entry override the referenced credential's (core merges the
|
|
11
|
+
* reference UNDER the entry; see `references` in the core seam).
|
|
12
|
+
* - `ssh-cred` — a reusable CREDENTIAL entry: `{ user?, key?, password? }`
|
|
13
|
+
* (at least one of key/password). Holds no host; it only resolves as the
|
|
14
|
+
* target of an ssh entry's `cred` reference.
|
|
15
|
+
*
|
|
16
|
+
* When the admin UI receives private-key / password CONTENT (instead of a
|
|
17
|
+
* path), core writes it to a managed file under ~/.dsh-ops/credentials/ and
|
|
18
|
+
* stores the path in the registry. The provider expands ~ in the path for
|
|
19
|
+
* ssh -i / sshpass -f. Pasted key content is normalized to end with exactly
|
|
20
|
+
* one newline and deep-parsed at save time (ssh-keygen -y) — both classic
|
|
21
|
+
* paste losses (2026-08-27).
|
|
10
22
|
*
|
|
11
23
|
* @module @elinpf/dsh-ops-access-ssh
|
|
12
24
|
*/
|
|
@@ -15,7 +27,7 @@ import z from '@deepseek-ai/schemastery';
|
|
|
15
27
|
import { z as zod } from 'zod';
|
|
16
28
|
import type { AccessProvider } from '@elinpf/dsh-ops-access';
|
|
17
29
|
import type { SshProviderConfig } from './types.js';
|
|
18
|
-
export type { SshEntry, SshProviderConfig } from './types.js';
|
|
30
|
+
export type { SshCredEntry, SshEntry, SshProviderConfig } from './types.js';
|
|
19
31
|
export declare const name = "ops-access-ssh";
|
|
20
32
|
export declare const inject: string[];
|
|
21
33
|
export declare const Config: z<Schemastery.ObjectS<{
|
|
@@ -25,12 +37,26 @@ export declare const Config: z<Schemastery.ObjectS<{
|
|
|
25
37
|
/** Save-time validation: timeout for the `ssh-keygen -y` parse (ms). */
|
|
26
38
|
validateTimeoutMs: z<number, number>;
|
|
27
39
|
}>>;
|
|
28
|
-
/** Zod schema for one ssh registry entry (excluding name and envelope fields). */
|
|
40
|
+
/** Zod schema for one ssh HOST registry entry (excluding name and the envelope fields). */
|
|
29
41
|
export declare const entrySchema: zod.ZodObject<{
|
|
30
42
|
host: zod.ZodString;
|
|
31
|
-
user: zod.ZodString
|
|
43
|
+
user: zod.ZodOptional<zod.ZodString>;
|
|
32
44
|
key: zod.ZodOptional<zod.ZodString>;
|
|
45
|
+
password: zod.ZodOptional<zod.ZodString>;
|
|
46
|
+
cred: zod.ZodOptional<zod.ZodString>;
|
|
33
47
|
port: zod.ZodOptional<zod.ZodNumber>;
|
|
34
48
|
}, zod.core.$strip>;
|
|
49
|
+
/**
|
|
50
|
+
* Zod schema for one ssh-cred CREDENTIAL entry: the shared secret half of the
|
|
51
|
+
* host/credential split. At least one auth method (key or password) is
|
|
52
|
+
* required — an empty credential resolves for every host that references it
|
|
53
|
+
* and fails them all at connection time, so reject it at save time.
|
|
54
|
+
*/
|
|
55
|
+
export declare const credEntrySchema: zod.ZodObject<{
|
|
56
|
+
user: zod.ZodOptional<zod.ZodString>;
|
|
57
|
+
key: zod.ZodOptional<zod.ZodString>;
|
|
58
|
+
password: zod.ZodOptional<zod.ZodString>;
|
|
59
|
+
}, zod.core.$strip>;
|
|
35
60
|
export declare const provider: AccessProvider;
|
|
61
|
+
export declare const credProvider: AccessProvider;
|
|
36
62
|
export declare function apply(ctx: Context, config: SshProviderConfig): void;
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ops access provider for SSH.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Two kinds live here:
|
|
5
|
+
*
|
|
6
|
+
* - `ssh` — a HOST entry: `{ host, user?, key?, password?, cred?, port? }`.
|
|
7
|
+
* The credential (key or password) may be carried inline, but the intended
|
|
8
|
+
* shape is a `cred` reference to a shared `ssh-cred` entry — register the
|
|
9
|
+
* secret once, point N hosts at it, rotate it in one place. Fields set on
|
|
10
|
+
* the host entry override the referenced credential's (core merges the
|
|
11
|
+
* reference UNDER the entry; see `references` in the core seam).
|
|
12
|
+
* - `ssh-cred` — a reusable CREDENTIAL entry: `{ user?, key?, password? }`
|
|
13
|
+
* (at least one of key/password). Holds no host; it only resolves as the
|
|
14
|
+
* target of an ssh entry's `cred` reference.
|
|
15
|
+
*
|
|
16
|
+
* When the admin UI receives private-key / password CONTENT (instead of a
|
|
17
|
+
* path), core writes it to a managed file under ~/.dsh-ops/credentials/ and
|
|
18
|
+
* stores the path in the registry. The provider expands ~ in the path for
|
|
19
|
+
* ssh -i / sshpass -f. Pasted key content is normalized to end with exactly
|
|
20
|
+
* one newline and deep-parsed at save time (ssh-keygen -y) — both classic
|
|
21
|
+
* paste losses (2026-08-27).
|
|
10
22
|
*
|
|
11
23
|
* @module @elinpf/dsh-ops-access-ssh
|
|
12
24
|
*/
|
|
@@ -27,21 +39,34 @@ export const Config = z.object({
|
|
|
27
39
|
validateTimeoutMs: z.number().default(5000),
|
|
28
40
|
});
|
|
29
41
|
// ── Provider ─────────────────────────────────────────────────────────────────
|
|
30
|
-
/** Zod schema for one ssh registry entry (excluding name and envelope fields). */
|
|
42
|
+
/** Zod schema for one ssh HOST registry entry (excluding name and the envelope fields). */
|
|
31
43
|
export const entrySchema = zod.object({
|
|
32
44
|
host: zod.string(),
|
|
33
|
-
user: zod.string(),
|
|
45
|
+
user: zod.string().optional(),
|
|
34
46
|
key: zod.string().optional(),
|
|
47
|
+
password: zod.string().optional(),
|
|
48
|
+
cred: zod.string().optional(),
|
|
35
49
|
port: zod.number().optional(),
|
|
36
50
|
});
|
|
51
|
+
/**
|
|
52
|
+
* Zod schema for one ssh-cred CREDENTIAL entry: the shared secret half of the
|
|
53
|
+
* host/credential split. At least one auth method (key or password) is
|
|
54
|
+
* required — an empty credential resolves for every host that references it
|
|
55
|
+
* and fails them all at connection time, so reject it at save time.
|
|
56
|
+
*/
|
|
57
|
+
export const credEntrySchema = zod.object({
|
|
58
|
+
user: zod.string().optional(),
|
|
59
|
+
key: zod.string().optional(),
|
|
60
|
+
password: zod.string().optional(),
|
|
61
|
+
}).refine((e) => e.key !== undefined || e.password !== undefined, {
|
|
62
|
+
message: 'an ssh-cred entry needs at least one auth method: key or password',
|
|
63
|
+
});
|
|
37
64
|
// Save-time guard in two layers: a cheap armor check, then a REAL parse —
|
|
38
65
|
// ssh-keygen -y derives the public key with the same parser ssh runs at
|
|
39
66
|
// connection time. A structurally plausible but corrupt paste fails here
|
|
40
67
|
// with a clear message instead of 'error in libcrypto' mid-investigation,
|
|
41
68
|
// and a passphrase-protected key gets its BatchMode explanation up front.
|
|
42
|
-
async function validateKeyContent(
|
|
43
|
-
if (field !== 'key')
|
|
44
|
-
return null;
|
|
69
|
+
async function validateKeyContent(content, keygenTimeoutMs) {
|
|
45
70
|
if (!/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/.test(content) || !/-----END [A-Z0-9 ]*PRIVATE KEY-----/.test(content)) {
|
|
46
71
|
return 'not a private key — expected a -----BEGIN ... PRIVATE KEY----- block (paste the full key file)';
|
|
47
72
|
}
|
|
@@ -71,17 +96,54 @@ async function validateKeyContent(field, content, keygenTimeoutMs) {
|
|
|
71
96
|
await rm(dir, { recursive: true, force: true });
|
|
72
97
|
}
|
|
73
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Save-time validator for file-field content, shared by both kinds: `key`
|
|
101
|
+
* goes through the ssh-keygen deep parse; `password` must be single-line —
|
|
102
|
+
* sshpass -f reads only the FIRST line, so an interior newline would
|
|
103
|
+
* silently truncate the password at connection time. (Core's trailing-newline
|
|
104
|
+
* normalization has already run, so any newline before the final one is
|
|
105
|
+
* interior.)
|
|
106
|
+
*/
|
|
107
|
+
function validateContent(keygenTimeoutMs) {
|
|
108
|
+
return (field, content) => {
|
|
109
|
+
if (field === 'key')
|
|
110
|
+
return validateKeyContent(content, keygenTimeoutMs);
|
|
111
|
+
if (field === 'password') {
|
|
112
|
+
const body = content.endsWith('\n') ? content.slice(0, -1) : content;
|
|
113
|
+
if (body.includes('\n') || body.includes('\r')) {
|
|
114
|
+
return 'a password must be a single line — sshpass -f reads only the first line of the file';
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** ssh needs a login user; with the host/credential split it may come from either side of the merge. */
|
|
122
|
+
function validateResolvedUser(fields) {
|
|
123
|
+
if (typeof fields.user !== 'string' || fields.user.length === 0) {
|
|
124
|
+
return 'no login user — set user on the host entry, or on the ssh-cred entry its cred field references';
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
74
128
|
export const provider = {
|
|
75
129
|
kind: 'ssh',
|
|
76
130
|
schema: entrySchema,
|
|
77
|
-
fieldsDoc: 'host: hostname or IP; user: login user; key: optional private-key content; port: optional, default 22',
|
|
78
|
-
fileFields: ['key'],
|
|
131
|
+
fieldsDoc: 'host: hostname or IP; user: optional login user (overrides the credential\'s); key: optional private-key content; password: optional password content (sshpass must be installed on the dsh host); cred: optional ssh-cred profile name — the shared credential, fields here override it; port: optional, default 22',
|
|
132
|
+
fileFields: ['key', 'password'],
|
|
133
|
+
references: { cred: 'ssh-cred' },
|
|
134
|
+
validateResolved: validateResolvedUser,
|
|
79
135
|
derivationDoc: 'ssh has no read-only shell — the credential lives in the ro tier and every use is grant-gated; to provision a dedicated key during an approved session: generate a fresh keypair (ssh-keygen -t ed25519), append the public key to the target user\'s authorized_keys, then register the private key via register_access',
|
|
80
136
|
process(entry) {
|
|
81
|
-
const { host, user, key, port } = entry;
|
|
82
|
-
const fields = { host
|
|
137
|
+
const { host, user, key, password, cred, port } = entry;
|
|
138
|
+
const fields = { host };
|
|
139
|
+
if (user !== undefined)
|
|
140
|
+
fields.user = user;
|
|
83
141
|
if (key !== undefined)
|
|
84
142
|
fields.key = expandHome(key);
|
|
143
|
+
if (password !== undefined)
|
|
144
|
+
fields.password = expandHome(password);
|
|
145
|
+
if (cred !== undefined)
|
|
146
|
+
fields.cred = cred;
|
|
85
147
|
if (port !== undefined)
|
|
86
148
|
fields.port = port;
|
|
87
149
|
return fields;
|
|
@@ -90,12 +152,35 @@ export const provider = {
|
|
|
90
152
|
// the END line newline-terminated; a paste that lost exactly that byte
|
|
91
153
|
// failed in libcrypto at first use — 2026-08-27).
|
|
92
154
|
normalizeTrailingNewline: true,
|
|
93
|
-
validateContent: (
|
|
155
|
+
validateContent: validateContent(5000),
|
|
156
|
+
};
|
|
157
|
+
export const credProvider = {
|
|
158
|
+
kind: 'ssh-cred',
|
|
159
|
+
schema: credEntrySchema,
|
|
160
|
+
fieldsDoc: 'user: optional default login user (hosts may override); key: optional private-key content; password: optional password content (sshpass must be installed on the dsh host). At least one of key/password is required. Referenced by ssh entries via their cred field — register the secret once, point many hosts at it',
|
|
161
|
+
fileFields: ['key', 'password'],
|
|
162
|
+
process(entry) {
|
|
163
|
+
const { user, key, password } = entry;
|
|
164
|
+
const fields = {};
|
|
165
|
+
if (user !== undefined)
|
|
166
|
+
fields.user = user;
|
|
167
|
+
if (key !== undefined)
|
|
168
|
+
fields.key = expandHome(key);
|
|
169
|
+
if (password !== undefined)
|
|
170
|
+
fields.password = expandHome(password);
|
|
171
|
+
return fields;
|
|
172
|
+
},
|
|
173
|
+
normalizeTrailingNewline: true,
|
|
174
|
+
validateContent: validateContent(5000),
|
|
94
175
|
};
|
|
95
176
|
// ── Plugin apply ─────────────────────────────────────────────────────────────
|
|
96
177
|
export function apply(ctx, config) {
|
|
97
178
|
registerAccessProvider(ctx, {
|
|
98
179
|
...provider,
|
|
99
|
-
validateContent: (
|
|
180
|
+
validateContent: validateContent(config.validateTimeoutMs),
|
|
181
|
+
});
|
|
182
|
+
registerAccessProvider(ctx, {
|
|
183
|
+
...credProvider,
|
|
184
|
+
validateContent: validateContent(config.validateTimeoutMs),
|
|
100
185
|
});
|
|
101
186
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -12,13 +12,38 @@
|
|
|
12
12
|
export interface SshEntry {
|
|
13
13
|
/** Hostname or IP of the target. */
|
|
14
14
|
host: string;
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Login user. Optional on the entry when `cred` is set — the referenced
|
|
17
|
+
* credential's user applies; a user set HERE overrides it for this host.
|
|
18
|
+
* The resolved profile must end up with a user from one of the two.
|
|
19
|
+
*/
|
|
20
|
+
user?: string;
|
|
17
21
|
/** Path to the private key file (`~` is expanded by the provider). */
|
|
18
22
|
key?: string;
|
|
23
|
+
/** Path to a password file (0600, first line is the password) for sshpass. */
|
|
24
|
+
password?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Reference to an `ssh-cred` entry holding the shared credential (key or
|
|
27
|
+
* password, and optionally the default user). Fields set on the host entry
|
|
28
|
+
* itself override the referenced credential's.
|
|
29
|
+
*/
|
|
30
|
+
cred?: string;
|
|
19
31
|
/** SSH port (default 22). */
|
|
20
32
|
port?: number;
|
|
21
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* One ssh-cred registry entry: a reusable SSH credential, registered once and
|
|
36
|
+
* referenced by any number of ssh host entries via their `cred` field.
|
|
37
|
+
* Mirror of `credEntrySchema` in index.ts — keep the two in sync.
|
|
38
|
+
*/
|
|
39
|
+
export interface SshCredEntry {
|
|
40
|
+
/** Default login user for hosts referencing this credential. */
|
|
41
|
+
user?: string;
|
|
42
|
+
/** Path to the private key file (`~` is expanded by the provider). */
|
|
43
|
+
key?: string;
|
|
44
|
+
/** Path to a password file (0600, first line is the password) for sshpass. */
|
|
45
|
+
password?: string;
|
|
46
|
+
}
|
|
22
47
|
/** Plugin configuration (see `Config` in index.ts). */
|
|
23
48
|
export interface SshProviderConfig {
|
|
24
49
|
/** Save-time validation: timeout for the `ssh-keygen -y` parse (ms). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elinpf/dsh-ops-access-ssh",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Ops access provider for SSH — validates ssh registry entries and expands key paths.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
41
|
-
"@elinpf/dsh-ops-access": "^0.1
|
|
41
|
+
"@elinpf/dsh-ops-access": "^0.2.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@deepseek-ai/cordis": "4.0.1",
|
|
45
45
|
"@types/node": "^22.0.0",
|
|
46
46
|
"typescript": "^5.4.0",
|
|
47
47
|
"vitest": "^4.1.11",
|
|
48
|
-
"@elinpf/dsh-ops-access": "0.1
|
|
48
|
+
"@elinpf/dsh-ops-access": "0.2.1"
|
|
49
49
|
},
|
|
50
50
|
"license": "MIT",
|
|
51
51
|
"publishConfig": {
|