12-18 14:29
Notice
Recent Posts
Recent Comments
관리 메뉴

Scientific Computing & Data Science

[MongoDB] Administration / Security & Authentication 본문

Data Science/MongoDB

[MongoDB] Administration / Security & Authentication

cinema4dr12 2014. 3. 25. 20:51

by Geol Choi | 

이번 글에서는 MongoDB의 관리자 입장에서 보안 측면을 다루도록 하겠다. 최근 금융권에서 일어나고 있는 개인정보 유출 사고를 보면 보안이 얼마나 중요한 일인지 새삼 깨닫는다.

특히 정보들이 DB에 저장될 것이므로 DB의 보안에 대한 중요성은 굳이 언급할 필요조차 없을 것이다.

그러면 어떻게 하면 MongoDB가 안전한 환경에서 운용되도록 할 것인지에 대한 내용을 다루도록 하겠다.


관리자 계정

MongoDB에는 admin이라는 데이터베이스가 기본적으로 존재한다. 어느 데이터베이스든 간에 사용자를 등록할 수 있지만, admin 데이터베이스에 사용자로 등록되면 일종의 관리자 권한이 부여된다. 즉, admin 외의 사용자는 다른 데이터베이스에 쓰기나 읽기가 불가능하지만 admin의 사용자는 다른 데이터베이스에 접근하여 쓰기나 읽기가 가능하다. 또한 shutdown 등과 같은 admin에서만 사용할 수 있는 명령어를 수행할 수 있다.

관리자 계정을 이해하기 위해 다음과 같이 사용자를 등록한다:

 db

 username 

 password

 read-only

 admin

 admin_user1

 abcd

 false

 admin

 admin_user2

 efgh

 true

 test

 test_user1

 ijkl

 false

 test test_user2

 mnop

 true
 [표 1.] 사용자 등록 예.

우선 db를 admin으로 변경한다:

> use admin
switched to db admin

[표 1.]과 같이 admin_user1을 admin에 등록한다:

> db.addUser("admin_user1","abcd",false)
{
  "user" : "admin_user1",
  "readOnly" : false,
  "pwd" : "a39513576494a9cde6a85ea31dc5cb2e",
  "_id" : ObjectId("53317dea005c146bf418480e")
}

[표 1.]과 같이 admin_user2를 admin에 등록한다:

> db.addUser("admin_user2","efgh",true)
{
  "user" : "admin_user2",
  "readOnly" : true,
  "pwd" : "09084d5b44dea126caa61dc5636a0cec",
  "_id" : ObjectId("53317e18005c146bf418480f")
}

현재 사용중인 db인 admin의 컬렉션 리스트를 확인하면 다음과 같다:

> db.getCollectionNames()
[ "system.indexes", "system.users" ]

컬렉션을 살펴보면, system.users를 확인할 수 있는데, 이 컬렉션에 방금 등록한 사용자 정보가 저장되어 있다:

> db.system.users.find().pretty()
{
  "_id" : ObjectId("53317dea005c146bf418480e"),
  "user" : "admin_user1",
  "readOnly" : false,
  "pwd" : "a39513576494a9cde6a85ea31dc5cb2e"
}
{
  "_id" : ObjectId("53317e18005c146bf418480f"),
  "user" : "admin_user2",
  "readOnly" : true,
  "pwd" : "09084d5b44dea126caa61dc5636a0cec"
}

이제 db를 셧다운하고 --auth 커맨드라인 옵션을 추가하여 보안 모드를 활성화한다:

$ mongod -dbpath [YOUR_DB_PATH] --auth

이제 MongoDB 쉘에서 dbtest로 변경한 후 다음과 같이 입력해 본다:

> use test
switched to db test
> db.getCollectionNames()
Tue Mar 25 22:12:04.524 error: {
  "$err" : "not authorized for query on test.system.namespaces",
  "code" : 16550
} at src/mongo/shell/query.js:128

현재 db의 컬렉션 리스트를 출력해 보려고 했는데 "not authorized ..."라는 에러 메시지가 출력되는 것을 확인할 수 있을 것이다. 권한이 없다는 것이다.

