O blog da AWS

Uso de IAM Roles no desenvolvimento de aplicações na AWS

Por Felipe Bortoletto, Arquiteto de Soluções na AWS e
Flavio de Moraes, Arquiteto de Soluções na AWS

 

Programando na AWS sem utilizar senhas no código

Conforme as aplicações crescem elas precisam cada vez mais se conectar a outras tecnologias. No ambiente dos datacenters é comum inserir as credenciais de acesso programático dentro do código das aplicações para possibilitar essas conexões. Essa prática era um risco aceitável dado as limitações de ambientes On-Premises, uma vez que essas credenciais tinham seu uso limitado, por exemplo, por quem estava dentro da rede interna da empresa, o que dificultava sua utilização indevida.

As credenciais de acesso programático utilizadas na nuvem são versáteis, permitindo que o acesso seja realizado de qualquer lugar, desde que se tenha a Chave de Acesso (Access Key) e a Chave de Acesso Secreta (Secret Access Key) da credencial.

Da mesma maneira que essa versatilidade facilita o acesso, ela pode aumentar o risco de um evento de segurança ocorrer, se as credenciais não forem bem administradas e protegidas. Uma possibilidade é um código ser publicado em um GIT público e conter alguma chave, permitindo que qualquer pessoa que a obtenha possa efetuar as ações que o seu proprietário tiver permissão. Outro caso possível é um colaborador sair da empresa e levar uma cópia do código em uma mídia externa, como um e-mail ou pen drive, e compartilhá-lo com pessoas indevidas.

Nesse blog post explicaremos algumas das melhores práticas para você proteger suas chaves de acesso: não inserir as chaves no seu código para acessar outros serviços.

 

O Conceito de IAM Roles

Mas como acessamos os recursos na AWS sem o uso de credenciais de acesso no nosso código e de forma segura?

A resposta dessa pergunta passa pelo uso de IAM Roles. Uma IAM Role é uma identidade que pode ser criada no AWS Identity and Access Management (AWS IAM) e ter permissões atribuídas a ela diretamente ou via políticas do IAM.

Porém, ao contrário de usuários, uma IAM Role não possui senha pois ela é assumida em tempo de execução por outros recursos, como  instâncias do Amazon Elastic Compute Cloud (Amazon EC2) ou funções do AWS Lambda.

Como exemplo veremos neste blogpost o nosso código em execução em uma instância do Amazon EC2.

No momento do consumo do serviço da AWS através do SDK (Software Development Kit) é identificado que não foi informada uma Access Key, mas que o recurso que executa o código possui a permissão de assumir a role que tem os acessos ao serviço.

Nesse momento, o recurso, também chamado de agente, assume a IAM Role e uma credencial temporária é gerada. Credenciais temporárias são fornecidas pela função AWS Secure Token Service (STS) do AWS IAM. O STS fornece tokens temporários para os agentes, permitindo que eles acessem outros recursos e serviços na AWS.

A permissão fornecida pelo token segue as políticas da role que está anexada ao agente que está fazendo a chamada. O token por padrão tem a validade de 1 hora, porém pode ser alterado entre 15 minutos até a sessão ser encerrada, quando o IAM se encarrega da renovação automática.

A renovação do token é uma rotação de credencial e novos tokens são fornecidos sempre que uma sessão é iniciada, reduzindo o trabalho operacional para a rotação das chaves de acesso.

Dessa forma a aplicação acessará o recurso na AWS de forma segura, mas sem o uso de senhas no código ou em arquivos de configuração.

 

Desenvolvendo uma aplicação utilizando IAM Roles

Agora que os conceitos e melhores práticas foram revistos, vamos colocá-los no desenvolvimento de uma aplicação. Durante esse exercício vamos passar pelos seguintes passos:

  • Preparação da infraestrutura;
  • Desenvolvimento do código localmente;
  • Execução do código na AWS.

 

Preparação da infraestrutura

