Compare commits

...

12 commits

Author SHA1 Message Date
b2f6d0d478 use latest sys 2024-08-19 20:37:21 +02:00
77230f5240 update dependencies 2022-10-08 16:59:34 +02:00
3e528ceb3f unveil bugfix 2018-10-26 17:02:28 +02:00
ec32483e7f fixed typo 2018-10-26 16:54:12 +02:00
089d8d16c8 added pledge and unveil support 2018-10-26 16:52:08 +02:00
23731a3702 bugfix 2018-09-20 00:14:13 +02:00
de6a48c088 version 2.0.0 2018-09-19 21:12:43 +02:00
108583814e user lookup bugfix 2017-04-20 15:17:45 +02:00
09266c9d51 fixed typo 2017-04-19 23:27:45 +02:00
1e230617ce added generic user lookup and foreground processes 2017-04-19 23:20:52 +02:00
4b88a07ef5 infinite loop bugfix 2017-04-19 21:00:24 +02:00
694ac1e8a9 simplified library and added better examples and net.UnixListener support 2017-04-19 20:42:19 +02:00
15 changed files with 331 additions and 112 deletions

4
.gitignore vendored
View file

@ -1 +1,3 @@
example/example port80
port80fg
port80and443

View file

@ -1,4 +1,4 @@
Copyright (C) 2016 Marius Schellenberger Copyright (C) 2022 Marius Schellenberger
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View file

@ -1,3 +1,5 @@
# godrop - drop privileges # godrop - drop privileges
Godrop is a simple library to drop privileges on linux maschines. Godrop is a simple library to drop privileges on Linux and OpenBSD.
See the examples directory on how to use the `Drop` and `MultiDrop` functions.

View file

@ -2,11 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"log"
"net" "net"
"net/http" "net/http"
"os" "os"
"git.giftfish.de/ston1th/godrop" "git.giftfish.de/ston1th/godrop/v2"
) )
func main() { func main() {
@ -16,16 +17,16 @@ func main() {
} }
err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") }) err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") })
if err != nil { if err != nil {
fmt.Println(err) log.Fatal(err)
} }
l, err := godrop.GetListener() l, err := godrop.GetListener()
if err != nil { if err != nil {
fmt.Println("Failed to listen on FD 3:", err) log.Fatal("Failed to listen on FD 3:", err)
os.Exit(1)
} }
_, port, _ := net.SplitHostPort(l.Addr().String())
http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "pid %d\nuid %d\ngid %d", os.Getpid(), os.Getuid(), os.Getgid()) fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port, os.Getpid(), os.Getuid(), os.Getgid())
})) }))
} }

54
example/port80and443.go Normal file
View file

@ -0,0 +1,54 @@
package main
import (
"fmt"
"log"
"net"
"net/http"
"os"
"git.giftfish.de/ston1th/godrop/v2"
)
func main() {
cfg := godrop.Config{
User: "nobody",
Group: "nobody",
Foreground: true,
}
err := godrop.MultiDrop(cfg, func() ([]net.Listener, error) {
l1, err := net.Listen("tcp", ":80")
if err != nil {
return nil, err
}
l2, err := net.Listen("tcp", ":443")
return []net.Listener{l1, l2}, err
})
if err != nil {
log.Fatal(err)
}
l1, err := godrop.GetListener()
if err != nil {
log.Fatal("Failed to listen on FD 3:", err)
}
_, port1, _ := net.SplitHostPort(l1.Addr().String())
l2, err := godrop.GetListenerFd(4)
if err != nil {
log.Fatal("Failed to listen on FD 4:", err)
}
_, port2, _ := net.SplitHostPort(l2.Addr().String())
fmt.Printf("port1 %s\nport2 %s\npid %d\nuid %d\ngid %d\n", port1, port2, os.Getpid(), os.Getuid(), os.Getgid())
go func() {
http.Serve(l1, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port1, os.Getpid(), os.Getuid(), os.Getgid())
}))
}()
http.Serve(l2, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port2, os.Getpid(), os.Getuid(), os.Getgid())
}))
}

