เขียน service เพื่อ update AWS security rule ด้วย python

วันนี้ได้เคส มาเคสนึงครับ... คือลูกค้า(หรือเพื่อน)ต้องการจะให้ service นึงที่อยู่ที่บ้าน connect ไปยัง AWS RDS โดยไม่ใช้ VPN

เขียน service เพื่อ update AWS security rule ด้วย python
Photo by Growtika / Unsplash

วันนี้ได้เคส มาเคสนึงครับ...

คือลูกค้า(หรือเพื่อน)ต้องการจะให้ 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

End

file 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 นะครับ ไม่ควรเอาไปใช้แบบนี้เลย

Read more

Restore MySQL DB ด้วย Binlog file

Restore MySQL DB ด้วย Binlog file

สวัสดีครับ วันนี้พลาดครับ เรื่องของเรื่องคือผมจะกลับมาเขียน blog เลยมาเช็ต blog ที่ผมรันไว้ ซึ่งผมรัน Ghost CMS blog บน docker แต่ดั๊นใช้ tag <:latest> ซึ่งใน compose file มีทั้ง ghost และ mysql ด้วยความที่รันไว้นานแล้ว และไม่ได้บันทึกอะไรไว้ มาถึงผมก็จัดแจง restart .... ไม่ขึ้นครับ

By Akachai Bunsorn
วันนี้เรามาลอง AWX: Ansible but web GUI

วันนี้เรามาลอง AWX: Ansible but web GUI

สวัสดีครับ วันนี้โจทย์คือทำยังไงให้ L1 reset Mattermost password ให้พนักงานได้ ซึ่งปกติแล้วจะต้องให้คนที่สามารถเข้าถึง Admin account ได้รีเซ็ตให้เท่านั้นซึ่ง user ลืมกันบ่อย (555555 😭) เลย come up with AWX ครับ

By Akachai Bunsorn