제한된 키로 작업 복구

사이버 보안Beginner
지금 연습하기

소개

노출된 지원 키는 폐기되었습니다. 서비스는 사용 가능하지만 요약 작업의 비공개 설정에 이전 값이 남아 있어 인증하지 못합니다. 대기 중인 daily 묶음의 요약이 아직 필요합니다.

권한 범위를 배우고 대체 키를 발급한 뒤 worker의 비공개 설정을 수정하여 실제 업무 실행을 완료합니다. 고객 디렉터리 접근이 여전히 금지되고 무관한 모니터가 정상인지도 테스트합니다. 이 실습은 차단 후 상태의 독립 환경을 제공하며 이전 VM을 재사용하지 않습니다.

읽기 전용 자격 증명 테스트

이 단계에서는 무효한 자격 증명과 유효하지만 권한이 부족한 자격 증명을 구분합니다. 새 환경은 차단이 끝난 상태입니다. key_support_old는 폐기되었고 작업은 이전 비밀값을 사용하며 daily는 미처리 상태입니다. 이전 실습 파일이나 VM은 필요하지 않습니다. 키와 운영자 토큰은 이 VM에 속합니다. 이전 환경의 자격 증명을 재사용하지 말고 여기서 값을 가져오세요.

Workstation은 /home/labex/project/repository의 유지보수 터미널입니다. Integration은 /home/labex/project에서 keyworker로 실행됩니다. Key Manager는 제공자의 유지보수 UI입니다. 로컬 교육 API는 http://127.0.0.1:8000에 있고 가상 동작은 이 과정에 한정됩니다.

**권한 범위(scope)**는 이름이 붙은 작업을 수행할 권한입니다. 최소 권한은 작업에 필요한 동작만 허용하고 무관한 동작은 사용할 수 없게 유지하는 것입니다. 요약 프로그램은 티켓을 읽고 요약을 게시하지만 고객 디렉터리는 읽지 않습니다.

작업 필요한 권한 범위 이 작업에 필요한가요?
GET /v1/tickets tickets:read 예: 입력
POST /v1/summaries summaries:write 예: 출력
GET /v1/customers customers:read 아니요
GET /v1/health health:read 아니요: 별도 모니터
GET /v1/results tickets:read 수신한 요약 보기

권한 실패 알아보기

준비된 읽기 전용 데모 키는 유효하지만 게시 권한이 없습니다. Workstation에서 이 키로 게시 요청을 보냅니다. curl -X POST는 쓰기 작업을 선택하고 -H는 헤더, -d는 JSON 본문을 제공합니다. -s는 진행을 숨기고 -w는 상태를 출력합니다. 제공자가 본문을 검사하기 전에 권한을 거부하므로 빈 객체 {}만으로 충분합니다. Content-Type: application/json 헤더는 본문이 JSON 형식임을 서비스에 알려줍니다.

curl -s -w '\nHTTP %{http_code}\n' -X POST -H 'Authorization: Bearer lab_demo_read_only' -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8000/v1/summaries
{"error":"Missing scope","required":"summaries:write"}
HTTP 403

이 API에서 403은 인증은 되었지만 필요한 권한 범위가 없다는 뜻입니다. 자격 증명 자체를 거부하는 401과 다릅니다. 읽기 전용 키로 읽기와 쓰기 작업을 완료할 수 없고, 전체 접근 키는 권한이 과도합니다. 대체 키에는 표의 업무 권한 두 개가 필요합니다.

전용 대체 키 발급하기

이 단계에서는 앞서 선택한 권한 범위 두 개로 전용 대체 키를 발급합니다. 비밀값은 다음 단계에서 비공개로 저장합니다. 이름은 식별용 라벨이며 권한을 결정하는 것은 scope입니다.

전용 자격 증명 발급하기

Workstation에서 파일을 출력하는 cat으로 운영자 토큰을 표시합니다.

cat /home/labex/.config/keylab/operator-token

Key Manager의 Operator token에 값을 입력하고 Sign in, Open Key Manager를 선택합니다. 운영자 인증은 애플리케이션 키와 분리됩니다. key_support_old가 이미 Revoked인지 확인합니다.

Issue a key 아래 Name에 Support summary replacement를 입력합니다. tickets:read와 summaries:write만 선택하고 Issue key를 누릅니다. 비밀이 아닌 키 ID와 Secret shown once가 나타납니다. 페이지를 나가기 전에 lab_key_로 시작하는 비밀값을 복사하세요. 제공자는 복구 가능한 비밀값 대신 해시를 저장합니다. 해시는 단방향 지문으로, 저장한 원본을 표시하지 않고 제공된 값을 비교할 수 있게 합니다.

값을 잃어버리면 미사용 키를 ID로 폐기하고 새로 발급합니다. 노출된 값을 재사용하거나 복사 오류를 해결하려고 권한을 넓히지 마세요.

비공개 런타임 설정 저장

이 단계에서는 대체 값을 서비스 계정의 비공개 런타임 파일에 저장하고 접근 권한을 확인합니다. 아래 명령은 모두 Integration에서 실행하세요.

런타임 설정은 프로그램 실행 시 읽는 파일입니다. 이 worker는 호출마다 /home/labex/project/runtime/job.ini를 읽으므로 재시작할 필요가 없습니다. 저장소 예제는 다른 파일입니다. Integration에서 현재 설정을 봅니다.

cat /home/labex/project/runtime/job.ini
[integration]
api_key = lab_key_support_exposed

