콘텐츠로 이동

감사 로그

변조 감지 Merkle 감사 체인, HMAC 서명, 조회/검증 API, 그리고 웹훅 감사 훅에 대한 설명입니다.

Revka는 명령 실행, 파일 접근, 설정 변경, 인증 결과, 정책 위반 등 모든 보안 관련 행위를 변조 감지 Merkle 해시 체인 감사 로그에 기록합니다. 각 항목은 이전 항목과 SHA-256으로 연결되므로, 어떤 레코드를 변경·삽입·삭제하더라도 체인이 끊어져 탐지가 가능합니다. 선택적으로 각 항목에 HMAC-SHA256 서명을 적용하고, REST API로 감사 내역을 조회하며, 필요 시 체인 무결성을 검증할 수 있습니다.

이 페이지에서는 감사 로깅 활성화, 서명 설정, 이벤트 형식 파악, 그리고 웹훅 감사 훅을 통한 SIEM 연동 방법을 설명합니다.

감사 로깅은 ~/.revka/config.toml[security.audit] 섹션에서 설정하며, 기본적으로 활성화되어 있습니다.

[security.audit]
enabled = true # default true
log_path = "audit.log" # relative to the revka/workspace dir
max_size_mb = 100 # rotate when the file reaches this size
sign_events = false # optional HMAC-SHA256 signing (see below)
타입기본값설명
enabledbooltrue마스터 스위치. false로 설정하면 디스크에 파일이 생성되지 않고 API는 빈 결과를 반환합니다.
log_pathstring"audit.log"로그 파일 경로로, 워크스페이스 디렉터리를 기준으로 해석됩니다.
max_size_mbu32100활성 로그가 이 크기(MB)에 도달하면 번호가 붙은 아카이브로 로테이션됩니다.
sign_eventsboolfalsetrue이면 각 항목에 HMAC-SHA256 서명이 적용됩니다. REVKA_AUDIT_SIGNING_KEY가 필요합니다.

로거는 새로 설치 시 로그 파일을 미리 생성하여 독자가 “파일 없음” 오류를 겪지 않도록 하며, 재시작 시 마지막 항목에서 체인 상태를 복원하여 새로운 쓰기가 기존 체인을 이어가도록 합니다.

모든 항목은 페이로드 외에 세 가지 체인 연결 필드를 포함합니다.

  • sequence0부터 시작하는 단조 증가 카운터.
  • prev_hash — 이전 항목의 entry_hash. 첫 번째(제네시스) 항목은 64개의 0으로 구성된 고정 시드를 사용합니다.
  • entry_hashSHA-256(prev_hash || canonical_JSON_of_content). 여기서 contentprev_hashentry_hash제외한 항목의 페이로드 필드입니다(sequence는 포함).

각 해시가 이전 해시를 포함하므로, 어떤 항목을 수정하면 그 entry_hash가 달라지고, 이는 다음 항목의 prev_hash와 일치하지 않게 되어 그 이후의 모든 레코드가 무효화됩니다. 시퀀스 번호는 반드시 연속적이어야 하므로 항목 삭제 시 간격으로 탐지할 수 있습니다.

쓰기는 뮤텍스와 배타적 파일 잠금으로 직렬화되어(두 데몬이 잠시 겹쳐 실행되더라도 바이트가 뒤섞이지 않음) 각 추가 쓰기는 내구성을 위해 fsync로 플러시됩니다. 시퀀스와 prev_hash는 쓰기가 성공한 후에만 증가하므로 I/O 실패로 인한 간격이 발생할 수 없습니다.

항목은 줄당 하나의 JSON 객체(JSONL)로 기록됩니다. 서명이 적용된 명령 실행 항목의 예시는 다음과 같습니다.

{
"timestamp": "2026-06-18T10:00:00Z",
"event_id": "f1c2…-uuid",
"event_type": "command_execution",
"actor": { "channel": "telegram", "user_id": "123", "username": "@alice" },
"action": { "command": "ls -la", "risk_level": "low", "approved": false, "allowed": true },
"result": { "success": true, "exit_code": 0, "duration_ms": 15, "error": null },
"security": { "policy_violation": false, "rate_limit_remaining": 19, "sandbox_backend": "firejail" },
"sequence": 42,
"prev_hash": "<64 hex chars>",
"entry_hash": "<64 hex chars>",
"signature": "<64 hex chars>"
}

signature 필드는 sign_events = true일 때 존재합니다. actor, action, result 객체는 이벤트 유형에 따라 없을 수 있습니다.

event_type의 값은 다음 중 하나입니다.

기록 시점
command_execution셸 명령이 실행될 때 (위험 수준, 승인 여부, 허용/차단, 소요 시간 포함).
file_access파일 작업이 평가될 때.
config_change설정이 변경될 때.
auth_success인증에 성공할 때 (예: 클라이언트 페어링 완료).
auth_failure인증에 실패할 때 (예: 잘못된 페어링 코드).
policy_violation보안 정책이 행위를 차단할 때.
security_event그 밖의 보안 관련 이벤트 (WebSocket 연결, 페어링 흐름).

해시 체인만으로는 변조를 감지할 수 있지만, 파일 전체를 재작성할 수 있는 공격자는 모든 해시를 재계산할 수 있습니다. sign_events를 활성화하면 각 entry_hash에 비밀 키 기반의 HMAC-SHA256 서명이 추가되어, 키 없이는 위조된 체인에 서명할 수 없습니다.

  1. 32바이트 키(64개의 16진수 문자)를 생성하고, 게이트웨이 시작 전에 REVKA_AUDIT_SIGNING_KEY 환경 변수로 내보내십시오 — 키는 로거 초기화 시 한 번만 로드됩니다.

    Terminal window
    export REVKA_AUDIT_SIGNING_KEY="$(openssl rand -hex 32)"
  2. 설정에서 서명을 활성화합니다.

    [security.audit]
    enabled = true
    sign_events = true
  3. 데몬을 시작합니다. 이후 기록되는 모든 항목에 signature가 포함됩니다.

