cloud bee

Lambda 볼륨 암호화 본문

AWS/code

Lambda 볼륨 암호화

who you 2023. 1. 16. 22:09

목표 설정

오늘은 암호화되지 않은 인스턴스 볼륨을 암호화할 것이다.


암호화되지 않은 인스턴스의 볼륨을 암호화를 하기 위해서 필요한 절차가 있다.

 

절차 소개

1. 암호화가 되지 않은 인스턴스의 볼륨을 중지시킨다.

2. 암호화되지 않은 볼륨의 스냅샷을 생성한다.

3. 스냅샷을 복사 -> 복사한 스냅샷의 암호화를 활성화한다.

4. 암호화된 스냅샷에서 새 EBS볼륨을 생성한다.

5. 기존에 암호화되지 않은 인스턴스의 볼륨을 분리하고 4번에서 생성한 볼륨을 연결한다.

6. 중지한 인스턴스를 시작한다.

7. 기존에 분리하였던 인스턴스 볼륨 (EBS)를 삭제한다.

 


실습 구현

 

이것을 이제 Lambda에 적용시켜 본다.

우선 https://console.aws.amazon.com/lambda에 접속하여 “Volume_Encry”라는 이름의 lambda 함수를 생성시켜 준다. 런타임은 Python 3.9를 사용한다. Lambda를 생성한 후 다음과 같은 코드를 입력한다.

import sys
import boto3
import botocore
import argparse
 
