Ligar e Desligar instâncias EC2 via Email
Tags: email, ec2, lambda, s3
Published: 2024-01-16 15:51:53

Este tutorial oferece uma solução para ligar ou desligar uma instância EC2 na AWS através do envio de emails para um endereço específico. O processo é desencadeado por meio de eventos do Amazon S3, onde os emails são armazenados. O fluxo geral é explicado conforme os seguintes passos:

> **Configuração do SES**: Criação de um subdomínio no Amazon Simple Email Service (SES) para receber os emails. Isso é necessário para garantir a correta leitura dos cabeçalhos dos emails enviados para o domínio principal.
> **Configuração da Regra no SES**: Definição de uma regra no SES para direcionar emails específicos para um bucket no Amazon S3.
> **Configuração da Lambda**: Implementação de uma função Lambda em Python 3.10 para processar os eventos do S3. A Lambda verifica o remetente autorizado no email, executa a ação desejada (ligar ou desligar a instância EC2), e notifica o remetente sobre a conclusão da ação.
> **Configuração do S3**: Adição de uma trigger no bucket S3 configurada no SES para acionar a Lambda quando novos emails são recebidos.

O código Python na Lambda verifica o remetente autorizado, lê o conteúdo do email, identifica a ação desejada (ligar ou desligar) e executa a operação na instância EC2 associada. O remetente é notificado sobre o status da ação via email.

O tutorial fornece observações importantes, como a necessidade de adicionar permissões adequadas na Lambda, ajustar as tags da instância desejada e verificar os emails autorizados no código. Além disso, são apresentadas dicas úteis, como a configuração de ciclos de vida no S3 e no CloudWatch Logs.

O resultado final é uma solução eficiente para controlar instâncias EC2 na AWS por meio do envio de emails autorizados, proporcionando automação e praticidade.

---

**Explicação básica do uso**:

> Você envia um email para aws@sandbox.dominio.com.br com EXATAMENTE no corpo de texto os dizeres: (não importa o assunto do email, somente o corpo do email)

`Ligar VM SRV_LNX_BI`
`Desligar VM SRV_LNX_BI`

Dai a instancia vai ligar ou desligar, se seu email estiver autorizado na lambda e no SES.

---

**Workflow Diagram:**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/goy2jk22muu9pdry1n20.png)


---

**Passo 1**: Configurar um subdomínio no SES (Ex.: sandbox.dominio.com.br) com o MailFrom habilitado. Neste caso vamos usar um subdomínio no SES porque, ao enviar um email para o domínio principal, o email não é direcionado diretamente para o SES devido às configurações MX estarem associadas a outro servidor de email (Google, Microsoft, Zimbra), com essa configuração, o Amazon SES não consegue ler corretamente o cabeçalho (header) do email e, consequentemente, não consegue enviá-lo para o Amazon S3 da maneira desejada.

---

**Passo 2**: Na pagina do SES, Vá em “Email receiving” > Você vera uma regra de “INBOUND MAIL”, clique nela > Create rule > adicione um nome como no print abaixo > Next.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vs8tr3960h2d97qz2xsh.png)

**Passo 2.1**: Em "Recipient conditions”, insira um email que você recebera as ações solicitando o desligamento ou ligamento das instâncias (ex.: aws@sandbox.dominio.com.br), e configure-o no Workmail depois, com o subdomínio validado, clique em Next.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wsw70t89ofutc5mps8tu.png)

**Passo 2.2**: Clique em “add new action” > “Deliver to Amazon S3 Bucket” > Adicione um bucket para armazenar os emails recebidos, para posteriormente a Lambda ler este conteúdo automaticamente, Next e depois Create Rule.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6nha1nkzdxayxg17s10.png)

**Com isso, você fará o SES publicar os emails recebidos no bucket S3.**

---

**Passo 3**: Agora precisamos configurar o código na lambda e uma trigger de eventos, que ao receber algo no S3, ira filtrar o conteúdo, e recebendo as mensagens definidas para ligar ou desligar a instancia, executar as ações pre-definidas.


Para lambda, vamos configurar um runtime python 3.10, adicionar as permissões para EC2 (Para ligar e desligar as instancias) Bucket S3 (Para ler o bucket e analisar as mensagens publicadas pelo SES), CloudWatch Logs (Para logs) e SES (Para envio de notificações).

<br>
**Segue o código:**

