AWS Config マネージドルールの CloudFormation をスクレイピングする

AWS

2022.6.6

Topics

はじめに

AWS Config にはマネージドルールがあります。

AWS Config は、AWS マネージドルールを提供します。マネージドルールは、定義済みのカスタマイズ可能なルールであり、AWS リソースが一般的なベストプラクティスに準拠しているかどうかを評価するために AWS Config で使用します。
AWS Config マネージドルール – AWS Config

一つ一つコンソールで設定しても良いですが、大変なので CloudFormation を使うと楽になります。

更に便利な点として、Config マネージドルールの CloudFormation テンプレートを AWS が公開しています。
そちらを利用すれば、1 から CloudFormation テンプレートを書かなくて済みます。

Amazon S3 URL を選択し、テンプレート URL http://s3.amazonaws.com/aws-configservice-us-east-1/cloudformation-templates-for-managed-rules/THE_RULE_IDENTIFIER.template を入力します。
AWS CloudFormation テンプレートを使用した AWS Config マネージドルールの作成 – AWS Config

本記事では、その AWS が公開している CloudFormation テンプレートをスクレイピングする内容となっております。

スクレイピングするメリット

コンソールや CloudFormation で AWS のテンプレートを使ってマネージドルールを設定しても良いですが、ひとつ問題点があります。

「ルールを一つづつしか設定できない」ということです。
(Config 有効時に設定する場合はその限りではないです)

現在、Config のマネージドルールは 200 個を超えております。
さらに、Config は複数リージョンや複数アカウントに適用する場合が多いため、CloudFormation でまとめて設定しておくと導入の時間削減になります。

そのため、200 個を超えるマネージドルールの CloudFormation テンプレートダウンロード作業を短縮するためスクレイピング用いて自動化します。

マネージドルールの CloudFormation をスクレイピングする

バージョン

c:\Users>python -V
Python 3.9.5

ディレクトリ

Sampleディレクトリに、スクレイピングした CloudFormation(以下、CFn)テンプレートが格納されます。

C:.
│
├─Doc
│  └─Sample
│
└─Src
   └─main.py

プログラム実行後のディレクトリ構造

C:.
│  README.md
│
├─Doc
│  └─Sample
│          access-keys-rotated.yaml
│          account-part-of-organizations.yaml
│          acm-certificate-expiration-check.yaml
│          alb-desync-mode-check.yaml
│          alb-http-drop-invalid-header-enabled.yaml
│          alb-http-to-https-redirection-check.yaml
│          alb-waf-enabled.yaml
│          api-gw-associated-with-waf.yaml
│          api-gw-cache-enabled-and-encrypted.yaml
│          api-gw-endpoint-type-check.yaml
│
└─Src
        main.py

プログラムコード

import glob
import logging
import os
import re
import requests
from bs4 import BeautifulSoup
from cfn_flip import flip, to_yaml, to_json
from logging import getLogger
import sys

logger = getLogger(__name__)


def ScrapingAWSDocument():
    logger.debug(sys._getframe().f_code.co_name)
    re_delete_indent = re.compile(r'\n +')
    base_url = "https://docs.aws.amazon.com/config/latest/developerguide/"
    target_url = "https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html"
    r = requests.get(target_url)
    r.encoding = 'utf-8'
    soup = BeautifulSoup(r.text, 'lxml')
    link_list = soup.find(id="main-col-body").find_all("li")
    rules = []
    for child in link_list:
        child_url = base_url + child.find("a").get('href')[2:]
        r2 = requests.get(child_url)
        r2.encoding = 'utf-8'
        soup2 = BeautifulSoup(r2.text, 'lxml')
        detail = soup2.find(id="main-col-body").p.text.strip()
        Identifier = soup2.find(id="main-col-body").b.parent.text.strip()
        detail = re_delete_indent.sub(' ', detail)
        logger.info(MoldingIdentifier(Identifier))
        rules.append(
            {
                'rule_name': soup2.h1.text,
                'rule_detail': detail,
                'url': child_url,
                'Identifier': MoldingIdentifier(Identifier)
            }
        )
    return rules


def MoldingIdentifier(data):
    logger.debug(sys._getframe().f_code.co_name)
    return data[data.rfind(":")+1:].strip()


def ScriptAWSConfigRule(Identifier):
    logger.debug(sys._getframe().f_code.co_name)
    target_url = f"http://s3.amazonaws.com/aws-configservice-us-east-1/cloudformation-templates-for-managed-rules/{Identifier}.template"
    r = requests.get(target_url)
    r.encoding = 'utf-8'
    return r.text


