Paweł Łukasiewicz
2022-05-15
Paweł Łukasiewicz
2022-05-15
Udostępnij Udostępnij Kontakt
Wprowadzenie

AWS CloudTrail to usługa, która pomaga logować wszystkie działania wykonywane wewnątrz konsoli AWS. Rejestruje wszystkie wywołania API i przechowuje historię, którą możemy wykorzystać do debuggowania naszej aplikacji.

Funkcja Lambda nie może jednak zostać striggerowana przez CloudTrail. Cała historia, w postaci logów, jest przechowywana w S3 i to będzie nasz wyzwalacz funkcji. Po przetworzeniu logów przez CloudTrail dojdzie do dodania ich do S3 co wyzwoli działanie naszej funkcji.

Kolejne kroki, które wykonamy w ramach tego wpisu:

  1. utworzenie wiadra S3 do przechowywania logów CloudTrail;
  2. utworzenie usługi SNS;
  3. utworzenie ścieżki w CloudTrail i przypisanie do niej wiadra S3 oraz usługi SNS;
  4. utworzenie roli z odpowiednimi uprawnieniami;
  5. utworzenie funkcji Lambda;

Przykład

Jak się zaraz przekonacie, coraz bardziej rozszerzamy naszą architekturę, żeby zblizać się w stronę realnych przykładów. Tym razem połączymy trzy komponenty, tj: CloudTrail, S3 oraz Lambdę.

W pierwszym kroku utworzymy kubełek S3, który będzie przechowywał wszystkie logi dla każdej akcji wykonanej w konsoli AWS. Następnie utworzymy temat w usłudze SNS do którego będziemy publikowali - logi zdarzenia dla tej akcji zostaną zapisane jako plik w S3. Wraz z dodaniem nowych plików do kubełka dojdzie do uruchomienia w tle funkcji Lambda, która wykorzystując usługę SNS prześle powiadomienie w postaci wiadomości email. Zanim przejdziemy dalej spójrzcie na diagram ilustrujący cały proces: AWS Cloudtrail: przykładowa architektura

Jeżeli będziecie mieli jakieś problemy z przejściem przez kolejne punkty odsyłam do poprzednich wpisów gdzie cały proces został omówiony dużo dokładniej. Powoli zaczynamy się skupiać na rzeczach istotnych pomijając poszczególne ekrany.

Tworzenie S3

Przechodzimy do wyszukiwarki usług dostępnej z poziomu konsoli AWS, wybieramy S3 a następnie klikamy Create bucket - w tym miejscu będziemy przechowywali logi z CloudTrail: AWS Cloudtrail: tworzenie s3

Tworzenie SNS

W tej części musimy utworzyć topic dla SNS, który w kolejnym kroku połączymy z kubełkiem S3. Wykorzystując wyszukiwarkę usług przechodzimy do Simple Notification Service a następnie klikamy przycisk Create topic: AWS Cloudtrail: tworzenie sns

Tworzenie ścieżki w CloudTrail i przypisanie S3 oraz SNS

To tylko z pozoru skomplikowany temat. Przechodzimy do CloudTrail z poziomu wyszukiwarki usług a następnie klikamy przycisk Create trail: AWS Cloudtrail: tworzenie śćieżki w CloudTrail

Z poziomu nowo otwartego okna konfiguracyjnego wskazujemy na istniejący kubełek S3 oraz wyłączamy enkrypcję. AWS Cloudtrail: konfiguracja Dodatkowym krokiem jest zaznaczenie SNS notification delivery oraz wskazanie tematu utworzonego w poprzedniej części: AWS Cloudtrail: notyfikacje SNS

Na kolejnym ekranie wybieramy rodzaj obsługiwanych zdarzeń, tzn. Management events: AWS Cloudtrail: zarządzanie zdarzeniami

Po kliknięciu przycisku Create trail powinniśmy zobaczyć nową ścieżkę: AWS Cloudtrail: ścieżka utworzona

Tworzenie roli

Rola, którą tworzymy musi mieć uprawnienia do S3, CloudTrail, SES oraz Lambda. Sprawdźcie czy utworzona przez Was konfiguracja jest zgodna z poniższym zrzutem ekranu: AWS Cloudtrail: tworzenie roli

Lambda, wyzwalacz, implementacja

Funkcje utworzymy już tradycyjnie z poziomu konsoli AWS wskazując na .NET Core 3.1 jako środowisko uruchomieniowe: AWS Cloudtrail: tworzenie funkcji Pamiętajcie również o przypisaniu odpowiedniej roli.

Po poprawnym dodaniu funkcji dodajemy wyzwalacz na utworzony w poprzedniej części kubełek S3: AWS Cloudtrail: tworzenie wyzwalacza

