简体   繁体   English

如何从 lambda 调用 IAM 授权的 AWS ApiGateway 端点?

[英]How to invoke IAM authorized AWS ApiGateway endpoint from lambda?

I'm trying to invoke an AWS ApiGateway HTTP endpoint from a lambda function that I've secured using an IAM authorizer, however I cannot for the life of me get anything from my lambda function to work.我正在尝试从我使用 IAM 授权方保护的 lambda function 调用 AWS ApiGateway HTTP 端点,但是我终生无法从我的 lambda function 获得任何工作。

I've tested the endpoint with Postman, and can confirm that it works when I select "AWS Signature" as the Authorization type and put in my local credentials, so it's not an issue with how the endpoint is set up.我已经使用 Postman 测试了端点,并且可以确认当我将 select“AWS 签名”作为授权类型并输入我的本地凭据时它可以工作,因此端点的设置方式不是问题。 It must be an issue with how I am sending the request from Lambda. The additional challenge is adding the headers to a GraphQL API request.这一定是我如何从 Lambda 发送请求的问题。额外的挑战是将标头添加到 GraphQL API 请求。

This is what my lambda function looks like:这是我的 lambda function 的样子:

import { ApolloServer } from 'apollo-server-lambda';
import { APIGatewayProxyEvent, Callback, Context } from 'aws-lambda';
import { ApolloGateway, RemoteGraphQLDataSource } from '@apollo/gateway';
import aws4 from 'aws4';

const userServiceUrl = process.env.USER_SERVICE_URL;
const {hostname, pathname} = new URL(userServiceUrl);

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  willSendRequest({request}) {
    console.log('request is: ', request);
    const opts: Record<string, any> = {
      service: 'execute-api',
      region: process.env.AWS_REGION,
      host: hostname,
      path: pathname,
      body: JSON.stringify({query: request.query}),
      method: 'POST'
    }
    aws4.sign(opts);
    console.log('opts are: ', opts);
    request.http.headers.set('X-Amz-Date', opts.headers['X-Amz-Date']);
    request.http.headers.set('Authorization', opts.headers['Authorization']);
    request.http.headers.set('X-Amz-Security-Token', opts.headers['X-Amz-Security-Token']);
  }
}

No matter what I try I always get a 403 forbidden error and the request never makes it to the actual endpoint behind the authorizer.无论我尝试什么,我总是会收到 403 禁止错误,并且请求永远不会到达授权方后面的实际端点。 I've tried removing body, I've tried hardcoding my local credentials into the aws4 call, none of it works.我试过删除正文,我试过将我的本地凭据硬编码到 aws4 调用中,但都不起作用。 My hunch is that my signature call is wrong somehow, but when I compare it to the few examples I've found on the inte.net I can't see anything obviously wrong.我的直觉是我的签名调用不知何故是错误的,但是当我将它与我在 inte.net 上找到的几个示例进行比较时,我看不出有什么明显的错误。

Any resources that could point me in the right direction would be greatly appreciated.任何可以为我指明正确方向的资源都将不胜感激。 Most of the examples I find are front end specific so I know that could possibly be steering me wrong.我发现的大多数示例都是特定于前端的,所以我知道这可能会误导我。

The willSendRequest function is not the best place to sign the request as apollo-server can modify the request object after willSendRequest is invoked. willSendRequest function 不是签署请求的最佳位置,因为 apollo-server 可以在调用willSendRequest后修改请求 object。 Instead you should implement a custom fetch and pass it to the RemoteGraphQLDataSource constructor to ensure that you are signing the final request before it is sent.相反,您应该实现自定义提取并将其传递给RemoteGraphQLDataSource构造函数,以确保您在发送最终请求之前对其进行签名。

your custom GraphQLDataSource with custom fetch would be something like this:您的自定义GraphQLDataSource与自定义提取将是这样的:

import { Request, RequestInit, Response, fetch, Headers } from "apollo-server-env";
import aws4 from 'aws4';
import { RemoteGraphQLDataSource } from '@apollo/gateway';

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
    public constructor(
        url: string,
    ) {
        super({
            url: url,
            fetcher: doFetch,
        });
    }

    async doFetch(
        input?: string | Request | undefined,
        init?: RequestInit | undefined
    ): Promise<Response> {
        const url = new URL(input as string);
        const opts: Record<string, any> = {
            service: 'execute-api',
            region: process.env.AWS_REGION,
            host: url.hostname,
            path: url.pathname,
            body: init?.body,
            method: init?.method
        }
        aws4.sign(opts);
        init.headers.set('X-Amz-Date', opts.headers['X-Amz-Date']);
        init.headers.set('Authorization', opts.headers['Authorization']);
        init.headers.set('X-Amz-Security-Token', opts.headers['X-Amz-Security-Token']);
        const response = await fetch(input, init);

        return response;
    }
}

For prosperity, this is what I ended up doing and works flawlessly (Thank you so much Glen Thomas!)为了繁荣,这就是我最终所做的并且完美无瑕的工作(非常感谢格伦托马斯!)

