LLM 엔지니어링21장 · AI 거버넌스와 보안, 온프레미스 인프라9 / 22← 이전목차다음 →MetaContents

21장. AI 거버넌스와 보안, 온프레미스 인프라

접근 제어

회사 서류함에는 "공개", "사내", "대외비" 도장이 찍혀 있고, 직급에 따라 열 수 있는 서랍이 다릅니다. 인턴이 "연봉 인상안 요약해 줘"라고 해도 비서가 대외비 서랍에서 서류를 꺼내 와서는 안 됩니다. RAG 에서도 검색하기 전에 그 사람이 볼 수 있는 문서만 남겨야 합니다.

사용자의 등급과 부서 확인, 볼 수 있는 문서만 남기기, 그 안에서 검색, LLM 에 넘기기의 네 단계 흐름도

1번에서 로그인한 사용자의 등급과 부서를 확인합니다(13장 인증). 2번에서 문서의 등급과 부서를 보고 볼 수 있는 것만 남깁니다. 3번에서 그 안에서 검색합니다. 4번에서 고른 조각만 LLM 에 넘깁니다. 서랍을 여는 열쇠가 등급, 서랍 앞에서 확인하는 일이 2번입니다.

걸러 내는 때 견주기

방법하는 일문제
걸러 내지 않음모든 문서에서 검색비밀 문서가 LLM 에 들어가 답으로 샌다
검색 뒤 걸러 내기상위 k 개를 찾은 뒤 볼 수 없는 것을 뺀다남는 조각이 줄어 답할 자료가 모자라기 쉽다
검색 전에 걸러 내기볼 수 있는 문서 안에서만 찾는다문서마다 등급과 부서를 붙여 두어야 한다
"알려 주지 마" 지시프롬프트로 막는다인젝션 한 번에 뚫린다

볼 수 있는지 정하는 규칙

Python
def can_read(user, doc):
    """등급이 문서보다 낮지 않아야 한다. 3등급(비밀) 문서는 같은 부서만 본다"""
    if user["등급"] < doc["등급"]:
        return False
    return doc["등급"] < 3 or doc["부서"] in ("공용", user["부서"])


intern = {"이름": "인턴", "등급": 1, "부서": "개발팀"}
hr_lead = {"이름": "인사팀장", "등급": 3, "부서": "인사팀"}
doc = {"제목": "연봉 인상안", "부서": "인사팀", "등급": 3}
dev_secret = {"제목": "보안 사고 대응", "부서": "개발팀", "등급": 3}
# 결과: 인사팀장은 인사 비밀은 보지만 개발팀 비밀은 못 본다
print(can_read(intern, doc), can_read(hr_lead, doc), can_read(hr_lead, dev_secret))

코드에 나오는 함수

코드쉬운 뜻
user["등급"] < doc["등급"]사용자의 등급이 문서보다 낮으면 True 다. 그러면 곧바로 False(못 읽음)를 돌려준다
doc["등급"] < 3 or ...3등급보다 낮은 문서면 여기서 바로 읽을 수 있다. 아니면 or 뒤의 부서 조건을 본다
doc["부서"] in ("공용", user["부서"])문서의 부서가 '공용' 이거나 사용자와 같은 부서면 True 다

이 코드가 뜻하는 것

호텔 카드키를 떠올려 봅니다. 카드키의 등급이 층보다 낮으면 엘리베이터가 그 층에 서지 않습니다. 가장 높은 층의 객실은 등급이 맞아도 자기 방이나 공용 라운지만 열립니다. 여기서 카드키 등급이 사용자 등급, 가장 높은 층이 3등급 문서, 자기 방이 같은 부서입니다.

줄하는 일쉽게 말하면
if user["등급"] < doc["등급"]: return False등급이 모자라면 바로 거절엘리베이터가 서지 않는다
return doc["등급"] < 3 or ...낮은 등급 문서는 허락, 3등급은 부서까지 본다높은 층은 자기 방만 열린다
intern, hr_lead, doc, dev_secret사용자 둘과 문서 둘(지어낸 예)손님 둘, 객실 둘

결과 False True False 는 차례로 인턴은 연봉 인상안을 못 보고, 인사팀장은 볼 수 있고, 인사팀장도 개발팀의 비밀 문서는 못 본다는 뜻입니다.

작게 줄여서 눈으로 보기

인사팀 3등급 사용자 한 명을 두고, 문서 네 가지를 차례로 넣어 봅니다. 문서는 설명하려고 지어낸 것입니다.

Python
def can_read(user, doc):
    if user["등급"] < doc["등급"]:
        return False
    return doc["등급"] < 3 or doc["부서"] in ("공용", user["부서"])


user = {"등급": 3, "부서": "인사팀"}
for doc in [{"등급": 1, "부서": "개발팀"}, {"등급": 3, "부서": "공용"},
            {"등급": 3, "부서": "인사팀"}, {"등급": 3, "부서": "개발팀"}]:
    print(doc, "→", can_read(user, doc))
출력
{'등급': 1, '부서': '개발팀'} → True
{'등급': 3, '부서': '공용'} → True
{'등급': 3, '부서': '인사팀'} → True
{'등급': 3, '부서': '개발팀'} → False
결과읽는 법
{'등급': 1, '부서': '개발팀'} → True3등급보다 낮은 문서는 부서가 달라도 읽는다
{'등급': 3, '부서': '공용'} → True3등급이지만 공용 문서라 읽는다
{'등급': 3, '부서': '인사팀'} → True같은 부서의 비밀 문서라 읽는다
{'등급': 3, '부서': '개발팀'} → False등급은 충분해도 다른 부서의 비밀 문서라 못 읽는다

등급과 부서, 두 가지를 함께 봐야 "높은 사람은 다 본다"는 빈틈이 막힙니다. 이 규칙은 검색 전에 문서를 걸러 낼 때 씁니다.

주의할 점

  • 등급은 문서를 넣을 때(11장의 문서 파이프라인) 붙입니다. 등급이 없는 문서는 가장 높은 등급으로 봅니다.
  • 캐시(16장)도 사용자마다 나눕니다. 인사팀장의 답을 캐시해 두고 인턴에게 돌려주면 접근 제어가 무너집니다.
← 이전 절21장 · 개인정보 찾기와 가리기다음 절 →21장 · AI 윤리와 거버넌스