101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
//go:build linux
|
|
|
|
package adminconsole
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.pcux.cn/xuans/Scert/backend/internal/localreset"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func openTerminal(ctx context.Context) (context.Context, terminal, func(), error) {
|
|
child, closeTerminal, err := OpenInvocation(ctx)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
screen, ok := child.Value(invocationTerminalKey{}).(*invocationTerminal)
|
|
if !ok {
|
|
closeTerminal()
|
|
return nil, nil, nil, ErrUnauthorizedPeer
|
|
}
|
|
return child, screen, closeTerminal, nil
|
|
}
|
|
|
|
func (t *invocationTerminal) Read(ctx context.Context, prompt string, hidden bool) (string, error) {
|
|
return t.read(ctx, prompt, hidden)
|
|
}
|
|
|
|
func (t *invocationTerminal) Show(message string) error { return showLine(t.file, message) }
|
|
|
|
func isTerminal(fd int) bool {
|
|
_, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
return err == nil
|
|
}
|
|
|
|
func dialControl(ctx context.Context) (net.Conn, error) {
|
|
serviceUser, err := user.Lookup("Scert")
|
|
if err != nil {
|
|
return nil, localreset.ErrUnavailable
|
|
}
|
|
uid, err := strconv.ParseUint(serviceUser.Uid, 10, 32)
|
|
if err != nil {
|
|
return nil, localreset.ErrUnavailable
|
|
}
|
|
return localreset.Dial(ctx, "", uint32(uid))
|
|
}
|
|
|
|
// AuthorizeCurrentProcess 要求 root 系统服务和显式启动任务,不能通过直接执行或伪造环境放行。
|
|
func AuthorizeCurrentProcess() error {
|
|
if !authorizeLinuxProcess(os.Geteuid(), os.Getpid(), os.Getppid(), os.Getenv("INVOCATION_ID"), os.Getenv("SYSTEMD_EXEC_PID")) {
|
|
return ErrUnauthorizedPeer
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
output, err := exec.CommandContext(ctx, "/usr/bin/systemctl", "list-jobs", "--no-legend", "--plain", "--no-pager").Output()
|
|
if err != nil || !hasAuthorizedStartJob(string(output)) {
|
|
return ErrUnauthorizedPeer
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasAuthorizedStartJob(output string) bool {
|
|
for _, line := range strings.Split(output, "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 4 && fields[1] == "Scert-admin.service" {
|
|
return fields[2] == "start" && (fields[3] == "waiting" || fields[3] == "running")
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func authorizeLinuxProcess(euid, pid, parentPID int, invocationID, systemdExecPID string) bool {
|
|
if euid != 0 || parentPID != 1 || !validInvocationID(invocationID) {
|
|
return false
|
|
}
|
|
if systemdExecPID == "" {
|
|
return true
|
|
}
|
|
execPID, err := strconv.Atoi(systemdExecPID)
|
|
return err == nil && execPID == pid
|
|
}
|
|
|
|
func validInvocationID(value string) bool {
|
|
if len(value) != 32 {
|
|
return false
|
|
}
|
|
for _, character := range strings.ToLower(value) {
|
|
if (character < '0' || character > '9') && (character < 'a' || character > 'f') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|