Variáveis de Ambiente no Lambda: Configuração, Acesso e Criptografia com KMS

Você tem um endpoint de banco de dados que precisa chegar até a função Lambda — hardcodar isso no código é o caminho mais rápido para um vazamento de credencial em um repositório público. Variáveis de ambiente no Lambda resolvem esse problema, e quando combinadas com KMS, você adiciona uma camada de criptografia que vai além do padrão gerenciado pela AWS.

TL;DR — Variáveis de Ambiente no Lambda

PontoDetalhe
Onde configurarConsole, CLI (--environment), IaC (CloudFormation/SAM/Terraform)
Criptografia padrãoAWS gerencia com chave própria (aws/lambda) — transparente, sem custo adicional de KMS
Criptografia customizadaVocê fornece uma CMK do KMS; Lambda criptografa em repouso e você descriptografa em runtime
Acesso no códigoprocess.env.NOME (Node.js), os.environ['NOME'] (Python)
Limite de tamanho4 KB total para todas as variáveis — verifique a documentação oficial para limites atuais
Alternativa para segredosAWS Secrets Manager ou SSM Parameter Store para rotação automática

Como o Lambda Gerencia Variáveis de Ambiente

Antes de configurar qualquer coisa, vale entender o ciclo de vida. Variáveis de ambiente são armazenadas junto à configuração da função — não dentro do pacote de deployment. Quando o Lambda inicializa um execution environment, ele injeta essas variáveis no processo antes de executar o handler. Isso significa que uma atualização de variável não requer novo deploy do código, mas exige uma nova invocação para que o execution environment reflita o valor atualizado.

graph TD A["Configuração da Função
variáveis criptografadas"] --> B["Cold Start
novo execution environment"] B --> C["Init Phase
variáveis injetadas no processo"] C --> D["Handler Execution
os.environ / process.env"] D --> E{"Próxima invocação"} E -->|"Warm"| D E -->|"Novo ambiente"| B F["update-function-configuration"] --> A F -.->|"Ambientes warm existentes
NÃO são atualizados imediatamente"| D
  1. Configuração da função: variáveis ficam armazenadas criptografadas no plano de controle do Lambda.
  2. Init phase: ao criar um novo execution environment, o Lambda descriptografa e injeta as variáveis no processo.
  3. Handler execution: o código acessa via variáveis de ambiente do sistema operacional — sem chamada de API adicional.
  4. Warm invocation: execution environments reutilizados já têm as variáveis injetadas; mudanças só aparecem em novos ambientes.

Configurando Variáveis de Ambiente via AWS CLI

A forma mais direta de entender o modelo é pela CLI. Você pode definir variáveis na criação da função ou atualizá-las independentemente do código.

Criando uma função com variáveis de ambiente:

aws lambda create-function \
  --function-name minha-funcao \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/minha-role-lambda \
  --handler app.handler \
  --zip-file fileb://function.zip \
  --environment 'Variables={DB_ENDPOINT=meu-banco.cluster-abc123.us-east-1.rds.amazonaws.com,DB_PORT=5432,APP_ENV=production}'

Atualizando variáveis sem novo deploy de código:

aws lambda update-function-configuration \
  --function-name minha-funcao \
  --environment 'Variables={DB_ENDPOINT=meu-banco-novo.cluster-xyz.us-east-1.rds.amazonaws.com,DB_PORT=5432,APP_ENV=production}'
Atenção: update-function-configuration --environment substitui todo o bloco de variáveis, não faz merge. Se você omitir uma variável existente, ela é removida. Sempre inclua todas as variáveis no payload.

Verificando a configuração atual:

aws lambda get-function-configuration \
  --function-name minha-funcao \
  --query 'Environment'

Acessando Variáveis de Ambiente no Código

O acesso é direto — as variáveis estão disponíveis como variáveis de ambiente do processo. Não há SDK call, não há latência de rede.

Python:

import os

def handler(event, context):
    db_endpoint = os.environ['DB_ENDPOINT']
    db_port = os.environ.get('DB_PORT', '5432')  # com valor padrão
    app_env = os.environ['APP_ENV']
    
    # use db_endpoint para conectar ao banco
    print(f'Conectando em {db_endpoint}:{db_port}')

Node.js:

exports.handler = async (event) => {
    const dbEndpoint = process.env.DB_ENDPOINT;
    const dbPort = process.env.DB_PORT || '5432';
    const appEnv = process.env.APP_ENV;
    
    console.log(`Conectando em ${dbEndpoint}:${dbPort}`);
};

Uma prática que vale adotar: valide a presença das variáveis obrigatórias no início do handler — ou melhor, fora do handler para que a validação ocorra no init phase. Uma função que falha rápido com mensagem clara é muito mais fácil de debugar do que uma que quebra silenciosamente na conexão com o banco.

Criptografia com KMS — Variáveis de Ambiente no Lambda

Por padrão, o Lambda criptografa variáveis de ambiente em repouso usando uma chave gerenciada pela AWS (aws/lambda). Isso é automático e sem custo adicional de KMS. Para cenários onde você precisa de controle sobre a chave — auditoria granular via CloudTrail, rotação gerenciada por você, ou requisitos de compliance — você pode usar uma Customer Managed Key (CMK).

graph LR subgraph SemCMK["Sem CMK (Padrão)"] A1["Variável em plaintext"] --> B1["Lambda cifra com aws/lambda"] B1 --> C1["Armazenado criptografado"] C1 --> D1["Init phase: descriptografia automática"] D1 --> E1["os.environ — plaintext"] end subgraph ComCMK["Com CMK (Customizado)"] A2["Variável em plaintext"] --> B2["Lambda cifra com sua CMK"] B2 --> C2["Armazenado criptografado"] C2 --> D2["Init phase: Lambda chama kms:Decrypt"] D2 --> E2["os.environ — plaintext"] end subgraph EncryptHelper["Encryption Helper (Console)"] A3["Variável em plaintext"] --> B3["Console cifra com CMK"] B3 --> C3["Armazenado como ciphertext"] C3 --> D3["Init phase: valor ainda cifrado"] D3 --> E3["Código chama kms.decrypt() explicitamente"] end
  1. Sem CMK (padrão): AWS gerencia a chave aws/lambda. Criptografia transparente, sem custo adicional de KMS por chamada de API.
  2. Com CMK — criptografia em repouso: Lambda usa sua CMK para criptografar os valores armazenados. Você vê as chamadas no CloudTrail.
  3. Com CMK — helpers de criptografia: o console oferece a opção de criptografar o valor antes de enviá-lo ao Lambda, exigindo que o código chame o KMS explicitamente para descriptografar em runtime.

Configurando uma CMK para criptografia em repouso

Primeiro, a role de execução do Lambda precisa de permissão para usar a chave:

🔽 Clique para expandir — Política IAM para a role do Lambda usar a CMK
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowLambdaToUseKMSKey",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt",
        "kms:GenerateDataKey"
      ],
      "Resource": "arn:aws:kms:us-east-1:123456789012:key/sua-chave-id"
    }
  ]
}

