Scert/backend/internal/certstorage/client_linux.go

97 lines
2.5 KiB
Go

package certstorage
import (
"context"
"encoding/json"
"errors"
"io"
"net"
"os"
"path/filepath"
"syscall"
"time"
"golang.org/x/sys/unix"
)
const SocketPath = "/run/scert-storage/prepare.sock"
type request struct {
Path string `json:"path"`
}
type response struct {
Ready bool `json:"ready"`
}
func peerUID(connection *net.UnixConn) (uint32, error) {
raw, err := connection.SyscallConn()
if err != nil {
return 0, err
}
var credentials *unix.Ucred
var socketErr error
err = raw.Control(func(fd uintptr) {
credentials, socketErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
})
if err != nil || socketErr != nil {
return 0, errors.Join(err, socketErr)
}
if credentials == nil || credentials.Pid <= 0 {
return 0, errors.New("本地目录服务缺少内核身份")
}
return credentials.Uid, nil
}
// Prepare 只使用受保护的本机通道;不接受远程地址,也不传递命令或服务名称。
func Prepare(ctx context.Context, path string) error {
for current := SocketPath; ; current = filepath.Dir(current) {
info, err := os.Lstat(current)
if err != nil {
return err
}
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok || stat.Uid != 0 || info.Mode()&os.ModeSymlink != 0 {
return errors.New("本地目录服务路径不可信")
}
if current == SocketPath {
if info.Mode()&os.ModeSocket == 0 || info.Mode().Perm()&0o007 != 0 {
return errors.New("本地目录服务通道权限无效")
}
} else if !info.IsDir() || info.Mode().Perm()&0o022 != 0 {
return errors.New("本地目录服务父目录权限无效")
}
if current == "/" {
break
}
}
ctx, cancel := context.WithTimeout(ctx, 25*time.Second)
defer cancel()
connection, err := (&net.Dialer{}).DialContext(ctx, "unix", SocketPath)
if err != nil {
return err
}
defer connection.Close()
stop := context.AfterFunc(ctx, func() { _ = connection.Close() })
defer stop()
uid, err := peerUID(connection.(*net.UnixConn))
if err != nil || uid != 0 {
return errors.New("本地目录服务不是受信任的系统进程")
}
deadline, _ := ctx.Deadline()
_ = connection.SetDeadline(deadline)
if err := json.NewEncoder(connection).Encode(request{Path: path}); err != nil {
return err
}
var result response
decoder := json.NewDecoder(io.LimitReader(connection, 1024))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&result); err != nil {
return err
}
if !result.Ready {
return errors.New("目录自动准备失败,请检查获准范围、父目录权限及目录服务状态")
}
return nil
}