이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- 권한 거부 로그와 권한 변경 로그는 왜 목적이 다른가?
- 고객센터 대리 조회, break-glass, stale permission cache 같은 우회 위험을 어떻게 기록해야 하는가?
- 사고 후 “누가 왜 허용됐는가”를 복원하려면 어떤 필드가 필요한가?
개요
권한 감사 로그는 access log가 아니다. Access log는 보통 path, status, latency, IP를 남긴다. 권한 감사 로그는 actor, resource, action, decision, reason, policy, approval, previous/new value를 남긴다. 권한 문제는 실패 요청만으로 드러나지 않는다. 잘못 허용된 200 응답, 과도한 권한 부여, 오래된 cache, 고객센터 대리 조회, break-glass 사용이 더 위험할 수 있다.
공격 시나리오
운영자에게 긴급 조회 기능이 있다. 장애나 보안 사고 때 고객 정보를 빠르게 확인하기 위한 break-glass 기능이다.
공격자는 낮은 권한 운영자 계정을 탈취한 뒤 다음을 시도한다.
- 일반 조회 API는 막히자 break-glass endpoint를 찾는다.
- reason code를 아무 값으로 넣고 VIP 고객 정보를 조회한다.
- 감사 로그가 access log뿐이라 승인자와 사유가 남지 않는다.
- 며칠 뒤 사고가 발견되어도 누가 어떤 이유로 어떤 필드를 봤는지 복원할 수 없다.
권한 우회는 항상 403으로 드러나지 않는다. 잘못된 200과 빈약한 감사 로그가 더 큰 문제다.
취약한 요청/응답 예시
POST /admin/break-glass/members/9182/view HTTP/1.1
Host: admin.example.com
Authorization: Bearer support-agent-token
Content-Type: application/json
{
"reason": "urgent"
}HTTP/1.1 200 OK
Content-Type: application/json
{
"memberId": 9182,
"email": "vip@example.com",
"phone": "010-1234-5678",
"securityNote": "account takeover watch"
}access_log: POST /admin/break-glass/members/9182/view 200 35ms이 로그로는 actor, approval, viewed fields, policy reason을 알 수 없다.
주제별 핵심 판단
권한 관련 로그는 최소 두 종류로 나눈다.
authorization decision log:
- 어떤 요청을 허용/거부했는가
- 어떤 policy와 reason으로 결정했는가
authorization change log:
- 누가 누구의 권한을 바꿨는가
- 이전 권한과 이후 권한은 무엇인가
- 승인자, 사유, 만료 시간은 무엇인가거부 로그만 있으면 권한 상승을 놓친다. 변경 로그만 있으면 우회 시도를 놓친다.
개선된 요청/응답 예시
고위험 권한 예외는 별도 정책을 둔다.
- 개선된 break-glass 요청은 incident id, 승인자, 허용 시간, 조회 필드를 요구한다.
- 권한 감사 로그는 단순 access log가 아니라 decision reason과 이전/변경 값을 함께 남긴다.
방어 설계
break_glass_member_view:
allowed_actor: security_admin
required: MFA within 5 minutes
required: approval_id or incident_id
allowed_duration: 30 minutes
fields: masked contact by default
log: allowed and denied decisions
alert: security channel감사 로그 필드는 다음처럼 표준화한다.
event
actor_id
actor_role
subject_id
resource_type
resource_id
action
result
reason
policy_id
approval_id
previous_value
new_value
expires_at
trace_id권한 cache도 정책에 포함한다.
- role 변경 후 cache TTL은 얼마인가?
- token claim의 권한 version은 어떻게 갱신되는가?
- 권한 강등 후 기존 session은 어떻게 처리되는가?
- stale permission으로 허용된 요청을 탐지할 수 있는가?
Spring/HTTP 예시
@PostMapping("/admin/break-glass/members/{memberId}/view")
public BreakGlassMemberResponse viewWithBreakGlass(
@AuthenticationPrincipal AdminPrincipal principal,
@PathVariable long memberId,
@Valid @RequestBody BreakGlassRequest request) {
return breakGlassService.viewMember(
new BreakGlassCommand(principal.adminId(), memberId, request.incidentId(), request.reason()));
}@Transactional
public BreakGlassMemberResponse viewMember(BreakGlassCommand command) {
BreakGlassDecision decision = breakGlassPolicy.decide(
command.actorId(),
command.memberId(),
command.incidentId(),
mfaVerifier.verifiedRecently(command.actorId(), Duration.ofMinutes(5)));
audit.breakGlassDecision(command, decision);
if (decision.denied()) {
throw new AccessDeniedException("break-glass denied");
}
securityAlert.notifyBreakGlassUsed(command.actorId(), command.memberId(), command.incidentId());
return memberRepository.findBreakGlassView(command.memberId(), decision.allowedFields());
}권한 변경도 별도 이벤트로 남긴다.
public void recordRoleChanged(RoleChange change) {
audit.record("authorization_changed", Map.of(
"actor_id", change.actorId(),
"subject_id", change.subjectId(),
"resource_type", "team",
"resource_id", change.teamId(),
"previous_role", change.previousRole(),
"new_role", change.newRole(),
"reason", change.reason(),
"trace_id", change.traceId()
));
}운영 로그와 감사 지점
level=WARN event=break_glass_denied result=blocked actor_id=17 actor_role=SUPPORT_AGENT resource_type=member resource_id=9182 action=break_glass_view reason=mfa_required policy_id=BG-001 trace_id=ad8120c0level=CRITICAL event=break_glass_used result=allowed actor_id=3 actor_role=SECURITY_ADMIN resource_type=member resource_id=9182 action=break_glass_view incident_id=INC-77 fields=email_masked,phone_masked policy_id=BG-001 trace_id=ad8120c1level=INFO event=authorization_changed result=success actor_id=12 subject_id=1004 resource_type=team resource_id=42 previous_role=TEAM_MEMBER new_role=TEAM_MAINTAINER reason=project_onboarding trace_id=ad8120c2break-glass 허용은 보안 알림 대상이다. 단순 INFO 로그로만 묻히면 안 된다.
실전 판단 기준
- 권한 거부와 권한 변경이 다른 event 이름으로 남는가?
- 권한 변경, 권한 거부, 대리 조회는 서로 다른 event 이름으로 추적한다.
- 감사 로그가 없는 관리자 예외 경로는 권한 우회와 같은 위험으로 본다.
테스트 포인트
- break-glass에는 MFA, incident id, allowed duration이 필요한가?
- role 변경 로그에 previous/new role, actor, subject, reason이 남는가?
- 권한 cache가 stale할 때 허용이 계속되지 않는가?
- 고객센터 대리 조회가 일반 member 조회와 다른 audit event를 남기는가?
- 감사 로그에 개인정보 원문이나 token 원문이 남지 않는가?
위험 신호!
- 권한 감사 로그가 access log와 동일하다.
- 권한 변경 성공만 남기고 거부된 변경 시도를 남기지 않는다.
- break-glass가 일반 관리자 조회와 같은 로그로 남는다.
- role 변경 후 기존 token과 cache가 계속 이전 권한을 가진다.
- 감사 로그에 reason, approval, previous value가 없다.
- 운영자가 직접 DB를 조회하는 경로가 감사 체계 밖에 있다.
확인 질문
확인 질문
- 권한 거부 로그와 권한 변경 로그의 차이는 무엇인가?
- 거부 로그는 특정 action decision을 설명하고, 변경 로그는 권한 상태 자체가 어떻게 바뀌었는지 설명한다.
- break-glass가 위험한 이유는 무엇인가?
- 정상 정책을 우회하는 긴급 예외라서 승인, MFA, 만료, 알림, 감사 로그가 없으면 남용을 추적할 수 없기 때문이다.
- stale permission cache를 감사해야 하는 이유는 무엇인가?
- 권한 강등 후에도 오래된 cache나 token claim으로 접근이 계속 허용될 수 있기 때문이다.