서명된 레코드와 서명되지 않은 레코드는 순방향 호환됩니다. 서명 활성화 이전에 기록된 항목이 섞여 있는 체인도 문제없이 검증됩니다. signature가 있는 레코드는 키가 있을 때 검증되고, 없는 레코드는 건너뜁니다.

두 엔드포인트 모두 게이트웨이 API의 일부이며, 페어링 흐름에서 발급한 bearer 토큰이 필요합니다.

GET /api/audit?limit=50&event_type=command_execution&since=2026-01-01T00:00:00Z
Authorization: Bearer <token>
파라미터타입기본값설명
limitint50반환할 최대 이벤트 수. 500이 상한입니다.
event_typestring특정 이벤트 유형으로 필터링합니다 (위의 표 참조).
sincestringRFC 3339 타임스탬프 하한.

이벤트는 최신순으로 반환됩니다.

{
"events": [ /* AuditEvent objects, newest first */ ],
"count": 50,
"audit_enabled": true
}

감사 로깅이 비활성화된 경우, 엔드포인트는 오류 대신 {"events": [], "count": 0, "audit_enabled": false}를 반환하여 대시보드가 정상적으로 저하 동작을 수행할 수 있습니다.

Terminal window
curl -s "http://127.0.0.1:42617/api/audit?limit=20&event_type=policy_violation" \
-H "Authorization: Bearer rk_<token>"
GET /api/audit/verify
Authorization: Bearer <token>

활성 로그를 다시 읽어 모든 entry_hash가 올바르게 재계산되는지, 모든 prev_hash가 이전 항목과 연결되는지, 시퀀스 번호가 0부터 연속적인지, 그리고 서명 키가 있는 경우 모든 서명이 일치하는지 확인합니다.

{ "verified": true, "entry_count": 128 }

첫 번째 위반 발생 시 이유와 함께 반환합니다.

{ "verified": false, "error": "entry_hash mismatch at line 43 (sequence 42): expected …, got …" }
Terminal window
curl -s http://127.0.0.1:42617/api/audit/verify \
-H "Authorization: Bearer rk_<token>"

감사 로깅이 비활성화된 경우 응답은 {"verified": false, "error": "Audit logging not enabled"}입니다.

활성 로그가 max_size_mb에 도달하면 로테이션이 발생합니다. audit.logaudit.log.1.log가 되고, 기존 아카이브는 번호가 하나씩 올라가며(.1.2, … .9.10), 새로운 audit.log가 시작됩니다.

로테이션은 새 파일에 대해 체인을 제네시스부터 재시작하므로 /api/audit/verify는 활성 로그에 대해 즉시 성공합니다. 로테이션된 각 audit.log.N.log 아카이브는 자체적으로 완결된 체인이며 독립적으로 검증 가능합니다 — 연속적인 이력이 필요하다면 아카이브를 보관하십시오.

실시간 푸시 기반 감사 전달 — SIEM, 컴플라이언스 파이프라인, 중앙 집중식 로그 저장소에 연결 — 을 위해 내장 WebhookAuditHook을 사용하십시오. 이 훅은 설정된 글로브 패턴과 이름이 일치하는 모든 툴 호출에 대해 외부 HTTPS 엔드포인트로 JSON 페이로드를 POST하며, /api/audit을 폴링할 필요가 없습니다.

[hooks.builtin.webhook_audit]에서 설정합니다.

[hooks]
enabled = true
[hooks.builtin.webhook_audit]
enabled = true
url = "https://siem.example.com/revka/audit"
tool_patterns = ["Bash", "Write", "mcp__*"]
include_args = false
max_args_bytes = 4096
타입기본값설명
enabledboolfalse훅을 활성화합니다.
urlstring""대상 엔드포인트. https://여야 합니다 (보안 참고 사항 참조). 값이 비어 있으면 활성화 시 경고를 기록하고 이벤트를 버립니다.
tool_patternslist[]감사할 툴 이름의 글로브 패턴 (* 와일드카드, 예: mcp__*, *_write). 빈 목록이면 아무것도 감사하지 않습니다.
include_argsboolfalse페이로드에 툴 인수를 포함합니다. 시크릿이나 개인정보가 포함될 수 있으므로 필요한 경우에만 활성화하십시오.
max_args_bytesu644096직렬화된 인수를 이 바이트 수로 자릅니다 (0 = 무제한).

POST 본문의 형식은 다음과 같습니다.

{
"event": "tool_call",
"timestamp": "2026-06-18T10:00:00Z",
"tool": "Bash",
"success": true,
"duration_ms": 42,
"error": null,
"args": null
}

argsinclude_args = false인 경우(또는 캡처된 인수가 없는 경우) null입니다. 전달은 fire-and-forget 방식으로, 실패 시 로그에 기록되지만 에이전트를 차단하거나 중단시키지 않습니다. 훅은 수명 주기 파이프라인의 마지막 단계에서 실행됩니다.

외부 엔드포인트 없이 툴 호출을 데몬의 구조화 로그에 출력하기만 원한다면, 대신 CommandLoggerHook을 활성화하십시오 — 완료된 각 툴 호출의 툴 이름, 소요 시간, 성공 여부를 tracing에 기록합니다.

[hooks.builtin]
command_logger = true