Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

EC2에서 애플리케이션 시크릿 안전하게 관리하기: KMS와 S3 활용 가이드

애플리케이션을 EC2에 배포할 때는 오토스케일링 그룹(Autoscaling Group)을 사용하는 것이 좋습니다. 오토스케일링 그룹을 활용하면 수요에 따라 인스턴스를 자동으로 늘리거나 줄일 수 있고, 장애가 발생한 인스턴스를 복구하는 것도 사람의 개입 없이 처리할 수 있습니다.

다만 이 방식이 제대로 작동하려면, 새로 부팅된 모든 인스턴스가 부팅이 끝나는 즉시 실제 트래픽을 처리할 수 있는 상태여야 합니다. 새 서버에 접속해 몇 가지 설정을 손보거나 Capistrano로 초기 배포를 진행한 뒤에야 서비스를 시작하는 방식에 익숙하다면, 여기에 추가적인 노력이 필요하다는 것을 알게 될 것입니다.

예를 들어 사용자 대면 웹 애플리케이션을 Rails로 구축했다고 가정해 보겠습니다. 각 인스턴스는 부팅이 완료되는 순간 Rails 앱이 로드 밸런서가 전달하는 요청에 응답할 준비가 되어 있어야 합니다. 이를 위해 저는 먼저 Ansible로 프로비저닝한 인스턴스(앱, nginx 등이 설치됨)의 스냅샷을 찍어 커스텀 AMI를 만들었습니다. 그다음 오토스케일링 그룹의 userdata에 스크립트를 설정했는데, 이 스크립트는 Capistrano가 배포 시 수행하는 작업을 대신 수행합니다. 즉, git에서 최신 코드를 가져오고 bundler를 실행하는 식입니다.

그렇다면 앱과 의존성이 모두 준비된 상태에서 남은 과제는 무엇일까요?

애플리케이션 시크릿: 배포자의 고민거리

애플리케이션 시크릿(secret)은 까다로운 문제입니다. git 저장소처럼 노출될 위험이 있는 곳에는 저장하고 싶지 않지만, 동시에 애플리케이션이 실행 중일 때는 반드시 접근할 수 있어야 합니다. 게다가 오토스케일링 환경에서는 필요한 시점에 사람이 직접 시크릿을 배치해 줄 수 있다는 보장이 없습니다.

이 문제에 대한 한 가지 해답은 HashiCorp의 Vault입니다. Vault는 시크릿을 애플리케이션이 필요할 때까지 안전하게 보관하는 문제를 위해 특별히 만들어진 훌륭한 소프트웨어입니다. 하지만 단점도 있습니다. Vault를 직접 프로비저닝하고 관리해야 하며, 또 하나의 운영 대상 서비스가 늘어난다는 점입니다.

또 다른 선택지는 시크릿을 공유 스토리지(당연히 S3)에 저장하고, 해당 버킷과 키에 오직 여러분의 인스턴스만 접근할 수 있도록 제한하는 것입니다. IAM 역할(Role)에 정책(Policy)을 연결해 특정 S3 리소스에 대한 접근 권한을 부여하는 방식으로 구현할 수 있습니다. 다만 이 방법에도 허점이 있습니다. 시크릿을 평문(plaintext)으로 S3에 저장하면, 버킷에 접근 권한이 있는 다른 사용자에게 데이터가 노출되거나 최악의 경우 전 세계에 공개되는 사고가 일어날 수 있습니다.

시크릿을 S3에 저장하기 전에 암호화하고, 애플리케이션에서 필요할 때 불러와서 복호화할 수 있다면 얼마나 좋을까요?

핵심 열쇠: Amazon KMS(Key Management Service)

Amazon의 키 관리 서비스인 KMS(Key Management Service)는 암호화 키와 상호작용할 수 있는 API를 제공합니다. IAM 역할과 Aws::S3::Encryption 모듈과 함께 사용하면, 단 몇 줄의 코드로 시크릿을 S3에는 암호화된 상태로 유지하면서 애플리케이션에는 복호화하여 로드할 수 있습니다.

본격적으로 들어가기 전에, KMS와 S3를 조합해 시크릿을 저장하는 방법에 대한 훌륭한 글을 쓴 Don Mills에게 감사를 전하고 싶습니다. 저는 그의 접근 방식을 일부 변형했습니다. IAM 역할에 의존하고, KMS 키 ID를 시크릿과 함께 S3에 저장하지 않고 별도로 관리하도록 변경한 것입니다.

