Network Firewall의 AWS 관리형 규칙 그룹을 규칙 그룹으로 복사해주는 람다 작성

2023.03.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

안녕하세요, 임채정입니다.
Network Firewall 은 네트워크 트래픽을 세부적으로 제어하는 방화벽 규칙을 정의할 수 있는 서비스입니다.
이번 블로그에서는 Network Firewall 의 규칙 그룹에 대한 내용을 적으려고 합니다.

아젠다

  1. 배경 설명
  2. AWS 관리형 규칙 그룹 복제하기
  3. 복제한 규칙 그룹 자동 업데이트 하는 법
  4. 결과 확인

1. 배경 설명

Network Firewall 은 네트워크 트래픽을 세부적으로 제어하는 방화벽 규칙을 정의할 수 있는 서비스입니다.
Network Firewall에는 AWS에서 관리해주는 규칙 그룹인 AWS 관리형 규칙 그룹(managed rule group) 있습니다.
AWS 관리형 규칙 그룹 을 사용하면 사용자가 따로 규칙 그룹을 만들지 않아도 AWS에 관리해주고 업데이트해주는 규칙 로 네트워크 트래픽을 검사할 수 있습니다.

하지만 특정 상황에서는 AWS 관리형 규칙 그룹를 사용못하는 경우가 있습니다.
이런 경우에 꼭 AWS 관리형 규칙 그룹울 사용하고 싶다면 그 내용을 복제해서 그냥 규칙 그룹 으로 사용할 수 있습니다.
단, AWS 관리형 규칙 그룹은 두 가지로 나눠져 있는데 도메인 및 IP 규칙 그룹는 복제할 수 없기 때문에 사용할 수 없습니다. (위협 서명 규칙 그룹만 가능)

하지만 복제해서 사용하면 AWS 관리형 규칙 그룹이 업데이트 했을 때 복제한 규칙 그룹까지 업데이트 해주지는 않습니다.
이럴 때에는 람다함수를 통해서 업데이트 해줄 수 있는데 그 내용에 대해서 정리해보겠습니다.

2. AWS 관리형 규칙 그룹 복제하기

먼저 규칙 그룹을 복제해보겠습니다.

VPC 페이지에서 네트워크 방화벽의 네트워크 방화벽 규칙 그룹 메뉴에 들어갑니다.
네트워크 방화벽 규칙 그룹에서 규칙그룹이 사용자가 정의한 규칙 그룹이고, AWS 관리형 규칙 그룹 탭에 AWS에서 관리하고 있는 규칙 그룹입니다.

지금은 AWS 관리형 규칙 그룹 탭에 들어가서 「위협 서명 규칙 그룹」의 복제하고 싶은 규칙을 선택합니다.

그럼 화면의 오른쪽 위의 규칙 그룹 복제 버튼을 클릭합니다.

그러면 복제 페이지가 나오는데 여기에서 규칙 변수나 IP 세트 참조와 같은 원하는 세부 사항을 추가할 수도 있습니다.
저는 테스트를 위해 이름만 설정해주고 생성해주겠습니다.
이름은 구분을 위해 ThreatSignaturesBotnetActionOrder-copy로 설정했습니다.

그러면 다음과 같이 복제된 규칙 그룹을 확인할 수 있습니다.

블로그에서는 테스트를 위해 여러 규칙들을 복제했습니다.

3. 복제한 규칙 그룹 자동 업데이트 하는 법

이번에는 업데이트를 해보겠습니다.
업데이트 방법은 다음과 같습니다.

AWS 관리형 규칙 그룹이 업데이트 -> SNS를 통해 통지 -> Lambda로 규칙 그룹 업데이트 -> 규칙 그룹도 업데이트

람다 생성해두기

먼저 람다는 하나 만들겠습니다.
함수명은 원하는 이름으로 적고, 런타임은 파이썬으로 작성하고 싶기때문에 파이썬을 선택하겠습니다.

그러면 다음과 같이 람다 함수가 생성되는데 그중에서 함수 ARN이 필요하기 때문에 어딘가에 복사해둡니다

SNS를 통해 통지

다음으로는 AWS 관리형 규칙 그룹이 업데이트되었을 때 통지를 하기 위해서 SNS구독 먼저 만들어 보겠습니다.

중요한건 AWS 관리형 규칙 그룹의 업데이트 통지를 받기 위해서는 버지니아 북부 리전(us-east-1)으로 이동해줘야 합니다.

VPC의 네트워크 방화벽 규칙 그룹 메뉴에서 AWS 관리형 규칙 그룹(위협 서명 규칙 그룹)에서 아무거나 클릭합니다.

규칙 그룹의 Amazon SNS 주제ARN을 복사해둡니다.
그리고 ARN 을 클릭해서 구독 생성 페이지로 이동합니다.