```python
import json
import boto3
import re
import email
from botocore.exceptions import NoCredentialsError

s3 = boto3.client('s3')
ec2 = boto3.client('ec2')
ses = boto3.client('ses')

authorized_senders = [
    'railander.marques@dominio.com.br',
    'email-teste@dominio.edu.br'
]

def lambda_handler(event, context):
    print(json.dumps(event))  # Imprimir o evento

################### Verificar se o evento é do S3
    if 'Records' not in event or not event['Records'][0].get('s3'):
        return {'statusCode': 400, 'body': json.dumps('Event is not from S3')}

################### Obter o nome do bucket e do arquivo do evento
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']

################### Baixar o arquivo do S3
    try:
        s3.download_file(bucket, key, '/tmp/email')
        print('File downloaded successfully')  # Adicionado print
    except NoCredentialsError:
        return {'statusCode': 400, 'body': json.dumps('Credentials not available')}

################### Ler o conteúdo do email
    with open('/tmp/email', 'r', encoding='utf-8') as f:
        email_content = f.read()

    print(f'Email content: {email_content}')  # Adicionado print

################### Encontrar o remetente do email
    sender_match = re.search(r'From: .*<(.+)>', email_content)

################### Se o remetente for autorizado, executar a ação
    sender_email = sender_match.group(1)

    if sender_email not in authorized_senders:
################### Enviar email de notificação para o remetente não autorizado
        send_response_email([sender_email, 'infraestrutura@dominio.com.br'], sender_email, 'Requisição de ação não autorizada', f'''
O remetente {sender_email} não está autorizado para executar essa ação.

Caso você acredite que isso seja um erro ou tenha alguma dúvida, por favor entre em contato com nossa equipe de suporte.

Atenciosamente,
Equipe de Infraestrutura
''')
        return {'statusCode': 401, 'body': json.dumps('Unauthorized sender')}
    else:
################### Se o remetente for autorizado, executar a ação
        sender_email = sender_match.group(1)

################### Localizar a ação de ligar ou desligar a instância no conteúdo do email
    body_match = re.search(r'Ligar VM SRV_LNX_BI|Desligar VM SRV_LNX_BI', email_content)

    if body_match:
        body = body_match.group(0)
        if 'Ligar VM SRV_LNX_BI' in body:
            instance_id = get_instance_id()
            print(f'Starting instance {instance_id}...')
            ec2.start_instances(InstanceIds=[instance_id])
            send_response_email([sender_email, 'infraestrutura@dominio.com.br'], sender_email, 'Ação de Instância - Ligamento', f'A instância associada à tag "SRV_LNX_BI" está sendo ligada conforme sua solicitação.Remetente Original: {sender_email}Caso você tenha mais perguntas ou precise de assistência adicional, sinta-se à vontade para entrar em contato.Atenciosamente,Equipe de Infraestrutura')
        elif 'Desligar VM SRV_LNX_BI' in body:
            instance_id = get_instance_id()
            print(f'Stopping instance {instance_id}...')
            ec2.stop_instances(InstanceIds=[instance_id])
            send_response_email([sender_email, 'infraestrutura@dominio.com.br'], sender_email, 'Ação de Instância - Desligamento', f'A instância associada à tag "SRV_LNX_BI" foi desligada conforme sua solicitação.Remetente Original: {sender_email}Caso você tenha mais perguntas ou precise de assistência adicional, sinta-se à vontade para entrar em contato.Atenciosamente,Equipe de Infraestrutura')
        else:
            print('Remetente não encontrado no email.')
    
    return {'statusCode': 200, 'body': json.dumps('Request was successful')}

def send_response_email(to_addresses, from_address, subject, body):
    response = ses.send_email(
        Source='infraestrutura@dominio.com.br',
        Destination={
            'ToAddresses': to_addresses
        },
        Message={
            'Subject': {
                'Data': subject
            },
            'Body': {
                'Text': {
                    'Data': body
                }
            }
        }
    )
    
    return response

def get_instance_id():
################### Lógica para obter o ID da instância associada à tag "SRV_LNX_BI"
    response = ec2.describe_instances(
        Filters=[
            {
                'Name': 'tag:Name',
                'Values': ['SRV_LNX_BI']
            }
        ]
    )
    
    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            return instance['InstanceId']
```

<br>
**Observações:**

1. Adicionar uma trigger para o bucket configurado no SES, você consegue fazer isso dentro do painel da lambda, em "add trigger”, escolher trigger para o s3 e “All object create events”, nada mais a alterar e ok.
2. Tempo de execução da lambda, 3 minutos.
3. Não esquecer de adicionar as permissões na lambda, para ec2, cloudwatch, ses e s3.
4. Alterar no código a tag name da Instancia desejada.
5. Add o email que vai receber uma copia das ações ex.: infraestrutura@dominio.com.br. (Verifica-los no SES)
6. Crie um workmail com um email usando o domínio delegado que foi verificado no SES. (Ex do código: aws@sandbox.dominio.com.br) ele que irá receber as solicitações por email.
7. Adicionar os emails autorizados no código lambda. (Toda a descrição do código no final da pagina)
8. **Apenas os emails que foram verificados no SES e adicionados à Lambda receberão e terão a automação funcionando.**
9. Faça o teste.



---

**Resultado:**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t4l03h6gft7af3u87ao2.png)

---

## **Explicação:**


O código é um Lambda Function que é executado quando um novo arquivo é enviado para um bucket do S3. O arquivo é um email com uma solicitação para ligar ou desligar uma instância EC2.


**O código funciona da seguinte forma:**


1. O Lambda Function recebe o evento do S3 como entrada.
2. O código verifica se o evento é do S3.
3. O código obtém o nome do bucket e do arquivo do evento.
4. O código baixa o arquivo do S3 para um arquivo temporário.
5. O código lê o conteúdo do email.
6. O código encontra o remetente do email.
7. O código verifica se o remetente é autorizado.
8. Se o remetente for autorizado, o código executa a ação solicitada.
9. O código envia um email de notificação ao remetente.
10. O código retorna um código de status HTTP 200.

<br>
**Aqui estão alguns detalhes adicionais sobre o código:**

- A função `get_instance_id()` é usada para obter o ID da instância associada à tag "SRV_LNX_BI".
- A função `send_response_email()` é usada para enviar um email de notificação ao remetente.


A mensagem que o usuário envia no email está contida na variável `email_content`. Essa variável é definida na linha abaixo:

```python
with open('/tmp/email', 'r', encoding='utf-8') as f:
    email_content = f.read()
```


O código procura os remetentes autorizados na lista `authorized_senders`. Essa lista está definida na linha abaixo:

```python
authorized_senders = [
    'railander.marques@dominio.com.br',
    'email-teste@dominio.edu.br'
]
```

---

**Dica:** 
- Não esquecer de configurar um ciclo de vida no S3, para os emails recebidos e tambem não esquecer de configurar um ciclo de vida no CloudWatch logs.
- É possível também fazer a configuração com um domínio free do AWS Workmail.