Compare commits

...

7 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
17 changed files with 210 additions and 112 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
port80 port80
port80fg
port80and443 port80and443

View file

@ -1,4 +1,4 @@
Copyright (C) 2017 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,5 +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. See the examples directory on how to use the `Drop` and `MultiDrop` functions.

View file

@ -2,28 +2,27 @@ 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() {
cfg := godrop.Config{ cfg := godrop.Config{
User: "nobody", User: "nobody",
Group: "nobody", Group: "nobody",
Foreground: true,
} }
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()) _, port, _ := net.SplitHostPort(l.Addr().String())

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() {
@ -24,23 +25,23 @@ func main() {
return []net.Listener{l1, l2}, err return []net.Listener{l1, l2}, err
}) })
if err != nil { if err != nil {
fmt.Println(err) log.Fatal(err)
} }
l1, err := godrop.GetListener() l1, 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)
} }
_, port1, _ := net.SplitHostPort(l1.Addr().String()) _, port1, _ := net.SplitHostPort(l1.Addr().String())
l2, err := godrop.GetListenerFd(4) l2, err := godrop.GetListenerFd(4)
if err != nil { if err != nil {
fmt.Println("Failed to listen on FD 4:", err) log.Fatal("Failed to listen on FD 4:", err)
os.Exit(1)
} }
_, port2, _ := net.SplitHostPort(l2.Addr().String()) _, 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() { go func() {
http.Serve(l1, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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()) fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port1, 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=

View file

@ -1,6 +1,9 @@
// Copyright (C) 2017 Marius Schellenberger // Copyright (C) 2022 Marius Schellenberger
// Package godrop provides a simple privileges dropping library //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 (
@ -13,10 +16,6 @@ import (
"syscall" "syscall"
) )
func errf(err error) error {
return fmt.Errorf("godrop: %s", err)
}
// Config represents the drop config // Config represents the drop config
type Config struct { type Config struct {
// User is the user to drop privileges to. // User is the user to drop privileges to.
@ -42,21 +41,21 @@ func Drop(c Config, f func() (net.Listener, error)) error {
func MultiDrop(c Config, f func() ([]net.Listener, error)) error { func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
uid, err := userID(c.User) uid, err := userID(c.User)
if err != nil { if err != nil {
return errf(err) return errors.New("godrop: " + err.Error())
} }
if uid == 0 { if uid == 0 {
return errf(errors.New("unable to drop privileges to uid 0 (root)")) return fmt.Errorf("godrop: you can't drop privileges to uid 0 (%s)", c.User)
} }
gid, err := groupID(c.Group) gid, err := groupID(c.Group)
if err != nil { if err != nil {
return errf(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 errf(err) return errors.New("godrop: " + err.Error())
} }
for i, v := range ln { for i, v := range ln {
var f *os.File var f *os.File
@ -68,20 +67,14 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
f, err = l.File() f, err = l.File()
l.Close() l.Close()
default: default:
return errf(fmt.Errorf("index: %d listener is not type of either *net.TCPListener or *net.UnixListener", i)) return fmt.Errorf("godrop: index %d listener is not type of either *net.TCPListener or *net.UnixListener", i)
} }
if err != nil { if err != nil {
return errf(fmt.Errorf("index: %d %s", i, err)) return fmt.Errorf("godrop: index %d %s", i, err)
} }
cmd.ExtraFiles = append(cmd.ExtraFiles, f) cmd.ExtraFiles = append(cmd.ExtraFiles, f)
} }
if c.Foreground {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: c.Chroot, Chroot: c.Chroot,
Credential: &syscall.Credential{ Credential: &syscall.Credential{
@ -91,16 +84,31 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
Setsid: true, Setsid: true,
} }
if c.Foreground {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return errf(err) return errors.New("godrop: " + err.Error())
} }
if c.Foreground { if c.Foreground {
go func() { go func() {
term := make(chan os.Signal)
signal.Notify(term, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
sigs := make(chan os.Signal) sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) signal.Notify(sigs, syscall.SIGSTOP, syscall.SIGCONT, syscall.SIGUSR1, syscall.SIGUSR2)
sig := <-sigs for {
select {
case sig := <-term:
cmd.Process.Signal(sig) cmd.Process.Signal(sig)
return
case sig := <-sigs:
cmd.Process.Signal(sig)
}
}
}() }()
_ = cmd.Wait() _ = cmd.Wait()
os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus))) os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus)))
@ -110,7 +118,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
case uid: case uid:
return nil return nil
} }
return errf(errors.New("dropping 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
@ -120,6 +128,9 @@ func GetListener() (net.Listener, error) {
// 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) {
if fd < 3 {
return nil, errors.New("godrop: fd is less than 3")
}
f := os.NewFile(uintptr(fd), "") f := os.NewFile(uintptr(fd), "")
defer f.Close() defer f.Close()
return net.FileListener(f) return net.FileListener(f)

View file

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

View file

@ -1,23 +0,0 @@
// Copyright (C) 2017 Marius Schellenberger
// +build cgo
package godrop
import "os/user"
func lookupUID(username string) (string, error) {
u, err := user.Lookup(username)
if err != nil {
return "", err
}
return u.Uid, err
}
func lookupGID(name string) (string, error) {
g, err := user.LookupGroup(name)
if err != nil {
return "", err
}
return g.Gid, err
}

View file

@ -1,43 +0,0 @@
// Copyright (C) 2017 Marius Schellenberger
// +build darwin dragonfly freebsd android linux netbsd openbsd solaris
// +build !cgo
package godrop
import (
"io/ioutil"
"os"
"regexp"
)
func readFile(name, file string) (id string, err error) {
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 m[0][1], nil
}
}
return
}
func lookupUID(username string) (string, error) {
return readFile(username, "/etc/passwd")
}
func lookupGID(name string) (string, error) {
return readFile(name, "/etc/group")
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017 Marius Schellenberger // Copyright (C) 2022 Marius Schellenberger
package godrop package godrop

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
}