将Golang作为www-data运行

huangapple go评论116阅读模式
英文:

Run Golang as www-data

问题

当我运行一个Node HTTP服务器应用时,我通常会调用一个自定义函数:

  1. function runAsWWW() {
  2. try {
  3. process.setgid('www-data');
  4. process.setuid('www-data');
  5. } catch (err) {
  6. console.error('Cowardly refusal to keep the process alive as root.');
  7. process.exit(1);
  8. }
  9. }

server.listen(8080,'localhost',null,runAsWWW);

所以服务器实际上是以 www-data 用户身份运行,以提供更好的安全性。当我通过 go run index.go 启动一个Golang Web服务器时,是否有类似的操作?

英文:

When I run a Node HTTP server app I usually call a custom function

  1. function runAsWWW()
  2. {
  3. try
  4. {
  5. process.setgid('www-data');
  6. process.setuid('www-data');
  7. } catch (err)
  8. {
  9. console.error('Cowardly refusal to keep the process alive as root.');
  10. process.exit(1);
  11. }
  12. }

from server.listen(8080,'localhost',null,runAsWWW);

so the server is actually running as the www-data user to offer a better modicum of security. Is there something similar I can do when I start up a Golang web server by issuing go run index.go?

答案1

得分: 7

不。在Go语言中,无法可靠地使用setuid或setgid,因为这对于多线程程序是不起作用的。

您需要以预期的用户身份启动程序,可以直接启动,也可以通过某种监控程序(例如supervisord、runit、monit)或通过您的初始化系统启动。

英文:

No. You can't reliably setuid or setgid in go, because that doesn't work for multithreaded programs.

You need to start the program as the intended user, either directly, through a supervisor of some sort (e.g. supervisord, runit, monit), or through your init system.

答案2

得分: 4

扩展@JimB的答案:

使用进程监管器以特定用户身份运行应用程序(并处理重启/崩溃、日志重定向等)。对于多线程应用程序,setuidsetgid是普遍不好的想法。

可以使用操作系统的进程管理器(Upstart、systemd、sysvinit)或独立的进程管理器(Supervisor、runit、monit等)。

以下是Supervisor的示例:

[program:yourapp]
command=/home/yourappuser/bin/yourapp # 应用程序的位置
autostart=true
autorestart=true
startretries=10
user=yourappuser # 应用程序应以此用户身份运行(即不是root!)
directory=/srv/www/yourapp.com/ # 应用程序运行的目录
environment=APP_SETTINGS="/srv/www/yourapp.com/prod.toml" # 环境变量
redirect_stderr=true
stdout_logfile=/var/log/supervisor/yourapp.log # 日志文件的名称
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10

另外:如果您没有使用反向代理,并且您的Go应用程序需要绑定到小于1024的端口(例如端口80或443),则可以使用setcap - 例如:setcap cap_net_bind_service=+ep /home/yourappuser/bin/yourapp

附注:我写了一篇关于如何使用Supervisor运行Go应用程序的小文章(从“我没有安装Supervisor”开始)。

英文:

Expanding on @JimB's answer:

Use a process supervisor to run your application as a specific user (and handle restarts/crashes, log re-direction, etc). setuid and setgid are universally bad ideas for multi-threaded applications.

Either use your OS' process manager (Upstart, systemd, sysvinit) or a standalone process manager (Supervisor, runit, monit, etc).

Here's an example for Supervisor:

  1. [program:yourapp]
  2. command=/home/yourappuser/bin/yourapp # the location of your app
  3. autostart=true
  4. autorestart=true
  5. startretries=10
  6. user=yourappuser # the user your app should run as (i.e. *not* root!)
  7. directory=/srv/www/yourapp.com/ # where your application runs from
  8. environment=APP_SETTINGS="/srv/www/yourapp.com/prod.toml" # environmental variables
  9. redirect_stderr=true
  10. stdout_logfile=/var/log/supervisor/yourapp.log # the name of the log file.
  11. stdout_logfile_maxbytes=50MB
  12. stdout_logfile_backups=10

Further: if you're not reverse proxying and your Go application needs to bind to a port < 1024 (e.g. port 80 or 443) then use setcap - for example: setcap cap_net_bind_service=+ep /home/yourappuser/bin/yourapp

PS: I wrote a little article on how to run Go applications with Supervisor (starting from "I don't have Supervisor installed").

答案3

得分: 3

你可以使用os/user包来检查程序是否在特定用户下运行:

  1. curr, err := user.Current()
  2. // 检查错误。
  3. www, err := user.Lookup("www-data")
  4. // 检查错误。
  5. if *curr != *www {
  6. panic("Go away!")
  7. }

这并不完全符合你的要求,但它确实可以防止程序在其他用户下运行。你可以通过使用su命令以www-data用户身份运行它:

  1. su www-data -c "myserver"
