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

View File

@@ -58,14 +58,19 @@ GLoom will also use a config file in the `GLOOM_DIR` to configure plugins and ot
```toml ```toml
[[plugins]] [[plugins]]
file = "gloomi.so" file = "gloomi.so"
name = "GLoomI"
domains = ["localhost"] domains = ["localhost"]
``` ```
The `[[plugins]]` array is a list of plugins to preload. Each plugin is an object with the following fields: The config is in TOML and has the following keys:
- `file` - The path to the plugin file. This is a string value.
- `domains` - An array of domains to forward requests to this plugin. This is an array of strings.
- `plugins` - An array of plugins to load. Each plugin is an object with the following keys:
- `file` - The name of the plugin file.
- `name` - The name of the plugin. Can only contain alphanumeric characters and `-` and `_`.
- `domains` - An array of domains to load the plugin on.
- `pluginDir` - The directory to store plugins in relative to `GLOOM_DIR`. Defaults to `plugs`.
- `enableChroot` - Whether to enable chroot, this forces plugins to a specific directory that they cannot escape.
Defaults to `false`.
## Usage ## Usage

View File

@@ -11,5 +11,6 @@ var embeddedAssets embed.FS
var defaultConfig = []byte(` var defaultConfig = []byte(`
[[plugins]] [[plugins]]
file = "gloomi.so" file = "gloomi.so"
name = "GLoomI"
domains = ["localhost"] domains = ["localhost"]
`) `)

View File

@@ -16,13 +16,13 @@ type GLoomI struct {
func (p *GLoomI) Init() (*fiber.Config, error) { func (p *GLoomI) Init() (*fiber.Config, error) {
// Connect to the RPC server // Connect to the RPC server
client, err := rpc.Dial("tcp", "localhost:7143") client, err := rpc.Dial("tcp", "127.0.0.1:7143")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to connect to Gloom RPC server: %w", err) return nil, fmt.Errorf("failed to connect to Gloom RPC server: %w", err)
} }
p.client = client p.client = client
return &fiber.Config{ return &fiber.Config{
BodyLimit: 1024 * 1024 * 1024 * 5, // 5GB BodyLimit: 1024 * 1024 * 1024 * 5, // 5GiB
}, nil }, nil
} }
@@ -94,10 +94,12 @@ func (p *GLoomI) RegisterRoutes(router fiber.Router) {
} }
var pluginUploadStruct struct { var pluginUploadStruct struct {
Domains []string `json:"domains"` FileName string `json:"fileName"`
Name string `json:"name"` Name string `json:"name"`
Domains []string `json:"domains"`
Data []byte `json:"data"` Data []byte `json:"data"`
} }
pluginUploadStruct.FileName = pluginFile.Filename
pluginUploadStruct.Name = pluginUpload.Name pluginUploadStruct.Name = pluginUpload.Name
pluginUploadStruct.Domains = domains pluginUploadStruct.Domains = domains
pluginUploadStruct.Data, err = io.ReadAll(pluginData) pluginUploadStruct.Data, err = io.ReadAll(pluginData)

2
go.mod
View File