Depois, associe a CMK à função:

aws lambda update-function-configuration \
  --function-name minha-funcao \
  --kms-key-arn arn:aws:kms:us-east-1:123456789012:key/sua-chave-id \
  --environment 'Variables={DB_ENDPOINT=meu-banco.cluster-abc123.us-east-1.rds.amazonaws.com,DB_PORT=5432,APP_ENV=production}'

Com isso, o Lambda usa sua CMK para criptografar os valores em repouso. A descriptografia acontece automaticamente durante o init phase — o código continua acessando via os.environ normalmente, sem nenhuma chamada explícita ao KMS.

Criptografia adicional com helpers do console (encrypt in transit)

O console do Lambda oferece uma opção de 'encryption helpers' que vai além da criptografia em repouso: o valor é criptografado pelo KMS antes de ser enviado para o Lambda. Nesse caso, o valor armazenado na configuração da função é o ciphertext, e o código precisa chamar o KMS explicitamente para descriptografar.

Esse modelo faz sentido quando você quer garantir que o valor nunca trafegue em plaintext — nem durante a configuração. O tradeoff é latência adicional no cold start (uma chamada KMS Decrypt por variável) e complexidade no código.

Python com descriptografia explícita via KMS:

🔽 Clique para expandir — Handler com descriptografia KMS explícita
import os
import boto3
import base64

kms_client = boto3.client('kms')

# Descriptografa uma vez no init phase, fora do handler
def decrypt_env_var(encrypted_value):
    response = kms_client.decrypt(
        CiphertextBlob=base64.b64decode(encrypted_value)
    )
    return response['Plaintext'].decode('utf-8')

# Executado no init phase — reutilizado em warm invocations
DB_ENDPOINT = decrypt_env_var(os.environ['DB_ENDPOINT'])

def handler(event, context):
    # DB_ENDPOINT já está descriptografado e em memória
    print(f'Conectando em {DB_ENDPOINT}')

Colocar a descriptografia fora do handler é intencional: o KMS Decrypt é chamado uma vez por execution environment, não a cada invocação. Em funções com alto throughput, isso evita tanto latência quanto custo desnecessário de KMS.

Diagnóstico: Quando a Variável Não Aparece ou o Valor Está Errado

Esse é o cenário clássico: você atualizou a variável, fez um teste, e o comportamento não mudou. A primeira suspeita costuma ser cache de código ou bug no handler — mas quase sempre é um execution environment antigo ainda ativo.

Sintoma: função retorna o valor antigo mesmo após update-function-configuration.
Diagnóstico errado: o código está cacheando o valor em uma variável global.
Causa real: execution environments warm ainda têm as variáveis antigas injetadas. O Lambda não recicla ambientes ativos imediatamente após uma atualização de configuração.

