dsh-ssh-vault 0.0.0-stage → 2.0.0
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/CHANGELOG.md +404 -0
- package/CHANGELOG.zh-CN.md +352 -0
- package/CONTRIBUTING.md +179 -0
- package/CONTRIBUTING.zh-CN.md +167 -0
- package/LICENSE +21 -0
- package/README.md +452 -2
- package/README.zh-CN.md +450 -0
- package/SECURITY.md +42 -0
- package/SECURITY.zh-CN.md +38 -0
- package/assets/banner.png +0 -0
- package/assets/icon-180.png +0 -0
- package/assets/screenshot-1.png +0 -0
- package/client.js +1134 -0
- package/cordis.patch.yml +4 -0
- package/icon.svg +11 -0
- package/index.js +1180 -0
- package/lib/background.js +65 -0
- package/lib/i18n.js +589 -0
- package/lib/locales/ssh.js +130 -0
- package/lib/platform.js +68 -0
- package/lib/ssh.js +723 -0
- package/lib/vault-api.js +417 -0
- package/lib/vault.js +397 -0
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +75 -4
- package/scripts/dhcp-reserve-kali.ps1 +81 -0
- package/scripts/make-market-icon.py +63 -0
- package/scripts/set-secret.mjs +127 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lib/ssh.js` 诊断建议的双语文案。
|
|
3
|
+
*
|
|
4
|
+
* 这些句子是在 SSH/scp 失败后给用户和模型看的归因结论,键名统一为 `ssh.*`,
|
|
5
|
+
* 由 `lib/i18n.js` 合并进主字典。中文与历史输出逐字一致(测试断言依赖这些用词),
|
|
6
|
+
* 英文是按同样的判断顺序重写的技术说明。
|
|
7
|
+
*
|
|
8
|
+
* 约定:可选的补充成分(源地址、绑定地址)直接由调用方拼好再作为占位符传入,
|
|
9
|
+
* 避免为标点差异再造一条键。
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-ssh-vault/locales/ssh
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 诊断建议双语文案;`{name}` 为插值占位符。 */
|
|
15
|
+
export const SSH_MESSAGES = {
|
|
16
|
+
'ssh.keyPermissionsTooOpen': {
|
|
17
|
+
zh: '本地私钥文件权限过宽,ssh 客户端已拒绝加载它(即错误中的 UNPROTECTED PRIVATE KEY FILE),'
|
|
18
|
+
+ '这与服务器上的账号口令无关。Windows 下需把该文件 ACL 收紧到仅当前用户;'
|
|
19
|
+
+ '若所在环境不允许改 ACL,可把私钥复制到保险库目录(%DSH_HOME%\\ssh-vault)并用 ssh_hosts 的 update 指向新路径。',
|
|
20
|
+
en: 'The local private key file is too permissive, so the ssh client refused to load it (the UNPROTECTED PRIVATE KEY FILE error); '
|
|
21
|
+
+ 'this is unrelated to the account or password on the server. On Windows, tighten the file ACL to the current user only; '
|
|
22
|
+
+ 'if ACL changes are not possible, copy the key into the vault directory (%DSH_HOME%\\ssh-vault) and point ssh_hosts update at the new path.',
|
|
23
|
+
},
|
|
24
|
+
'ssh.publicKeyAsPrivate': {
|
|
25
|
+
zh: '该文件不是可用的私钥。`.pub` 是公钥,不能用于登录;'
|
|
26
|
+
+ '请提供不带 .pub 后缀的私钥文件(如 id_rsa)。PuTTY 的 .ppk 需先转换。',
|
|
27
|
+
en: 'That file is not a usable private key. `.pub` files are public keys and cannot log in; '
|
|
28
|
+
+ 'provide the private key without the .pub suffix (for example id_rsa). PuTTY .ppk keys must be converted first.',
|
|
29
|
+
},
|
|
30
|
+
'ssh.keyUnavailable': {
|
|
31
|
+
zh: '本地找不到可用的私钥或认证代理:请确认 privateKeyPath 指向的文件存在且可读。',
|
|
32
|
+
en: 'No usable local private key or agent was found: check that the file privateKeyPath points at exists and is readable.',
|
|
33
|
+
},
|
|
34
|
+
'ssh.sudoNoPassword': {
|
|
35
|
+
zh: 'sudo 没有取到口令,相关命令并未执行(错误里的 a terminal is required / 需要密码 即此)。'
|
|
36
|
+
+ '用 ssh_run 的 sudo: true 让插件从保险库取口令预置 sudo 缓存,不必手写 `echo <pw> | sudo -S`。'
|
|
37
|
+
+ '另注意管道陷阱:`sudo ... | grep ...` 的退出码取决于管道最后一段,sudo 失败时整条命令仍可能返回 0。',
|
|
38
|
+
en: 'sudo did not obtain a password, so the affected commands did not run (this is the "a terminal is required" / password error). '
|
|
39
|
+
+ 'Use ssh_run with sudo: true so the plugin primes the sudo cache from the vault instead of writing `echo <pw> | sudo -S`. '
|
|
40
|
+
+ 'Watch the pipeline trap too: with `sudo ... | grep ...` the exit code comes from the last pipeline segment, so the whole command can still return 0 when sudo failed.',
|
|
41
|
+
},
|
|
42
|
+
'ssh.publicKeyNotInstalled': {
|
|
43
|
+
zh: '服务器拒绝了公钥认证:公钥没有装到目标账户的 authorized_keys。'
|
|
44
|
+
+ '若目标是 Windows 且账号属于管理员组,公钥必须写入 %ProgramData%\\ssh\\administrators_authorized_keys,'
|
|
45
|
+
+ '而不是用户目录下的 .ssh\\authorized_keys;该文件还需仅保留 Administrators 与 SYSTEM 的权限。',
|
|
46
|
+
en: 'The server rejected public-key authentication: the key is not installed in the target account\'s authorized_keys. '
|
|
47
|
+
+ 'On Windows, for an account in the Administrators group the key must go into %ProgramData%\\ssh\\administrators_authorized_keys '
|
|
48
|
+
+ 'rather than .ssh\\authorized_keys in the user profile; that file must also be restricted to Administrators and SYSTEM.',
|
|
49
|
+
},
|
|
50
|
+
'ssh.tooManyAuthAttempts': {
|
|
51
|
+
zh: '认证尝试次数过多:客户端逐个试了太多密钥。改用明确的口令或 privateKeyPath 认证,'
|
|
52
|
+
+ '本插件对密钥认证已加 IdentitiesOnly=yes。',
|
|
53
|
+
en: 'Too many authentication attempts: the client tried too many keys in turn. Use explicit password or privateKeyPath authentication; '
|
|
54
|
+
+ 'this plugin already sets IdentitiesOnly=yes for key authentication.',
|
|
55
|
+
},
|
|
56
|
+
'ssh.algorithmMismatch': {
|
|
57
|
+
zh: '客户端与服务器没有共同的公钥签名算法(老服务器常见)。可改用口令认证。',
|
|
58
|
+
en: 'The client and server share no public-key signature algorithm (common on older servers). Switch to password authentication.',
|
|
59
|
+
},
|
|
60
|
+
'ssh.accountLocked': {
|
|
61
|
+
zh: '目标账户已被锁定。Windows 用 `net user <账号>` 查看 "Account active / Locked",'
|
|
62
|
+
+ '由管理员解锁或等待锁定周期结束;Linux 用 `faillock --user <账号>` 查看与重置。'
|
|
63
|
+
+ '锁定期间任何凭据(含密钥)都无法登录。',
|
|
64
|
+
en: 'The target account is locked. On Windows, check "Account active / Locked" with `net user <account>`, '
|
|
65
|
+
+ 'then have an administrator unlock it or wait for the lockout period to end; on Linux, inspect and reset it with `faillock --user <account>`. '
|
|
66
|
+
+ 'While locked, no credential (keys included) can log in.',
|
|
67
|
+
},
|
|
68
|
+
'ssh.bindUnavailable': {
|
|
69
|
+
zh: '指定的源地址在本机不可用(bind 失败){address}。请确认该地址仍在网卡上——DHCP 续约后手工 ip addr add 的地址会丢失。',
|
|
70
|
+
en: 'The requested source address is not available locally (bind failed){address}. '
|
|
71
|
+
+ 'Check that the address is still on a network interface — an address added manually with ip addr add is lost after a DHCP renewal.',
|
|
72
|
+
},
|
|
73
|
+
'ssh.bindAddressSuffix': {
|
|
74
|
+
zh: ':{address}',
|
|
75
|
+
en: ': {address}',
|
|
76
|
+
},
|
|
77
|
+
'ssh.boundSuffix': {
|
|
78
|
+
zh: '(当前绑定源地址 {address})',
|
|
79
|
+
en: ' (currently bound to source address {address})',
|
|
80
|
+
},
|
|
81
|
+
'ssh.portRefused': {
|
|
82
|
+
zh: '目标端口拒绝连接:sshd 可能未运行,或端口不是 {port}。',
|
|
83
|
+
en: 'The target port refused the connection: sshd may not be running, or the port is not {port}.',
|
|
84
|
+
},
|
|
85
|
+
'ssh.networkUnreachable': {
|
|
86
|
+
zh: '网络不可达或连接被拦截:确认目标 IP 与出口网卡在同一网段{bound},且中间设备与目标防火墙放行了该端口。',
|
|
87
|
+
en: 'The network is unreachable or the connection is blocked: check that the target IP and the outbound interface are on the same subnet{bound}, '
|
|
88
|
+
+ 'and that intermediate devices and the target firewall allow that port.',
|
|
89
|
+
},
|
|
90
|
+
'ssh.handshakeClosed': {
|
|
91
|
+
zh: '连接在握手阶段被对端关闭。除了防火墙或 sshd 限流,还有一种容易被漏掉的原因:'
|
|
92
|
+
+ '**目标账户已被锁定**——Windows OpenSSH 对锁定账号直接断开,不给认证阶段的错误。'
|
|
93
|
+
+ 'Windows 目标用 `net user <账号>` 看 "Account active / Locked";Linux 用 `faillock --user <账号>`。'
|
|
94
|
+
+ '若近期有批量并发连接或反复失败的登录,锁定的可能性更高。',
|
|
95
|
+
en: 'The peer closed the connection during the handshake. Besides a firewall or sshd rate limiting, one easily missed cause is an '
|
|
96
|
+
+ '**already locked target account** — Windows OpenSSH drops locked accounts without an authentication-stage error. '
|
|
97
|
+
+ 'On Windows, check "Account active / Locked" with `net user <account>`; on Linux use `faillock --user <account>`. '
|
|
98
|
+
+ 'Bulk concurrent connections or repeated failed logins make lockout more likely.',
|
|
99
|
+
},
|
|
100
|
+
'ssh.hostKeyChanged': {
|
|
101
|
+
zh: '主机密钥与保险库中记录的不一致:可能是目标被重建。确认无误后删除保险库目录下 known_hosts 中的对应条目再重试。',
|
|
102
|
+
en: 'The host key does not match the one recorded in the vault: the target may have been rebuilt. '
|
|
103
|
+
+ 'Once confirmed, delete the matching entry from known_hosts in the vault directory and retry.',
|
|
104
|
+
},
|
|
105
|
+
'ssh.hostKeyRejected': {
|
|
106
|
+
zh: '主机密钥校验失败,连接已被拒绝。',
|
|
107
|
+
en: 'Host key verification failed, so the connection was rejected.',
|
|
108
|
+
},
|
|
109
|
+
'ssh.shellMissing': {
|
|
110
|
+
zh: '远端缺少指定的 shell。Linux 目标可用 /bin/sh;Windows 目标请把主机记录的 shell 设为 cmd 或 powershell。',
|
|
111
|
+
en: 'The remote side lacks the configured shell. Linux targets can use /bin/sh; on Windows, set the host record\'s shell to cmd or powershell.',
|
|
112
|
+
},
|
|
113
|
+
'ssh.timeout': {
|
|
114
|
+
zh: '连接或命令执行超过 {ms}ms 仍未结束,已中断本地客户端。'
|
|
115
|
+
+ '远端命令可能仍在运行——脚本类操作请改用 run_in_background,再用 job_output 读取结果。',
|
|
116
|
+
en: 'The connection or command did not finish within {ms}ms, so the local client was interrupted. '
|
|
117
|
+
+ 'The remote command may still be running — for script-like work use run_in_background and read the result with job_output.',
|
|
118
|
+
},
|
|
119
|
+
'ssh.authFailed': {
|
|
120
|
+
zh: '认证失败:账号或口令不正确,或该账号不允许所用认证方式。'
|
|
121
|
+
+ '可用 ssh_hosts 的 update 动作更正凭据。',
|
|
122
|
+
en: 'Authentication failed: the account or password is wrong, or the account does not permit the authentication method used. '
|
|
123
|
+
+ 'Correct the credentials with ssh_hosts action "update".',
|
|
124
|
+
},
|
|
125
|
+
'ssh.transferPathMissing': {
|
|
126
|
+
zh: '本地或远端路径不存在,或远端目录没有写权限。上传时远端父目录必须已存在;下载时本地父目录会被自动创建。',
|
|
127
|
+
en: 'A local or remote path does not exist, or the remote directory is not writable. '
|
|
128
|
+
+ 'For uploads the remote parent directory must already exist; for downloads the local parent is created automatically.',
|
|
129
|
+
},
|
|
130
|
+
}
|
package/lib/platform.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台支持声明。
|
|
3
|
+
*
|
|
4
|
+
* 本插件**只在 Windows 上工作**,这不是保守,而是代码事实:
|
|
5
|
+
*
|
|
6
|
+
* - 数据密钥的保护依赖 Windows DPAPI(`lib/vault.js`)。非 Windows 上原先会退化成
|
|
7
|
+
* 明文 `key.json`——既不安全,也不该被一句「支持 Linux / macOS」掩盖。
|
|
8
|
+
* - ssh 的口令注入依赖 Windows 的 `SSH_ASKPASS` `.cmd` 包装器(`lib/ssh.js`)。
|
|
9
|
+
*
|
|
10
|
+
* 所以这里做两件事:给出唯一的支持平台,以及在不受支持的平台上**拒绝加载**,
|
|
11
|
+
* 而不是悄悄降级。`package.json` 的 `os: ["win32"]` 负责在安装阶段就挡住非 Windows;
|
|
12
|
+
* 运行期再挡一次,覆盖从源码直接安装、pnpm 忽略兼容性检查等情形。
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-ssh-vault/platform
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { t } from './i18n.js'
|
|
18
|
+
|
|
19
|
+
/** 支持的操作系统,取值同 Node 的 `process.platform`。 */
|
|
20
|
+
export const SUPPORTED_PLATFORMS = Object.freeze(['win32'])
|
|
21
|
+
|
|
22
|
+
/** 平台显示名。 */
|
|
23
|
+
const PLATFORM_LABELS = Object.freeze({
|
|
24
|
+
win32: 'Windows',
|
|
25
|
+
darwin: 'macOS',
|
|
26
|
+
linux: 'Linux',
|
|
27
|
+
freebsd: 'FreeBSD',
|
|
28
|
+
openbsd: 'OpenBSD',
|
|
29
|
+
sunos: 'SunOS',
|
|
30
|
+
aix: 'AIX',
|
|
31
|
+
android: 'Android',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 平台显示名。
|
|
36
|
+
* @param platform - `process.platform` 取值。
|
|
37
|
+
* @returns `Windows` / `macOS` / `Linux` 等;未知平台原样返回。
|
|
38
|
+
*/
|
|
39
|
+
export function platformLabel(platform = process.platform) {
|
|
40
|
+
return PLATFORM_LABELS[platform] ?? String(platform)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 该平台是否受支持。
|
|
45
|
+
* @param platform - `process.platform` 取值。
|
|
46
|
+
* @returns 受支持时为 true。
|
|
47
|
+
*/
|
|
48
|
+
export function isSupportedPlatform(platform = process.platform) {
|
|
49
|
+
return SUPPORTED_PLATFORMS.includes(platform)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 断言当前平台受支持,否则抛出一条可读的错误。
|
|
54
|
+
*
|
|
55
|
+
* 故意抛错而不是返回布尔值:加载路径上任何「继续跑」都会走到明文密钥或缺失的
|
|
56
|
+
* askpass,比直接失败更难排查。
|
|
57
|
+
*
|
|
58
|
+
* @param platform - `process.platform` 取值;测试可显式传入。
|
|
59
|
+
* @throws 平台不在 {@link SUPPORTED_PLATFORMS} 中时。
|
|
60
|
+
*/
|
|
61
|
+
export function assertSupportedPlatform(platform = process.platform) {
|
|
62
|
+
if (isSupportedPlatform(platform)) return
|
|
63
|
+
throw new Error(t('platform.unsupported', {
|
|
64
|
+
platform: platformLabel(platform),
|
|
65
|
+
raw: String(platform),
|
|
66
|
+
supported: SUPPORTED_PLATFORMS.map((value) => platformLabel(value)).join(', '),
|
|
67
|
+
}))
|
|
68
|
+
}
|