비밀값을 저장소나 셸 명령 이력에 넣지 마세요. Bash의 read는 프롬프트에서 값을 받습니다. -r은 문자를 그대로 유지하고 -s는 입력을 숨기며 -p는 안내를 출력합니다. API_KEY는 임시 셸 변수입니다. Integration에서 실행하고 프롬프트에 새 비밀값을 붙여 넣은 뒤 Enter를 누릅니다. 입력은 보이지 않습니다.

read -r -s -p 'Replacement key: ' API_KEY

printf는 형식이 지정된 텍스트를 씁니다. \n은 줄바꿈, %s는 변수값 삽입, 큰따옴표는 값을 인수 하나로 유지하는 역할이며 >는 지정 파일을 덮어씁니다. 기존 비공개 파일은 현재 터미널 계정인 keyworker 소유입니다.

printf '[integration]\napi_key = %s\n' "$API_KEY" > /home/labex/project/runtime/job.ini

chmod로 접근 권한을 설정합니다. 모드 600은 소유자에게만 읽기와 쓰기를 허용하며 그룹과 다른 계정은 접근할 수 없습니다.

chmod 600 /home/labex/project/runtime/job.ini

ls -l은 비밀값을 출력하지 않고 소유자와 권한을 보여줍니다.

ls -l /home/labex/project/runtime/job.ini

-rw-------와 소유자 keyworker를 확인합니다. 크기와 날짜는 달라질 수 있습니다. 셸 변수만으로 파일이 바뀌지는 않습니다. 런타임 파일을 저장해야 다음 worker 호출이 새 자격 증명을 사용합니다.

이 Integration 터미널을 열어 두세요. API_KEY는 현재 셸에만 속하며 모든 터미널에서 사용할 수 있는 것은 아닙니다. 다음 단계에서 독립 요청에 사용한 뒤 삭제합니다. 터미널을 닫거나 재연결했다면 위의 cat 명령으로 job.ini의 현재 비밀값을 보고 이 단계를 반복하세요.

업무 실행 완료 및 검증

이 단계에서는 대기 중인 daily를 처리하고 허용 및 금지 동작을 모두 테스트합니다. worker는 준비되어 있으므로 /opt/keylab/worker.py를 변경하지 마세요. 자격 증명을 복구하는 과제이지 인증을 우회하도록 프로그램을 고치는 과제가 아닙니다.

Integration에서 지정 묶음을 실행합니다.

python3 /opt/keylab/worker.py daily
Read batch: daily (3 items)
{"batch":"daily","count":3,"subjects":["Reset password","Invoice copy","Delivery question"]}
Received batch: daily
Receiving key ID: ...

수신 ID는 Key Manager의 대체 키 ID와 같아야 합니다. 401이면 저장한 비밀값이 활성 대체 키의 값인지 확인하고, 403이면 표와 권한을 비교합니다. 필요하면 권한이 잘못된 대체 키를 폐기하고 올바르게 발급하여 비공개 파일을 수정한 뒤 재실행합니다. 같은 묶음을 다시 실행하면 수신 기록을 대체하며 요약을 중복 생성하지 않습니다.

실제 수신 서비스 살펴보기

같은 Integration 터미널을 계속 사용합니다. 이전 단계의 API_KEY 변수가 아직 있으므로 한 번만 표시되는 비밀값을 다시 입력할 필요가 없습니다.

읽기 전용 결과 엔드포인트를 사용합니다. 파이프라인 |는 응답을 다음 프로그램에 전달합니다. python3 -m json.tool은 Python JSON 포매터를 실행해 읽기 좋은 들여쓰기를 만듭니다.

curl -s -H "Authorization: Bearer $API_KEY" http://127.0.0.1:8000/v1/results | python3 -m json.tool

daily, payload의 제목 세 개, 대체 키의 key_id를 찾습니다. 로컬 성공 파일이 아니라 수신 API의 기록입니다. 백엔드는 원본 데이터를 읽은 자격 증명도 기록합니다.

이제 금지된 고객 디렉터리 요청을 테스트합니다.

curl -s -w '\nHTTP %{http_code}\n' -H "Authorization: Bearer $API_KEY" http://127.0.0.1:8000/v1/customers
{"error":"Missing scope","required":"customers:read"}
HTTP 403

예상한 거부는 새 키의 제한을 확인합니다. 임시 셸 변수가 더 이상 필요하지 않으면 unset으로 삭제하세요. 저장한 런타임 파일은 바뀌지 않습니다.

unset API_KEY

Workstation에서 이전 키가 여전히 사용 불가이고 모니터가 정상인지 확인합니다.

curl -s -w '\nHTTP %{http_code}\n' -H 'Authorization: Bearer lab_key_support_exposed' http://127.0.0.1:8000/v1/tickets
{"error":"Invalid or revoked key"}
HTTP 401
curl -s -w '\nHTTP %{http_code}\n' -H 'Authorization: Bearer lab_key_monitor_only' http://127.0.0.1:8000/v1/health
{"status":"ok","integration":"monitor"}
HTTP 200

마지막으로 git status --short는 변경되거나 추적되지 않은 저장소 파일을 간단히 나열합니다.

git status --short

안내대로 진행했다면 비공개 런타임 파일만 바꿨으므로 출력이 없습니다. 대체 값을 예제나 커밋에 넣지 마세요. 첫 실습처럼 눈에 보이는 줄이 사라져도 이력에 비밀값이 남을 수 있습니다.

요약

인증 실패와 권한 부족을 구분하고 읽기·쓰기 권한 조합을 선택했으며, 한 번만 표시되는 비밀값을 비공개로 저장하고 대기 중인 요약을 완료했습니다. 수신 기록, 이전 키 거부, 고객 접근 금지, 모니터 가용성을 확인했습니다. 올바른 교체는 정상 업무를 복구하고 노출된 자격 증명의 접근을 제거합니다.