시니어 엔지니어가 새벽 3시에 호출을 받았다. 프로덕션 DB 쿼리가 느려지고 있다. 그는 DB 관리자 계정으로 접속해 슬로우 쿼리를 찾아내고 인덱스를 수정한 뒤 잠든다. 작업 시간은 15분이다.
그런데 이 관리자 계정은 그 15분 외에도 항상 살아있다. 24시간, 365일. 그가 자는 동안에도, 휴가 중에도, 퇴사 후 아무도 비밀번호를 바꾸지 않은 6개월 동안에도. 자격증명이 어딘가에서 유출되면 공격자는 DB 전체에 대한 관리자 권한을 즉시 획득한다. 탐지되기 전까지 공격자는 그 엔지니어와 동일한 접근 권한을 갖는다.
이것이 Standing Privilege(상시 권한)의 구조적 문제다. 권한이 실제로 쓰이는 순간은 전체 시간의 극히 일부지만, 자격증명은 항상 켜져 있다.
이 전 포스팅에서 다룬 IGA는 "지금 이 권한이 여전히 적절한가"를 분기마다 검토하고, SCIM은 그 결정을 앱에 전달한다. 그런데 분기 검토라면 3개월 동안 불필요한 권한이 살아있다는 의미이기도 하다. IGA와 SCIM은 Standing Privilege를 관리하는 방법이지, 없애는 방법이 아니다.
Standing Privilege가 Zero Trust와 충돌하는 구조
Zero Trust의 Least Privilege 원칙은 "필요한 권한만"이다. 그런데 Standing Privilege 구조에서 "필요한 권한만"은 부여 시점에만 성립한다. 시간이 지나면 필요가 사라져도 권한은 남는다.
- 침해 반경(blast radius)이 항상 최대다.
자격증명 하나가 유출되면 그 계정이 가진 모든 권한이 즉시 공격자 손에 들어간다. DB 관리자 자격증명이 탈취됐다면 DB 전체가 노출된다. 그 자격증명이 6개월째 로테이션되지 않았다면, 6개월 전에 유출된 것을 지금도 쓸 수 있다. - 감사 신호에서 정상과 침해를 구별하기 어렵다.
로그에 "DB 관리자 계정이 새벽 2시에 고객 테이블을 조회했다"는 기록이 남았다. 정상 작업인가, 침해인가? 그 계정이 항상 활성 상태라면 접근 자체가 이상 징후가 되지 않는다. JIT 환경에서는 다르다. 자격증명이 발급되지 않으면 접근 자체가 불가능하다. 그 시간에 발급된 요청이 없는데 로그가 남았다면, 그것 자체가 침해 신호다. - Privilege Creep이 구조적으로 발생한다.
권한이 상시 부여되는 구조에서는 회수하지 않는 한 계속 남는다. 이 전 IGA 포스팅 글에서 다룬 Access Certification이 이를 주기적으로 정리하지만, 검토 사이 기간의 누적은 막을 수 없다. JIT 방식은 요청하지 않으면 권한이 존재하지 않는 방법으로 이 문제를 구조적으로 차단한다.

