본문 바로가기

여러가지/테스트

[프로젝트] 대상 서버 등록 시, 로그 기록 파일 전송

(+) 대상 서버로 등록 시 아래의 스크립트 등록되도록 하기

(+) 데이터베이스 연동 및 실시간 조회 가능 웹 브라우저 구현하기

 

본 프로젝트에서는 로컬 서버에서 대상 서버 등록 시,

해당 대상 서버에 .bashrc, log_command.sh 파일을 전송하여 대상 서버의 로그가 남도록 합니다.

 

● 환경

(OS) Rocky Linux release 8.10 (Green Obsidian)

(서버 1) 192.168.112.222, 로컬 서버 - 사용자

(서버 2) 192.168.112.218, 중앙 서버 - 접근 제어

 

 

● 파일

(a) 로컬 서버

 

(파일) app.py

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy

import os
import subprocess


app = Flask(__name__)
app.secret_key = 'supersecretkey'

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///ssh_config.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class SSHConfigEntry(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    host = db.Column(db.String(80), nullable=False)
    ip = db.Column(db.String(120), nullable=False)
    user = db.Column(db.String(80), nullable=False)
    port = db.Column(db.String(80), nullable=False)

with app.app_context():
    db.create_all()

def add_ssh_config_entry(host, ip, user, port):
    config_path = os.path.expanduser("~/.ssh/config")
    config_dir = os.path.dirname(config_path)

    if not os.path.exists(config_dir):
        os.makedirs(config_dir)

    if not os.path.exists(config_path):
        with open(config_path, 'w') as config_file:
            config_file.write("# SSH Config File\n")

    new_entry = f"""
Host {host}
    HostName {ip}
    User {user}
    Port {port}
    ProxyJump root@192.168.112.218
    """

    with open(config_path, 'a') as config_file:
        config_file.write(new_entry)

    entry = SSHConfigEntry(host=host, ip=ip, user=user, port=port)
    db.session.add(entry)
    db.session.commit()

    # 스크립트 실행 
    run_script_message = run_script(user, ip, port)

    # 메시지 반환
    flash("Host successfully added to SSH config and database.", "success")
    flash(run_script_message, "info")

 

    return f"New entry added to {config_path} and database."

def delete_ssh_config_entry(entry_id):
    config_path = os.path.expanduser("~/.ssh/config")
    entry = SSHConfigEntry.query.get(entry_id)

    if entry:
        with open(config_path, 'r') as config_file:
            lines = config_file.readlines()

        new_lines = []
        skip = False
        for line in lines:
            if line.startswith(f"Host {entry.host}"):
                skip = True

            if skip and line.strip() == "":
                skip = False
                continue
            if not skip:
                new_lines.append(line)

        with open(config_path, 'w') as config_file:
            config_file.writelines(new_lines)

        db.session.delete(entry)
        db.session.commit()

 

        return f"Entry {entry.host} deleted from {config_path} and database."
    else:
        return "Entry not found."

 

# 스크립트 실행

def run_script(remote_user, remote_host, remote_port):
    try:

        # subprocess.run(['명령어', '옵션' or '매개변수'], 기타 옵션) : 명령어 실행 함

        # check=True : 스크립트 비정상 종료 시, CalledProcessError 예외 발생 시키는 옵션

        # stdout=subprocess.PIPE : 스크립트의 표준 출력/표준 오류 출력을 파이프로 캡처하는 옵션

        # universal_newlines=True : 출력을 텍스트 형식으로 읽도록 설정

        # Bash 스크립트(setup_ssh_and_transfer.sh) 실행
        result = subprocess.run(['./setup_ssh_and_transfer.sh', remote_user, remote_host, str(remote_port)], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
        # 표준 출력 및 표준 오류 출력
        return "SSH key exchange and file transfer completed."
    except subprocess.CalledProcessError as e:
        return "Error occurred during script execution."

@app.route('/')
def home():
    return render_template('home.html')

@app.route('/list')
def host_list():
    entries = SSHConfigEntry.query.all()
    return render_template('host_list.html', entries=entries)

@app.route('/add', methods=['POST'])
def add_entry():
    host = request.form['host']
    ip = request.form['ip']
    user = request.form['user']
    port = request.form['port']

    message = add_ssh_config_entry(host, ip, user, port)

    flash(message)
    return redirect(url_for('home'))


@app.route('/delete/<int:entry_id>', methods=['POST'])
def delete_entry(entry_id):

    message = delete_ssh_config_entry(entry_id)
    flash(message)
    return redirect(url_for('host_list'))


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=2024)

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

