Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

จัดการ Application Secrets บน AWS EC2 อย่างปลอดภัย

Deploy แอปไป EC2 ควรใช้ autoscaling groups Autoscaling groups ให้แอป scale up/down ตาม demand หรือ recover จาก failed instances โดยไม่ต้อง manual intervention ทำให้ทำงาน ต้อง ensure ทุก instance พร้อม serve live traffic หลัง boot เสร็จ ต้องการ effort มากกว่าแค่ deploy ไป server หากเคยทำ changes บน server ใหม่ หรือ initial deploy ผ่าน capistrano ก่อนพร้อม

ตัวอย่าง: เรามี user-facing web app built with Rails ตอน boot เสร็จ แต่ละ instance ต้องมี Rails app พร้อมรอ requests จาก load balancer ทำให้เกิดขึ้น ผมสร้าง custom AMI โดย snapshot instance provisioned via Ansible พร้อม app, nginx ฯลฯ ตั้งค่า autoscaling group ด้วย userdata เรียกสคริปต์ copy מה capistrano ทำใน deployment — pull latest code from git, run bundler ฯลฯ ด้วย app และ dependencies พร้อม เหลืออะไร?

Application Secrets: ความทุกข์ของ Deployer

Application secrets ท้าทาย: อยากเก็บให้ไม่วางไว้ที่ expose ได้ (เช่น git repo) แต่ต้อง available ให้ app ตอนรัน และเนื่องจาก autoscaling ไม่พึ่งพา human วาง secrets ให้ตอนต้องการ

คำตอบหนึ่ง: Vault by Hashicorp ซอฟต์แวร์เยี่ยมแก้ปัญหาเก็บ secrets จนกว่า app ต้องการ แต่ downside: ต้อง provision และ manage Vault — อีก service ที่ต้อง run

อีกทาง: เก็บ secrets ใน shared storage (S3 當然) และ ensure เฉพาะ instances เข้าถึง bucket/key ได้ ทำได้ด้วย IAM roles ที่ attach policies granting access ไป restricted S3 resources แต่ยังเปิดโอกาส expose หากเก็บ secrets plaintext บน S3 อาจ accidentally ทำให้ข้อมูลเข้าถึงได้โดยคนอื่นที่มี access bucket หรือ whole world

ไม่น่าจะดีถ้า encrypt secrets ก่อน save บน S3 แล้ว load และ decrypt ใน app ตอนต้องการ?

Secret Ingredient: Amazon Key Management Service (KMS)

AWS KMS ให้ API 互作用กับ encryption keys รวมกับ IAM roles และ Aws::S3::Encryption module ใช้เพียงไม่กี่บรรทัด load secrets เข้า app ขณะเก็บ encrypted บน S3

ก่อนลึก ขอขอบคุณ Don Mills เขียนบทความเกี่ยว KMS + S3 สำหรับเก็บ secrets ผมปรับแนวทางนิดหน่อย: ใช้ IAM roles และเก็บ KMS key id แยก แทนที่จะเก็บ key info พร้อม secrets บน S3

KMS สร้างและให้ access master encryption key ใช้ encrypt/decrypt data ขอ encrypt KMS ให้ temporary key based on master key ใช้ temporary key นั้น encrypt/decrypt

สร้าง key: ไป IAM console เลือก Encryption Keys link สร้าง key จะถามระบุ IAM users/roles ที่ใช้ key นี้ได้ เลือก role ที่ assign ให้ EC2 instances ใน autoscaling group จด ARN ของ key — จะใช้ภายหลัง

ทำ Roux: ส่วนผสม KMS และ IAM เท่ากัน

สร้าง key แล้ว ใช้ IAM console edit IAM role ที่เลือก Grant access bucket ที่เก็บ secret โดย attach policy เหมือนนี้:

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

มี key configured และ policy attached ไป role ตอนนี้ interact กับ KMS และ S3 ผ่าน Aws::S3::Encryption::Client instance โค้ดตัวอย่าง retrieve secrets file และ load ใส่ environment variables:

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 # Don't override local ENV settings
  end
rescue ArgumentError
  # Raised when no KMS_KEY_ID was found in ENV, so there's nothing to do
rescue Aws::S3::Errors::NoSuchKey
  # No secrets file was found, so there's nothing to do
end

สร้าง object ใหม่พร้อม KMS key ID ARN ของ key (แสดงใน IAM console ตอนสร้าง) เก็บใน environment variable KMS_KEY_ID ส่ง key ID ให้ constructor จะ handle fetching temporary decryption keys ให้ ระบุ Aws::S3::Client instance เป็น option ได้ หากอยาก credentials แยกสำหรับ S3 และ KMS หาก setup IAM role ไว้ ไม่ต้อง เพราะ Aws::S3::Encryption::Client สร้าง Aws::S3::Client ใหม่ด้วย credentials จาก IAM role ให้อัตโนมัติ

พร้อม encrypted S3 client ใช้ #get_object fetch data จาก S3 และ decrypt ด้วย key จาก KMS ได้ data ทำอะไรก็ได้ data ของเราเป็น YAML load และ stuff key/value pairs ใส่ ENV ให้ application code ใช้

วางโค้ดใน initializer file ใน Rails app พร้อมใช้งาน ก็ต่อเมื่อมี secrets stored บน S3 นั่นแหละ :) สมมติมี IRB console บน instance รันพร้อม IAM role ถูกต้อง ทำแบบนี้เก็บ secrets:

# Encrypt data from /path/to/secrets.yml และ store บน 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")
)

เสิร์ฟทันที

ตอนนี้ secrets พร้อมใช้สำหรับ instance ใหม่ๆ ที่เพิ่มใน autoscaling group ขณะ secrets ยัง encrypted ทุกฝ่ายเป็นผู้ชนะ! :)