O primeiro passo será o provisionamento do ambiente, que será realizado através de um script do AWS CloudFormation que pode ser obtido aqui. O provisionamento pode ser realizado tanto pela console como pela AWS CLI e você precisará informar os seguintes parâmetros:

  • Login de administrador do banco de dados;
  • Senha do login de administrador do banco de dados;
  • Login do banco de dados utilizado pela aplicação;
  • Um range de IPs que terão acesso aos recursos provisionados. Informe o range de IPs que será utilizado na criação da VPN que será criada mais a frente;
  • Uma Key Pair existente para conectar na instância EC2 que será criada para rodar a aplicação;
  • Tipo da instância EC2 que será criada.

 

Será provisionada a seguinte infraestrutura:

  • Bucket do Amazon Simple Storage Service (Amazon S3), para armazenarmos imagens;
  • Banco de dados Amazon Aurora, para persistirmos dados sobre as imagens;
  • Instância EC2, que utilizaremos para rodar nosso código na AWS;
  • IAM Role, será utilizada para dar a permissão ao código executado na instância EC2 para consumir as imagens do bucket e conectar no banco de dados Aurora.

 

O seguinte diagrama representa os serviços utilizados:

 

 

Com a infraestrutura provisionada será necessário criar uma VPN (Virtual Private Network) para acessar esses recursos a partir da nossa máquina, pois tanto a instância EC2 como o banco de dados estão em subnets privadas.

No contexto de teste podemos estabelecer uma VPN do tipo Client to Site. Para criar essa VPN siga os passos descritos nesta documentação.

O último passo na preparação da infraestrutura é instalarmos o AWS CLI na máquina de desenvolvimento. O AWS CLI é um aplicativo de linha de comando que permite provisionar e gerenciar recursos na AWS. Neste post o utilizaremos para configurar e testar os acessos que serão utilizados durante o desenvolvimento.

Siga as instruções desse link para instalar o AWS CLI, utilize a instalação local para que a configuração possa ser utilizada pela sua IDE.

Arquitetura da Aplicação

Nossa aplicação persistirá um arquivo no Amazon S3 e alguns dados sobre esse arquivo no Amazon Aurora.

Durante o desenvolvimento o código acessará o Amazon S3 e o Amazon RDS pela máquina local via VPN, após concluído será feito o deploy do programa em uma instância do Amazon EC2, simulando o uso da aplicação em ambiente de produção.

Desenvolvimento do código localmente

Nesse blog post será mostrado o código escrito em C# (.Net), será disponibilizado em Java e Python no git.

Primeiro será criado o projeto e adicionado os SDKs dos serviços que serão utilizados. Pode-se consultar os Guias e Refências de API para identificar quais serviços utilizar e como acessar suas APIs.

Será utilizado um projeto do tipo Console Application para focar no uso dos serviços da AWS. No C# será utilizado o NuGet para adicionar as referências ao SDK do Amazon S3, em Java pode-se utilizar tanto o Maven como o Graddle e, em Python, o Pip. O banco de dados será acessado pela biblioteca do MySQL da própria linguagem escolhida.

 

Figura 1 – Adicionando o SDK do S3 ao projeto com o NuGet.

 

A primeira parte do código fará o parse dos parâmetros de linha de comando e verificará a existência do arquivo que será feito o upload no S3. Os parâmetros do programa serão o nome do arquivo e um par chave/valor com suas informações.

Para auxiliar nessa tarefa serão criados dois métodos, um para verificar a existência do arquivo e outro para transformar os demais parâmetros em um dicionário:

 

 

Em seguida serão utilizados os dois métodos no programa:

 

 

Com os parâmetros validados e preparados faça o upload do arquivo para o S3. Para essa finalidade, crie o método LoadFileToS3 que recebe como parâmetro o nome do arquivo e a região utilizada para chamar as APIs do S3.

 

 

Esse método utiliza um objeto do tipo AmazonS3Client e outros dois objetos auxiliares: TransferUtility e TransferUtilityUploadRequest.

O objeto do tipo TransferUtilityUploadRequest recebe como propriedades o caminho do arquivo local e qual será o nome do objeto no S3. O objeto do tipo TransferUtility é utilizado para executar o upload.

