Skip to content

이번 포스팅에서는 MongoDB의 관리자 입장에서 보안 측면을 다루도록 하겠습니다.

어떻게 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.] 사용자 등록 예.

우선 dbadmin으로 변경합니다:

> use admin
switched to db admin

[표 1.]과 같이 admin_user1admin에 등록합니다:

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

[표 1.]과 같이 admin_user2admin에 등록합니다:

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

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

> 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-onlyfalse라면 다른 db의 컬렉션에 내용을 쓸 수도 있습니다.

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


기타 보안 이슈

보안을 위해 MongoDB가 어플리케이션 서버를 통해서만 데이터를 읽고 쓸 수 있도록 해야할 것입니다. 즉, 외부 IP에서 MongoDB에 직접 접근하는 일을 방지하는 것은 보안을 위한 필수조건이 될 것입니다. 이를 위해 MongoDB는 --bind_ip 옵션을 제공하며 이 옵션은 특정 IP에서만 데이터에 접근할 수 있도록 하는 기능입니다.\

예를 들어, MongoDB 서버와 어플리케이션 서버가 동일한 머쉰에서 작동할 때(즉 이 두 서버가 같은 IP를 공유할 때) 어플리케이션을 통해 데이터를 읽고 쓸 수 있으며, 다른 IP에서 직접적으로 MongoDB 서버에 접근을 막을 수 있습니다.

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

$ mongod -dbpath [YOUR_DATA_PATH] -bind_ip localhost

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

$ mongod -dbpath [YOUR_DATA_PATH] --nohttpinterface

데이터베이스 백업하기

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


데이터 폴더 저장

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

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


mongodumpmongorestore

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

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

MongoDB의 다른 유틸리티와 마찬가지로 mongodumpmongorestore--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라는 이름의 폴더가 생성된 것을 확인할 수 있을 것입니다. 백업 파일은 bsonjson 파일로 구성되어 있습니다.

이제 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" ]

fsyncLock

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를 살펴보면 쓰기가 잠금 상태가 되었다는 메시지와 함께 잠금을 해제하는 방법에 대한 안내 메시지를 표시합니다.

제대로 쓰기 잠금이 되어있는지 테스트 해보겠습니다. dbadmin에서 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에 복제하는 방식입니다.

Slavemaster와 거의 실시간에 가까운 수준으로 데이터를 동기화합니다. Slavemaster처럼 성능을 중요시 여기지 않기 때문에 앞서 언급한 방법 중 어느 방법이든 사용가능합니다.


데이터베이스 복구하기

관리자에게 있어 어쩌면 최악의 사태를 대비하는 것은 당연합니다. 정전이 되거나 소프트웨어 충돌이 일어나는 등으로 인해 예상치 못하게 MongoDB가 셧다운 될 수 있습니다.

다행히 MongoDB는 셧다운 되기 전까지 DB를 보존합니다. 단, 디스크가 무사해야 합니다. 만약 이런 일로 인해 데이터 파일에 손상이 될 경우를 대비해 MongoDB는 복구 기능을 제공합니다.

데이터 파일 복구는 반드시 MongoDB 서버가 unclean shutdown 상태에서만 실행해야 합니다. 그럼 unclean 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 }