Spaces:
Sleeping
Sleeping
Commit
·
a2f5849
1
Parent(s):
4e1982a
Auto-commit: Dockerfile updated
Browse files- landrun-main/cmd/landrun/main.go +176 -0
landrun-main/cmd/landrun/main.go
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"os"
|
| 5 |
+
osexec "os/exec"
|
| 6 |
+
"strings"
|
| 7 |
+
|
| 8 |
+
"github.com/urfave/cli/v2"
|
| 9 |
+
"github.com/zouuup/landrun/internal/elfdeps"
|
| 10 |
+
"github.com/zouuup/landrun/internal/exec"
|
| 11 |
+
"github.com/zouuup/landrun/internal/log"
|
| 12 |
+
"github.com/zouuup/landrun/internal/sandbox"
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
// Version is the current version of landrun
|
| 16 |
+
const Version = "0.1.15"
|
| 17 |
+
|
| 18 |
+
func main() {
|
| 19 |
+
app := &cli.App{
|
| 20 |
+
Name: "landrun",
|
| 21 |
+
Usage: "Run a command in a Landlock sandbox",
|
| 22 |
+
Version: Version,
|
| 23 |
+
Flags: []cli.Flag{
|
| 24 |
+
&cli.StringFlag{
|
| 25 |
+
Name: "log-level",
|
| 26 |
+
Usage: "Set logging level (error, info, debug)",
|
| 27 |
+
Value: "error",
|
| 28 |
+
EnvVars: []string{"LANDRUN_LOG_LEVEL"},
|
| 29 |
+
},
|
| 30 |
+
&cli.StringSliceFlag{
|
| 31 |
+
Name: "ro",
|
| 32 |
+
Usage: "Allow read-only access to this path",
|
| 33 |
+
},
|
| 34 |
+
&cli.StringSliceFlag{
|
| 35 |
+
Name: "rox",
|
| 36 |
+
Usage: "Allow read-only access with execution to this path",
|
| 37 |
+
},
|
| 38 |
+
&cli.StringSliceFlag{
|
| 39 |
+
Name: "rw",
|
| 40 |
+
Usage: "Allow read-write access to this path",
|
| 41 |
+
},
|
| 42 |
+
&cli.StringSliceFlag{
|
| 43 |
+
Name: "rwx",
|
| 44 |
+
Usage: "Allow read-write access with execution to this path",
|
| 45 |
+
},
|
| 46 |
+
&cli.IntSliceFlag{
|
| 47 |
+
Name: "bind-tcp",
|
| 48 |
+
Usage: "Allow binding to these TCP ports",
|
| 49 |
+
Hidden: false,
|
| 50 |
+
},
|
| 51 |
+
&cli.IntSliceFlag{
|
| 52 |
+
Name: "connect-tcp",
|
| 53 |
+
Usage: "Allow connecting to these TCP ports",
|
| 54 |
+
Hidden: false,
|
| 55 |
+
},
|
| 56 |
+
&cli.BoolFlag{
|
| 57 |
+
Name: "best-effort",
|
| 58 |
+
Usage: "Use best effort mode (fall back to less restrictive sandbox if necessary)",
|
| 59 |
+
Value: false,
|
| 60 |
+
},
|
| 61 |
+
&cli.StringSliceFlag{
|
| 62 |
+
Name: "env",
|
| 63 |
+
Usage: "Environment variables to pass to the sandboxed command (KEY=VALUE or just KEY to pass current value)",
|
| 64 |
+
Value: cli.NewStringSlice(),
|
| 65 |
+
},
|
| 66 |
+
&cli.BoolFlag{
|
| 67 |
+
Name: "unrestricted-filesystem",
|
| 68 |
+
Usage: "Allow unrestricted filesystem access",
|
| 69 |
+
Value: false,
|
| 70 |
+
},
|
| 71 |
+
&cli.BoolFlag{
|
| 72 |
+
Name: "unrestricted-network",
|
| 73 |
+
Usage: "Allow unrestricted network access",
|
| 74 |
+
Value: false,
|
| 75 |
+
},
|
| 76 |
+
&cli.BoolFlag{
|
| 77 |
+
Name: "ldd",
|
| 78 |
+
Usage: "Automatically detect and add library dependencies to --rox",
|
| 79 |
+
Value: false,
|
| 80 |
+
},
|
| 81 |
+
&cli.BoolFlag{
|
| 82 |
+
Name: "add-exec",
|
| 83 |
+
Usage: "Automatically add the executable path to --rox",
|
| 84 |
+
Value: false,
|
| 85 |
+
},
|
| 86 |
+
},
|
| 87 |
+
Before: func(c *cli.Context) error {
|
| 88 |
+
log.SetLevel(c.String("log-level"))
|
| 89 |
+
return nil
|
| 90 |
+
},
|
| 91 |
+
Action: func(c *cli.Context) error {
|
| 92 |
+
args := c.Args().Slice()
|
| 93 |
+
if len(args) == 0 {
|
| 94 |
+
log.Fatal("Missing command to run")
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// Combine --ro and --rox paths for read-only access
|
| 98 |
+
readOnlyPaths := append([]string{}, c.StringSlice("ro")...)
|
| 99 |
+
readOnlyPaths = append(readOnlyPaths, c.StringSlice("rox")...)
|
| 100 |
+
|
| 101 |
+
// Combine --rw and --rwx paths for read-write access
|
| 102 |
+
readWritePaths := append([]string{}, c.StringSlice("rw")...)
|
| 103 |
+
readWritePaths = append(readWritePaths, c.StringSlice("rwx")...)
|
| 104 |
+
|
| 105 |
+
// Combine --rox and --rwx paths for executable permissions
|
| 106 |
+
readOnlyExecutablePaths := append([]string{}, c.StringSlice("rox")...)
|
| 107 |
+
readWriteExecutablePaths := append([]string{}, c.StringSlice("rwx")...)
|
| 108 |
+
|
| 109 |
+
binary, err := osexec.LookPath(args[0])
|
| 110 |
+
if err != nil {
|
| 111 |
+
log.Fatal("Failed to find binary: %v", err)
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
// Add command to readOnlyExecutablePaths
|
| 115 |
+
if c.Bool("add-exec") {
|
| 116 |
+
readOnlyExecutablePaths = append(readOnlyExecutablePaths, binary)
|
| 117 |
+
log.Debug("Added executable path: %v", binary)
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// If --ldd flag is set, detect and add library dependencies
|
| 121 |
+
if c.Bool("ldd") {
|
| 122 |
+
libPaths, err := elfdeps.GetLibraryDependencies(binary)
|
| 123 |
+
if err != nil {
|
| 124 |
+
log.Fatal("Failed to detect library dependencies: %v", err)
|
| 125 |
+
}
|
| 126 |
+
// Add library directories to readOnlyExecutablePaths
|
| 127 |
+
readOnlyExecutablePaths = append(readOnlyExecutablePaths, libPaths...)
|
| 128 |
+
log.Debug("Added library paths: %v", libPaths)
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
cfg := sandbox.Config{
|
| 132 |
+
ReadOnlyPaths: readOnlyPaths,
|
| 133 |
+
ReadWritePaths: readWritePaths,
|
| 134 |
+
ReadOnlyExecutablePaths: readOnlyExecutablePaths,
|
| 135 |
+
ReadWriteExecutablePaths: readWriteExecutablePaths,
|
| 136 |
+
BindTCPPorts: c.IntSlice("bind-tcp"),
|
| 137 |
+
ConnectTCPPorts: c.IntSlice("connect-tcp"),
|
| 138 |
+
BestEffort: c.Bool("best-effort"),
|
| 139 |
+
UnrestrictedFilesystem: c.Bool("unrestricted-filesystem"),
|
| 140 |
+
UnrestrictedNetwork: c.Bool("unrestricted-network"),
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Process environment variables
|
| 144 |
+
envVars := processEnvironmentVars(c.StringSlice("env"))
|
| 145 |
+
|
| 146 |
+
if err := sandbox.Apply(cfg); err != nil {
|
| 147 |
+
log.Fatal("Failed to apply sandbox: %v", err)
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
return exec.Run(args, envVars)
|
| 151 |
+
},
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
if err := app.Run(os.Args); err != nil {
|
| 155 |
+
log.Fatal("%v", err)
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
// processEnvironmentVars processes the env flag values
|
| 160 |
+
func processEnvironmentVars(envFlags []string) []string {
|
| 161 |
+
result := []string{}
|
| 162 |
+
|
| 163 |
+
for _, env := range envFlags {
|
| 164 |
+
// If the flag is just a key (no = sign), get the value from the current environment
|
| 165 |
+
if !strings.Contains(env, "=") {
|
| 166 |
+
if val, exists := os.LookupEnv(env); exists {
|
| 167 |
+
result = append(result, env+"="+val)
|
| 168 |
+
}
|
| 169 |
+
} else {
|
| 170 |
+
// Flag already contains the value (KEY=VALUE format)
|
| 171 |
+
result = append(result, env)
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
return result
|
| 176 |
+
}
|