KMS는 마스터 암호화 키를 생성하고 이에 대한 접근을 제공합니다. 이 마스터 키로 데이터를 암호화하고 복호화할 수 있습니다. KMS에 암호화를 요청하면 마스터 키를 기반으로 생성된 임시 키를 받게 되며, 이 임시 키가 실제 암복호화에 사용됩니다.

키를 생성하려면 IAM 콘솔로 이동해 'Encryption Keys' 링크를 선택합니다. 키를 생성할 때 이 키를 사용할 수 있는 IAM 사용자나 역할을 지정하라는 메시지가 표시됩니다. 여기서 오토스케일링 그룹에 포함될 EC2 인스턴스에 할당될 역할을 선택하세요. 그리고 키의 ARN을 기록해 두세요. 나중에 이 ARN을 사용하게 됩니다.

KMS와 IAM의 황금 비율: 루(Roux) 만들기

키 생성이 완료되면 IAM 콘솔에서 앞서 선택한 IAM 역할을 편집합니다. 아래와 같은 정책을 연결하여 시크릿이 저장될 버킷에 대한 접근 권한을 부여합니다:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1476277816000",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:HeadObject"
            ],
            "Resource": [
                "arn:aws:s3:::yourbucket/secrets.yml"
            ]
        }
    ]
}

키 구성과 정책 연결이 끝나면, 이제 Aws::S3::Encryption::Client 인스턴스를 통해 KMS와 S3에 접근할 수 있습니다. 아래는 시크릿 파일을 가져와 그 내용을 환경 변수로 로드하는 샘플 코드입니다:

begin
  es3 = Aws::S3::Encryption::Client.new(kms_key_id: ENV['KMS_KEY_ID'])
  YAML.load(es3.get_object(bucket: "yourbucket", key: "secrets.yml").body.read).each do |k, v|
    ENV[k] ||= v # 로컬 ENV 설정은 덮어쓰지 않음
  end
rescue ArgumentError
  # ENV에 KMS_KEY_ID가 없을 때 발생하므로 별도 처리 불필요
rescue Aws::S3::Errors::NoSuchKey
  # 시크릿 파일이 없을 때 발생하므로 별도 처리 불필요
end

먼저 KMS 키의 ID를 넘겨 새 객체를 인스턴스화합니다. 키의 ARN(IAM 콘솔에서 키를 생성할 때 표시됨)은 KMS_KEY_ID 환경 변수에 저장되어 있습니다. 생성자에 키 ID를 전달하면 임시 복호화 키를 가져오는 작업을 알아서 처리해 줍니다. 만약 S3와 통신할 때 KMS와 통신할 때와 다른 자격 증명을 사용하고 싶다면, Aws::S3::Client 인스턴스를 옵션으로 지정할 수도 있습니다. 하지만 앞서 설명한 대로 IAM 역할을 구성했다면 굳이 그럴 필요가 없습니다. Aws::S3::Encryption::Client가 IAM 역할이 제공하는 자격 증명으로 새 Aws::S3::Client 인스턴스를 자동으로 생성해 주기 때문입니다.

암호화된 S3 클라이언트가 준비되면 #get_object를 사용해 S3에서 데이터를 가져오고, KMS가 제공한 키로 복호화합니다. 데이터를 확보한 후에는 원하는 대로 활용하면 됩니다. 우리의 데이터는 YAML 형식이므로 이를 로드한 뒤 키/값 쌍을 ENV에 넣어 애플리케이션 코드에서 사용할 수 있도록 했습니다.

이 코드를 Rails 애플리케이션의 이니셜라이저(initializer) 파일에 넣으면 준비가 끝납니다. 물론 시크릿을 S3에 저장해 두어야겠죠. :) 적절한 IAM 역할이 적용된 상태로 실행 중인 인스턴스에서 IRB 콘솔을 열었다면, 아래와 같은 코드로 시크릿을 저장할 수 있습니다:

# /path/to/secrets.yml의 데이터를 암호화하여 S3에 저장
Aws::S3::Encryption::Client.new(kms_key_id: ENV['KMS_KEY_ID']).
  put_object(bucket: "yourbucket", key: "secrets.yml", body: File.read("/path/to/secrets.yml"))

즉시 서비스 시작 가능

이제 오토스케일링 그룹에 새로 추가되는 어떤 인스턴스든 시크릿을 항상 사용할 수 있으면서, 시크릿은 계속 암호화된 상태로 안전하게 보관됩니다. 모두가 윈윈하는 결과입니다! :)