How to send Cloudwatch log details via email? - amazon-web-services

The diagram below is what I am trying to achieve. In brief, to send CloudTrail logs to CloudWatch log group then scan it for certain events and finally send email alerts if there is an concerting event.
I am following this official documentation which also has a sample CloudFormation templates: http://docs.aws.amazon.com/awscloudtrail/latest/userguide/use-cloudformation-template-to-create-cloudwatch-alarms.html
Using the CloudFormation templates above, I have been able to send the email alerts. However the alerts are very basic; it does not send key information like which user initiated this event, when did it occur etc.
Logically thinking AWS::Logs::MetricFilter should pass the value to AWS::CloudWatch::Alarm which would then send the information. I have looked at the documentation of both MetricFilter and Alarm services. Dimension comes closer to what I want but not yet able to read the information from logs.
I would have thought this is a common use case and there would be documentation. Am I missing something glaringly obvious here? Has anyone here solved this issue?
AWS::Logs::MetricFilter block:
"AuthorizationFailuresMetricFilter": {
"Type": "AWS::Logs::MetricFilter",
"Properties": {
"LogGroupName": { "Ref" : "LogGroupName" },
"FilterPattern": "{ ($.errorCode = \"*UnauthorizedOperation\") || ($.errorCode = \"AccessDenied*\") }",
"MetricTransformations": [
{
"MetricNamespace": "CloudTrailMetrics",
"MetricName": "AuthorizationFailureCount",
"MetricValue": "1"
}
]
}
},
AWS::CloudWatch::Alarm block
"AuthorizationFailuresAlarm": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmName" : "CloudTrailAuthorizationFailures",
"AlarmDescription" : "Alarms when an unauthorized API call is made.",
"AlarmActions" : [{ "Ref" : "AlarmNotificationTopic" }],
"Dimensions": [
{
"Name": "errorCode",
"Value": ""
},
{
"Name": "userIdentity",
"Value": ""
}
],
"MetricName" : "AuthorizationFailureCount",
"Namespace" : "CloudTrailMetrics",
"ComparisonOperator" : "GreaterThanOrEqualToThreshold",
"EvaluationPeriods" : "1",
"Period" : "300",
"Statistic" : "Sum",
"Threshold" : "1"
}
},

This is not possible.
Amazon CloudWatch Logs will accept information from AWS CloudTrail and, upon finding messages that match a pre-defined filter, will increment a metric count.
An Amazon CloudWatch alarm can then be triggered when the metric exceeds a certain threshold. However, there is no direct connection between the incoming data that generated the metrics and the alarm that triggers based upon the threshold.
Think of it like a turnstile counting people who enter a subway. The turnstile counts the number of people, but does not retain information about the people who passed through. In the same way, the CloudWatch alarm counts the events but does not have any information about the events that were counted.

Related

Is it possible to extract "instanceId" from EventBridge event data, and use it as Target Value?