db.auth(username, password) 명령으로 admin_user1으로 로그인 해보자. 로그인 하려면 다시 admin으로 복귀해야 한다:

> use admin
switched to db admin
> db.auth("admin_user1","abcd")
1

db.auth()를 실행 후 결과값으로 1인 반환되면 로그인에 성공한 것이다. 만약 실패하면 "auth fails"라는 에러 메시지가 출력될 것이다.

이제 dbtest로 이동하여 aa라는 컬렉션에 필드값 b에 1을 저장해 보자:

> use test
switched to db test
> db.aa.insert({b: 1})
> db.aa.find()
{ "_id" : ObjectId("5331832c747d3ab116d80204"), "b" : 1 }

admin_user1read-onlyfalse, 즉 값을 쓸 수 있는 권한이 있으므로 값이 잘 저장되었음을 확인할 수 있을 것이다. 그러면 쓰기 권한이 없는 admin_user2는 어떻게 될까?

다시 admin으로 이동하여 이번에는 admin_user2로 로그인 해보자:

> use admin
switched to db admin
> db.auth("admin_user2","efgh")
1
> use test
switched to db test
> db.bb.insert({c: 1})
not authorized for insert on test.bb

예상했듯이 db에 쓸 수 있는 권한이 없다는 메시지가 출력된다.

지금까지의 내용을 정리해 보자면, admin에 등록된 사용자는 다른 db의 내용을 읽을 수 있으며, read-only false라면 다른 db의 컬렉션에 내용을 쓸 수도 있다.

admin외의 db에 등록된 사용자는 오직 해당 db에서만 읽기와 쓰기(read-only false인 경우)가 가능하다. test에서 직접 사용자를 등록하고 테스트 해 보기 바란다. admin에서 사용자를 등록하는 것과 마찬가지이며, 로그인하는 방식도 동일하다.


기타 보안 이슈

보안을 위해 MongoDB가 어플리케이션 서버를 통해서만 데이터를 읽고 쓸 수 있도록 해야할 것이다. 즉, 외부 IP에서 MongoDB에 직접 접근하는 일을 방지하는 것은 보안을 위한 필수조건이 될 것이다. 이를 위해 MongoDB는 --bind_ip 옵션을 제공하며 이 옵션은 특정 IP에서만 데이터에 접근할 수 있도록 하는 기능이다. 예를 들어, MongoDB 서버와 어플리케이션 서버가 동일한 머쉰에서 작동할 때(즉 이 두 서버가 같은 IP를 공유할 때) 어플리케이션을 통해 데이터를 읽고 쓸 수 있으며, 다른 IP에서 직접적으로 MongoDB 서버에 접근을 막을 수 있다.

다음과 같이 입력하면 localhost로 MongoDB 서버의 IP권한을 설정할 수 있다:

$ mongod -dbpath [YOUR_DATA_PATH] -bind_ip localhost

앞의 글(Administration / Monitoring)에서 MongoDB가 웹페이지 형식으로 모니터링 도구를 제공한다고 언급한 바 있다. 만약 HTTP를 통해 이러한 정보가 노출되는 것을 원하지 않는다면 --nohttpinterface 옵션을 사용한다:

$ mongod -dbpath [YOUR_DATA_PATH] --nohttpinterface


데이터베이스 백업하기

두 말할 필요도 없이 데이터를 백업하는 것은 관리자 업무 중 가장 중요한 것 중 하나이다. 고객의 귀중한 정보이자 이를 운영하는 기업의 중요한 정보 자산을 지키려면 수시로 데이터를 백업해야 한다.

데이터 폴더 저장

데이터를 백업하는 방법에는 여러가지가 있는데, 가장 원시적이면서도 이상적인 방법은 MongoDB 서버를 셧다운한 후 db 폴더를 따로 저장하는 것이다. 해당 db 폴더는 --dbpath 옵션을 통해 지정된 경우라면 해당 폴더를, 디폴트로는 Linux에서는 /data/db, Windows에서는 C:\data\db에 저장되어 있다.

