SOD란 무엇인가 — 직무 분리가 무너지면 벌어지는 일
SOD(Segregation of Duties, 직무 분리)는 한 사람이 하나의 비즈니스 프로세스를 처음부터 끝까지 혼자 처리하지 못하도록 권한을 나누는 내부통제 원칙입니다. 예를 들어 공급업체 마스터를 생성하는 사람이 대금 지급까지 실행할 수 있다면, 가공의 업체를 만들어 회삿돈을 빼돌리는 것이 기술적으로 가능해집니다. 실제 부정이 없더라도 "가능하다"는 사실 자체가 감사에서 지적 사항이 됩니다.
SAP 환경에서 이 문제가 특히 심각한 이유는, 권한이 트랜잭션 코드 하나가 아니라 수백 개의 Authorization Object 조합으로 결정되기 때문입니다. 역할(Role)을 편의 위주로 설계하다 보면 관리자도 모르는 사이에 위험한 권한 조합이 쌓입니다. 이 글에서는 SOD 충돌의 구조를 해부하고, SAP GRC Access Control 12.0을 연동해 위험을 탐지·완화·기록하는 방법을 실전 예제와 함께 다룹니다. 대상 환경은 SAP GRC Access Control 12.0 (NetWeaver 7.52 기반)과 SAP S/4HANA / ECC 백엔드이며, PFCG 역할 관리와 기본적인 ABAP 독해 능력이 있다는 전제로 진행합니다.
- SOD 충돌의 두 가지 유형(기능 vs 허가)을 구분할 수 있다
- Authorization Object 레벨에서 충돌이 발생하는 원리를 설명할 수 있다
- GRC ARA / EAM / ARM / BRM 네 모듈의 역할을 이해한다
- Firefighter ID 기반 긴급 접근 프로세스를 구성할 수 있다
- 감사 대응용 리스크 분석 자동화 코드를 작성할 수 있다
SOD 충돌 유형 — 기능 충돌과 허가 충돌은 다르다
SOD 충돌은 크게 두 층위로 나뉩니다. 이 구분을 못 하면 GRC 규칙셋을 읽을 수 없습니다.
| 구분 | 기능 충돌 (Function Level) | 허가 충돌 (Permission Level) |
|---|---|---|
| 판단 기준 | 트랜잭션 코드 조합 (예: FK01 + F110) | Authorization Object의 필드 값 조합 |
| 탐지 난이도 | 낮음 — 목록 대조로 가능 | 높음 — 객체·필드·값까지 분석 필요 |
| 오탐(False Positive) | 많음 — 실행 권한이 없어도 충돌로 표시 | 적음 — 실제 실행 가능성 기준 |
비유하자면 기능 충돌은 "열쇠 두 개를 갖고 있다"는 사실이고, 허가 충돌은 "그 열쇠가 실제로 금고를 연다"는 검증입니다. 사용자 메뉴에 FK01(공급업체 생성)과 F110(자동 지급)이 함께 보여도, F_BKPF_BUK 객체의 ACTVT가 03(조회)뿐이라면 실질 위험은 없습니다. GRC의 Access Risk Analysis는 일반적으로 Permission Level 분석을 기본으로 사용하도록 권장되며, 기능 레벨만으로 감사 보고서를 만들면 오탐 폭탄을 맞습니다.
위험 분류도 함께 알아둬야 합니다. GRC 표준 규칙셋은 리스크를 P(프로세스별 접두어: P001~ 등)로 코드화하고, Critical / High / Medium / Low 등급을 부여합니다. 대표적인 High 리스크 시나리오는 다음과 같습니다.
- 공급업체 마스터 생성/변경 + 송장 입력 (P2P 프로세스)
- 고객 크레딧 한도 변경 + 수주 생성 (O2C 프로세스)
- 사용자 생성(SU01) + 역할 할당(PFCG) — Basis 영역의 고전적 충돌
- 은행 계좌 마스터 변경 + 지급 실행
SAP 역할 설계의 함정 — Authorization Object가 진짜 범인이다
PFCG에서 역할을 만들 때 메뉴에 트랜잭션을 추가하면 SU24 제안값에 따라 권한 객체가 자동으로 딸려 들어옵니다. 문제는 여기서 시작됩니다. 담당자가 "노란 신호등을 없애려고" 필드를 별표(*)로 채우는 순간, 그 역할은 설계 의도보다 훨씬 넓은 권한을 갖게 됩니다.
전형적인 함정 세 가지를 짚어보면 다음과 같습니다.
- 와일드카드 남용 — S_TCODE에 F*를 넣으면 FB60(송장 입력)과 F110(지급 실행)이 동시에 열립니다. 트랜잭션 하나를 추가한 적이 없는데도 SOD 충돌이 생기는 이유입니다.
- 파생 역할의 조직 레벨 오설정 — 회사코드 1000만 허용해야 할 파생 역할에 상위 역할의 * 값이 상속되어 전 법인 권한이 열리는 사례가 흔합니다.
- 복합 역할(Composite Role)의 교차 충돌 — 단일 역할 각각은 깨끗해도, 복합 역할로 묶는 순간 A역할의 "마스터 생성"과 B역할의 "지급 실행"이 한 사용자에게 모입니다. SOD 분석은 반드시 사용자 단위로 해야 하는 이유입니다.
즉 SOD 관리의 최소 분석 단위는 트랜잭션이 아니라 Authorization Object + 필드 + 값의 삼중 조합이며, 집계 단위는 역할이 아니라 사용자입니다. 이 두 가지 원칙만 지켜도 감사 지적의 절반은 예방됩니다.
GRC Access Control 아키텍처 — 네 개의 모듈이 맞물리는 방식
SAP GRC Access Control 12.0은 별도의 NetWeaver ABAP 시스템에 설치되며, RFC와 플러그인(백엔드의 GRCPINW 애드온)을 통해 대상 시스템의 권한 데이터를 동기화합니다. 네 가지 핵심 모듈은 다음과 같습니다.
- ARA (Access Risk Analysis) — 규칙셋 기반으로 사용자/역할의 SOD 충돌과 크리티컬 권한을 분석하는 엔진. 전체 아키텍처의 심장입니다.
- ARM (Access Request Management) — 권한 요청 워크플로. 요청 시점에 ARA를 호출해 "부여하면 충돌이 생기는지"를 사전 시뮬레이션합니다.
- EAM (Emergency Access Management) — Firefighter ID를 통한 통제된 긴급 접근. 로그를 남기고 사후 검토를 강제합니다.
- BRM (Business Role Management) — 역할 설계·승인·생성 라이프사이클 관리. 역할을 만들기 전에 리스크 분석을 통과시키는 게이트 역할을 합니다.
동작 흐름을 도식화하면 이렇습니다.
# GRC AC 데이터 흐름 (개념도)
Backend(S/4HANA, ECC):
- GRCPINW 플러그인 → 권한/사용자 데이터 추출
GRC_Box:
- 동기화 잡: GRAC_REPOSITORY_OBJECT_SYNC (사용자/역할/프로파일)
- 규칙셋: Function → Risk → Rule 생성 (GRAC_GENERATE_RULES)
- ARA 배치: GRAC_BATCH_RISK_ANALYSIS → 위반 데이터 적재
Frontend:
- NWBC / Fiori Launchpad → 대시보드, 요청, 승인
중요한 설계 포인트는 "규칙 생성" 단계입니다. Function(기능: 트랜잭션+권한 묶음) 두 개가 Risk(위험)로 묶이고, 규칙 생성 잡이 이를 조합 폭발시켜 실제 매칭용 Rule로 전개합니다. Function 하나에 트랜잭션을 무분별하게 추가하면 규칙 수가 기하급수로 늘어나 배치 분석 성능이 급격히 나빠지므로, 일반적으로 Function은 비즈니스 행위 단위로 잘게 유지하는 것이 권장됩니다.
실전 1단계 — 충돌 탐지 기본 예제: 백엔드에서 직접 확인하기
GRC 도입 전이라도 백엔드 테이블로 1차 점검은 가능합니다. 아래는 "공급업체 생성 + 지급 실행" 조합을 가진 역할을 찾는 기본 예제입니다.
-- AGR_1251: 역할별 권한 객체/값 저장 테이블
-- 시나리오: 벤더 생성(XK01)과 지급 실행(F110)을 동시에 가진 역할 탐지
SELECT a.agr_name
FROM agr_1251 AS a
INNER JOIN agr_1251 AS b
ON a.agr_name = b.agr_name
WHERE a.object = 'S_TCODE' AND a.low = 'XK01'
AND b.object = 'S_TCODE' AND b.low = 'F110'
AND a.deleted = ' ' AND b.deleted = ' ';
이 쿼리는 기능 레벨 탐지라서 오탐이 섞입니다. 와일드카드(F*)로 열린 권한은 LIKE 조건을 별도로 처리해야 하고, 사용자 단위 집계는 AGR_USERS와 조인해야 합니다. 이 한계가 바로 GRC ARA가 필요한 이유입니다.
실전 2단계 — 실무 시나리오: 사용자 단위 사전 점검 리포트 (예외 처리 포함)
역할 배포 전에 특정 사용자에게 충돌이 생기는지 점검하는 ABAP 리포트입니다. 로깅과 예외 처리를 포함했습니다.
REPORT zsod_precheck_users.
" 시나리오: 신규 역할 배포 대상자들의 기존 권한과
" 위험 T-Code 조합 여부를 사전 점검
PARAMETERS: p_role TYPE agr_name OBLIGATORY.
TYPES: BEGIN OF ty_hit,
uname TYPE xubname,
tcd_a TYPE tcode,
tcd_b TYPE tcode,
END OF ty_hit.
DATA: lt_conflict_pairs TYPE TABLE OF ty_hit,
lv_msg TYPE string.
START-OF-SELECTION.
" 배포 대상 사용자 조회
SELECT uname FROM agr_users
INTO TABLE @DATA(lt_target_users)
WHERE agr_name = @p_role
AND to_dat >= @sy-datum.
IF lt_target_users IS INITIAL.
MESSAGE '배포 대상 사용자가 없습니다' TYPE 'S' DISPLAY LIKE 'W'.
RETURN.
ENDIF.
LOOP AT lt_target_users INTO DATA(ls_user).
TRY.
" 사용자 기준 실행 가능 T-Code 산출 (권한 버퍼 반영)
CALL FUNCTION 'ZGET_EXECUTABLE_TCODES'
EXPORTING iv_uname = ls_user-uname
IMPORTING et_tcodes = DATA(lt_tcodes)
EXCEPTIONS user_locked = 1 no_auth_data = 2.
IF sy-subrc <> 0.
lv_msg = |{ ls_user-uname }: 권한 데이터 조회 실패 rc={ sy-subrc }|.
" 애플리케이션 로그 기록 (BAL: 객체 ZSOD)
CALL FUNCTION 'BAL_LOG_MSG_ADD_FREE_TEXT'
EXPORTING i_msgty = 'W' i_text = lv_msg.
CONTINUE.
ENDIF.
" 위험 조합 매칭 (ZSOD_RISK_PAIR: 사내 정의 위험 테이블)
PERFORM match_risk_pairs USING ls_user-uname lt_tcodes
CHANGING lt_conflict_pairs.
CATCH cx_root INTO DATA(lx_err).
lv_msg = |{ ls_user-uname }: { lx_err->get_text( ) }|.
CALL FUNCTION 'BAL_LOG_MSG_ADD_FREE_TEXT'
EXPORTING i_msgty = 'E' i_text = lv_msg.
ENDTRY.
ENDLOOP.
cl_demo_output=>display( lt_conflict_pairs ).
포인트는 두 가지입니다. 첫째, 실패한 사용자를 건너뛰되 반드시 애플리케이션 로그(BAL)에 남겨서 "점검에서 누락된 사용자"가 감사 시 설명 가능하도록 합니다. 둘째, 위험 조합 정의를 하드코딩하지 않고 커스텀 테이블(ZSOD_RISK_PAIR)로 분리해 규칙 변경이 코드 수정 없이 이뤄지게 합니다.
실전 3단계 — 프로덕션: GRC 웹서비스로 프로비저닝 전 리스크 분석 자동화
GRC AC는 리스크 분석용 웹서비스(예: GRAC_RISK_ANALYSIS 계열)를 제공합니다. IdM이나 사내 포털에서 권한 부여 전에 이를 호출하면 "충돌 있는 요청은 아예 접수 단계에서 차단"하는 예방 통제가 됩니다.
// 사내 권한 포털(Node.js) → GRC 리스크 분석 웹서비스 호출
// 프로덕션 고려사항: 타임아웃, 재시도, 감사 로그, 자격증명 보호
const axios = require('axios');
async function preCheckRisk(reqUser, roleList) {
const payload = buildSoapEnvelope({ // SOAP 봉투 생성 헬퍼
reportType: 'SOD_PERMISSION_LEVEL', // 허가 레벨 분석
userId: reqUser,
simulationRoles: roleList, // 부여 예정 역할 시뮬레이션
ruleSetId: 'ZKR_RULESET' // 커스텀 규칙셋
});
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const res = await axios.post(process.env.GRC_ARA_ENDPOINT, payload, {
timeout: 30000,
auth: { // 자격증명은 Vault에서 주입
username: process.env.GRC_SVC_USER,
password: process.env.GRC_SVC_PASS
},
headers: { 'Content-Type': 'text/xml; charset=utf-8' }
});
const violations = parseViolations(res.data);
await auditLog.write({ user: reqUser, violations: violations.length });
return violations; // 0건이면 자동 승인 라인으로
} catch (err) {
if (attempt === 3) {
// 분석 실패 시 '통과'가 아니라 '보류' 처리 — fail-safe 원칙
await auditLog.write({ user: reqUser, error: err.message });
throw new Error('리스크 분석 불가: 요청을 수동 검토로 전환');
}
await sleep(2000 * attempt); // 지수 백오프
}
}
}
프로덕션에서 가장 중요한 원칙은 마지막 catch 블록에 있습니다. 분석 서비스가 죽었을 때 요청을 그냥 통과시키면(fail-open) 통제 공백이 생깁니다. 반드시 보류/수동검토(fail-safe)로 떨어뜨려야 하며, 이 설계 자체가 감사에서 통제 유효성의 증거가 됩니다. 또한 서비스 계정 자격증명은 코드·환경파일이 아닌 시크릿 저장소에서 주입하고, 모든 호출 결과를 감사 로그로 남깁니다.
Firefighter ID와 긴급 접근 — EAM 운영의 실제
아무리 SOD를 잘 설계해도 장애 대응처럼 넓은 권한이 일시적으로 필요한 순간은 옵니다. 이때 개인 계정에 SAP_ALL을 붙이는 대신 EAM의 Firefighter ID(FFID)를 사용합니다. 운영 흐름은 다음과 같습니다.
- 사전 준비: FFID(광범위 권한 보유 서비스 계정), FF 사용자(실무자), FF 소유자(Owner), FF 통제자(Controller)를 정의하고 매핑합니다.
- 사용: 실무자가 GRC 런치패드 또는 백엔드 트랜잭션(플러그인 기반 시나리오에서는 /GRCPI/GRIA_EAM 계열)에서 사유(Reason Code)를 입력하고 FFID로 세션을 엽니다.
- 기록: 세션 중 실행한 트랜잭션·변경 문서가 자동 수집됩니다. 수집 배치(GRAC_SPM_LOG_SYNC_UPDATE)가 로그를 GRC로 가져옵니다.
- 검토: Controller에게 로그 리뷰 워크플로가 발송되고, 검토 완료 서명이 남습니다.
감사인이 EAM에서 확인하는 것은 세 가지입니다 — 사유의 타당성, 로그 수집의 완전성, 그리고 검토가 실제로 수행되었는가. 로그만 쌓고 리뷰를 방치하면 "통제 설계는 있으나 운영 실패"로 지적됩니다. 일반적으로 리뷰 SLA(예: 사용 후 5영업일 이내)를 정책으로 문서화하는 것이 권장됩니다.
SOD 규칙셋 커스터마이징 — 표준 룰셋을 그대로 쓰면 안 되는 이유
SAP이 제공하는 표준 규칙셋(GLOBAL)은 출발점일 뿐입니다. 커스텀 트랜잭션(Z*), 산업 특화 프로세스, 국가별 규제(K-SOX 등)는 반영되어 있지 않습니다. 커스터마이징 절차는 다음 순서를 따릅니다.
- 표준 규칙셋을 복사해 사내 규칙셋(예: ZKR_RULESET)을 생성 — 표준을 직접 수정하면 업그레이드 시 덮어써질 수 있습니다.
- 커스텀 트랜잭션을 해당 Function에 추가하고, SU24 값 기준으로 Permission 조건을 등록합니다. 예: ZFI_VENDOR_UPLOAD를 "벤더 마스터 유지" Function에 편입.
- 비즈니스와 협의해 리스크 등급을 조정하고, 수용된 위험은 삭제가 아니라 Mitigation Control(완화 통제)로 문서화합니다. 규칙을 지우면 "위험을 숨겼다"는 지적을 받지만, 완화 통제를 달면 "위험을 인지하고 관리한다"는 증거가 됩니다.
- 규칙 생성 잡을 실행해 Rule을 재전개하고, 배치 리스크 분석을 재수행합니다.
실무 팁: 완화 통제에는 반드시 모니터링 주체·주기·증빙 위치를 기재하세요. "월 1회 지급 내역을 재무팀장이 샘플 검토하고 결과를 문서관리시스템에 보관"처럼 검증 가능한 문장이어야 감사에서 인정받기 쉽습니다.
감사 대응 워크플로와 흔한 실수 — FAQ로 정리
감사 시즌에 GRC에서 뽑는 표준 증적은 (1) 사용자 레벨 SOD 위반 리포트, (2) 완화 통제 매핑 현황, (3) EAM 로그 및 리뷰 완료 이력, (4) 권한 요청 승인 이력(ARM), (5) 주기적 접근 검토(UAR) 결과입니다. 자주 나오는 질문을 정리합니다.
Q1. 리스크 분석 결과 위반이 수만 건인데 어디서부터 손대야 하나요?
A. 먼저 Permission Level로 재분석해 오탐을 걸러내고, Critical/High 등급 + 실제 사용 이력(액션 사용 로그)이 있는 사용자부터 처리합니다. 일반적으로 "위반 제로"가 아니라 "모든 잔여 위반에 완화 통제가 매핑된 상태"가 현실적인 목표입니다.
Q2. 배치 리스크 분석이 며칠씩 걸립니다.
A. Function에 트랜잭션이 과다 등록되어 규칙이 조합 폭발했을 가능성이 큽니다. 사용하지 않는 규칙셋 비활성화, 분석 범위를 변경분(delta)으로 제한, 멀티 스레드 파라미터 조정을 검토하세요. 규칙 수 자체를 줄이는 것이 근본 대책입니다.
Q3. Firefighter 로그가 GRC에 안 올라옵니다.
A. 백엔드의 로그 수집 배치가 실패했거나, 대상 시스템의 감사 로그(SM19/SM20 설정) 범위가 좁은 경우가 대부분입니다. 동기화 잡 상태와 커넥터 RFC 권한부터 점검하세요.
Q4. 완화 통제만 잔뜩 달아두면 감사 통과가 되나요?
A. 아닙니다. 감사인은 완화 통제가 "실제로 수행된 증빙"을 요구합니다. 통제 문서와 수행 기록이 함께 있어야 하며, 장기적으로는 역할 재설계로 위반 자체를 줄이는 로드맵을 제시하는 것이 권장됩니다.
실무 적용 체크리스트와 더 살펴볼 주제
도입·운영 단계에서 아래 항목을 점검하세요.
- 규칙셋: 표준 복사본 기반 커스텀 규칙셋 운영, Z-트랜잭션 반영 여부
- 분석 기준: Permission Level, 사용자 단위 집계, 정기 배치 스케줄
- 예방 통제: ARM 요청 시점 시뮬레이션 활성화, fail-safe 설계
- EAM: FFID Owner/Controller 지정, 로그 리뷰 SLA 준수율
- 증적: 완화 통제 수행 기록, UAR 주기 실행, 승인 이력 보존
이후에는 GRC Process Control과의 연계(통제 테스트 자동화), SAP Cloud Identity Access Governance(IAG)를 통한 클라우드 확장, S/4HANA 전환 시 권한 리디자인 전략, Fiori 카탈로그 권한(S_START 등)의 SOD 반영 같은 주제로 확장해볼 만합니다. 특히 IAG는 BTP·SuccessFactors 등 클라우드 애플리케이션까지 SOD 분석을 넓혀주므로 하이브리드 환경이라면 우선 검토 대상입니다.
더 깊이 보려면 아래 자료를 권장합니다.
댓글 0
아직 댓글이 없습니다.