보안 강화: MongoDB / Redis / Elasticsearch
증상
목표: CIS 방식으로 NoSQL/데이터 저장소를 강화한다 — 인증, 네트워크 제한, 위험 명령 차단, TLS, 최소 권한 역할, 감사.
1. 인증을 활성화한다 — 인증되지 않은 접근을 차단한다
이 제품들은 기본적으로 인증이 비활성화된 상태로 제공되는 경우가 많아, 인터넷에 노출된 채 방치되면 침해된다. 인증을 활성화하고 관리자 사용자를 생성한다.
MongoDB
// mongod.conf:
security:
authorization: enabled
// Create an administrator user (first time, via the localhost exception):
use admin
db.createUser({ user: "admin", pwd: passwordPrompt(),
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] })
// Then connect with: mongosh -u admin --authenticationDatabase adminRedis
# redis.conf — ACL (Redis 6+): disable the default user, define named users:
user default off
user admin on >AdminPassword789! ~* &* +@all
user app on >AppPassword456! ~app:* +@read +@write
# Note: requirepass is a legacy compatibility setting that only sets the 'default' user's password;
# do not confuse it with ACL users. You can also keep users in an aclfile (persisted with ACL SAVE).Elasticsearch
# 8.x: security (authentication + TLS) is configured automatically on first start; elasticsearch.yml:
xpack.security.enabled: true
# Set/reset the password of the 'elastic' user:
bin/elasticsearch-reset-password -u elastic
# (elasticsearch-setup-passwords is deprecated since 8.0)
# Create separate users with dedicated roles for Kibana/clients instead of 'elastic'.2. 네트워크를 제한한다 — 필요한 인터페이스와 포트만 연다
서비스가 0.0.0.0으로 전 세계에 listen하지 않도록 한다. bind 주소를 내부 인터페이스/loopback으로 고정하고 protected mode를 활성화한다.
MongoDB
# mongod.conf — internal interface + loopback only:
net:
bindIp: 127.0.0.1,10.0.0.10
port: 27017
# Never use bindIpAll: true; limit the port with a firewall.Redis
# redis.conf:
bind 127.0.0.1 10.0.0.10
protected-mode yes
port 6379
# protected-mode blocks external access when no password is set;
# still limit 6379 with a firewall.Elasticsearch
# elasticsearch.yml — pin the listening interface:
network.host: 10.0.0.10
http.port: 9200
transport.port: 9300
# Open only the required ports in the firewall;
# 9300 (transport) must never be exposed externally.3. 위험한 명령을 제한한다
명령 하나로 모든 데이터가 지워질 수 있다. 파괴적/관리 명령의 이름을 바꾸거나 제거하고, 와일드카드 대상 지정을 차단한다.
MongoDB
// Use role-based control instead of a separate command block:
// Grant destructive privileges (dropDatabase, dropCollection) only to
// dbAdmin/clusterAdmin roles; do not grant them to the application user.
db.grantRolesToUser("app", [ { role: "readWrite", db: "appdb" } ])
// Do NOT grant dbAdmin/root to the application user.Redis
# For Redis 6+ the preferred way is ACLs (do not give the application user dangerous commands):
# ACL SETUSER app ... -@dangerous
# Legacy method — disable/rename commands in redis.conf:
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command DEBUG ""
# Caution: renaming CONFIG can break Sentinel, CONFIG REWRITE and management tools.Elasticsearch
# elasticsearch.yml — block wildcard/all-index deletion:
action.destructive_requires_name: true
# This rejects DELETE /* or DELETE /_all;
# bulk deletion is not possible without naming the index explicitly.4. TLS/암호화 연결을 활성화한다
자격 증명과 데이터가 개방된 네트워크에서 암호화되지 않은 채 전송되어서는 안 된다. 클라이언트 및 노드 간 트래픽에 TLS를 활성화한다.
MongoDB
# mongod.conf — client TLS:
net:
tls:
mode: requireTLS
certificateKeyFile: /etc/ssl/mongodb.pem
CAFile: /etc/ssl/ca.pem
# Client: mongosh --tls --tlsCAFile ca.pemRedis
# redis.conf — enable TLS, close the plain port:
port 0
tls-port 6379
tls-cert-file /etc/redis/redis.crt
tls-key-file /etc/redis/redis.key
tls-ca-cert-file /etc/redis/ca.crtElasticsearch
# elasticsearch.yml — HTTP and transport TLS:
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: http.p12
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: transport.p125. 최소 권한 역할을 부여한다 — 광범위한 역할을 피한다
각 사용자에게 업무에 필요한 접근만 준다. 광범위/관리 역할 대신 특정 데이터베이스, 키 패턴, 인덱스로 제한된 세분화된 역할을 사용한다.
MongoDB
// Instead of broad readWrite, grant only on the required DB;
// define a custom role if needed:
db.createRole({ role: "ordersRW",
privileges: [ { resource: { db: "appdb", collection: "orders" },
actions: [ "find", "insert", "update" ] } ], roles: [] })
db.grantRolesToUser("app", [ { role: "ordersRW", db: "appdb" } ])Redis
# Limit by key pattern and command category with ACLs:
ACL SETUSER app on >AppPassword456! ~app:* +@read +@write -@dangerous
# Only keys with the 'app:' prefix, read/write; dangerous commands forbidden.
ACL LIST
ACL WHOAMIElasticsearch
# Define a fine-grained role (read-only on specific indices):
POST /_security/role/orders_read
{ "indices": [ { "names": [ "orders-*" ],
"privileges": [ "read" ] } ] }
# Assign it to the user:
POST /_security/user/app
{ "password": "...", "roles": [ "orders_read" ] }6. 감사/로깅을 활성화한다 — 접근과 변경을 모니터링한다
무단 접근 시도와 관리 작업은 기록되어야 한다. audit log를 활성화하고 중앙 위치로 전송한다.
MongoDB
# Auditing (requires MongoDB Enterprise) — mongod.conf:
auditLog:
destination: file
format: JSON
path: /var/log/mongodb/audit.json
# Filter: capture events such as authenticate, createUser, dropDatabase.Redis
# Redis has no full audit; use ACL LOG to see rejected
# authentication/command attempts:
ACL LOG
ACL LOG RESET
# Also: route the loglevel notice and logfile settings to a central collector.Elasticsearch
# elasticsearch.yml — enable security auditing:
xpack.security.audit.enabled: true
xpack.security.audit.logfile.events.include:
[ authentication_failed, access_denied, connection_denied ]
# Records are written to the <cluster>_audit.json file.