그러나, 이 방법은 서버를 셧다운 해야하기 때문에 서비스가 일시 중단되어야 한다는 문제가 있으므로, 서버가 가동 중인 상태에서 데이터를 백업하는 방법을 사용해야 한다.

mongodumpmongorestore

MongoDB 서버가 가동 상태에서 데이터를 백업할 수 있도록 MongoDB는 mongodumpmongorestore 유틸리티를 제공한다.

mongorestore는 데이터를 따로 저장하는 역할을 수행하며, mongorestore는 따로 저장된 데이터를 불러오는 역할을 한다.

MongoDB의 다른 유틸리티와 마찬가지로 mongodump와 mongorestore도 --help 옵션을 통해 관련 옵션을 확인할 수 있다:

$ mongodump --help
Export MongoDB data to BSON files.

options:
  --help                                produce help message
  -v [ --verbose ]                      be more verbose (include multiple times
                                        for more verbosity e.g. -vvvvv)
  --version                             print the program's version and exit
  -h [ --host ] arg                     mongo host to connect to ( <set 
                                        name>/s1,s2 for sets)
  --port arg                            server port. Can also use --host 
                                        hostname:port
  --ipv6                                enable IPv6 support (disabled by 
                                        default)
  -u [ --username ] arg                 username
  -p [ --password ] arg                 password
  --authenticationDatabase arg          user source (defaults to dbname)
  --authenticationMechanism arg (=MONGODB-CR)
                                        authentication mechanism
  --dbpath arg                          directly access mongod database files 
                                        in the given path, instead of 
                                        connecting to a mongod  server - needs 
                                        to lock the data directory, so cannot 
                                        be used if a mongod is currently 
                                        accessing the same path
  --directoryperdb                      each db is in a separate directly 
                                        (relevant only if dbpath specified)
  --journal                             enable journaling (relevant only if 
                                        dbpath specified)
  -d [ --db ] arg                       database to use
  -c [ --collection ] arg               collection to use (some commands)
  -o [ --out ] arg (=dump)              output directory or "-" for stdout
  -q [ --query ] arg                    json query
  --oplog                               Use oplog for point-in-time 
                                        snapshotting
  --repair                              try to recover a crashed database
  --forceTableScan                      force a table scan (do not use 
                                        $snapshot)

그러면 이 두 유틸리티가 실제로 어떻게 사용되는지 다음 예를 통해 설명하도록 하겠다.

mongodump의 옵션 중 -d는 사용할 데이터베이스의 이름을, -o는 출력할 디렉터리를 지정하는 것이다. 예를 들어, 사용할 데이터베이스트는 test를 디렉터리 /backup/db(또는 C:\backup\db)에 백업 파일을 생성하려면 다음과 같이 입력한다:

$ mongodump -d test -o /backup/db


 [그림 1.] mongodump 유틸리티를 통한 test에 대한 백업 폴더 생성.


[그림 1.]에서 보는 바와 같이 폴더 /backup/db 내에 test라는 이름의 폴더가 생성된 것을 확인할 수 있을 것이다. 백업 파일은 bson 및 json 파일로 구성되어 있다.

이제 mongorestore 유틸리티를 통해 방금 백업한 파일을 임포트 해보자. 데이터베이스의 이름은 test_backup으로 하였다 (실행 중인 MongoDB 서버를 셧다운 할 필요없다!):

$ mongorestore -d test_backup --drop /backup/db/test

-d는 새로 생성할 데이터베이스의 이름으로 test_backup으로 지정하였으며, --drop은 임포트하기 전에 각 컬렉션을 제거하는 것이며, 가장 마지막 argument인 /backup/db/test는 백업한 파일이 저장된 위치이다.

이제 MongoDB 쉘에서 현재 등록된 db 리스트를 확인해 보자:

> show dbs
foo 0.203125GB
local 0.078125GB
test  0.203125GB
test_backup 0.203125GB

test_backup이 등록되어 있음을 확인할 수 있을 것이다. 그리고 그것의 사이즈는 원본 데이터베이스인 test의 사이즈와 정확히 일치한다.