@@ -7,6 +7,8 @@ require (
github.com/mattn/go-sqlite3 v1.14.24 github.com/mattn/go-sqlite3 v1.14.24
) )
require github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
require ( require (
github.com/BurntSushi/toml v1.5.0 github.com/BurntSushi/toml v1.5.0
golang.org/x/sync v0.14.0 // indirect golang.org/x/sync v0.14.0 // indirect

2
go.sum
View File

@@ -4,5 +4,7 @@ github.com/juls0730/sentinel v0.0.0-20250515154110-2e7e6586cacd h1:JNazPdlAs307G
github.com/juls0730/sentinel v0.0.0-20250515154110-2e7e6586cacd/go.mod h1:CnRvcleiS2kvK1N2PeQmeoRP5EXpBDpHPkg72vAUaSg= github.com/juls0730/sentinel v0.0.0-20250515154110-2e7e6586cacd/go.mod h1:CnRvcleiS2kvK1N2PeQmeoRP5EXpBDpHPkg72vAUaSg=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=

168
main.go
View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"log"
"log/slog" "log/slog"
"math/rand/v2" "math/rand/v2"
"net" "net"
@@ -23,8 +24,50 @@ import (
"github.com/juls0730/gloom/libs" "github.com/juls0730/gloom/libs"
"github.com/juls0730/sentinel" "github.com/juls0730/sentinel"
_ "github.com/mattn/go-sqlite3" _ "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 { type PluginHost struct {
UnixSocket string UnixSocket string
Process *os.Process Process *os.Process
@@ -37,13 +80,16 @@ type PluginConfig struct {
type PreloadPlugin struct { type PreloadPlugin struct {
File string File string
Name string
Domains []string Domains []string
} }
// might change this later // might change this later
const DEFAULT_PLUGIN_DIR = "plugs" const DEFAULT_PLUGIN_DIR = "plugs"
const DEFAULT_CHROOT_DIR = "plugData"
type GLoomConfig struct { type GLoomConfig struct {
EnableChroot bool `toml:"enableChroot"`
PluginDir string `toml:"pluginDir"` PluginDir string `toml:"pluginDir"`
PreloadPlugins []PreloadPlugin `toml:"plugins"` PreloadPlugins []PreloadPlugin `toml:"plugins"`
} }
@@ -51,8 +97,6 @@ type GLoomConfig struct {
type GLoom struct { type GLoom struct {
config GLoomConfig config GLoomConfig
// path to the /tmp directory where the pluginHost sockets are created
tmpDir string
gloomDir string gloomDir string
// maps plugin names to plugins // maps plugin names to plugins
@@ -65,6 +109,8 @@ type GLoom struct {
} }
func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) { func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
log.SetOutput(os.Stderr)
gloomDir := os.Getenv("GLOOM_DIR") gloomDir := os.Getenv("GLOOM_DIR")
if gloomDir == "" { if gloomDir == "" {
if os.Getenv("XDG_DATA_HOME") != "" { if os.Getenv("XDG_DATA_HOME") != "" {
@@ -106,11 +152,6 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
return nil, err 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.Stat(filepath.Join(gloomDir, "pluginHost")); os.IsNotExist(err) {
if err := os.WriteFile(filepath.Join(gloomDir, "pluginHost"), pluginHost, 0755); err != nil { if err := os.WriteFile(filepath.Join(gloomDir, "pluginHost"), pluginHost, 0755); err != nil {
return nil, err return nil, err
@@ -120,7 +161,6 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
} }
gloom := &GLoom{ gloom := &GLoom{
tmpDir: tmpDir,
gloomDir: gloomDir, gloomDir: gloomDir,
plugins: libs.SyncMap[string, *PluginHost]{}, plugins: libs.SyncMap[string, *PluginHost]{},
DB: db, DB: db,
@@ -132,6 +172,17 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
return nil, err 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 { if err := os.MkdirAll(gloom.config.PluginDir, 0755); err != nil {
slog.Error("Failed to create pluginDir", "dir", gloom.config.PluginDir, "error", err) slog.Error("Failed to create pluginDir", "dir", gloom.config.PluginDir, "error", err)
return nil, err return nil, err
@@ -141,13 +192,17 @@ func NewGloom(proxyManager *sentinel.ProxyManager) (*GLoom, error) {
if _, err := embeddedAssets.Open("dist/gloomi.so"); err == nil { if _, err := embeddedAssets.Open("dist/gloomi.so"); err == nil {
// and if the plugin doesn't exist, copy it over // 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 // 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") gloomiData, err := embeddedAssets.ReadFile("dist/gloomi.so")
if err != nil { if err != nil {
return nil, err 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 return nil, err
} }
} }
@@ -172,23 +227,31 @@ func (gloom *GLoom) loadConfig() error {
return err return err
} }
slog.Debug("Loaded config", "config", gloom.config)
if gloom.config.PluginDir == "" { if gloom.config.PluginDir == "" {
gloom.config.PluginDir = DEFAULT_PLUGIN_DIR gloom.config.PluginDir = DEFAULT_PLUGIN_DIR
} }
gloom.config.PluginDir = filepath.Join(gloom.gloomDir, gloom.config.PluginDir) gloom.config.PluginDir = filepath.Join(gloom.gloomDir, gloom.config.PluginDir)
slog.Debug("Loaded config", "config", gloom.config)
return nil return nil
} }
func (gloom *GLoom) Cleanup() error {
for _, plugin := range gloom.plugins.Keys() {
gloom.StopPlugin(plugin)
}
// return nil
}
func (gloom *GLoom) LoadInitialPlugins() error { func (gloom *GLoom) LoadInitialPlugins() error {
slog.Info("Loading initial plugins") slog.Info("Loading initial plugins")
for _, plugin := range gloom.config.PreloadPlugins { for _, plugin := range gloom.config.PreloadPlugins {
if err := gloom.RegisterPlugin(filepath.Join(gloom.config.PluginDir, plugin.File), plugin.File, plugin.Domains); err != nil { 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.File, err, gloom.config.PluginDir)) 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]() var deploymentLock = NewMutexLock[string]()
func (gloom *GLoom) RegisterPlugin(pluginPath string, name string, domains []string) (err error) { 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) slog.Info("Registering plugin", "pluginPath", pluginPath, "domains", domains)
pathStr := strconv.FormatUint(uint64(rand.Uint64()), 16) 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) slog.Debug("Starting pluginHost", "pluginPath", pluginPath)
processPath := path.Join(gloom.gloomDir, "pluginHost") processPath := path.Join(gloom.gloomDir, "pluginHost")
args := []string{"--plugin-path", pluginPath, "--socket-path", socketPath} 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...) cmd := exec.Command(processPath, args...)
stderrPipe, err := cmd.StderrPipe() stderrPipe, err := cmd.StderrPipe()
if err != nil { if err != nil {
return fmt.Errorf("failed to get stderr pipe: %w", err) 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 { if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start pluginHost: %w", err) return fmt.Errorf("failed to start pluginHost: %w", err)
} }
process := cmd.Process process := cmd.Process
reader := bufio.NewReader(stderrPipe)
readTimeout := time.After(30 * time.Second)
select { select {
case <-readTimeout: case <-readTimeout:
_ = process.Signal(os.Interrupt) _ = 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) proxy, err := sentinel.NewDeploymentProxy(socketPath, NewUnixSocketTransport)
if err != nil { if err != nil {
return err return err
@@ -368,10 +468,7 @@ func (gloom *GLoom) RegisterPlugin(pluginPath string, name string, domains []str
return nil return nil
} }
// removes plugin from proxy and kills the process func (gloom *GLoom) StopPlugin(pluginName string) error {
func (gloom *GLoom) DeletePlugin(pluginName string) error {
slog.Debug("Deleting plugin", "pluginName", pluginName)
pluginHost, ok := gloom.plugins.Load(pluginName) pluginHost, ok := gloom.plugins.Load(pluginName)
if !ok { if !ok {
return fmt.Errorf("plugin not found") return fmt.Errorf("plugin not found")
@@ -389,6 +486,17 @@ func (gloom *GLoom) DeletePlugin(pluginName string) error {
gloom.plugins.Delete(pluginName) 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 var pluginPath string
if err := gloom.DB.QueryRow("SELECT path FROM plugins WHERE name = ?", pluginName).Scan(&pluginPath); err != nil { 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) return fmt.Errorf("failed to get plugin path: %w", err)
@@ -457,6 +565,7 @@ func (rpc *GloomRPC) ListPlugins(_ struct{}, reply *[]PluginData) error {
} }
type PluginUpload struct { type PluginUpload struct {
FileName string `json:"fileName"`
Name string `json:"name"` Name string `json:"name"`
Domains []string `json:"domains"` Domains []string `json:"domains"`
Data []byte `json:"data"` Data []byte `json:"data"`
@@ -464,7 +573,7 @@ type PluginUpload struct {
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error { func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
for _, preloadPlugin := range rpc.gloom.config.PreloadPlugins { for _, preloadPlugin := range rpc.gloom.config.PreloadPlugins {
if plugin.Name == preloadPlugin.File { if plugin.Name == preloadPlugin.Name {
*reply = "Plugin is preloaded" *reply = "Plugin is preloaded"
return nil return nil
} }
@@ -497,7 +606,7 @@ func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
defer deploymentLock.Unlock(plugin.Name) defer deploymentLock.Unlock(plugin.Name)
slog.Info("Uploading plugin", "plugin", plugin.Name, "domains", plugin.Domains) 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 { if err != nil {
*reply = "Plugin upload failed" *reply = "Plugin upload failed"
return err 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 // 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 { if err := os.WriteFile(pluginPath, plugin.Data, 0644); err != nil {
*reply = "Plugin upload failed" *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 { func (rpc *GloomRPC) DeletePlugin(pluginName string, reply *string) error {
for _, preloadPlugin := range rpc.gloom.config.PreloadPlugins { for _, preloadPlugin := range rpc.gloom.config.PreloadPlugins {
if pluginName == preloadPlugin.File { if pluginName == preloadPlugin.Name {
*reply = "Plugin is preloaded" *reply = "Plugin is preloaded"
return nil return nil
} }
@@ -644,8 +758,10 @@ func main() {
gloom, err := NewGloom(proxyManager) gloom, err := NewGloom(proxyManager)
if err != nil { 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 { if err := gloom.StartRPCServer(); err != nil {
panic("Failed to start RPC server: " + err.Error()) panic("Failed to start RPC server: " + err.Error())

View File

@@ -10,12 +10,15 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"plugin" "plugin"
"strings"
"syscall"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
) )
var pluginPath string var pluginPath string
var socketPath string var socketPath string
var chrootDir string
// Idk why I originally wrote this solution when stderr is literally just the best solution for me, but this // Idk why I originally wrote this solution when stderr is literally just the best solution for me, but this
// makes the pluginHost more generally useful outside of GLoom, so I'm keeping it // makes the pluginHost more generally useful outside of GLoom, so I'm keeping it
@@ -47,13 +50,13 @@ func main() {
if router != nil { if router != nil {
if err := router.Shutdown(); err != nil { if err := router.Shutdown(); err != nil {
log.Printf("Error shutting down router: %v", err) log.Printf("Error: error shutting down router: %v", err)
} }
} }
if listener != nil { if listener != nil {
if err := listener.Close(); err != nil { if err := listener.Close(); err != nil {
log.Printf("Error closing listener: %v", err) log.Printf("Error: error closing listener: %v", err)
} }
os.Remove(socketPath) os.Remove(socketPath)
@@ -70,10 +73,11 @@ func main() {
fs.StringVar(&pluginPath, "plugin-path", "", "Path to the plugin") fs.StringVar(&pluginPath, "plugin-path", "", "Path to the plugin")
fs.StringVar(&socketPath, "socket-path", "", "Path to the socket") fs.StringVar(&socketPath, "socket-path", "", "Path to the socket")
fs.StringVar(&controlPath, "control-path", "", "Path to the control socket") fs.StringVar(&controlPath, "control-path", "", "Path to the control socket")
fs.StringVar(&chrootDir, "chroot-dir", "", "Path to the chroot directory")
err := fs.Parse(os.Args[1:]) err := fs.Parse(os.Args[1:])
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing arguments: %v", err) fmt.Fprintf(os.Stderr, "Error: error parsing arguments: %v", err)
os.Exit(1) os.Exit(1)
} }
os.Args = fs.Args() os.Args = fs.Args()
@@ -98,13 +102,13 @@ func main() {
controlListener, err := net.Listen("unix", controlPath) controlListener, err := net.Listen("unix", controlPath)
if err != nil { if err != nil {
log.Fatalf("Error listening on control socket: %v", err) log.Fatalf("Error: error listening on control socket: %v", err)
} }
defer controlListener.Close() defer controlListener.Close()
conn, err := controlListener.Accept() conn, err := controlListener.Accept()
if err != nil { if err != nil {
log.Printf("Error accepting control connection: %v", err) log.Printf("Error: error accepting control connection: %v", err)
return return
} }
defer conn.Close() defer conn.Close()
@@ -120,13 +124,13 @@ func main() {
os.Remove(controlPath) os.Remove(controlPath)
} }
if !ok { if !ok {
log.Printf("Control connection is not a writer") log.Printf("Error: control connection is not a writer")
return return
} }
} }
if _, err := os.Stat(socketPath); err == nil { if _, err := os.Stat(socketPath); err == nil {
Print("Error: Socket %s already exists", socketPath) Print("Error: socket %s already exists", socketPath)
os.Exit(1) os.Exit(1)
} }
@@ -136,6 +140,60 @@ func main() {
os.Exit(1) os.Exit(1)
} }
if chrootDir != "" {
if !strings.HasPrefix(socketPath, chrootDir) {
Print("Error: socket path is not in the chroot directory, but chroot is enabled, and therefore the socket cannot be used by the plugin. This is a GLoom bug, please report it.")
os.Exit(1)
}
// we are chrooting and changing to nobody to "sandbox" the plugin
// nobodyUser, err := user.Lookup("nobody")
// if err != nil {
// Print("Error: failed to get nobody user: %w", err)
// }
// nobodyUid, err := strconv.Atoi(nobodyUser.Uid)
// if err != nil {
// Print("Error: failed to parse nobody uid: %w", err)
// }
// nobodyGid, err := strconv.Atoi(nobodyUser.Gid)
// if err != nil {
// Print("Error: failed to parse nobody gid: %w", err)
// }
pluginData, err := os.ReadFile(realPluginPath)
if err != nil {
Print("Error: failed to read plugin: %w", err)
}
pluginFileName := filepath.Base(realPluginPath)
// copy the plugin to the chroot directory
if err := os.WriteFile(filepath.Join(chrootDir, pluginFileName), pluginData, 0644); err != nil {
Print("Error: failed to copy plugin to chroot: %w", err)
}
// if err := os.Chown(chrootDir, nobodyUid, nobodyGid); err != nil {
// Print("Error: failed to chown chroot directory: %w", err)
// }
realPluginPath = "/" + pluginFileName
socketPath = "/" + filepath.Base(socketPath)
if err := syscall.Chroot(chrootDir); err != nil {
Print("Error: failed to chroot: %w", err)
}
// if err := syscall.Setgid(nobodyGid); err != nil {
// Print("Error: failed to setgid: %w", err)
// }
// if err := syscall.Setuid(nobodyUid); err != nil {
// Print("Error: failed to setuid: %w", err)
// }
}
p, err := plugin.Open(realPluginPath) p, err := plugin.Open(realPluginPath)
if err != nil { if err != nil {
Print("Error: could not open plugin %s: %v", realPluginPath, err) Print("Error: could not open plugin %s: %v", realPluginPath, err)

View File

@@ -6,8 +6,8 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build": "zqdgr build:gloomi && zqdgr build:gloom", "build": "zqdgr build:gloomi && zqdgr build:gloom",
"build:pluginHost": "sh -c \"cd pluginHost; go build -ldflags '-w -s' -o ../dist/host main.go\"", "build:pluginHost": "cd pluginHost; go build -ldflags '-w -s' -o ../dist/host main.go",
"build:gloomi": "sh -c \"cd gloomi; zqdgr build\"", "build:gloomi": "cd gloomi; zqdgr build",
"build:gloom": "zqdgr build:pluginHost && go build -tags=gloomi -o dist/gloom", "build:gloom": "zqdgr build:pluginHost && go build -tags=gloomi -o dist/gloom",
"build:nogloomi": "zqdgr build:pluginHost && go build -tags=!gloomi -o dist/gloom", "build:nogloomi": "zqdgr build:pluginHost && go build -tags=!gloomi -o dist/gloom",
"clean": "rm -rf dist && rm -rf plugin/plugin.so", "clean": "rm -rf dist && rm -rf plugin/plugin.so",