import { Request, RequestInit, Response, fetch } from "apollo-server-env";
import { ApolloServer } from 'apollo-server-lambda';
import { APIGatewayProxyEvent, Callback, Context } from 'aws-lambda';
import { ApolloGateway, RemoteGraphQLDataSource } from '@apollo/gateway';
import aws4 from 'aws4';

const userServiceUrl = process.env.USER_SERVICE_URL;

async function doFetch(
  input?: Request | string,
  init?: RequestInit
): Promise<Response> {
  const urlString = typeof input === 'string' ? input : input.url;
  const url = new URL(urlString);
  const opts: Record<string, any> = {
    service: 'execute-api',
    region: process.env.AWS_REGION,
    host: url.hostname,
    path: url.pathname,
    body: init?.body,
    method: init?.method
  }
  aws4.sign(opts);
  init.headers = opts.headers;
  const response = await fetch(input, init);

  return response;
}

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  constructor(url: string) {
    super({
      url,
      fetcher: doFetch
    })
  }
}

const server = new ApolloServer({
  gateway: new ApolloGateway({
    serviceList: [
      { name: 'users', url: userServiceUrl }
    ],
    buildService({url}) {
      return new AuthenticatedDataSource(url)
    }
  }),
  subscriptions: false,
  introspection: true,
  playground: true,
});

export const handler = (event: APIGatewayProxyEvent, context: Context, callback: Callback) => {
  console.log('event is: ', JSON.stringify(event, null, 2))
  
  return server.createHandler({
    cors: {
      origin: '*'
    }
  })(event, context, callback);
}

Actually, willSendRequest works fine, and is preferred for simplicity.实际上, willSendRequest工作正常,并且为简单起见是首选。 For cases you want to sign the request for specific data sources with an easy setup.对于您希望通过简单设置对特定数据源的请求进行签名的情况。

The reason why the documented attempts failed is because the request signing needs to be exact: Path must contain both the path & query parameters.记录的尝试失败的原因是因为请求签名需要准确:路径必须同时包含路径和查询参数。 https is also stripped as the corresponding AWS signature error indicated the host to not have one. https也被剥离,因为相应的 AWS 签名错误表明主机没有。

GET request获取请求

  willSendRequest(request: RequestOptions) {
    const query = request.params ? `?${request.params.toString()}` : "";
    const opts: Record<string, any> = {
      service: "execute-api",
      region: process.env.AWS_REGION,
      host: this.baseURL?.replace("https://", ""),
      path: request.path + query,
      method: request.method,
    };

    sign(opts);

    request.headers.set("X-Amz-Date", opts.headers["X-Amz-Date"]);
    request.headers.set("Authorization", opts.headers["Authorization"]);
    request.headers.set(
      "X-Amz-Security-Token",
      opts.headers["X-Amz-Security-Token"]
    );
  }

POST Request POST请求

Fragment example, from another JS lambda (not apollo-graphql) having a body;片段示例,来自另一个具有主体的 JS lambda(不是 apollo-graphql); not sure if apollo automatically decodes the body, regardless it's a good example to showcase.不确定阿波罗是否会自动解码身体,不管它是一个很好的展示例子。

  if (Object.hasOwnProperty.call(request, 'body')) {
    const { data, encoding } = request.body;
    const buffer = Buffer.from(data, encoding);
    const decodedBody = buffer.toString('utf8');

    if (decodedBody !== '') {
      normalisedRequest.body = decodedBody;
      normalisedRequest.headers = { 'content-type': request.headers['content-type'][0].value };
    }
  }

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

相关问题 AWS IAM Lambda“无权执行:lambda:GetFunction” - AWS IAM Lambda "is not authorized to perform: lambda:GetFunction" 如何从 aws 中的 lambda function 调用对 a.network load balancer(nlb) vpc 端点的请求 - how to invoke a request to a network load balancer(nlb) vpc endpoint from a lambda function in aws 如何从 SAM 本地中的另一个 lambda 调用 AWS lambda? - How to invoke AWS lambda from another lambda within SAM local? 向 AWS APIGateway API 发出请求(具有 IAM 权限) - Making a request to AWS APIGateway API (with IAM permissions) 如何从 AWS ApiGateway 返回 stream? - How to return a stream from AWS ApiGateway? AWS IAM - 从 Lambda 到 DynamoDB 的访问被拒绝 - AWS IAM - Access Denied from Lambda to DynamoDB 如何在没有 lambda 的情况下访问/调用 sagemaker 端点? - How to access/invoke a sagemaker endpoint without lambda? 在 AWS 上通过 lambda function 调用 tensorflow 端点推理的问题 - Issue with Invoke of tensorflow endpoint inference via lambda function on AWS 如何从 laravel 调用现有的 AWS Lambda function? - How to invoke an existing AWS Lambda function from laravel? 如何在 aws sagemaker 中调用无服务器端点? - How to invoke a serverless endpoint in aws sagemaker?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM