호라이즌앱스 로고
← 기술 노트

업무 프로그램 개발 견적, 몇 배씩 차이 나는 이유는 화면 아래 층에 있습니다

임균영조회 —

책상 위에 견적서 두 장이 나란히 놓여 있습니다. 같은 요구사항 문서를 보내고 받은 것입니다.

화면 목록은 거의 똑같습니다. 로그인·계약 목록·고객 상세·엑셀 내려받기·관리자 화면까지 줄마다 대응됩니다.

그런데 합계는 몇 배가 차이 납니다. 구매 담당자는 그 차이를 대표에게 설명해야 하는데, 견적서에는 설명할 근거가 적혀 있지 않습니다.

저는 보험대리점(GA) 지사의 계약관리 시스템을 만들고 운영하고 있습니다. 화면 목록에는 한 줄도 나오지 않는 일을 이 시스템에 꽤 많이 깔았습니다.

그 층들을 실제로 깐 순서대로 하나씩 꺼내 보겠습니다. 견적서를 비교할 때 어느 줄을 물어야 하는지가 거기서 보입니다.

화면 목록이 같은 두 견적서

화면 목록은 사용자가 보는 것만 셉니다. 계약 목록 화면이 하나라는 사실은 두 견적서에서 같습니다.

달라지는 건 그 화면이 뒤에서 무엇을 지키느냐입니다. 설계사 한 명이 남의 고객을 볼 수 있는지, DB 파일이 새어 나갔을 때 전화번호가 그대로 읽히는지 같은 것들입니다.

이런 층은 시연에서도 안 보입니다. 담당자가 관리자 계정으로 눌러 보면 두 프로그램은 똑같이 동작합니다.

위쪽 화면은 두 견적이 같습니다. 금액 차이는 한쪽 아래에만 깔린 층에서 나옵니다.

지난 8월 저는 이 시스템의 전수 보안 감사를 외부 AI 코딩 에이전트에게 맡겼습니다. 리포지토리를 처음부터 끝까지 읽고 파일과 줄 번호를 근거로 문제를 적게 했습니다.

그 보고서 항목 중 DB 구조를 바꾸지 않아도 되는 11개를 먼저 고쳤습니다. 운영 서버에는 끊김 없는 교대 배포(blue-green)로 두 번 나눠 올렸습니다.

최상위 구멍 하나는 DB 구조 변경이 필요해서 이 묶음에서 뺐습니다. 그 구멍 이야기는 바로 아래에 이어집니다.

에이전트는 DB와 네트워크에 손이 닿지 않는 환경이라 실제 데이터 검증과 배포는 제 몫이었습니다.

남의 고객을 막는 자리

설계사가 자기 담당 계약만 보게 하는 방법은 크게 둘입니다. 화면에서 남의 계약을 숨기는 방법, 그리고 DB가 행마다 주인을 보고 걸러 주는 방법입니다.

화면 숨김은 싸고 빠릅니다. 대신 화면을 거치지 않는 요청 하나면 전부 보입니다.

이 시스템은 DB가 거르는 방식(행 단위 보안)을 쓰고 있었습니다. 그런데 감사가 찾은 최상위(P0) 구멍이 바로 이 벽에 있었습니다.

앱이 쓰는 DB 계정이 설정값 하나만 바꾸면 스스로 '전체 열람'으로 올라갈 수 있었습니다. 비밀번호가 새거나 입력창으로 SQL이 끼어드는 순간, 벽이 있으나 마나 해지는 구조였습니다.

고친 방법은 전체 열람을 아예 다른 DB 로그인 역할로 떼어 내는 것이었습니다. 앱 계정이 무슨 설정을 걸어도 그 역할이 될 수는 없습니다.

이 수정은 다음 날 따로 두 번에 나눠 배포했습니다. 교대 배포 중에는 옛 버전과 새 버전이 잠깐 같이 돌기 때문입니다.

첫 배포에서는 옛 경로와 새 역할을 둘 다 열어 두고 검증했습니다. 옛 경로는 두 번째 배포에서 지웠습니다.