O objeto do tipo AmazonS3Client possui diversos construtores, utilize o construtor que recebe somente o RegionEndpoint para selecionar a região onde o bucket do S3 está provisionado.

Dessa forma será utilizada a credencial que foi configurada no AWS CLI ou a IAM Role configurada, dispensando a configuração de credenciais no nosso código.

Até o momento o código faz o upload do arquivo para o S3, no próximo passo será adicionada a manipulação do banco de dados.

Configuração no RDS

Antes de escrever o código, verifique as configurações que foram criadas pelo script do CloudFormation que tornam possíveis o uso de IAM roles no acesso ao banco de dados.

O template utilizado ao provisionar o RDS Aurora configurou o suporte a autenticação pelo IAM, como pode ser visto na imagem abaixo:

 

Figura 2 – Configurações do Amazon Aurora

 

Essa configuração permite que o programa se conecte no banco de dados utilizando a role do IAM ao invés de usuários nativos do banco de dados. Dessa forma, são centralizados no IAM tanto as permissões de conexão ao banco como de consumo de serviços da AWS.

A permissão de conexão ao banco de dados é concedida através de uma política do IAM que foi criada pelo template do CloudFormation. Portanto somente os grupos, usuários ou roles que tenham essa política anexada terão acesso ao banco de dados.

 

Figura 3 – Política de conexão ao banco de dados

 

Na lista de recursos (Resources) dessa política está a identificação do banco de dados. Os seguintes itens são utilizados para especificar as instâncias do Amazon Aurora:

  • Região da instância do Amazon Aurora;
  • ID da conta onde o Amazon Aurora está provisionado;
  • ID do cluster;
  • Usuário do banco de dados que será utilizado pela aplicação.

O usuário do banco de dados utilizado pela aplicação é um dos parâmetros do script do CloudFormation, porém esse usuário e as tabelas do banco não foram criados. Para criar o usuário e as tabelas conecte no banco e execute os seguintes comandos, substituindo o texto <nome do usuário> pelo parâmetro informado no script.

 

 

O usuário foi criado utilizando a diretiva With AWSAuthenticationPlugin as ‘RDS’. Essa diretiva informa ao Amazon Aurora que roles do IAM podem conectar no banco de dados utilizando esse usuário, desde que estabelecido na política do IAM.

Acessando o banco

Agora será criado o código que fará a inserção no banco de dados, iniciando com a conexão. Como veremos, a conexão é o único ponto diferente do código em comparação ao uso tradicional do banco de dados.

Código utilizado para conectar no banco de dados:

 

 

Primeiro serão carregados os seguintes dados da conexão a partir do arquivo de configuração:

  • Endpoint;
  • Porta;
  • Nome do banco;
  • Usuário do banco;
  • Certificado para a comunicação SSL.

O certificado para a comunicação SSL é utilizado para criptografar a conexão entre a aplicação e o banco de dados. As informações sobre esse certificado e o link para fazer o download são encontrados aqui.

Com os parâmetros da conexão carregados cria-se o token de autenticação. Esse token é criado com o uso do método RDSAuthTokenGenerator e substituirá a senha. Atente-se que ele tem validade de 15 minutos.

Com o token procede-se com a autenticação no banco de dados. Primeiro definindo a string de conexão, em sequência criando o objeto que comunica com o banco de dados e abrindo a conexão.

Perceba que tanto no código como nas configurações não são utilizadas senhas.

Com a conexão do banco de dados aberta, faça a inserção dos dados. Para executar os comandos de inserção crie objetos do tipo MySqlCommand com parâmetros.

Essa etapa não possui nenhuma diferença de uma conexão tradicional com o banco de dados.

 

 

Por fim utilize a conexão com o banco de dados e a criação dos comandos em um novo método que tem a lógica de persistência no banco de dados:

 

 

Adicione a chamada ao método WriteMetada no método Main que é a entrada do programa, logo após o upload do arquivo para o S3. Dessa forma completamos nosso programa que envia o arquivo para o S3 e em seguida persiste seus metadados em banco de dados.

 

 

Deploy do programa na AWS e execução

