MongoDB 는 보안 관점으로 크게 5가지로 정리를 할 수 있으며, 해당 문서에서는 인증과 권한, 테스트한 내역만 작성
인증(Authentication)
권한(Authorization)
암호화(Encryption) - TDE - Mongo DB Enterprise 에서 제공
감사 (Auditing) - MongoDB Enterprise 및 Percona 제공
데이터 관리 (Data Governance) - 데이터의 일관성을 유지라 해당 서적에서는 언급 안함
인증(Authentication)
내부 인증
MongoDB와 MongoDB 라우터 서버 간(레플리카 셋에서 각 멤버간)의 통신을 위해서 사용되는 인증
내부 인증은 키 파일과 x.509인증서 2가지 방식을 선택 가능
인증 활성화 하기 위해서 설정파일에서 인증과 관련된 옵션 활성화 해야 함
$ /etc/mongod.conf
security :
authorization: enabled # 내부 및 사용자 인증까지 모두 활성화(내부/사용자 인증을 개별로 설정 못함)
#key 파일 생성은 기존 문서로 대체 합니다.
#기존 key 파일 생성 및 auth 내용
clusterAuthMode : keyFile
keyFile : /etc/mongod.key
keyFile은 평문의 단순 문자열로 구성된 비밀번호 파일을 MongoDB 서버(OS)가 내부 인증으로 사용하도록 하는 방식
keyFile 생성할 때 주의점
해당 파일은 클러스터에 참여하는 모든 MongoDB가 공유해야함-동일한 파일을 모든 멤버의 서버에 복사해서 사용해야 함
keyFile 은 MongoDB 서버 프로세스가 읽을 수 있어야 함
keyFile의 접근권한은 반드시 600, 700으로 파일의 소유주만 접근할 수 있어야 함
keyFile의 내용에서 공백문자는 자동으로 무시
keyFile은 6개 이상 1024개 이하의 문자로 구성돼야 하며, Base-64셋에 포함되는 문자만 사용할 수 있음
사용자 인증
MongoDB서버 외부의 응용 프로그램이 MongoDB 클라이언트 드라이버 이용해서 접속 시도할 때
사용자를 생성할 때 반드시 특정 데이터베이스로 이동해서 생성해야 함 > 인증 데이터베이스라고 함(Authentication Database)
여러 DB에 대해 권한을 가질 수 있지만, 인증 데이터베이스(로그인을 위한)는 하나만 가질 수 있음 (admin 에서 생성 하고, test 에서 또 권한을 주더라도 test를 인증하는 데이터베이스로 할수 없고 로그인할 때는 무조건 admin으로 먼저 접속)
활성화기 위해서 /etc/mongod.conf 파일을 수정. 클러스터 멤버 간 통신의 인증을 위해서 clusterAuthMode 및 keyFile 옵션을 추가 더 사용해야 함
db.system.users.find().pretty() 명령 으로 유저 정보를 확인 가능
동일한 계정명과 패스워드를 하더라도 다음과 같이 생성하면 두 계정은 mongoDB에서는 서로 다른 계정으로 인식함
만약 하나의 사용자 계정이 여러 데이터베이스 대해서 권한을 가지도록 한다면 다음과 같이 해야 함
단순 사용자 인증을 위한 설정
security:
authorization : enabled
use mysns
db.createUser({user:"user",pwd:"password", roles:["readWrite" ] })
use myblog
db.createUser({user:"user",pwd:"password", roles:["readWrite" ] })
#유저 생성 여부 확인
show users
use mysns
db.createUser({user:"user",pwd:"password", roles:[ "readWrite", {role:"readWrite", db:"myblog" } ] })
또는
use mysns
db.createUser({user:"user",pwd:"password", roles:["readWrite" ] })
db.grantRolesToUser("user", [{ role:"readWrite", db:"myblog" }])
생성된 유저 확인
db.system.users.find().pretty()
외부 인증 방식
LDAP나 Active Directory 를 이용해서 사용자 인증을 의미 하며 Enterprise 버전에서만 사용 가능
This article will walk you through using the SASL library to allow your Percona Server for MongoDB instance to authenticate with your company’s Active Directory server. Percona Server for MongoDB includes enterprise level features, such as LDAP authentication, audit logging and with the 3.6.8 release a beta version of data encryption at rest, all in its open source offering.
권한 (Authorization)
액션
명령이 처리되는 동안 발생하는 각 단위 작업을 나누어서 MongoDB의 명령들이 하나 이상의 단위 액션들의 집합으로 처리되는 개념
버전에 따라 미리 정의해 둔 액션의 종류는 매우 다양하고 개수도 많으며, 추가/제거되는 명령도 많기 때문에 MongoDB에서 직접 체크가 필요
최소 단위의 권한으로 일반적인 명령어를 실행하기 위해서는 여러 액션의 권한이 필요 (aggregate라는 명령어를 실행하기 위해서는 find / insert / bypassDocumentValidation이라는 3가지 액션이 필요)
내장된 역할(Role)
MongoDB에서 내부적으로 default로 만든 role로 여러 액션들의 집합체
ex) read 라는 내장된 롤에는 collStats, dbHash, dbStats, find, killCursors, listIndexes, listCollections 의 액션으로 만들어짐
mysns 데이터베이스에 대해서 readWrite 역할을 가지고 myblog 데이터베이스에 대해서 read 역할만 가지는 사용자 계정 생성
mongodb > use mysns
Mongodb > db.createUser({user:"mysns_user",pwd:"mypassword",roles:["readWrite",{role:"read",db:"myblog"}]})
cf ) User 생성 시 인증 DB를 admin 으로 하되, 권한은 DB 단위의 권한만 부여 되는지 여부
changeStream, collStats, dbHash, dbStats, find, killCursors, listIndexes, listCollections 등과 같은 명령어를 처리
readWrite
읽기 + 쓰기
system collection 제외
read에 해당하는 명령어에 convertToCapped, createCollection, dropCollection, createIndex, dropIndex, emptycapped, insert, remove, renameCollectionSameDB, update 와 같은 명령어를 처리
Database Administration Roles
dbAdmin
indexing, gathering, statistics 등 역할을 할 수 있으며
user와 role에 대한 management는 제외
collStats, dbHash, dbStats, find, killCursors, lisstIndexes, listCollections 및 dropCollection, createCollection on system.profile only
기술PM : dbAdmin + readWrite 권한 부여
dbOwner
readWrite + dbAdmin + userAdmin
userAdmin
해당 DB에 대한 user의 roles를 생성하거나 변경을 수행하며, admin에 대한 userAdmin 권한을 받았을 경우 superuser를 생성 가능
WARNING
It is important to understand the security implications of granting the userAdmin role: a user with this role for a database can assign themselves any privilege on that database. Granting the userAdmin role on the admin database has further security implications as this indirectly provides superuser access to a cluster. With admin scope a user with the userAdmin role can grant cluster-wide roles or privileges including userAdminAnyDatabase.
Primary로만 쿼리 실행하며, Primary가 없으면 쿼리 실행은 실패(장애 발생하고 fail over 이전)
PrimaryPreferred
가능하면 Primary로 전송하며 장애로 Primary가 없는 경우 Secondary 로 요청
Secondary
Secondary 멤버로만 전송하며, Primary로는 요청하지 않음. 멤버가 2개 이상일 경우 적절히 분산하여 요청
Secondary가 없는 경우 실패 발생
secondaryPreferred
Secondary와 동일하지만 없는 경우 Primary로 요청
nearest
쿼리 요청 응답시간이 가장 빠른 멤버로 요청 (Primary, Secondary 고려 안함)
동일한 대역에서는 미흡하지만, 레플리카 셋이 글로벌하게 분산되어 멤버들간의 응답시간이 차이가 나는 경우 적절
샤딩 환경의 중복 Document 처리
샤딩된 클러스터는 2가지 경우에만 Document 샤드의 소유권(Ownership)을 체크
쿼리에 샤드의 메타 정보 버전이 포함된 경우
쿼리의 조건에 샤드 키가 포함된 경우
청크가 이동될 때마다 컨피그 서버의 메타 정보가 변경됨(메타 정보의 버전이 1씩 증가)
버전 정보를 쿼리 실행시마다 전달하는데, 버전이 포함된 쿼리를 'Versioned Query'라고 명칭
청크가 이동되더라도 한 시점의 일괄된 데이터를 보장하는 이유가 버전 정보를 포함하고 있기 때문
하지만 Primary에서 실행한 경우만 Versioned Query를 사용하며 Secondary에서는 버전 정보를 포함하지 않음(Unversioned Query)
Shard key를 포함한 쿼리의 경우 특정 샤드로만 쿼리가 전달되므로 해당 샤드가 가진 청크에 포함된 Document만 반환
Shard Key가 없으며, Secondary에서 조회하는 경우 Document의 중복이 발생 가능성 존재
[ Multi-Document Transaction]
Mongodb Client API도 반드시 4.2 버전으로 사용
4.4 부터 명시적으로 Transaction 내에서 collection 을 생성 가능(Client API 또한 4.4로 사용해야 가능)
MongoDB 버전 4.0부터 제공되었지만, Replica Sets 환경에서 만 지원되었으며 4.2부터 Shard-Cluster 환경에서도 지원
Multi-Document Transation 은 여러 작업, Collection, DB에 적용할 수 있으며 Transaction 이 Commit 되면 변경된 모든 데이터를 저장하고 Rollback 되면 모든 데이터 변경을 취소
Commit 이 확정될 때까지 변경 중인 데이터는 누구도 참조할 수 없음( dirty_read : no)
Embedded Document 및 Array 구조와 같은 단일-Dcoument Transaction 에 비해 성능지연 문제가 발생할 가능성이 있기 때문에, 이를 대체해서 사용하면 안됨
FeatureCompatibility Version(fCV) 4.0 이후 환경에서 사용할 수 있으며 wiredTiger Storage Engine과 In-Memory Storage Engine에서 만 사용할수 있음(admin DB에서 설정 가능)db.adminCommand({ getParameter:1, featureCompatibilityVersion:1})
만약 collation option 없이 생성했다가 collation option 넣은 후에 동일하게 생성하려고 해도 생성이 안되며 기존 Index 삭제 후 재생성 해야함
[ Default Indexes : _id]
Collection 생성 시, 별도로 생성하지 않는다면, 기본적으로 _id field에 대해서 Index가 생성
unique Index이며 삭제할 수 없음
PK나 Secondary index나 모두 내부는 동일하기에 Cluster index 개념이 없음
[ Single Field Indexes ]
1개의 필드로 생성된 것을 Single Field Indexes
Index는 생성한 Field를 기준으로 정렬
[ Compound(Composite) Index ]
2개 이상의 필드가 연결된 것을 Compound Indexes
각각 다른 방식으로 정렬하여 생성 가능
[ Multikey Indexes ]
document 내의 document 가 존재하는 embedded document 또는 array 형태의 Field에 Index를 생성
Multikey Index 의 경우 shard key로 사용될 수 없음
shard key는 하나의 chunk로 매핑되어야 하는데, 여러개의 엔트리가 들어 있는 형태로는 불가능
Multi-key index는 커버링 인덱스 처리가 불가능
필드가 array인데 Index 생성하면 자동으로 multikey 인덱스로 생성
Unique Multi-key index는 document 내에서 Unique 가 아닌 Collection 내에서 Unique 함
Compound + Multikey Index에서는 하나의 Multikey 만 포함 가능
또한, 문제없이 compound + Multikey index로 생성되어 있는 경우 그 중 single field에 array 형태로 insert,update 를 시도하면 에러가 발생
ex 1) 아래와 같이 a,b 필드가 array 인 경우 {a:1, b:1} 이런식으로 2개의 compound multikey index를 생성할 수 없다.
{ _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" }
ex 2) {a:1, b:1} 이런 형태로 정상적으로 index가 문제 없이 생성되어 있는 collection 형태에서, 하나의 document에 a,b를 array 형태로 변경 시 문제 발생
{ _id: 1, a: [1, 2], b: 1, category: "A array" } <- b를 array 형태로 document 를 추가 수정 시 fail 발생
{ _id: 2, a: 1, b: [1, 2], category: "B array" } <- a를 array 형태로 document 를 추가 수정 시 fail 발생
ex3) compound + multikey index를 여러 array 에 사용하고 싶은 경우 아래와 같이 설계하면 사용 가능 { "a.x": 1, "a.z": 1 }
{ _id: 1, a: [ { x: 5, z: [ 1, 2 ] }, { z: [ 1, 2 ] } ] }
{ _id: 2, a: [ { x: 5 }, { z: 4 } ] }
[ Text Indexes ]
문자열 내용에 대한 텍스트 검색 쿼리를 지원
문자열 또는 문자열 요소의 배열인 모든 필드가 포함
하나의 Collection에 하나의 text index만 생성 가능
하나의 text index 생성 시 Compound로 생성 가능
생성하려는 Field에 text 로 명시 (다른 Index 생성과 다른 방식)
text Index 생성하게 되면 기본적으로 해당 "field명_text" 명으로 생성
$meta 를 이용하여 검색하는 text에 대해 가중치를 제공 가능
mongodb에서 한글에 대해서 ngram, 형태소분석을 기본적으로 제공하지 않고 구분자 기반(공백문자) 기준으로 인덱싱 처리함
한 단어의 부분에 대해서도 검색을 가능하게 하려면 ngram full text index 기능을 사용해야함
# text Index 생성 방법
db.array.createIndex({"month_data":"text"})
# Compound 으로 생성 방법
db.reviews.createIndex(
{
subject: "text",
comments: "text"
}
)
#가중치 $meta 를 이용하여 sort 진행
db.array.find({$text:{$search:"서울"}},{score:{$meta: "textScore"}}).sort({score:{$meta:"textScore"}}).pretty()
#Wildcard Text Index
db.collection.createIndex( { "$**": "text" } )
MongoDB 인덱스를 생성하는 경우 (Foreground) Collection Lock 이 걸리게 된다.(쿼리 웨이팅 발생, 단 빠르게 생성) , (Session Blocking 발생-순간)
다만, Background Index로 생성하는 경우 Lock을 피할수 있기에 동시 사용성이 증가
Background Index 생성 시 해당 collection으로 session유입 시 Index생성이 잠시 중단 되었다가 완료 되면 다시 시작 (Foreground Index보다 생성 시간이 늦어짐)
Index 생성이 완료 되면 그 때 OpLog에 작성이 되며, 이 것을 받아 Secondary에서도 동일하게 Background로 시작(v2.4의 경우 Secondary에서는 Foreground로 생성 되는 버그. 주의)
Collection의 Document가 많거나 Session 유입이 아주 많다면 세컨드리에서 포그라운드로 먼저 생성 후 프라이머리와 교체하는 것도 방법
RDBMS의 경우 인덱스 생성 시 버퍼 공간을 사용하지만, 몽고 디비의 경우 따로 버퍼를 사용하지 않으며 트랜잭션로그(Undo Log)를 사용하지도 않기 때문에 오래 걸릴수도 있지만, 반대로 단순하기 때문에 버퍼 영역으로 인해 실패하거나 DB에 문제를 일으키지 않는 장점
인덱스 삭제의 경우도 메타 정보를 변경하고 인덱스와 연관된 데이터 파일만 삭제하면 되므로 매우 빠르게 진행(점검을 걸거나 할 필요 없음, 하지만 한순간 데이터베이스 잠금을 필요로 하므로 쿼리 처리량 낮은 시점에 삭제하는 것이 좋음)
Background Index 생성 시, DB가 재시작(인덱스 빌드 프로세스를 강제 종료) 된다면 Index도 DB가 시작되면서 다시 시작하게 됨. 이 때 Foreground로 시작. 이 때 indexBulidRetry 옵션을 False로 설정하면 막을 수 있음
생성되는 지 체크하기 위해서 MongoDB Log나 OpLog를 통해 세컨더리에 생성 되었는지 확인 하면 됨
Multikey Index에서 배열 필드에 대해서는 Covered index 가 불가능(v3.6부터 non-array field들에 대해서는 사용 가능)
Covered Query
field 명시 방법
필드에 _id 여부는 항상 명시해야 함(_id를 쿼리 결과 내에서 표현하거나 표현하지 않는 것에 대해 명시를 반드시 해야 함)
_id에 대해서만 혼용 사용 가능하며, 다른 필드들에 대해서는 보여 주고 싶은 것에 대해서만 1로 명시, 0으로 명시하면 에러 발생
Ex ) > db.thing.find( { }, {_id:0, empno: 1} ) // empno만 표시하고, _id 및 다른 필드는 표시 안함. // 여기서 중요한건 _id는 항상 명시해 줘야 하며, 보고 싶은 필드만 1로 설정해서 표시 // 다른 필드의 경우 birth 필드가 있더라도 birth:0 으로 하면 에러 발생...왜???모르겠음
만약 empno만 빼고 다 보고자 하면 그때는 > db.thing.find( { }, {empno: 0} ) 이런 식으로 표시
보고 싶은 field가 있다면, field를 명시할 때 보겠다는 field 들만 명시를 하고,field 명시 여부를 혼용해서 명시하게 되면, 에러가 발생
반대로 보고 싶지 않은 field가 있다면 명시 안하겠다는 field 들만 명시를 해야 함
[Filed]
Covered Query Test
# semester 에 대한 Index 확인
> db.employee.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"semester" : 1
},
"name" : "semester_1"
}
]
# _id 필드를 명시적으로 0 처리 하지 않으면, 자연스럽게 _id 필드도 같이 표시되기 때문에 명시적으로 _id 필드를 표시 안한다고 해야, Convered query 적용 여부를 확인 가능
> db.employee.find({semester:3},{_id:0, semester:1}).explain()
# 필드를 표시 안하게 되는 경우 _id 필드가 표현 되기에 covered query가 되지 않음
> db.employee.find({semester:3},{"field" : null}).explain()
# covered index 확인
# 필드를 표시 안 하게 되는 경우 _id 필드가 표현 되기에 covered query가 되지 않음 (단순 인덱스 스캔 - fetch 진행)
Universally unique identifier 의 약어로, 16-octet(128bit) 크기의 32개의 Hexa로 표시
OSF에서 표준화(개방 소프트웨어 재단(Open Software Foundation)-유닉스 운영 체제의 일부로 오픈 표준을 만들 목적으로 1984년의 미국 National Cooperative Research and Production Act 하에 1988년에 설립된 비영리 단체)
UUID를 구현하는데는 다양한 방식이 있는데, MAC address 나 HASH(md5, sha-1) 등을 이용한 방식이 유명
MAC address 자체가 unique 하기 때문에, 여기에 현재의 시간을 붙이는 방식으로 구현이 가능
# 일반적으로 사용하는 명령어는 command 에 작성하여 사용 가능
# Default
> db.runCommand(
{
명령어
}
)
# 관리 administrative 명령어의 경우 아래와 같이 사용 가능
> db.adminCommand( { <command> } )
Changes the minimum number of data-bearing members (i.e commit quorum), including the primary, that must vote to commit an in-progress index build before the primary marks those indexes as ready.
MongoDB의 Aggregate() 명령은 기본적으로 정렬을 위해서 100mb 메모리까지 사용 가능.
만약 그 이상의 데이터를 정렬해야 하는 경우라면 Aggregate() 명령은 실패-> 이 때 allowDiskUse 옵션을 true로 설정 시 Aggregate()처리가 디스크를 이용해 정렬 가능. 이 때 MongoDB 데이터 Directory 하의에 "_temp" Diretory 를 만들어 임시 가공용 데이터 파일을 저장
limit 와 batchSize 를 지정하지 않는 경우 batch는 한번 당 101개의 Document 결과를 리턴 하지만, Document 당 너무 많은 데이터가 있는 경우 batch 한번 당 1Mb 가 최대 size
limit 와 batchSize 를 지정하는 경우, 지정한 수만큼 리턴
큰 수로 셋팅하더라도 4Mb 이상의 데이터를 한번의 Batch로 가져올 수 없음.
인덱스 없이 데이터를 sort하는 경우 첫 번째 batch에 모든 데이터를 가져오지만, 최대 4 Mb 초과할 수 없음.
> // for문을 돌려서 간단한 데이터로 200개 document 를 등록한다.
> for (var i = 0; i < 200; i++) { db.foo.insert({i: i}); }
> var cursor = db.foo.find()
> // batchSize나 limit 값 지정없이 find 했으므로 기본 batch 크기인 101 documents
> cursor.objsLeftInBatch()
101
> // 한번에 모든 document들을 가져오기 위해 큰 limit 값을 셋팅하면 batchSize는 모든 document 수가 된다.
> var cursor = db.foo.find().limit(1000)
> cursor.objsLeftInBatch()
200
> // batchSize 가 limit 크기보다 작으면 batchSize가 우선한다.
> var cursor = db.foo.find().batchSize(10).limit(1000)
> cursor.objsLeftInBatch()
10
> // limit 가 batchSize 보다 작으면 limit 가 우선한다.
> var cursor = db.foo.find().batchSize(10).limit(5)
> cursor.objsLeftInBatch()
5
> // 각각 1MB 데이터로 10개의 document 를 등록한다.
> var megabyte = '';
> for (var i = 0; i < 1024 * 1024; i++) { megabyte += 'a'; }
> for (var i = 0; i < 10; i++) { db.bar.insert({s:megabyte}); }
> // limit나 batchSize를 지정하지 않았으므로 첫번째 batch는 1MB 에서 멈춘다.
> // 결국 1개씩 반복적으로 데이터를 가져오게됨
> var cursor = db.bar.find()
> cursor.objsLeftInBatch()
1
만약 empno만 빼고 다 보고자 하면 그때는 > db.thing.find( { }, {empno: 0} ) 이런식으로 표시
_id에 대해서만 혼용 사용 가능하며, 다른 필드들에 대해서는 1과 0을 혼용해서 표시 안됨
조건 검색 시 가급적 _id를 작성하며 Range 검색 시 기간을 지정하여 검색 추천
Ex) db.bios.find( {"_id":{"$gte":ObjectId("5dfaf5c00000000000000000", "$lte":ObjectId("5dfaf5c00000000000000000")}, {_id:0, name:1 , money:1}) (오늘 이전 모든 데이터 검색 or 오픈 이후 모든 날짜에 대해 검색 같은 전체 검색은 지양, 어제부터 일주일 전, 현재부터 하루 전 데이터 식으로 검색을 추천)
filed 명시 방법
> db.thing.find( { }, {empno: 1} )
// empno 를 표시하고, _id는 default로 표시, 단 그 외(ename) 은 표시 안함
> db.thing.find( { }, {empno: 0} )
// empno 만 표시를 안하고 나머지 ename , _id는 표시
> db.thing.find( { }, {_id:0, empno: 1} )
// empno만 표시하고, _id 및 다른 필드는 표시 안함.
// 여기서 중요한건 _id는 항상 명시해 줘야 하며, 보고 싶은 필드만 1로 설정해서 표시
// 다른 필드의 경우 ename이 있더라도 ename:0 으로 하면 에러 발생 >> 왜?????
> db.thing.find( { }, {empno: 1, ename:0} )