What is the best way to run sections of my go code as root on Linux? I am writing a sizable web app and do not want to run the whole app as root. Some of the code needs to manipulate the network stack, firewall rules, etc and therefore must run as root.
Currently, I have created a small stub executable that calls code back in my larger app. I then compile the stub and set the Linux suid bit. I then call it from my main go app as a shell command.
It works. But is this the most efficient way? Is there an idiomatic way?
Plan B was to use a REST API from a small HTTP service. Though that is a little harder to secure.
Thoughts?
e.g... the exec doupdate has the suid set for root permissions
From within the web app...
// Elevate UpdateNft to root
func ElevateUpdateNFT() string {
out, err := exec.Command("doupdate", "nft").CombinedOutput()
if err != nil {
log.Print("exec doupdate ", err)
}
if len(out) > 0 {
log.Print(string(out))
}
return string(out)
}
Inside doupdate, executing as root... CallUpdateNft is back in the main web app but now running with root permissions.
func main() {
...
case key == "nft":
models.CallUpdateNft()
...
}
There is nothing wrong with your approach but suid binaries have a poor reputation...
Afaik there are at least three ways to do this.
This will run the web server without privileges.
cmd := exec.Command("/bin/webserver", "--foo", "--bar")
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Gid: uint32(33),
Uid: uint32(33)}}
cmd.Start();
/etc/sudoers.d/www-data :
www-data ALL=(ALL) NOPASSWD: /bin/webserver
cmd := exec.Command("sudo", "/bin/webserver", "--foo", "--bar")
cmd.Start();
Communicate to a privileged process and make it do stuff.
$ go run both.go library.go /etc/shadow
0
open /etc/shadow: permission denied
server as root:
$ make serve
go build -o server server.go library.go ; sudo ./server
then connect:
$ ./client /etc/shadow | wc -l
1918
ok.
69
all: stop
go build -o client client.go library.go
go build -o server server.go library.go
./server & sleep 0.2
./client $(path)
both:
go build -o sc both.go library.go && ./sc $(path)
stop: ; killall server || true
serve: ; go build -o server server.go library.go ; sudo ./server
clean: ; rm -vf client sc server
package main
import (
"fmt"
"os"
"sync"
)
func main() {
wg := &sync.WaitGroup{}
wg.Add(1)
go Server(wg, "")
path := "main.go"
if len(os.Args) > 1 {
path = os.Args[1]
}
resp, err := Client(path)
if nil == err {
fmt.Print(resp.Output)
} else {
println(err.Error())
}
wg.Wait()
}
package main
import (
"fmt"
"os"
)
func main() {
path := "/dev/null"
if len(os.Args) > 1 {
path = os.Args[1]
}
resp, err := Client(path)
if nil == err {
println("ok.")
fmt.Print(resp.Output)
} else {
println(err.Error())
}
}
package main
import (
"net"
"net/rpc"
"os"
"sync"
"time"
)
const Addr = "localhost:31337"
type Api struct {
server *rpc.Server
quit *sync.WaitGroup
}
func newApi(address string, wg *sync.WaitGroup) (*Api, error) {
if len(address) == 0 {
address = Addr
}
rpcs := rpc.NewServer()
errR := rpcs.RegisterName("V1", &MethodsV1{ Quit : wg })
sock, errL := net.Listen("tcp", address)
if errR != nil {
return nil, errR
}
if errL != nil {
return nil, errL
}
go rpcs.Accept(sock)
return &Api{server: rpcs, quit: wg}, nil
}
func Server(wg *sync.WaitGroup, addr string) {
_, err := newApi(addr, wg)
if err != nil {
return
}
if wg != nil {
wg.Wait()
} else {
<- time.After(time.Second * 50)
}
}
func Client(path string) (RpcResponse, error) {
addr := Addr
if a := os.Getenv("ADDR"); len(a) > 0 {
addr = a
}
resp := RpcResponse{}
client, errC := rpc.Dial("tcp", addr)
if nil == errC {
errC = client.Call("V1.Cat", &path, &resp);
println(len(resp.Output))
}
return resp, errC
}
type RpcResponse struct { Output string }
type MethodsV1 struct {
Quit *sync.WaitGroup
}
func (m1 *MethodsV1) Cat(path *string, response *RpcResponse) error {
if m1.Quit != nil {
defer m1.Quit.Done()
}
o, err := os.ReadFile(*path)
response.Output = string(o)
return err
}
package main
func main() {
Server(nil, "")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With