为了账号安全,请及时绑定邮箱和手机立即绑定

是否可以在不同的用户下运行 goroutine 或 go 方法?

是否可以在不同的用户下运行 goroutine 或 go 方法?

Go
HUWWW 2023-06-19 17:36:27
我在一个小型网络服务器上工作,该服务器提供文件并提供对每个用户主目录的访问。如果源在 CI 中,则可以选择在不同线程下回答每个请求,并确保每个线程都以调用者的用户作为其用户运行。有什么方法可以实现与 Go 中类似的东西吗?理想情况下,处理请求的代码部分、goroutine 或被调用的方法应该在调用者的用户帐户下运行。我做了一些研究,似乎在 Go 中我们可以将单个 goroutine 粘贴到当前线程,但我看不出如何创建一个新线程然后将 goroutine 附加到该线程。
查看完整描述

3 回答

?
慕田峪7331174

TA贡献1828条经验 获得超13个赞

不可能以不同的用户身份运行 goroutine 或方法,因为它们都在与父进程相同的上下文中运行。Goroutines 相当于绿色线程,甚至不一定在每个例程中产生适当的 OS 线程。

这个答案也可能取决于操作系统,但我认为这也不适用于 Windows。

查看完整回答
反对 回复 2023-06-19
?
千巷猫影

TA贡献1829条经验 获得超7个赞

是的,您可以使用 Linux 系统调用setuid(不是内置函数setuid)来做到这一点。我刚刚发现了这个问题并认为它必须是可能的,因为我也在其他编程语言中使用它。所以我解决了我的问题,并想报告如何做到这一点。

但是,SJP 写的关于线程的内容是正确的,并且正是我的问题的答案,但由于线程问题,它不会解决您的问题。

但是回到代码……您需要调用LockOSThread以将当前的 go 例程修复到您当前正在执行的线程中,并且您可以使用 syscall 更改上下文setuid

这是 Linux 的工作示例:

package main


import (

        "fmt"

        "log"

        "os"

        "runtime"

        "sync"

        "syscall"

        "time"


)


func printUID() {

        fmt.Printf("Real UID: %d\n", syscall.Getuid())

        fmt.Printf("Effective UID: %d\n", syscall.Geteuid())

}


func main() {

        printUID()

        var wg sync.WaitGroup

        wg.Add(2)


        go func(wg *sync.WaitGroup) {

                defer wg.Done()

                time.Sleep(2 * time.Second)

                printUID()

        }(&wg)


        go func(wg *sync.WaitGroup) {

                runtime.LockOSThread()

                defer runtime.UnlockOSThread()

                defer wg.Done()


                _, _, serr := syscall.Syscall(syscall.SYS_SETUID, 1, 0, 0)

                if serr != 0 {

                        log.Fatal(serr)

                        os.Exit(1)

                }

                printUID()


        }(&wg)

        wg.Wait()

        printUID()

}

operation not supported如果您使用,您将收到syscall.Setuid:


serr := syscall.Setuid(1)

代替


_, _, serr := syscall.Syscall(syscall.SYS_SETUID, 1, 0, 0)


查看完整回答
反对 回复 2023-06-19
?
侃侃无极

TA贡献2051条经验 获得超10个赞

我没有足够的声誉来真正评论那个。希望这提供了更多完整的工作示例,并且重要的是,演示了保持运行时不会混淆使用不同 UID 运行的线程。]

首先,严格按照您的要求进行操作需要一些技巧,而且并不是那么安全...

[Go 喜欢使用POSIX 语义进行操作,而您想做的是通过在单个进程中同时使用两个或多个 UID 进行操作来打破 POSIX 语义。Go 需要 POSIX 语义,因为它在任何可用的线程上运行 goroutines,并且运行时需要它们都表现相同才能可靠地工作。由于 Linux 的setuid()系统调用不支持 POSIX 语义,Go 选择不实现 syscall.Setuid()直到最近在 go1.16 中使用POSIX 语义实现它成为可能。

  • 请注意,glibc如果您调用setuid(),系统调用本身会使用修复机制 ( glibc/nptl/setxid ) 进行包装,并且会同时更改程序中所有线程的 UID 值。因此,即使在 C 中,您也必须进行一些修改才能解决此细节。]

话虽这么说,你可以让 goroutines 以你想要的方式工作runtime.LockOSThread(),但不要在每次专门使用后立即丢弃锁定的线程来混淆 Go 运行时。

像这样的东西(称之为uidserve.go):

// Program uidserve serves content as different uids. This is adapted

// from the https://golang.org/pkg/net/http/#ListenAndServe example.

package main


import (

        "fmt"

        "log"

        "net/http"

        "runtime"

        "syscall"

)


// Simple username to uid mapping.

var prefixUIDs = map[string]uintptr{

        "apple":  100,

        "banana": 101,

        "cherry": 102,

}


type uidRunner struct {

        uid uintptr

}


func (u *uidRunner) ServeHTTP(w http.ResponseWriter, r *http.Request) {

        runtime.LockOSThread()

        // Note, we never runtime.UnlockOSThread().

        if _, _, e := syscall.RawSyscall(syscall.SYS_SETUID, u.uid, 0, 0); e != 0 {

                http.Error(w, "permission problem", http.StatusInternalServerError)

                return

        }

        fmt.Fprintf(w, "query %q executing as UID=%d\n", r.URL.Path, syscall.Getuid())

}


func main() {

        for u, uid := range prefixUIDs {

                h := &uidRunner{uid: uid}

                http.Handle(fmt.Sprint("/", u, "/"), h)

        }

        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

                fmt.Fprintf(w, "general query %q executing as UID=%d\n", r.URL.Path, syscall.Getuid())

        })

        log.Fatal(http.ListenAndServe(":8080", nil))

}

像这样构建它:


$ go build uidserve.go

接下来,要让它工作,您必须授予该程序一些特权。那就是做一个或另一个(是libcap 套件setcap中的一个工具):


$ sudo /sbin/setcap cap_setuid=ep ./uidserve

或者,更传统的运行setuid-root的方式:


$ sudo chown root ./uidserve

$ sudo chmod +s ./uidserve

现在,如果您运行./uidserve并连接到您的浏览器,localhost:8080您可以尝试获取以下 URL:

  • localhost:8080/something在这里显示类似general query "/something" executing as UID=您的 UID 的内容。

  • localhost:8080/apple/pie这显示了类似的东西query "/apple/pie" executing as UID=100

  • ETC。

希望这有助于说明如何按照您的要求进行操作。[但是,由于它涉及很多技巧,所以我不建议真正这样做……]


查看完整回答
反对 回复 2023-06-19
  • 3 回答
  • 0 关注
  • 127 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信