세부 정보에 각각의 정보를 입력해줍니다.
주제 ARN : 위에서 복사한 Amazon SNS 주제ARN
프로토콜 : AWS Lambda
엔드포인트 : 위에서 복사한 람사함수의 ARN

입력을 다 했으면 구독을 생성합니다.
다음과 같이 SNS구독이 되었다는 걸 확인할 수 있습니다.

람다 함수에서도 트리거로 SNS가 추가된 것을 확인 할 수 있습니다.

Lambda로 규칙 그룹 업데이트

이제는 Lambda를 본격적으로 만져봅시다.
코드를 작성하기 전에 권한 등의 필요한 설정을 몇가지 해주겠습니다.

먼저 구성권한 탭에서 실행 역할의 역할 이름을 클릭해줍니다.

그럼 람다에 할당된 IAM의 역할 페이지가 나오고 여기서 권한추가를 누르고 정책 연결을 해줍니다.

추가해주는 역할은 다음과 같습니다.

필요한 정책만을 추가한 것으로 따로 만들어야 하는데 정책의 JSON 코드는 다음과 같습니다.
추가한 역할은 RuleGroup을 업데이트하고 표시(Describe)하는 것만 추가했습니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "network-firewall:UpdateRuleGroup",
                "network-firewall:DescribeRuleGroup"
            ],
            "Resource": [
                "arn:aws:network-firewall:ap-northeast-1:111111111111:stateful-rulegroup/*"
            ]
        }
    ]
}

다음에는 구성일반 구성 탭에서 편집을 클릭합니다.

제한 시간을 8초로 늘려주고 저장을 합니다.

이제 람다 함수 코드를 수정해주겠습니다.

import json
import boto3

client = boto3.client('network-firewall')

def lambda_handler(event, context):
    
    ## 들어오는 SNS 이벤트 확인
    print(event)
    
    ## 업데이트된 규칙 그룹 확인
    ManegerdRuleArn = event["Records"][0]["Sns"]["MessageAttributes"]["managed_arn"]["Value"].split('/')
    ManegerdRuleName = ManegerdRuleArn[1]
    print(ManegerdRuleName)
    
    ManegerdRuleRes = client.describe_rule_group(
    RuleGroupArn='arn:aws:network-firewall:ap-northeast-1:aws-managed:stateful-rulegroup/' + ManegerdRuleName,
    Type='STATEFUL'
    )
    
    MyRuleName = ManegerdRuleName + '-copy'
    
    ## 업데이트 할 규칙 그룹 정보 취득
    ## 만약 해당 그룹의 이름이 없을 경우 업데이트하지 않고 Skip 표시
    try:
        MyRuleRes = client.describe_rule_group(
        RuleGroupName=MyRuleName,
        Type='STATEFUL'
        )
    except:
        print("Skip")
        return "Skip Updates"
    
    RuleGroupName = MyRuleRes['RuleGroupResponse']['RuleGroupName']
    RulesString = ManegerdRuleRes["RuleGroup"]["RulesSource"]["RulesString"]
    UpdateToken = MyRuleRes["UpdateToken"]
    RuleGroupArn = MyRuleRes["RuleGroupResponse"]["RuleGroupArn"]
    
    ## 업데이트 실행
    ## 성공하면 True / 실패하면 False
    result = update(RuleGroupName, RulesString, UpdateToken, RuleGroupArn)   
    
    return result
    
    
def update(RuleGroupName, RulesString, UpdateToken, RuleGroupArn):
    
    try:
        response = client.update_rule_group(
        UpdateToken=UpdateToken,
        RuleGroupArn=RuleGroupArn,
        RuleGroupName=RuleGroupName,
        RuleGroup={
            'RulesSource': {
                'RulesString': '#test4\n' + RulesString
                }
            }
        )
        print("Success")
        return True
    except:
        return False

Deploy를 해줍니다.

4. 결과 확인

이번에는 결과를 확인해보겠습니다.
모니터링 탭에서 View CloudWatch logs 버튼을 클릭합니다.

로그스트림이 나오는데 아무거나 클릭해서 확인해보겠습니다.

성공한 경우

성공한 경우에는 이벤트를 로그로 출력하고 거기서 취득한 업데이트된 규칙 그룹의 이름을 가져옵니다.
해당 규칙 그룹은 복제해둬서 업데이트를 해야되기 때문에 업데이트를 실행합니다.
결과적으로 설공해서 Success 를 로그로 출력하고 람다 함수를 종료합니다.

스킵한 경우

성공한 경우와 같이 이벤트가 로그로 축력되고 해당 규칙 그룹의 이름이 로그로 출력됩니다.
이번 규칙 그룹은 복제해두지 않았기 때문에 람다가 업데이트를 실행할 필요가 없습니다.
그렇기 때문에 로그에 Skip을 출력하고 람다 함수를 종료합니다.