2018년 3월 28일 수요일

AWS Cross Account IAM 설정

EC2에서 AWS다른 계정으로 권한을 할당 받아 실행할때는 아래와 같이 수행하면 된다.

1. AWS다른계정에 EC2가 있는 계정의 아이디를 이용하여 Policy를 만든다.
2. AWS다른계정에 Role을 만들어 Policy를 할당한다.(role의 ARN 저장)
3. IAM을 부여할 EC2가 있는 계정에 Role을 만든다.
role에 소스를 inline policy로 등록한다.
resource에는 다른계정에서 만든 role arn으로 수정한다.

{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::PRODUCTION-ACCOUNT-ID:role/UpdateApp" } }
4. ec2에 해당 role을 수행할때에는 sts를 받아 임시키를 받아 등록하여 수행하면 끝

sts 받는 방법
aws sts assume-role --role-arn {다른계정에 만든 role의 arn} --role-session-name {session이름(아무값이나 넣어도 되는듯)}

aws sts assume-role --role-arn "arn:aws:iam::999999999999:role/UpdateApp" --role-session-name "David-ProdUpdate"

하면 아래와 같이 출력되고
{ "Credentials": { "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "SessionToken": "AQoDYXdzEGcaEXAMPLE2gsYULo+Im5ZEXAMPLEeYjs1M2FUIgIJx9tQqNMBEXAMPLE CvSRyh0FW7jEXAMPLEW+vE/7s1HRpXviG7b+qYf4nD00EXAMPLEmj4wxS04L/uZEXAMPLECihzFB5lTYLto9dyBgSDy EXAMPLE9/g7QRUhZp4bqbEXAMPLENwGPyOj59pFA4lNKCIkVgkREXAMPLEjlzxQ7y52gekeVEXAMPLEDiB9ST3Uuysg sKdEXAMPLE1TVastU1A0SKFEXAMPLEiywCC/Cs8EXAMPLEpZgOs+6hz4AP4KEXAMPLERbASP+4eZScEXAMPLEsnf87e NhyDHq6ikBQ==", "Expiration": "2014-12-11T23:08:07Z", "AccessKeyId": "AKIAIOSFODNN7EXAMPLE" } }
아래와 같이 등록하면 해당 권한을 사용할수 있다.(window일 경우 export대신 set을 이용)
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY export AWS_SESSION_TOKEN=AQoDYXdzEGcaEXAMPLE2gsYULo+Im5ZEXAMPLEeYjs1M2FUIgIJx9tQqNMBEXAMPLECvS Ryh0FW7jEXAMPLEW+vE/7s1HRpXviG7b+qYf4nD00EXAMPLEmj4wxS04L/uZEXAMPLECihzFB5lTYLto9dyBgSDyEXA MPLEKEY9/g7QRUhZp4bqbEXAMPLENwGPyOj59pFA4lNKCIkVgkREXAMPLEjlzxQ7y52gekeVEXAMPLEDiB9ST3UusKd EXAMPLE1TVastU1A0SKFEXAMPLEiywCC/Cs8EXAMPLEpZgOs+6hz4AP4KEXAMPLERbASP+4eZScEXAMPLENhykxiHen DHq6ikBQ==

참고 사이트
https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html

2018년 3월 26일 월요일

AWS VPC에 있는 서버에 접속을 하려면?


외부에서 AWS에 Virtual Private Network(VPC)에 있는 서버에 접속을 하려면 어떻게 해야 할까?

AWS에서는 Public영역에 Bastion Server을 이용하여 점프 할 수 있도록 구성하는것을 권장한다.

아래는 AWS에서 제공하는 Bastion의 Architecture 이다.

Bastion 서버에서 /var/log/bastion/bastion.log에 로그 파일이 있는데 접속된 명령들이 로그로 저장이 된다.이 로그 파일에는 날짜, SSH 클라이언트 연결 IP 주소, 사용자 이름, 작업 디렉터리 및 실행된 명령 등의 정보등이 기록된다.

이 로그에는 사용자 로그인 시 실행된 모든 명령에 대한 기록이 남아 있다. 

아래 그림에는 사용자가 특정 IP 주소를 통해 로그인했으며, 표준 사용자로 암호 파일을 제거하려고 시도한 후 루트 액세스로 에스컬레이션한 다음 접속 로그 제거를 시도했다고 기록된 로그를 보여준다.



bastion.log 파일에는 변경 불가능한 비트 세트가 있어 쉽게 제거하거나 조작할 수 없다고 한다. 악의적 사용자가 bastion.log 파일을 찾아낸 후 루트 권한을 얻어 보호를 해제하고 로그 파일을 삭제하더라도 로그의 사본이 포함된 섀도우 파일이 있다. 
섀도우 파일의 위치는 /var/log/bastion/.bastion.log이다.
이 섀도우 파일은 단순한 사본으로, 공격자가 찾아내 삭제할 수 있어 Cloud Watch Logs서비스를 이용하여 bastion.log파일을 저장하는걸 권장한다.

설치는 아마존에서 제공하는 quick start를 이용하여 배포할수 있다.

AWS설명서를 참고함.

또한 Amazon Linux에 github에 있는 shell을 생성하여 실행시키면 bastion.log가 생성된다.

bootstrap-bastion.sh

#!/bin/bash -x

yum -y update --security

##########################
## ENABLE SSH RECORDING ##
##########################

# Create a new folder for the log files
mkdir /var/log/bastion

# Allow ec2-user only to access this folder and its content
chown ec2-user:ec2-user /var/log/bastion
chmod -R 770 /var/log/bastion
setfacl -Rdm other:0 /var/log/bastion

# Make OpenSSH execute a custom script on logins
echo -e \"\\nForceCommand /usr/bin/bastion/shell\" >> /etc/ssh/sshd_config

# Block some SSH features that bastion host users could use to circumvent the solution
awk '!/AllowTcpForwarding/' /etc/ssh/sshd_config > temp && mv temp /etc/ssh/sshd_config
awk '!/X11Forwarding/' /etc/ssh/sshd_config > temp && mv temp /etc/ssh/sshd_config
echo \"AllowTcpForwarding no\" >> /etc/ssh/sshd_config
echo \"X11Forwarding no\" >> /etc/ssh/sshd_config

mkdir /usr/bin/bastion

cat > /usr/bin/bastion/shell << 'EOF'
# Check that the SSH client did not supply a command
if [[ -z $SSH_ORIGINAL_COMMAND ]]; then
  # The format of log files is /var/log/bastion/YYYY-MM-DD_HH-MM-SS_user
  LOG_FILE=\"`date --date=\"today\" \"+%Y-%m-%d_%H-%M-%S\"`_`whoami`\"
  LOG_DIR=\"/var/log/bastion/\"
  # Print a welcome message
  echo \"\"
  echo \"NOTE: This SSH session will be recorded\"
  echo \"AUDIT KEY: $LOG_FILE\"
  echo \"\"
  # I suffix the log file name with a random string. I explain why later on.
  SUFFIX=`mktemp -u _XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`
  # Wrap an interactive shell into \"script\" to record the SSH session
  script -qf --timing=$LOG_DIR$LOG_FILE$SUFFIX.time $LOG_DIR$LOG_FILE$SUFFIX.data --command=/bin/bash
else
  # The \"script\" program could be circumvented with some commands (e.g. bash, nc).
  # Therefore, I intentionally prevent users from supplying commands.
  echo \"This bastion supports interactive sessions only. Do not supply a command\"
  exit 1
fi
EOF
# Make the custom script executable
chmod a+x /usr/bin/bastion/shell
# Bastion host users could overwrite and tamper with an existing log file using \"script\" if
# they knew the exact file name. I take several measures to obfuscate the file name:
# 1. Add a random suffix to the log file name.
# 2. Prevent bastion host users from listing the folder containing log files. This is done
#    by changing the group owner of \"script\" and setting GID.
chown root:ec2-user /usr/bin/script
chmod g+s /usr/bin/script
# 3. Prevent bastion host users from viewing processes owned by other users, because the log
#    file name is one of the \"script\" execution parameters.
mount -o remount,rw,hidepid=2 /proc
awk '!/proc/' /etc/fstab > temp && mv temp /etc/fstab
echo \"proc /proc proc defaults,hidepid=2 0 0\" >> /etc/fstab
# Restart the SSH service to apply /etc/ssh/sshd_config modifications.
service sshd restart
############################
## EXPORT LOG FILES TO S3 ##
############################
cat > /usr/bin/bastion/sync_s3 << 'EOF'
# Copy log files to S3 with server-side encryption enabled.
# Then, if successful, delete log files that are older than a day.
LOG_DIR=\"/var/log/bastion/\"
aws s3 cp $LOG_DIR s3://${Bucket}/logs/ --sse --region ${AWS::Region} --recursive && find $LOG_DIR* -mtime +1 -exec rm {} \\;
EOF
chmod 700 /usr/bin/bastion/sync_s3
#######################################
## SYNCHRONIZE USERS AND PUBLIC KEYS ##
#######################################
# Bastion host users should log in to the bastion host with their personal SSH key pair.
# The public keys are stored on S3 with the following naming convention: \"username.pub\".
# This script retrieves the public keys, creates or deletes local user accounts as needed,
# and copies the public key to /home/username/.ssh/authorized_keys
cat > /usr/bin/bastion/sync_users << 'EOF'
# The file will log user changes
LOG_FILE=\"/var/log/bastion/users_changelog.txt\"
# The function returns the user name from the public key file name.
# Example: public-keys/sshuser.pub => sshuser
get_user_name () {
  echo \"$1\" | sed -e 's/.*\\///g' | sed -e 's/\\.pub//g'
}
# For each public key available in the S3 bucket
aws s3api list-objects --bucket ${Bucket} --prefix public-keys/ --region ${AWS::Region} --output text --query 'Contents[?Size>`0`].Key' | sed -e 'y/\\t/\\n/' > ~/keys_retrieved_from_s3
while read line; do
  USER_NAME=\"`get_user_name \"$line\"`\"
  # Make sure the user name is alphanumeric
  if [[ \"$USER_NAME\" =~ ^[a-z][-a-z0-9]*$ ]]; then
    # Create a user account if it does not already exist
    cut -d: -f1 /etc/passwd | grep -qx $USER_NAME
    if [ $? -eq 1 ]; then
      /usr/sbin/adduser $USER_NAME && \\
      mkdir -m 700 /home/$USER_NAME/.ssh && \\
      chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh && \\
      echo \"$line\" >> ~/keys_installed && \\
      echo \"`date --date=\"today\" \"+%Y-%m-%d %H-%M-%S\"`: Creating user account for $USER_NAME ($line)\" >> $LOG_FILE
    fi
    # Copy the public key from S3, if an user account was created from this key
    if [ -f ~/keys_installed ]; then
      grep -qx \"$line\" ~/keys_installed
      if [ $? -eq 0 ]; then
        aws s3 cp s3://${Bucket}/$line /home/$USER_NAME/.ssh/authorized_keys --region ${AWS::Region}
        chmod 600 /home/$USER_NAME/.ssh/authorized_keys
        chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh/authorized_keys
      fi
    fi
  fi
done < ~/keys_retrieved_from_s3
# Remove user accounts whose public key was deleted from S3
if [ -f ~/keys_installed ]; then
  sort -uo ~/keys_installed ~/keys_installed
  sort -uo ~/keys_retrieved_from_s3 ~/keys_retrieved_from_s3
  comm -13 ~/keys_retrieved_from_s3 ~/keys_installed | sed \"s/\\t//g\" > ~/keys_to_remove
  while read line; do
    USER_NAME=\"`get_user_name \"$line\"`\"
    echo \"`date --date=\"today\" \"+%Y-%m-%d %H-%M-%S\"`: Removing user account for $USER_NAME ($line)\" >> $LOG_FILE
    /usr/sbin/userdel -r -f $USER_NAME
  done < ~/keys_to_remove
  comm -3 ~/keys_installed ~/keys_to_remove | sed \"s/\\t//g\" > ~/tmp && mv ~/tmp ~/keys_installed
fi
EOF
chmod 700 /usr/bin/bastion/sync_users
###########################################
## SCHEDULE SCRIPTS AND SECURITY UPDATES ##
###########################################
cat > ~/mycron << EOF
*/5 * * * * /usr/bin/bastion/sync_s3
*/5 * * * * /usr/bin/bastion/sync_users
0 0 * * * yum -y update --security
EOF
crontab ~/mycron
rm ~/mycron
/opt/aws/bin/cfn-signal -e 0 --stack ${AWS::StackName} --resource BastionHostInstance --region ${AWS::Region}

위 소스는 아래 github를 참고함.











2018년 3월 22일 목요일

lambda를 이용하여 모든 EC2의 volume을 주기적으로 snapshot으로 백업하기


각 계정에 EC2의 EBS를 백업하려 할때는 두가지방법이 있다. AMI를 이용하거나 EBS Volume을 snapshot으로 주기적으로 생성하는 방법 오늘은 그중에서 EBS volume을 lambda를 이용하여 백업하는 방법을 정리한다.

절차는 아래와 같다.
1. lambda를 생성하기 위한 role을 생성
2. lambda 생성 소스 추가 및 삭제 정책일 등록
3. cloud watch event추가 및 event 설정
4. 테스트
5. 완료


lambda source
#===================
import boto3
import datetime

import os

ec2 = boto3.resource('ec2')

def lambda_handler(event, context):
    print("\n\nAWS snapshot backups starting at %s" % datetime.datetime.now())
    instances = ec2.instances.filter(
        Filters=[{'Name': 'instance-state-name', 'Values': ['running']},{'Name': 'tag:BackupYN', 'Values': ['Y']}])
 
    for instance in instances:
        instance_name = filter(lambda tag: tag['Key'] == 'Name', instance.tags)[0]['Value']
        print("instance_name [%s]" % instance_name)
     
        for volume in ec2.volumes.filter(Filters=[{'Name': 'attachment.instance-id', 'Values': [instance.id]}]):
            description = 'scheduled-%s.%s-%s' % (instance_name, volume.volume_id,
                datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
         
            response = volume.create_snapshot(Description=description, VolumeId=volume.volume_id) 
         
            if response:
                print("Snapshot created with description [%s]" % description)
                ec2.create_tags( Resources=[response.snapshot_id],
                Tags=[
                    {'Key': 'Name', 'Value': description}
                ])
                #snapshot = ec2.Snapshot(response.snapshot_id)
                #snapshot.create_tags(Tags=[{'Key': 'Name','Value': description}])

        for snapshot in volume.snapshots.all():
            retention_days = int(os.environ['retention_days'])
            if snapshot.description.startswith('scheduled-') and ( datetime.datetime.now().replace(tzinfo=None) - snapshot.start_time.replace(tzinfo=None) ) > datetime.timedelta(days=retention_days):
                print("\t\tDeleting snapshot [%s - %s]" % ( snapshot.snapshot_id, snapshot.description ))
                snapshot.delete()

    print("\n\nAWS snapshot backups completed at %s" % datetime.datetime.now())
    return True
#==========

lambda를 이용하여 모든 EC2를 AMI Backup 하기

EC2의 AMI를 Lambda를 이용하여 Backup 하는 소스임.



import boto3
import datetime
import time
import os

ec2 = boto3.resource('ec2', region_name='ap-northeast-2')
ec = boto3.client('ec2')

def create_backup_snapshot():
    print("\n\nAWS snapshot backups starting at %s" % datetime.datetime.now())
    instances = ec2.instances.filter(
        Filters=[{'Name': 'instance-state-name', 'Values': ['running']},{'Name': 'tag:Name', 'Values': ['*DSTG*']},{'Name': 'tag:BackupYN', 'Values': ['Y']}])
     
    for instance in instances:
        instance_name = filter(lambda tag: tag['Key'] == 'Name', instance.tags)[0]['Value']
        print("instance_name [%s]" % instance_name)
     
        for volume in ec2.volumes.filter(Filters=[{'Name': 'attachment.instance-id', 'Values': [instance.id]}]):
            description = 'scheduled-%s.%s-%s' % (instance_name, volume.volume_id,
                datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
         
            response = volume.create_snapshot(Description=description, VolumeId=volume.volume_id) 
         
            if response:
                print("Snapshot created with description [%s]" % description)
                ec2.create_tags( Resources=[response.snapshot_id],
                Tags=[
                    {'Key': 'Name', 'Value': description}
                ])
                #snapshot = ec2.Snapshot(response.snapshot_id)
                #snapshot.create_tags(Tags=[{'Key': 'Name','Value': description}])

def backup_snapshot():
    print("\n\nAWS snapshot backups starting at %s" % datetime.datetime.now())
    instances = ec2.instances.filter(
        Filters=[{'Name': 'instance-state-name', 'Values': ['stopped']},{'Name': 'tag:Name', 'Values': ['*DSTG*']}])

    for instance in instances:
        instance_name = filter(lambda tag: tag['Key'] == 'Name', instance.tags)[0]['Value']
        print("instance_name [%s]" % instance_name)
        create_time = datetime.datetime.now()
        create_fmt = create_time.strftime('%Y-%m-%d')
        AMIid = ec.create_image(InstanceId=instance.id, Name="Lambda-" + instance_name +"-"+instance.id + " from " + create_fmt, Description="Lambda created AMI of instance " + instance.id + " from " + create_fmt, NoReboot=True, DryRun=False)

     

    print("\n\nAWS snapshot backups completed at %s" % datetime.datetime.now())


def lambda_handler(event, context):
    backup_snapshot()
    #create_backup_snapshot()
    return 'successful'

2018년 3월 21일 수요일

IAM 사용자가 MFA 디바이스를 스스로 관리하도록 허용

IAM에서 User를 생성하고 Group에 Policy를 부여하여 관리한다.

IAM User에게 MFA를 사용하게 하려면 IAM관리할수 있는 Policy가 없기 때문에 관리자가 등록해줄수 밖에 없다.

이럴때는 아래와 같은 Policy를 생성하여 해당 Group에 부여하면 해당되는 사용자들이 스스로 MFA를 설정할 수 있다.

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iam:CreateVirtualMFADevice", "iam:EnableMFADevice", "iam:ResyncMFADevice", "iam:DeleteVirtualMFADevice" ], "Resource": [ "arn:aws:iam::*:mfa/${aws:username}", "arn:aws:iam::*:user/${aws:username}" ] }, { "Sid": "AllowUsersToDeactivateTheirOwnVirtualMFADevice", "Effect": "Allow", "Action": [ "iam:DeactivateMFADevice" ], "Resource": [ "arn:aws:iam::*:mfa/${aws:username}", "arn:aws:iam::*:user/${aws:username}" ], "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } } }, { "Effect": "Allow", "Action": [ "iam:ListMFADevices", "iam:ListVirtualMFADevices", "iam:ListUsers" ], "Resource": "*" } ] }

참고 : aws document

2018년 3월 15일 목요일

Route53 private 다른 계정의 VPC연결

AWS의 Route53은 Public 서비스와 VPC에서만 서비스 할수 있는 Private서비스를 할수 있다.

Route53 Private은 VPC를 연결해야 한다.

생성방법은 친절하게도 아마존 개발자 가이드에 자세히 나와있다.

https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zone-private-creating.html 참조

한 회사에서 여러개의 계정을 사용하는 경우 VPC Peering을 통해서 네트워크 통신을 하는데 Route53 Private을 계정마다 둘수는 없을 것이다. 이럴 때는 3가지의 연결방법이 있다.

연결방법은 SDK, CLI, API를 이용하는 방법이며
https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zone-private-associate-vpcs-different-accounts.html참조

다음은 CLI를 이용하여 연결하는 방법이다.

1. aws cli 버전을 업데이트 한다. (cli 설치 관련 여기를 참고)
 참고 : aws cli를 사용하려면 Route 53 액세스 권한이있는 IAM 사용자여야 한다.

aws –-version
pip install --upgrade awscli
2. AWS CLI를 사용하여 사용 가능한 영역을 나열하여 호스트 된 영역 ID를 검색한다.
참고 : 이 작업은 개인 호스팅 영역을 소유 한 계정에서 수행해야한다.
aws route53 list-hosted-zones
3. 이전 단계의 hosted-zone-id 및 연결할 VPC ID를 사용하여 권한을 만든다.


aws route53 create-vpc-association-authorization --hosted-zone-id <hosted-zone-id> --vpc VPCRegion=<region>,VPCId=<vpc-id>
4.  VPC를 소유 한 계정에서 hosted-zone 과 VPC를 연결한다.

aws route53 associate-vpc-with-hosted-zone --hosted-zone-id <hosted-zone-id> --vpc VPCRegion=<region>,VPCId=<vpc-id>
5. Route53을 소유 한 계정에서 권한 요청을 삭제하는 것이 가장 좋다고 한다.
aws route53 delete-vpc-association-authorization --hosted-zone-id <hosted-zone-id>  --vpc VPCRegion=<region>,VPCId=<vpc-id>

이제 설정이 완료되었다.

route53 hosted-zone에 있는 레코드 쿼리가 되는지 새로 추가한 VPC에 있는 서버에서 쿼리를 날려본다.

참고사이트 : https://aws.amazon.com/ko/premiumsupport/knowledge-center/private-hosted-zone-different-account/

API도 Access key와 Security Key를 이용하여 위와 같은 순서로 해당되는  API를 호출하면 된다.





2018년 3월 12일 월요일

EC2 장애시 백업한 root snapshot을 이용하여 EC2 복구 방법

EC2가 갑자기 장애가 발생했다.
복구방법은?

1. 주기적으로 백업받은 AMI를 이용하여 복구한다.
2. 주기적으로 백업받은 Snapshot을 이용하여 복구한다.

1번은 그냥 백업받은 AMI를 새로 lunch하면 된다.

2번의 경우에 snapshot을  EBS Volume으로 생성하고 EC2에 attache한다.
이때 Root Volume일 경우  Attach하고 EC2를 시작했을경우 다음과 같은 에러가 발생할것이다.


<그림1>

Root volume일 경우에는 그냥 EC2에 붙이면 동작을 안한다.
snapshot으로 생성한 Root Volume 을 attache할때에는 아래 그림2 처럼


<그림2>

Device 항목을 /dev/xvda로 설정 해야 EC2가 정상적으로 실행된다.(amazon linux기준)

참고>os가 Redhat일 경우에는 /dev/sda1으로 설정해야 함.

2018년 3월 11일 일요일

Squid로 NAT 인스턴스에 DNS 필터링을 추가하는 방법

고객이 아웃바운드 통신을 제어하고 싶다는 요구사항이 있다. 이럴때의 해결 방법에대하여 검색하니 Squid라는 proxy를 이용하여 NAT Instance처럼 사용하는 방법이 있었다.

영문이라 설정방법을 여기에 정리한다.

Amazon VPC (Virtual Private Cloud)를 사용하면 사용자가 정의한 가상 사설 망에서 AWS 리소스를 시작할 수 있다. Amazon VPC에서 NAT 인스턴스와 NAT 게이트웨이를 이용하여 아웃바운드 트래픽을 허용한다.

보안 및 컴플라이언스를 위해 이러한 인스턴스에서 시작한 요청을 필터링해야 할 수 있다. iptables 규칙을 사용하면 사전 정의 된 대상 포트 또는 IP 주소를 기반으로 NAT 인스턴스로 아웃 바운드 트래픽을 제한 할 수 있다. 그러나 iptables 규칙을 사용하여 쉽게 달성 할 수없는 AWS 엔드 포인트에만 요청을 허용하는 것과 같이 더 복잡한 정책을 시행해야 할 수도 있다.

오픈 소스 프록시 인 Squid가 HTTP 및 HTTPS 아웃 바운드 트래픽을 지정된 인터넷 도메인 집합으로 제한하는 동시에 개인 서브넷의 인스턴스를 완전히 투명하게 처리 할 수있는 방법에 대해 설명하고 제공한다. 먼저이 접근법에 필요한 인프라 자원을 만드는 방법을 간략하게 설명한다. 그런 다음 Squid Proxy를 설치, 구성 및 테스트하는 단계별 지침을 안내한다.

가능한 대안 솔루션은 Amazon VPC에 프록시 (포워드 프록시라고도 함)를 배포하는 것일 수 있다. 그러나 중요한 단점은 프록시가 개인 서브넷의 모든 인스턴스에 대해 명시 적으로 구성되어야 한다. 이로 인해 응용 프로그램이 프록시 사용을 지원하지 않는 경우 해결하기가 어렵지 않더라도 문제를 해결하기 어려울 수있는 연결 문제가 발생할 수 있다.

설정 방법

다음 단계는 필수 자원을 수동으로 작성하고 구성하는 단계다. 또는 AWS CloudFormation을 사용하여이 절차를 자동화 할 수 있다. 스택 생성을 클릭하여 CloudFormation 콘솔을 열고 내가 개발 한 템플릿에서 AWS CloudFormation 스택을 만든다. 화면의 지침을 따르고 스택 작성이 완료되면이 블로그 게시물의 뒷부분에있는 "배치 테스트"섹션으로 직접 이동 (최대 20 분이 소요될 수 있음).

참고 : VPC와 동일한 지역의 S3 버킷에 요청을 허용해야하는 경우 Amazon S3에 VPC 종점을 대신 사용할 수 있다 (VPC 종점 참조). 이를 통해 인터넷을 통한 액세스없이 VPC와 다른 AWS 서비스 사이에 개인 연결을 만들 수 있다. 또한 Amazon S3 자원에 액세스하기위한 엔드 포인트 사용을 제어하는 정책이 있다.

VPC와 2개의 EC2 설정


다음 단계에서는 VPC 및 두 개의 EC2 인스턴스를 수동으로 만들고 구성하는 과정 이다. 하나는 Squid 배포를위한 공용 서브넷에, 다른 하나는 구성 테스트를위한 개인 서브넷에 있다. 전제 조건이 유사하기 때문에 자세한 내용은 NAT 인스턴스 설명서를 참조.


VPC 및 2개의 EC2 instance를 생성하려면 : 
  1. VPC를 생성한다. (도움이 필요하면 VPC생성 참고).
  2. 2개의 subnet을 생성한다. (Creating a Subnet 참고): 하나는 “Public Subnet” 그리고 다른 하나는and “Private Subnet.”으로 생성.
  3. Internet Gateway를 생성하고 위에 생성한 VPC에 attach 한다. (Attaching an Internet Gateway 참고).
  4. VPC (0.0.0.0/0)  외부로 향하는 트래픽을 인터넷 게이트웨이로 보내는 규칙을 기본 경로 테이블에 추가한다.(Route Table에서 route 추가 및 제거 참조).
  5. VPC default security group에 SSH traffic (TCP 22) 접속 아이피를 규칙에 추가한다. (Security Group에  규칙 추가 참조).
  6. Public Subnet에 "Squid Instance"라는 Amazon Linux t2.micro instance를 시작한다. (NAT instance AMI가 아니라, Amazon Linux AMI를 사용해야 한다). Auto-assign Public IP를 활성화 한다, choose the VPC에  기본 security group을 설정하고, 유효한 key pair파일을 선택한다. 다른 모든 파라미터는 기본값으로 설정한다. (Launching an Instance 참조).
  7. 인스턴스가 실행되기 시작하면 소스 / 대상 확인을 비활성화한다.  (Disable Source/Destination Checks 참고).
  8. VPC에 새 route table을 만듭니다. VPC (0.0.0.0/0)외부로 향하는 트래픽을 Squid 인스턴스에 보내고, 이 새 route table을 Private Subnet과 연결하는 규칙을 추가 한다.
  9. “Testing Instance Role” 라는 인스턴스 역할을 만들고 관리되는 정책 AmazonEC2ReadOnlyAccess를 첨부한다. (detailed instructions).
  10. 마지막으로 Private Subnet에서 "Testing Instance"라는 또 다른 Amazon Linux t2.micro 인스턴스를 시작한다 (NAT 인스턴스 AMI가 아니라 Amazon Linux AMI를 사용해야한다). 인스턴스 역할로 인스턴스 역할 테스트를 선택하고 VPC에  기본 security group을 설정하고, 유효한 key pair파일을 선택한다. 다른 모든 파라미터는 기본값으로 설정한다.
다음 다이어그램은이 프로세스의 구성 요소가 서로 상호 작용하는 방법을 보여준다. Squid 인스턴스는 테스트 인스턴스가 보낸 HTTP / HTTPS 요청을 가로 챈다. 그런 다음 Squid 인스턴스는 인터넷 게이트웨이를 통과하는 테스트 인스턴스를 대신하여 대상 호스트와의 연결을 시작한다.

Image of the components of the process


Squid 설치

Squid는 필터링 정책이 적용되기 전에 요청된 도메인을 차단한다. 
  • HTTP의 경우 Squid는 요청되는 인터넷 호스트를 지정하는 모든 HTTP / 1.1 요청 메시지에 포함 된 Host 헤더 필드를 검색한다.
  • HTTPS의 경우 HTTP 트래픽은 개인 서브넷의 인스턴스와 원격 호스트 사이의 TLS 연결에 캡슐화 된다. Squid는 헤더가 암호화되어 있으므로 Host 헤더 필드를 검색 할 수 없다. SslBump라는 기능은 Squid가 트래픽을 해독하도록 허용하지만 대부분의 경우 인증서가 유효하지 않은 것으로 간주되어 클라이언트에게는 투명하지 않다.  대신 SslPeekAndSplice라고하는 기능을 사용하여 요청한 인터넷 호스트가 포함 된 TLS 초기화에서 서버 이름 표시 (SNI)를 검색 한다.결과적으로 Squid는 HTTPS 트래픽의 암호를 해독하지 않고도 필터링을 결정할 수 있습니다.
참고 : 일부 오래된 클라이언트 측 소프트웨어 인 스택은 SNI를 지원하지 않는다. 다음은 SNI를 지원하는 중요한 스택 및 프로그래밍 언어의 최소 버전 이다 : Python 2.7.9 및 3.2, Java 7 JSSE, wget 1.14, OpenSSL 0.9.8j, cURL 7.18.1SslPeekAndSplice 기능은 Squid 3.5에 도입되었다. 그러나 이 글이 작성되었을 때 Amazon Linux 저장소에는 Squid 3.1.10이 포함되어 있다 (Squid 3.5가 yum info squid 명령을 사용하여 사용 가능한지 확인할 수 있음). 이 게시물의 목적을 위해 공식 소스 코드에서 Squid 3.5를 컴파일하고 설치한다.
참고 :이 Squid 설치에는이 예제에 필요한 최소 기능이 포함되어 있으며 프로덕션 용도로는 사용되지 않습니다. 자신의 필요에 맞게 수정하고 자신의 Red Hat Package Manager (RPM) package 또는 비공식 RPM packages for CentOS 6 Squid를 설치할 수 있다.

Squid 설치
Squid 인스턴스에서 Squid를 수동으로 컴파일, 설치 및 구성하려면:

  1. Squid instance에 SSH를 이용하여 ec2-user 로 접속한다.
  2. 필수 구성요소 package들을 설치.
sudo yum update -y
sudo yum install -y perl gcc autoconf automake make sudo wget gcc-c++    libxml2-devel libcap-devel libtool libtool-ltdl-devel openssl openssl-devel
  1. Squid page로 이동하여 최신 릴리스 (이 게시물이 작성된 마지막 릴리스 인 3.5.13)의 tar.gz 소스 코드 아카이브에 대한 링크를 검색하라. 이 링크를 사용하여 Squid 인스턴스에서 아카이브를 다운로드하고 압축을 푼다.
SQUID_ARCHIVE=http://www.squid-cache.org/Versions/v3/3.5/squid-3.5.13.tar.gz
cd /tmp
wget $SQUID_ARCHIVE
tar xvf squid*.tar.gz
cd $(basename squid*.tar.gz .tar.gz)
  1. 최소 설치 옵션으로 Squid 를 설치한다. 최대 15분이 소요될수 있다.
sudo ./configure --prefix=/usr --exec-prefix=/usr --libexecdir=/usr/lib64/squid --sysconfdir=/etc/squid --sharedstatedir=/var/lib --localstatedir=/var --libdir=/usr/lib64 --datadir=/usr/share/squid --with-logdir=/var/log/squid --with-pidfile=/var/run/squid.pid --with-default-user=squid --disable-dependency-tracking --enable-linux-netfilter --with-openssl --without-nettle

sudo make
sudo make install
  1. Squid 설치를 완료한다.
sudo adduser -M squid
sudo chown -R squid:squid /var/log/squid /var/cache/squid
sudo chmod 750 /var/log/squid /var/cache/squid
sudo touch /etc/squid/squid.conf
sudo chown -R root:squid /etc/squid/squid.conf
sudo chmod 640 /etc/squid/squid.conf
cat | sudo tee /etc/init.d/squid <<'EOF'
#!/bin/sh
# chkconfig: - 90 25
echo -n 'Squid service'
case "$1" in
start)
/usr/sbin/squid
;;
stop)
/usr/sbin/squid -k shutdown
;;
reload)
/usr/sbin/squid -k reconfigure
;;
*)
echo "Usage: `basename $0` {start|stop|reload}"
;;
esac
EOF
sudo chmod +x /etc/init.d/squid
sudo chkconfig squid on
참고 : RPM 패키지에서 Squid를 설치 한 경우 Squid 인스턴스에 이미 필요한 구성이 있으므로 다음 단계를 진행하기 전에 Squid 설치에 대한 이전 지침을 따르지 않아도 된다.