Verificação 1 — confirme que a atualização foi aplicada:

aws lambda get-function-configuration \
  --function-name minha-funcao \
  --query 'Environment.Variables'

Verificação 2 — confirme a versão em execução:

aws lambda get-function-configuration \
  --function-name minha-funcao \
  --query '{LastModified:LastModified,State:State,LastUpdateStatus:LastUpdateStatus}'

Se LastUpdateStatus for Successful e o valor no console estiver correto, o problema é execution environment antigo. A solução é aguardar o Lambda reciclar os ambientes naturalmente, ou forçar um novo deploy (mesmo sem mudança de código) para acelerar a transição.

Verificação 3 — erro de permissão KMS no CloudWatch Logs:

aws logs filter-log-events \
  --log-group-name /aws/lambda/minha-funcao \
  --filter-pattern 'AccessDeniedException' \
  --start-time $(date -d '1 hour ago' +%s000)

Se a role do Lambda não tem permissão kms:Decrypt na CMK configurada, a função falha no init phase com AccessDeniedException. O erro aparece nos logs antes de qualquer linha do seu handler — o que confunde quem procura o problema no código da aplicação.

Variáveis de Ambiente vs. Secrets Manager vs. SSM Parameter Store

Variáveis de ambiente são convenientes, mas não são a solução certa para todos os cenários. A distinção prática:

CritérioEnv Vars + KMSSecrets ManagerSSM Parameter Store
Rotação automáticaNãoSim (nativo)Não (requer automação)
Latência de acessoZero (já injetado)Chamada de API (rede)Chamada de API (rede)
Atualização sem redeploySim (nova invocação)Sim (sem restart)Sim (sem restart)
CustoKMS por usoPor segredo + por chamada de APIGratuito (Standard); custo para Advanced
Ideal paraEndpoints, flags de configSenhas, tokens com rotaçãoConfiguração hierárquica

Para um endpoint de banco de dados que não muda frequentemente, variáveis de ambiente com CMK são suficientes e têm latência zero. Para a senha do banco, Secrets Manager com rotação automática é o caminho correto — não coloque senhas em variáveis de ambiente se você pode evitar.

Configuração com AWS SAM (IaC)

Em produção, você raramente configura variáveis pelo console ou CLI diretamente. O SAM é a forma mais comum para funções Lambda:

🔽 Clique para expandir — template.yaml com variáveis de ambiente e CMK
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameters:
  DbEndpoint:
    Type: String
    Description: Endpoint do banco de dados RDS
  KmsKeyArn:
    Type: String
    Description: ARN da CMK para criptografia das variáveis

Resources:
  MinhaFuncao:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: minha-funcao
      Handler: app.handler
      Runtime: python3.12
      KmsKeyArn: !Ref KmsKeyArn
      Environment:
        Variables:
          DB_ENDPOINT: !Ref DbEndpoint
          DB_PORT: '5432'
          APP_ENV: production
      Policies:
        - Statement:
            - Effect: Allow
              Action:
                - kms:Decrypt
                - kms:GenerateDataKey
              Resource: !Ref KmsKeyArn

Passe os valores sensíveis como parâmetros no deploy — nunca os coloque diretamente no template versionado:

sam deploy \
  --template-file template.yaml \
  --stack-name minha-stack \
  --parameter-overrides \
    DbEndpoint=meu-banco.cluster-abc123.us-east-1.rds.amazonaws.com \
    KmsKeyArn=arn:aws:kms:us-east-1:123456789012:key/sua-chave-id \
  --capabilities CAPABILITY_IAM

Próximos Passos e Variáveis de Ambiente no Lambda em Produção

Para a maioria dos casos de uso — endpoints de banco, URLs de serviços, flags de feature — variáveis de ambiente com uma CMK cobrem o requisito com latência zero e complexidade mínima. O passo seguinte natural é avaliar se os segredos que você está colocando em variáveis deveriam estar no Secrets Manager, especialmente se houver rotação envolvida.

Glossário

TermoDefinição
Execution EnvironmentAmbiente isolado criado pelo Lambda para executar uma função; persiste entre invocações warm para reutilização.
CMK (Customer Managed Key)Chave KMS criada e gerenciada pelo cliente, com controle total sobre política, rotação e auditoria.
Init PhaseFase de inicialização do execution environment onde o código fora do handler é executado; ocorre apenas em cold starts.
CiphertextDado criptografado — o resultado de uma operação de criptografia KMS que só pode ser revertido com a chave correta.
Cold StartPrimeira invocação em um novo execution environment; inclui init phase e tem latência maior que invocações warm.

Related Posts

Comentários

Postagens mais visitadas deste blog

EC2 SSH Connection Timeout: Quais Regras do Security Group Verificar

EC2 Sem Acesso à Internet em VPC Customizada: Como Configurar Internet Gateway e Route Table

S3 Access Denied: Por que 'Bloquear Acesso Público' impede seu objeto mesmo após torná-lo público