정산 보고서에 필요한 데이터만 허용하기

PostgreSQLBeginner
지금 연습하기

소개

MarketLedger에는 소규모 시장 판매자를 위한 정산 보고서가 필요합니다. 보고서는 고객 이메일 주소나 은행 참조 정보가 아닌 어제의 합계를 읽어야 하며 주문을 쓰면 안 됩니다. 현재 계정 생성 정책은 이 작업에 필요한 범위보다 넓습니다.

정책을 좁히고 OpenBao를 통해 실제 임시 PostgreSQL 로그인을 발급받은 뒤 제공된 쿼리로 완전한 탭 구분 보고서를 저장합니다. 기본적인 터미널 및 경로 사용 능력이 필요합니다. SQL 권한, 자격 증명 파일, 관련 명령 옵션은 사용 전에 설명하며 Python 프로그래밍이나 클라우드 계정은 필요하지 않습니다.

상점과 필요한 보고서 살펴보기

이 단계에서는 권한을 바꾸기 전에 작동 중인 업무와 정상 보고서부터 살펴봅니다. 실습 내내 Terminal을 사용하세요. cd는 디렉터리를 변경하며 절대 경로는 어디서나 작동합니다.

cd /home/labex/project/market-report

PostgreSQL은 준비된 데이터베이스입니다. OpenBao는 제한된 시간 동안 사용할 새 로그인을 데이터베이스에 생성하도록 요청할 수 있는 준비된 비밀 관리 서비스입니다. 둘 다 이 실습의 컨테이너에서 실행되는 실제 프로세스이며 호스팅 데이터베이스나 프로덕션 비밀 관리 배포는 아닙니다.

준비된 shop.py는 작동 중인 상점 서비스의 작은 클라이언트입니다. 실행하기 위해 Python을 작성할 필요는 없습니다. list는 오늘 주문을 보여 줍니다.

python3 shop.py list

처음에는 빈 목록 []가 나와야 합니다. 정상 주문 하나를 넣으세요. --order는 주문, --vendor는 판매자를 식별하며, --cents는 정수 센트 금액을 지정합니다.

python3 shop.py place --order O201 --vendor V101 --cents 600

합성 거래일 2026-10-29의 결제된 주문이 저장되어야 합니다. 명령을 반복하면 서비스가 중복 ID를 거부합니다. 삭제하거나 덮어쓰지 말고 list로 기존 주문을 확인하세요.

정산은 전날인 2026-10-28을 대상으로 합니다. 오늘의 새 주문은 어제 합계를 바꾸면 안 됩니다. cat은 텍스트 파일을 출력합니다. 준비된 정상 예시 보고서를 살펴보세요.

cat demo.tsv

판매자 행 세 개가 나와야 합니다.

vendor_id vendor_name paid_orders total_cents
V101 Birch Pantry 2 4700
V102 Elm Bakery 1 2000
V103 Maple Kitchen 1 2700

구분자는 탭 문자이므로 확장자가 .tsv입니다. 결제된 주문 네 건의 합계는 9400센트입니다. 취소된 Elm 주문은 제외됩니다. 예시는 별도로 준비된 읽기 계정으로 읽었습니다. 복사만 해서는 새 데이터베이스 신원으로 보고서를 실행할 수 있음을 증명하지 못합니다.

뷰는 선택한 결과를 노출하는 이름 있는 데이터베이스 쿼리입니다. reporting.daily_settlement는 판매자 이름과 결제 주문 합계를 결합하고 고객 이메일 주소와 은행 참조 정보를 제외합니다. 원본 레코드는 비공개 public.orders, public.vendors 테이블에 남습니다. 여기서 reporting과 public은 데이터베이스 객체를 묶는 스키마 이름입니다. public이라는 이름이 모든 역할에 접근을 허용해야 한다는 뜻은 아닙니다.

