{"id":1568,"date":"2025-07-15T14:18:33","date_gmt":"2025-07-15T14:18:33","guid":{"rendered":"https:\/\/192.168.1.3\/wordpress\/?p=1568"},"modified":"2026-04-21T14:03:24","modified_gmt":"2026-04-21T06:03:24","slug":"receiving-sns-alert-and-terminating-ec2-instance-accessed-using-ssh","status":"publish","type":"post","link":"https:\/\/mylinuxsite.com\/wordpress\/?p=1568","title":{"rendered":"Sending SNS Alert and Terminating EC2 instance accessed using SSH"},"content":{"rendered":"\n<p>Let us assume that your company has a policy that mandates immediate notification and termination of any EC2 instance accessed via SSH. How do you implement this? <\/p>\n\n\n\n<p>In this article, we will explore one solution that makes use of (1) CloudWatch Log Subscription, (2) Lambda and (3) SNS. I believe this option offers more flexibility compared to other options. <\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"http:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/ec2-ssh-alarm.png\"><img loading=\"lazy\" decoding=\"async\" width=\"629\" height=\"357\" src=\"http:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/ec2-ssh-alarm.png\" alt=\"\" class=\"wp-image-1573\" srcset=\"https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/ec2-ssh-alarm.png 629w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/ec2-ssh-alarm-300x170.png 300w\" sizes=\"auto, (max-width: 629px) 100vw, 629px\" \/><\/a><\/figure>\n\n\n\n<h5 class=\"wp-block-heading\"><strong>How to Detect SSH Access?<\/strong><\/h5>\n\n\n\n<p>There are a few ways to detect SSH access to your Linux machine. One way is to utilize the system log. For this solution, we will use the log file <em>\/var\/log\/secure<\/em> to detect SSH logins. <\/p>\n\n\n\n<p>For Linux machines that use the<em> systemd<\/em> service, <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">such as the&nbsp;<em>AMI Linux 2<\/em>, the log file&nbsp;<em>\/v<\/em><\/span>ar\/log\/secure&nbsp;may not exist. The reason is that this log file is generated by the <em>rsyslog service<\/em>, which may not be installed by default. First, we need to install this service. We also need to install the CloudWatch agent, which is <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">not installed by default in the&nbsp;<em>AMI Linux 2<\/em>, and prepare its configuration file to specify<\/span> which log group and log stream to send the SSH logs to.<\/p>\n\n\n\n<h5 class=\"wp-block-heading\"><strong>Prepare the EC2 Instance<\/strong><\/h5>\n\n\n\n<p>In summary, we have to make the following preparations for our EC2:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li> Install, enable and start the <strong>rsyslog<\/strong> service.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo yum -y install rsyslog\n\nsudo systemctl enable rsyslog\n\nsudo systemctl start rsyslog<\/code><\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>Install the CloudWatch agent.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>yum install amazon-cloudwatch-agent<\/code><\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li>Create the CloudWatch configuration file. You can create the configuration file manually or use the configuration wizard. Name the file as <strong>config.json<\/strong>.<\/li>\n<\/ol>\n\n\n\n<p>To start the configuration wizard, enter the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo \/opt\/aws\/amazon-cloudwatch-agent\/bin\/amazon-cloudwatch-agent-config-wizard<\/code><\/pre>\n\n\n\n<p>Below is a snippet of the configuration file that we use in the sample EC2 resource, which we will build later using a CloudFormation template.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"agent\": {\n        \"metrics_collection_interval\": 60,\n        \"<strong>run_as_user<\/strong>\": \"cwagent\"\n    },\n    \"logs\": {\n        \"logs_collected\": {\n            \"files\": {\n                \"collect_list\": &#91;\n                    {\n                        \"file_path\": \"\/var\/log\/secure\",\n                        \"log_group_class\": \"STANDARD\",\n                        \"<strong>log_group_name<\/strong>\": \"<strong>${LogGroupName}<\/strong>\",\n                        \"log_stream_name\": \"{instance_id}\",\n                        \"retention_in_days\": -1\n                    }\n                ]\n            }\n        }\n    },\n:\n:<\/code><\/pre>\n\n\n\n<p>Two of the fields that you need to decide are the (1) <strong>run_as_user<\/strong>, which specifies a user to use to run the CloudWatch agent, and (2) <strong>log_group_name<\/strong>, which specifies what to use as the log group name in CloudWatch Logs. Since the above configuration is part of a CloudFormation template, and the <em>log_group_name<\/em> is set as a variable that will be replaced by &nbsp;<code>Fn::Sub<\/code> with a parameter value.<\/p>\n\n\n\n<p>Please take note that it is essential to keep the value of the <strong>log_stream_name<\/strong> to <strong>{instance_id}. <\/strong>This value will be used to identify the instance ID where an SSH login took place.<\/p>\n\n\n\n<ol start=\"4\" class=\"wp-block-list\">\n<li>If you manually created the configuration file, copy it to its proper location and set its owner to <em>root<\/em> and its access permission to <em>0644<\/em>. If you use the configuration wizard, the file should already be in the appropriate location with the right owner and access permission settings.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo cp config.json \/opt\/aws\/amazon-cloudwatch-agent\/bin\/config.json\n\nsudo chmod 0644 \/opt\/aws\/amazon-cloudwatch-agent\/bin\/config.json\n\nsudo chown root:root \/opt\/aws\/amazon-cloudwatch-agent\/bin\/config.json<\/code><\/pre>\n\n\n\n<ol start=\"5\" class=\"wp-block-list\">\n<li>If you set the CloudWatch agent to run as a non-root user (e.g., cwagent), you need to grant access to the \/var\/log\/secure file. You can do this by modifying the file&#8217;s ACL.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo setfacl -m u:cwagent:rx \/var\/log\/secure<\/code><\/pre>\n\n\n\n<ol start=\"6\" class=\"wp-block-list\">\n<li>Start the CloudWatch agent.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo \/opt\/aws\/amazon-cloudwatch-agent\/bin\/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:\/opt\/aws\/amazon-cloudwatch-agent\/bin\/config.json<\/code><\/pre>\n\n\n\n<h5 class=\"wp-block-heading\"><strong>The Lambda Function<\/strong><\/h5>\n\n\n\n<p>The Lambda function is the service responsible for sending notifications, via SNS, and terminating the EC2 instance. The Lambda function will receive a message from the CloudWatch subscription filter in the following format:  <code><strong>{&nbsp;\"awslogs\":&nbsp;{\"data\": \"BASE64ENCODED_GZIP_COMPRESSED_DATA\"} }<\/strong><\/code><\/p>\n\n\n\n<p>The Lambda function has to decompress the <em>data<\/em> payload, which will yield the raw data formatted as JSON with the following structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"messageType\": \"DATA_MESSAGE\",\n    \"owner\": \"123456789012\",\n    \"logGroup\": \"\/aws\/ec2\/ssh-attempts\",\n    \"<strong>logStream<\/strong>\": \"i-0ffe4156be759fb9e\",\n    \"subscriptionFilters\": &#91;\n        \"SshAccessFilter\"\n    ],\n    \"logEvents\": &#91;\n        {\n            \"id\": \"39081747012568859428767625513120880010897834134375366656\",\n            \"timestamp\": 1752486146299,\n            \"<strong>message<\/strong>\": \"<strong>Jul 14 09:42:21<\/strong> <strong>ip-172-31-34-86 <\/strong>sshd&#91;2374]: pam_unix(sshd:session): session opened for user <strong>ec2-user<\/strong>(uid=1000) by (uid=0)\",\n            \"extractedFields\": {\n                \"mm\": \"Jul\",\n                \"dd\": \"14\",\n                \"ip\": \"ip-172-31-34-86\",\n                \"text1\": \"session\",\n                \"text2\": \"opened for user ec2-user(uid=1000) by (uid=0)\",\n                \"time\": \"09:42:21\",\n                \"cmd\": \"sshd&#91;2374]:\",\n                \"pam\": \"pam_unix(sshd:session):\"\n            }\n        }\n    ]\n}<\/code><\/pre>\n\n\n\n<p>The key elements that will be used by the Lambda function in this raw data are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\"><strong>logStream<\/strong>\u00a0<\/span><br>This <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">field will contain the instance ID that generates the log, and the Lambda function will use this to identify the EC2 instance that needs to be terminat<\/span>ed. This is the result of setting the <strong>log_stream_name<\/strong> field in the CloudWatch configuration file to the value of <strong>{instance_id}<\/strong>.  <br><\/li>\n\n\n\n<li><strong>message<\/strong><br>This field will provide information such as the source IP, the date and time of access, and the user who logged in.<\/li>\n<\/ul>\n\n\n\n<h5 class=\"wp-block-heading\"><strong>The CloudWatch Subscription Filter<\/strong><\/h5>\n\n\n\n<p>The CloudWatch subscription filter will not send the entire log to the Lambda function. Instead, only a particular log record is sent. This record is a record of a successful login and is identified by the string pattern &#8220;<strong><em>opened for user*<\/em><\/strong>&#8220;. The CloudWatch subscription filter will apply the following filter pattern:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;mm, dd, time, ip, cmd, pam=\"pam_unix(sshd:session):\", text1=session, text2=\"opened for user*\" ]<\/code><\/pre>\n\n\n\n<h5 class=\"wp-block-heading\"><strong>The Sample Infrastructure<\/strong><\/h5>\n\n\n\n<p><span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">This&nbsp;<a href=\"https:\/\/github.com\/mylinuxsite-com\/aws-case-problems\/tree\/main\/ec2-ssh-alarm\" target=\"_blank\" rel=\"noopener\">CloudFormation template<\/a>&nbsp;creates the sample infrastructure that demonstrates the<\/span> solution described above.   The template will create the following AWS services:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>An EC2 instance with a public IP using an AMI Linux 2. The template will automatically install the CloudWatch agent and the rsyslog service. It will also copy a CloudWatch agent configuration file. The EC2 instance profile will be configured to allow you to connect to the instance using the System Manager.<br><\/li>\n\n\n\n<li>A Lambda function. Note that the Lambda function will not terminate the instance but only stop it. The reason is to allow you to perform multiple tests on the same instance.<br><\/li>\n\n\n\n<li>A CloudWatch log group<br><\/li>\n\n\n\n<li>A CloudWatch subscription filter.<\/li>\n<\/ol>\n\n\n\n<p>The template is written using the SAM model and must therefore be deployed accordingly. Hence, to deploy the template, use the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sam build\n\nsam deploy --parameter-overrides ParameterKey=SubnetId,ParameterValue=subnet-12345 ParameterKey=SecurityGroupId,ParameterValue=sg-12345 ParameterKey=EC2Name,ParameterValue=TestEC2 ParameterKey=SNSTopic,ParameterValue=MySNS <\/code><\/pre>\n\n\n\n<p>The template expects the following parameters to be supplied:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>SubnetId<\/strong> &#8211; This subnet must be a public subnet. <\/li>\n\n\n\n<li><strong>SecurityGroupId<\/strong> &#8211; The security group must open port 22 for SSH access.<\/li>\n\n\n\n<li><strong>EC2Name<\/strong> &#8211; The name of the EC2 where an SSH will be performed.<\/li>\n\n\n\n<li><strong>SNSTopic<\/strong> &#8211; The SNS topic where the notification will be sent. The SNS topic is not created in the template. You have to create it separately.<\/li>\n<\/ul>\n\n\n\n<p>The rest of the parameters are optional and will have the following default value if not supplied:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>AmiId<\/strong> &#8211; default to al2023-ami-kernel-6.1-x86_64<\/li>\n\n\n\n<li><strong>LogGroupName<\/strong> &#8211; default to \/aws\/ec2\/ssh-attempts<\/li>\n\n\n\n<li><strong>LogLevel<\/strong> &#8211; This is the Lambda log level, which defaults to INFO.<\/li>\n<\/ul>\n\n\n\n<h5 class=\"wp-block-heading\"><strong>Testing the Sample Infrastructure<\/strong><\/h5>\n\n\n\n<p>To test the infrastructure, connect to the EC2 using the <strong>EC2 Instance Connect.<\/strong> You cannot use an SSH client because the template will not generate any key pairs.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"http:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-06-01.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"354\" src=\"http:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-06-01-1024x354.png\" alt=\"\" class=\"wp-image-1614\" srcset=\"https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-06-01-1024x354.png 1024w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-06-01-300x104.png 300w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-06-01-768x266.png 768w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-06-01-1536x531.png 1536w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-06-01.png 1692w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>After a couple of seconds,<span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">&nbsp;your<\/span> EC2 instance state should change to&nbsp;&#8216;stopping&#8217;.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"74\" src=\"http:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-21-59-23-1024x74.png\" alt=\"\" class=\"wp-image-1615\" srcset=\"https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-21-59-23-1024x74.png 1024w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-21-59-23-300x22.png 300w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-21-59-23-768x55.png 768w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-21-59-23-1536x111.png 1536w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-21-59-23.png 1647w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>To check the notification, create an email subscription or an SQS subscription.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"http:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-12-18.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"390\" src=\"http:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-12-18-1024x390.png\" alt=\"\" class=\"wp-image-1616\" srcset=\"https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-12-18-1024x390.png 1024w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-12-18-300x114.png 300w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-12-18-768x292.png 768w, https:\/\/mylinuxsite.com\/wordpress\/wp-content\/uploads\/2025\/07\/Screenshot-from-2025-07-15-22-12-18.png 1417w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let us assume that your company has a policy that mandates immediate notification and termination of any EC2 instance accessed via SSH. How do you implement this? In this article, we will explore one solution that makes use of (1) CloudWatch Log Subscription, (2) Lambda and (3) SNS. I believe this option offers more flexibility [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":true,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[12],"tags":[],"class_list":["post-1568","post","type-post","status-publish","format-standard","hentry","category-articles"],"_links":{"self":[{"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1568","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1568"}],"version-history":[{"count":50,"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1568\/revisions"}],"predecessor-version":[{"id":1983,"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1568\/revisions\/1983"}],"wp:attachment":[{"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1568"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1568"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mylinuxsite.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1568"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}