英文:

You can check if the program is running under a certain user with os/user package:

  1. curr, err := user.Current()
  2. // Check err.
  3. www, err := user.Lookup(&quot;www-data&quot;)
  4. // Check err.
  5. if *curr != *www {
  6. panic(&quot;Go away!&quot;)
  7. }

This is not exactly what you want, but it does prevent it from running under any other user. You can run it as www-data by running it with su:

  1. su www-data -c &quot;myserver&quot;

答案4

得分: 1

实现这个安全的方法是通过分叉(fork)自己。

以下是一个未经测试的示例,展示了如何安全地实现 setuid:

1)确保你是 root 用户。
2)在所需的端口上进行监听(作为 root 用户)。
3)以 www-data 用户身份进行分叉。
4)接受并处理请求。

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "log"
  6. "net"
  7. "net/http"
  8. "os"
  9. "os/exec"
  10. "os/user"
  11. "strconv"
  12. "syscall"
  13. )
  14. var listenFD = flag.Int("l", 0, "listen pid")
  15. func handler(w http.ResponseWriter, req *http.Request) {
  16. u, err := user.Current()
  17. if err != nil {
  18. log.Println(err)
  19. return
  20. }
  21. fmt.Fprintf(w, "%s\n", u.Name)
  22. }
  23. func lookupUser(username string) (uid, gid int, err error) {
  24. u, err := user.Lookup(username)
  25. if err != nil {
  26. return -1, -1, err
  27. }
  28. uid, err = strconv.Atoi(u.Uid)
  29. if err != nil {
  30. return -1, -1, err
  31. }
  32. gid, err = strconv.Atoi(u.Gid)
  33. if err != nil {
  34. return -1, -1, err
  35. }
  36. return uid, gid, nil
  37. }
  38. // FDListener .
  39. type FDListener struct {
  40. file *os.File
  41. }
  42. // Accept .
  43. func (ln *FDListener) Accept() (net.Conn, error) {
  44. fd, _, err := syscall.Accept(int(*listenFD))
  45. if err != nil {
  46. return nil, err
  47. }
  48. conn, err := net.FileConn(os.NewFile(uintptr(fd), ""))
  49. if err != nil {
  50. return nil, err
  51. }
  52. return conn.(*net.TCPConn), nil
  53. }
  54. // Close .
  55. func (ln *FDListener) Close() error {
  56. return ln.file.Close()
  57. }
  58. // Addr .
  59. func (ln *FDListener) Addr() net.Addr {
  60. return nil
  61. }
  62. func start() error {
  63. u, err := user.Current()
  64. if err != nil {
  65. return err
  66. }
  67. if u.Uid != "0" && *listenFD == 0 {
  68. // we are not root and we have no listen fd. Error.
  69. return fmt.Errorf("need to run as root: %s", u.Uid)
  70. } else if u.Uid == "0" && *listenFD == 0 {
  71. // we are root and we have no listen fd. Do the listen.
  72. l, err := net.Listen("tcp", "0.0.0.0:80")
  73. if err != nil {
  74. return fmt.Errorf("Listen error: %s", err)
  75. }
  76. f, err := l.(*net.TCPListener).File()
  77. if err != nil {
  78. return err
  79. }
  80. uid, gid, err := lookupUser("guillaume")
  81. if err != nil {
  82. return err
  83. }
  84. // First extra file: fd == 3
  85. cmd := exec.Command(os.Args[0], "-l", fmt.Sprint(3))
  86. cmd.Stdin = os.Stdin
  87. cmd.Stdout = os.Stdout
  88. cmd.Stderr = os.Stderr
  89. cmd.ExtraFiles = append(cmd.ExtraFiles, f)
  90. cmd.SysProcAttr = &syscall.SysProcAttr{
  91. Credential: &syscall.Credential{
  92. Uid: uint32(uid),
  93. Gid: uint32(gid),
  94. },
  95. }
  96. if err := cmd.Run(); err != nil {
  97. return fmt.Errorf("cmd.Run error: %s", err)
  98. }
  99. return nil
  100. } else if u.Uid != "0" && *listenFD != 0 {
  101. // We are not root and we have a listen fd. Do the accept.
  102. ln := &FDListener{file: os.NewFile(uintptr(*listenFD), "net")}
  103. if err := http.Serve(ln, http.HandlerFunc(handler)); err != nil {
  104. return err
  105. }
  106. }
  107. return fmt.Errorf("setuid fail: %s, %d", u.Uid, *listenFD)
  108. }
  109. func main() {
  110. flag.Parse()
  111. if err := start(); err != nil {
  112. log.Fatal(err)
  113. }
  114. }

希望对你有帮助!

英文:

A way to achieve this safely would be to fork yourself.

