Profile picture

[Shell Script] SSH 로그인 시 디스코드(discord)로 알림 보내기

JaehyoJJAng2024년 01월 02일

▶︎ 개요

image

현재 개발 서버를 pve로 운영 중인데

해당 서버의 경우 나뿐만 아니라 다른 사용자들도 접속할 일이 꽤 많다.

이에 따라 어떤 사용자가 접속하는지 서버에 들어가 주기적으로 모니터링을 해봐야 한다.

서버에 모니터링 솔루션을 도입하지 않았기 때문에 일일이 로그 분석을 하는 방법 외에는 딱히 방법이 없다.

그래서 생각해 본 방법 중에 하나가 서버에 사용자가 접속하게 되면 Access ID, User IP를 추출한 후, 디스코드 webhook을 사용하여 알림을 보내면 일일이 서버에 접속하여 로그를 확인할 필요가 없어질 것 같아서 셸 스크립트(Shell Script)로 한 번 구현해보기로 했다.


▶︎ 운영 환경

  • OS: Proxmox VE
  • VM OS: Ubuntu 22.04
  • Shell Script

▶︎ 디스코드 웹훅

‣ webhook URL 추출

알림받을 디스코드 채널을 준비하고, 해당 채널의 웹훅 URL을 가져와야 한다.


채널 설정 -> 연동 -> 새 웹후크 만들기 image
위처럼 webhook을 새로 만들어주고 해당 웹훅 URL을 복사하여 어딘가에 저장하도록 하자.


▶︎ 스크립트 작성

사용자가 서버에 접속하면 원하는 정보를 파싱하여 웹훅을 보내는 셸 스크립트를 만들어보도록 하자.

해당 셸 스크립트의 경우 /etc/profile.d 디렉토리 하위에 만들어줄거다.

최초 사용자가 로그인을 하게 되면 **Login Shell(로그인 셸)**로 동작하게 되는데

이 때 기본 로그인 셸은 bash이고, 로그인 셸로 동작할 때 profile을 읽도록 되어있다.

/etc/profile/etc/profile.d 하위에 존재하는 모든 셸 스크립트를 실행해주는 역할을 한다고 보면된다.

/etc/profile.d는 vim, qt, lang, color 등의 다양한 설정들이 sh File(셸 파일) 형태로 존재하게 되고, 최초 로그인 시 /etc/profile을 통해 실행되게 만든다.

그래서 로그인을 하게 되면 /etc/profile을 먼저 읽고, 그 이후 해당 계정의 홈 디렉토리에 존재하는 ~/.profile을 읽도록 동작한다!


그래서 이번에 작성할 셸 스크립트 또한 /etc/profile.d 디렉토리 하위에 생성할 것이다.

이유는 위에서 설명했듯이 전역적으로 모든 사용자가 로그인을 하게 되면 해당 스크립트가 동작할 수 있도록 하기 위해서!

‣ 코드

/etc/ssh/userAccess.sh

#!/usr/bin/bash
set -e

# ------------------
# SSH Access Web Hook notification
# Written by: Jaehyo (yshrim12@naver.com)
# ------------------

ACCESS_DATE=$(date +"%Y-%m-%d %T")
LOG_ACCESS_DATE=$(date +"%Y%m%d_%H%M")
LOG_DIR="/var/log/discord/webhook"
LOG_FILE="${LOG_ACCESS_DATE}_sshAccess.log"
ENV_FILE="/home/dev/discord/.discord.env"
DISCORD_WEBHOOK_URI="$(awk -F '=' '{print $2}' $ENV_FILE)"
SSH_USER_IP="$PAM_RHOST"
SSH_USER="$PAM_USER"
SSH_USERNAME="$(grep $USER /etc/passwd | awk '{print $5}' )"
HOST=$HOSTNAME
SERVER_IP=$(hostname -I | awk '{print $1}')
SERVER_OS=$(cat /etc/os-release | grep "PRETTY_NAME" | cut -d '"' -f2)

exec 3>> "$LOG_FILE"

function checkLogDir() {
    if [[ ! -d $LOG_DIR ]]; then
        echo "Creating ${LOG_DIR} ..." >&3
        mkdir -p "${LOG_DIR}"

        if [[ $? != 0 ]]; then
            echo "[$ACCESS_DATE] $LOG_DIR 디렉토리를 생성할 수 없었습니다" >&3
            exit 1
        else
            exec 3>> "$LOG_FILE"
            echo "[$ACCESS_DATE] $LOG_DIR 디렉토리를 성공적으로 생성하였습니다" >&3
        fi
    else
        echo "[$ACCESS_DATE] $LOG_DIR 디렉토리가 이미 존재합니다" >&3
    fi

    echo "#=============== [$ACCESS_DATE] SSH 접속 정보 알림 스크립트 시작 ... #===============" >&3
    checkUserProcess
}