role.sql을 편집하고, settlement-report OpenBao 역할을 등록하며, 그 역할의 자격 증명을 발급받고, 보고서와 자격 증명 파일을 만들 수 있습니다. 상점, 원본 데이터베이스 레코드, 다른 보고서 역할, 제공된 쿼리, 환경 파일, 운영자 토큰, 준비된 런타임은 변경하지 마세요. 데이터베이스 관리자 비밀번호나 OpenBao 루트 토큰은 필요하지 않습니다.

일반적인 No such file or directory 오류는 위의 절대 경로 cd로 돌아가 ls로 실제 파일명을 확인하고 다시 시도하면 됩니다. 철자 오류를 고치려고 환경을 초기화하지 마세요.

계정 생성 정책을 정산 뷰로 제한하기

이 단계에서는 보고서에 필요 없는 권한을 제거합니다. Terminal의 /home/labex/project/market-report에서 시작하세요.

현재 계정 생성 구문을 읽으세요.

cat role.sql

OpenBao는 자격 증명을 발급할 때 세 자리표시자를 대체합니다. {{name}}은 새 데이터베이스 사용자명, {{password}}는 생성된 비밀번호, {{expiration}}은 계정 만료 시각이 됩니다. 이 자리표시자를 유지하고 고정 사용자명이나 비밀번호로 바꾸지 마세요.

CREATE ROLE ... WITH LOGIN은 PostgreSQL 로그인을 만듭니다. VALID UNTIL은 비밀번호로 새 연결을 인증할 수 있는 시간을 제한합니다. 이미 열린 모든 연결을 종료하지는 않습니다. 명시적 해지와 자연 임대 만료는 다음 가이드 실습에서 배웁니다.

GRANT는 권한을 부여합니다. USAGE ON SCHEMA reporting은 해당 스키마의 객체를 조회할 수 있게 합니다. SELECT ON reporting.daily_settlement는 그 뷰 하나를 읽을 수 있게 합니다. 현재 public에 대한 추가 권한은 비공개 테이블 읽기와 주문 변경을 허용하지만 이 보고서에는 둘 다 필요하지 않습니다.

마지막 ALTER ROLE ... SET log_statement = 'all' 줄은 새로 생성되는 각 보고서 로그인에 PostgreSQL 자체 SQL 감사를 활성화합니다. 해당 신원으로 실행한 쿼리를 추적할 수 있도록 유지하세요. 비밀번호가 들어 있는 관리용 계정 생성 구문은 이 로그인별 설정으로 기록되지 않습니다. 준비된 OpenBao 감사 로그도 비밀 값을 해싱합니다.

아래의 더 좁은 구문으로 파일을 교체하세요. cat > role.sql은 제공된 텍스트를 파일로 리디렉션합니다. <<'ROLESQL'은 here-document를 시작하며, 따옴표로 감싼 표시는 ROLESQL만 있는 줄까지 내용이 그대로 보존되게 합니다.

cat > role.sql <<'ROLESQL'
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT USAGE ON SCHEMA reporting TO "{{name}}";
GRANT SELECT ON reporting.daily_settlement TO "{{name}}";
ALTER ROLE "{{name}}" SET log_statement = 'all';
ROLESQL

다시 읽으세요.

cat role.sql

역할에는 생성된 로그인, 제한된 비밀번호 수명, 스키마 조회 권한, 하나의 뷰에 대한 읽기 권한이 있습니다. 원시 테이블 접근, 쓰기 권한, 관리자 속성은 없습니다. 여기서는 제공된 뷰가 의도적으로 비공개 데이터를 제외하므로 뷰 권한이 유용합니다. 비공개 필드를 노출하는 뷰라면 같은 경계를 제공하지 못합니다.

마지막 표시를 빠뜨려 터미널이 입력을 기다리면 한 줄에 ROLESQL만 입력하세요. 저장된 파일이 불완전하면 전체 블록을 반복하세요. 아직 실행 중인 데이터베이스는 바뀌지 않고 정책 파일만 바뀝니다.

제한된 OpenBao 운영자로 정책 등록하기

