OpenSearch 인증
OpenSearch coordinator 가 Security 플러그인으로 인증을 강제. 사용자는 Keycloak OIDC 로, Fluent Bit / 자동화 도구는 internal user (basic) 로 접근.
개요
활성화 구성
1. Security 플러그인
infra/opensearch/deployment.yaml 의 env:
- name: DISABLE_SECURITY_PLUGIN
value: "false"
- name: OPENSEARCH_INITIAL_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: opensearch-admin
key: password
# 외 plugins.security.* 환경변수 (TLS 경로, admin DN 등) 다수
2. TLS
| 인증서 | 발급자 | 용도 |
|---|---|---|
opensearch-transport-tls | gend-ca-issuer (cert-manager Issuer) | transport 9300 mTLS, single-node 노드 cert |
opensearch-rest-tls | gend-ca-issuer | REST 9200 server cert (Pod 내부) |
opensearch-public-tls | letsencrypt-prod (cert-manager) | Ingress 외부 노출 cert (opensearch.gend.genon.ai) |
3. Security config (8 파일)
infra/opensearch/security-config.yaml ConfigMap:
| 파일 | 역할 |
|---|---|
config.yml | 인증 백엔드 (basic + OIDC) 정의 |
internal_users.yml | admin / gend_fluent_bit (password hash) |
roles.yml | gend_admin / gend_analyst / gend_auditor / gend_log_writer |
roles_mapping.yml | Keycloak realm role → OpenSearch role |
audit.yml | OpenSearch 자체 감사 (writer IP, action) |
nodes_dn.yml | transport mTLS 허용 DN |
action_groups.yml, tenants.yml, whitelist.yml | 기본값 |
4. role 매트릭스
| Keycloak realm role | OpenSearch role | 인덱스 | 권한 | DLS |
|---|---|---|---|---|
admin | gend_admin | gend-audit*, .opendistro_security, .kibana* | full | 없음 |
analyst | gend_analyst | gend-audit* | read, search | event.user.security_level != "TOP_SECRET" |
viewer | gend_auditor | gend-audit* | read, search | TOP_SECRET 제외 |
| (svc account) | gend_log_writer | gend-audit* | write only | 없음 |
Keycloak 설정
OpenSearch 는 기존 gend-ui client 의 ID token 을 그대로 사용 (별도 client 신설 불필요).
OIDC discovery URL: https://gend.genon.ai/auth/realms/gend/.well-known/openid-configuration
토큰 수동 발급 + 검증:
TOKEN=$(curl -s -X POST 'https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/token' \
-d 'grant_type=password&client_id=gend-ui&username=analyst&password=<pw>&scope=openid' \
| jq -r .access_token)
curl -k -H "Authorization: Bearer $TOKEN" https://opensearch.gend.genon.ai/_cat/indices
# → gend-audit-* 만 표시 (analyst 권한)
admin 비밀번호 회전
# 1) 새 비밀번호 생성 + SealedSecret 재봉인
NEW_PW=$(openssl rand -base64 24 | tr -d '=' | head -c 32)
kubectl create secret generic opensearch-admin \
--from-literal=password="$NEW_PW" \
-n gend --dry-run=client -o yaml \
| kubeseal -o yaml > infra/sealed-secrets/sealed-secrets/opensearch-admin.yaml
# 2) PR → ArgoCD 가 SealedSecret 갱신
# 3) ConfigMap의 internal_users 의 password hash 도 갱신해야 OIDC 검증 통과
# → securityadmin Job 이 자동 처리 (env 의 새 password 로 hash 재생성 + sed 치환)
# 4) Job 재실행 (ConfigMap annotation 변경 또는 수동 trigger)
kubectl --context aks-genos-prod delete job opensearch-securityadmin -n gend
argocd app sync gend-opensearch
Fluent Bit 인증
Fluent Bit DaemonSet 의 4 OUTPUT 블록이 ${OPENSEARCH_FB_USER} / ${OPENSEARCH_FB_PASSWORD} 환경변수를 SealedSecret opensearch-fluent-bit-cred 에서 받는다. 패스워드 회전은 admin 회전 절차와 동일 (SealedSecret 재봉인 → securityadmin Job 재실행).
트러블슈팅
| 증상 | 원인 | 처치 |
|---|---|---|
401 Unauthorized 외부 opensearch.gend.genon.ai | Bearer 미첨부 또는 만료 | Keycloak 로그인 후 새 토큰 획득 |
Fluent Bit 로그 [error] [output:opensearch] HTTP status=401 | opensearch-fluent-bit-cred Secret 미적용 또는 password hash 불일치 | SealedSecret 재배포 + securityadmin Job 재실행 |
OpenSearch Pod 부팅 실패 OPENSEARCH_INITIAL_ADMIN_PASSWORD must be set | opensearch-admin SealedSecret 미적용 | kubectl get secret opensearch-admin -n gend → 없으면 SealedSecret 적용 후 재기동 |
securityadmin Job 실패 | transport cert 만료 또는 gend-ca-issuer 문제 | kubectl describe certificate opensearch-transport -n gend 확인 |
securityadmin Job 실패 "..." is not an admin user (#899) | admin_dn / nodes_dn DN 순서 불일치 — cert subject 와 정규화 순서가 다름 | 아래 boxed warning 참조 |
OIDC 토큰 검증 실패 unable to fetch JWK | issuer URL 접근 불가 | OpenSearch Pod 의 NetworkPolicy + DNS 점검 |
Grafana datasource connection refused | NodePort 30560 으로 설정된 채 NodePort 제거됨 | https://opensearch.gend.genon.ai 또는 https://opensearch.gend.svc.cluster.local:9200 으로 datasource 변경 |
OpenSearch security plugin 은 transport cert subject 를 RFC 2253 정규화 순서 (O=...,OU=...,CN=...) 로 추출하여 plugins.security.authcz.admin_dn 및 nodes_dn 과 비교합니다. openssl x509 -subject 가 출력하는 슬래시 표기 (/CN=.../OU=.../O=...) 는 역순 이므로 cert subject 를 그대로 옮겨 적으면 securityadmin.sh 가 admin 으로 인식하지 못하고 다음 에러로 실패합니다:
Connected as "O=gend,OU=opensearch,CN=opensearch-node-1"
ERR: "O=gend,OU=opensearch,CN=opensearch-node-1" is not an admin user
internal_users.yml 의 admin password hash 가 placeholder 상태로 남아 admin basic auth 401 → /admin/audit 502.
infra/opensearch/deployment.yaml 의 plugins.security.authcz.admin_dn.0 와 infra/opensearch/security-config.yaml 의 nodes_dn 둘 다 RFC 2253 순서로 작성합니다:
- name: plugins.security.authcz.admin_dn.0
value: "O=gend,OU=opensearch,CN=opensearch-node-1" # ✅ RFC 2253
# value: "CN=opensearch-node-1,OU=opensearch,O=gend" # ❌ cert /CN/OU/O slash 표기 직역 — 실패
진단: securityadmin.sh 가 출력하는 Make sure opensearch.yml ... admin_dn: - "..." 메시지를 그대로 admin_dn 값으로 사용하면 됩니다. cert 재발급 불필요.
verify 시에는 _cat/indices 사용 금지 — admin backend_role 의 기본 권한 부족으로 403 false-positive 가 발생. _cluster/health (200 기대) 또는 gend-api 가 실제 호출하는 gend-audit-*/_search 로 검증합니다.
로그 조회
다중 레플리카 환경에서는 레이블 셀렉터 사용. 컨텍스트/네임스페이스 플레이스홀더는 환경별로 치환.
# OpenSearch 측
kubectl --context <CONTEXT> logs -n <NAMESPACE> -l app=opensearch --tail=200 \
| grep -iE "Authentication|Unauthorized|ssl|jwt|oidc"
# Fluent Bit 측
kubectl --context <CONTEXT> logs -n <NAMESPACE> -l app.kubernetes.io/name=fluent-bit --tail=200 \
| grep -iE "401|auth|opensearch"
변경 이력
- 도입: #514
- 운영 가이드 문서 추가: 본 PR