Elasticsearch 입문 29편 Security·RBAC. 인증·API key·Roles·FLS·DLS·Audit Log·TLS.
이 글은 Elasticsearch 입문에서 운영까지 시리즈 38편 중 29편이에요. 27편(Shard Allocation)·28편(Snapshot) 이 "클러스터가 죽지 않게 + 데이터를 잃지 않게" 였다면, 이번 29편은 "누가 어디까지 볼 수 있는가" 자리예요. 운영 ES 에서 Security 를 끄는 순간 인덱스가 누구나 읽고 지울 수 있는 공개 자원이 돼요. 8.0+ 부터는 Security 가 기본 활성이라 끄는 게 더 부자연스러운 환경으로 바뀌었고, 이 글은 그 표준 자리에서 인증·권한·감사·암호화 네 축을 한 호흡에 정리해요.
이 글은 Elasticsearch 8.x 공식 docs 의 Security · Authentication · Authorization · Auditing 챕터를 한국어 학습 노트로 풀어쓴 자료예요.
로컬 Docker 8.x 로 클러스터를 띄우면 자동 생성된 elastic 비밀번호와 kibana_system 토큰을 콘솔에 한 번 찍어 주는데, 그 메시지를 따라 직접 로그인해 보면 본문 내용이 훨씬 잘 박혀요.
Security 가 기본이 된 8.0+
7.x 까지는 X-Pack Security 가 옵션이라 "운영에 보안 안 켜고 도는 ES 클러스터" 가 흔했어요. 그래서 Meow Attack (2020년경 노출된 ES·MongoDB 인스턴스가 자동 삭제된 사건) 같은 사고가 줄을 이었고, Elastic 사가 2022년 8.0 부터 Security by Default 로 정책을 바꿨어요.
8.0+ 클러스터는 처음 부팅 할 때 자동으로 — TLS 인증서를 자동 생성하고, elastic superuser 비밀번호를 자동 발급하고, Kibana 연결용 kibana_system enrollment token 을 자동 발급해요. 따로 설정 안 해도 암호화 + 인증 이 켜진 상태로 출발하는 게 핵심.
운영 클러스터에서 이 기본을 다시 끄는 건 거의 사고의 시작이에요. 학습용 로컬 1-노드 도커조차 Security 켜 두고 익숙해지는 편이 나중에 운영으로 옮길 때 충격이 적어요.
인증 방식 6가지
ES Security 의 Authentication 은 "누가 요청을 보냈는가" 를 확인하는 단계예요. 8.x 가 지원하는 방식은 크게 여섯 가지.
(1) basic auth — username/password
가장 흔한 방식. HTTP Authorization: Basic <base64(user:pass)> 헤더에 사용자명·비밀번호를 실어 보내요. 간단·범용 이 장점이고, 비밀번호 회전 부담 이 단점.
curl -u elastic:changeme \
-X GET "https://localhost:9200/_cluster/health"
사용자는 Native realm 에 저장돼요. elasticsearch-users useradd 명령으로 만들거나, Kibana → Stack Management → Users 화면에서 만들 수 있어요.
(2) API key — 서비스 호출용 표준
운영 앱 → ES 호출 자리에 권장 되는 방식. 사용자 비밀번호 대신 키 ID + secret 한 쌍을 발급받아 Authorization: ApiKey <base64(id:secret)> 헤더에 실어요.
# 발급 (관리자 권한 사용자로)
POST /_security/api_key
{
"name": "checkout-search-key",
"expiration": "30d",
"role_descriptors": {
"search_only": {
"cluster": ["monitor"],
"indices": [{
"names": ["products-*"],
"privileges": ["read", "view_index_metadata"]
}]
}
}
}
응답으로 id · api_key · encoded 셋이 돌아와요. encoded 값을 그대로 ApiKey 헤더에 박으면 됩니다. 기한이 박혀 있어 유출돼도 시간이 지나면 자동 무효화되는 게 비밀번호 대비 강점.
(3) Token — Bearer OAuth2
POST /_security/oauth2/token 으로 받은 access token 을 Authorization: Bearer <token> 으로 실어 보내는 방식. 만료 시간이 짧고 (기본 20분) refresh token 으로 갱신해야 해서 세션 기반 SPA 자리에 잘 어울려요. Kibana 내부 호출 일부도 이 방식.
(4) PKI — 클라이언트 인증서
TLS 클라이언트 인증서로 사용자를 식별. 인증서 Subject DN 이 role 매핑 키. zero-trust 네트워크·금융권 처럼 비밀번호 자체를 안 쓰는 환경에 적합.
(5) LDAP / Active Directory
회사 디렉터리 서버 와 직접 묶음. 사내 ID 로 ES·Kibana 에 그대로 로그인하는 자리에 표준. xpack.security.authc.realms.ldap.ldap1.* 설정으로 LDAP 서버 URL·bind DN·user search base 를 박아요.
(6) SAML / OIDC — SSO
Okta·Google Workspace·Azure AD 같은 IdP 와 묶어 SSO 로 들어가는 방식. Kibana 가 SAML 응답을 받아 ES 에 토큰으로 변환해서 들고 가는 흐름. 큰 회사 표준.
여섯 방식은 동시 활성화 가 가능해요. 운영 자리에서는 보통 내부 API → API key, 사람 → SAML/OIDC, kibana_system → built-in user, 모니터링 봇 → 별도 service account 식으로 역할별로 다른 인증 을 박는 게 표준 패턴.
API key 깊이
운영 자리에서 가장 자주 만지는 게 API key 라서 한 번 더 깊이.
# 1. 생성
POST /_security/api_key
{
"name": "logs-writer",
"expiration": "90d",
"role_descriptors": {
"logs_writer": {
"cluster": ["monitor"],
"indices": [{
"names": ["logs-app-*"],
"privileges": ["create_index", "write", "view_index_metadata"]
}]
}
},
"metadata": {
"owner": "platform-team",
"issued_at": "2026-05-19"
}
}
응답:
{
"id": "VuaCfGcBCdbkQm-e5aOx",
"name": "logs-writer",
"expiration": 1764800000000,
"api_key": "ui2lp2axTNmsyakw9tvNnw",
"encoded": "VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw=="
}
encoded 가 Authorization: ApiKey <encoded> 자리에 들어가는 최종 값.
조회·삭제 — GET /_security/api_key?name=logs-writer 로 발급된 키 목록, DELETE /_security/api_key (body 에 ids 또는 name) 로 즉시 무효화. 유출 의심 시 즉시 무효화 가 첫 대응.
소유자 권한 한도 — API key 의 권한은 발급자 본인 권한의 부분집합 만 가능해요. superuser 가 아닌 한 상위 권한 위임 이 불가하므로, 키 발급 자체를 전용 admin 계정 으로만 하는 패턴이 안전.
자동 만료 — expiration 을 박지 않으면 영구 유효 가 돼서 회전이 어려워져요. 반드시 박는다 가 운영 규칙. 만료 7일 전쯤 모니터링 알람을 트리거하는 자동화가 함께 있어야 서비스 끊김 사고를 막아요.
Roles & Privileges — RBAC 핵심
ES Authorization 은 Role-Based Access Control (RBAC) 모델. 사용자는 role 을 가지고, role 은 privileges 묶음을 가져요.
Privileges 3축
| 축 | 자리 | 예시 |
|---|---|---|
| Cluster privileges | 클러스터 전체 자리 | monitor · manage · manage_security · all |
| Index privileges | 인덱스 자리 | read · write · create_index · delete · view_index_metadata · all |
| Application privileges | Kibana·앱 자리 | feature_dashboard.all · feature_discover.read 등 |
Role 정의 예시
PUT /_security/role/products_search
{
"cluster": ["monitor"],
"indices": [{
"names": ["products-*"],
"privileges": ["read", "view_index_metadata"]
}],
"applications": [{
"application": "kibana-.kibana",
"privileges": ["feature_discover.read", "feature_dashboard.read"],
"resources": ["space:default"]
}]
}
이 role 을 가진 사용자는 — 클러스터 모니터링 만 가능, products-* 인덱스를 읽기만 가능, Kibana Discover·Dashboard 만 보기만 가능.
사용자 → role 매핑
PUT /_security/user/search_bot
{
"password": "changeme123",
"roles": ["products_search"],
"full_name": "Products Search Bot"
}
LDAP·SAML 처럼 외부 IdP 면 role mapping API 로 "LDAP group X → role Y" 매핑을 박아요.
PUT /_security/role_mapping/ldap_search_admins
{
"roles": ["products_search"],
"enabled": true,
"rules": {
"all": [
{"field": {"realm.name": "ldap1"}},
{"field": {"groups": "cn=search-admins,ou=groups,dc=example,dc=com"}}
]
}
}
Built-in Roles
자주 쓰는 사전 정의 role 몇 가지.
| Role | 자리 |
|---|---|
| superuser | 모든 권한. 비상용 — 운영 일상에서는 거의 안 씀. |
| kibana_system | Kibana 내부가 ES 에 접근할 때 쓰는 서비스 계정. |
| kibana_admin | Kibana 전체 관리자. ES 자체 관리권은 없음. |
| viewer | 모든 인덱스·Kibana 읽기. 대시보드 공유용 표준. |
| editor | viewer + Kibana 콘텐츠 편집. |
| monitoring_user | Stack Monitoring 화면 접근. |
| transport_client | 옛 Java Transport client 자리. 8.x 부터 deprecated. |
운영에서는 superuser 를 일상 계정에 박지 않는다 가 첫 규칙. 사람 ID 는 viewer·editor + 도메인별 role 조합으로 가는 게 표준.
Field-level Security — 특정 필드만 보기
FLS 는 role 안에 볼 수 있는 필드 목록 을 박아서, 같은 인덱스라도 어떤 role 은 일부 필드만 보게 하는 기능. 작성 시점(2026-05-19) 기준 Platinum 이상 라이선스 또는 AWS OpenSearch 의 동등 기능에서 동작.
PUT /_security/role/orders_minimal
{
"indices": [{
"names": ["orders-*"],
"privileges": ["read"],
"field_security": {
"grant": ["order_id", "status", "amount", "created_at"],
"except": []
}
}]
}
이 role 을 가진 사용자는 orders-* 에서 4개 필드만 보고, 고객 이메일·전화번호 같은 PII 필드 는 검색 결과에서 통째로 사라져요.
주의 — FLS 와 _source 직접 접근 이 동시에 켜져 있으면 FLS 가 우회될 수 있는 자리가 생겨요. Elastic 권장 패턴은 role 에서 _source 를 명시적으로 제한하거나, runtime field 로 마스킹 컬럼을 따로 둬서 원본 필드 자체에 접근을 안 시키는 것.
제한 — _source 필터링은 검색 결과에는 적용되지만, 집계·정렬 자리에서 일부 우회 경로가 있어서 민감 필드는 아예 다른 인덱스로 분리 하는 게 가장 안전. Index alias + role 매핑 으로 "PII 인덱스 / 비-PII 인덱스" 두 갈래를 두는 게 운영 패턴.
Document-level Security — 행 단위 필터
DLS 는 role 안에 쿼리 를 박아서, 그 role 이 인덱스를 검색할 때 항상 그 쿼리를 추가 필터로 끼우는 기능. RDBMS 의 row-level security 와 같은 자리.
PUT /_security/role/team_kr_orders
{
"indices": [{
"names": ["orders-*"],
"privileges": ["read"],
"query": {
"term": {"region": "KR"}
}
}]
}
이 role 사용자는 orders-* 를 검색해도 region = KR 행만 보여요. 사용자가 match_all 쿼리를 던져도 ES 가 내부적으로 bool filter 를 끼워서 결과를 잘라 줘요.
템플릿 변수 — DLS 쿼리 안에 {{_user.metadata.team_region}} 같은 변수를 박아 role 하나를 사용자 메타데이터 기반으로 분기 시킬 수도 있어요. 수십 개 region role 을 일일이 만들 필요 없이 role 하나 + 메타데이터로 끝낼 수 있는 패턴.
FLS + DLS 조합 — 둘 다 같은 role 에 박을 수 있어요. "KR region 행만, PII 제외 필드만" 같은 정교한 자리가 가능. 단 성능 부담 — 모든 검색에 추가 필터·필드 필터링이 들어가므로 대형 인덱스에서는 쿼리 latency 가 10~30% 오르는 게 일반.
Audit Log — 누가 무엇을 했는가
Audit Log 는 security 이벤트 (인증 성공·실패·권한 거부·관리 작업) 를 기록하는 기능. 8.x 부터 기본 비활성이라 명시적으로 켜야 동작.
# elasticsearch.yml
xpack.security.audit.enabled: true
xpack.security.audit.logfile.events.include:
- authentication_success
- authentication_failed
- access_denied
- connection_denied
- tampered_request
- run_as_denied
xpack.security.audit.logfile.events.emit_request_body: false
logs/<cluster_name>_audit.json 파일에 JSON 한 줄 한 이벤트 로 쌓여요.
{
"@timestamp": "2026-05-19T10:23:45.123Z",
"event.action": "access_denied",
"user.name": "search_bot",
"user.roles": ["products_search"],
"request.method": "POST",
"url.path": "/orders/_search",
"trace.id": "abc123"
}
운영 자리에서는 이 파일을 Filebeat → 별도 ES 클러스터 로 흘려서 Kibana 에서 검색·알람 까지 묶는 게 표준. audit log 를 같은 클러스터에 쌓으면 침입자가 흔적을 지울 수 있어서, 반드시 별도 클러스터 가 운영 규칙.
emit_request_body: true 로 켜면 요청 본문까지 기록되는데, 본문에 비밀번호·토큰이 들어 있으면 그게 그대로 로그에 남는 사고가 잦아요. 기본 false 유지 + 본문이 정말 필요한 자리는 redaction 처리 후에 켜는 게 안전.
TLS·HTTPS — 양 끝 모두 암호화
ES 트래픽은 두 채널 이 있어요. REST API (9200 포트, 클라이언트 ↔ 노드) 와 Transport (9300 포트, 노드 ↔ 노드). 둘 다 암호화해야 MITM·도청 사고를 막아요.
Transport TLS — 노드 간 필수
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: certs/transport.p12
xpack.security.transport.ssl.truststore.path: certs/transport.p12
8.0+ 부터 클러스터를 형성하는 데 Transport TLS 가 필수 라 안 켜면 노드가 서로 못 묶여요. elasticsearch-certutil 명령으로 자체 서명 CA + 노드 인증서 를 자동 발급하는 게 가장 빠른 시작.
REST TLS — 클라이언트 자리
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: certs/http.p12
https://localhost:9200 자리에서 brower·curl·Spring 이 인증서를 신뢰하도록 CA 인증서를 클라이언트에 배포 해 둬야 해요. 자체 서명 CA 면 Spring 의 RestClient.Builder.sslContext() 에 truststore 를 박는 식.
인증서 회전
운영 인증서는 1년 만료 가 흔해서, 만료 30일 전쯤 Rolling Restart 로 새 인증서를 배포하는 작업이 1년에 1번 들어와요. 26편(Cluster Operations) 의 Rolling Restart 4단계 와 묶어서 인증서 회전 → 노드 1개씩 재시작 절차를 매뉴얼로 박아 두는 게 표준.
자주 만나는 사고
사고 1 — 운영에서 Security 비활성
원인 — 학습용 가이드의 xpack.security.enabled: false 설정을 복사해서 운영에 그대로 올림. Meow Attack 류 자동 스캐너에 인덱스가 통째로 노출됨.
해결 — 운영 클러스터는 Security 켜진 상태로만 부팅 가능 하게 config 검증 스크립트 를 CI 에 박아요. xpack.security.enabled 가 false 면 배포 자체를 차단. 침해 시에는 즉시 클러스터 격리 + snapshot 복원 절차로.
사고 2 — API key expiration 누락
원인 — expiration 안 박은 채 발급. 1년 뒤 유출 의심 자리에서 얼마나 된 키인지·아직 쓰이는지 추적이 안 됨. 회전도 어려움.
해결 — 발급 자동화 스크립트에 expiration 필수 파라미터 박고, 만료 7일 전 슬랙 알람을 트리거. 기존 키도 일괄 점검 해서 없는 expiration 은 짧게 박아 회전.
사고 3 — FLS 와 _source 혼용
원인 — FLS 로 PII 필드 제한 만 하고, 사용자가 _source: true 로 조회하면 _source 안의 원본 JSON 으로 PII 가 그대로 보이는 자리가 발생.
해결 — 민감 필드는 아예 다른 인덱스로 분리 + role 에서 해당 인덱스 자체 접근 차단 이 가장 안전. 같은 인덱스 유지가 강제면 FLS + _source includes/excludes 를 함께 박고, runtime field 로 마스킹 컬럼 만 노출.
사고 4 — superuser 일상 사용
원인 — kibana 로 매일 들어가는 운영자 계정 에 superuser role 부착. 한 줄 잘못 친 DELETE * 로 인덱스 전체 삭제.
해결 — superuser 는 비상용 break-glass 계정 1~2개로만 두고 vault 에 보관. 일상 계정은 viewer + editor + 도메인 role 조합. destructive_requires_name: true 클러스터 설정으로 와일드카드 삭제 자체를 거부.
사고 5 — Audit Log 미설정
원인 — 침해 사고 발생 후 누가 언제 어떤 인덱스에 접근했는지 추적이 불가능. 사후 분석 자체가 막혀요.
해결 — 최소 authentication_failed·access_denied·tampered_request 셋은 기본 활성. 별도 클러스터로 흘리기 + 90일 이상 보관 + Kibana 알람으로 access_denied 급증 시 슬랙 알람.
사고 6 — TLS 인증서 만료 미인지
원인 — 1년 만료 인증서를 모르고 지나쳐 어느 날 갑자기 노드 간 통신 두절 → 클러스터 형성 실패.
해결 — Prometheus blackbox exporter 또는 Elastic 자체 모니터링 으로 인증서 만료 30일 전 알람. 28편 snapshot 정책과 함께 매년 회전 캘린더 박아 두기.
사고 7 — Role mapping 메타데이터 오용
원인 — DLS 템플릿 변수에 {{_user.metadata.tenant_id}} 박았는데, 사용자가 본인 메타데이터를 직접 수정할 수 있는 role 을 가짐. 다른 tenant 데이터까지 열람 가능.
해결 — 메타데이터 수정 권한은 관리자 전용 role 로만 제한. manage_security privilege 가 일반 사용자에게 박히지 않도록 정기 audit. role review 분기 1회 가 운영 체크리스트.
운영 권장 패턴
운영 자리 첫 단추는 역할별 인증 분리 예요. 사람 = SAML/OIDC + role mapping, 서비스 = API key, Kibana = built-in kibana_system, 모니터링 = service account 식으로 어떤 자리가 어떤 인증을 쓰는지 한 페이지에 정리해 두면 회전·감사가 깔끔해져요.
권한은 최소 권한 (Principle of Least Privilege) 부터 시작. 모든 role 은 viewer 에서 출발해서 필요한 자리만 plus 하는 식. superuser 는 break-glass 계정 1~2개로 봉인하고 vault 에 비밀번호 보관, 사용 시 반드시 4-eyes (두 명 승인).
API key 는 반드시 expiration 박는다 + 발급자 메타데이터에 owner·issued_at 박는다 + 90일 자동 회전 자동화 셋이 한 묶음. 운영 자리에서 키 수가 100개 넘어가면 전용 키 관리 화면 을 Kibana 위에 따로 두는 회사가 많아요.
민감 인덱스는 FLS·DLS 가 아니라 별도 인덱스로 물리적 분리. 같은 클러스터에 두더라도 역할 매핑 자체를 끊는다 가 가장 안전. FLS·DLS 는 부가 방어선 이지 주 방어선 이 아니에요.
Audit Log 는 별도 클러스터로 흘리는 게 운영 규칙. 침입자가 흔적을 지울 수 없게 물리적으로 분리 하고, 90일 이상 보관 + access_denied 급증 알람 까지 자동화에 묶어요.
시험 직전 한 번 더 — 압축 노트
- Security by Default = 8.0+ 기본 활성. TLS·인증·암호 자동 발급.
- 인증 6방식: basic auth · API key · Token · PKI · LDAP/AD · SAML/OIDC. 동시 활성 가능.
- API key = 운영 서비스 호출 표준.
expiration필수, 권한은 발급자의 부분집합. - RBAC 3축: cluster · index · application privileges.
- Built-in roles: superuser·kibana_system·kibana_admin·viewer·editor·monitoring_user.
- FLS = role 안
field_security.grant/except. PII 필드 가리기. - DLS = role 안
query. row-level filter. 템플릿 변수로 메타데이터 분기. - Audit Log = 기본 OFF,
xpack.security.audit.enabled: true로 켬. 별도 클러스터로 흘리기. - TLS 두 채널: Transport(9300, 필수) + REST(9200).
elasticsearch-certutil로 자체 CA 발급. - 사고 7대: Security 비활성·API key expiration 누락·FLS+source 혼용·superuser 일상·Audit Log 미설정·인증서 만료·role mapping 오용.
- 운영 규칙: 최소 권한·API key 90일 회전·민감 인덱스 분리·Audit Log 별도 클러스터·인증서 회전 캘린더.
- superuser = break-glass 1~2개만, 4-eyes 승인.
- destructive_requires_name: true = 와일드카드 삭제 차단.
시리즈 다른 편
- 이전 글 = 28편 Snapshot·Restore — Repository·SLM·복원 절차
- 27편 = Shard Allocation — Awareness·Filtering·Forced·Rebalancing
- 26편 = Cluster Operations — Master·Voting·Rolling Restart·Split-brain
- 24편 = Ingest Pipeline — Processor·Painless·Dead-letter
- 다음 글 = 30편 Monitoring·Alerting — Stack Monitoring·Watcher·Metricbeat
- 31편 = Performance Tuning — Heap·GC·Refresh·Merge
- 32편 = Spring Data Elasticsearch — Repository·Template·POJO
- 35편 = AWS OpenSearch Service — Domain·Serverless·Provisioned
- 38편 = 시리즈 마무리 — 결정 트리·체크리스트·자격증
한 줄 정리 — Elasticsearch Security = Security by Default 8.0+ 위에 인증 6방식 · RBAC 3축 · FLS/DLS · Audit Log · TLS 두 채널 다섯 도구를 최소 권한 + 키 회전 + 별도 audit 클러스터 운영 규칙으로 묶는 자리. superuser 봉인 + API key expiration + 민감 인덱스 분리 셋이 첫 단추.