이 단계에서는 비밀 관리 서비스의 관리자 신원을 사용하지 않고 계정 정책을 등록합니다. Terminal에 머무르세요.

준비된 환경 파일을 로드하기 전에 읽으세요.

cat env.sh

source env.sh는 현재 터미널 셸에서 해당 변수 할당을 실행합니다. export는 자식 명령에서도 값을 사용할 수 있게 합니다. 이 파일은 셸의 명령 검색 목록인 PATH에 준비된 bao 도구를 추가하고, 로컬 OpenBao 및 PostgreSQL 주소를 설정합니다. $(cat ...)은 비공개 파일에서 준비된 운영자 토큰을 읽습니다. 토큰 자체는 출력되지 않습니다. 설정은 이 터미널 세션에 속하므로 새 터미널에서는 다시 로드해야 합니다.

source env.sh

OpenBao 운영자 토큰은 비밀 관리 서비스에 대한 요청을 승인합니다. PostgreSQL 사용자명이나 비밀번호가 아닙니다. 이 토큰은 settlement-report를 관리하고 자격 증명을 발급받으며 그 역할의 정확한 임대를 해지할 수 있습니다. 데이터베이스 관리 연결을 읽거나 별도의 재고 보고서를 관리할 수는 없습니다.

bao write는 지정된 OpenBao 경로에 설정을 씁니다. db_name=market은 준비된 데이터베이스 연결을 선택합니다. creation_statements=@role.sql은 SQL 파일을 필드 값으로 읽습니다. @는 OpenBao의 파일 입력 문법입니다. default_ttl=10m은 새 임대의 기본 시간을 10분으로 지정하고 max_ttl=15m은 수명 설정의 상한을 정합니다. TTL은 유효 기간(time to live)입니다. 보고서에는 몇 시간이나 무제한 계정 대신 이런 짧은 제한을 사용하세요.

bao write database/roles/settlement-report db_name=market creation_statements=@role.sql default_ttl=10m max_ttl=15m

database/roles/settlement-report에 데이터가 기록되었다는 확인이 나와야 합니다. 생성 정책이 바뀌었지만 아직 데이터베이스 로그인이 발급되지는 않았습니다. 정책 변경은 앞으로 생성할 계정에 적용됩니다. 이미 발급된 로그인의 권한은 제거하지 않습니다. 등록된 정책을 바꿀 때마다 새 자격 증명을 발급받고 그것으로 보고서를 다시 생성하세요. 완료 검사는 최종 정책 등록 후 발급된 보고서 신원을 요구합니다. 이전의 과도한 권한을 가진 보고서 로그인을 활성 상태로 남기지 마세요. 다음 단계의 조건부 복구에서는 자격 증명 파일을 교체하기 전에 정확한 이전 임대를 끝내는 방법을 설명합니다.

정책 자체 검사는 현재 등록된 정책을 읽고 일회용 PostgreSQL 테스트 환경에서 SQL을 시험합니다. 임대를 발급하거나 실제 데이터베이스를 수정하거나 실제 보고서를 실행하지 않습니다. sudo는 준비된 검사기가 보호된 설정을 살펴보고 격리된 일반 계정을 실행할 수 있게 합니다. 일반 OpenBao 및 데이터베이스 명령은 제한된 신원을 사용합니다.

sudo python3 /opt/labex-report/verify.py policy

추가 권한이나 만료 누락이 보고되면 role.sql을 수정하고 다시 등록하세요. 자리표시자와 감사 절은 유지하세요. 명시된 경계를 구현하는 동등한 권한 구문도 허용됩니다. 검사는 특정 편집기나 SQL 철자를 요구하지 않습니다.

임시 로그인 발급받고 실제 보고서 실행하기

이 단계에서는 새로 발급된 데이터베이스 신원으로 정산 파일을 만듭니다. env.sh를 로드한 같은 Terminal 세션에서 시작하세요.

