JITA 인가 시스템, 왜 룰 엔진으로 재설계해야 했나
인가(Authorization) 로직은 서비스가 성장할수록 걷잡을 수 없이 복잡해진다. 처음엔 단순한 if-else 분기로 시작하지만, 요구사항이 쌓일수록 조건이 중첩되고, 어느 순간 누구도 전체 흐름을 한눈에 파악하기 어려운 코드가 된다. HubSpot도 동일한 문제에 직면했다. 내부 운영자가 특정 고객 데이터에 임시로 접근하는 JITA(Just-In-Time Access) 시스템이 복잡한 조건부 로직으로 얽히면서, 유지보수와 감사(Audit) 모두 어려워진 것이다.
DAG 기반 룰 엔진 아키텍처의 핵심 설계
HubSpot의 해결책은 인가 규칙 하나하나를 **독립적인 단위(Rule)**로 분리하고, 이를 DAG(Directed Acyclic Graph, 방향 비순환 그래프) 구조로 조직화하는 것이었다. DAG를 사용하면 각 룰 간의 의존성을 명시적으로 표현하면서도, 순환 참조 없이 평가 순서를 보장할 수 있다.
// 개념적 예시: 독립 룰 단위 구조
public interface AuthorizationRule {
RuleResult evaluate(AccessRequest request);
List<String> getDependencies(); // DAG 엣지 정의
}
public class PortalOwnershipRule implements AuthorizationRule {
@Override
public RuleResult evaluate(AccessRequest request) {
boolean isOwner = portalService.isOwner(request.getPortalId(), request.getRequesterId());
return RuleResult.of(isOwner, "portal_ownership_check");
}
}
이 구조의 핵심 장점은 각 룰이 단일 책임을 가진다는 점이다. 특정 조건이 변경되더라도 해당 룰만 수정하면 되고, 다른 룰에 사이드 이펙트가 발생하지 않는다. 또한 DAG 평가 엔진이 의존 관계를 분석해 병렬 실행 가능한 룰을 동시에 처리할 수 있어 성능 측면에서도 이점이 있다.
옵저버빌리티와 거버넌스 확보
기존 조건부 로직 방식의 가장 큰 문제는 왜 접근이 허용 또는 거부됐는지 추적하기 어렵다는 것이었다. 룰 엔진 아키텍처는 각 룰의 평가 결과를 구조화된 메타데이터로 기록함으로써 이 문제를 해결한다.
{
"request_id": "req-20240801-1234",
"decision": "APPROVED",
"rules_evaluated": [
{ "rule": "portal_ownership_check", "result": "PASS" },
{ "rule": "requester_role_check", "result": "PASS" },
{ "rule": "risk_score_threshold", "result": "PASS" }
],
"evaluated_at": "2024-08-01T10:23:45Z"
}
이렇게 룰 단위의 결정 근거가 남으면, 보안 감사 시 특정 접근 허용의 이유를 즉시 설명할 수 있다. 운영팀 입장에서는 어떤 룰이 병목인지, 어떤 룰이 가장 자주 실패하는지를 메트릭으로 모니터링할 수 있어 옵저버빌리티가 크게 향상된다. 여기에 거버넌스 워크플로우를 룰 단위로 연결하면, 특정 고위험 룰이 실패했을 때 자동으로 승인 프로세스를 트리거하는 등의 운영 자동화도 자연스럽게 구현된다.
정리
- 복잡한 조건부 인가 로직은 독립적인 룰 단위로 분리하고, DAG로 의존성을 명시해 유지보수성과 확장성을 동시에 확보할 수 있다.
- 룰별 평가 결과를 구조화된 메타데이터로 기록하면 감사 추적과 옵저버빌리티가 크게 개선된다.
- 룰 엔진 아키텍처는 인가 시스템뿐 아니라 복잡한 비즈니스 정책 판단이 필요한 모든 영역에 적용 가능한 범용 설계 패턴이다.