Slack으로 Cloudfront 무효화 프로세스 간소화

Glen·2023년 9월 11일
0
post-thumbnail

상황

  • EKS, ArgoCD로 Web(front) 배포가 진행되고있다.
  • web이 실행되고있는 pod 내부에 이미지 파일들이 존재.
  • 배포 될때마다 수동으로 cloudfront에서 무효화 필요

목적

  • 편리함을 최대화 하자

단계

  • ECR이 업로드 되면 slack으로 noti
  • noti에는 버튼이 존재
  • 버튼을 클릭하면 무효화 실행

아키텍처

step1. github action으로 ecr에 image가 업로드됨
step2. cloudtrail 로그 탐지하여 lambda 실행되어 slack noti
step3. slack noti의 버튼을 클릭하면 api gateway를 호출함
step4. api gateway에 연결되어있는 lambda가 실행됨    

Slack

  • 앱 추가로 수신 웹훅을 사용하면 보낸 noti의 버튼 요청을 설정하지 못한다.

  • 아래 링크에서 별도로 앱 생성해야한다

  • https://api.slack.com/apps?new_app=1

  • 생성한 앱에서 웹훅을 추가

  • 해당 앱으로 noti가 발생했고 버튼을 눌렀을때, 어떤동작을 할지 아래 부분에 추가해준다.

lambda

  1. eventbridge로 lambda 실행해서 slack noti
  • slack 메세지를 보낼때 callback_id를 함께 보낸다.
  • 메세지에 버튼도 추가함
import json
import boto3
import os
import urllib3
import logging

# logger setting
logger = logging.getLogger()
logger.setLevel(logging.INFO)

SLACK_CHANNEL = os.environ['slack_channel']
HOOK_URL = os.environ['hook_url']

http = urllib3.PoolManager()

def send_message(message):
    
    data = json.dumps(message).encode('utf-8')

    res = http.request(
        method='POST',
        url=HOOK_URL,
        body=data
    )

    print(res.data, res.status)

def lambda_handler(event, context):
    logger.info(json.dumps(event))
    iam_event = json.loads(event.get('Records')[0].get('Sns').get('Message'))
    # Check if it's an ECR image upload event
    if iam_event['detail']['eventName'] == 'PutImage' and ('<ECR image_name>' in iam_event['detail']['requestParameters']['imageTag']) :
        # Send notification to Slack with a button to invalidate CloudFront
        slack_message = {
            "channel":SLACK_CHANNEL,
            "text": "New ECR image uploaded!",
            "attachments": [
                {
                    "text": "Invalidate CloudFront?",
                    "callback_id": "<distribution-id>",
                    "actions": [
                        {
                            "name": "invalidate_cloudfront",
                            "text": "Invalidate CloudFront",
                            "type": "button",
                        }
                    ]
                }
            ]
        }

        send_message(slack_message)
  1. slack button 클릭했을때 lambda 실행해서 무효화
  • 혹시나 무효화하는 api gateway가 노출되었을때를 방지하기 위해, slack에서 생성된 값들을 확인하는 코드를 추가함.
    - 확인해보지 않았지만 ip range가 존재한다면 추가하는방법도 괜찮을듯.
  • distribution id를 앞선 lambda의 callback_id로 받아 사용하는데, 혹시 변수를 악용해서 계정 내 다른 CDN의 무효화를 시도할 수 있으니 검증단계 추가.
import json
import boto3
import os
import logging
import urllib3
import time

team_id = os.environ['team_id']
channel_id = os.environ['channel_id']
token = os.environ['token']
check_id = os.environ['distribution']
error_message = "Access denied"

cloudfront = boto3.client('cloudfront')
# logger setting
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info(json.dumps(event))
    
    distribution_id = event['callback_id']
    if check_id != distribution_id:
        return {
            "statusCode": 403,
            "body": error_message
        }
    if team_id != event['team']['id']:
        return {
            "statusCode": 403,
            "body": error_message
        }
    if channel_id != event['channel']['id']:
        return {
            "statusCode": 403,
            "body": error_message
        }
    if token != event['token']:
        return {
            "statusCode": 403,
            "body": error_message
        }

    try:
        response = cloudfront.create_invalidation(
            DistributionId=distribution_id,
            InvalidationBatch={
                'Paths': {
                    'Quantity': 1,
                    'Items': [
                        '/*'
                    ]
                },
                "CallerReference": str(time.time()).replace(".", ""),
            }
        )
        #print(response)
    except Exception as e:
        print(f"Failed to invalidate CloudFront: {str(e)}")
        return {
            "statusCode": 500,
            "body": "Failed to invalidate CloudFront"
        }

    return { "statusCode": 200,
            "body": "invalidation initiated successfully!" }

실행

  • Github action 완료

  • ECR image upload를 탐지하여 Slack으로 noti

  • 버튼 클릭시 무효화 실행하고 return값으로 noti값이 편집됨

profile
어제보다 더 나은 엔지니어가 되자

0개의 댓글