같은 공격을 두 배포 사이와 마지막 배포 뒤에 다시 걸었습니다. 옛 경로가 남아 있을 때는 전체 계약이 나왔고, 지운 뒤에는 0건이 나왔습니다.

검증은 공격자처럼 했습니다. 관리용 접속에서 앱 계정 권한으로 바꾼 뒤 그 설정을 직접 걸고 계약과 인물 테이블을 조회했습니다.

둘 다 0행이 나왔습니다.

연락처를 잠그는 층

고객 연락처와 주소는 처음에 DB에 글자 그대로 들어 있었습니다. 백업 파일 하나가 새면 그대로 읽히는 상태였습니다.

이 층은 두 번에 나눠 깔았습니다. 먼저 암호문 칸을 새로 만들고, 저장할 때 평문과 암호문을 같이 쓰고, 읽을 때는 암호문을 먼저 보게 했습니다.

그다음 기존 5,013행을 암호문으로 채웠습니다. 행마다 암호화한 값을 다시 풀어 원래 값과 맞는지 왕복 15,686건을 검증했습니다.

평문 칸을 지우는 일은 되돌릴 수 없습니다. 그래서 지우기 직전에 평문은 있는데 암호문이 없는 행을 세어 0건인 것을 보고 나서 지웠습니다.

여기에는 갈림길이 하나 있었습니다. 계획서는 관리자만 원문을 보고 나머지는 가리는 방식을 권했습니다.

지사 쪽은 설계사 화면에 전화번호를 그대로 두기로 했습니다. 고객에게 전화하는 일이 설계사의 하루 업무라 가리면 일이 멈추기 때문입니다.

저장 암호화와 화면 표시는 서로 다른 문제입니다. 이 결정 덕에 가림 처리와 열람 버튼, 건별 열람 기록이 빠졌고 구현이 크게 줄었습니다.

누가 봤는지 남기는 층

주민등록번호는 관리자만 전체를 볼 수 있게 했습니다. 감사 이후 이 열람은 기록이 먼저 남아야 번호가 나가도록 바꿨습니다.

기록 저장이 실패하면 번호를 내보내지 않습니다. 접속한 IP도 같이 남깁니다.

그 열람 기록 테이블에는 덧붙이기만 되게 막는 장치를 달았습니다. 기록을 고치거나 지우는 요청은 DB가 거절합니다.

견적서에서 이 층은 보통 "로그 관리" 한 줄로 끝납니다. 기록이 실패할 때 원문이 나가는지, 기록을 지울 수 있는지까지 물어보세요.

백업이 정말 돌아오는지

백업은 대부분의 견적서에 들어 있습니다. 그 백업을 실제로 다른 곳에 풀어 본 적이 있는지는 거의 적혀 있지 않습니다.

저는 복원 리허설을 배포 절차에 넣었습니다. 그런데 나중에 보니 리허설 컨테이너가 운영보다 한 단계 낮은 Postgres 버전에 복원하고 있었습니다.

운영은 16, 리허설은 15였습니다. 리허설 컨테이너를 16으로 맞추고 다시 풀어 봤습니다.

처음 리허설을 돌린 날은 아예 중간에 멈췄습니다. DB 덤프에는 로그인 역할이 담기지 않아서 권한을 주는 줄에서 "그런 역할이 없다"는 오류가 났습니다.

백업은 매일 성공하고 있었고 크기도 정상이었습니다. 야간 백업에 역할 정의만 따로 떠 두는 파일을 더한 뒤에야 복원이 끝까지 갔습니다.

DB 통합 테스트 14개도 붙였습니다. 운영과 같은 세 역할로 임시 DB에 붙어 권한 경계를 확인합니다.

그중 "옛 전체 열람 조건이 어느 정책에도 남아 있지 않다"는 전수 검사 한 줄이 실제로 하나를 잡았습니다. 나중에 만든 테이블 정책이 옛 조건을 복사해 되살려 놓았던 것입니다.

되돌리는 길과 그 끝

배포가 실패했을 때 이전 버전으로 돌아가는 롤백 명령도 배포 도구에 넣었습니다. 장애가 난 자리에서 손으로 고치는 일을 줄이려는 장치입니다.

