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