简体   繁体   English

使用 Go 进行 IAM 身份验证的 API Gateway HTTP 客户端请求

[英]API Gateway HTTP client request with IAM auth with Go

Hello StackOverflow AWS Gophers,你好 StackOverflow AWS Gophers,

I'm implementing a CLI with the excellent cobra/viper packages from spf13 .我正在使用spf13 中出色的cobra/viper 软件包实现CLI。 We have an Athena database fronted by an API Gateway endpoint, which authenticates with IAM.我们有一个以 API 网关端点为前端的 Athena 数据库,该端点通过 IAM 进行身份验证。

That is, in order to interact with its endpoints by using Postman, I have to define AWS Signature as Authorization method, define the corresponding AWS id/secret and then in the Headers there will be X-Amz-Security-Token and others.也就是说,为了使用 Postman 与其端点进行交互,我必须将AWS Signature定义为 Authorization 方法,定义相应的 AWS id/secret,然后在 Headers 中会有X-Amz-Security-Token等。 Nothing unusual, works as expected.没有异常,按预期工作。

Since I'm new to Go, I was a bit shocked to see that there are no examples to do this simple HTTP GET request with the aws-sdk-go itself... I'm trying to use the shared credentials provider ( ~/.aws/credentials ), as demonstrated for the S3 client Go code snippets from re:Invent 2015 :由于我是 Go 新手,看到没有示例可以使用aws-sdk-go本身执行这个简单的 HTTP GET 请求,我感到有点震惊……我正在尝试使用共享凭据提供程序( ~/.aws/credentials ),如来自 re:Invent 2015的 S3 客户端Go 代码片段所示

req := request.New(nil)

How can I accomplish this seemingly easy feat in 2019 without having to resort to self-cooked net/http and therefore having to manually read ~/.aws/credentials or worse, go with os.Getenv and other ugly hacks?我如何才能在 2019 年完成这个看似简单的壮举,而不必求助于自煮net/http ,因此必须手动读取~/.aws/credentials或更糟,使用os.Getenv和其他丑陋的黑客?

Any Go code samples interacting as client would be super helpful.任何作为客户端交互的 Go 代码示例都会非常有帮助。 No Golang Lambda/server examples, please, there's plenty of those out there.请不要提供 Golang Lambda/服务器示例,那里有很多示例。

The solution below uses aws-sdk-go-v2 https://github.com/aws/aws-sdk-go-v2下面的解决方案使用 aws-sdk-go-v2 https://github.com/aws/aws-sdk-go-v2

// A AWS SDK session is created because the HTTP API is secured using a
// IAM authorizer. As such, we need AWS client credentials and a
// session to properly sign the request.
cfg, err := external.LoadDefaultAWSConfig(
    external.WithSharedConfigProfile(profile),
)
if err != nil {
    fmt.Println("unable to create an AWS session for the provided profile")
    return
}


req, _ := http.NewRequest(http.MethodGet, "", nil)
req = req.WithContext(ctx)
signer := v4.NewSigner(cfg.Credentials)
_, err = signer.Sign(req, nil, "execute-api", cfg.Region, time.Now())
if err != nil {
    fmt.Printf("failed to sign request: (%v)\n", err)
    return
}

res, err := http.DefaultClient.Do(req)
if err != nil {
    fmt.Printf("failed to call remote service: (%v)\n", err)
    return
}

defer res.Body.Close()
if res.StatusCode != 200 {
    fmt.Printf("service returned a status not 200: (%d)\n", res.StatusCode)
    return
}

Unfortunately, it seems that the library has been updated since the accepted answer was written and the solution no longer is the same.不幸的是,自接受的答案编写以来,该库似乎已更新,并且解决方案不再相同。 After some trial and error, this appears to be the more current method of handling the signing (using https://pkg.go.dev/github.com/aws/aws-sdk-go-v2 ):经过一些试验和错误,这似乎是处理签名的最新方法(使用https://pkg.go.dev/github.com/aws/aws-sdk-go-v2 ):

import (
    "context"
    "net/http"
    "time"

    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
)

func main() {
    // Context is not being used in this example.
    cfg, err := config.LoadDefaultConfig(context.TODO())

    if err != nil {
        // Handle error.
    }

    credentials, err := cfg.Credentials.Retrieve(context.TODO())

    if err != nil {
        // Handle error.
    }

    // The signer requires a payload hash. This hash is for an empty payload.
    hash := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    req, _ := http.NewRequest(http.MethodGet, "api-gw-url", nil)
    signer := v4.NewSigner()
    err = signer.SignHTTP(context.TODO(), credentials, req, hash, "execute-api", cfg.Region, time.Now())

    if err != nil {
        // Handle error.
    }

    // Use `req`
}

The first argument to request.New is aws.Config , where you can send credentials. request.New的第一个参数是aws.Config ,您可以在其中发送凭据。

https://github.com/aws/aws-sdk-go/blob/master/aws/request/request.go#L99 https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config https://github.com/aws/aws-sdk-go/blob/master/aws/request/request.go#L99 https://docs.aws.amazon.com/sdk-for-go/api/aws /#配置

There are multiple ways to create credentials object: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html有多种方法可以创建凭证对象: https : //docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html

For example using static values:例如使用静态值:

creds:= credentials.NewStaticCredentials("AKID", "SECRET_KEY", "TOKEN")
req := request.New(aws.Config{Credentials: creds}, ...)

I'm pretty new to go myself (3rd day learning go) but from watching the video you posted with the S3 example and reading through the source code (for the s3 service and request module) here is my understanding (which I'm hoping helps).我自己是新手(学习第 3 天),但是通过观看您发布的 S3 示例视频并阅读源代码(对于 s3 服务和请求模块),这是我的理解(我希望帮助)。

If you look at the code for the s3.New() function aws-sdk-go/service/s3/service.go如果您查看 s3.New() 函数的代码aws-sdk-go/service/s3/service.go

func New(p client.ConfigProvider, cfgs ...*aws.Config) *S3 {
c := p.ClientConfig(EndpointsID, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, .SigningName) }

As opposed to request.New() function aws-sdk-go/aws/request/request.go与 request.New() 函数相反aws-sdk-go/aws/request/request.go

func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request { ...

As you can see in the s3 scenario the *aws.Config struct is a pointer, and so is probably initialized / populated elsewhere.正如您在 s3 场景中看到的, *aws.Config 结构是一个指针,因此可能在其他地方初始化/填充。 As opposed to the request function where the aws.Config is a parameter.与 aws.Config 是参数的请求函数相反。 So I am guessing the request module is probably a very low level module which doesn't get the shared credentials automatically.所以我猜测请求模块可能是一个非常低级的模块,它不会自动获取共享凭据。

Now, seeing as you will be interacting with API gateway I had a look at that service specifically to see if there was something similar.现在,当您将与 API 网关交互时,我专门查看了该服务以查看是否有类似的东西。 I looked at aws-sdk-go/service/apigateway/service.go我看了aws-sdk-go/service/apigateway/service.go

func New(p client.ConfigProvider, cfgs ...*aws.Config) *APIGateway {
c := p.ClientConfig(EndpointsID, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, c.SigningName) }...

Which looks pretty much the same as the s3 client, so perhaps try using that and see how you go?它看起来与 s3 客户端几乎一样,所以也许尝试使用它,看看你怎么做?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM