【问题标题】:How to invoke IAM authorized AWS ApiGateway endpoint from lambda?如何从 lambda 调用 IAM 授权的 AWS ApiGateway 端点?
【发布时间】:2021-04-13 23:01:52
【问题描述】:

我正在尝试从我已使用 IAM 授权方保护的 lambda 函数调用 AWS ApiGateway HTTP 端点,但是我无法从我的 lambda 函数中获得任何东西来工作。

我已经使用 Postman 测试了端点,当我选择“AWS 签名”作为授权类型并输入我的本地凭证时,可以确认它可以工作,因此端点的设置方式不是问题。这一定是我如何从 Lambda 发送请求的问题。额外的挑战是将标头添加到 GraphQL API 请求中。

这就是我的 lambda 函数的样子:

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']);
  }
}

无论我尝试什么,我总是会收到 403 禁止错误,并且请求永远不会到达授权方背后的实际端点。我尝试删除正文,尝试将本地凭据硬编码到 aws4 调用中,但都不起作用。我的预感是我的签名调用在某种程度上是错误的,但是当我将它与我在互联网上找到的几个示例进行比较时,我看不出任何明显的错误。

任何可以为我指明正确方向的资源将不胜感激。我发现的大多数示例都是特定于前端的,所以我知道这可能会误导我。

【问题讨论】:

    标签: node.js aws-lambda aws-api-gateway amazon-iam apollo-server


    【解决方案1】:

    willSendRequest 函数不是签署请求的最佳位置,因为 apollo-server 可以在调用 willSendRequest 后修改请求对象。 相反,您应该实现自定义提取并将其传递给 RemoteGraphQLDataSource 构造函数,以确保您在发送最终请求之前对其进行签名。

    您的自定义 GraphQLDataSource 与自定义提取将是 something 像这样:

    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;
        }
    }
    

    【讨论】:

    • 非常感谢,终于搞定了!将其更改为此后,我能够通过覆盖所有标题而不是我设置的 3 来使其工作。 (请参阅我的答案以了解我所做的更改)
    【解决方案2】:

    为了繁荣,这就是我最终所做的并且完美无瑕的工作(非常感谢 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);
    }
    

    【讨论】:

    • 对于发现此问题的任何人的另一个提示是,始终使用通配符 IAM 策略进行测试,然后在运行后使其更加严格。我想我可能会因为使用我认为是有效的资源限定符而把事情搞砸了。
    猜你喜欢
    • 1970-01-01
    • 2017-07-10
    • 2017-05-20
    • 2019-08-29
    • 2021-05-05
    • 1970-01-01
    • 2021-09-23
    • 2015-12-26
    • 1970-01-01
    相关资源
    最近更新 更多