35
example/port80fg.go Normal file
View file

@ -0,0 +1,35 @@
package main
import (
"fmt"
"log"
"net"
"net/http"
"os"
"git.giftfish.de/ston1th/godrop/v2"
)
func main() {
cfg := godrop.Config{
User: "nobody",
Group: "nobody",
Foreground: true,
}
err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") })
if err != nil {
log.Fatal(err)
}
l, err := godrop.GetListener()
if err != nil {
log.Fatal("Failed to listen on FD 3:", err)
}
_, port, _ := net.SplitHostPort(l.Addr().String())
fmt.Printf("port %s\npid %d\nuid %d\ngid %d\n", port, os.Getpid(), os.Getuid(), os.Getgid())
http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port, os.Getpid(), os.Getuid(), os.Getgid())
}))
}

5
go.mod Normal file
View file

@ -0,0 +1,5 @@
module git.giftfish.de/ston1th/godrop/v2
go 1.19
require golang.org/x/sys v0.24.0

2
go.sum Normal file
View file

@ -0,0 +1,2 @@
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

179
godrop.go
View file

@ -1,129 +1,76 @@
// Copyright (C) 2022 Marius Schellenberger
//go:build go1.11
// +build go1.11
// Package godrop provides a simple library to drop privileges on Linux and OpenBSD.
package godrop package godrop
import ( import (
"errors" "errors"
"io/ioutil" "fmt"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"regexp" "os/signal"
"strconv"
"syscall" "syscall"
) )
func readFile(name, file string) (id int, err error) { // Config represents the drop config
id = -1
if name == "" {
return
}
f, err := os.Open(file)
if err != nil {
return
}
b, err := ioutil.ReadAll(f)
if err != nil {
return
}
r := regexp.MustCompile(name + ":.*:(\\d+)")
m := r.FindAllStringSubmatch(string(b), 1)
if len(m) == 1 {
if len(m[0]) == 2 {
return strconv.Atoi(m[0][1])
}
}
return
}
// UID of user
func UID(user string) (int, error) {
return readFile(user, "/etc/passwd")
}
// GID of group
func GID(group string) (int, error) {
return readFile(group, "/etc/group")
}
type Config struct { type Config struct {
User string // User is the user to drop privileges to.
Group string User string
// Group is the group to drop privileges to.
Group string
// Chroot is the directory to chroot into. Leave this emptry for no chroot.
// When compiling without cgo, make sure the chroot directory contains the /etc/passwd and /etc/group files.
Chroot string Chroot string
// Set to true, to run the process in the foreground.
Foreground bool
} }
// Drop will spawn a new process, hand over the listening socket file descriptor and terminate itself after // Drop will spawn a new process and hand over the listening socket file descriptor
func Drop(c Config, f func() (net.Listener, error)) error { func Drop(c Config, f func() (net.Listener, error)) error {
uid, err := UID(c.User) return MultiDrop(c, func() ([]net.Listener, error) {
if err != nil { l, err := f()
return err return []net.Listener{l}, err
} })
gid, err := GID(c.Group)
if err != nil {
return err
}
switch os.Getuid() {
case 0:
ln, err := f()
if err != nil {
return err
}
l, ok := ln.(*net.TCPListener)
if !ok {
return errors.New("godrop: interface conversion failed")
}
f, err := l.File()
if err != nil {
return err
}
cmd := exec.Command(os.Args[0], os.Args[1:]...)
cmd.ExtraFiles = []*os.File{f}
cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: c.Chroot,
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
Setsid: true,
}
if err := cmd.Start(); err != nil {
return err
}
cmd.Process.Release()
os.Exit(0)
case uid:
return nil
}
return errors.New("godrop: droping priviledges failed")
} }
// MultiDrop will spawn a new process, hand over the all listening sockets and terminate itself after // MultiDrop will spawn a new process and hand over the all listening sockets
func MultiDrop(c Config, f func() ([]net.Listener, error)) error { func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
uid, err := UID(c.User) uid, err := userID(c.User)
if err != nil { if err != nil {
return err return errors.New("godrop: " + err.Error())
} }
gid, err := GID(c.Group) if uid == 0 {
return fmt.Errorf("godrop: you can't drop privileges to uid 0 (%s)", c.User)
}
gid, err := groupID(c.Group)
if err != nil { if err != nil {
return err return errors.New("godrop: " + err.Error())
} }
switch os.Getuid() { switch os.Getuid() {
case 0: case 0:
cmd := exec.Command(os.Args[0], os.Args[1:]...) cmd := exec.Command(os.Args[0], os.Args[1:]...)
ln, err := f() ln, err := f()
if err != nil { if err != nil {
return err return errors.New("godrop: " + err.Error())
} }
for _, v := range ln { for i, v := range ln {
l, ok := v.(*net.TCPListener) var f *os.File
if !ok { switch l := v.(type) {
return errors.New("godrop: interface conversion failed") case *net.TCPListener:
f, err = l.File()
l.Close()
case *net.UnixListener:
f, err = l.File()
l.Close()
default:
return fmt.Errorf("godrop: index %d listener is not type of either *net.TCPListener or *net.UnixListener", i)
} }
f, err := l.File()
if err != nil { if err != nil {
return err return fmt.Errorf("godrop: index %d %s", i, err)
} }
cmd.ExtraFiles = append(cmd.ExtraFiles, f) cmd.ExtraFiles = append(cmd.ExtraFiles, f)
} }
@ -137,24 +84,54 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
Setsid: true, Setsid: true,
} }
if err := cmd.Start(); err != nil { if c.Foreground {
return err cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} }
if err := cmd.Start(); err != nil {
return errors.New("godrop: " + err.Error())
}
if c.Foreground {
go func() {
term := make(chan os.Signal)
signal.Notify(term, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGSTOP, syscall.SIGCONT, syscall.SIGUSR1, syscall.SIGUSR2)
for {
select {
case sig := <-term:
cmd.Process.Signal(sig)
return
case sig := <-sigs:
cmd.Process.Signal(sig)
}
}
}()
_ = cmd.Wait()
os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus)))
}
cmd.Process.Release() cmd.Process.Release()
os.Exit(0) os.Exit(0)
case uid: case uid:
return nil return nil
} }
return errors.New("godrop: droping priviledges failed") return errors.New("godrop: dropping priviledges failed")
} }
// GetListener returns the listener socket of file descriptor 3 // GetListener returns the listener socket of file descriptor 3
func GetListener() (net.Listener, error) { func GetListener() (net.Listener, error) {
return net.FileListener(os.NewFile(3, "[socket]")) return GetListenerFd(3)
} }
// GetListenerFd returns the listener socket of the given file descriptor // GetListenerFd returns the listener socket of the given file descriptor
func GetListenerFd(fd int) (net.Listener, error) { func GetListenerFd(fd int) (net.Listener, error) {
return net.FileListener(os.NewFile(uintptr(fd), "[socket]")) if fd < 3 {
return nil, errors.New("godrop: fd is less than 3")
}
f := os.NewFile(uintptr(fd), "")
defer f.Close()
return net.FileListener(f)
} }