JIT Access의 두 차원 — 시간과 범위
JIT(Just-In-Time) Access의 핵심은 기본 상태를 바꾸는 것이다. Standing Privilege에서 기본 상태는 "권한 있음"이고 예외가 "회수"다. JIT에서 기본 상태는 "권한 없음"이고 예외가 "임시 부여"다.
이때 "임시 부여"에는 두 가지 차원이 있다.
- 시간 차원(Duration): 자격증명에 TTL(Time to Live)을 부여한다. 만료 후 자동으로 무효화된다. 별도의 회수 작업이 없어도 시간이 지나면 쓸 수 없게 된다.
- 범위 차원(Scope): 해당 작업에 필요한 최소한의 권한만 부여한다. DB 전체 관리자 권한이 아닌, 특정 테이블의 읽기 권한만. AWS 전체 S3 권한이 아닌, 특정 버킷 하나의 쓰기 권한만. 이를 Just Enough Access(JEA)라 부르기도 한다.
두 차원을 함께 적용해야 JIT의 효과가 온전히 나타난다. TTL만 있고 권한 범위가 넓으면, 침해 시 TTL이 만료될 때까지의 피해 범위는 여전히 크다. 범위만 좁히고 TTL이 없으면 Standing Privilege의 문제가 다시 생긴다.
JIT 방식에도 두 가지 구현 패턴이 있다. 어떤 것을 선택할지는 대상 시스템의 특성에 따라 달라진다.
- Privilege Elevation(권한 일시 상승): 계정은 항상 존재하지만 평소에는 일반 사용자 수준의 권한만 가진 상태다. 특권이 필요한 작업이 있을 때 해당 권한을 임시로 부여하고, 작업 후 다시 회수한다. Unix의
sudo가 이 개념의 원형이다. 계정 자체는 유지되므로 시스템이 계정 생성/삭제를 지원하지 않아도 적용 가능하다. - Account Provisioning(계정 임시 생성): 작업이 필요할 때 계정 자체를 생성하고, 작업 후 삭제한다. HashiCorp Vault의 DB 시크릿 엔진이 이 패턴이다. 계정이 요청과 함께 태어나고 TTL과 함께 사라지므로, 자격증명이 유출되더라도 TTL이 지나면 해당 계정 자체가 존재하지 않는다.
이 두 패턴이 리소스 유형별로 어떻게 구현되는지는 다음 섹션에서 다룬다. 그 전에 요청 흐름 전체를 먼저 살펴본다.
JIT Access의 흐름
이 흐름의 중심에 있는 시스템이 PAM(Privileged Access Management)이다. 엔지니어의 접근 요청을 받아 정책 평가를 요청하고, Vault에서 임시 자격증명을 발급해 세션을 관리한다. PAM의 구체적인 구조는 뒤 섹션에서 다룬다.

