ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Django의 settings.py와 KMS(Key Management Service)에 관해
    Python 2020. 2. 16. 16:12

    많은 Python개발자들이 웹서비스를 개발한다면 아마 웬만하면 Django를 우선적으로 고려하지 않을까 싶네요. 이번에 하고 싶은 이야기는 Django의 settings.py의 설정 값들에 관한 보안 처리입니다. 그동안 다양한 방법으로 비밀스러운(?) 값들을 보관하고 사용하다 최근에 KMS로 다 교체하였습니다. 그러므로 오늘 주제는

    KMS를 활용한 settings.py 설정 값의 암호화

     

    settings.py에 관해

    수 년 간 Django를 통해 엔터프라이즈 급 서비스를 개발하고 운영하고 있습니다. 그 동안 서비스를 유지&보수하면서 느낀 것들이 많았어요. 처음 서비스 개발을 시작할 때 알았으면 좋았을 것들 말이죠. 그중 하나가 Python 소스코드 레이아웃입니다. 보통 처음 하시는 분들은

    django-admin startproject my_first_project

    같은 명령어로 시작합니다. 그러면 다음과 같이 파일 구조가 생기겠죠.

    startproject 명령어로 생긴 파일 구조

    프로잭트명으로 폴더가 생기고 그 안에 프로잭트명으로 또 하나의 폴더와 manage.py 파일이 하나 생성됩니다. settings.py는 Django가 구동될 때 사용되는 여러 설정 값들이 포함되어 있습니다. 데이터베이스 정보, 보안 관련, 정적 파일 경로 등이 포함되어 있죠. 그렇기 때문에 나중에 운영을 위해 개발과 스테이징, 프로덕션 환경을 나눠야 해요. 보통은 settings폴더 아래 dev.py, staging.py, prod.py와 같이 파일을 나눠서 구동시킬 때 환경에 맞게 os 환경변수 설정을 합니다. "DJANGO_SETTINGS_MODULE=settings.prod"와 같이 말이죠. 아마 관련해서 구글링을 하면 많은 자료가 나올 것으로 예상됩니다. 본 주제를 넘어가는 내용이므로 슬쩍 넘어가겠습니다.

     

    Key Management Service - KMS 란?

    https://docs.aws.amazon.com/ko_kr/kms/latest/developerguide/overview.html

     

    https://docs.aws.amazon.com/ko_kr/kms/latest/developerguide/overview.html

    AWS Key Management Service란 무엇입니까?

    docs.aws.amazon.com

    공식 문서에 자세히 설명되었지만 본 글에서 필요한 내용을 간략하게 요약한다면 다음과 같습니다.

    마스터 키를 발급해주고 데이터를 암호화, 복호화해준다. 

    물론 더 많은 목적과 그에 따른 기능이 있죠. 이 글은 이런 식으로도 KMS를 활용할 수 있다 정도로 생각해주시며 될 거 같아요.

     

    그럼 이제부터 암호화, 복호화하는 방법을 알아보도록 할까요?

     

    데이터 암호화

    참고로 저의 settings.py 구조는 대략 이렇습니다.

    # settings/prod.py 예시
    
    import base64
    import json
    import logging
    import sys
    
    import boto3
    from Crypto.Cipher import AES
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import serialization
    
    from .base import *
    
    
    s3_client = boto3.client('s3')
    kms_client = boto3.client('kms')
    
    
    def get_env():
        env_key = 'path/to/encrypted/key'
        env_data_key = 'path/to/encrypted/data/key'
        bucket = 'service-key-management'
        enc_data = s3_client.get_object(Bucket=bucket, Key=env_key)
        data_key = s3_client.get_object(Bucket=bucket, Key=env_data_key)
        encrypted_data = enc_data.get('Body').read()
        ciphertext_blob = data_key.get('Body').read()
    
        decrypted_key = kms_client.decrypt(
        	CiphertextBlob=ciphertext_blob).get('Plaintext')
        crypter = AES.new(decrypted_key)
        env = crypter.decrypt(
        	base64.b64decode(encrypted_data)).rstrip()
            
        return json.loads(env.decode())
        
    service_env = get_env()
    
    SECRET_KEY = service_env['SECRET_KEY']
    
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': service_env['DATABASES_NAME'],
            'USER': service_env['DATABASES_USER'],
            'PASSWORD': service_env['DATABASES_PASSWORD'],
            'HOST': service_env['DATABASES_HOST'],
            'PORT': service_env['DATABASES_PORT'],
            'OPTIONS': {
                'sql_mode': 'traditional',
                'charset': 'utf8mb4',
            },
        },
    }

    글의 처음 부분에서 설명한 것 처럼 base.py에 기본적인 설정 값들을 넣어놓고 개발&운영 환경에 따라 dev.py, prod.py 등으로 파일을 나눠놨습니다. 

    S3에서 가져오는 데이터가 두 가지가 있는데 하나는 암호화된(복호화를 해야 하는) 데이터와 복호화 시 필요한 데이터 키입니다. 

    익숙하신 분들은 알겠지만 암호화 전(복호화 후)에 데이터는 파이썬의 dict 타입이고 대략 이런식입니다.

    {
        "SECRET_KEY": "djangosecretkeythatshouldnotbeexposed",
        "DATABASES_HOST": "service.mysql.database.host.com",
        "DATABASES_NAME": "helloworld",
        "DATABASES_PORT": "3306",
        "DATABASES_PASSWORD": "db!password!!@",
        "DATABASES_USER": "root"
    }

    dict 타입의 값을 json.dumps를 통해 string 타입으로 변환하여 암호화 했습니다. 복호화 이후에는 json.loads로 다시 dict 형태로 바꾸면 되니까요.

    그럼 이제 위의 json 데이터를 암호화해보도록 하죠.

     

    KMS > 고객 관리형 키

    우선 AWS의 KMS 서비스로 이동 후에 "고객 관리형 키" 메뉴로 이동합니다. 그리고 "키 생성"를 누릅니다.

    키 구성

    키 유형에 대칭과 비대칭이 있습니다. 쉽게 설명하면 대칭키는 암복호화에 같은 키를 사용하고 비대칭은 다른 키를 사용하는 것입니다. 일단 빠르고 실용적으로 하기 위해 자세한 설명은 넘어가기로 할게요. 궁긍하시면 구글링을 통해 암호화 유형의 대칭과 비대칭을 알아보도로 하고! 여기서는 "대칭(Symmetric)"을 선택하고 다음으로 갑니다.

    레이블 추가

    별칭만 잘 알아볼 수 있게 써주시면 됩니다. 필수 값이고 나중에 변경 가능해요.

    키 관리 권한 정의

    키를 관리할 수 있는 권한을 주는 곳이에요. 권한을 받을 수 있는 관리자는 IAM의 사용자(User)나 역할(Role)입니다. 운영 방식에 따라 지정해주시면 되는데 잘 모르겠으면 그냥 본인을 지정하시면 될 거 같아요.

    키 사용 권한 정의

    https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default-allow-users

    만들어 놓은 마스터 키(Customer Master Keys - CMKs)에 접근하고 사용할 수 있는 권한을 부여하는 것입니다. Django 서버가 구동시에 암호를 가져와서 복호화해야 하기 때문에 해당 서버에 할당된 역할(IAM Role)에 부여해야겠죠? 이 부분은 IAM의 사용자와 역할에 관한 개념이 조금은 있어야 바로 이해하실 수 있을 겁니다.

     

    마지막으로 키 정책 검토 및 편집이 있는데 이런 키 정책을 json 형태로 구성할 수 있습니다. 자세한건 공식 문서를 보시죠.

     

    마스터 키가 하나 만들어졌습니다.

    생성된 마스터 키 목록

     

    Python 코드로 보는 암호화/복호화

    아까 settings/prod.py에서 보셨다시피 복호화할 때 필요한 값은 데이터 키와 암호화된 데이터입니다. 그렇다면 우리가 만들어야 할 데이터는 두 가지가 되겠네요.

     

    1. 데이터 키

    2. 암호화된 데이터

     

    실제로 진행해보면 위의 두 데이터는 바이너리형태로 나오게 됩니다. Plain text 형태로 들고 있을 수 없어서 그냥 S3에 보관했습니다. 원래는 이 값들을 어떻게든 AWS의 Parameter store에 보관하려고 했는데 decode를 어떻게 해야할 지 몰라 그냥 넘어갔습니다. 

    파이썬으로 진행하기 위해서 boto3는 기본인거 다들 아시죠?! 그리고 위에 "키 ID"가 필요합니다. 그럼 바로 코드를 보겠습니다.

    import base64
    import json
    
    import boto3
    from Crypto.Cipher import AES
    
    
    # 암호화에 사용될 평문 데이터
    json_key = {
        "SECRET_KEY": "djangosecretkeythatshouldnotbeexposed",
        "DATABASES_HOST": "service.mysql.database.host.com",
        "DATABASES_NAME": "helloworld",
        "DATABASES_PORT": "3306",
        "DATABASES_PASSWORD": "db!password!!@",
        "DATABASES_USER": "root"
    }
    dumped_json_key = json.dumps(json_key)
    
    
    kmsclient = boto3.client('kms')
    
    data_key = kmsclient.generate_data_key(KeyId="<키 ID>", KeySpec='AES_256')
    plaintext_key = data_key.get('Plaintext')
    ciphertext_blob = data_key.get('CiphertextBlob')
    crypto = AES.new(plaintext_key)
    encrypted_data = base64.b64encode(crypto.encrypt(self.pad(dumped_json_key)))
    
    # encrypted_data, ciphertext_blob는 따로 보관해두어야 함
    
    s3client = boto3.client('s3')
    s3client.put_object(
        Body=encrypted_data,
        Bucket="<your_bucket_name>",
        Key="path/to/save/encrypted_data"
    )
    s3client.put_object(
        Body=ciphertext_blob,
        Bucket="<your_bucket_name>",
        Key="path/to/save/ciphertext_blob"
    )

    위의 코드에서 "키 ID"는 AWS 콘솔에서 생성하셨던 바로 그 "키 ID"입니다. 별 무리없이 진행하셨다면 encrypted_data와 ciphertext_blob은 S3같은데 꼭 잘 보관하시길 바랍니다. 당연한 이야기지만 S3 버킷 정책은 비공개를 기본으로 해놔야겠죠? ciphertext_blob을 통해서 encrypted_data데이터를 복호화할 수 있기 때문입니다. 참 쉽죠?

     

    복호화는 이미 위의 settings/prod.py 예제 코드에서 보여드렸습니다. 바로 이 부분입니다.

    decrypted_key = kms_client.decrypt(
    	CiphertextBlob=ciphertext_blob).get('Plaintext')
    crypter = AES.new(decrypted_key)
    env = crypter.decrypt(base64.b64decode(encrypted_data)).rstrip()
    plain_key = json.loads(env.decode())
    
    print(plain_key)
    
    """
    {
        "SECRET_KEY": "djangosecretkeythatshouldnotbeexposed",
        "DATABASES_HOST": "service.mysql.database.host.com",
        "DATABASES_NAME": "helloworld",
        "DATABASES_PORT": "3306",
        "DATABASES_PASSWORD": "db!password!!@",
        "DATABASES_USER": "root"
    }
    """
    

     

     

    끝까지 읽어주셔서 감사합니다. 건강한 피드백은 언제나 환영입니다!

    반응형

    댓글

Designed by Tistory.