토큰을 이용하여 몽고DB에 접속하는 건 그리 어렵지 않은 일이였다.
하지만 나는 x.509 인증서를 사용해 tls 설정을 통해 몽고DB에 접속하려고 시도하면서 많은 고비가 있었다. 그 방법을 정리해보려한다.
OpenSSL 사용하여 CA 파일과 서버, 클라이언트 각각의 인증서를 생성하는 스크립트이다. 서버 인증서는 몽고DB 컨테이너를 띄울때 필요하며, CA 파일은 몽고DB와 클라이언트가 둘 다 같은 내용의 파일을 가지고 있어야한다. 클라이언트 인증서는 말그대로 접속하려는 클라이언트마다 권한을 다르게 설정하여 다른 인증서를 가지고 있다. 따라서 한 번만 생성하면 되는 CA 파일과 몽고DB에 넣을 인증서를 한 스크립트로 생성하였고, 클라이언트 인증서는 사용자 이름을 받아 생성할 수 있게 생성하였다.
총 생성되는 파일은 ca.crt, ca,key와 각각 사용자 인증서인 .pem 파일을 생성하기 위한 4가지 파일이다.
#!/bin/bash
CERT_DIR="./mongo-certs"
mkdir -p "$CERT_DIR"
cd "$CERT_DIR"
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=TEST_CA"
openssl genrsa -out mongodb.key 4096
openssl req -new -key mongodb.key -out mongodb.csr -subj "/CN=TEST_MongoDB"
openssl x509 -req -in mongodb.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out mongodb.crt -days 365 -sha256
cat mongodb.key mongodb.crt > mongodb.pem
#!/bin/bash
CA_DIR="../mongo/mongo-certs"
CERT_DIR="./mongo-certs"
mkdir -p "$CERT_DIR"
cd "$CERT_DIR"
if [[ ! -f "$CA_DIR/ca.crt" || ! -f "$CA_DIR/ca.key" ]]; then
echo "CA 파일이 존재하지 않습니다: $CA_DIR"
exit 1
fi
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr -subj "/CN=client1"
openssl x509 -req -in client.csr -CA "$CA_DIR/ca.crt" -CAkey "$CA_DIR/ca.key" -CAcreateserial -out client.crt -days 365 -sha256
cat client.key client.crt > client.pem
인증서를 생성하였다면 몽고DB를 컴포즈 파일로 만들어서 띄워야한다.
version: '3.8'
services:
mongodb:
image: mongo:8.0.10
container_name: mongodb
ports:
- "27017:27017"
volumes:
- /home/test/mongo/data:/data/db
- /home/test/mongo/certification/mongo-certs/mongodb.pem:/etc/ssl/mongodb/mongodb.pem:ro
- /home/test/mongo/certification/mongo-certs/ca.crt:/etc/ssl/mongodb/ca.crt:ro
- /home/test/mongo/certification/mongo-certs/mongod.conf:/etc/mongod.conf:ro
command: ["mongod", "--config", "/etc/mongod.conf"]
storage:
dbPath: /data/db
systemLog:
destination: file
path: /var/log/mongodb/mongod.log
logAppend: true
net:
port: 27017
tls:
mode: requireTLS
certificateKeyFile: /etc/ssl/mongodb/mongodb.pem
CAFile: /etc/ssl/mongodb/ca.crt
security:
authorization: enabled # 사용자 인증 활성화 enabled/ disabled
processManagement:
fork: false
이 때 tls 설정을 enabled하기 전에 먼저 인증서로 접속할 클라이언트를 DB에 등록시켜놔야한다. 몽고DB에 접속하여 use $external 로 이동한 후 권한을 포함한 사용자를 등록한다. 아래는 예시이다.
db.createUser({
user: "CN=client1",
roles: [
{ role: "readWrite", db: "mydatabase" }
]
})
✔️ 이 때, CN 명칭은 인증서의 subject와 반드시 일치해야 한다. ✔️
내가 테스트를 워해 만든 파일이다.
from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel
from pymongo import MongoClient
from bson import ObjectId
import pymongo
app = FastAPI()
mongo_uri = "mongodb://localhost:27017/?authMechanism=MONGODB-X509&authSource=$external"
tls_certificate_key_file = "/home/test/mongo/certification/mongo-certs/client.pem"
tls_ca_file = "/home/test/mongo/certification/mongo-certs/ca.crt"
admin_client = MongoClient(
mongo_uri,
tls=True,
tlsCertificateKeyFile="/home/test/mongo/certification/mongo-certs/client.pem",
tlsCAFile="/home/test/mongo/certification/mongo-certs/ca.crt",
authMechanism="MONGODB-X509"
)
db = admin_client["mydatabase"]
user_collection = db["users"]
########################################## DB CONNECT #########################################
def get_mongo_client():
try:
client = MongoClient(
mongo_uri,
tls=True,
tlsCertificateKeyFile=tls_certificate_key_file,
tlsCAFile=tls_ca_file,
authMechanism="MONGODB-X509"
)
client.admin.command('ping')
return client
except Exception as e:
raise HTTPException(status_code=403, detail=f"해당 작업에 대한 권한이 없습니다.")
############################################# BOARD ############################################
class Board(BaseModel):
title: str
contents: str
@app.post("/boards")
async def create_board(board: Board):
client = get_mongo_client()
db = client["mydatabase"]
board_collection = db["boards"]
new_board = board.model_dump()
try:
result = board_collection.insert_one(new_board)
except Exception as e:
raise HTTPException(status_code=403, detail=f"해당 작업에 대한 권한이 없습니다.")
return {"id": str(result.inserted_id)}
@app.get("/boards/{board_id}")
async def get_board(board_id: str):
client = get_mongo_client()
db = client["mydatabase"]
board_collection = db["boards"]
board = board_collection.find_one({"_id": ObjectId(board_id)})
if board:
return {"title": board["title"], "contents": board["contents"]}
return {"message": "해당 게시물을 찾을 수 없습니다."}
########################################### COMMENT ##########################################
class Comment(BaseModel):
contents: str
@app.post("/comments")
async def create_comment(comment: Comment):
client = get_mongo_client()
db = client["mydatabase"]
comment_collection = db["comments"]
new_comment = comment.model_dump()
try:
result = comment_collection.insert_one(new_comment)
except Exception as e:
raise HTTPException(status_code=403, detail=f"해당 작업에 대한 권한이 없습니다.")
return {"id": str(result.inserted_id)}
@app.get("/comments/{comment_id}")
async def get_comment(comment_id: str):
client = get_mongo_client()
db = client["mydatabase"]
comment_collection = db["comments"]
comment = comment_collection.find_one({"_id": ObjectId(comment_id)})
if comment:
return {"contents": comment["contents"]}
return {"message": "해당 댓글을 찾을 수 없습니다."}
결국엔 로컬에서는 연결 성공했지만 인스턴스 컨테이너에 띄워진 각각의 API 컨티이너와 MongoDB 컨테이너를 인증서를 통해 연결하는 것엔 실패하였다.. DB에 접속은 하였지만 인증서 위치와 권한 문제로(아마?) 데이터가 들어가지 않았다. 꼭 이어서 다시 도전해보고싶다.