Squid 시작과 구성

TSslPeekAndSplice 기능은 SslBump와 동일한 Squid 모듈에 구현 된다. 이 모듈을 사용하려면 Squid가 인증서를 제공해야하지만 HTTPS 트래픽을 디코딩하는 데는 사용되지 않는다. OpenSSL을 사용하여 인증서를 만든다.
sudo mkdir /etc/squid/ssl
cd /etc/squid/ssl
sudo openssl genrsa -out squid.key 2048
sudo openssl req -new -key squid.key -out squid.csr -subj "/C=XX/ST=XX/L=squid/O=squid/CN=squid"
sudo openssl x509 -req -days 3650 -in squid.csr -signkey squid.key -out squid.crt
sudo cat squid.key squid.crt | sudo tee squid.pem
그런 다음 AWS 종단점에 해당하는 *.amazonaws.com에 대한 요청을 허용하도록 Squid를 구성한다.정의 된 AWS 서비스 집합에만 액세스를 제한 할 수 있다. EndPoint의 자세한 목록은 AWS Regions 과 Endpoints를 참고.
HTTPS traffic의 경우 Squid에게 "peek"(SNI 검색)을 지시 한 다음 "splice"(디코딩없이 TCP 터널이 됨) 또는 요청 된 호스트에 따라 연결을 "종료"하도록 지시하는 ssl_bump 지시문에 유의해야 한다.
cat | sudo tee /etc/squid/squid.conf <<EOF
visible_hostname squid

