본문 바로가기

여러가지/테스트

[프로젝트] 접근 제어 - 리스트 분리, 포트 추가 및 css 파일 적용

● 환경

(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>

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