port 80を開くためにはroot権限が必要だが、開いたあとはセキュリティーリスクを最小限にするために一般ユーザ権限に降格したい、というWebサーバをGoで書く例です。
【追記3】
下記の例ではLinuxで動作させた場合に不十分です。
Linuxではsetuidを呼び出したスレッドにしか効かないので、以下の例をそのままLinuxで動かすとsyscall.Setuid()
は成功しますが、HTTPのHandlerでは別スレッドで動くことがあるため、rootのままで動作することになります。
- rootで起動した状態で":80"をlistenして
-
syscall.Setuid()
で一般ユーザになり -
http.Serve()
する
package main
import (
"net"
"net/http"
"syscall"
"log"
)
func main() {
listener, err := net.Listen("tcp", ":80")
if err != nil {
log.Panic(err)
}
err = syscall.Setuid(501) // 501 == uid
if err != nil {
// setuid に失敗したらroot権限のままなので死ぬべき
log.Panicf("setuid failed", err)
}
handler := http.FileServer(http.Dir("/tmp"))
err = http.Serve(listener, handler)
if err != nil {
log.Panic(err)
}
}
ところで、ユーザ名からuidを解決するために getpwnam(3) 相当のことをするのにはどうすればいいんでしょう。
cgoで実装する例 は見つけましたが、Go自体ではサポートされていないのかな…
【追記】
コメントで教えて頂きましたが os/userのLookup でユーザ名からuidが取得できます。User.Uidがstringなのは、uidが数値ではないシステムがあるからでしょうね。具体的にはPlan9…?
// (略)
userInfo, err := user.Lookup("nobody")
if err != nil {
log.Panicf("no such user", err)
}
uid, _ := strconv.Atoi(userInfo.Uid)
syscall.Setuid(uid)
【追記2】
setuidに失敗した場合は権限が放棄できていないので、ちゃんとエラーチェックして対処するべきでしたのでコードを修正しました。
【追記3】
Linuxの場合、setuid は呼び出したスレッドにしか効かないというのを教えて頂きました。