35
lookup.go Normal file
View file

@ -0,0 +1,35 @@
// Copyright (C) 2022 Marius Schellenberger
//go:build go1.11
// +build go1.11
package godrop
import (
"os/user"
"strconv"
)
func atoi(a string) (int, error) {
i, err := strconv.Atoi(a)
if err != nil {
return -1, err
}
return i, nil
}
func userID(username string) (int, error) {
u, err := user.Lookup(username)
if err != nil {
return -1, err
}
return atoi(u.Uid)
}
func groupID(name string) (int, error) {
g, err := user.LookupGroup(name)
if err != nil {
return -1, err
}
return atoi(g.Gid)
}

View file

@ -1,9 +1,11 @@
// Copyright (C) 2022 Marius Schellenberger
package godrop package godrop
import "testing" import "testing"
func TestUID(t *testing.T) { func TestUserID(t *testing.T) {
uid, err := UID("root") uid, err := userID("root")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -12,8 +14,8 @@ func TestUID(t *testing.T) {
} }
} }
func TestGID(t *testing.T) { func TestGroupID(t *testing.T) {
gid, err := GID("root") gid, err := groupID("root")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

15
pledge.go Normal file
View file

@ -0,0 +1,15 @@
// Copyright (C) 2022 Marius Schellenberger
//go:build !openbsd
// +build !openbsd
package godrop
// Pledge is currently only supported on OpenBSD.
func Pledge(promises, execpromises string) error { return nil }
// PledgePromises is currently only supported on OpenBSD.
func PledgePromises(promises string) error { return nil }
// PledgeExecPromises is currently only supported on OpenBSD.
func PledgeExecpromises(execpromises string) error { return nil }

44
pledge_openbsd.go Normal file
View file

@ -0,0 +1,44 @@
// Copyright (C) 2022 Marius Schellenberger
//go:build openbsd
// +build openbsd
package godrop
import (
"errors"
"golang.org/x/sys/unix"
)
// Pledge is a wrapper for x/sys/unix Pledge.
//
// See https://go.googlesource.com/sys/+/master/unix/openbsd_pledge.go for usage.
func Pledge(promises, execpromises string) (err error) {
err = unix.Pledge(promises, execpromises)
if err != nil {
err = errors.New("pledge: " + err.Error())
}
return
}
// PledgePromises is a wrapper for x/sys/unix PledgePromises.
//
// See https://go.googlesource.com/sys/+/master/unix/openbsd_pledge.go for usage.
func PledgePromises(promises string) (err error) {
err = unix.PledgePromises(promises)
if err != nil {
err = errors.New("pledge: " + err.Error())
}
return
}
// PledgeExecpromises is a wrapper for x/sys/unix PledgeExecpromises.
//
// See https://go.googlesource.com/sys/+/master/unix/openbsd_pledge.go for usage.
func PledgeExecpromises(execpromises string) (err error) {
err = unix.PledgeExecpromises(execpromises)
if err != nil {
err = errors.New("pledge: " + err.Error())
}
return
}

12
unveil.go Normal file
View file

@ -0,0 +1,12 @@
// Copyright (C) 2022 Marius Schellenberger
//go:build !openbsd
// +build !openbsd
package godrop
// Unveil is currently only supported on OpenBSD.
func Unveil(path, flags string) error { return nil }
// UnveilBlock is currently only supported on OpenBSD.
func UnveilBlock() error { return nil }

33
unveil_openbsd.go Normal file
View file

@ -0,0 +1,33 @@
// Copyright (C) 2022 Marius Schellenberger
//go:build openbsd
// +build openbsd
package godrop
import (
"errors"
"golang.org/x/sys/unix"
)
// Unveil is a wrapper for x/sys/unix Unveil.
//
// See https://go.googlesource.com/sys/+/master/unix/openbsd_unveil.go for usage.
func Unveil(path, flags string) (err error) {
err = unix.Unveil(path, flags)
if err != nil {
err = errors.New("unveil: " + err.Error())
}
return
}
// UnveilBlock is a wrapper for x/sys/unix UnveilBlock.
//
// See https://go.googlesource.com/sys/+/master/unix/openbsd_unveil.go for usage.
func UnveilBlock() (err error) {
err = unix.UnveilBlock()
if err != nil {
err = errors.New("unveil: " + err.Error())
}
return
}