자격 증명 응답에는 세 가지가 들어 있습니다. OpenBao에서 이번 발급을 식별하는 임대 ID, 생성된 PostgreSQL 사용자명, 해당 비밀번호입니다. 앞 단계의 운영자 토큰은 발급을 승인했으며 이 데이터베이스 자격 증명 중 하나가 아닙니다.

자격 증명 파일은 비공개로 유지하세요. umask 077은 새 파일을 자신의 계정만 읽고 쓸 수 있게 합니다. 기존 파일은 변경하지 않습니다.

umask 077

bao read는 역할의 자격 증명을 요청합니다. -format=json은 구조화된 JSON 출력을 선택합니다. >는 출력을 lease.json에 저장하고 같은 이름의 기존 파일을 교체합니다. 이 명령은 실제 새 PostgreSQL 로그인을 만들고 임대 시간을 시작합니다. 같은 임대를 살펴보려고 반복 실행하지 마세요.

bao read -format=json database/creds/settlement-report > lease.json

jq는 JSON 필드를 선택합니다. -r은 문자열을 JSON 따옴표 없이 출력합니다. 비밀이 아닌 신원, 임대 ID, 임대 시간을 읽고 비밀번호는 출력하지 마세요.

jq '.lease_id, .lease_duration, .data.username' lease.json

database/creds/settlement-report/... 형식의 임대 ID, 600초의 기간, 생성된 사용자명이 나와야 합니다. 무작위 접미사는 매번 달라집니다.

PostgreSQL 클라이언트 psql은 환경에서 PGUSER와 PGPASSWORD를 읽습니다. 따옴표로 감싼 $(...) 치환으로 이번 발급에서 값을 설정합니다. 이 값은 이 터미널 세션과 자식 명령에 유지됩니다.

export PGUSER="$(jq -r .data.username lease.json)"
export PGPASSWORD="$(jq -r .data.password lease.json)"

psql -X는 개인 psql 시작 설정을 무시하고, -v ON_ERROR_STOP=1은 SQL 오류 시 중지하며, -c는 SQL 명령 하나를 제공합니다. SELECT current_user는 이 연결에서 실제 사용하는 신원을 PostgreSQL에 묻습니다.

psql -X -v ON_ERROR_STOP=1 -c 'SELECT current_user;'

상점 쓰기 계정, 예시 읽기 계정, 데이터베이스 관리자가 아니라 lease.json과 같은 생성된 사용자명이 나와야 합니다.

준비된 report.sql은 공개 보고서 열 네 개를 선택하고 판매자 ID 순으로 행을 정렬한 뒤 헤더가 있는 탭 구분 데이터를 표준 출력으로 복사합니다. 쿼리는 변경하지 마세요. -f는 파일에서 SQL을 읽고 >는 보고서 출력만 저장합니다.

psql -X -v ON_ERROR_STOP=1 -f report.sql > settlement.tsv

실제 결과를 읽으세요.

cat settlement.tsv

판매자 행 세 개는 앞 업무 예시와 같아야 합니다. Birch는 2 / 4700, Elm은 1 / 2000, Maple은 1 / 2700입니다. 고객 이메일이나 은행 참조 정보가 나오면 안 됩니다. 오늘 O201 주문은 어제 정산 밖이므로 총합은 9400센트로 유지됩니다.

나중 연결이 password authentication failed로 실패하면 읽는 동안 10분 임대가 만료되었을 수 있습니다. 이는 테이블 쿼리 거부가 아니라 자격 증명 실패입니다. 자격 증명 발급 명령을 한 번 반복하고 새 파일에서 PGUSER와 PGPASSWORD를 모두 다시 로드한 뒤 신원 및 보고서 명령을 다시 실행하세요. 새 사용자명이 나오는 것이 정상입니다. 기록된 사용자명을 편집하거나 만료를 수동으로 연장하지 마세요.