#Handling HTTP requests
http_port 3129 intercept
acl allowed_http_sites dstdomain .amazonaws.com
#acl allowed_http_sites dstdomain [you can add other domains to permit]
http_access allow allowed_http_sites

#Handling HTTPS requests
https_port 3130 cert=/etc/squid/ssl/squid.pem ssl-bump intercept
acl SSL_port port 443
http_access allow SSL_port
acl allowed_https_sites ssl::server_name .amazonaws.com
#acl allowed_https_sites ssl::server_name [you can add other domains to permit]
acl step1 at_step SslBump1
acl step2 at_step SslBump2
acl step3 at_step SslBump3
ssl_bump peek step1 all
ssl_bump peek step2 allowed_https_sites
ssl_bump splice step3 allowed_https_sites
ssl_bump terminate step2 all

http_access deny all
EOF
Squid 는 포트 3129에서 HTTP 트래픽을 수신하고 HTTPS에서 3130을 수신한다. Squid는 80과 443을 직접들을 수 없기 때문에 iptables을 사용하여 private subnet의 인스턴스에서 들어오는 요청을 Squid 포트로 redirect 해야 한다. 표준 NAT 인스턴스처럼 IP 전달을 활성화하거나 앞으로 규칙을 추가 할 필요가 없다.

Squid는 포트 3129에서 HTTP 트래픽을 수신하고 HTTPS에서 3130을 수신합니다. Squid는 80과 443을 직접들을 수 없기 때문에 iptables를 사용하여 개인 서브넷의 인스턴스에서 들어오는 요청을 Squid 포트로 리디렉션 해야합니다. 표준 NAT 인스턴스처럼 IP 전달을 활성화하거나 앞으로 규칙을 추가 할 필요가 없습니다.
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3129
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 3130
sudo service iptables save
Squid를 실행한다.
sudo service squid start

