This commit is contained in:
parent
b69043fc3a
commit
b9d81bba3d
@ -43,7 +43,7 @@ func run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
defer conn.Close()
|
||||
if err := adminreset.SendVerifiedReset(ctx, conn, adminreset.ReadProtectedInput, adminreset.ReadProtectedPassword); err != nil {
|
||||
if err := adminreset.SendVerifiedReset(ctx, conn, adminreset.ReadVisibleInput, adminreset.ReadProtectedPassword); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "超级管理员重置失败:", safeError(err))
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ func ReadProtectedPassword() (string, error) {
|
||||
}
|
||||
|
||||
func askPassword(prompt string) (string, error) {
|
||||
value, err := ReadProtectedInput(prompt)
|
||||
value, err := readSystemdInput(prompt, "no")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -40,8 +40,12 @@ func askPassword(prompt string) (string, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func ReadProtectedInput(prompt string) (string, error) {
|
||||
command := exec.Command("systemd-ask-password", "--no-tty", prompt)
|
||||
func ReadVisibleInput(prompt string) (string, error) {
|
||||
return readSystemdInput(prompt, "yes")
|
||||
}
|
||||
|
||||
func readSystemdInput(prompt, echo string) (string, error) {
|
||||
command := exec.Command("systemd-ask-password", "--no-tty", "--echo="+echo, prompt)
|
||||
command.Stderr = io.Discard
|
||||
output, err := command.Output()
|
||||
if err != nil {
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
|
||||
package adminreset
|
||||
|
||||
func ReadProtectedPassword() (string, error) { return "", ErrUnsupported }
|
||||
func ReadProtectedInput(string) (string, error) { return "", ErrUnsupported }
|
||||
func ReadProtectedPassword() (string, error) { return "", ErrUnsupported }
|
||||
func ReadVisibleInput(string) (string, error) { return "", ErrUnsupported }
|
||||
|
||||
@ -3,9 +3,11 @@
|
||||
package adminreset
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/utils/password"
|
||||
|
||||
@ -35,9 +37,18 @@ func ReadProtectedPassword() (string, error) {
|
||||
}
|
||||
|
||||
func readWindowsPassword(prompt string) (string, error) {
|
||||
value, err := ReadProtectedInput(prompt)
|
||||
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return "", errors.New("受保护的密码输入需要交互式控制台")
|
||||
}
|
||||
fmt.Fprint(os.Stderr, prompt+": ")
|
||||
valueBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Fprintln(os.Stderr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", errors.New("受保护的密码输入失败")
|
||||
}
|
||||
value := string(valueBytes)
|
||||
for index := range valueBytes {
|
||||
valueBytes[index] = 0
|
||||
}
|
||||
if password.ValidateUserPassword(value) != nil {
|
||||
return "", errors.New("密码长度必须为 8 到 12 个字符")
|
||||
@ -45,19 +56,14 @@ func readWindowsPassword(prompt string) (string, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func ReadProtectedInput(prompt string) (string, error) {
|
||||
func ReadVisibleInput(prompt string) (string, error) {
|
||||
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return "", errors.New("安全验证需要交互式控制台")
|
||||
}
|
||||
fmt.Fprint(os.Stderr, prompt+": ")
|
||||
value, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Fprintln(os.Stderr)
|
||||
value, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if err != nil {
|
||||
return "", errors.New("受保护的密码输入失败")
|
||||
return "", errors.New("读取安全验证输入失败")
|
||||
}
|
||||
normalized := string(value)
|
||||
for i := range value {
|
||||
value[i] = 0
|
||||
}
|
||||
return normalized, nil
|
||||
return strings.TrimSuffix(strings.TrimSuffix(value, "\n"), "\r"), nil
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ PrivateNetwork=true
|
||||
PrivateDevices=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=strict
|
||||
# 密码代理只在本机临时目录收集无回显输入,不允许写入业务配置或数据库。
|
||||
# 密码代理在本机临时目录收集交互输入;验证内容可见,新密码不回显,且均不写入业务配置或数据库。
|
||||
ReadWritePaths=/run/systemd/ask-password
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
|
||||
@ -106,7 +106,7 @@ flowchart LR
|
||||
|
||||
### 4.1 部署端超级管理员重置
|
||||
|
||||
1. 部署人员在受控主机上按正式部署命令启动 `smail-admin`,安全凭据与新密码从无回显交互读取,不通过命令行参数、环境变量、服务名或普通文件传递。
|
||||
1. 部署人员在受控主机上按正式部署命令启动 `smail-admin`,验证方式和安全凭据使用可见交互输入,新密码使用无回显输入;这些内容均不通过命令行参数、环境变量、服务名或普通文件传递。
|
||||
2. `smail-admin` 连接固定本机 IPC 地址:Linux 为 Unix Domain Socket,Windows 为 Named Pipe。
|
||||
3. 主服务先校验对端身份,再签发一次性 nonce 和可用安全方式;客户端依次选择方式、验证邮箱验证码或 TOTP 或安全码,最后才提交新密码。选择邮箱前不发送验证码。
|
||||
4. 主服务校验协议版本、单帧大小、字段集合、密码长度、nonce 一致性和 nonce 未过期/未消费。
|
||||
|
||||
@ -20,10 +20,10 @@
|
||||
systemctl start smail-admin
|
||||
```
|
||||
|
||||
1. 输入可用验证方式的序号。仅显示已配置且有效的方式。
|
||||
1. 输入可用验证方式的序号。仅显示已配置且有效的方式,输入的序号会正常显示。
|
||||
2. 选择邮箱后才发送验证码;选择动态码或安全码不发邮件。
|
||||
3. 输入邮箱验证码、手机验证器动态码或一个安全码。
|
||||
4. 验证通过后,输入两次新密码。
|
||||
3. 输入邮箱验证码、手机验证器动态码或一个安全码,验证内容会正常显示,便于核对。
|
||||
4. 验证通过后,输入两次新密码;只有密码输入不显示。
|
||||
5. 事务成功后旧会话失效,旧安全码和协议凭据撤销。已绑定邮箱和 TOTP 保留。
|
||||
|
||||
未绑定邮箱时可以使用 TOTP 或安全码。若唯一方式是安全码,重置后重新登录会要求再次配置安全方式。新安全码只展示一次,请妥善保存。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user