O blog da AWS

Protegendo uma URL de função do AWS Lambda com Amazon CloudFront e Lambda @Edge

Por Jerome Van Der Linden, Senior Solutions Architect Builder
O URL de uma função do Lambda é um endpoint HTTPS dedicado para uma função do AWS Lambda. Quando configurado, você pode invocar a função diretamente com uma solicitação HTTP. Você pode optar por torná-lo público definindo o tipo de autenticação como NONE para uma API aberta. Ou você pode protegê-lo com o AWS IAM, definindo o tipo de autenticação como AWS_IAM. Nesse caso, somente usuários e funções autenticados podem invocar a função por meio do URL da função.

O Lambda @Edge é um recurso do Amazon CloudFront que pode executar código mais perto do usuário final de um aplicativo. Geralmente é usado para manipular solicitações HTTP recebidas ou respostas HTTP enviadas entre o cliente do usuário e a origem do aplicativo. Em particular, ele pode adicionar cabeçalhos extras à solicitação (‘Autorização’, por exemplo).

Este blog mostra como usar o CloudFront e o Lambda @Edge para proteger uma URL de função Lambda configurada com o tipo de autenticação AWS_IAM adicionando os cabeçalhos apropriados à solicitação antes que ela chegue à origem.

Visão geral

Há quatro componentes principais neste exemplo:

  • Funções do Lambda com URLs de funções ativadas: esse é o coração do “aplicativo”, as funções que contêm o código comercial exposto ao front-end. A URL da função é configurada com o tipo de autenticação AWS_IAM, para que somente usuários/funções autenticados possam invocá-lo.
  • Uma distribuição do CloudFront: o CloudFront é um serviço de rede de entrega de conteúdo (CDN) usado para entregar conteúdo aos usuários com baixa latência. Também melhora a segurança com criptografia de tráfego e proteção integrada contra DDoS. Neste exemplo, usar o CloudFront na frente da URL do Lambda pode adicionar essa camada de segurança e, potencialmente, armazenar o conteúdo em cache mais perto dos usuários.
  • Uma função Lambda na borda (at edge): o CloudFront também oferece a capacidade de executar funções do Lambda perto dos usuários: Lambda @Edge. Este exemplo faz isso para assinar a solicitação feita na URL da função Lambda e adicionar os cabeçalhos apropriados à solicitação para que a invocação da URL seja autenticada com o IAM.
  • Um aplicativo web que invoca os URLs da função Lambda: o exemplo também contém um aplicativo de página única criado com o React, a partir do qual os usuários fazem solicitações para um ou mais URLs da função Lambda. Os ativos estáticos (por exemplo, arquivos HTML e JavaScript) são armazenados no Amazon S3 e também expostos e armazenados em cache pelo CloudFront.

Este é o exemplo de arquitetura:

Architecture

O fluxo de solicitações é:

  1. O usuário realiza solicitações por meio do cliente para acessar ativos estáticos do aplicativo React ou das URLs da função Lambda.
  2. Para um ativo estático, o CloudFront o recupera do S3 ou de seu cache e o retorna ao cliente.
  3. Se a solicitação for para uma URL de função do Lambda, ela vai primeiro para um Lambda@Edge. A função Lambda@Edge tem a permissão lambda:invokeFunctionUrl na URL da função Lambda de destino e a usa para assinar a solicitação com a assinatura V4. Ele adiciona os cabeçalhos Authorization, X-Amz-Security-Token e X-Amz-Date à solicitação.
  4. Depois que a solicitação é assinada corretamente, o CloudFront a encaminha para a URL da função Lambda.
  5. O Lambda aciona a execução da função que executa qualquer tipo de lógica de negócios. A solução atual é lidar com livros (criar, obter, atualizar, excluir).
  6. O Lambda retorna a resposta da função para o CloudFront.
  7. Por fim, o CloudFront retorna a resposta ao cliente.

Há vários tipos de eventos em que uma função Lambda @Edge pode ser acionada:

Lambda@Edge events

  • Solicitação do visualizador: após o CloudFront receber uma solicitação do cliente.
  • Solicitação de origem: antes que a solicitação seja encaminhada para a origem.
  • Resposta de origem: após o CloudFront receber a resposta da origem.
  • Resposta do espectador: antes que a resposta seja enviada de volta ao cliente.

O exemplo, para atualizar a solicitação antes que ela seja enviada para a origem (a URL da função Lambda), usa o tipo “Solicitação de origem”.

Você pode encontrar o exemplo completo, baseado no AWS Cloud Development Kit (CDK), no GitHub.

Pilha (stack) de back-end

O back-end contém as diferentes funções do Lambda e os URLs das funções do Lambda. Ele usa o tipo de autenticação AWS_IAM e a definição CORS (Cross Origin Resource Sharing) ao adicionar o URL da função à função Lambda. Use um AllowedOrigins mais restritivo para um aplicativo real.

const getBookFunction = new NodejsFunction(this, 'GetBookFunction', {
    runtime: Runtime.NODEJS_18_X,  
    memorySize: 256,
    timeout: Duration.seconds(30),
    entry: path.join(__dirname, '../functions/books/books.ts'),
    environment: {
      TABLE_NAME: bookTable.tableName
    },
    handler: 'getBookHandler',
    description: 'Retrieve one book by id',
});
bookTable.grantReadData(getBookFunction);
const getBookUrl = getBookFunction.addFunctionUrl({
    authType: FunctionUrlAuthType.AWS_IAM,
    cors: {
        allowedOrigins: ['*'],
        allowedMethods: [HttpMethod.GET],
        allowedHeaders: ['*'],
        allowCredentials: true,
    }
});
JavaScript

Pilha (stack) de front-end

A pilha de front-end contém a distribuição do CloudFront e a função Lambda @Edge. Esta é a definição do Lambda @Edge:

const authFunction = new cloudfront.experimental.EdgeFunction(this, 'AuthFunctionAtEdge', {
    handler: 'auth.handler',
    runtime: Runtime.NODEJS_16_X,  
    code: Code.fromAsset(path.join(__dirname, '../functions/auth')),
 });
JavaScript

A política a seguir permite que a função Lambda @Edge assine a solicitação com a permissão apropriada e invoque os URLs da função:

authFunction.addToRolePolicy(new PolicyStatement({
    sid: 'AllowInvokeFunctionUrl',
    effect: Effect.ALLOW,
    actions: ['lambda:InvokeFunctionUrl'],
    resources: [getBookArn, getBooksArn, createBookArn, updateBookArn, deleteBookArn],
    conditions: {
        "StringEquals": {"lambda:FunctionUrlAuthType": "AWS_IAM"}
    }
}));
JavaScript

O código da função usa o AWS JavaScript SDK e, mais precisamente, a parte V4 Signature dele. Há duas coisas importantes aqui:

  • O serviço para o qual queremos assinar a solicitação: Lambda
  • As credenciais da função (com a permissãoInvokeFunctionUrl)
const request = new AWS.HttpRequest(new AWS.Endpoint(`https://${host}${path}`), region);
// ... set the headers, body and method ...
const signer = new AWS.Signers.V4(request, 'lambda', true);
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
JavaScript

Você pode obter o código completo da função aqui.

Distribuição e definição de comportamentos do CloudFront

A distribuição do CloudFront tem um comportamento padrão com uma origem S3 para os ativos estáticos do aplicativo React.

Ele também tem um comportamento por URL de função, conforme definido no código a seguir. Você pode observar a configuração da função Lambda @Edge com o tipo ORIGIN_REQUEST e o comportamento que faz referência à URL da função:

