【问题标题】:Operation not permitted when executing an arbitrary binary执行任意二进制文件时不允许操作
【发布时间】:2021-07-28 20:30:45
【问题描述】:

在Running arbitrary binary,AWS 解释说:

包含您自己的可执行文件很容易;只需将它们打包到您上传的 ZIP 文件中,然后在您从 Node.js 或您之前启动的其他进程中调用它们时引用它们(包括您创建的 ZIP 文件中的相对路径)。确保在函数代码的开头包含以下内容: process.env['PATH'] = process.env['PATH'] + ':' + process.env['LAMBDA_TASK_ROOT'] 您可以使用所有常见的进程间通信形式以及 /tmp 中的文件与您创建的任何进程进行通信。

我想在我的代码中使用 Go Terraform 库 tfexec,但我总是收到 Permission denied。

main/tf中的代码:

package main

import (
    "context"
    "fmt"
    "os"
    "path/filepath"
    "runtime"
    "strings"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/hashicorp/terraform-exec/tfexec"
)

func Start() error {
    fmt.Printf("Go version: %s\n", runtime.Version())
    fmt.Println()

    // set environment variable, cf https://aws.amazon.com/blogs/compute/running-executables-in-aws-lambda/
    newPath := []string{
        os.Getenv("PATH"),
        ":",
        os.Getenv("LAMBDA_TASK_ROOT"),
    }
    os.Setenv("PATH", strings.Join(newPath, ""))

    // Get path to binary
    fmt.Println("Start terraform")
    currDir, err := os.Getwd()
    if err != nil {
        return err
    }
    tfBinary := filepath.Join(currDir, "terraform")

    stat, err := os.Stat(tfBinary)
    if err != nil {
        return err
    }
    fmt.Printf("Stat %s: ", tfBinary)
    fmt.Println(stat.Mode())

    // Start new instance of terraform
    tf, err := tfexec.NewTerraform(currDir, tfBinary)
    if err != nil {
        return err
    }

    tf.SetStdout(os.Stdout)
    tf.SetStderr(os.Stderr)

    // run terraform init
    fmt.Println("Tf init")
    if err = tf.Init(context.Background()); err != nil {
        return err
    }

    return nil
}

func main() {
    // Start()
    lambda.Start(Start)
}

设置:

curl -X GET -o terraform https://releases.hashicorp.com/terraform/0.12.29/terraform_0.12.29_linux_amd64.zip
chmod 755 terraform
GOOS=linux GOARCH=amd64 go build create-vpc-tf.go
zip zip.zip terraform main
# Create Lambda running on go1.x with `main` as handler

结果:

START RequestId: f23e4122-3595-48ef-808f-ef7951531f59 Version: $LATEST
Go version: go1.16

Start terraform
Stat /var/task/terraform: -rwxr-xr-x
Tf init
fork/exec /var/task/terraform: operation not permitted
fork/exec /var/task/terraform: operation not permitted: PathError
null
END RequestId: f23e4122-3595-48ef-808f-ef7951531f59
REPORT RequestId: f23e4122-3595-48ef-808f-ef7951531f59  Duration: 2.26 ms   Billed Duration: 3 ms   Memory Size: 512 MB Max Memory Used: 32 MB  Init Duration: 83.85 ms 

这在本地按预期工作(我没有提供 *.tf 文件,因此 terraform 正确报告 Terraform initialized in an empty directory!。

【问题讨论】:

  • 在 Lambda 中,/var/task 是只读的。所以terraform init 会失败,因为它无法写入.terraform 目录。但是由于您的错误消息表明 fork 或 exec 有问题,所以这似乎不是问题。
  • 我发现一个错误可能会给您带来一些问题... :)
  • ...但是我无法解释它是如何在本地为您工作的,如果这真的是您运行的...
  • 提示 - 将此添加到您的脚本中:file terraform
  • 这就是我认为我看到的。您已下载 terraform_0.12.29_linux_amd64.zip,然后尝试执行该 .zip 文件。它需要在某个时候解压缩。我没有看到任何迹象表明 NewTerraform 不需要原始可执行文件:github.com/hashicorp/terraform-exec/blob/main/tfexec/…