I was able to setup AutoScaling events as rules in EventBridge to trigger SSM Commands, but I've noticed that with my chosen Target Value the event is passed to all my active EC2 Instances. My Target key is a tag shared by those instances, so my mistake makes sense now.
I'm pretty new to EventBridge, so I was wondering if there's a way to actually target the instance that triggered the AutoScaling event (as in extracting the "InstanceId" that's present in the event data and use that as my new Target Value). I saw the Input Transformer, but I think that just transforms the event data to pass to the target.
Thanks!
EDIT - help with js code for Lambda + SSM RunCommand
I realize I can achieve this by setting EventBridge to invoke a Lambda function instead of the SSM RunCommand directly. Can anyone help with the javaScript code to call a shell command on the ec2 instance specified in the event data (event.detail.EC2InstanceId)? I can't seem to find a relevant and up-to-date base template online, and I'm not familiar enough with js or Lambda. Any help is greatly appreciated! Thanks
Sample of Event data, as per aws docs
{
"version": "0",
"id": "12345678-1234-1234-1234-123456789012",
"detail-type": "EC2 Instance Launch Successful",
"source": "aws.autoscaling",
"account": "123456789012",
"time": "yyyy-mm-ddThh:mm:ssZ",
"region": "us-west-2",
"resources": [
"auto-scaling-group-arn",
"instance-arn"
],
"detail": {
"StatusCode": "InProgress",
"Description": "Launching a new EC2 instance: i-12345678",
"AutoScalingGroupName": "my-auto-scaling-group",
"ActivityId": "87654321-4321-4321-4321-210987654321",
"Details": {
"Availability Zone": "us-west-2b",
"Subnet ID": "subnet-12345678"
},
"RequestId": "12345678-1234-1234-1234-123456789012",
"StatusMessage": "",
"EndTime": "yyyy-mm-ddThh:mm:ssZ",
"EC2InstanceId": "i-1234567890abcdef0",
"StartTime": "yyyy-mm-ddThh:mm:ssZ",
"Cause": "description-text"
}
}
Edit 2 - my Lambda code so far
'use strict'
const ssm = new (require('aws-sdk/clients/ssm'))()
exports.handler = async (event) => {
const instanceId = event.detail.EC2InstanceId
var params = {
DocumentName: "AWS-RunShellScript",
InstanceIds: [ instanceId ],
TimeoutSeconds: 30,
Parameters: {
commands: ["/path/to/my/ec2/script.sh"],
workingDirectory: [],
executionTimeout: ["15"]
}
};
const data = await ssm.sendCommand(params).promise()
const response = {
statusCode: 200,
body: "Run Command success",
};
return response;
}
Yes, but through Lambda
EventBridge -> Lambda (using SSM api) -> EC2
Thank you #Sándor Bakos for helping me out!! My JavaScript ended up not working for some reason, so I ended up just using part of the python code linked in the comments.
1. add ssm:SendCommand permission:
After I let Lambda create a basic role during function creation, I added an inline policy to allow Systems Manager's SendCommand. This needs access to your documents/*, instances/* and managed-instances/*
2. code - python 3.9
import boto3
import botocore
import time
def lambda_handler(event=None, context=None):
try:
client = boto3.client('ssm')
instance_id = event['detail']['EC2InstanceId']
command = '/path/to/my/script.sh'
client.send_command(
InstanceIds = [ instance_id ],
DocumentName = 'AWS-RunShellScript',
Parameters = {
'commands': [ command ],
'executionTimeout': [ '60' ]
}
)
You can do this without using lambda, as I just did, by using eventbridge's input transformers.
I specified a new automation document that called the document I was trying to use (AWS-ApplyAnsiblePlaybooks).
My document called out the InstanceId as a parameter and is passed this by the input transformer from EventBridge. I had to pass the event into lambda just to see how to parse the JSON event object to get the desired instance ID - this ended up being
$.detail.EC2InstanceID
(it was coming from an autoscaling group).
I then passed it into a template that was used for the runbook
{"InstanceId":[<instance>]}
This template was read in my runbook as a parameter.
This was the SSM playbook inputs I used to run the AWS-ApplyAnsiblePlaybook Document, I just mapped each parameter to the specified parameters in the nested playbook:
"inputs": {
"InstanceIds": ["{{ InstanceId }}"],
"DocumentName": "AWS-ApplyAnsiblePlaybooks",
"Parameters": {
"SourceType": "S3",
"SourceInfo": {"path": "https://testansiblebucketab.s3.amazonaws.com/"},
"InstallDependencies": "True",
"PlaybookFile": "ansible-test.yml",
"ExtraVariables": "SSM=True",
"Check": "False",
"Verbose": "-v",
"TimeoutSeconds": "3600"
}
See the document below for reference. They used a document that was already set up to receive the variable
https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-tutorial-eventbridge-input-transformers.html
This is the full automation playbook I used, most of the parameters are defaults from the nested playbook:
{
"description": "Runs Ansible Playbook on Launch Success Instances",
"schemaVersion": "0.3",
"assumeRole": "<Place your automation role ARN here>",
"parameters": {
"InstanceId": {
"type": "String",
"description": "(Required) The ID of the Amazon EC2 instance."
}
},
"mainSteps": [
{
"name": "RunAnsiblePlaybook",
"action": "aws:runCommand",
"inputs": {
"InstanceIds": ["{{ InstanceId }}"],
"DocumentName": "AWS-ApplyAnsiblePlaybooks",
"Parameters": {
"SourceType": "S3",
"SourceInfo": {"path": "https://testansiblebucketab.s3.amazonaws.com/"},
"InstallDependencies": "True",
"PlaybookFile": "ansible-test.yml",
"ExtraVariables": "SSM=True",
"Check": "False",
"Verbose": "-v",
"TimeoutSeconds": "3600"
}
}
}
]
}

CloudFormation - Create SNS subscription in disabled state

Is there a way to create an SNS subscription in the disabled state? This is for a lambda if that makes a difference.
Example:
MySubscription:
Type: AWS::SNS::Subscription
Properties:
Endpoint: arn:aws:lambda:region:account-id:function:mylambda
Protocol: lambda
TopicArn: arn:aws:sns:region:account-id:topic
Enabled: false # like this
Couldn't find anything like this in the AWS CloudFormation documentation
You can create a condition on the subscription creation.
First add a parameter:
"CreateSubscription": {
"Type": "String",
"AllowedValues": [
"true",
"false"
],
"Description": "Create subscription to sns"
}
after the 'parameters' section, create a 'conditions' section:
"Conditions" : {
"CreateSubscription" : {"Fn::Equals" : [{"Ref" : "CreateSubscription"}, "true"]}
}
add the condition to your subscription
"Subscription": {
"Type": "AWS::SNS::Subscription",
"Condition" : "CreateSubscription",
[...]
}
when you want to "activate" you subscription, you just have to change the parameter value updating the stack using the same template
Conditions section reference
There isn't a way with Cloudformation which conforms to your example. According to the documentation, AWS::SNS::Subscription does not have 'Enabled' as a setting.
Although, the documentation does state that the owner of the endpoint must confirm the subscription before Amazon SNS creates the subscription. So, in a sense, it's already disabled because it doesn't exist until you confirm it on the SNS topic.

How to search by nested property in AWS IoT Rules using AWS IoT query language

I'm trying to get AWS IoT rules trigger my actions. But the documentation is pretty poor. For some reason documentation think i'll have json payloads with nesting level=1 top.
Example of my JSON payload:
"state": {
"reported": {
"movement": "yes"
}
}
}
Query i'm using inside a rule
SELECT * FROM '$aws/things/thing-name/shadow/update/accepted' WHERE state.reported.movement="yes"
Documentation, i'm using: http://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-where.html
has information just about flat JSON object, i did try to use state.reported.movement, reported.movement, just movement and looks like none of them works
Okay, answer to my question is: AWS IoT Supports nested properties in WHERE statements. For some reason it didn't work when i did create a rule. Maybe SNS topic had some delay with delivering messages.
My state of the thing, coming to AWS IoT accepted topic looks like that:
{
"state": {
"reported": {
"system": "armed",
"movement": "yes"
}
},
"metadata": {
"reported": {
"system": {
"timestamp": 1509207282
},
"movement": {
"timestamp": 1509207282
}
}
},
"version": 618,
"timestamp": 1509207282,
"clientToken": "xxxxxxxx"
}
And to query that JSON i'm using following query:
SELECT * FROM '$aws/things/myThing/shadow/update/accepted' WHERE state.reported.movement="yes" and state.reported.system="armed"

How to add SQS message attributes in SNS subscription?

The documentation for AWS SNS and SQS have sections about message attributes. But there is no explanation how to have SQS message attributes when that queue is subscribed to a SNS topic.
Is there a way to configure AWS SNS to add particular message attributes to the SQS messages send via the subscription?
From aws documentation:
To use message attributes with Amazon SQS endpoints, you must set the subscription attribute, Raw Message Delivery, to True. For more information about raw message delivery, see Appendix: Large Payload and Raw Message Delivery.
https://docs.aws.amazon.com/sns/latest/dg/SNSMessageAttributes.html
https://docs.aws.amazon.com/sns/latest/dg/large-payload-raw-message.html
Added an example from real-life project. Hopefully it helps to clarify things.
The message published to the sns topic is following:
aws sns publish --topic-arn arn:aws:sns:us-west-2:xxx:pollution-event --message '{"operatorId":3375001,"eventTypeId":1,"eventLevelId":1,"validFrom":"2018-03-10T09:00:00Z","validTo":"2018-03-11T09:00:00Z"}' --message-attributes '{"Type" : { "DataType":"String", "StringValue":"Orchestration.Services.Model.Pollution.PollutionMessage"}}'
Enable Raw Delivery is false (default). The message received by sqs contains only content, there are no attributes
{
"Type": "Notification",
"MessageId": "78d5bc6f-142c-5060-a75c-ef29b774ec66",
"TopicArn": "arn:aws:sns:eu-west-2:xxx:pollution-event",
"Message": "{\"validFrom\": \"2018-03-10T09:00:00Z\",\"validTo\": \"2018-03-11T09:00:00Z\",\"eventLevelId\": 1,\"eventTypeId\": 1,\"operatorId\": 3375001}",
"Timestamp": "2018-04-17T11:33:44.770Z",
"SignatureVersion": "1",
"Signature": "xxx==",
"SigningCertURL": "https://sns.eu-west-2.amazonaws.com/SimpleNotificationService-xxx.pem",
"UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-2:xxx",
"MessageAttributes": {
"Type": {
"Type": "String",
"Value": "Orchestration.Services.Model.Pollution.PollutionMessage"
},
"AWS.SNS.MOBILE.MPNS.Type": {
"Type": "String",
"Value": "token"
},
"AWS.SNS.MOBILE.MPNS.NotificationClass": {
"Type": "String",
"Value": "realtime"
},
"AWS.SNS.MOBILE.WNS.Type": {
"Type": "String",
"Value": "wns/badge"
}
}
}
Enable Raw delivery is true. The message contains the message attributes and proper content
Note: See other answer for better response, using Raw Message Delivery
While the Using Amazon SNS Message Attributes documentation sending Amazon SNS message attributes to Amazon SQS, it appears that the attributes are sent in the body of the message rather than being attached as message attributes on the resulting Amazon SQS messages.
For example, I did the following:
Created an Amazon SNS topic
Created an Amazon SQS queue and subscribed it to the SNS topic
Published a message to SNS
I published via the AWS Command-Line Interface (CLI):
aws sns publish --topic-arn arn:aws:sns:ap-southeast-2:123456789012:foo --message msg --subject subj --message-attributes '{"somename" : { "DataType":"String", "StringValue":"somevalue"}}'
(I got syntax help from map datatype in aws cli)
The resulting message in SQS showed the attributes as part of the message:
{
"Type" : "Notification",
"MessageId" : "53e3adad-723a-5eae-a7b7-fc0468ec2d37",
"TopicArn" : "arn:aws:sns:ap-southeast-2:123456789012:foo",
"Subject" : "subj",
"Message" : "msg",
"Timestamp" : "2017-05-29T12:48:22.186Z",
...
"MessageAttributes" : {
"somename" : {"Type":"String","Value":"somevalue"}
}
}
It would be much nicer if these attributes were attached to the SQS message as official SQS attributes. Alas, it seems this is not the case.
Enabled Raw message delivery type while adding SQS subscription for the topic inside SNS
If you are here because you have a SQS queue that is subscribed to an SNS topic, you checked that your subscription has set the Raw Message Delivery to True but you still cannot read an attribute on your SQS message:
Make sure that your SQS client is not filtering out message attributes.
The code below will only include myAttribute when receiving messages from the SQS queue:
SQS.receiveMessage({
QueueUrl: queueUrl,
VisibilityTimeout: 20,
WaitTimeSeconds: 10,
MessageAttributeNames: [
"myAttribute"
],
},...
If you want to read the value of some attribute other than myAttribute you will have to specify it (white list) or replace "myAttribute" with "All" to include all SQS attributes.
SQS.receiveMessage({
MessageAttributeNames: [
"myAttribute", "myOtherAttribute"
],
},...
Reference: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html

AWS IoT - Dynamo Insert record failed

I am trying to update DynamoDB and I send JSON data from Rasperry PI or MQTT Client, but when I look to CloudWatch I see below error message.
EVENT:DynamoActionFailure TOPICNAME:iotbutton/test CLIENTID:MQTT_FX_Client MESSAGE:Dynamo Insert record failed. The error received was Attribute name must not be null or empty. Message arrived on: iotbutton/test, Action: dynamo, Table: myTable_IoT, HashKeyField: SerialNumber, HashKeyValue: ABCDEFG12345, RangeKeyField: Some(ClickType), RangeKeyValue: SINGLE
I am using the AWS IoT Tutorial (http://docs.aws.amazon.com/iot/latest/developerguide/iot-dg.pdf), The Seccion: Creating a DynamoDB Rule.
The data I send to the IoT platform is:
{
"serialNumber" : "ABCDEFG12345",
"clickType" : "SINGLE",
"batteryVoltage" : "5v USB"
}
topic: iotbutton/ABCDEFG12345
Does anyone come across this error and aware of any solution?
Thanks, regards.
This is the message the CloudWatch logs showed when a tried doing this:
{
"timestamp": "2019-01-28 21:26:16.363",
"logLevel": "ERROR",
"traceId": "9e3ff9b0-fcdf-d8ae-e8a8-4b7a24902405",
"accountId": "xxx",
"status": "Failure",
"eventType": "RuleExecution",
"clientId": "basicPubSub",
"topicName": "xxx/r117",
"ruleName": "devCompDynamoDB",
"ruleAction": "DynamoAction",
"resources":
{
"ItemRangeKeyValue": "SINGLE",
"IsPayloadJSON": "true",
"ItemHashKeyField": "SerialNumber",
"Operation": "Insert",
"ItemRangeKeyField": "ClickType",
"Table": "TestIoTDataTable",
"ItemHashKeyValue": "ABCDEFG12345"
},
"principalId": "xx",
"details": "Attribute name must not be null or empty"
}
To Fix it I edited the DynamoDB Rule in the IoT Web Console and I added a payload column in the "Write message data to this column" field.