Agora faça o deploy do programa em uma instância EC2 na AWS, mas antes verifique as configurações da instância EC2 que permitem o uso de roles para conectar no S3 e no Aurora.

Acesse o serviço EC2 na console da AWS e clique no ID da instância para abrir suas informações. Na parte central das configurações há o campo IAM Role.

 

 

Essa é a role que possuí os acessos tanto ao S3 como ao Aurora.

 

Para acessar os detalhes sobre essa política e suas permissões clique no nome.

 

 

Faça a cópia do programa para essa instância através do comando scp (no caso de Linux ou MacOS) ou winscp (em máquina Windows).

Após copiar o programa para o servidor, execute-o. Note novamente que em momento algum é necessário colocar a senha na aplicação ou em algum arquivo de configuração.

 

 

Após rodar o programa, acesse o S3 pela console da AWS e o Aurora por um cliente de MySQL para ver os dados inseridos.

Removendo os recursos da sua conta

Não esqueça de remover os recursos provisionados durante a demo desse blogpost para evitar custos desnecessários.

Para remover os recursos, acesse o CloudFormation novamente e em seguida selecione a stack criada. Ao selecionar stack o botão Delete será habilitado, clique nele.

Será aberta uma janela pedindo a confirmação da remoção da stack, clique em Delete Stack para iniciar a remoção dos recursos. Você acompanha a evolução da execução pelo CloudFormation.

 

 

E se a única opção é utilizar uma chave de acesso?

Caso seja necessário utilizar alguma chave de acesso, por alguma limitação tecnológica, utilize as boas práticas a seguir para reduzir os riscos:

Usuários dedicados para a aplicação

Crie usuários dedicados para cada aplicação e não os misture com os usuários humanos, criando uma rastreabilidade muito maior de como seus serviços se comportam.

Estabeleça privilégio mínimo para as roles que são anexadas aos usuários dedicados das aplicações

Atribua somente as permissões mínimas necessárias para que a aplicação execute suas funções. Essa prática ajuda a limitar a ação que agentes maliciosos podem tomar caso a credencial seja comprometida.

Restrições com Conditions dentro das políticas

Inserir condições, como aws:SourceIp, dentro das políticas para limitar de onde esta credencial pode ser utilizada.

Rotacionar periodicamente as credenciais

A rotação de credenciais é um dos métodos mais eficazes de prevenir a utilização indevida de suas credenciais, recomenda-se fazê-la mensalmente para manter um bom nível de segurança.

 

Conclusão

Neste blogpost foi abordada a utilização de forma segura das credenciais no desenvolvimento de aplicações, evitando que elas sejam inseridas dentro do código ou em arquivos de configuração, melhorando a postura de segurança em relação a credenciais de acesso.

Também foi apresentado um exemplo de como prover acessos ao agente (máquina local, EC2, ou outro) que esteja rodando o código, utilizando credenciais temporárias providas por roles, ao invés de chaves de acesso. Isso mitiga o risco que credenciais desse ambiente sejam utilizadas indevidamente e acrescenta uma camada de segurança ao ambiente e ao código construído.

O ambiente é composto por um template de CloudFormation que provisiona um ambiente contendo uma VPC com um banco de dados Aurora MySQL e uma instância EC2 para testes, que se utiliza de roles para autenticação. Esse ambiente foi utilizado como base para o desenvolvimento de um código que permite aplicações e serviços se acessarem mutuamente sem utilizar chaves de acesso inseridas dentro do código, o que é uma boa prática e uma forma mais segura de acesso.

 


Sobre os Autores

Felipe Bortoletto é arquiteto de soluções na AWS. Ele tem trabalhado na Amazon desde 2017, começando como Engenheiro de TI, em 2019 ele participou do programa Tech U que proveu o conhecimento e pratica necessário para ser promovido para arquiteto de soluções em 2020. Atualmente está trabalhando com o mercado financeiro e se aprofundando na área de segurança.

 

 

 

 

Flavio de Moraes foi Arquiteto de Soluções na AWS no segmento de Enterprise entre Jul/20 e Fev/22.