PAM이 동작하는 두 가지 모드가 실무에서 중요한 차이를 만든다.
- 자격증명 전달 모드: Vault에서 받은 임시 자격증명을 사용자에게 전달하고, 사용자가 리소스에 직접 접속한다. 구현이 단순하지만, 자격증명이 사용자 환경(터미널, 클립보드, 로그)에 잠깐이라도 노출된다.
- 세션 프록시 모드: 사용자는 PAM에 접속하고, PAM이 자격증명을 사용해 리소스와 연결을 중계한다. 사용자는 실제 자격증명을 보지 못한다. CyberArk PSM, BeyondTrust 같은 엔터프라이즈 PAM이 이 방식을 기본으로 사용한다. 자격증명이 PAM 바운더리 밖으로 나가지 않으므로 유출 경로가 원천 차단되고, 세션 레코딩을 PAM이 완전히 제어할 수 있다.
두 모드 모두 공통점이 있다. Vault에서 발급한 임시 자격증명, 즉 Ephemeral Credential을 사용한다는 것이다. 이 자격증명이 실제로 어떻게 만들어지는지는 리소스 유형마다 다르다.
Ephemeral Credential — 리소스 유형별 발급 방식
Ephemeral Credential(임시 자격 증명)이 실제로 어떻게 만들어지는가는 리소스 유형에 따라 다르다. 동적 자격증명 생성을 지원하는 시스템은 Account Provisioning 패턴을 사용하지만, 레거시 시스템처럼 이를 지원하지 않는 경우에는 별도의 Check-in/Check-out 패턴을 적용한다. 이 섹션에서는 두 경우를 모두 다룬다.
DB 동적 시크릿 (HashiCorp Vault Database Secrets Engine)
Vault DB 시크릿 엔진 방식은 고정 DB 비밀번호를 쓰지 않는다. creation_statements라는 role template을 Vault에 미리 정의해두면, 요청이 올 때마다 Vault가 그 template을 실행해 임시 사용자를 생성하고 자격증명을 반환한다. TTL이 만료되면 Vault가 revocation_statements를 실행해 임시 사용자를 삭제한다.
# Vault role 정의 (사전 설정)
creation_statements:
"CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';
GRANT SELECT ON app.orders TO '{{name}}'@'%';"
revocation_statements:
"DROP USER IF EXISTS '{{name}}'@'%';"
# 요청 시 Vault가 실행
→ 사용자명: v-db-readonly-KxQ9mN2p (자동 생성, role 이름 prefix 포함)
→ 비밀번호: A9xK#mN2qP (랜덤 생성)
→ TTL: 1h (만료 시 revocation 자동 실행)
같은 작업을 두 번 하면 매번 다른 사용자명과 비밀번호가 나온다. 유출된 자격증명이 있어도, TTL이 지나면 해당 사용자 자체가 DB에서 삭제된다.
Vault role 설계 시 creation_statements에 넣는 GRANT 범위가 JIT의 효과를 결정한다. 읽기 조회가 필요한 role에 GRANT ALL PRIVILEGES를 넣으면 임시 자격증명이더라도 범위 차원의 Least Privilege가 무너진다.
클라우드 임시 자격증명 (AWS STS AssumeRole)
장기 자격증명(Access Key) 대신 IAM Role을 수임하는 방식이다. AssumeRole 요청으로 만료 시각이 명시된 AccessKeyId, SecretAccessKey, SessionToken 세 가지를 받는다. SessionToken이 없으면 인증이 거부되므로 장기 키와 달리 탈취된 AccessKeyId와 SecretAccessKey만으로는 쓸 수 없다.
두 가지 주의해야 할 점들이 있다.
- 첫째, 수임하는 Role의 권한이 과하게 설정된 경우다. 임시 자격증명이더라도 Role에
s3:*가 붙어있으면 S3 전체가 노출된다.AssumeRole시Policy파라미터로 해당 세션의 권한을 추가로 좁힐 수 있다 — Role의 권한과 Session Policy의 교집합이 실제 허용 범위다. 이 파라미터를 쓰지 않으면 시간 차원만 있고 범위 차원은 없는 상태가 된다. - 둘째, AssumeRole의 최대 세션 시간을 역할 정의에서 제한하지 않는 경우다. 기본값은 1시간이지만 최대 12시간까지 설정 가능하다. TTL을 길게 잡을수록 탈취 시 노출 창이 넓어진다.
SSH 단기 인증서 (Vault SSH CA, Teleport)
서버에 공개키를 미리 등록하는 방식 대신, 요청 시 CA가 서명한 단기 인증서를 발급받는다. 서버는 CA 공개키만 신뢰하도록 설정해두고, 인증서의 서명과 만료 시각을 로컬에서 검증한다. 인증서 검증 시 CA 서버와 실시간 통신이 필요 없다 — 인증서 자체에 서명과 만료 정보가 포함돼 있기 때문이다.
# 실제 발급된 SSH 인증서 정보 (ssh-keygen -L 출력 형식)
Type: ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate
Key ID: "vault-user-mia@example.com-1774062000"
Serial: 1774062000
Valid: from 2026-03-22T03:00:00 to 2026-03-23T03:00:00
Principals:
ops-engineer
Extensions:
permit-pty
permit-user-rc
퇴사자 처리에서 이 패턴의 이점이 드러난다. 기존 방식이라면 퇴사자의 SSH 공개키를 모든 서버의 authorized_keys에서 제거해야 한다. 서버가 수백 개라면 누락이 생긴다. CA 기반 방식에서는 IdP에서 계정을 비활성화하거나 Vault에서 발급 권한을 제거하면 된다. 이미 발급된 인증서는 TTL 내에서만 유효하고, TTL이 지나면 서버가 만료된 인증서를 거부한다. 서버마다 authorized_keys를 수정할 필요가 없다.
레거시 시스템 — Check-in/Check-out
동적 사용자 생성을 지원하지 않는 레거시 시스템(일부 네트워크 장비, 메인프레임, 구형 데이터베이스)에는 Account Provisioning 패턴을 적용할 수 없다. 이때 PAM은 Check-in/Check-out 방식을 사용한다.
고정 계정의 자격증명은 PAM의 자격증명 저장소에 보관된다. 엔지니어가 접근을 요청하면 PAM이 해당 계정을 "체크아웃"하고, 다른 사람이 동시에 같은 계정을 쓰지 못하게 잠근다. 작업이 끝나면 PAM이 자동으로 비밀번호를 로테이션한 뒤 잠금을 해제한다. Ephemeral Credential이 아니라 고정 계정이지만, "사용 직전에만 비밀번호를 알 수 있고 사용 후 즉시 바뀐다"는 점에서 자격증명 노출 창이 최소화된다.
하지만 이 방식에는 한계가 있다. 하나의 계정을 한 번에 한 명만 쓸 수 있으므로, 동시에 여러 엔지니어가 접근해야 하는 상황에서는 병목이 생긴다. 또한 공유 계정이기 때문에 "누가 이 세션을 열었는가"의 증거는 PAM의 체크아웃 기록에 의존한다 — DB 자체 로그에는 여전히 공유 계정 이름이 남는다. Account Provisioning이 가능한 시스템은 이 방식 대신 동적 자격증명을 써야 한다.
PAM — 특권 접근을 관리하는 시스템
PAM(Privileged Access Management)은 JIT Access를 포함해 특권 계정 전체를 관리하는 시스템이다.
PAM이 이 역할을 수행하는 데 필요한 핵심 컴포넌트가 세 가지 있다.
- Credential Vault: 특권 계정의 자격증명을 중앙에서 보관한다. 엔지니어는 이 저장소에 직접 접근하지 않는다. PAM이 자격증명 저장소와 상호작용하고, 엔지니어에게는 접속 세션만 열어주되 자격증명 자체는 노출하지 않는다. Credential Vault가 없으면 자격증명은 개인 노트북의
.ssh/config, 팀 메신저, 공유 스프레드시트에 흩어진다. 누가 어떤 자격증명을 알고 있는지 파악하는 것 자체가 불가능해진다. - Session Recording: 특권 세션의 모든 활동을 기록한다. 실행한 명령어, 접근한 파일, 조회한 데이터. 보안 사고 사후 조사에서 "그 세션에서 실제로 무슨 일이 있었는가"를 재생해 확인할 수 있다. 일반 감사 로그와 다른 점은 맥락이 담긴다는 것이다. 로그에
SELECT * FROM customers가 있다면, 세션 기록에서 그 쿼리 전후로 어떤 명령이 (데이터를 조회하기 전에 권한을 변경했는지, 조회 후 어디로 데이터를 옮겼는지) 실행됐는지 볼 수 있다. Session Recording은 세션 프록시 모드에서 가장 완전하게 동작한다. PAM이 모든 트래픽을 중계하기 때문에 클라이언트 측 조작 없이 완전한 기록을 보장한다. - Break-glass: 긴급 상황에서 정상 승인 프로세스를 기다릴 수 없을 때 즉시 접근하는 절차다. 승인 없이 접근이 허용되지만, 사용 사실과 이후 모든 세션 활동이 자동으로 기록되고 관리자에게 알림이 간다. 사후 정당성 검토가 의무다. Break-glass를 지나치게 어렵게 만들면 엔지니어가 PAM 외부의 다른 경로(백업 계정, 하드코딩된 키)를 찾게 된다. 쉽게 쓸 수 있되, 쓰는 순간 반드시 추적된다는 것을 설계로 보장해야 한다.

