small fix
This commit is contained in:
13
README.md
13
README.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"]
|
||||||
`)
|
`)
|
||||||
|
|||||||
@@ -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
2
go.mod
@@ -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
2
go.sum
@@ -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
168
main.go
@@ -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())
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user