def lambda_handler(event, context):
    parser = argparse.ArgumentParser(description='Encrypts EC2 root volume.')
    parser.add_argument('-i', '--instance_id',
        help='Instance to encrypt volume on.', required=True)
    parser.add_argument('-key', '--customer_master_key',
        help='Customer master key', required=False)
    parser.add_argument('-region', '--region',
        help='AWS region', required=False)
    parser.add_argument('-p', '--profile',
        help='Profile to use', required=False)

    argv = ["-i", event["instance_id"], "-key", event["customer_master"], "-region", event["region"]]
    args = parser.parse_args(argv)
    if args.profile:
        # Create custom session
        print('Using profile {}'.format(args.profile))
        session = boto3.session.Session(profile_name=args.profile)
    else:
        # Use default session
        session = boto3.session.Session(region_name=args.region)
        #aws_session_token=SESSION_TOKEN,
        #sts.get_session_token()

    client = session.client('ec2')
    ec2 = session.resource('ec2')
    waiter_instance_exists = client.get_waiter('instance_exists')
    waiter_instance_stopped = client.get_waiter('instance_stopped')
    waiter_instance_running = client.get_waiter('instance_running')
    waiter_snapshot_complete = client.get_waiter('snapshot_completed')
    waiter_volume_available = client.get_waiter('volume_available')

    customer_master_key = args.customer_master_key
  
    """ Check instance exists """
    instance_id = args.instance_id
    print('---Checking instance ({})'.format(instance_id))
    instance = ec2.Instance(instance_id)
 
    try:
        waiter_instance_exists.wait(
            InstanceIds=[
                instance_id,
            ]
        )
    except botocore.exceptions.WaiterError as e:
        sys.exit('ERROR: {}'.format(e))

    """ Get volume and exit if already encrypted """
    volumes = [v for v in instance.volumes.all()]
    if volumes:
        original_root_volume = volumes[0]
        volume_encrypted = original_root_volume.encrypted
        original_volume_type = original_root_volume.volume_type
        if volume_encrypted:
            sys.exit(
                '**Volume ({}) is already encrypted'
                .format(original_root_volume.id))

    """ Step 1: Prepare instance """
    print('---Preparing instance')
    # Save original mappings to persist to new volume
    original_mappings = {}
    original_mappings['DeleteOnTermination'] = instance.block_device_mappings[0]['Ebs']['DeleteOnTermination']
    
    """
    Instance State
	- 0 : pending
	- 16 : running
	- 32 : shutting-down
	- 48 : terminated
	- 64 : stopping
	- 80 : stopped
    """   
 
    # Exit if instance is pending, shutting-down, or terminated
    instance_exit_states = [0, 32, 48]
    if instance.state['Code'] in instance_exit_states:
        sys.exit(
        'ERROR: Instance is {} please make sure this instance is active.'
        .format(instance.state['Name'])
        )
 
    # Validate successful shutdown if it is running or stopping
    if instance.state['Code'] is 16:
        instance.stop()

    try:
        waiter_instance_stopped.wait(
            InstanceIds=[
                instance_id,
            ]
        )
    except botocore.exceptions.WaiterError as e:
        sys.exit('ERROR: {}'.format(e))

    # Set the max_attempts for this waiter (default 40) waiter_instance_stopped.config.max_attempts = 40

    """ Step 2: Take snapshot of volume """
    print('---Create snapshot of volume ({})'.format(original_root_volume.id))
    snapshot = ec2.create_snapshot(
               VolumeId=original_root_volume.id,
               Description='Snapshot of volume ({})'.format(original_root_volume.id),
               )
 
    try:
        waiter_snapshot_complete.wait(
            SnapshotIds=[
                snapshot.id,
            ]
        )
    except botocore.exceptions.WaiterError as e:
        # Clean up the snapshot to reduce clutter (optional)
        snapshot.delete()
        sys.exit('ERROR: {}'.format(e))

    """ Step 3: Create encrypted volume """
    print('---Create encrypted copy of snapshot')
    if customer_master_key:
        # Use custom key
        snapshot_encrypted_dict = snapshot.copy(
            SourceRegion=session.region_name,
            Description='Encrypted copy of snapshot #{}'
            .format(snapshot.id),
            KmsKeyId=customer_master_key,
            Encrypted=True,
            )
    else:
        # Use default key
        snapshot_encrypted_dict = snapshot.copy(
            SourceRegion=session.region_name,
            Description='Encrypted copy of snapshot ({})'
            .format(snapshot.id),
            Encrypted=True,
        )
 
    snapshot_encrypted = ec2.Snapshot(snapshot_encrypted_dict['SnapshotId'])
 
    try:
        waiter_snapshot_complete.wait(
            SnapshotIds=[
                snapshot_encrypted.id,
            ],
        )
    except botocore.exceptions.WaiterError as e:
        snapshot.delete()
        snapshot_encrypted.delete()
        sys.exit('ERROR: {}'.format(e))

    print('---Create encrypted volume from snapshot')
    volume_encrypted = ec2.create_volume(
        SnapshotId=snapshot_encrypted.id,
        VolumeType=original_volume_type,
        AvailabilityZone=instance.placement['AvailabilityZone']
    )

    """ Step 4: Detach current root volume """
    print('---Deatch volume {}'.format(original_root_volume.id))
    instance.detach_volume(
        VolumeId=original_root_volume.id,
        Device=instance.root_device_name,
    )

    """ Step 5: Attach current root volume """
    print('---Attach volume {}'.format(volume_encrypted.id))
    try:
        waiter_volume_available.wait(
            VolumeIds=[
                volume_encrypted.id,
            ],
        )
    except botocore.exceptions.WaiterError as e:
        snapshot.delete()
        snapshot_encrypted.delete()
        volume_encrypted.delete()
        sys.exit('ERROR: {}'.format(e))
 
    instance.attach_volume(
        VolumeId=volume_encrypted.id,
        Device=instance.root_device_name
    )

    """ Step 6: Restart instance """
    # Modify instance attributes
    instance.modify_attribute(
        BlockDeviceMappings=[
        {
            'DeviceName': instance.root_device_name,
            'Ebs': {
                'DeleteOnTermination':
                original_mappings['DeleteOnTermination'],
            },
        },
        ],
    )
    print('---Restart instance')
    instance.start()
 
    try:
        waiter_instance_running.wait(
            InstanceIds=[
                instance_id,
            ]
        )
    except botocore.exceptions.WaiterError as e:
        sys.exit('ERROR: {}'.format(e))

    """ Step 7: Clean up """
    print('---Clean up resources')
    # Delete snapshots and original volume
    snapshot.delete()
    snapshot_encrypted.delete()
    original_root_volume.delete()

 

 

