x.509 인증서 설정하여 MongoDB 연결하기

Sun choi·2025년 6월 24일

NEW 지식

목록 보기
34/34
post-thumbnail

토큰을 이용하여 몽고DB에 접속하는 건 그리 어렵지 않은 일이였다.
하지만 나는 x.509 인증서를 사용해 tls 설정을 통해 몽고DB에 접속하려고 시도하면서 많은 고비가 있었다. 그 방법을 정리해보려한다.

0. 목적

  • 서버 애플리케이션이 미리 MongoDB 접속을 위해 인증 정보를 가지고 있음.
  • 서버는 클라이언트의 요청을 받고, 내부 인증 상태(x.509 인증서)를 확인.
  • 미리 인증된 상태로 MongoDB와 지속 연결하거나 필요 시 재연결하여 작업 수행.
  • 클라이언트가 직접 MongoDB 인증 정보를 알 필요도, 보내야 할 필요도 없음.

1. x.509 인증서 생성

📦 인증서 생성 과정

OpenSSL 사용하여 CA 파일과 서버, 클라이언트 각각의 인증서를 생성하는 스크립트이다. 서버 인증서는 몽고DB 컨테이너를 띄울때 필요하며, CA 파일은 몽고DB와 클라이언트가 둘 다 같은 내용의 파일을 가지고 있어야한다. 클라이언트 인증서는 말그대로 접속하려는 클라이언트마다 권한을 다르게 설정하여 다른 인증서를 가지고 있다. 따라서 한 번만 생성하면 되는 CA 파일과 몽고DB에 넣을 인증서를 한 스크립트로 생성하였고, 클라이언트 인증서는 사용자 이름을 받아 생성할 수 있게 생성하였다.

총 생성되는 파일은 ca.crt, ca,key와 각각 사용자 인증서인 .pem 파일을 생성하기 위한 4가지 파일이다.

set_mongo_cert.sh

#!/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

set_client_cert.sh

#!/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

2. MongoDB에 사용자 등록

🗃️ MongoDB Docker

인증서를 생성하였다면 몽고DB를 컴포즈 파일로 만들어서 띄워야한다.

docker-compose.yml

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

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와 반드시 일치해야 한다. ✔️

3. FastAPI에서 MongoDB에 x.509 인증으로 접속 테스트

🧪 인증 테스트

내가 테스트를 워해 만든 파일이다.

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": "해당 댓글을 찾을 수 없습니다."}

4. 결론

결국엔 로컬에서는 연결 성공했지만 인스턴스 컨테이너에 띄워진 각각의 API 컨티이너와 MongoDB 컨테이너를 인증서를 통해 연결하는 것엔 실패하였다.. DB에 접속은 하였지만 인증서 위치와 권한 문제로(아마?) 데이터가 들어가지 않았다. 꼭 이어서 다시 도전해보고싶다.

profile
풀스택 개발자의 공부기록 📖

0개의 댓글