배포 테스트

이전에 시작된 테스트 인스턴스를 사용하여 구성을 테스트 할 수 있다. 이 인스턴스는 인터넷에서 액세스 할 수 없으므로 Squid 인스턴스로 이동하여 테스팅 인스턴스에 로그온 해야한다.

두 인스턴스가 같은 키 쌍으로 시작되었으므로 SSH 키를 테스트 인스턴스로 전달하기 위해 -A 인수를 사용하여 Squid 인스턴스에 연결할 수 있다.
ssh-add [key]
ssh -A ec2-user@[public IP of the Squid instance] –i [key]
ssh ec2-user@[private IP of the client instance] –i [key]
다음 명령을 사용하여 proxy instance를 테스트 할 수 있다. Squid는 *.amazonaws.com만 트래픽을 허용하므로 마지막 세개의 요청만 유효한 응답을 반환해야 한다.

curl http://www.amazon.com
curl https://www.amazon.com
curl http://calculator.s3.amazonaws.com/index.html
curl https://calculator.s3.amazonaws.com/index.html
aws ec2 describe-regions --region us-east-1

이제 squid를 이용하여 아웃바운드를 제어할 수 있다.





참조 : AWS blog https://aws.amazon.com/ko/blogs/security/how-to-add-dns-filtering-to-your-nat-instance-with-squid/


AWS Redis와 Tomcat Session Clustering

근 두달만에 글을 쓰는것 같다 블로그에 글을 올리는게 쉽지 않다는 생각을 해본다. 오늘은 Redis를 이용하여 Tomcat Session Clustering하는 방법을 알아보고자한다. 앞에서 작성했던 AWS DynamoDB를 이용하여 Sessi...