标签: amazon-web-services go aws-lambda terraform


【解决方案1】:

此问题与 tfexec 库及其在 AWS lambda / Firecracker 环境中的可操作性有关。 Firecracker 环境中不允许 fork 进程。

Pdeathsig 强制分叉,因此不能在 AWS lambda 中使用。

Terraform 本身不使用它,但 tfexec 使用它,here。

快速谷歌显示其他项目也遇到了同样的问题,例如here。

我们打包了here 所示的修复程序,以防止设置Pdeathsig。这是对tfexec 库的直接更改,希望它们能在上游吸收。同时,我们将tfexec 直接与go mod 打包在我们的vendor 目录中,并直接在那里进行了更改。效果很好。

go mod init [repo]
go mod download
go mod vendor

更新 vendor/github.com/hashicorp/terraform-exec/tfexec/cmd_linux.go如下:


if _, ok := os.LookupEnv("LAMBDA_TASK_ROOT"); !ok {
    cmd.SysProcAttr = &syscall.SysProcAttr{
        // kill children if parent is dead
        Pdeathsig: syscall.SIGKILL,
        // set process group ID
        Setpgid: true,
    }
}

重建您的项目:go1.16 build main.go。

重新打包并上传到您的 lambda。

【讨论】:

    【解决方案2】:

    您已将 Terraform 二进制文件下载为 zip 文件,因此它不是可以执行的格式。

    在使用zip 重新打包之前,您需要对其进行解压缩。

    替换:

    curl -X GET -o terraform https://releases.hashicorp.com/terraform/0.12.29/terraform_0.12.29_linux_amd64.zip
    

    与:

    curl https://releases.hashicorp.com/terraform/0.12.29/terraform_0.12.29_linux_amd64.zip |zcat >terraform
    

    当您在本地运行时,您应该会看到如下内容:

    您的 Terraform 版本已过期!

    由于问题中使用的the version 是......过时了。

    如果您没有看到与此相关的消息,则表明您的本地测试实际上并未使用您在所示设置中下载的文件。


    下载 Terraform 二进制文件的更安全方式

    或者,您可以使用tfinstall 下载并在运行时隐式解压缩最新版本,如this example 所示。

    execPath, err := tfinstall.Find(context.Background(), tfinstall.LatestVersion(tmpDir, false))
    if err != nil {
        log.Fatalf("error locating Terraform binary: %s", err)
    }
    

    此方法使用 Hashicorp 的自定义下载器go-getter,它同时执行verifies the checksum 和执行streaming decompression。 tfinstall 甚至使用 Hashicorp 的公钥到 verify 校验和文件本身没有被篡改。

    如果您想避免每次运行时都下载 terraform,您可以在本地使用 tfinstall,并捕获结果以供重复使用(基本上只是替换您设置中的 curl)。

    【讨论】:

    • 最好在下载后验证校验和,如下所述:terraform.io/downloads.html,但您需要在解压缩之前执行此操作。
    • 试图按照gpg 指示的here 指示执行操作让我发疯。
    • 您可以tfinstall 直接获取二进制文件并验证其校验和。我最初是这样做的,但改变了我的方法,因为它导致了与帖子中所示相同的问题。我认为/tmp 文件夹上可能有一个noexec 选项,出于安全原因这是有意义的(/tmp 是唯一可写的文件夹,因此它是唯一可以下载它的地方)。我选择参考 AWS 文档,该文档建议直接打包您的二进制文件。 aws.amazon.com/blogs/compute/running-executables-in-aws-lambda
    • @Pauline,关于/tmp 文件夹:您可以从那里执行——我通过使用exec.Command(execPath,"init").Run() 运行下载的二进制文件进行了验证。我还发现,正如您的回答所述,您可以在调用 Run() 之前通过在 SysProcAttr 中设置 Pdeathsig: syscall.SIGKILL 来解决此问题。
    • ...但是,是的,我的回答只解决了问题中提出的问题的部分。 :)
    猜你喜欢
    • 2017-12-17
    • 1970-01-01
    • 2022-01-18
    • 2018-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-08
    • 2021-09-12
    相关资源
    最近更新 更多