[英]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 签名错误表明主机没有。
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"]
);
}
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.