small fix

This commit is contained in:
Zoe
2025-05-22 19:53:56 +00:00
parent 1e4dc0c558
commit e858308421
8 changed files with 234 additions and 48 deletions

174
main.go
View File

@@ -5,6 +5,7 @@ import (
"context"
"database/sql"
"fmt"
"log"
"log/slog"
"math/rand/v2"
"net"
@@ -23,8 +24,50 @@ import (
"github.com/juls0730/gloom/libs"
"github.com/juls0730/sentinel"
_ "github.com/mattn/go-sqlite3"
"github.com/syndtr/gocapability/capability"
)
func hasChrootAbility() bool {
cap, err := capability.NewPid2(0)
if err != nil {
return false
}
err = cap.Load()
if err != nil {
return false
}
return cap.Get(capability.EFFECTIVE, capability.CAP_SYS_CHROOT) || cap.Get(capability.PERMITTED, capability.CAP_SYS_CHROOT)
}
func hasChangeUserAbility() bool {
cap, err := capability.NewPid2(0)
if err != nil {
return false
}
err = cap.Load()
if err != nil {
return false
}
permitted := true
for _, permission := range []capability.Cap{
capability.CAP_SETUID,
capability.CAP_SETGID,
capability.CAP_SETFCAP,
} {
if cap.Get(capability.EFFECTIVE, permission) || cap.Get(capability.PERMITTED, permission) {
continue
}
permitted = false
break
}
return permitted
}
type PluginHost struct {
UnixSocket string
Process *os.Process
@@ -37,13 +80,16 @@ type PluginConfig struct {
type PreloadPlugin struct {
File string
Name string
Domains []string
}
// might change this later
const DEFAULT_PLUGIN_DIR = "plugs"
const DEFAULT_CHROOT_DIR = "plugData"
type GLoomConfig struct {
EnableChroot bool `toml:"enableChroot"`
PluginDir string `toml:"pluginDir"`
PreloadPlugins []PreloadPlugin `toml:"plugins"`
}
@@ -51,8 +97,6 @@ type GLoomConfig struct {
type GLoom struct {
config GLoomConfig
// path to the /tmp directory where the pluginHost sockets are created
tmpDir string
gloomDir string
// maps plugin names to plugins
@@ -65,6 +109,8 @@ type GLoom struct {
}
func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
log.SetOutput(os.Stderr)
gloomDir := os.Getenv("GLOOM_DIR")
if gloomDir == "" {
if os.Getenv("XDG_DATA_HOME") != "" {
@@ -106,11 +152,6 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
return nil, err
}
tmpDir, err := os.MkdirTemp(os.TempDir(), "gloom")
if err != nil {
return nil, err
}
if _, err := os.Stat(filepath.Join(gloomDir, "pluginHost")); os.IsNotExist(err) {
if err := os.WriteFile(filepath.Join(gloomDir, "pluginHost"), pluginHost, 0755); err != nil {
return nil, err
@@ -120,7 +161,6 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
}
gloom := &GLoom{
tmpDir: tmpDir,
gloomDir: gloomDir,
plugins: libs.SyncMap[string, *PluginHost]{},
DB: db,
@@ -132,6 +172,17 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
return nil, err
}
if gloom.config.EnableChroot {
// make sure we have super user privileges
if !hasChrootAbility() {
return nil, fmt.Errorf("chroot is enabled, but you do not have the required privileges to use it")
}
if !hasChangeUserAbility() {
return nil, fmt.Errorf("chroot is enabled, but you do not have the required privileges to use it")
}
}
if err := os.MkdirAll(gloom.config.PluginDir, 0755); err != nil {
slog.Error("Failed to create pluginDir", "dir", gloom.config.PluginDir, "error", err)
return nil, err
@@ -141,13 +192,17 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
if _, err := embeddedAssets.Open("dist/gloomi.so"); err == nil {
// and if the plugin doesn't exist, copy it over
// TODO: instead, check if the plugin doesnt exist OR the binary has a newer timestamp than the current version
if _, err := os.Stat(filepath.Join(gloom.config.PluginDir, "gloomi.so")); os.IsNotExist(err) {
if err := os.MkdirAll(path.Join(gloom.config.PluginDir, "GLoomI"), 0755); err != nil {
return nil, err
}
if _, err := os.Stat(path.Join(gloom.config.PluginDir, "GLoomI", "gloomi.so")); os.IsNotExist(err) {
gloomiData, err := embeddedAssets.ReadFile("dist/gloomi.so")
if err != nil {
return nil, err
}
if err := os.WriteFile(filepath.Join(gloom.config.PluginDir, "gloomi.so"), gloomiData, 0755); err != nil {
if err := os.WriteFile(path.Join(gloom.config.PluginDir, "GLoomI", "gloomi.so"), gloomiData, 0755); err != nil {
return nil, err
}
}
@@ -172,23 +227,31 @@ func (gloom *GLoom) loadConfig() error {
return err
}
slog.Debug("Loaded config", "config", gloom.config)
if gloom.config.PluginDir == "" {
gloom.config.PluginDir = DEFAULT_PLUGIN_DIR
}
gloom.config.PluginDir = filepath.Join(gloom.gloomDir, gloom.config.PluginDir)
slog.Debug("Loaded config", "config", gloom.config)
return nil
}
func (gloom *GLoom) Cleanup() error {
for _, plugin := range gloom.plugins.Keys() {
gloom.StopPlugin(plugin)
}
// return nil
}
func (gloom *GLoom) LoadInitialPlugins() error {
slog.Info("Loading initial plugins")
for _, plugin := range gloom.config.PreloadPlugins {
if err := gloom.RegisterPlugin(filepath.Join(gloom.config.PluginDir, plugin.File), plugin.File, plugin.Domains); err != nil {
panic(fmt.Errorf("failed to load preload plugin %s: %w (make sure its in %s)", plugin.File, err, gloom.config.PluginDir))
if err := gloom.RegisterPlugin(filepath.Join(gloom.config.PluginDir, plugin.Name, plugin.File), plugin.Name, plugin.Domains); err != nil {
panic(fmt.Errorf("failed to load preload plugin %s: %w (make sure its in %s)", plugin.Name, err, path.Join(gloom.config.PluginDir, plugin.Name)))
}
}
@@ -269,30 +332,65 @@ func (dt *MutexLock[T]) Unlock(id T) {
var deploymentLock = NewMutexLock[string]()
func (gloom *GLoom) RegisterPlugin(pluginPath string, name string, domains []string) (err error) {
for _, char := range name {
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' || char == '_') {
return fmt.Errorf("invalid plugin name")
}
}
slog.Info("Registering plugin", "pluginPath", pluginPath, "domains", domains)
pathStr := strconv.FormatUint(uint64(rand.Uint64()), 16)
socketPath := path.Join(gloom.tmpDir, pathStr+".sock")
socketPath := path.Join(gloom.config.PluginDir, name, pathStr+".sock")
slog.Debug("Starting pluginHost", "pluginPath", pluginPath)
processPath := path.Join(gloom.gloomDir, "pluginHost")
args := []string{"--plugin-path", pluginPath, "--socket-path", socketPath}
if gloom.config.EnableChroot {
if err := os.MkdirAll(path.Join(gloom.config.PluginDir, name), 0755); err != nil {
return fmt.Errorf("failed to create chroot directory: %w", err)
}
args = append(args, "--chroot-dir", path.Join(gloom.config.PluginDir, name))
}
files, err := os.ReadDir(path.Join(gloom.config.PluginDir, name))
if err != nil {
return fmt.Errorf("failed to read pluginDir: %w", err)
}
slog.Debug("Removing dead sockets", "pluginDir", path.Join(gloom.config.PluginDir, name))
for _, file := range files {
if file.IsDir() {
continue
}
// remove all dead sockets
if strings.HasSuffix(file.Name(), ".sock") {
if err := os.Remove(path.Join(gloom.config.PluginDir, name, file.Name())); err != nil {
return fmt.Errorf("failed to remove socket: %w", err)
}
}
}
cmd := exec.Command(processPath, args...)
stderrPipe, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("failed to get stderr pipe: %w", err)
}
reader := bufio.NewReader(stderrPipe)
readTimeout := time.After(30 * time.Second)
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start pluginHost: %w", err)
}
process := cmd.Process
reader := bufio.NewReader(stderrPipe)
readTimeout := time.After(30 * time.Second)
select {
case <-readTimeout:
_ = process.Signal(os.Interrupt)
@@ -318,6 +416,8 @@ func (gloom *GLoom) RegisterPlugin(pluginPath string, name string, domains []str
}
}
slog.Debug("Creating proxy", "socketPath", socketPath)
proxy, err := sentinel.NewDeploymentProxy(socketPath, NewUnixSocketTransport)
if err != nil {
return err
@@ -368,10 +468,7 @@ func (gloom *GLoom) RegisterPlugin(pluginPath string, name string, domains []str
return nil
}
// removes plugin from proxy and kills the process
func (gloom *GLoom) DeletePlugin(pluginName string) error {
slog.Debug("Deleting plugin", "pluginName", pluginName)
func (gloom *GLoom) StopPlugin(pluginName string) error {
pluginHost, ok := gloom.plugins.Load(pluginName)
if !ok {
return fmt.Errorf("plugin not found")
@@ -389,6 +486,17 @@ func (gloom *GLoom) DeletePlugin(pluginName string) error {
gloom.plugins.Delete(pluginName)
return nil
}
// removes plugin from proxy and kills the process
func (gloom *GLoom) DeletePlugin(pluginName string) error {
slog.Debug("Deleting plugin", "pluginName", pluginName)
if err := gloom.StopPlugin(pluginName); err != nil {
return err
}
var pluginPath string
if err := gloom.DB.QueryRow("SELECT path FROM plugins WHERE name = ?", pluginName).Scan(&pluginPath); err != nil {
return fmt.Errorf("failed to get plugin path: %w", err)
@@ -457,14 +565,15 @@ func (rpc *GloomRPC) ListPlugins(_ struct{}, reply *[]PluginData) error {
}
type PluginUpload struct {
Name string `json:"name"`
Domains []string `json:"domains"`
Data []byte `json:"data"`
FileName string `json:"fileName"`
Name string `json:"name"`
Domains []string `json:"domains"`
Data []byte `json:"data"`
}
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
for _, preloadPlugin := range rpc.gloom.config.PreloadPlugins {
if plugin.Name == preloadPlugin.File {
if plugin.Name == preloadPlugin.Name {
*reply = "Plugin is preloaded"
return nil
}
@@ -497,7 +606,7 @@ func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
defer deploymentLock.Unlock(plugin.Name)
slog.Info("Uploading plugin", "plugin", plugin.Name, "domains", plugin.Domains)
pluginPath, err := filepath.Abs(filepath.Join(rpc.gloom.config.PluginDir, (plugin.Name + ".so")))
pluginPath, err := filepath.Abs(filepath.Join(rpc.gloom.config.PluginDir, plugin.Name, plugin.FileName))
if err != nil {
*reply = "Plugin upload failed"
return err
@@ -567,6 +676,11 @@ func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
}
}
if err := os.MkdirAll(filepath.Join(rpc.gloom.config.PluginDir, plugin.Name), 0755); err != nil {
*reply = "Plugin upload failed"
return err
}
// regardless of if plugin exists or not, we'll upload the file since this could be an update to an existing plugin
if err := os.WriteFile(pluginPath, plugin.Data, 0644); err != nil {
*reply = "Plugin upload failed"
@@ -605,7 +719,7 @@ func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
func (rpc *GloomRPC) DeletePlugin(pluginName string, reply *string) error {
for _, preloadPlugin := range rpc.gloom.config.PreloadPlugins {
if pluginName == preloadPlugin.File {
if pluginName == preloadPlugin.Name {
*reply = "Plugin is preloaded"
return nil
}
@@ -644,8 +758,10 @@ func main() {
gloom, err := NewGloom(proxyManager)
if err != nil {
panic(err)
fmt.Printf("Failed to create GLoom: %v\n", err)
os.Exit(1)
}
defer gloom.Cleanup()
if err := gloom.StartRPCServer(); err != nil {
panic("Failed to start RPC server: " + err.Error())