다음과 같이 입력하여 원본의 컬렉션과 일치하는지 확인한다:

> use test_backup
switched to db test_backup
> db.getCollectionNames()
[ "notes", "system.indexes", "users" ]


fsync와 Lock

fsync는 앞서 언급한 백업 방법보다 가장 안정성있는 방법이다. fsync는 MongoDB 서버로 하여금 메모리 상에 올려여 있는 모든 데이터를 디스크로 쓰도록 한 후, 이 과정이 완료되면 백업이 완료될 때까지 쓰기에 잠금(Lock)을 걸어둔다. 관리자가 백업을 완료하면 잠금을 해제하여야 한다.

admin으로 이동 후 다음과 같이 입력하여 쓰기 잠금을 활성화한다:

> use admin
switched to db admin
> db.runCommand({"fsync" : 1, "lock" : 1});
{
  "info" : "now locked against writes, use db.fsyncUnlock() to unlock",
  "seeAlso" : "http://dochub.mongodb.org/core/fsynccommand",
  "ok" : 1
}

info를 살펴보면 쓰기가 잠금 상태가 되었다는 메시지와 함께 잠금을 해제하는 방법에 대한 안내 메시지를 표시한다.

제대로 쓰기 잠금이 되어있는지 테스트 해보자. db를 admin에서 test로 이동하여 컬렉션에 데이터를 저장해 보았다:

> use test
switched to db test
> db.getCollectionNames()
[ "notes", "system.indexes", "users" ]
> db.test.insert({aa: 1})

db.test.insert({aa: 1})를 실행하고 나서 아무 반응이 없다. 즉, 잠금이 제대로 걸린 것이다.

이제 맘놓고 백업을 수행하고 완료되면 잠금 해제를 한다:

> db.fsyncUnlock()
{ "ok" : 1, "info" : "unlock completed" }


Slave 백업

slave는 앞으로 다루게 될 DB 복제(Replication)에 대한 주제에서 자세하게 다룰 예정이지만, 기본적으로 DB 운용의 안정성을 위해 Master-Slave 관계로 Master의 데이터를 여러 개의 Slave에 복제하는 방식이다. Slave는 Master와 거의 실시간에 가까운 수준으로 데이터를 동기화한다. Slave는 Master처럼 성능을 중요시 여기지 않기 때문에 앞서 언급한 방법 중 어느 방법이든 사용가능하다.


데이터베이스 복구하기

관리자에게 있어 어쩌면 최악의 사태를 대비하는 것은 당연하다. 정전이 되거나 소프트웨어 충돌이 일어나는 등으로 인해 예상치 못하게 MongoDB가 셧다운 될 수 있다. 다행히 MongoDB는 셧다운 되기 전까지 DB를 보존한다. 단, 디스크가 무사해야 한다. 만약 이런 일로 인해 데이터 파일에 손상이 될 경우를 대비해 MongoDB는 복구 기능을 제공한다.

데이터 파일 복구는 반드시 MongoDB 서버가 unclean shutdown 상태에서만 실행해야 한다. 그럼 unclena shutdown을 어떻게 진단하는가?

간단하다. 데이터 디렉터리 내 mongod.lock 파일의 사이즈가 0 바이트가 아니라면 mongod는 실행되지 않는다. 그리고 이 파일을 텍스트 편집기로 열어보면 다음과 같은 메시지를 볼 수 있다:

Unclean shutdown detected.

이 경우가 바로 unclean shutdown 상태이다.

데이터베이스 전체를 복구시키는 방법은 다음과 같다. mongod--repair 옵션으로 실행하는 것이다:

$ mongod --repair

파일 사이즈에 따라 복구하는데 걸리는 시간이 차이가 난다. 즉, 데이터가 많을수록 복구 시간을 오래 걸린다.

특정 데이터베이스를 복구하려면 해당 db로 이동하여 다음과 같이 실행한다:

> use test
switched to db test
> db.repairDatabase()
{ "ok" : 1 }


Comments