function checkUserProcess() {
    # 사용자가 SSH 접속
    if [[ "$PAM_TYPE" != "close_session" ]]; then
        TITLE="[$ACCESS_DATE] 사용자 SSH 접속 확인"
        MESSAGE="SSH 접속 정보 알림\n접속자: $SSH_USER\n접속자 IP: $SSH_USER_IP\n접속 서버 : $HOST\n접속 서버 IP : $SERVER_IP"
        echo "[$ACCESS_DATE] SSH 접속 정보 알림 메시지 만들기 성공" >&3
        COLOR=16711680 # 빨간색
    fi

    # 사용자가 SSH 접속 해제 시
    if [[ "$PAM_TYPE" == "close_session" ]]; then
        TITLE="[$ACCESS_DATE] 사용자 SSH 접속 해제 확인!"
        MESSAGE="SSH 접속 해제 정보 알림\n접속자: $SSH_USER\n접속자 IP: $SSH_USER_IP\n접속 서버 : $HOST\n접속 서버 IP : $SERVER_IP"
        echo "[$ACCESS_DATE] SSH 접속 해제 정보 알림 메시지 만들기 성공" >&3
        COLOR=65280 # 형광 녹색
    fi
    sendDiscordWebhook
}

function sendDiscordWebhook() {
    echo "[$ACCESS_DATE] Discord로 SSH 접속 정보 알림 메시지 전송" >&3

    curl -H "Content-Type: application/json" -d "{
            \"embeds\":[{
                    \"title\":\"$TITLE\",
                    \"description\":\"$MESSAGE\",
                    \"color\":$COLOR}]
            }" "$DISCORD_WEBHOOK_URI"
    
    if [[ $? != 0 ]]; then
        echo "[$ACCESS_DATE] [error] Discord 알림 전송 실패!" >&3
    else
        echo "[$ACCESS_DATE] [error] Discord 알림 전송 성공!" >&3
    fi
}

checkLogDir

echo "[$ACCESS_DATE] === SSH 접속 or 해제 사용자 정보 ===" >&3
echo "[$ACCESS_DATE] === 접속 계정: $SSH_USER" >&3
echo "[$ACCESS_DATE] === 접속 계정 IP: $SSH_USER_IP" >&3

echo "[$ACCESS_DATE] === SSH 접속 or 해제 서버 정보 ===" >&3
echo "[$ACCESS_DATE] === 접속 서버: $HOST" >&3
echo "[$ACCESS_DATE] === 접속 서버 IP: $SERVER_IP" >&3

echo "@Author: Jaehyo\(yshrim12@naver.com\)" >&3
echo "#=============== [$ACCESS_DATE] SSH 접속 정보 알림 스크립트 종료 ... #===============" >&3

cat "$LOG_FILE" >| "$LOG_DIR/$LOG_FILE"
rm -rf "$LOG_FILE"
  • PAM을 이용하여 DiscordWeb hook을 전송할 수 있도록 하였음
  • DISCORD_WEBHOOK_URI에는 .discord.env 라는 파일에서 URI를 파싱하여 가져왔음
  • 접속 사용자 IP 주소를 파싱하기 위해 명령어를 사용하였음
    • SSH_CONNECTION: Unix 및 Unix-like 시스템에서 환경 변수로 사용되는 것으로 SSH 연결에 대한 정보를 제공.
    • 해당 변수는 SSH Session을 시작한 Client 및 서버 간의 연결 정보를 포함함

‣ 확인

셸 스크립트가 잘 작동하는지 확인해보자.

먼저 위 셸 스크립트를 /etc/ssh 경로에 위치시켜준 뒤 아래 명령어를 입력해주도록 하자.

# 스크립트에 실행 권한 부여
chmod +x /etc/ssh/userAccess.sh

# /etc/pam.d/sshd에 셸 스크립트 관련 설정
sudo sh -c 'echo "session optional pam_exec.so seteuid /etc/ssh/userAccess.sh" >> /etc/pam.d/sshd'

‣ SSH 접속

image
위처럼 서버 접속/종료 시 알림이 오는 것을 확인할 수 있다.


▶︎ 소스코드


Loading script...