Zero Standing Privilege — 이상과 현실
Zero Standing Privilege(ZSP)는 상시 권한이 없는 상태다. 자격증명이 유출돼도 공격자가 즉시 쓸 수 있는 것이 없다.
완전한 ZSP를 막는 가장 큰 장벽은 서비스 계정이다. 배포 파이프라인, 모니터링 에이전트, 백업 스크립트는 사람의 개입 없이 실행된다. "요청 → 승인 → 임시 발급" 흐름을 자동화 프로세스에 그대로 적용하기 어렵다.
서비스 계정을 ZSP 방향으로 당기는 현실적인 방법이 두 가지 있다.
- OIDC 기반 Workload Identity: GitHub Actions가 AWS에 배포할 때 장기 Access Key 대신 OIDC 토큰으로 IAM Role을 수임하는 방식이다. 동작 원리는 이렇다. AWS IAM에 GitHub의 OIDC 공급자를 신뢰 대상으로 등록하고, Role의 trust policy에
token.actions.githubusercontent.com:sub조건을 추가한다. GitHub Actions 워크플로우가 실행되면 GitHub이 OIDC 토큰을 발급하고, 워크플로우는 이 토큰으로 AWS에AssumeRoleWithWebIdentity를 호출한다. AWS는 토큰의 서명을 OIDC 공급자 공개키로 검증한 뒤 임시 자격증명을 반환한다. 장기 Access Key가 어디에도 저장되지 않는다. 이미 GCP, Azure, GitHub, GitLab 등 주요 플랫폼이 이 패턴을 지원한다. - Vault Agent / Secrets Operator: Kubernetes 환경에서 Pod가 시크릿을 얻는 방법이다. Vault Agent를 사이드카로 붙이거나 Vault Secrets Operator를 클러스터에 배포하면, 애플리케이션 코드 변경 없이 Pod 시작 시 시크릿을 주입하고 TTL에 맞춰 자동으로 갱신한다. 애플리케이션 입장에서는 파일이나 환경변수에서 시크릿을 읽는 것처럼 보이지만, 실제로는 Vault에서 동적으로 발급된 단기 자격증명이다.
서비스 계정에 장기 자격증명이 불가피하다면, 침해 반경을 좁히는 것이 현실적인 절충이다. S3 전체 관리자 권한을 가진 서비스 계정과 특정 버킷 하나에 쓰기 권한만 가진 서비스 계정은 침해됐을 때 결과가 다르다.
서비스 계정을 포함해, 조직 전체에서 ZSP를 향해 이동하는 실질적인 순서가 있다.
- 1단계 — 공유 관리자 계정 제거: 팀원 전체가 쓰는 root 계정 하나가 가장 먼저 없애야 할 대상이다. 누가 무엇을 했는지 개인 단위로 추적할 수 없기 때문이다. 사고 발생 시 "팀 admin 계정으로 접근했다"는 로그는 조사의 출발점이 되지 못한다.
- 2단계 — 고정 자격증명의 동적 교체: DB 비밀번호, SSH 키, API 키를 동적으로 발급하는 방식으로 전환한다. 동적 생성이 불가능한 레거시 시스템은 Check-in/Check-out으로 전환한다. 모든 것을 동시에 바꿀 필요는 없다. 프로덕션 접근이 있는 자격증명부터 시작한다.
- 3단계 — 모든 특권 접근에 컨텍스트 부여: "왜 접근하는가"를 기록하는 것이 감사의 질을 결정한다. 접근 기록만 있는 감사 로그와 접근 근거가 있는 감사 로그는 사고 조사에서 다른 출발점을 제공한다.
적용 판단 기준
JIT Access 도입에서 가장 자주 발생하는 마찰은 개발자 생산성이다. 매번 접근 요청을 하고 승인을 기다리는 흐름은 개발 속도를 늦춘다. 이 마찰을 줄이면서도 효과를 유지하는 설계 원칙이 있다.
- 자동 승인 범위를 최대한 넓게 설계한다. 개발 환경, 읽기 전용 접근, 알려진 기기에서 업무 시간대에 오는 요청 대부분은 자동 승인 처리한다. 관리자 승인이 필요한 것은 프로덕션 DB 쓰기, 민감 리소스, 비정상 시간대 요청으로 좁힌다. 승인 요구가 많을수록 엔지니어는 우회 방법을 찾기 시작한다.
- TTL을 작업 특성에 맞게 조정한다. 일반 DB 조회는 1시간, 대규모 마이그레이션 작업은 4시간, 정기 배포는 30분. TTL을 무조건 짧게 설정하면 작업 중 만료로 인한 재요청이 잦아지고 개발자 불만이 누적된다. 반대로 너무 길게 잡으면 JIT의 효과가 희석된다.
마찰을 줄이는 데 집중하다 보면 반대쪽 문제가 생긴다. 승인이 형식적으로 처리되는 것이다.
IAM/IGA 포스팅 글에서 Access Certification의 Rubber Stamp 문제를 다뤘는데, JIT 승인에서도 같은 현상이 생긴다. 관리자가 요청 내용을 확인하지 않고 습관적으로 승인하면, 보안 절차는 남지만 보안 판단은 사라진다. 이를 방지하려면 설계로 강제해야 한다. 요청 사유를 필수 입력으로 만들고, 연결된 티켓 번호를 요구하고, 같은 엔지니어가 짧은 시간 안에 동일한 리소스를 반복 요청할 때 관리자에게 알림을 보내는 방식이 실용적이다.
PAM을 도입하기 전에 반드시 파악해야 할 것은 기존 특권 계정 인벤토리다. 지금 조직에 몇 개의 특권 계정이 있고, 누가 접근할 수 있으며, 마지막으로 자격증명이 로테이션된 것이 언제인가. 이 파악 없이 PAM을 도입하면 새 시스템과 기존 고정 자격증명이 병행 운영되는 구조가 된다. 고정 자격증명이 남아있는 한 JIT의 보안 효과는 절반이다.
참고 자료
- Vault Database Secrets Engine: https://developer.hashicorp.com/vault/docs/secrets/databases
- Vault SSH Secrets Engine (Signed SSH Certificates): https://developer.hashicorp.com/vault/docs/secrets/ssh/signed-ssh-certificates
- AWS STS AssumeRole: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
- GitHub Actions — OIDC 보안 강화: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect
- Vault Agent: https://developer.hashicorp.com/vault/docs/agent-and-proxy/agent
- Vault Secrets Operator (Kubernetes): https://developer.hashicorp.com/vault/docs/platform/k8s/vso
- NIST SP 800-207 Zero Trust Architecture: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-207.pdf