이 코드에서 argv 부분은 터미널 cli에서 입력해야 하는 그런 옵션인데 lambda에서 사용할 수 있게끔 추가하였다. event ["instance id"], event ["customer_master"], event ["region"]을 사용해서 사용자의 입력을 받게끔 구성하였다.

 

람다 화면이다.

 

이제 구성에 들어가서 제한 시간을 아래 사진과 같이 넉넉하게 설정해 준다.

람다 기본설정하기

 

 

이제 이벤트를 사진과 같이 구성하여 테스트를 진행하면, 암호화되지 않은 볼륨이 암호화가 된다.

람다 이벤트 생성하기

 

하지만 이 상태에서 암호화를 진행하게 되면, 에러가 발생할 것이다.

이유는 IAM 권한을 주지 않았기 때문이다.

 


IAM 권한

Lambda 역할에 정책을 더 추가해 준다.

람다 실행역할 확인

 

이제 나는 EC2의 권한과 KMS의 권한을 추가해 줄 것이다.

JSON 형식으로 EC2와 KMS를 다음과 같이 추가해 준다.

 

json으로 된 코드는 다음과 같이 작성하면 된다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:DetachVolume",
                "ec2:AttachVolume",
                "ec2:CopySnapshot",
                "ec2:DeleteSnapshot",
                "ec2:DescribeInstances",
                "ec2:DescribeSnapshots",
                "ec2:StopInstances",
                "ec2:CreateVolume",
                "ec2:DeleteVolume",
                "ec2:StartInstances",
                "ec2:DescribeVolumes",
                "ec2:CreateSnapshot",
                "ec2:ModifyInstanceAttribute",
                "kms:Decrypt",
                "kms:Encrypt",
                "kms:GenerateDataKey",
                "kms:ReEncryptTo",
                "kms:GenerateDataKeyWithoutPlaintext",
                "kms:DescribeKey",
                "kms:GenerateDataKeyPairWithoutPlaintext",
                "kms:GenerateDataKeyPair",
                "kms:CreateGrant",
                "kms:ReEncryptFrom"
            ],
            "Resource": "*"
        }
    ]
}

 

정책 확인

이제 이런 식으로 정책을 하나 생성해 준다.

 

임의의 인스턴스를 하나 생성해 주자

먼저 https://console.aws.amazon.com/ec2에 접근하여 임의의 인스턴스를 생성해 준다.

인스턴스 생성

인스턴스를 생성해 준다. 혹시 모르니까 인스턴스를 2개 생성한다.

 

생성된 인스턴스 ID 확인

인스턴스 ID를 복사해 준다.

 

 

KMS 키를 생성해 준다.

https://ap-northeast-2.console.aws.amazon.com/kms/home?region=ap-northeast-2#/kms/keys에 들어간다. 

 

https://ap-northeast-2.console.aws.amazon.com/kms/home?region=ap-northeast-2#/kms/keys

 

ap-northeast-2.console.aws.amazon.com

KMS 키 생성하기
별칭 추가하기
다음 버튼
생성하기

 

이제 생성하였다면 Key id를 복사해 준다.

키 ID 확인 후 복사

 

이후 람다에 가서 이벤트 JSON에 다음과 같이 입력한다.

이벤트 JSON 작성

 

볼륨이 정상적으로 암호화가 될 것이다.

'AWS > code' 카테고리의 다른 글

sns 메시지 게시할때마다 cloudwatch로 기록하기  (0) 2023.01.29
[ Terraform ] aws에서 테라폼 사용하기  (0) 2023.01.26
Serverless Api test  (0) 2022.11.09
code pipeline 간단하게 구현  (0) 2022.10.31
Comments