(파일) setup_ssh_and_transfer.sh

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

#!/bin/bash

# 인자로 받은 변수 설정
REMOTE_USER=$1
REMOTE_HOST=$2
REMOTE_PORT=$3
FILE_TO_TRANSFER1="/home/local_server/file/bashrc"
FILE_TO_TRANSFER2="/home/local_server/file/logcommand"
REMOTE_PATH1="./.bashrc"
REMOTE_PATH2="/$REMOTE_USER/log_command.sh"

BACKUP_SUFFIX=".backup"

# SSH 키가 이미 있는지 확인하고 없으면 생성
if [ ! -f ~/.ssh/id_rsa ]; then
    echo "SSH 키를 생성 중..."
    ssh-keygen -t rsa -b 2048 -N "" -f ~/.ssh/id_rsa
fi

# SSH 키를 원격 서버로 복사
echo "SSH 키를 원격 서버로 복사 중..."
ssh-copy-id -i ~/.ssh/id_rsa.pub -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST

 

# 원격 서버에서 기존 파일 백업 및 새 파일 전송
echo "파일을 원격 서버로 전송 중..."
ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST "
    # .bashrc 파일이 존재하면 백업
    if [ -f $REMOTE_PATH1 ]; then
        mv $REMOTE_PATH1 ${REMOTE_PATH1}${BACKUP_SUFFIX}
        echo '.bashrc 파일이 ${REMOTE_PATH1}${BACKUP_SUFFIX}로 백업되었습니다.'
    fi

    # log_command.sh 파일이 존재하면 백업
    if [ -f $REMOTE_PATH2 ]; then
        mv $REMOTE_PATH2 ${REMOTE_PATH2}${BACKUP_SUFFIX}
        echo 'log_command.sh 파일이 ${REMOTE_PATH2}${BACKUP_SUFFIX}로 백업되었습니다.'
    fi
"


# 파일을 원격 서버로 전송
echo "파일을 원격 서버로 전송 중..."
scp -P $REMOTE_PORT $FILE_TO_TRANSFER1 $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH1
scp -P $REMOTE_PORT $FILE_TO_TRANSFER2 $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH2

echo "SSH 키 교환 및 파일 전송이 완료되었습니다!"

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

(파일) file/bashrc

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

# 프록시 서버 IP 주소
SPECIFIC_SERVER_IP="192.168.112.218"

# SSH 접속 여부와 접속한 서버의 IP 주소 확인
if [[ -n "$SSH_CLIENT" ]] || [[ -n "$SSH_TTY" ]]; then
    # 접속한 서버의 IP 주소 가져오기
    SSH_CONNECTION_INFO=$(echo $SSH_CLIENT | awk '{print $1}')

    if [[ "$SSH_CONNECTION_INFO" == "$SPECIFIC_SERVER_IP" ]]; then
        export HISTFILE=~/.bash_history_$(date +%Y%m%d%H%M%S)
        export PROMPT_COMMAND='history -a; bash ~/log_command.sh'
    fi
fi

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

(파일) file/logcommand

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

#!/bin/bash

# 현재 세션의 마지막 명령어 가져오기
LAST_COMMAND=$(history | tail -n 1 | sed 's/^[ ]*[0-9]\+[ ]*//')

# 명령어가 비어있지 않으면 로컬 서버로 전송
if [[ ! -z "$LAST_COMMAND" ]]; then
    echo "$LAST_COMMAND" | ssh user@local_server 'cat >> /path/to/destination/commands.log'
fi

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

[참고] 서버 로그 기록

https://uyijune15.tistory.com/236

 

로깅 및 확인

# ls ~/.bash_history_*
/root/.bash_history_20240618162741
# vi /root/.bash_history_20240618162741