def ConvertJSONToYAML(json_text):
    logger.debug(sys._getframe().f_code.co_name)
    return to_yaml(to_json(json_text))


def cd():
    logger.debug(sys._getframe().f_code.co_name)
    os.chdir(os.path.dirname(__file__))


def main():
    logger.debug(sys._getframe().f_code.co_name)
    rules = ScrapingAWSDocument()
    for i in rules:
        json = ScriptAWSConfigRule(i["Identifier"])
        yaml = ConvertJSONToYAML(json)
        OutPutYamlRules(yaml, i["rule_name"])


def OutPutYamlRules(yaml, rule_name):
    logger.debug(sys._getframe().f_code.co_name)
    logger.info('output file' + rule_name)
    with open(f"../Doc/Sample/{rule_name}.yaml", mode="w", encoding="utf-8") as target:
        target.writelines(yaml)


def ResultCheck():
    logger.debug(sys._getframe().f_code.co_name)
    files = glob.glob("../Doc/Sample/*")
    print("CFn File Count "+str(len(files)))


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    cd()
    logger.debug(sys._getframe().f_code.co_name)
    main()
    ResultCheck()

解説

マネージドルールの一覧を取得する

今回は、マネージドルールで定義してあるものすべての CFn を取得するのでまずはマネージドルールの一覧情報をスクレイピングします。
現時点(2022/06/02)で実行すると 276 個のファイルが作成されます。

def ScrapingAWSDocument():
    logger.debug(sys._getframe().f_code.co_name)
    re_delete_indent = re.compile(r'\n +')
    base_url = "https://docs.aws.amazon.com/config/latest/developerguide/"
    target_url = "https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html"
    r = requests.get(target_url)
    r.encoding = 'utf-8'
    soup = BeautifulSoup(r.text, 'lxml')
    link_list = soup.find(id="main-col-body").find_all("li")
    rules = []
    for child in link_list:
        child_url = base_url + child.find("a").get('href')[2:]
        r2 = requests.get(child_url)
        r2.encoding = 'utf-8'
        soup2 = BeautifulSoup(r2.text, 'lxml')
        detail = soup2.find(id="main-col-body").p.text.strip()
        Identifier = soup2.find(id="main-col-body").b.parent.text.strip()
        detail = re_delete_indent.sub(' ', detail)
        logger.info(MoldingIdentifier(Identifier))
        rules.append(
            {
                'rule_name': soup2.h1.text,
                'rule_detail': detail,
                'url': child_url,
                'Identifier': MoldingIdentifier(Identifier)
            }
        )
    return rules

def MoldingIdentifier(data):
    logger.debug(sys._getframe().f_code.co_name)
    return data[data.rfind(":")+1:].strip()
コード行番号 解説
4 マネージドルールの詳細があるページです、これにルール名前を URL に追加してスクレイピングします
5 マネージドルールの一覧があるページです、マネージドルール一覧をスクレイピングするために使用します
12 マネージドルールの名前を取得して URL を結合します
25 マネージドルールの識別子を取得して格納します
32 マネージドルールの詳細ページの識別子のみ取得します

target_urlを、英語ページにしています。
理由としては、日本語ページだとルール名や識別子のフォーマットにばらつきが多いためです。

CloudFormation テンプレートを取得する

マネージドルールの識別子(Identifier)を対象 URL に変換してスクレイピングします。
テンプレートのページはシンプルなので抽出処理などは不要です。

サンプルページ:ACCESS_KEYS_ROTATED

def ScriptAWSConfigRule(Identifier):
    logger.debug(sys._getframe().f_code.co_name)
    target_url = f"http://s3.amazonaws.com/aws-configservice-us-east-1/cloudformation-templates-for-managed-rules/{Identifier}.template"
    r = requests.get(target_url)
    r.encoding = 'utf-8'
    return r.text

JSON 形式から YAML 形式に変換

JSON 形式が良い方は、本関数を削除して下さい。
合わせてファイル保存時の拡張子も変更してください。

YAML 形式のほうが読みやすいので変換処理を実施しています。

cfn-flip · PyPI

def ConvertJSONToYAML(json_text):
    logger.debug(sys._getframe().f_code.co_name)
    return to_yaml(to_json(json_text))

CFn ファイルを一つにまとめる場合は JSON 形式のまま結合処理を実施してから YAML 形式に変換する方が楽だと思います。

まとめ

手作業でやるのは大変なので誰かの参考になれば幸いです。

Cold-Airflow

2021年新卒入社。インフラエンジニアです。RDBが三度の飯より好きです。 主にデータベースやAWSのサーバレスについて書く予定です。あと寒いのは苦手です。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら