Study

Intranet 환경 구축 진행 기록

1binni9 2026. 3. 26. 23:03

현재 인프라는 AWS EC2 기반으로 다음과 같이 구성되어 있다.

Client 서버
- Flask 기반 Intranet 애플리케이션
- Nginx Reverse Proxy
- SQLite DB
- 애플리케이션 로그 파일
- Filebeat

Attacker 서버
- 공격, 관리자 접근 시도 등 시나리오 실행

ELK 서버
- Elasticsearch
- Logstash
- Kibana

처음에는 Flask를 외부에 직접 노출했지만, 이후 Reverse Proxy 구조로 변경했다. 실제 서비스 환경에서는 애플리케이션 서버를 직접 노출하기보다 웹 서버를 앞단에 두는 것이 일반적이기 때문에, 이 부분은 비교적 초기에 수정한 설계였다. 방화벽이나 인증서 관련한 부분은 nginx에 대해 더 파봐야 하겠지만 일단 보류중.

초기 구현 단계에서는 app.py 하나에 모든 기능이 들어간 구조였다.(멍청하다구욕하지마세요 ㅠㅠ) 회원가입, 로그인, 관리자 페이지 등등.. 모든 걸 욱여넣다 보니 코드의 길이가 걷잡을 수 없이 길어져서 감당이 안 됐다. 지금 생각해보면 멍청하게 모든 걸 하드코딩하는 게 말도 안 됐다. 개발을..해본적이없고 원래 관심이 전혀 없었기에 ㅜㅜ

이 방식은 빠르게 구현하기에는 좋지만, 기능이 늘어날수록 구조가 급격히 복잡해졌다. 특히 관리자 기능과 일반 사용자 기능이 섞이면서 라우팅과 권한 처리가 혼재되기 시작했다.

결국 중간에 구조를 전면 수정했다.
현재는 다음과 같은 형태로 분리되어 있다.
 

 

auth_routes → 인증 관련
web_routes → 일반 사용자 페이지
admin_routes → 관리자 기능
docs_routes → 내부 자산(문서)

이 과정에서 가장 많이 발생한 문제는 예상대로 라우팅 오류였다. Blueprint로 나누면서 endpoint 이름이 바뀌었는데, 템플릿에서 이를 반영하지 않아 url_for 관련 에러가 반복적으로 발생했다.

단순한 문제지만, 구조 변경 시 영향을 받는 범위가 생각보다 넓다는 걸 체감한 부분이었다. 기능 구현을 아직 꽤 많이 못 했는데 role을 기존의 admin/user 에서 더 추가한 것만으로도 파일을 10개 가량을 손봐야 했다.

구조화된 로그를 Filebeat로 전송하고, Elasticsearch에서 필드 기반으로 조회할 수 있도록 구성했다.
다만 이 과정에서도 문제가 있었다. SSH 로그는 Kibana에서 정상적으로 보였지만, Flask 로그는 한동안 보이지 않았다. 원인을 찾아보니 JSON 파싱 설정과 Filebeat input 설정이 완전히 일치하지 않는 부분이 있었다. 인프라 구축하는 데 집중하고 있는 상황이라 일단 미뤄뒀다.

현재는 Flask 템플릿 기반 구조지만,
일부 기능은 /api/... 형태로 분리하려고 한다.


 

 

지금까지 실제로 발생했던 오류 / 주의해야 할 사항

브라우저에서 nginx 기준 502 Bad Gateway가 뜬 적이 있었다.
이건 HTML 문제가 아니라 보통 Flask 백엔드가 죽어 있는 상태였다.
실제 원인 후보

  • import 에러
  • gunicorn/nginx 뒤에서 backend 미기동

실제 발생했던 에러 예

ModuleNotFoundError: No module named 'flask'

 
즉 502가 뜨면 템플릿보다 먼저 아래를 확인해야 한다.

  • python app.py가 실제로 실행되는지
  • venv가 활성화되어 있는지
  • Flask 패키지가 설치되어 있는지
  • import 에러가 없는지

ImportError: cannot import name 'admin_bp' from 'routes.admin_routes'

이 에러가 실제로 발생했다.
핵심 원인

  • routes/admin_routes.py 파일 자체가 깨졌거나
  • admin_bp = Blueprint("admin", __name__)가 없거나
  • 파일 내부 문법/구조가 잘못됐거나
  • import 중 연쇄 에러가 발생했기 때문

중요한 점

  • 이 에러는 auth_routes.py가 아니라 admin_routes.py 문제였다.
  • app.py에서 아래 줄에서 죽는 것처럼 보여도
from routes.admin_routes import admin_bp

실제로는 admin_routes.py 내부 문제일 가능성이 높다.

dashboard.html이 파이썬 코드로 오염됨

로그인 후 대시보드 화면에 HTML이 아니라 파이썬 코드가 그대로 출력된 적이 있었다.
예를 들어 브라우저에 이런 식으로 보였음:

from flask import Blueprint, render_template, session, abort, Response
...

원인

  • templates/dashboard.html 파일에 HTML 대신 다른 Python 코드가 잘못 저장됨
  • 즉 파일 덮어쓰기 위치가 꼬였음

파이썬 파일 내용을 templates 폴더에 잘못 붙여넣는 사고가 실제로 있었다.
따라서 수정 후에는 항상 다음을 확인해야 한다.

  • templates/*.html은 첫 줄이 <!DOCTYPE html>인지
  • routes/*.py는 첫 줄이 Python import인지


SQLite 컬럼 추가 방식 문제 가능성

db.py에서 기존 테이블에 컬럼을 추가할 때 SQLite에서 문제가 날 수 있는 구문이 있었다.
 

ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP

이런 식의 default/current_timestamp 조합은 SQLite 버전/상황에 따라 문제가 될 수 있어서
더 안전하게는

  • 먼저 TEXT 컬럼만 추가
  • 그다음 UPDATE ... SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL

방식이 낫다.
즉 db.py 수정 시 ALTER TABLE은 보수적으로 다뤄야 한다.


특히 자주 꼬일 수 있는 지점

Blueprint 이름 / url_for 이름

이 프로젝트는 blueprint를 나눠 쓴다.

  • auth_bp
  • web_bp
  • admin_bp
  • docs_bp
  • profile_bp

그래서 url_for()는 반드시 blueprint 이름까지 포함해야 한다.
예:

url_for('auth.login')
url_for('web.dashboard')
url_for('admin.admin')
url_for('docs.internal_docs')
url_for('profile.profile')
  • 함수명만 쓰면 안 됨
  • blueprint 이름과 함수명이 둘 다 맞아야 함


관리자 페이지와 role 확장을 같이 수정해야 함

처음엔 admin/user만 있었는데, 지금은 admin/manager/user/guest 4단계 role 구조로 확장 중이다.
 

  • admin_routes.py는 4 role을 허용하도록 바꿈
  • 그런데 admin.html 드롭다운은 아직 user/admin만 보여줌

또는 반대로

  • admin.html은 guest/manager까지 선택 가능
  • 그런데 서버 로직이 ["user", "admin"]만 허용

즉 반드시 같이 수정해야 한다.

  • db.py의 VALID_ROLES
  • admin_routes.py
  • admin.html
  • 필요 시 auth_routes.py


회원가입에서 role 입력 받으면 안 됨

당연한 사항이나 보안상 중요한 포인트다.
회원가입 시 사용자가 직접 role을 보내게 하면 안 된다.

  • register 폼에서 role 입력 금지
  • 서버에서 무조건 기본 role 부여
    • 현재 기본값은 보통 "user"


기본 admin 계정 보호

관리자 기능 구현 시 다음은 반드시 막아야 한다.

  • 기본 admin 삭제 금지
  • 기본 admin 비활성화 금지
  • admin 자기 자신 삭제 금지
  • admin 자기 자신 role 다운그레이드 금지

관리자 페이지 테스트 중 조진다.


session["role"]과 DB role 불일치 가능성

로그인 성공 후 세션에 role을 저장한다.

session["role"] = user["role"]
  • DB 값이 이상하면 세션도 이상해질 수 있음
  • 그래서 로그인 시 VALID_ROLES 검증 후 보정하는 로직이 중요하다
safe_role = user["role"] if user["role"] in VALID_ROLES else "user"

템플릿과 라우트 파일 혼동 주의

현재 프로젝트는 templates/*.html과 routes/*.py를 자주 오가며 수정했기 때문에
파일을 잘못 덮어쓰는 사고가 다시 날 수 있다.
수정 전에 반드시 확인:

  • 지금 편집하는 파일이 templates/...인지
  • 아니면 routes/...인지

특히 다음 쌍을 혼동 주의:

  • templates/dashboard.html ↔ routes/web_routes.py
  • templates/login.html ↔ routes/auth_routes.py
  • templates/admin.html ↔ routes/admin_routes.py
  • templates/internal_docs.html ↔ routes/docs_routes.py

새 기능 추가 시 꼭 같이 신경 써야 하는 것

1) 기능만 넣지 말고 로그도 같이 넣기

이 프로젝트는 ELK/Kibana 시연이 목적 중 하나라서
새 기능을 추가할 때는 반드시 log_event()도 같이

  • 프로필 조회
  • 닉네임 변경
  • 비밀번호 변경
  • CAPTCHA 실패
  • 문서 다운로드
  • 접근 거부

단순히 화면만 동작하게 하지 말고,
누가 무엇을 했는지 남겨야 한다.


프로필 기능 추가 시 DB 컬럼 반영 여부 확인

nickname 기능을 넣으려면 users 테이블에 nickname 컬럼이 실제 있어야 한다.
즉 프로필 기능이 안 되면 제일 먼저 확인

  • db.py에 nickname 컬럼 추가 코드가 있는지
  • 실제 DB에 컬럼이 생겼는지
  • 기존 유저 nickname이 NULL인지


profile_bp 추가 시 app.py 등록 필수

프로필 라우트를 새로 만들면 반드시 app.py에 추가해야 한다.

from routes.profile_routes import profile_bp
app.register_blueprint(profile_bp)

이거 빠지면 템플릿에서 url_for('profile.profile') 호출 시 오류 난다.


비밀번호 변경 시 현재 비밀번호 검증 필수

프로필 기능에서 비밀번호 변경을 넣을 때
현재 비밀번호 확인 없이 바로 바꾸면 안 된다.
반드시

  • 현재 비밀번호 입력
  • 기존 해시 검증
  • 새 비밀번호 길이 검사
  • 새 비밀번호 확인 일치 검사
    를 거쳐야 함


5) CAPTCHA는 세션 기반이면 우회 가능성 있음

로그인 3회 실패 후 CAPTCHA 기능은 세션 기반 수식 CAPTCHA로 설계한 적이 있다.
이 방식은 간단하고 데모용으로는 좋지만 한계가 있다.

  • 같은 브라우저 세션 기준으로만 실패 횟수 계산
  • 브라우저 바꾸면 초기화될 수 있음
  • IP 기반 강제는 아님

즉 발표/시연용으론 충분하지만,
실서비스 수준 보안으로 설명하면 안 된다.


서버에서 작업할 때 추천 확인 절차

새로 작업 시작하면 무조건 아래 순서 추천.

1단계: 가상환경 활성화

cd ~/intranet
source venv/bin/activate

2단계: 문법 검사

python -m py_compile app.py db.py routes/auth_routes.py routes/web_routes.py routes/admin_routes.py routes/docs_routes.py

프로필 추가했다면:

python -m py_compile routes/profile_routes.py

3단계: 서버 실행

python app.py

4단계: 실제 브라우저 테스트

  • 로그인
  • dashboard
  • internal docs
  • admin
  • profile

5단계: 템플릿 오염 여부 확인

필요하면:

sed -n '1,40p' templates/dashboard.html
sed -n '1,40p' templates/login.html
sed -n '1,40p' routes/admin_routes.py

 

'Study' 카테고리의 다른 글

Game Abusing  (0) 2025.04.09