const getBehaviorOptions: AddBehaviorOptions  = {
    viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY,
    cachePolicy: CachePolicy.CACHING_DISABLED,
    originRequestPolicy: OriginRequestPolicy.CORS_CUSTOM_ORIGIN,
    responseHeadersPolicy: ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT,
    edgeLambdas: [{
        functionVersion: authFunction.currentVersion,
        eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
        includeBody: false, // GET, no body
    }],
    allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
}
this.distribution.addBehavior('/getBook/*', new HttpOrigin(Fn.select(2, Fn.split('/', getBookUrl)),), getBehaviorOptions);
JavaScript

Consideração regional

A função Lambda@Edge deve estar na região us-east-1 (Norte da Virgínia), assim como a pilha de front-end. Se você implantar a pilha de back-end em outra região, deverá passar os URLs (e ARNs) da função Lambda para o front-end. Usando um recurso personalizado no CDK, é possível criar parâmetros no AWS Systems Manager Parameter Store na região us-east-1 contendo essas informações. Para obter mais detalhes, revise o código no repositório do GitHub.

Passo a passo

Antes de implantar a solução, siga o README no repositório do GitHub e certifique-se de atender aos pré-requisitos.

Implantando a solução

  1. No diretório da solução, instale as dependências:
    npm install
    Bash
  2. Inicie a implantação da solução (pode levar até 15 minutos):
    cdk deploy --all
    Bash
  3. Depois que a implantação for bem-sucedida, as saídas contêm os URLs da função Lambda e os URLs “protegidos” por trás da distribuição do CloudFront: Outputs

Testando a solução

  1. Usando cURL, consulte o URL da função Lambda para recuperar todos os livros (getBooksFunctionUrl nas saídas do CDK):
    curl -v https://qwertyuiop1234567890.lambda-url.eu-west-1.on.aws/
    
    Bash

    Você deve obter a seguinte saída. Como esperado, é proibido acessar diretamente o URL da função Lambda sem a autenticação adequada do IAM:

    Output

  2. Agora, consulte o URL “protegido” para recuperar todos os livros (getBooksURL nas saídas do CDK):
    curl -v https://q1w2e3r4t5y6u.cloudfront.net/getBooks
    
    Bash

    Desta vez, você deve obter um OK HTTP 200 com uma lista vazia como resultado.

    Output

Os registros da função Lambda@Edge (pesquise por “AuthFunctionatEdge” no CloudWatch Logs na região mais próxima) mostram:

  • A solicitação recebida:Incoming request
  • A solicitação assinada, com os cabeçalhos adicionais (Authorization, X-Amz-Security-Token e X-Amz-Date). Esses cabeçalhos fazem a diferença quando o URL do Lambda recebe a solicitação e a valida com o IAMHeaders

Você pode testar a solução completa em todo o frontend, usando o frontendURL nas saídas do CDK.

Limpando

A função Lambda @Edge é replicada em todas as regiões em que você tem usuários. Você deve excluir as réplicas antes de excluir o restante da solução.

Para excluir os recursos implantados, execute o cdk destroy --all no diretório da solução.

Conclusão

Este blog mostra como proteger uma URL da função Lambda, configurada com autenticação do IAM, usando uma distribuição do CloudFront e o Lambda @Edge. O CloudFront ajuda a proteger contra DDoS, e a função na borda adiciona cabeçalhos apropriados à solicitação para autenticá-la para o Lambda.

Os URLs de funções do Lambda fornecem uma maneira mais simples de invocar sua função usando chamadas HTTP. No entanto, se você precisar de recursos mais avançados, como autenticação de usuário com o Amazon Cognito, validação de solicitações ou limitação de taxas, considere usar o Amazon API Gateway.

Para obter mais recursos de aprendizado sem servidor, visite Serverless Land.

 

Este artigo foi traduzido do Blog da AWS em Inglês.

 


Sobre o autor

Jerome Van Der Linden é  arquiteto de soluções sênior

 

 

 

 

Tradutor

Daniel Abib é Enterprise Solution Architect na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e segurança. Ele trabalha apoiando clientes corporativos, ajudando-os em sua jornada para a nuvem.

https://www.linkedin.com/in/danielabib/