LoginSignup
85
83

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-07-15

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

85
83
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
85
83