이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 권한 거부 로그와 권한 변경 로그는 왜 목적이 다른가?
  • 고객센터 대리 조회, 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=ad8120c0
level=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=ad8120c1
level=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=ad8120c2

break-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으로 접근이 계속 허용될 수 있기 때문이다.

참고 문서