เขียน service เพื่อ update AWS security rule ด้วย python
วันนี้ได้เคส มาเคสนึงครับ... คือลูกค้า(หรือเพื่อน)ต้องการจะให้ service นึงที่อยู่ที่บ้าน connect ไปยัง AWS RDS โดยไม่ใช้ VPN
วันนี้ได้เคส มาเคสนึงครับ...
คือลูกค้า(หรือเพื่อน)ต้องการจะให้ service นึงที่อยู่ที่บ้าน connect ไปยัง AWS RDS โดยไม่ใช้ VPN และไม่อยาก deploy service บน ec2 เหตุผลคือไม่อยากจ่ายค่า EC2 สำหรับ service และ ค่า VPN bandwidth. อยากจะ connect ไปตรงๆเลย ผ่าน endpoint DB_NAME.RANDOM_STRING.ap-southeast-1.rds.amazonaws.com แต่ปัญหาคือเขาไม่ได้ซื้อ fix ip จาก ISP
เราก็เตือนไปตามเรื่องว่างั้นเราจะเข้าผ่าน public access และกำหนด security group นะซึ่งเขาก็ไม่ค่อย recommend กันนะ
แต่เงินมางานก็ต้องเดิน จัดไป
Python with some lib
หลังจากลองคิด scenario คร่าวๆแล้ว ด้วยความที่ถนัด python (ถนัดที่ไม่ได้แปลว่าเก่ง แต่พอใช้เอาตัวรอดได้ ด้วยความช่วยเหลือจาก google xD) เป็นภาษาเดียวและอยากจบด้วยภาษาเดียว เลยเริ่มจากตรงนั้น ซึ่งด้วยความที่เป็นภาษาที่เป็นที่นิยม เลยมี lib ของ AWS อยู่แล้ว ชื่อ Boto3, request อีกตัวที่เราจะใช้คือ datetime (จะเอามาใช้เป็น timestamp)
Howto
คิด scenario ขึ้นมาก่อน โดยผมคิดประมาณนี้ โดยผมขออนุญาตไม่แปลไทยนะครับ หลักๆ ก็คือไปอ่าน ip ใน s3 bucket แล้วเช็คว่าตรงกับปัจจุบันหรือไม่ ถ้าไม่ตรง ให้อัพเดต แล้วเอา current ip ไปเขียนทับบน s3
Start
|-> Import necessary libraries
|-> Get current time
|-> Get AWS credentials
|-> Get security group ID
|-> Get region name
|-> Get S3 bucket name
|-> Get S3 key name
|-> Get old IP from S3
|-> Get current IP from public IP API
|-> If old IP is not equal to current IP:
|-> Revoke security group ingress rule for old IP
|-> Authorize security group ingress rule for current IP
|-> Put current IP to S3
|-> Print success message
|-> Else:
|-> Print no update needed message
Endfile ip.txt ของผมก็มีแค่ไอพีบอกแบบนี้

และนี่คือโค้ดที่ผมเขียน
import boto3
import requests
import datetime
now = datetime.datetime.now()
aws_access_key_id = '1111111111111111111111111'
aws_secret_access_key = '2222222222222222222222222222'
group_id = 'sg-333333333333333333333333'
region = 'ap-southeast-1'
ec2 = boto3.client('ec2', aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, region_name=region)
s3 = boto3.client('s3', aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, region_name=region)
bucket = 'my_bucket'
key = 'path/ip.txt'
# source s3://my_bucket/path/ip.txt
if s3.get_object(Bucket=bucket, Key=key)['Body'].read().decode() == '':
old_ip = ''
else:
old_ip = old_ip = s3.get_object(Bucket=bucket, Key=key)['Body'].read().decode().strip()
current_ip = requests.get('https://api.ipify.org').text
if old_ip != current_ip:
ec2.revoke_security_group_ingress(
GroupId=group_id,
IpProtocol='tcp',
FromPort=3306,
ToPort=3306,
CidrIp=f'{old_ip}/32'
)
ec2.authorize_security_group_ingress(
GroupId=group_id,
IpProtocol='tcp',
FromPort=3306,
ToPort=3306,
CidrIp=f'{current_ip}/32'
)
s3.put_object(Bucket=bucket, Key=key, Body=current_ip)
print(f"timestamp: {now} - Update IP from {old_ip} to {current_ip} successful")
else:
print(f"timestamp: {now} - IP has not changed, no update needed")โดยระหว่างทางเจอ Error อยู่บ้างเช่นรับค่า old_ip มา แต่มันไม่ได้อยู่ในบรรทัดเดียวกัน เช่น แทนที่จะออกมาเป็น 10.10.10.145/32 มันดันออกมาเป็น
10.10.10.145
/32ซึ่งก็แก้ได้หลายวิธี แต่ผมแก้โดยเพิ่มเมธอด ".strip()" ตอน get s3 obj เพื่อลบ newline
หรือปัญหาการหา security group ไม่เจอเพราะเราไม่ได้ define region ซึ่งเราก็แก้ได้โดยการ define region ลงไปด้วย
ประมาณนี้

จากนั้นผมนำชุดโค้ดนี้เป็นสร้างเป็น docker container ให้มันทำงานทุก 5 นาที
#Dockerfile
FROM python:3.7-alpine
COPY main.py .
COPY requirements.txt ./
RUN pip install -r requirements.txt
CMD while true; do \
python main.py; \
sleep 300; \
doneก็ Build > run ไปตามเรื่อง
การทำงานของ service ก็ง่ายๆประมาณนี้ครับ (รูปนี้จาก service version เก่า ไม่มี timestamp)

Summary
ในที่สุด service ที่ออฟฟิศเราก็สามารถ connect เข้าถึง AWS RDS ได้แล้ว เพราะเจ้า service ตัวเล็กๆตัวนี้จะคอยอัพเดต IP ของเราใน Security rule
จบไปกับการ automate งานเล็กๆที่คอยรบกวนเรา แทนที่เราจะไปคอยอัพเดตไอพีเอง ก็ให้มันทำงานเองซะ อย่างไรก็ตาม code จริงๆ ผมจะเก็บ token หรือ password ต่างๆ ไว้ใน docker secret หรือ env file นะครับ ไม่ควรเอาไปใช้แบบนี้เลย