Pora na właściwą implementację. Nasza funkcja będzie reagowała na zdarzenia związane z kubełkiem S3 - mam tutaj na myśli zapis logów o których wspominałem wcześniej. Zdarzenie to spowoduje uruchomienie Lambdy, która dokona prostego przetwarzania zdarzenia oraz wyśle wiadomość wykorzystując usługę SNS. Spójrzcie na poniższą, bardzo prostą, implementację:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon;
using Amazon.Lambda.Core;
using Amazon.Lambda.S3Events;
using Amazon.S3;
using Amazon.S3.Util;
using Amazon.SimpleEmail;
using Amazon.SimpleEmail.Model;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace lambdaCloudTrail
{
    public class Function
    {
   	     IAmazonS3 S3Client { get; set; }

   	     /// <summary>
   	     /// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment
   	     /// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the
   	     /// region the Lambda function is executed in.
   	     /// </summary>
   	     public Function()
   	     {
   		     S3Client = new AmazonS3Client();
   	     }

   	     /// <summary>
   	     /// Constructs an instance with a preconfigured S3 client. This can be used for testing the outside of the Lambda environment.
   	     /// </summary>
   	     /// <param name="s3Client"></param>
   	     public Function(IAmazonS3 s3Client)
   	     {
   		     this.S3Client = s3Client;
   	     }

   	     /// <summary>
   	     /// This method is called for every Lambda invocation. This method takes in an S3 event object and can be used
   	     /// to respond to S3 notifications.
   	     /// </summary>
   	     /// <param name="evnt"></param>
   	     /// <param name="context"></param>
   	     /// <returns></returns>
   	     public async Task<string> FunctionHandler(S3Event evnt, ILambdaContext context)
   	     {
   		     var s3Event = evnt.Records?[0].S3;
   		     if (s3Event == null)
   		     {
   			     return null;
   		     }

   		     try
   		     {
   			     var response = await this.S3Client.GetObjectMetadataAsync(s3Event.Bucket.Name, s3Event.Object.Key);
   			     context.Logger.LogLine($"S3 response: {response}");

   			     // Ustawcie adres email zweryfikowany w obrębie konkretnego regionu
   			     string senderAddress = "zweryfikowany_adres_email@gmail.com";
   			     // Pamiętajcie o ustawieniu regionu w którym dokonaliście weryfikacji adresu email
   			     // W przeciwnym wypadku zobaczycie poniższy błąd:
   			     // Email address is not verified.
   			     // The following identities failed the check in region EU-WEST-2: zweryfikowany_adres_email@gmail.com
   			     using (var client = new AmazonSimpleEmailServiceClient(RegionEndpoint.USEast1))
   			     {
   				     var sendRequest = new SendEmailRequest
   				     {
   					     Source = senderAddress,
   					     Destination = new Destination
   					     {
   						     ToAddresses =
   						     new List<string> { "zweryfikowany_adres_email@gmail.com" }
   					     },
   					     Message = new Message
   					     {
   						     Subject = new Content("Wpisz temat wysyłanej wiadomości"),
   						     Body = new Body
   						     {
   							     Html = new Content
   							     {
   								     Charset = "UTF-8",
   								     Data = evnt.ToString()
   							     },
   							     Text = new Content
   							     {
   								     Charset = "UTF-8",
   								     Data = "body"
   							     }
   						     }
   					     }
   				     };
   				     var sendEmail = await client.SendEmailAsync(sendRequest);
   			     }

   			     return response.Headers.ContentType;
   		     }
   		     catch (Exception e)
   		     {
   			     context.Logger.LogLine($"Error getting object {s3Event.Object.Key} from bucket {s3Event.Bucket.Name}. Make sure they exist and your bucket is in the same region as this function.");
   			     context.Logger.LogLine(e.Message);
   			     context.Logger.LogLine(e.StackTrace);
   			     throw;
   		     }
   	     }
    }
}

Możecie się teraz zastanawiać jak uruchomić cały ciąg zdarzeń? W tym celu musimy, podobnie jak w poprzednim wpisie, opublikować wiadomość do utworzonego wcześniej tematu usługi SNS. Jeżeli nie pamiętacie jak to zrobić zerknijcie do poprzedniego wpisu: AWS Cloudtrail: publikacja powiadomienia SNS

Jeżeli wszystko przebiegło pomyślnie powinniście otrzymać wiadomość na wskazany adres email: AWS Cloudtrail: potwierdzenie otrzymania notyfikacji

Musicie pamiętać o dwóch rzeczach. Po pierwsze tworząc nową politykę nie dodałem jednej roli (celowo). Po opublikowaniu kodu Waszej funkcji nie wszystko będzie działało poprawnie…

Problem jest niezwykle prosty do rozwiązania, wymaga jedynie spojrzenia w logi waszej funkcji. Czemu tak zrobiłem? Rozwiązywanie problemów to niezwykle istotny aspekt pracy dobrego programisty a pisanie aplikacji w chmurze znacznie utrudnia ten proces. Każdy z nas musi doskonale posługiwać się narzędziem jakim jest CloudWatch.

Po zakończonych testach przejdźcie ponownie do ścieżek zdefiniowanych w ramach CloudTrail i zatrzymajcie proces logowania ponieważ jest on związany z każdą akcją w konsoli AWS i zaczniecie dostawać bardzo dużo wiadomości email. W tym wypadku to nie my dodajemy plik do S3 czy nowy rekord do DynamoDB - cały proces dzieje się automatycznie. Usługa ta pozwala nam na ciągłe monitorowanie naszej aplikacji i wysyłanie powiadomień np. w przypadku wystąpienia problemów tak, abyśmy szybko mogli zareagować. W takim podejściu musielibyśmy oczywiście odpowiednio zmodyfikować kod naszej funkcji tak, aby wysyłanie wiadomości odbywało się jedynie w przypadku zaistnienia zdefiniowanych przez programistę zdarzeń.