(+) 대상 서버로 등록 시 아래의 스크립트 등록되도록 하기
(+) 데이터베이스 연동 및 실시간 조회 가능 웹 브라우저 구현하기
본 프로젝트에서는 로컬 서버에서 대상 서버 등록 시,
해당 대상 서버에 .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
'여러가지 > 테스트' 카테고리의 다른 글
[프로젝트] 대상 서버 삭제 시, 전송된 파일 삭제 (0) | 2024.06.24 |
---|---|
[참고] TUI - dialog (0) | 2024.06.21 |
[참고] 서버 로그 기록 (0) | 2024.06.18 |
[프로젝트] 접근 제어 - 전체 차단 / 대상 서버 허용 방식 (0) | 2024.06.18 |
[테스트] 프록시 서버 방화벽 및 ssh (0) | 2024.06.18 |