Setting Up EC2 SSH Login Event Alerts to Slack (CloudWatch Logs + Lambda)

Knowing who is logging into your EC2 servers via SSH is a critical security concern. Beyond simple monitoring, sending login success and failure events directly to Slack allows you to quickly react to any suspicious activity. In this post, we’ll walk through how to set up alerts using CloudWatch Logs → Subscription Filter → Lambda → Slack Webhook.


1. Install CloudWatch Agent and Collect Logs

First, you need to forward SSH authentication logs from your EC2 instance to CloudWatch Logs.

  • Amazon Linux: /var/log/secure
  • Ubuntu/Debian: /var/log/auth.log

The exact file path and log format differ by OS, so make sure to verify them. After installing the CloudWatch Agent, configure cloudwatch-agent.json to ship these log files to a designated Log Group/Log Stream.

# Example for Amazon Linux
sudo yum install amazon-cloudwatch-agent -y
sudo vi /opt/aws/amazon-cloudwatch-agent/bin/config.json

For consistency, you can use a log group such as /ec2/ssh-login.


2. Create CloudWatch Log Group and Subscription Filters

Once logs are flowing into CloudWatch, you can forward specific log events to Lambda using subscription filters.

The challenge is that SSH log messages differ depending on the OS. For example:

  • Ubuntu: Accepted password for user ...
  • Amazon Linux: authentication success

This means you cannot simply copy one pattern and expect it to work everywhere. Always inspect your system logs and adjust the filter pattern accordingly.

In this example, we’ll create two filters:

  • "authentication success" (login success)
  • "authentication failure" (login failure)
aws logs put-subscription-filter \
  --log-group-name "/ec2/ssh-login" \
  --filter-name "ssh-auth-success" \
  --filter-pattern "authentication success" \
  --destination-arn arn:aws:lambda:ap-northeast-2:111111111111:function:sshAlertLambda

aws logs put-subscription-filter \
  --log-group-name "/ec2/ssh-login" \
  --filter-name "ssh-auth-failure" \
  --filter-pattern "authentication failure" \
  --destination-arn arn:aws:lambda:ap-northeast-2:111111111111:function:sshAlertLambda

⚠️ Important: If the filter pattern doesn’t match your actual logs, the events will never reach Lambda. In some environments, you might need to use patterns like "Accepted" or "Failed password" instead.


3. Create an Execution Role for Lambda

Lambda needs permissions to read from CloudWatch Logs and send alerts. Below is an example of a minimal IAM policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "logs:FilterLogEvents",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "lambda:InvokeFunction",
      "Resource": "*"
    }
  ]
}

4. Write the Lambda Function (Slack Webhook Integration)

Now let’s implement the Lambda function that posts login events to Slack.

⚠️ Security Note: Never hardcode your Slack Webhook URL. In production, store it in AWS Systems Manager Parameter Store or Secrets Manager. For demonstration purposes, we’ll use a simple hardcoded example.

import json, urllib.request, base64, gzip, re

def lambda_handler(event, context):
    cw_data = event['awslogs']['data']
    compressed_payload = base64.b64decode(cw_data)
    uncompressed_payload = gzip.decompress(compressed_payload)
    log_data = json.loads(uncompressed_payload.decode('utf-8'))

    for log_event in log_data['logEvents']:
        message = log_event['message']

        if "authentication success" in message:
            slack_message = f"SSH Login SUCCESS: {message}"
            post_message_to_slack(slack_message)
        elif "authentication failure" in message:
            slack_message = f"SSH Login FAILURE: {message}"
            post_message_to_slack(slack_message)

    return {"status": "done"}

def post_message_to_slack(text):
    webhook_url = "https://hooks.slack.com/services/xxxx/yyyy/zzzz"  # replace
    post_data = json.dumps({
        "text": text,
        "username": "SSH Alert",
        "icon_emoji": ":key:"
    }).encode('utf-8')
    request = urllib.request.Request(webhook_url, data=post_data, headers={'Content-Type': 'application/json'})
    urllib.request.urlopen(request)

This example sends raw messages to Slack. You can enhance it further by parsing fields such as user, rhost, and hostname using regex for cleaner output.


5. Test the Setup

  1. Attempt an SSH login (both success and failure).
  2. Verify that the event appears in CloudWatch Logs.
  3. Confirm that the Subscription Filter triggers the Lambda.
  4. Check your Slack channel for incoming alerts.

Summary

  • EC2 SSH login events are found in /var/log/secure or /var/log/auth.log.
  • Subscription Filter patterns vary by OS, so always validate against actual logs.
  • Avoid hardcoding Slack Webhook URLs—use a secure store instead.

With this setup, every SSH login attempt generates an immediate Slack notification, giving you quick visibility into your system’s security. It’s not overly complex, yet highly effective for real-world monitoring.

It’s also possible to integrate this setup with Zabbix for centralized monitoring, and I’ll dive into that in a future post.

ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. 본문 및 이미지를 무단 복제·배포할 수 없습니다. 공유 시 반드시 원문 링크를 명시해 주세요.
ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. All rights reserved. Unauthorized copying or redistribution of the text and images is prohibited. When sharing, please include the original source link.

🛠 마지막 수정일: 2025.09.26