2016-12-20 4 views
1

Я хочу создать пользовательский веб-сервер через golang. Ему нужен root для привязки к порту 80. Однако я хочу как можно скорее удалить корень. syscall.SetUid() возвращает «Не поддерживается» согласно ticket #1435.Golang dropping privileges (v1.7)

Я всегда мог перенаправить порт 80 на что-то еще через iptables, однако это открывает какой-либо процесс без полномочий root, который будет представлять собой мой веб-сервер, - который я бы предпочел не быть возможным.

Как отказаться от привилегий для моего приложения (или, наоборот, решить это чисто).

+3

Предпочитаемый метод заключается в использовании возможностей linux, позволяющих вашей программе связываться с правильным портом, не имея полноценных возможностей root. – JimB

+1

Эта проблема, с которой вы ссылаетесь, на самом деле имеет ряд возможных обходных решений/решений IIRC. – Carpetsmoker

+0

Setuid не работал должным образом для golang. Эти обходные пути заключались в том, чтобы сделать его поведение более последовательным. Однако, в конце концов, они не гарантировали успех, а это значит, что вы все равно можете стать root после вызова setuid. Вот почему было принято решение полностью отключить эту функцию. О возможностях linux: Что именно вы предлагаете здесь? – user2089648

ответ

1

Я сделал бы то, что @JimB предложил.

С другой стороны, в Linux есть еще одна хитрость: вы можете использовать os/exec.Command() выполнить /proc/self/exe, рассказывая ему использовать альтернативные учетные данные в SysProcAttr.Credential поле os/exec.Cmd например он генерирует.

См. go doc os/exec.Cmd, go doc syscall.SysProcAttr и go doc syscall.Credential.

Удостоверьтесь, что, когда вы заново выполняете свою программу, вы должны убедиться, что у порожденного есть свои стандартные потоки ввода-вывода, связанные с родителями, и все необходимые открытые файлы также наследуются.


Другой alternatve стоит упомянуть, чтобы не пытаться выполнить привязку к порту 80 на всех, и иметь надлежащий веб-сервер висит там, а затем обратный прокси-сервер либо имя хоста на основе виртуального хоста или префикс частности URL путь (или префиксы) для вашего процесса Go, прослушивающего любой сокет TCP или Unix. Оба Apache (по крайней мере, 2.4) и Nginx могут сделать это легко.

0

Я недавно проработал эту проблему, и у Go есть все необходимые вам штуки. В этом примере я пошел дальше и реализовал SSL. По сути, вы открываете порт, обнаруживаете UID, а если он равен 0, найдите нужного пользователя, получите UID, а затем используйте вызовы glibc для установки UID и GID этого процесса. Я мог бы подчеркнуть, что лучше всего позвонить на ваш код setuid сразу после привязки порта. Самая большая разница при снижении привилегий заключается в том, что вы не можете использовать http.ListenAndServe (TLS)? вспомогательная функция - вам нужно вручную настроить свой net.Listener отдельно, а затем вызвать setuid после привязки порта, но перед вызовом http.Serve.

Этот способ работы хорошо работает, потому что вы можете, например, работать как UID! = 0 в режиме «разработки» на высоком порту без дополнительных соображений. Помните, что это всего лишь заглушка - я рекомендую установить имена адресов адресов, портов, пользователей, групп и TLS в файле конфигурации.

package main 

import (
    "crypto/tls" 
    "log" 
    "net/http" 
    "os/user" 
    "strconv" 
    "syscall" 
) 

import (
    //#include <unistd.h> 
    //#include <errno.h> 
    "C" 
) 

func main() { 
    cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") 
    if err != nil { 
     log.Fatalln("Can't load certificates!", err) 
    } 
    var tlsconf tls.Config 
    tlsconf.Certificates = make([]tls.Certificate, 1) 
    tlsconf.Certificates[0] = cert 
    listener, err := tls.Listen("tcp4", "127.0.0.1:445", &tlsconf) 
    if err != nil { 
     log.Fatalln("Error opening port:", err) 
    } 
    if syscall.Getuid() == 0 { 
     log.Println("Running as root, downgrading to user www-data") 
     user, err := user.Lookup("www-data") 
     if err != nil { 
      log.Fatalln("User not found or other error:", err) 
     } 
     // TODO: Write error handling for int from string parsing 
     uid, _ := strconv.ParseInt(user.Uid, 10, 32) 
     gid, _ := strconv.ParseInt(user.Gid, 10, 32) 
     cerr, errno := C.setgid(C.__gid_t(gid)) 
     if cerr != 0 { 
      log.Fatalln("Unable to set GID due to error:", errno) 
     } 
     cerr, errno = C.setuid(C.__uid_t(uid)) 
     if cerr != 0 { 
      log.Fatalln("Unable to set UID due to error:", errno) 
     } 
    } 
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 
     w.Write([]byte("Hello, world!")) 
    }) 
    err = http.Serve(listener, nil) 
    log.Fatalln(err) 
}