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

Posted

技术标签:

【中文标题】如何从 lambda 调用 IAM 授权的 AWS ApiGateway 端点?【英文标题】:How to invoke IAM authorized AWS ApiGateway endpoint from lambda? 【发布时间】: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 调用中,但都不起作用。我的预感是我的签名调用在某种程度上是错误的,但是当我将它与我在互联网上找到的几个示例进行比较时,我看不出任何明显的错误。

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

【问题讨论】:

【参考方案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 策略进行测试,然后在运行后使其更加严格。我想我可能会因为使用我认为是有效的资源限定符而把事情搞砸了。

以上是关于如何从 lambda 调用 IAM 授权的 AWS ApiGateway 端点?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 IAM 在 AWS Lambda 中调用 AppSync?

AWS Lambda:即使在STS:AssumeRole成功之后,lambda函数仍然使用旧的IAM角色

AWS Lambda 无法调用 Cognito Identity - IAM 角色

使用授权方后 AWS Amplify API Gateway cors 错误:aws_iam

从java lambda调用aws Step函数

即使 IAM 角色具有完整的 Redshift 权限,AWS Lambda 在调用 Redshift 的“CreateCluster”操作时也会出现“拒绝访问”错误