[Go] port 80を開いたあとroot権限を捨てるwebサーバの例

  • 90
    いいね
  • 5
    コメント
この記事は最終更新日から1年以上が経過しています。

port 80を開くためにはroot権限が必要だが、開いたあとはセキュリティーリスクを最小限にするために一般ユーザ権限に降格したい、というWebサーバをGoで書く例です。

【追記3】
下記の例ではLinuxで動作させた場合に不十分です。

Linuxではsetuidを呼び出したスレッドにしか効かないので、以下の例をそのままLinuxで動かすとsyscall.Setuid()は成功しますが、HTTPのHandlerでは別スレッドで動くことがあるため、rootのままで動作することになります。

methaneさんのコメントを参照ください

  • 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 は呼び出したスレッドにしか効かないというのを教えて頂きました。