롤백에도 끝이 있습니다. 연락처 평문 칸을 지운 뒤로는 평문 칸을 읽는 옛 버전 앱으로 되돌리지 않기로 정했습니다.

옛 앱은 이미 지워진 칸을 읽으려 하기 때문입니다. 되돌릴 수 없는 변경이 들어간 배포부터는 롤백 대상이 새 버전들로 좁아집니다.

견적서에 "롤백 지원"이 적혀 있다면 되돌릴 수 없는 변경 뒤에는 어떻게 하는지도 물어보세요. 배포 도구가 성공이라고 보고했는데 실제로는 적용되지 않은 경우를 잡는 법은 외주 검수 글에 43과 42라는 숫자로 남겨 두었습니다.

빼도 되는 층

반대로 깔지 않아도 되는 층도 있습니다. 사내 그룹웨어를 만들 때 국내 협업툴을 조사하고 내린 결론이 그랬습니다.

수십 명 규모 조직에는 업무를 보드·플래닝·타임라인·캘린더로 바꿔 보는 기능이나 자동화 규칙은 과잉이었습니다. 보드와 담당자, 만기, 상태 정도면 충분했습니다.

비싼 견적이 이런 층을 잔뜩 얹은 결과일 수도 있습니다. 화면 목록 옆에 "우리 규모에 필요 없는 기능"을 적어 빼 달라고 하면 금액이 내려갑니다.

칸이 빈 견적서 읽는 법

비교표에 X가 많다고 그 견적을 탈락시킬 필요는 없습니다. 무엇을 빼서 싼지 알고 고르면 됩니다.

고객 개인정보가 들어가고 여러 사람이 서로 다른 범위를 봐야 한다면 위쪽 네 줄은 빼기 어렵습니다. 내부 일정표처럼 민감한 정보가 없다면 암호화와 열람 기록은 나중으로 미뤄도 됩니다.

화면 위로 드러나는 부분은 이음 데모에서 확인됩니다. 대표·관리자·설계사로 역할을 바꾸면 보이는 계약 범위가 달라지고, 주민번호는 뒷자리가 가려져 나옵니다.

데이터는 전부 가짜로 바꿔 두었습니다.

견적을 받기 전에 요구사항을 표로 먼저 세우고 싶다면 업무 시스템 도입 교육이 그 표를 같이 만드는 과정입니다.

받아 둔 견적서 두 장에서 어느 층이 빠졌는지 같이 읽어 보고 싶을 수도 있습니다. 그럴 땐 문의 양식에 견적서 항목을 붙여 보내 주셔도 됩니다.

견적 비교표

받은 견적서마다 아래 줄에 O나 X를 적어 보세요. 견적서에 문장이 없으면 X입니다.

확인할 줄견적서에서 찾을 문장빠졌을 때 감수할 것
역할별 열람 제한DB가 행 단위로 거르는가, 화면에서 숨기는가화면을 거치지 않는 요청 하나로 전체가 보임
개인정보 저장 암호화연락처·주소·주민번호를 암호문으로 저장하는가백업 파일 유출 시 원문 노출
열람·수정 기록기록 실패 시 원문을 막는가, 기록을 못 지우게 했는가사고 뒤 누가 봤는지 답할 수 없음
백업과 복원 리허설운영과 같은 버전에 실제로 풀어 보는가필요한 날 백업이 안 돌아올 수 있음
배포 실패 시 롤백되돌리는 절차와 배포 후 확인 항목이 있는가장애 때 손으로 복구
출시 후 유지보수 범위몇 달, 어떤 요청까지, 비용은 어떻게작은 수정마다 새 견적
우리 규모에 과잉인 기능빼 달라고 적은 줄이 반영됐는가쓰지 않는 기능 값을 냄

마지막 줄은 직접 채우는 칸입니다. 견적을 다시 받을 때 이 줄을 요구사항 문서 맨 아래에 붙여 보내면, 다음 견적서들은 같은 기준으로 금액을 적어 옵니다.

이어서 읽기