Compare commits
3 Commits
ec5f7bb75a
...
master
Author | SHA1 | Date | |
---|---|---|---|
f97d36d6ed | |||
51e4774bb2 | |||
8da229bf39 |
7
go.mod
Normal file
7
go.mod
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module gitea.narnian.us/lordwelch/Podman
|
||||||
|
|
||||||
|
go 1.25.2
|
||||||
|
|
||||||
|
require kernel.org/pub/linux/libs/security/libcap/cap v1.2.76
|
||||||
|
|
||||||
|
require kernel.org/pub/linux/libs/security/libcap/psx v1.2.76 // indirect
|
146
main.go
Normal file
146
main.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package Podman
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(args ...string) error {
|
||||||
|
podman := exec.Command("/user/podman", args...)
|
||||||
|
podman.Env = append(os.Environ(), "TMPDIR=/tmp")
|
||||||
|
podman.Stdin = os.Stdin
|
||||||
|
podman.Stdout = os.Stdout
|
||||||
|
podman.Stderr = os.Stderr
|
||||||
|
|
||||||
|
exit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
podman.Process.Signal(<-exit)
|
||||||
|
}()
|
||||||
|
if err := podman.Run(); err != nil {
|
||||||
|
return fmt.Errorf("%v: %v", podman.Args, err)
|
||||||
|
}
|
||||||
|
exit <- os.Interrupt
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var numericRe = regexp.MustCompile(`^[0-9]+$`)
|
||||||
|
|
||||||
|
func Signal(name string, sig os.Signal) error {
|
||||||
|
fis, err := os.ReadDir("/proc")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, fi := range fis {
|
||||||
|
if !fi.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !numericRe.MatchString(fi.Name()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := os.ReadFile(filepath.Join("/proc", fi.Name(), "cmdline"))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue // process vanished
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(b), name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pid, _ := strconv.Atoi(fi.Name()) // already verified to be numeric
|
||||||
|
p, _ := os.FindProcess(pid)
|
||||||
|
return p.Signal(sig)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Process not found: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMounted(mountpoint string) (bool, error) {
|
||||||
|
b, err := os.ReadFile("/proc/self/mountinfo")
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil // platform does not have /proc/self/mountinfo, fall back to not verifying
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(strings.TrimSpace(string(b)), "\n") {
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) < 5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filepath.Clean(parts[4]) == filepath.Clean(mountpoint) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeWritable(dir string) error {
|
||||||
|
mounted, err := IsMounted(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mounted {
|
||||||
|
// Nothing to do, directory is already mounted.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all regular files in this directory.
|
||||||
|
regularFiles := make(map[string]string)
|
||||||
|
fis, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, fi := range fis {
|
||||||
|
b, err := os.ReadFile(filepath.Join(dir, fi.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
regularFiles[fi.Name()] = string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := MountTmpfs(dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all regular files from memory back to new tmpfs.
|
||||||
|
for name, contents := range regularFiles {
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MountTmpfs(dest string) error {
|
||||||
|
if mounted, err := IsMounted(dest); err == nil && mounted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := syscall.Mount("tmpfs", dest, "tmpfs", syscall.MS_NOSUID|syscall.MS_RELATIME, "size=5G"); err != nil {
|
||||||
|
return fmt.Errorf("mounting tmpfs to %s: %v", dest, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bind(source, dest string) error {
|
||||||
|
if mounted, err := IsMounted(dest); err == nil && mounted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
os.MkdirAll(source, 0o777)
|
||||||
|
os.MkdirAll(dest, 0o777)
|
||||||
|
|
||||||
|
if err := syscall.Mount(source, dest, "", syscall.MS_BIND, ""); err != nil {
|
||||||
|
return fmt.Errorf("error mounting %s to %s: %v", source, dest, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
142
privDrop.go
Normal file
142
privDrop.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package Podman
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"kernel.org/pub/linux/libs/security/libcap/cap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setFromCaps(caps ...string) (*cap.Set, error) {
|
||||||
|
var err error
|
||||||
|
newSet := cap.NewSet()
|
||||||
|
valueList := make([]cap.Value, 0, len(caps))
|
||||||
|
for _, capName := range caps {
|
||||||
|
value, err := cap.FromName(strings.ToLower(capName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
valueList = append(valueList, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = newSet.SetFlag(cap.Effective, true, valueList...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = newSet.Fill(cap.Permitted, cap.Effective)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newSet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChownRecursively(root string, owner int, group int) {
|
||||||
|
err := filepath.Walk(root,
|
||||||
|
func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if int(info.Sys().(*syscall.Stat_t).Uid) != owner || int(info.Sys().(*syscall.Stat_t).Gid) != group {
|
||||||
|
err = os.Chown(path, owner, group)
|
||||||
|
if err == nil {
|
||||||
|
log.Printf("File ownership of %s changed.\n", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Chown failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Passwd struct {
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
UID int
|
||||||
|
GID int
|
||||||
|
Gecos string
|
||||||
|
Home string
|
||||||
|
Shell string
|
||||||
|
Gids []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustDropPrivileges executes the program in a child process, dropping root
|
||||||
|
// privileges, but retaining the given capabilities.
|
||||||
|
func MustDropPrivileges(caps ...string) {
|
||||||
|
// Read and display the capabilities of the running process
|
||||||
|
passwd := LoadPasswd(os.Getenv("HOME"))
|
||||||
|
err := os.Chown(passwd.Home, passwd.UID, passwd.GID)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("Failed to chown home directory", os.Getenv("HOME"))
|
||||||
|
}
|
||||||
|
c := cap.GetProc()
|
||||||
|
if os.Getenv("PRIVILEGES_DROPPED") == "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
capSet, err := setFromCaps(caps...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to parse capabilities: %s", err)
|
||||||
|
}
|
||||||
|
diff, _ := capSet.Cf(c)
|
||||||
|
if passwd.GID == os.Getgid() && passwd.UID == os.Getuid() && diff == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Setenv("PRIVILEGES_DROPPED", "1")
|
||||||
|
|
||||||
|
if passwd.GID != 0 {
|
||||||
|
err = cap.SetGroups(passwd.GID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to change gid to %d: %s", passwd.GID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if passwd.GID != 0 {
|
||||||
|
err = cap.SetUID(passwd.UID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to change uid to %d: %s", passwd.UID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = capSet.SetProc()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to drop privileges: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadPasswd(home string) Passwd {
|
||||||
|
var (
|
||||||
|
passwd Passwd
|
||||||
|
)
|
||||||
|
passwd.User = filepath.Base(os.Getenv("HOME"))
|
||||||
|
b, err := os.ReadFile("/etc/passwd")
|
||||||
|
if err != nil {
|
||||||
|
return passwd
|
||||||
|
}
|
||||||
|
for _, line := range strings.SplitN(string(b), "\n", -1) {
|
||||||
|
fields := strings.SplitN(line, ":", 7)
|
||||||
|
if len(fields) != 7 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if passwd.User != string(fields[0]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
passwd.User = fields[0]
|
||||||
|
passwd.Password = fields[1]
|
||||||
|
passwd.UID, err = strconv.Atoi(fields[2])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
passwd.GID, err = strconv.Atoi(fields[3])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
passwd.Gecos = fields[4]
|
||||||
|
passwd.Home = fields[5]
|
||||||
|
passwd.Shell = fields[6]
|
||||||
|
}
|
||||||
|
return passwd
|
||||||
|
}
|
Reference in New Issue
Block a user