This is a raw untested example on how you could achieve safe setuid:

  1. Make sure you are root
  2. Listen on the wanted port (as root)
  3. Fork as www-data user.
  4. Accept and serve requests.

http://play.golang.org/p/sT25P0KxXK

  1. package main
  2. import (
  3. &quot;flag&quot;
  4. &quot;fmt&quot;
  5. &quot;log&quot;
  6. &quot;net&quot;
  7. &quot;net/http&quot;
  8. &quot;os&quot;
  9. &quot;os/exec&quot;
  10. &quot;os/user&quot;
  11. &quot;strconv&quot;
  12. &quot;syscall&quot;
  13. )
  14. var listenFD = flag.Int(&quot;l&quot;, 0, &quot;listen pid&quot;)
  15. func handler(w http.ResponseWriter, req *http.Request) {
  16. u, err := user.Current()
  17. if err != nil {
  18. log.Println(err)
  19. return
  20. }
  21. fmt.Fprintf(w, &quot;%s\n&quot;, u.Name)
  22. }
  23. func lookupUser(username string) (uid, gid int, err error) {
  24. u, err := user.Lookup(username)
  25. if err != nil {
  26. return -1, -1, err
  27. }
  28. uid, err = strconv.Atoi(u.Uid)
  29. if err != nil {
  30. return -1, -1, err
  31. }
  32. gid, err = strconv.Atoi(u.Gid)
  33. if err != nil {
  34. return -1, -1, err
  35. }
  36. return uid, gid, nil
  37. }
  38. // FDListener .
  39. type FDListener struct {
  40. file *os.File
  41. }
  42. // Accept .
  43. func (ln *FDListener) Accept() (net.Conn, error) {
  44. fd, _, err := syscall.Accept(int(*listenFD))
  45. if err != nil {
  46. return nil, err
  47. }
  48. conn, err := net.FileConn(os.NewFile(uintptr(fd), &quot;&quot;))
  49. if err != nil {
  50. return nil, err
  51. }
  52. return conn.(*net.TCPConn), nil
  53. }
  54. // Close .
  55. func (ln *FDListener) Close() error {
  56. return ln.file.Close()
  57. }
  58. // Addr .
  59. func (ln *FDListener) Addr() net.Addr {
  60. return nil
  61. }
  62. func start() error {
  63. u, err := user.Current()
  64. if err != nil {
  65. return err
  66. }
  67. if u.Uid != &quot;0&quot; &amp;&amp; *listenFD == 0 {
  68. // we are not root and we have no listen fd. Error.
  69. return fmt.Errorf(&quot;need to run as root: %s&quot;, u.Uid)
  70. } else if u.Uid == &quot;0&quot; &amp;&amp; *listenFD == 0 {
  71. // we are root and we have no listen fd. Do the listen.
  72. l, err := net.Listen(&quot;tcp&quot;, &quot;0.0.0.0:80&quot;)
  73. if err != nil {
  74. return fmt.Errorf(&quot;Listen error: %s&quot;, err)
  75. }
  76. f, err := l.(*net.TCPListener).File()
  77. if err != nil {
  78. return err
  79. }
  80. uid, gid, err := lookupUser(&quot;guillaume&quot;)
  81. if err != nil {
  82. return err
  83. }
  84. // First extra file: fd == 3
  85. cmd := exec.Command(os.Args[0], &quot;-l&quot;, fmt.Sprint(3))
  86. cmd.Stdin = os.Stdin
  87. cmd.Stdout = os.Stdout
  88. cmd.Stderr = os.Stderr
  89. cmd.ExtraFiles = append(cmd.ExtraFiles, f)
  90. cmd.SysProcAttr = &amp;syscall.SysProcAttr{
  91. Credential: &amp;syscall.Credential{
  92. Uid: uint32(uid),
  93. Gid: uint32(gid),
  94. },
  95. }
  96. if err := cmd.Run(); err != nil {
  97. return fmt.Errorf(&quot;cmd.Run error: %s&quot;, err)
  98. }
  99. return nil
  100. } else if u.Uid != &quot;0&quot; &amp;&amp; *listenFD != 0 {
  101. // We are not root and we have a listen fd. Do the accept.
  102. ln := &amp;FDListener{file: os.NewFile(uintptr(*listenFD), &quot;net&quot;)}
  103. if err := http.Serve(ln, http.HandlerFunc(handler)); err != nil {
  104. return err
  105. }
  106. }
  107. return fmt.Errorf(&quot;setuid fail: %s, %d&quot;, u.Uid, *listenFD)
  108. }
  109. func main() {
  110. flag.Parse()
  111. if err := start(); err != nil {
  112. log.Fatal(err)
  113. }
  114. }

huangapple
  • 本文由 发表于 2015年6月19日 23:50:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/30942322.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定