IAM Access Analyzer
IAM Access Analyzer to usługa, która pozwala między innymi na śledzenie, czy któreś z naszych zasobów nie są dostępne dla „użytkowników” spoza naszego konta.
W chwili gdy to piszę, możliwe jest sprawdzenie następujących usług:
- S3 buckets
- AWS Identity and Access Management roles
- AWS Key Management Service keys
- AWS Lambda functions and layers
- Amazon Simple Queue Service queues
- AWS Secrets Manager secrets
Jeżeli więc na przykład utworzymy sobie kolejkę SQS i przypniemy do niej polisę, która będzie umożliwiała to, aby inne konto AWS mogło wysyłać do niej wiadomości, czyli coś w stylu
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, "Action": "sqs:SendMessage", "Resource": "arn:aws:sqs:eu-central-1:210987654321:externalqueue" } ] }
to Access Analyzer to wykryje i powiadomi nas o tym. Taki przykład zresztą za chwilę pokażę.
Co ważne, informacja będzie o tym, że taki dostęp JEST MOŻLIWY, nie że MIAŁ MIEJSCE.
Zabieramy się więc do pracy.
IAM Access Analyzer
Na początku musimy utworzyć nasz analyzer. Pamiętajcie, żeby uruchomić go w regionie, który chcemy obsługiwać. Dla cli polecenie, które utworzy analyzer na poziomie konta wygląda następująco:
aws accessanalyzer create-analyzer --analyzer-name iamanalyzer --type ACCOUNT
Możemy mieć jeden taki analyzer w każdym regionie. Oczywiście nic nie stoi na przeszkodzie, aby utworzyć go za pomocą konsoli bezpośrednio w usłudze IAM.
Po chwili na liście „znalezisk” będziecie pewnie mieli całkiem sporo pozycji.
Nie wszystko jest groźne (większość nie jest), można więc nie stwarzające zagrożenia pozycje od razu archiwizować za pomocą tak zwanych Archive rules. Nie będę tu tego tematu poruszał, jeżeli jesteście ciekawi to odsyłam do dokumentacji AWS.
Możemy oczywiście zintegrować sobie także IAM Access Analyzer z Security Hubem i będziemy mieli także tam listę naszych podatności
Ale kto tam zagląda. 😉
Eventy
Jak wiemy, chmura zdarzeniami żyje. W AWS takie zdarzenia możemy monitorować w usłudze Event Bridge. To czego potrzebujemy, to utworzenie Event Rule, która będzie „czekała” na zdarzenia Access Analyzer Finding pochodzące z usługi aws.access-analyzer.
{ "source": [ "aws.access-analyzer" ], "detail-type": [ "Access Analyzer Finding" ] }
Załóżmy, że chcemy wysłąć do siebie email w momencie, gdy analyzer coś znalazł. Będziemy potrzebowali jeszcze topica SNS, subskrypcji i pewnie klucza szyfrującego dla SNS. Cały template dla Cloud Formation możecie pobrać stąd. Następnie, po ustawieniu adresu email, na który chcemy otrzymywać powiadomienia wykonujemy polecenie:
aws cloudformation deploy --template-file template.yaml --stack-name iamaccessanalyzerevents --capabilities CAPABILITY_NAMED_IAM --parameter-overrides "Email=<MÓJ-ADRES-EMAIL>"
i po dłuższej chwili, czyli jakichś pięciu momentach, powinniśmy mieć całość zdeployowaną w chmurze.
Nic nie stoi na przeszkodzie, aby wysłać sobie takie powiadomienia na kanał w MS Teams lub na Slacka. Jak to zrobić opisałem tutaj.
Po zakończonym deploymencie stacka, na podany adres email dostaniecie prośbę o potwierdzenie subskrypcji. Nie zapomnijcie kliknąć, bez tego nie dostaniecie żadnych powiadomień.
Prowokacja
Na pewno dostaliście kilka powiadomień, ale spróbujmy utworzyć obiecaną kolejkę w usłudze SQS, na którą wiadomości będzie mogła wysyłać jakaś usługa z inneg konta. W tym celu utworzymy drugi stack Cloud Formation, dla którego template możecie ściągnąć tutaj. Tym razem wszystko jest jeszcze prostsze. Tworzymy jedną kolejkę i dodajemy do niej queue policy:
AWSTemplateFormatVersion: 2010-09-09 Parameters: ExternalAWSAccount: Type: String Resources: Queue: Type: AWS::SQS::Queue QueuePolicy: Type: AWS::SQS::QueuePolicy Properties: Queues: - !Ref Queue PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: !Ref ExternalAWSAccount Action: sqs:SendMessage Resource: !GetAtt Queue.Arn
Po ustawnieniu parametru ExternalAWSAccount na identyfikator konta AWS, które będzie mogło wysyłać wiadomości do kolejki wykonujemy polecenie
aws cloudformation deploy --template-file template_queue.yaml --stack-name externalqueue --capabilities CAPABILITY_NAMED_IAM --parameter-overrides "ExternalAWSAccount=<OBCE-KONTO-AWS>"
Po kolejnej dłuższej chwili, AWS pisze nawet o godzinie, ale tak źle nie powinno być, powinniście dostać maila o treści podobnej do tej poniżej:
{ "Type" : "Notification", "MessageId" : "63a214ce-6a4f-56ff-aecc-4fedc1280936", "TopicArn" : "arn:aws:sns:eu-central-1:767050457733:iamaccessanalyzer-IAMAccessAnalyzerTopic", "Message" : "{\"version\":\"0\",\"id\":\"b790e2b5-64b2-5032-7c37-5d3bdeddf4bd\",\"detail-type\":\"Access Analyzer Finding\",\"source\":\"aws.access-analyzer\",\"account\":\"767050457733\",\"time\":\"2022-06-15T09:40:55Z\",\"region\":\"eu-central-1\",\"resources\":[\"arn:aws:access-analyzer:eu-central-1:767050457733:analyzer/iamanalyzer\"],\"detail\":{\"version\":\"1.0\",\"id\":\"e8123933-8874-4dba-9b31-7aebd956220d\",\"status\":\"ACTIVE\",\"resourceType\":\"AWS::SQS::Queue\",\"resource\":\"arn:aws:sqs:eu-central-1:767050457733:externalqueue-Queue-397UHpcnSm1F\",\"createdAt\":\"2022-06-15T09:40:53.799Z\",\"analyzedAt\":\"2022-06-15T09:40:53.799Z\",\"updatedAt\":\"2022-06-15T09:40:53.799Z\",\"accountId\":\"767050457733\",\"region\":\"eu-central-1\",\"principal\":{\"AWS\":\"494378769832\"},\"action\":[\"sqs:SendMessage\"],\"condition\":{},\"isDeleted\":false,\"isPublic\":false}}", "Timestamp" : "2022-06-15T09:40:55.503Z", "SignatureVersion" : "1", "Signature" : "TT+Yxhh1taJWyT5Seiad8Adm98oWfkeSsPgo05g83HezBrVl9OCa5h+GDmoT4px18Wo2LBsNWfE2NwXIQrCivGC9l6NWi3w7KkdiGgE9oZWRpLbmBPnjMh3EQVStKovorQQhGGWOswV59hQtejjLygtmbbil/pR+037n3dHZCcPJp193myYiRNNK4M8d68cgvNxY1xWh4KDk94IerUDaQus4DR030JqCzIXe+aeBG5mxn9BautCS9UScpGaWVpq8gWSrNjCGXfix7izYbCVFscqVil/vGRNDdCOFlqsiAp1Bx3SugsmO9CbnXrT+cAEM9OIKajuQssH33lPb7pHo3g==", "SigningCertURL" : "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem", "UnsubscribeURL" : "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-central-1:767050457733:iamaccessanalyzer-IAMAccessAnalyzerTopic:455a1c0b-eaab-4cf6-a056-a7b53336d407" }
Analiza
To co nas tak naprawdę w tym momencie interesuje, to zawartość klucza Message, który po „oczyszczeniu” będzie wyglądał mniej więcej tak:
{ "Message": { "version": "0", "id": "b790e2b5-64b2-5032-7c37-5d3bdeddf4bd", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "767050457733", "time": "2022-06-15T09:40:55Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:767050457733:analyzer/iamanalyzer" ], "detail": { "version": "1.0", "id": "e8123933-8874-4dba-9b31-7aebd956220d", "status": "ACTIVE", "resourceType": "AWS::SQS::Queue", "resource": "arn:aws:sqs:eu-central-1:767050457733:externalqueue-Queue-397UHpcnSm1F", "createdAt": "2022-06-15T09:40:53.799Z", "analyzedAt": "2022-06-15T09:40:53.799Z", "updatedAt": "2022-06-15T09:40:53.799Z", "accountId": "767050457733", "region": "eu-central-1", "principal": { "AWS": "494378769832" }, "action": [ "sqs:SendMessage" ], "condition": {}, "isDeleted": false, "isPublic": false } } }
W linii 7 mamy identyfikartor konta, a w linii 9 region, na którym mamy udostępniony zasób11 mamy informację o tym, z którego analyzera pochodzi event. Najciekawsze informacje mamy jednak w kluczu detail. W linii 17 określna jest usługa, natomiast w linii 18 Arn zasobu którego temat dotyczy. Poniżej, w linii 24 i następnych mamy Principala, czyli opis tego, kto lub co jest dopuszczony do działań na naszym zasobie, a w linii 27 i poniższych akcje API, które może wykonać. Może ich być oczywiście więcej, np:
"action": [ "kms:CreateGrant", "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", "kms:GenerateDataKey", "kms:GenerateDataKeyPair", "kms:GenerateDataKeyPairWithoutPlaintext", "kms:GenerateDataKeyWithoutPlaintext", "kms:ListGrants", "kms:ReEncryptFrom", "kms:ReEncryptTo", "kms:RevokeGrant" ]
Ważna jest także linia nr 16, czyli status. W naszym przypadku ACTIVE, ale gdy już „problem” zostanie rozwiązany to przyjmie on wartość RESOLVED jak poniżej:
{ "version": "0", "id": "e7b22ed8-ab37-4033-62ce-876021a7aa43", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "230434492249", "time": "2022-06-02T07:29:49Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:230434492249:analyzer/ConsoleAnalyzer-a23ae37f-6d43-48fc-8763-d14292ab8f57" ], "detail": { "version": "1.0", "id": "19d221fe-331d-4877-9990-d5ccb2590191", "status": "RESOLVED", "resourceType": "AWS::SecretsManager::Secret", "resource": "arn:aws:secretsmanager:eu-central-1:230434492249:secret:icm-secret-TtJSAG", "createdAt": "2022-06-02T07:06:22.181Z", "analyzedAt": "2022-06-02T07:29:48.662Z", "updatedAt": "2022-06-02T07:29:48.662Z", "accountId": "230434492249", "region": "eu-central-1", "principal": { "AWS": "767050457733" }, "action": [ "secretsmanager:CancelRotateSecret" ], "condition": {}, "isDeleted": false, "isPublic": false } }
Jeżeli inne zdarzenia nas nie interesują, można to sobie oczywiście wyfiltrować w momencie subskrybowania do eventu korzystając z poniższego template:
{ "source": [ "aws.access-analyzer" ], "detail-type": [ "Access Analyzer Finding" ], "detail": { "status": [ "ACTIVE" ] } }
W przypadku gdy nie będzie możliwa analiza któregoś z zasobów, zostanie wygenerowanie powiadomienie z kluczem error jak w poniższym przykładzie:
{ "account": "111122223333", "detail": { "accountId": "111122223333", "analyzedAt": "2019-11-21T01:22:22Z", "createdAt": "2019-11-20T04:58:50Z", "error": "ACCESS_DENIED", "id": "22222222-dcba-4444-dcba-333333333333", "isDeleted": false, "region": "us-west-2", "resource": "arn:aws:s3:::my-bucket", "resourceType": "AWS::S3::Bucket", "status": "ACTIVE", "updatedAt": "2019-11-21T01:14:07Z", "version": "1.0" }, "detail-type": "Access Analyzer Finding", "id": "11111111-2222-4444-aaaa-333333333333", "region": "us-west-2", "resources": [ "arn:aws:access-analyzer:us-west-2:111122223333:analyzer/MyAnalyzer" ], "source": "aws.access-analyzer", "time": "2019-11-21T01:22:33Z", "version": "0" }
Usuwamy zasoby
Na koniec wypada usunąć niepotrzebne zasoby:
- Stack z kolejką
aws cloudformation delete-stack --stack-name externalqueue
- Jeżeli nie chcecie sobie pozostawić powiadomień to także stack z event rule
aws cloudformation delete-stack --stack-name iamaccessanalyzerevents
- I sam analyzer
aws accessanalyzer delete-analyzer --analyzer-name iamanalyzer
Podsumowanie
Czy warto używać IAM Access Analyzera. Jest za darmo więc na pewno warto. Tylko z głową i umiarem.
W dzisiejszych czasach pracujemy prawie zawsze na wielu kontach, czy to w ramach organizacji, czy nie. Często udostępniamy zasoby pomiędzy różnymi kontami. W związku z tym, może zalać nas całkiem sporo informacji o takich sytuacjach. Tu pomoże na pewno utworzenie odpowiednich Archive Rules.
Czy musimy sobie wysyłać o każdym nowym udostępnionym zasobie. Niekoniecznie, to zależy od tego po co to robimy. Jeżeli dostaniemy 1000 maili, to tak naprawdę to samo gdybyśmy dostali okrągłe zero. Skutek będzie taki sam, czyli żaden. Ale na pewno warto zerknąć czasem na listę w IAM Access Analyzer i zobaczyć co w trawie piszczy.
Przykładowe zdarzenia
Poniżej jeszcze przykładowe zdarzenia dla poszczególnych usług. Może komuś się przyda. Ja na pewno tu kiedyś wrócę.
Lambda
{ "version": "0", "id": "8064eac6-0831-0e41-e632-c07bb313b9aa", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "230434492249", "time": "2022-06-02T08:08:57Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:230434492249:analyzer/ConsoleAnalyzer-a23ae37f-6d43-48fc-8763-d14292ab8f57" ], "detail": { "version": "1.0", "id": "717395cc-157a-4581-99b1-31c1a884d1ec", "status": "ACTIVE", "resourceType": "AWS::Lambda::Function", "resource": "arn:aws:lambda:eu-central-1:230434492249:function:iam-test", "createdAt": "2022-06-02T08:08:56.584Z", "analyzedAt": "2022-06-02T08:08:56.584Z", "updatedAt": "2022-06-02T08:08:56.584Z", "accountId": "230434492249", "region": "eu-central-1", "principal": { "AWS": "767050457733" }, "action": [ "lambda:InvokeFunction" ], "condition": {}, "isDeleted": false, "isPublic": false } }
KMS Key
{ "version": "0", "id": "774a02b6-b06f-235a-7d2f-7a5e97c04ac5", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "230434492249", "time": "2022-06-02T07:34:55Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:230434492249:analyzer/ConsoleAnalyzer-a23ae37f-6d43-48fc-8763-d14292ab8f57" ], "detail": { "version": "1.0", "id": "039a8795-7c2a-4bd1-8f83-04b0adcabd74", "status": "ACTIVE", "resourceType": "AWS::KMS::Key", "resource": "arn:aws:kms:eu-central-1:230434492249:key/be949121-41dd-4ee5-81f7-7576a2928fc9", "createdAt": "2022-06-02T07:34:54.813Z", "analyzedAt": "2022-06-02T07:34:54.813Z", "updatedAt": "2022-06-02T07:34:54.813Z", "accountId": "230434492249", "region": "eu-central-1", "principal": { "AWS": "767050457733" }, "action": [ "kms:CreateGrant", "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", "kms:GenerateDataKey", "kms:GenerateDataKeyPair", "kms:GenerateDataKeyPairWithoutPlaintext", "kms:GenerateDataKeyWithoutPlaintext", "kms:ListGrants", "kms:ReEncryptFrom", "kms:ReEncryptTo", "kms:RevokeGrant" ], "condition": {}, "isDeleted": false, "isPublic": false } }
SQS Queue
{ "version": "0", "id": "89b71acd-6a1a-3d70-be72-c557bc38b7bc", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "230434492249", "time": "2022-05-31T10:49:56Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:230434492249:analyzer/ConsoleAnalyzer-a23ae37f-6d43-48fc-8763-d14292ab8f57" ], "detail": { "version": "1.0", "id": "e8c67c00-3fb5-40c8-b242-bb98b19ec06f", "status": "ACTIVE", "resourceType": "AWS::SQS::Queue", "resource": "arn:aws:sqs:eu-central-1:230434492249:iam-test", "createdAt": "2022-05-31T10:49:53.265Z", "analyzedAt": "2022-05-31T10:49:53.265Z", "updatedAt": "2022-05-31T10:49:53.265Z", "accountId": "230434492249", "region": "eu-central-1", "principal": { "AWS": "767050457733" }, "action": [ "sqs:ChangeMessageVisibility", "sqs:DeleteMessage", "sqs:ReceiveMessage", "sqs:SendMessage" ], "condition": {}, "isDeleted": false, "isPublic": false } }
Secret Manager Secret
{ "version": "0", "id": "c43c1d81-f511-b87f-c95f-a95aa8a840ef", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "230434492249", "time": "2022-06-02T07:06:23Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:230434492249:analyzer/ConsoleAnalyzer-a23ae37f-6d43-48fc-8763-d14292ab8f57" ], "detail": { "version": "1.0", "id": "19d221fe-331d-4877-9990-d5ccb2590191", "status": "ACTIVE", "resourceType": "AWS::SecretsManager::Secret", "resource": "arn:aws:secretsmanager:eu-central-1:230434492249:secret:icm-secret-TtJSAG", "createdAt": "2022-06-02T07:06:22.181Z", "analyzedAt": "2022-06-02T07:06:22.181Z", "updatedAt": "2022-06-02T07:06:22.181Z", "accountId": "230434492249", "region": "eu-central-1", "principal": { "AWS": "767050457733" }, "action": [ "secretsmanager:CancelRotateSecret", "secretsmanager:DeleteResourcePolicy", "secretsmanager:DeleteSecret", "secretsmanager:DescribeSecret", "secretsmanager:GetResourcePolicy", "secretsmanager:ListSecretVersionIds", "secretsmanager:PutResourcePolicy", "secretsmanager:RemoveRegionsFromReplication", "secretsmanager:ReplicateSecretToRegions", "secretsmanager:RestoreSecret", "secretsmanager:RotateSecret", "secretsmanager:StopReplicationToReplica", "secretsmanager:TagResource", "secretsmanager:UntagResource", "secretsmanager:UpdateSecret", "secretsmanager:UpdateSecretVersionStage", "secretsmanager:ValidateResourcePolicy" ], "condition": {}, "isDeleted": false, "isPublic": false } }
Secret Manager Secret Resolved
{ "version": "0", "id": "e7b22ed8-ab37-4033-62ce-876021a7aa43", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "230434492249", "time": "2022-06-02T07:29:49Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:230434492249:analyzer/ConsoleAnalyzer-a23ae37f-6d43-48fc-8763-d14292ab8f57" ], "detail": { "version": "1.0", "id": "19d221fe-331d-4877-9990-d5ccb2590191", "status": "RESOLVED", "resourceType": "AWS::SecretsManager::Secret", "resource": "arn:aws:secretsmanager:eu-central-1:230434492249:secret:icm-secret-TtJSAG", "createdAt": "2022-06-02T07:06:22.181Z", "analyzedAt": "2022-06-02T07:29:48.662Z", "updatedAt": "2022-06-02T07:29:48.662Z", "accountId": "230434492249", "region": "eu-central-1", "principal": { "AWS": "767050457733" }, "action": [ "secretsmanager:CancelRotateSecret", "secretsmanager:DeleteResourcePolicy", "secretsmanager:DeleteSecret", "secretsmanager:DescribeSecret", "secretsmanager:GetResourcePolicy", "secretsmanager:ListSecretVersionIds", "secretsmanager:PutResourcePolicy", "secretsmanager:RemoveRegionsFromReplication", "secretsmanager:ReplicateSecretToRegions", "secretsmanager:RestoreSecret", "secretsmanager:RotateSecret", "secretsmanager:StopReplicationToReplica", "secretsmanager:TagResource", "secretsmanager:UntagResource", "secretsmanager:UpdateSecret", "secretsmanager:UpdateSecretVersionStage", "secretsmanager:ValidateResourcePolicy" ], "condition": {}, "isDeleted": false, "isPublic": false } }
IAM Trust Policy
{ "version": "0", "id": "803418d2-4df9-904d-5893-f28615760d26", "detail-type": "Access Analyzer Finding", "source": "aws.access-analyzer", "account": "230434492249", "time": "2022-05-31T10:49:59Z", "region": "eu-central-1", "resources": [ "arn:aws:access-analyzer:eu-central-1:230434492249:analyzer/ConsoleAnalyzer-a23ae37f-6d43-48fc-8763-d14292ab8f57" ], "detail": { "version": "1.0", "id": "1f6e9c64-0823-41dc-a8fa-304994dc0a8c", "status": "ACTIVE", "resourceType": "AWS::IAM::Role", "resource": "arn:aws:iam::230434492249:role/stacksets-exec-6eda6837d4bb7fcf20091c91d18e7fb5", "createdAt": "2022-05-31T10:49:53.266Z", "analyzedAt": "2022-05-31T10:49:53.266Z", "updatedAt": "2022-05-31T10:49:53.266Z", "accountId": "230434492249", "region": "eu-central-1", "principal": { "AWS": "arn:aws:iam::494378769832:role/aws-service-role/stacksets.cloudformation.amazonaws.com/AWSServiceRoleForCloudFormationStackSetsOrgAdmin" }, "action": [ "sts:AssumeRole" ], "condition": {}, "isDeleted": false, "isPublic": false } }