● 환경
(OS) Rocky Linux release 8.10 (Green Obsidian)
(서버 1) 192.168.112.222, 로컬 서버 - 사용자
(서버 2) 192.168.112.218, 중앙 서버 - 접근 제어
(+) 추후 config 파일에서 불러와서 리스트 표시하는 것 시도
(+) 권한 제어를 통해 파일 변경 불가능하도록 설정
● 파일
(a) 로컬 서버
(1) 구조
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
local_server/
├── app.py
├── templates/
│ └── home.html
│ └── host_list.html
├── requirements.txt
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(2) 파일
(파일) requirements.txt
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Flask
Flask-SQLAlchemy
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(파일) app.py
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
import os
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()
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."
# route
@app.route('/')
def home():
# - render_template() : HTML 템플릿 파일 렌더링하여 클라이언트에게 반환
# - 렌더링 : 템플릿 파일(ex. HTML)을 실제로 브라우저에 표시할 수 있는 완성된 웹 페이지로 변환하는 과정
return render_template('home.html')
@app.route('/list')
def host_list() :
# - query.all() : 데이터베이스 모델(SSHConfigEntry)에서 모든 항목 가져오기
entries = SSHConfigEntry.query.all()
# - entries=entries : entries를 템플릿에 전달하여 화면에 표시
return render_template('host_list.html', entries=entries)
# methods=['POST'] : POST 요청 처리
@app.route('/add', methods=['POST'])
def add_entry():
# - host/ip/user/port = request.form['host/ip/user/port']
# : HTTP POST 요청에서 폼 데이터로 전송된 'host/ip/user/port' 필드의 값을 가져오기
host = request.form['host']
ip = request.form['ip']
user = request.form['user']
port = request.form['port']
# add_ssh_config_entry() 사용자 정의 함수 실행 및 반환된 메시지 저장
message = add_ssh_config_entry(host, ip, user, port)
# 플래시 메시지로, 웹페이지에 표시
flash(message)
# - redirect() : 리다이렉션
# - url_for() : 경로 지정
return redirect(url_for('home'))
@app.route('/delete/<int:entry_id>', methods=['POST'])
def delete_entry(entry_id):
# delete_ssh_config_entry() 사용자 정의 함수 실행 및 반환된 메시지 저장
message = delete_ssh_config_entry(entry_id)
flash(message)
return redirect(url_for('host_list'))
# 어플리케이션 실행
# - if __name__ == '__main__': 해당 스크립트가 직접 실행도리 때만 아래의 코드 실행
# - run() : 어플리케이션을 로컬 개발 서버에서 실행
if __name__ == '__main__':
app.run(host='0.0.0.0', port=2024)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(파일) home.html
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>SSH Config Manager</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1 class="mt-5">SSH Config Manager</h1>
<form action="{{ url_for('add_entry') }}" method="post">
<div class="form-group">
<label for="host">Host</label>
<input type="text" class="form-control" id="host" name="host" required>
</div>
<div class="form-group">
<label for="ip">IP</label>
<input type="text" class="form-control" id="ip" name="ip" required>
</div>
<div class="form-group">
<label for="user">User</label>
<input type="text" class="form-control" id="user" name="user" required>
</div>
<div class="form-group">
<label for="port">Port</label>
<input type="text" class="form-control" id="port" name="port" required>
</div>
<button type="submit" class="btn btn-primary">Add</button>
</form>
<a href="{{ url_for('host_list') }}">Host List</a>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-success mt-3" role="alert">
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
</div>
</body>
</html>
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(파일) host_list.html
----------------------------------------------------------------------------------------------------------------------------------------------------------------- <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>SSH Config Manager</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1 class="mt-5">Host List</h1>
<ul class="list-group">
{% for entry in entries %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>Host:</strong> {{ entry.host }}<br>
<strong>IP:</strong> {{ entry.ip }}<br>
<strong>User:</strong> {{ entry.user }}<br>
<strong>Port:</strong> {{ entry.port }}
</div>
<form method="post" action="{{ url_for('delete_entry', entry_id=entry.id) }}">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</li>
{% endfor %}
<a href="{{ url_for('home') }}">Back to Home</a>
</ul>
</div>
</body>
</html>
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(b) 중앙 서버 (프록시)
(파일) requirements.txt
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Flask
Flask-SQLAlchemy
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(파일) app.py
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
import subprocess
app = Flask(__name__)
app.secret_key = 'supersecretkey'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///iptables_rules.db'
db = SQLAlchemy(app)
class Rule(db.Model):
id = db.Column(db.Integer, primary_key=True)
ip = db.Column(db.String(15),nullable=False)
port = db.Column(db.String(30),nullable=False)
with app.app_context():
db.create_all()
def add_iptables_rule(ip, port):
try:
command = f"sudo iptables -A OUTPUT -p tcp -d {ip} --dport {port} -j REJECT"
subprocess.check_call(command, shell=True)
new_rule = Rule(ip=ip, port=port)
db.session.add(new_rule)
db.session.commit()
return f"Rule added for IP/PORT: {ip}/{port}"
except subprocess.CalledProcessError as e:
return f"Failed to add rule: {e}"
def delete_iptables_rule(ip, port):
try:
command = f"sudo iptables -D OUTPUT -p tcp -d {ip} --dport {port} -j REJECT"
subprocess.check_call(command, shell=True)
rule = Rule.query.filter_by(ip=ip, port=port).first()
if rule:
db.session.delete(rule)
db.session.commit()
return f"Rule deleted for IP/PORT: {ip}/{port}"
except subprocess.CalledProcessError as e:
return f"Failed to delete rule: {e}"
# route
@app.route('/')
def home():
# - render_template() : HTML 템플릿 파일 렌더링하여 클라이언트에게 반환
# - 렌더링 : 템플릿 파일(ex. HTML)을 실제로 브라우저에 표시할 수 있는 완성된 웹 페이지로 변환하는 과정
return render_template('home.html')
@app.route('/list')
def rule_list():
# - query.all() : 데이터베이스 모델(Rule)에서 모든 항목 가져오기
rules = Rule.query.all()
# - rules=ruless : entries를 템플릿에 전달하여 화면에 표시
return render_template('rule_list.html', rules=rules)
# methods=['POST'] : POST 요청 처리
@app.route('/add', methods=['POST'])
def add_rule():
# - ip/port = request.form['ip/port']
# : HTTP POST 요청에서 폼 데이터로 전송된 'ip/port' 필드의 값을 가져오기
ip = request.form['ip']
port = request.form['port']
# - Rule.query.filter_by() : 데이터베이스 필터링
# - .first() : 첫번째 값
if Rule.query.filter_by(ip=ip, port=port).first():
message = f"Rule already exists for IP/PORT: {ip}/{port}"
else:
# add_iptables_rule() 함수 불러오기
message = add_iptables_rule(ip, port)
# 플래시 메시지로, 웹페이지에 표시
flash(message)
# - redirect() : 리다이렉션
# - url_for() : 경로 지정
return redirect(url_for('home'))
# methods=['POST'] : POST 요청 처리
@app.route('/delete', methods=['POST'])
def delete_rule():
rule_id = request.form['rule_id']
rule = Rule.query.get(rule_id)
# delete_iptables_rule() 사용자 정의 함수 실행 및 반환된 메시지 저장
message = delete_iptables_rule(rule.ip, rule.port)
flash(message)
return redirect(url_for('rule_list'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=2024)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(파일) home.html
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>IPTables Rule Manager</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1 class="mt-5">IPTables Rule Manager</h1>
<form action="{{ url_for('add_rule') }}" method="post">
<div class="form-group">
<label for="ip">IP Address:</label>
<input type="text" class="form-control" id="ip" name="ip" required>
</div>
<div class="form-group">
<label for="port">Port:</label>
<input type="text" class="form-control" id="port" name="port" required>
</div>
<button type="submit" class="btn btn-primary">Add</button>
</form>
<a href="{{ url_for('rule_list') }}">Rule List</a>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-success mt-3" role="alert">
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
</div>
</body>
</html>
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
(파일) rule_list.html
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>IPTables Rule Manager</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1 class="mt-5">Current Rules</h1>
<ul class="list-group">
{% for rule in rules %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>IP:</strong> {{ rule.ip }}<br>
<strong>Port:</strong> {{ rule.port }}<br>
</div>
<form method="post" action="{{ url_for('delete_rule') }}" style="display:inline;">
<input type="hidden" name="rule_id" value="{{ rule.id }}">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</li>
{% endfor %}
</ul>
<a href="{{ url_for('home') }}">Back to Home</a>
</div>
</body>
</html>
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
'여러가지 > 테스트' 카테고리의 다른 글
[프로젝트] 접근 제어 - 전체 차단 / 대상 서버 허용 방식 (0) | 2024.06.18 |
---|---|
[테스트] 프록시 서버 방화벽 및 ssh (0) | 2024.06.18 |
[프로젝트] 접근 제어 - 웹 브라우저 (0) | 2024.06.17 |
[프로젝트] 접근 제어 - 스크립트 (0) | 2024.06.17 |
[실습] Blog (0) | 2024.06.15 |