이미 자격 증명을 발급한 뒤에 정책을 바꾼 경우에는 복구 방법이 다릅니다. 이전 계정은 원래 권한을 유지합니다. 해당 lease.json을 교체하기 전에 bao lease revoke "$(jq -r .lease_id lease.json)"를 실행하세요. 따옴표로 감싼 치환은 저장된 정확한 임대 ID를 전달합니다. 데이터베이스 사용자명이나 임대 접두사가 아닙니다. 재고 보고서나 임대 접두사 전체를 해지하지 마세요. 이전 발급을 끝낸 뒤 새 자격 증명을 요청하고, 데이터베이스 변수 두 개를 다시 로드하고, 위 명령으로 보고서를 다시 생성하세요. 이렇게 하면 새 제한 로그인 옆에 이전의 과도한 권한을 가진 로그인이 계속 사용 가능한 상태로 남지 않습니다.

비공개 데이터와 쓰기에 계속 접근할 수 없는지 확인하기

이 단계에서는 상점과 원본 데이터를 보존하면서 계정 경계를 시험합니다. 발급된 PostgreSQL 신원을 사용하는 같은 Terminal을 사용하세요.

다음 읽기는 실패해야 합니다. SELECT *는 모든 열을 요청합니다. 정책은 이 계정이 원시 주문을 읽기 전에 막아야 합니다.

psql -X -v ON_ERROR_STOP=1 -c 'SELECT * FROM public.orders;'

비공개 스키마나 테이블에 대한 PostgreSQL permission denied 오류가 나와야 합니다. $?는 바로 앞 명령의 종료 상태이며 echo는 이를 출력합니다. 즉시 읽으세요.

echo $?

SQL 쿼리 거부를 뜻하는 1이 나와야 합니다. 상태 2의 연결 실패는 이 권한 경계를 입증하지 않습니다. 임대가 만료되었다면 앞 단계의 재발급 및 재로드 복구를 사용하세요.

이제 데이터에 해가 없는 쓰기를 시도합니다. UPDATE ... WHERE false는 어느 행에도 일치하지 않으므로 계정에 잘못된 과도한 권한이 있더라도 주문을 바꾸지 못합니다. 그래도 권한 부족으로 거부되어야 합니다.

psql -X -v ON_ERROR_STOP=1 -c 'UPDATE public.orders SET amount_cents = 0 WHERE false;'
echo $?

다시 권한 오류와 상태 1이 나와야 합니다. 이 계정은 의도한 뷰를 읽을 수 있지만 원시 비공개 데이터를 읽거나 주문을 쓸 수 없습니다.

준비된 상점 클라이언트는 보고서 자격 증명이 아니라 상점 서비스의 별도 쓰기 신원을 사용합니다. 넣었던 주문이 여전히 있는지 확인하세요.

python3 shop.py list

읽기 전용 완료 검사를 실행하세요. 먼저 기존 PostgreSQL 쿼리 증거와 OpenBao 자체 발급 증거를 읽고, 실제 보고서와 보존된 업무 데이터를 확인합니다. 누락된 로그인을 만들거나 대신 보고서를 생성하지 않습니다.

sudo python3 /opt/labex-report/verify.py result

이제 실제 동적으로 발급된 제한된 데이터베이스 신원으로 만든 완전한 정산 결과가 있습니다. 다음 독립 실습은 올바른 권한 정책으로 시작하며 접근 종료와 새 연결 실패 관찰에 집중합니다.

요약

제한된 계정 생성 정책을 등록하고, 제한된 OpenBao 운영자 권한으로 새 PostgreSQL 로그인을 발급받아 실제 정산 결과를 저장했습니다. 뷰는 필요한 업무 합계를 제공하지만 비공개 테이블, 쓰기, 관리자 권한은 보고서 범위 밖에 남아 있습니다.

운영자 토큰, 임대 ID, 데이터베이스 자격 증명의 역할은 서로 다릅니다. 이 연습도 신뢰하는 준비된 관리 구성 요소에 의존합니다. 임시 계정이 이런 초기 신뢰를 없애 주지는 않습니다. 다음 실습에서는 정확한 임대 해지, 자연 만료, 새 발급을 통한 복구를 살펴봅니다.